1 | /* |
2 | * Copyright (c) 1994, 2019, Oracle and/or its affiliates. All rights reserved. |
3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
4 | * |
5 | * This code is free software; you can redistribute it and/or modify it |
6 | * under the terms of the GNU General Public License version 2 only, as |
7 | * published by the Free Software Foundation. Oracle designates this |
8 | * particular file as subject to the "Classpath" exception as provided |
9 | * by Oracle in the LICENSE file that accompanied this code. |
10 | * |
11 | * This code is distributed in the hope that it will be useful, but WITHOUT |
12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
14 | * version 2 for more details (a copy is included in the LICENSE file that |
15 | * accompanied this code). |
16 | * |
17 | * You should have received a copy of the GNU General Public License version |
18 | * 2 along with this work; if not, write to the Free Software Foundation, |
19 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
20 | * |
21 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
22 | * or visit www.oracle.com if you need additional information or have any |
23 | * questions. |
24 | */ |
25 | |
26 | /* |
27 | * Pathname canonicalization for Unix file systems |
28 | */ |
29 | |
30 | #include <stdio.h> |
31 | #include <stdlib.h> |
32 | #include <string.h> |
33 | #include <sys/stat.h> |
34 | #include <errno.h> |
35 | #include <limits.h> |
36 | #if !defined(_ALLBSD_SOURCE) |
37 | #include <alloca.h> |
38 | #endif |
39 | |
40 | |
41 | /* Note: The comments in this file use the terminology |
42 | defined in the java.io.File class */ |
43 | |
44 | |
45 | /* Check the given name sequence to see if it can be further collapsed. |
46 | Return zero if not, otherwise return the number of names in the sequence. */ |
47 | |
48 | static int |
49 | collapsible(char *names) |
50 | { |
51 | char *p = names; |
52 | int dots = 0, n = 0; |
53 | |
54 | while (*p) { |
55 | if ((p[0] == '.') && ((p[1] == '\0') |
56 | || (p[1] == '/') |
57 | || ((p[1] == '.') && ((p[2] == '\0') |
58 | || (p[2] == '/'))))) { |
59 | dots = 1; |
60 | } |
61 | n++; |
62 | while (*p) { |
63 | if (*p == '/') { |
64 | p++; |
65 | break; |
66 | } |
67 | p++; |
68 | } |
69 | } |
70 | return (dots ? n : 0); |
71 | } |
72 | |
73 | |
74 | /* Split the names in the given name sequence, |
75 | replacing slashes with nulls and filling in the given index array */ |
76 | |
77 | static void |
78 | splitNames(char *names, char **ix) |
79 | { |
80 | char *p = names; |
81 | int i = 0; |
82 | |
83 | while (*p) { |
84 | ix[i++] = p++; |
85 | while (*p) { |
86 | if (*p == '/') { |
87 | *p++ = '\0'; |
88 | break; |
89 | } |
90 | p++; |
91 | } |
92 | } |
93 | } |
94 | |
95 | |
96 | /* Join the names in the given name sequence, ignoring names whose index |
97 | entries have been cleared and replacing nulls with slashes as needed */ |
98 | |
99 | static void |
100 | joinNames(char *names, int nc, char **ix) |
101 | { |
102 | int i; |
103 | char *p; |
104 | |
105 | for (i = 0, p = names; i < nc; i++) { |
106 | if (!ix[i]) continue; |
107 | if (i > 0) { |
108 | p[-1] = '/'; |
109 | } |
110 | if (p == ix[i]) { |
111 | p += strlen(p) + 1; |
112 | } else { |
113 | char *q = ix[i]; |
114 | while ((*p++ = *q++)); |
115 | } |
116 | } |
117 | *p = '\0'; |
118 | } |
119 | |
120 | |
121 | /* Collapse "." and ".." names in the given path wherever possible. |
122 | A "." name may always be eliminated; a ".." name may be eliminated if it |
123 | follows a name that is neither "." nor "..". This is a syntactic operation |
124 | that performs no filesystem queries, so it should only be used to cleanup |
125 | after invoking the realpath() procedure. */ |
126 | |
127 | static void |
128 | collapse(char *path) |
129 | { |
130 | char *names = (path[0] == '/') ? path + 1 : path; /* Preserve first '/' */ |
131 | int nc; |
132 | char **ix; |
133 | int i, j; |
134 | char *p, *q; |
135 | |
136 | nc = collapsible(names); |
137 | if (nc < 2) return; /* Nothing to do */ |
138 | ix = (char **)alloca(nc * sizeof(char *)); |
139 | splitNames(names, ix); |
140 | |
141 | for (i = 0; i < nc; i++) { |
142 | int dots = 0; |
143 | |
144 | /* Find next occurrence of "." or ".." */ |
145 | do { |
146 | char *p = ix[i]; |
147 | if (p[0] == '.') { |
148 | if (p[1] == '\0') { |
149 | dots = 1; |
150 | break; |
151 | } |
152 | if ((p[1] == '.') && (p[2] == '\0')) { |
153 | dots = 2; |
154 | break; |
155 | } |
156 | } |
157 | i++; |
158 | } while (i < nc); |
159 | if (i >= nc) break; |
160 | |
161 | /* At this point i is the index of either a "." or a "..", so take the |
162 | appropriate action and then continue the outer loop */ |
163 | if (dots == 1) { |
164 | /* Remove this instance of "." */ |
165 | ix[i] = 0; |
166 | } |
167 | else { |
168 | /* If there is a preceding name, remove both that name and this |
169 | instance of ".."; otherwise, leave the ".." as is */ |
170 | for (j = i - 1; j >= 0; j--) { |
171 | if (ix[j]) break; |
172 | } |
173 | if (j < 0) continue; |
174 | ix[j] = 0; |
175 | ix[i] = 0; |
176 | } |
177 | /* i will be incremented at the top of the loop */ |
178 | } |
179 | |
180 | joinNames(names, nc, ix); |
181 | } |
182 | |
183 | |
184 | /* Convert a pathname to canonical form. The input path is assumed to contain |
185 | no duplicate slashes. On Solaris we can use realpath() to do most of the |
186 | work, though once that's done we still must collapse any remaining "." and |
187 | ".." names by hand. */ |
188 | |
189 | int |
190 | canonicalize(char *original, char *resolved, int len) |
191 | { |
192 | if (len < PATH_MAX) { |
193 | errno = EINVAL; |
194 | return -1; |
195 | } |
196 | |
197 | if (strlen(original) > PATH_MAX) { |
198 | errno = ENAMETOOLONG; |
199 | return -1; |
200 | } |
201 | |
202 | /* First try realpath() on the entire path */ |
203 | if (realpath(original, resolved)) { |
204 | /* That worked, so return it */ |
205 | collapse(resolved); |
206 | return 0; |
207 | } |
208 | else { |
209 | /* Something's bogus in the original path, so remove names from the end |
210 | until either some subpath works or we run out of names */ |
211 | char *p, *end, *r = NULL; |
212 | char path[PATH_MAX + 1]; |
213 | |
214 | // strlen(original) <= PATH_MAX, see above |
215 | strncpy(path, original, PATH_MAX); |
216 | // append null for == case |
217 | path[PATH_MAX] = '\0'; |
218 | end = path + strlen(path); |
219 | |
220 | for (p = end; p > path;) { |
221 | |
222 | /* Skip last element */ |
223 | while ((--p > path) && (*p != '/')); |
224 | if (p == path) break; |
225 | |
226 | /* Try realpath() on this subpath */ |
227 | *p = '\0'; |
228 | r = realpath(path, resolved); |
229 | *p = (p == end) ? '\0' : '/'; |
230 | |
231 | if (r != NULL) { |
232 | /* The subpath has a canonical path */ |
233 | break; |
234 | } |
235 | else if (errno == ENOENT || errno == ENOTDIR || errno == EACCES) { |
236 | /* If the lookup of a particular subpath fails because the file |
237 | does not exist, because it is of the wrong type, or because |
238 | access is denied, then remove its last name and try again. |
239 | Other I/O problems cause an error return. */ |
240 | continue; |
241 | } |
242 | else { |
243 | return -1; |
244 | } |
245 | } |
246 | |
247 | if (r != NULL) { |
248 | /* Append unresolved subpath to resolved subpath */ |
249 | int rn = strlen(r); |
250 | if (rn + (int)strlen(p) >= len) { |
251 | /* Buffer overflow */ |
252 | errno = ENAMETOOLONG; |
253 | return -1; |
254 | } |
255 | if ((rn > 0) && (r[rn - 1] == '/') && (*p == '/')) { |
256 | /* Avoid duplicate slashes */ |
257 | p++; |
258 | } |
259 | strcpy(r + rn, p); |
260 | collapse(r); |
261 | return 0; |
262 | } |
263 | else { |
264 | /* Nothing resolved, so just return the original path */ |
265 | strcpy(resolved, path); |
266 | collapse(resolved); |
267 | return 0; |
268 | } |
269 | } |
270 | |
271 | } |
272 | |