/src/tor/src/lib/fs/path.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* Copyright (c) 2003, Roger Dingledine |
2 | | * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. |
3 | | * Copyright (c) 2007-2018, The Tor Project, Inc. */ |
4 | | /* See LICENSE for licensing information */ |
5 | | |
6 | | /** |
7 | | * \file path.c |
8 | | * |
9 | | * \brief Manipulate strings that contain filesystem paths. |
10 | | **/ |
11 | | |
12 | | #include "lib/fs/path.h" |
13 | | #include "lib/malloc/malloc.h" |
14 | | #include "lib/log/log.h" |
15 | | #include "lib/log/util_bug.h" |
16 | | #include "lib/string/printf.h" |
17 | | #include "lib/string/util_string.h" |
18 | | #include "lib/string/compat_ctype.h" |
19 | | #include "lib/fs/userdb.h" |
20 | | |
21 | | #ifdef HAVE_UNISTD_H |
22 | | #include <unistd.h> |
23 | | #endif |
24 | | |
25 | | #include <errno.h> |
26 | | #include <string.h> |
27 | | |
28 | | /** Removes enclosing quotes from <b>path</b> and unescapes quotes between the |
29 | | * enclosing quotes. Backslashes are not unescaped. Return the unquoted |
30 | | * <b>path</b> on success or 0 if <b>path</b> is not quoted correctly. */ |
31 | | char * |
32 | | get_unquoted_path(const char *path) |
33 | 0 | { |
34 | 0 | size_t len = strlen(path); |
35 | 0 |
|
36 | 0 | if (len == 0) { |
37 | 0 | return tor_strdup(""); |
38 | 0 | } |
39 | 0 |
|
40 | 0 | int has_start_quote = (path[0] == '\"'); |
41 | 0 | int has_end_quote = (len > 0 && path[len-1] == '\"'); |
42 | 0 | if (has_start_quote != has_end_quote || (len == 1 && has_start_quote)) { |
43 | 0 | return NULL; |
44 | 0 | } |
45 | 0 | |
46 | 0 | char *unquoted_path = tor_malloc(len - has_start_quote - has_end_quote + 1); |
47 | 0 | char *s = unquoted_path; |
48 | 0 | size_t i; |
49 | 0 | for (i = has_start_quote; i < len - has_end_quote; i++) { |
50 | 0 | if (path[i] == '\"' && (i > 0 && path[i-1] == '\\')) { |
51 | 0 | *(s-1) = path[i]; |
52 | 0 | } else if (path[i] != '\"') { |
53 | 0 | *s++ = path[i]; |
54 | 0 | } else { /* unescaped quote */ |
55 | 0 | tor_free(unquoted_path); |
56 | 0 | return NULL; |
57 | 0 | } |
58 | 0 | } |
59 | 0 | *s = '\0'; |
60 | 0 | return unquoted_path; |
61 | 0 | } |
62 | | |
63 | | /** Expand any homedir prefix on <b>filename</b>; return a newly allocated |
64 | | * string. */ |
65 | | char * |
66 | | expand_filename(const char *filename) |
67 | 0 | { |
68 | 0 | tor_assert(filename); |
69 | | #ifdef _WIN32 |
70 | | /* Might consider using GetFullPathName() as described here: |
71 | | * http://etutorials.org/Programming/secure+programming/ |
72 | | * Chapter+3.+Input+Validation/3.7+Validating+Filenames+and+Paths/ |
73 | | */ |
74 | | return tor_strdup(filename); |
75 | | #else /* !(defined(_WIN32)) */ |
76 | 0 | if (*filename == '~') { |
77 | 0 | char *home, *result=NULL; |
78 | 0 | const char *rest; |
79 | 0 |
|
80 | 0 | if (filename[1] == '/' || filename[1] == '\0') { |
81 | 0 | home = getenv("HOME"); |
82 | 0 | if (!home) { |
83 | 0 | log_warn(LD_CONFIG, "Couldn't find $HOME environment variable while " |
84 | 0 | "expanding \"%s\"; defaulting to \"\".", filename); |
85 | 0 | home = tor_strdup(""); |
86 | 0 | } else { |
87 | 0 | home = tor_strdup(home); |
88 | 0 | } |
89 | 0 | rest = strlen(filename)>=2?(filename+2):""; |
90 | 0 | } else { |
91 | 0 | #ifdef HAVE_PWD_H |
92 | 0 | char *username, *slash; |
93 | 0 | slash = strchr(filename, '/'); |
94 | 0 | if (slash) |
95 | 0 | username = tor_strndup(filename+1,slash-filename-1); |
96 | 0 | else |
97 | 0 | username = tor_strdup(filename+1); |
98 | 0 | if (!(home = get_user_homedir(username))) { |
99 | 0 | log_warn(LD_CONFIG,"Couldn't get homedir for \"%s\"",username); |
100 | 0 | tor_free(username); |
101 | 0 | return NULL; |
102 | 0 | } |
103 | 0 | tor_free(username); |
104 | 0 | rest = slash ? (slash+1) : ""; |
105 | | #else /* !(defined(HAVE_PWD_H)) */ |
106 | | log_warn(LD_CONFIG, "Couldn't expand homedir on system without pwd.h"); |
107 | | return tor_strdup(filename); |
108 | | #endif /* defined(HAVE_PWD_H) */ |
109 | | } |
110 | 0 | tor_assert(home); |
111 | 0 | /* Remove trailing slash. */ |
112 | 0 | if (strlen(home)>1 && !strcmpend(home,PATH_SEPARATOR)) { |
113 | 0 | home[strlen(home)-1] = '\0'; |
114 | 0 | } |
115 | 0 | tor_asprintf(&result,"%s"PATH_SEPARATOR"%s",home,rest); |
116 | 0 | tor_free(home); |
117 | 0 | return result; |
118 | 0 | } else { |
119 | 0 | return tor_strdup(filename); |
120 | 0 | } |
121 | 0 | #endif /* defined(_WIN32) */ |
122 | 0 | } |
123 | | |
124 | | /** Return true iff <b>filename</b> is a relative path. */ |
125 | | int |
126 | | path_is_relative(const char *filename) |
127 | 0 | { |
128 | 0 | if (filename && filename[0] == '/') |
129 | 0 | return 0; |
130 | | #ifdef _WIN32 |
131 | | else if (filename && filename[0] == '\\') |
132 | | return 0; |
133 | | else if (filename && strlen(filename)>3 && TOR_ISALPHA(filename[0]) && |
134 | | filename[1] == ':' && filename[2] == '\\') |
135 | | return 0; |
136 | | #endif /* defined(_WIN32) */ |
137 | | else |
138 | 0 | return 1; |
139 | 0 | } |
140 | | |
141 | | /** Clean up <b>name</b> so that we can use it in a call to "stat". On Unix, |
142 | | * we do nothing. On Windows, we remove a trailing slash, unless the path is |
143 | | * the root of a disk. */ |
144 | | void |
145 | | clean_fname_for_stat(char *name) |
146 | 0 | { |
147 | | #ifdef _WIN32 |
148 | | size_t len = strlen(name); |
149 | | if (!len) |
150 | | return; |
151 | | if (name[len-1]=='\\' || name[len-1]=='/') { |
152 | | if (len == 1 || (len==3 && name[1]==':')) |
153 | | return; |
154 | | name[len-1]='\0'; |
155 | | } |
156 | | #else /* !(defined(_WIN32)) */ |
157 | | (void)name; |
158 | 0 | #endif /* defined(_WIN32) */ |
159 | 0 | } |
160 | | |
161 | | /** Modify <b>fname</b> to contain the name of its parent directory. Doesn't |
162 | | * actually examine the filesystem; does a purely syntactic modification. |
163 | | * |
164 | | * The parent of the root director is considered to be iteself. |
165 | | * |
166 | | * Path separators are the forward slash (/) everywhere and additionally |
167 | | * the backslash (\) on Win32. |
168 | | * |
169 | | * Cuts off any number of trailing path separators but otherwise ignores |
170 | | * them for purposes of finding the parent directory. |
171 | | * |
172 | | * Returns 0 if a parent directory was successfully found, -1 otherwise (fname |
173 | | * did not have any path separators or only had them at the end). |
174 | | * */ |
175 | | int |
176 | | get_parent_directory(char *fname) |
177 | 0 | { |
178 | 0 | char *cp; |
179 | 0 | int at_end = 1; |
180 | 0 | tor_assert(fname); |
181 | | #ifdef _WIN32 |
182 | | /* If we start with, say, c:, then don't consider that the start of the path |
183 | | */ |
184 | | if (fname[0] && fname[1] == ':') { |
185 | | fname += 2; |
186 | | } |
187 | | #endif /* defined(_WIN32) */ |
188 | | /* Now we want to remove all path-separators at the end of the string, |
189 | 0 | * and to remove the end of the string starting with the path separator |
190 | 0 | * before the last non-path-separator. In perl, this would be |
191 | 0 | * s#[/]*$##; s#/[^/]*$##; |
192 | 0 | * on a unixy platform. |
193 | 0 | */ |
194 | 0 | cp = fname + strlen(fname); |
195 | 0 | at_end = 1; |
196 | 0 | while (--cp >= fname) { |
197 | 0 | int is_sep = (*cp == '/' |
198 | | #ifdef _WIN32 |
199 | | || *cp == '\\' |
200 | | #endif |
201 | | ); |
202 | 0 | if (is_sep) { |
203 | 0 | if (cp == fname) { |
204 | 0 | /* This is the first separator in the file name; don't remove it! */ |
205 | 0 | cp[1] = '\0'; |
206 | 0 | return 0; |
207 | 0 | } |
208 | 0 | *cp = '\0'; |
209 | 0 | if (! at_end) |
210 | 0 | return 0; |
211 | 0 | } else { |
212 | 0 | at_end = 0; |
213 | 0 | } |
214 | 0 | } |
215 | 0 | return -1; |
216 | 0 | } |
217 | | |
218 | | #ifndef _WIN32 |
219 | | /** Return a newly allocated string containing the output of getcwd(). Return |
220 | | * NULL on failure. (We can't just use getcwd() into a PATH_MAX buffer, since |
221 | | * Hurd hasn't got a PATH_MAX.) |
222 | | */ |
223 | | static char * |
224 | | alloc_getcwd(void) |
225 | 0 | { |
226 | 0 | #ifdef HAVE_GET_CURRENT_DIR_NAME |
227 | 0 | /* Glibc makes this nice and simple for us. */ |
228 | 0 | char *cwd = get_current_dir_name(); |
229 | 0 | char *result = NULL; |
230 | 0 | if (cwd) { |
231 | 0 | /* We make a copy here, in case tor_malloc() is not malloc(). */ |
232 | 0 | result = tor_strdup(cwd); |
233 | 0 | raw_free(cwd); // alias for free to avoid tripping check-spaces. |
234 | 0 | } |
235 | 0 | return result; |
236 | | #else /* !(defined(HAVE_GET_CURRENT_DIR_NAME)) */ |
237 | | size_t size = 1024; |
238 | | char *buf = NULL; |
239 | | char *ptr = NULL; |
240 | | |
241 | | while (ptr == NULL) { |
242 | | buf = tor_realloc(buf, size); |
243 | | ptr = getcwd(buf, size); |
244 | | |
245 | | if (ptr == NULL && errno != ERANGE) { |
246 | | tor_free(buf); |
247 | | return NULL; |
248 | | } |
249 | | |
250 | | size *= 2; |
251 | | } |
252 | | return buf; |
253 | | #endif /* defined(HAVE_GET_CURRENT_DIR_NAME) */ |
254 | | } |
255 | | #endif /* !defined(_WIN32) */ |
256 | | |
257 | | /** Expand possibly relative path <b>fname</b> to an absolute path. |
258 | | * Return a newly allocated string, possibly equal to <b>fname</b>. */ |
259 | | char * |
260 | | make_path_absolute(char *fname) |
261 | 0 | { |
262 | | #ifdef _WIN32 |
263 | | char *absfname_malloced = _fullpath(NULL, fname, 1); |
264 | | |
265 | | /* We don't want to assume that tor_free can free a string allocated |
266 | | * with malloc. On failure, return fname (it's better than nothing). */ |
267 | | char *absfname = tor_strdup(absfname_malloced ? absfname_malloced : fname); |
268 | | if (absfname_malloced) raw_free(absfname_malloced); |
269 | | |
270 | | return absfname; |
271 | | #else /* !(defined(_WIN32)) */ |
272 | | char *absfname = NULL, *path = NULL; |
273 | 0 |
|
274 | 0 | tor_assert(fname); |
275 | 0 |
|
276 | 0 | if (fname[0] == '/') { |
277 | 0 | absfname = tor_strdup(fname); |
278 | 0 | } else { |
279 | 0 | path = alloc_getcwd(); |
280 | 0 | if (path) { |
281 | 0 | tor_asprintf(&absfname, "%s/%s", path, fname); |
282 | 0 | tor_free(path); |
283 | 0 | } else { |
284 | 0 | /* LCOV_EXCL_START Can't make getcwd fail. */ |
285 | 0 | /* If getcwd failed, the best we can do here is keep using the |
286 | 0 | * relative path. (Perhaps / isn't readable by this UID/GID.) */ |
287 | 0 | log_warn(LD_GENERAL, "Unable to find current working directory: %s", |
288 | 0 | strerror(errno)); |
289 | 0 | absfname = tor_strdup(fname); |
290 | 0 | /* LCOV_EXCL_STOP */ |
291 | 0 | } |
292 | 0 | } |
293 | 0 | return absfname; |
294 | 0 | #endif /* defined(_WIN32) */ |
295 | 0 | } |