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