aboutsummaryrefslogtreecommitdiff
path: root/src/lib/fs/path.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/fs/path.c')
-rw-r--r--src/lib/fs/path.c289
1 files changed, 289 insertions, 0 deletions
diff --git a/src/lib/fs/path.c b/src/lib/fs/path.c
new file mode 100644
index 0000000000..68cda67765
--- /dev/null
+++ b/src/lib/fs/path.c
@@ -0,0 +1,289 @@
+/* Copyright (c) 2003, Roger Dingledine
+ * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
+ * Copyright (c) 2007-2018, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+#include "lib/fs/path.h"
+#include "lib/malloc/util_malloc.h"
+#include "lib/log/torlog.h"
+#include "lib/log/util_bug.h"
+#include "lib/string/printf.h"
+#include "lib/string/util_string.h"
+#include "lib/string/compat_ctype.h"
+#include "lib/fs/userdb.h"
+
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include <errno.h>
+#include <string.h>
+
+/** Removes enclosing quotes from <b>path</b> and unescapes quotes between the
+ * enclosing quotes. Backslashes are not unescaped. Return the unquoted
+ * <b>path</b> on success or 0 if <b>path</b> is not quoted correctly. */
+char *
+get_unquoted_path(const char *path)
+{
+ size_t len = strlen(path);
+
+ if (len == 0) {
+ return tor_strdup("");
+ }
+
+ int has_start_quote = (path[0] == '\"');
+ int has_end_quote = (len > 0 && path[len-1] == '\"');
+ if (has_start_quote != has_end_quote || (len == 1 && has_start_quote)) {
+ return NULL;
+ }
+
+ char *unquoted_path = tor_malloc(len - has_start_quote - has_end_quote + 1);
+ char *s = unquoted_path;
+ size_t i;
+ for (i = has_start_quote; i < len - has_end_quote; i++) {
+ if (path[i] == '\"' && (i > 0 && path[i-1] == '\\')) {
+ *(s-1) = path[i];
+ } else if (path[i] != '\"') {
+ *s++ = path[i];
+ } else { /* unescaped quote */
+ tor_free(unquoted_path);
+ return NULL;
+ }
+ }
+ *s = '\0';
+ return unquoted_path;
+}
+
+/** Expand any homedir prefix on <b>filename</b>; return a newly allocated
+ * string. */
+char *
+expand_filename(const char *filename)
+{
+ tor_assert(filename);
+#ifdef _WIN32
+ /* Might consider using GetFullPathName() as described here:
+ * http://etutorials.org/Programming/secure+programming/
+ * Chapter+3.+Input+Validation/3.7+Validating+Filenames+and+Paths/
+ */
+ return tor_strdup(filename);
+#else /* !(defined(_WIN32)) */
+ if (*filename == '~') {
+ char *home, *result=NULL;
+ const char *rest;
+
+ if (filename[1] == '/' || filename[1] == '\0') {
+ home = getenv("HOME");
+ if (!home) {
+ log_warn(LD_CONFIG, "Couldn't find $HOME environment variable while "
+ "expanding \"%s\"; defaulting to \"\".", filename);
+ home = tor_strdup("");
+ } else {
+ home = tor_strdup(home);
+ }
+ rest = strlen(filename)>=2?(filename+2):"";
+ } else {
+#ifdef HAVE_PWD_H
+ char *username, *slash;
+ slash = strchr(filename, '/');
+ if (slash)
+ username = tor_strndup(filename+1,slash-filename-1);
+ else
+ username = tor_strdup(filename+1);
+ if (!(home = get_user_homedir(username))) {
+ log_warn(LD_CONFIG,"Couldn't get homedir for \"%s\"",username);
+ tor_free(username);
+ return NULL;
+ }
+ tor_free(username);
+ rest = slash ? (slash+1) : "";
+#else /* !(defined(HAVE_PWD_H)) */
+ log_warn(LD_CONFIG, "Couldn't expand homedir on system without pwd.h");
+ return tor_strdup(filename);
+#endif /* defined(HAVE_PWD_H) */
+ }
+ tor_assert(home);
+ /* Remove trailing slash. */
+ if (strlen(home)>1 && !strcmpend(home,PATH_SEPARATOR)) {
+ home[strlen(home)-1] = '\0';
+ }
+ tor_asprintf(&result,"%s"PATH_SEPARATOR"%s",home,rest);
+ tor_free(home);
+ return result;
+ } else {
+ return tor_strdup(filename);
+ }
+#endif /* defined(_WIN32) */
+}
+
+/** Return true iff <b>filename</b> is a relative path. */
+int
+path_is_relative(const char *filename)
+{
+ if (filename && filename[0] == '/')
+ return 0;
+#ifdef _WIN32
+ else if (filename && filename[0] == '\\')
+ return 0;
+ else if (filename && strlen(filename)>3 && TOR_ISALPHA(filename[0]) &&
+ filename[1] == ':' && filename[2] == '\\')
+ return 0;
+#endif /* defined(_WIN32) */
+ else
+ return 1;
+}
+
+/** Clean up <b>name</b> so that we can use it in a call to "stat". On Unix,
+ * we do nothing. On Windows, we remove a trailing slash, unless the path is
+ * the root of a disk. */
+void
+clean_fname_for_stat(char *name)
+{
+#ifdef _WIN32
+ size_t len = strlen(name);
+ if (!len)
+ return;
+ if (name[len-1]=='\\' || name[len-1]=='/') {
+ if (len == 1 || (len==3 && name[1]==':'))
+ return;
+ name[len-1]='\0';
+ }
+#else /* !(defined(_WIN32)) */
+ (void)name;
+#endif /* defined(_WIN32) */
+}
+
+/** Modify <b>fname</b> to contain the name of its parent directory. Doesn't
+ * actually examine the filesystem; does a purely syntactic modification.
+ *
+ * The parent of the root director is considered to be iteself.
+ *
+ * Path separators are the forward slash (/) everywhere and additionally
+ * the backslash (\) on Win32.
+ *
+ * Cuts off any number of trailing path separators but otherwise ignores
+ * them for purposes of finding the parent directory.
+ *
+ * Returns 0 if a parent directory was successfully found, -1 otherwise (fname
+ * did not have any path separators or only had them at the end).
+ * */
+int
+get_parent_directory(char *fname)
+{
+ char *cp;
+ int at_end = 1;
+ tor_assert(fname);
+#ifdef _WIN32
+ /* If we start with, say, c:, then don't consider that the start of the path
+ */
+ if (fname[0] && fname[1] == ':') {
+ fname += 2;
+ }
+#endif /* defined(_WIN32) */
+ /* Now we want to remove all path-separators at the end of the string,
+ * and to remove the end of the string starting with the path separator
+ * before the last non-path-separator. In perl, this would be
+ * s#[/]*$##; s#/[^/]*$##;
+ * on a unixy platform.
+ */
+ cp = fname + strlen(fname);
+ at_end = 1;
+ while (--cp >= fname) {
+ int is_sep = (*cp == '/'
+#ifdef _WIN32
+ || *cp == '\\'
+#endif
+ );
+ if (is_sep) {
+ if (cp == fname) {
+ /* This is the first separator in the file name; don't remove it! */
+ cp[1] = '\0';
+ return 0;
+ }
+ *cp = '\0';
+ if (! at_end)
+ return 0;
+ } else {
+ at_end = 0;
+ }
+ }
+ return -1;
+}
+
+#ifndef _WIN32
+/** Return a newly allocated string containing the output of getcwd(). Return
+ * NULL on failure. (We can't just use getcwd() into a PATH_MAX buffer, since
+ * Hurd hasn't got a PATH_MAX.)
+ */
+static char *
+alloc_getcwd(void)
+{
+#ifdef HAVE_GET_CURRENT_DIR_NAME
+ /* Glibc makes this nice and simple for us. */
+ char *cwd = get_current_dir_name();
+ char *result = NULL;
+ if (cwd) {
+ /* We make a copy here, in case tor_malloc() is not malloc(). */
+ result = tor_strdup(cwd);
+ raw_free(cwd); // alias for free to avoid tripping check-spaces.
+ }
+ return result;
+#else /* !(defined(HAVE_GET_CURRENT_DIR_NAME)) */
+ size_t size = 1024;
+ char *buf = NULL;
+ char *ptr = NULL;
+
+ while (ptr == NULL) {
+ buf = tor_realloc(buf, size);
+ ptr = getcwd(buf, size);
+
+ if (ptr == NULL && errno != ERANGE) {
+ tor_free(buf);
+ return NULL;
+ }
+
+ size *= 2;
+ }
+ return buf;
+#endif /* defined(HAVE_GET_CURRENT_DIR_NAME) */
+}
+#endif /* !defined(_WIN32) */
+
+/** Expand possibly relative path <b>fname</b> to an absolute path.
+ * Return a newly allocated string, possibly equal to <b>fname</b>. */
+char *
+make_path_absolute(char *fname)
+{
+#ifdef _WIN32
+ char *absfname_malloced = _fullpath(NULL, fname, 1);
+
+ /* We don't want to assume that tor_free can free a string allocated
+ * with malloc. On failure, return fname (it's better than nothing). */
+ char *absfname = tor_strdup(absfname_malloced ? absfname_malloced : fname);
+ if (absfname_malloced) raw_free(absfname_malloced);
+
+ return absfname;
+#else /* !(defined(_WIN32)) */
+ char *absfname = NULL, *path = NULL;
+
+ tor_assert(fname);
+
+ if (fname[0] == '/') {
+ absfname = tor_strdup(fname);
+ } else {
+ path = alloc_getcwd();
+ if (path) {
+ tor_asprintf(&absfname, "%s/%s", path, fname);
+ tor_free(path);
+ } else {
+ /* LCOV_EXCL_START Can't make getcwd fail. */
+ /* If getcwd failed, the best we can do here is keep using the
+ * relative path. (Perhaps / isn't readable by this UID/GID.) */
+ log_warn(LD_GENERAL, "Unable to find current working directory: %s",
+ strerror(errno));
+ absfname = tor_strdup(fname);
+ /* LCOV_EXCL_STOP */
+ }
+ }
+ return absfname;
+#endif /* defined(_WIN32) */
+}