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 #include "jdk_util.h"
41 
42 /* Note: The comments in this file use the terminology
43          defined in the java.io.File class */
44 
45 
46 /* Check the given name sequence to see if it can be further collapsed.
47    Return zero if not, otherwise return the number of names in the sequence. */
48 
49 static int
collapsible(char * names)50 collapsible(char *names)
51 {
52     char *p = names;
53     int dots = 0, n = 0;
54 
55     while (*p) {
56         if ((p[0] == '.') && ((p[1] == '\0')
57                               || (p[1] == '/')
58                               || ((p[1] == '.') && ((p[2] == '\0')
59                                                     || (p[2] == '/'))))) {
60             dots = 1;
61         }
62         n++;
63         while (*p) {
64             if (*p == '/') {
65                 p++;
66                 break;
67             }
68             p++;
69         }
70     }
71     return (dots ? n : 0);
72 }
73 
74 
75 /* Split the names in the given name sequence,
76    replacing slashes with nulls and filling in the given index array */
77 
78 static void
splitNames(char * names,char ** ix)79 splitNames(char *names, char **ix)
80 {
81     char *p = names;
82     int i = 0;
83 
84     while (*p) {
85         ix[i++] = p++;
86         while (*p) {
87             if (*p == '/') {
88                 *p++ = '\0';
89                 break;
90             }
91             p++;
92         }
93     }
94 }
95 
96 
97 /* Join the names in the given name sequence, ignoring names whose index
98    entries have been cleared and replacing nulls with slashes as needed */
99 
100 static void
joinNames(char * names,int nc,char ** ix)101 joinNames(char *names, int nc, char **ix)
102 {
103     int i;
104     char *p;
105 
106     for (i = 0, p = names; i < nc; i++) {
107         if (!ix[i]) continue;
108         if (i > 0) {
109             p[-1] = '/';
110         }
111         if (p == ix[i]) {
112             p += strlen(p) + 1;
113         } else {
114             char *q = ix[i];
115             while ((*p++ = *q++));
116         }
117     }
118     *p = '\0';
119 }
120 
121 
122 /* Collapse "." and ".." names in the given path wherever possible.
123    A "." name may always be eliminated; a ".." name may be eliminated if it
124    follows a name that is neither "." nor "..".  This is a syntactic operation
125    that performs no filesystem queries, so it should only be used to cleanup
126    after invoking the realpath() procedure. */
127 
128 static void
collapse(char * path)129 collapse(char *path)
130 {
131     char *names = (path[0] == '/') ? path + 1 : path; /* Preserve first '/' */
132     int nc;
133     char **ix;
134     int i, j;
135     char *p, *q;
136 
137     nc = collapsible(names);
138     if (nc < 2) return;         /* Nothing to do */
139     ix = (char **)alloca(nc * sizeof(char *));
140     splitNames(names, ix);
141 
142     for (i = 0; i < nc; i++) {
143         int dots = 0;
144 
145         /* Find next occurrence of "." or ".." */
146         do {
147             char *p = ix[i];
148             if (p[0] == '.') {
149                 if (p[1] == '\0') {
150                     dots = 1;
151                     break;
152                 }
153                 if ((p[1] == '.') && (p[2] == '\0')) {
154                     dots = 2;
155                     break;
156                 }
157             }
158             i++;
159         } while (i < nc);
160         if (i >= nc) break;
161 
162         /* At this point i is the index of either a "." or a "..", so take the
163            appropriate action and then continue the outer loop */
164         if (dots == 1) {
165             /* Remove this instance of "." */
166             ix[i] = 0;
167         }
168         else {
169             /* If there is a preceding name, remove both that name and this
170                instance of ".."; otherwise, leave the ".." as is */
171             for (j = i - 1; j >= 0; j--) {
172                 if (ix[j]) break;
173             }
174             if (j < 0) continue;
175             ix[j] = 0;
176             ix[i] = 0;
177         }
178         /* i will be incremented at the top of the loop */
179     }
180 
181     joinNames(names, nc, ix);
182 }
183 
184 
185 /* Convert a pathname to canonical form.  The input path is assumed to contain
186    no duplicate slashes.  On Solaris we can use realpath() to do most of the
187    work, though once that's done we still must collapse any remaining "." and
188    ".." names by hand. */
189 
190 JNIEXPORT int
JDK_Canonicalize(const char * orig,char * out,int len)191 JDK_Canonicalize(const char *orig, char *out, int len)
192 {
193     if (len < PATH_MAX) {
194         errno = EINVAL;
195         return -1;
196     }
197 
198     if (strlen(orig) > PATH_MAX) {
199         errno = ENAMETOOLONG;
200         return -1;
201     }
202 
203     /* First try realpath() on the entire path */
204     if (realpath(orig, out)) {
205         /* That worked, so return it */
206         collapse(out);
207         return 0;
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(orig) <= PATH_MAX, see above
215         strncpy(path, orig, 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, out);
229             *p = (p == end) ? '\0' : '/';
230 
231             if (r != NULL) {
232                 /* The subpath has a canonical path */
233                 break;
234             } else if (errno == ENOENT || errno == ENOTDIR || errno == EACCES) {
235                 /* If the lookup of a particular subpath fails because the file
236                    does not exist, because it is of the wrong type, or because
237                    access is denied, then remove its last name and try again.
238                    Other I/O problems cause an error return. */
239                 continue;
240             } else {
241                 return -1;
242             }
243         }
244 
245         if (r != NULL) {
246             /* Append unresolved subpath to resolved subpath */
247             int rn = strlen(r);
248             if (rn + (int)strlen(p) >= len) {
249                 /* Buffer overflow */
250                 errno = ENAMETOOLONG;
251                 return -1;
252             }
253             if ((rn > 0) && (r[rn - 1] == '/') && (*p == '/')) {
254                 /* Avoid duplicate slashes */
255                 p++;
256             }
257             strcpy(r + rn, p);
258             collapse(r);
259             return 0;
260         } else {
261             /* Nothing resolved, so just return the original path */
262             strcpy(out, path);
263             collapse(out);
264             return 0;
265         }
266     }
267 }
268