/* Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "apr.h" #include "apr_private.h" #include "apr_arch_file_io.h" #include "apr_strings.h" #include "apr_lib.h" #include #include #ifdef NETWARE #include #include #endif /* WinNT accepts several odd forms of a 'root' path. Under Unicode * calls (ApiFunctionW) the //?/C:/foo or //?/UNC/mach/share/foo forms * are accepted. Ansi and Unicode functions both accept the //./C:/foo * form under WinNT/2K. Since these forms are handled in the utf-8 to * unicode translation phase, we don't want the user confused by them, so * we will accept them but always return the canonical C:/ or //mach/share/ * * OS2 appears immune from the nonsense :) */ APR_DECLARE(apr_status_t) apr_filepath_root(const char **rootpath, const char **inpath, apr_int32_t flags, apr_pool_t *p) { const char *testpath = *inpath; char *newpath; #ifdef NETWARE char seperator[2] = { 0, 0}; char server[APR_PATH_MAX+1]; char volume[APR_PATH_MAX+1]; char file[APR_PATH_MAX+1]; char *volsep = NULL; int elements; if (inpath && *inpath) volsep = strchr (*inpath, ':'); else return APR_EBADPATH; if (strlen(*inpath) > APR_PATH_MAX) { return APR_EBADPATH; } seperator[0] = (flags & APR_FILEPATH_NATIVE) ? '\\' : '/'; /* Allocate and initialize each of the segment buffers */ server[0] = volume[0] = file[0] = '\0'; /* If we don't have a volume separator then don't bother deconstructing the path since we won't use the deconstructed information anyway. */ if (volsep) { /* Split the inpath into its separate parts. */ deconstruct(testpath, server, volume, NULL, file, NULL, &elements, PATH_UNDEF); /* If we got a volume part then continue splitting out the root. Otherwise we either have an incomplete or relative path */ if (volume && strlen(volume) > 0) { newpath = apr_pcalloc(p, strlen(server)+strlen(volume)+5); construct(newpath, server, volume, NULL, NULL, NULL, PATH_NETWARE); /* NetWare doesn't add the root slash so we need to add it manually. */ strcat(newpath, seperator); *rootpath = newpath; /* Skip the inpath pointer down to the first non-root character */ newpath = volsep; do { ++newpath; } while (*newpath && ((*newpath == '/') || (*newpath == '\\'))); *inpath = newpath; /* Need to handle APR_FILEPATH_TRUENAME checking here. */ return APR_SUCCESS; } else return APR_EBADPATH; } else if ((**inpath == '/') || (**inpath == '\\')) { /* if we have a root path without a volume then just split in same manner as unix although this path will be incomplete. */ *rootpath = apr_pstrdup(p, seperator); do { ++(*inpath); } while ((**inpath == '/') || (**inpath == '\\')); } else return APR_ERELATIVE; return APR_EINCOMPLETE; #else /* ndef(NETWARE) */ char seperator[2]; const char *delim1; const char *delim2; seperator[0] = (flags & APR_FILEPATH_NATIVE) ? '\\' : '/'; seperator[1] = 0; if (testpath[0] == '/' || testpath[0] == '\\') { if (testpath[1] == '/' || testpath[1] == '\\') { #ifdef WIN32 /* //server/share isn't the only // delimited syntax */ if ((testpath[2] == '?' || testpath[2] == '.') && (testpath[3] == '/' || testpath[3] == '\\')) { if (IS_FNCHAR(testpath[4]) && testpath[5] == ':') { apr_status_t rv; testpath += 4; /* given '//?/C: or //./C: let us try this * all over again from the drive designator */ rv = apr_filepath_root(rootpath, &testpath, flags, p); if (!rv || rv == APR_EINCOMPLETE) *inpath = testpath; return rv; } else if (strncasecmp(testpath + 4, "UNC", 3) == 0 && (testpath[7] == '/' || testpath[7] == '\\') && (testpath[2] == '?')) { /* given '//?/UNC/machine/share, a little magic * at the end makes this all work out by using * 'C/machine' as the starting point and replacing * the UNC delimiters with \'s, including the 'C' */ testpath += 6; } else /* This must not be a path to a file, but rather * a volume or device. Die for now. */ return APR_EBADPATH; } #endif /* WIN32 (non - //server/share syntax) */ /* Evaluate path of '//[machine/[share[/]]]' */ delim1 = testpath + 2; do { /* Protect against //X/ where X is illegal */ if (*delim1 && !IS_FNCHAR(*(delim1++))) return APR_EBADPATH; } while (*delim1 && *delim1 != '/' && *delim1 != '\\'); if (*delim1) { apr_status_t rv; delim2 = delim1 + 1; while (*delim2 && *delim2 != '/' && *delim2 != '\\') { /* Protect against //machine/X/ where X is illegal */ if (!IS_FNCHAR(*(delim2++))) return APR_EBADPATH; } /* Copy the '//machine/[share[/]]' path, always providing * an extra byte for the trailing slash. */ newpath = apr_pstrmemdup(p, testpath, delim2 - testpath + 1); if (delim2 == delim1 + 1) { /* We found simply \\machine\, so give up already */ *rootpath = newpath; *inpath = delim2; return APR_EINCOMPLETE; } if (flags & APR_FILEPATH_TRUENAME) { /* Validate the \\Machine\Share\ designation, * Win32 will argue about slashed in UNC paths, * so use backslashes till we finish testing, * and add the trailing backslash [required]. * apr_pstrmemdup above guarentees us the new * trailing null character. */ newpath[0] = '\\'; newpath[1] = '\\'; newpath[delim1 - testpath] = '\\'; newpath[delim2 - testpath] = '\\'; rv = filepath_root_test(newpath, p); if (rv) return rv; rv = filepath_root_case(&newpath, newpath, p); if (rv) return rv; newpath[0] = seperator[0]; newpath[1] = seperator[0]; newpath[delim1 - testpath] = seperator[0]; newpath[delim2 - testpath] = (*delim2 ? seperator[0] : '\0'); } else { /* Give back the caller's own choice of delimiters */ newpath[0] = testpath[0]; newpath[1] = testpath[1]; newpath[delim1 - testpath] = *delim1; newpath[delim2 - testpath] = *delim2; } /* If this root included the trailing / or \ designation * then lop off multiple trailing slashes and give back * appropriate delimiters. */ if (*delim2) { *inpath = delim2 + 1; while (**inpath == '/' || **inpath == '\\') ++*inpath; } else { *inpath = delim2; } *rootpath = newpath; return APR_SUCCESS; } /* Have path of '\\[machine]', if the machine is given, * append same trailing slash as the leading slash */ delim1 = strchr(testpath, '\0'); if (delim1 > testpath + 2) { newpath = apr_pstrndup(p, testpath, delim1 - testpath + 1); if (flags & APR_FILEPATH_TRUENAME) newpath[delim1 - testpath] = seperator[0]; else newpath[delim1 - testpath] = newpath[0]; newpath[delim1 - testpath + 1] = '\0'; } else { newpath = apr_pstrndup(p, testpath, delim1 - testpath); } if (flags & APR_FILEPATH_TRUENAME) { newpath[0] = seperator[0]; newpath[1] = seperator[0]; } *rootpath = newpath; *inpath = delim1; return APR_EINCOMPLETE; } /* Left with a path of '/', what drive are we asking about? */ *inpath = testpath + 1; newpath = apr_palloc(p, 2); if (flags & APR_FILEPATH_TRUENAME) newpath[0] = seperator[0]; else newpath[0] = testpath[0]; newpath[1] = '\0'; *rootpath = newpath; return APR_EINCOMPLETE; } /* Evaluate path of 'd:[/]' */ if (IS_FNCHAR(*testpath) && testpath[1] == ':') { apr_status_t rv; /* Validate that D:\ drive exists, test must be rooted * Note that posix/win32 insists a drive letter is upper case, * so who are we to argue with a 'feature'. * It is a safe fold, since only A-Z is legal, and has no * side effects of legal mis-mapped non-us-ascii codes. */ newpath = apr_palloc(p, 4); newpath[0] = testpath[0]; newpath[1] = testpath[1]; newpath[2] = seperator[0]; newpath[3] = '\0'; if (flags & APR_FILEPATH_TRUENAME) { newpath[0] = apr_toupper(newpath[0]); rv = filepath_root_test(newpath, p); if (rv) return rv; } /* Just give back the root the user handed to us. */ if (testpath[2] != '/' && testpath[2] != '\\') { newpath[2] = '\0'; *rootpath = newpath; *inpath = testpath + 2; return APR_EINCOMPLETE; } /* strip off remaining slashes that designate the root, * give the caller back their original choice of slash * unless this is TRUENAME'ed */ *inpath = testpath + 3; while (**inpath == '/' || **inpath == '\\') ++*inpath; if (!(flags & APR_FILEPATH_TRUENAME)) newpath[2] = testpath[2]; *rootpath = newpath; return APR_SUCCESS; } /* Nothing interesting */ return APR_ERELATIVE; #endif /* ndef(NETWARE) */ } APR_DECLARE(apr_status_t) apr_filepath_merge(char **newpath, const char *basepath, const char *addpath, apr_int32_t flags, apr_pool_t *p) { char path[APR_PATH_MAX]; /* isn't null term */ const char *baseroot = NULL; const char *addroot; apr_size_t rootlen; /* the length of the root portion of path, d:/ is 3 */ apr_size_t baselen; /* the length of basepath (excluding baseroot) */ apr_size_t keptlen; /* the length of the retained basepath (incl root) */ apr_size_t pathlen; /* the length of the result path */ apr_size_t segend; /* the end of the current segment */ apr_size_t seglen; /* the length of the segment (excl trailing chars) */ apr_status_t basetype = 0; /* from parsing the basepath's baseroot */ apr_status_t addtype; /* from parsing the addpath's addroot */ apr_status_t rv; #ifndef NETWARE int fixunc = 0; /* flag to complete an incomplete UNC basepath */ #endif /* Treat null as an empty path, otherwise split addroot from the addpath */ if (!addpath) { addpath = addroot = ""; addtype = APR_ERELATIVE; } else { /* This call _should_ test the path */ addtype = apr_filepath_root(&addroot, &addpath, APR_FILEPATH_TRUENAME | (flags & APR_FILEPATH_NATIVE), p); if (addtype == APR_SUCCESS) { addtype = APR_EABSOLUTE; } else if (addtype == APR_ERELATIVE) { addroot = ""; } else if (addtype != APR_EINCOMPLETE) { /* apr_filepath_root was incomprehensible so fail already */ return addtype; } } /* If addpath is (even partially) rooted, then basepath is * unused. Ths violates any APR_FILEPATH_SECUREROOTTEST * and APR_FILEPATH_NOTABSOLUTE flags specified. */ if (addtype == APR_EABSOLUTE || addtype == APR_EINCOMPLETE) { if (flags & APR_FILEPATH_SECUREROOTTEST) return APR_EABOVEROOT; if (flags & APR_FILEPATH_NOTABSOLUTE) return addtype; } /* Optimized tests before we query the current working path */ if (!basepath) { /* If APR_FILEPATH_NOTABOVEROOT wasn't specified, * we won't test the root again, it's ignored. * Waste no CPU retrieving the working path. */ if (addtype == APR_EABSOLUTE && !(flags & APR_FILEPATH_NOTABOVEROOT)) { basepath = baseroot = ""; basetype = APR_ERELATIVE; } /* If APR_FILEPATH_NOTABSOLUTE is specified, the caller * requires an absolutely relative result, So do not retrieve * the working path. */ if (addtype == APR_ERELATIVE && (flags & APR_FILEPATH_NOTABSOLUTE)) { basepath = baseroot = ""; basetype = APR_ERELATIVE; } } if (!basepath) { /* Start with the current working path. This is bass akwards, * but required since the compiler (at least vc) doesn't like * passing the address of a char const* for a char** arg. * We must grab the current path of the designated drive * if addroot is given in drive-relative form (e.g. d:foo) */ char *getpath; #ifndef NETWARE if (addtype == APR_EINCOMPLETE && addroot[1] == ':') rv = filepath_drive_get(&getpath, addroot[0], flags, p); else #endif rv = apr_filepath_get(&getpath, flags, p); if (rv != APR_SUCCESS) return rv; basepath = getpath; } if (!baseroot) { /* This call should _not_ test the path */ basetype = apr_filepath_root(&baseroot, &basepath, (flags & APR_FILEPATH_NATIVE), p); if (basetype == APR_SUCCESS) { basetype = APR_EABSOLUTE; } else if (basetype == APR_ERELATIVE) { baseroot = ""; } else if (basetype != APR_EINCOMPLETE) { /* apr_filepath_root was incomprehensible so fail already */ return basetype; } } baselen = strlen(basepath); /* If APR_FILEPATH_NOTABSOLUTE is specified, the caller * requires an absolutely relative result. If the given * basepath is not relative then fail. */ if ((flags & APR_FILEPATH_NOTABSOLUTE) && basetype != APR_ERELATIVE) return basetype; /* The Win32 nightmare on unc street... start combining for * many possible root combinations. */ if (addtype == APR_EABSOLUTE) { /* Ignore the given root path, and start with the addroot */ if ((flags & APR_FILEPATH_NOTABOVEROOT) && strncmp(baseroot, addroot, strlen(baseroot))) return APR_EABOVEROOT; keptlen = 0; rootlen = pathlen = strlen(addroot); memcpy(path, addroot, pathlen); } else if (addtype == APR_EINCOMPLETE) { /* There are several types of incomplete paths, * incomplete UNC paths (//foo/ or //), * drives without rooted paths (d: as in d:foo), * and simple roots (/ as in /foo). * Deal with these in significantly different manners... */ #ifndef NETWARE if ((addroot[0] == '/' || addroot[0] == '\\') && (addroot[1] == '/' || addroot[1] == '\\')) { /* Ignore the given root path if the incomplete addpath is UNC, * (note that the final result will be incomplete). */ if (flags & APR_FILEPATH_NOTRELATIVE) return addtype; if ((flags & APR_FILEPATH_NOTABOVEROOT) && strncmp(baseroot, addroot, strlen(baseroot))) return APR_EABOVEROOT; fixunc = 1; keptlen = 0; rootlen = pathlen = strlen(addroot); memcpy(path, addroot, pathlen); } else #endif if ((addroot[0] == '/' || addroot[0] == '\\') && !addroot[1]) { /* Bring together the drive or UNC root from the baseroot * if the addpath is a simple root and basepath is rooted, * otherwise disregard the basepath entirely. */ if (basetype != APR_EABSOLUTE && (flags & APR_FILEPATH_NOTRELATIVE)) return basetype; if (basetype != APR_ERELATIVE) { #ifndef NETWARE if (basetype == APR_INCOMPLETE && (baseroot[0] == '/' || baseroot[0] == '\\') && (baseroot[1] == '/' || baseroot[1] == '\\')) fixunc = 1; #endif keptlen = rootlen = pathlen = strlen(baseroot); memcpy(path, baseroot, pathlen); } else { if (flags & APR_FILEPATH_NOTABOVEROOT) return APR_EABOVEROOT; keptlen = 0; rootlen = pathlen = strlen(addroot); memcpy(path, addroot, pathlen); } } #ifdef NETWARE else if (filepath_has_drive(addroot, DRIVE_ONLY, p)) { /* If the addroot is a drive (without a volume root) * use the basepath _if_ it matches this drive letter! * Otherwise we must discard the basepath. */ if (!filepath_compare_drive(addroot, baseroot, p) && filepath_has_drive(baseroot, 0, p)) { #else else if (addroot[0] && addroot[1] == ':' && !addroot[2]) { /* If the addroot is a drive (without a volume root) * use the basepath _if_ it matches this drive letter! * Otherwise we must discard the basepath. */ if (addroot[0] == baseroot[0] && baseroot[1] == ':') { #endif /* Base the result path on the basepath */ if (basetype != APR_EABSOLUTE && (flags & APR_FILEPATH_NOTRELATIVE)) return basetype; rootlen = strlen(baseroot); keptlen = pathlen = rootlen + baselen; if (keptlen >= sizeof(path)) return APR_ENAMETOOLONG; memcpy(path, baseroot, rootlen); memcpy(path + rootlen, basepath, baselen); } else { if (flags & APR_FILEPATH_NOTRELATIVE) return addtype; if (flags & APR_FILEPATH_NOTABOVEROOT) return APR_EABOVEROOT; keptlen = 0; rootlen = pathlen = strlen(addroot); memcpy(path, addroot, pathlen); } } else { /* Now this is unexpected, we aren't aware of any other * incomplete path forms! Fail now. */ return APR_EBADPATH; } } else { /* addtype == APR_ERELATIVE */ /* If both paths are relative, fail early */ if (basetype != APR_EABSOLUTE && (flags & APR_FILEPATH_NOTRELATIVE)) return basetype; #ifndef NETWARE /* An incomplete UNC path must be completed */ if (basetype == APR_INCOMPLETE && (baseroot[0] == '/' || baseroot[0] == '\\') && (baseroot[1] == '/' || baseroot[1] == '\\')) fixunc = 1; #endif /* Base the result path on the basepath */ rootlen = strlen(baseroot); keptlen = pathlen = rootlen + baselen; if (keptlen >= sizeof(path)) return APR_ENAMETOOLONG; memcpy(path, baseroot, rootlen); memcpy(path + rootlen, basepath, baselen); } /* '/' terminate the given root path unless it's already terminated * or is an incomplete drive root. Correct the trailing slash unless * we have an incomplete UNC path still to fix. */ if (pathlen && path[pathlen - 1] != ':') { if (path[pathlen - 1] != '/' && path[pathlen - 1] != '\\') { if (pathlen + 1 >= sizeof(path)) return APR_ENAMETOOLONG; path[pathlen++] = ((flags & APR_FILEPATH_NATIVE) ? '\\' : '/'); } /* XXX: wrong, but gotta figure out what I intended; * else if (!fixunc) * path[pathlen++] = ((flags & APR_FILEPATH_NATIVE) ? '\\' : '/'); */ } while (*addpath) { /* Parse each segment, find the closing '/' */ seglen = 0; while (addpath[seglen] && addpath[seglen] != '/' && addpath[seglen] != '\\') ++seglen; /* Truncate all trailing spaces and all but the first two dots */ segend = seglen; while (seglen && (addpath[seglen - 1] == ' ' || addpath[seglen - 1] == '.')) { if (seglen > 2 || addpath[seglen - 1] != '.' || addpath[0] != '.') --seglen; else break; } if (seglen == 0 || (seglen == 1 && addpath[0] == '.')) { /* NOTE: win32 _hates_ '/ /' and '/. /' (yes, with spaces in there) * so eliminate all preconceptions that it is valid. */ if (seglen < segend) return APR_EBADPATH; #ifndef NETWARE /* This isn't legal unless the unc path is completed */ if (fixunc) return APR_EBADPATH; #endif /* Otherwise, this is a noop segment (/ or ./) so ignore it */ } else if (seglen == 2 && addpath[0] == '.' && addpath[1] == '.') { /* NOTE: win32 _hates_ '/.. /' (yes, with a space in there) * and '/..../', some functions treat it as ".", and some * fail! Eliminate all preconceptions that they are valid. */ if (seglen < segend && (seglen != 3 || addpath[2] != '.')) return APR_EBADPATH; #ifndef NETWARE /* This isn't legal unless the unc path is completed */ if (fixunc) return APR_EBADPATH; #endif /* backpath (../) when an absolute path is given */ if (rootlen && (pathlen <= rootlen)) { /* Attempt to move above root. Always die if the * APR_FILEPATH_SECUREROOTTEST flag is specified. */ if (flags & APR_FILEPATH_SECUREROOTTEST) return APR_EABOVEROOT; /* Otherwise this is simply a noop, above root is root. */ } else if (pathlen == 0 || (pathlen >= 3 && (pathlen == 3 || path[pathlen - 4] == ':' || path[pathlen - 4] == '/' || path[pathlen - 4] == '\\') && path[pathlen - 3] == '.' && path[pathlen - 2] == '.' && (path[pathlen - 1] == '/' || path[pathlen - 1] == '\\'))) { /* Verified path is empty, exactly "..[/\]", or ends * in "[:/\]..[/\]" - these patterns we will not back * over since they aren't 'prior segements'. * * If APR_FILEPATH_SECUREROOTTEST.was given, die now. */ if (flags & APR_FILEPATH_SECUREROOTTEST) return APR_EABOVEROOT; /* Otherwise append another backpath. */ if (pathlen + 3 >= sizeof(path)) return APR_ENAMETOOLONG; path[pathlen++] = '.'; path[pathlen++] = '.'; if (addpath[segend]) { path[pathlen++] = ((flags & APR_FILEPATH_NATIVE) ? '\\' : ((flags & APR_FILEPATH_TRUENAME) ? '/' : addpath[segend])); } /* The 'root' part of this path now includes the ../ path, * because that backpath will not be parsed by the truename * code below. */ keptlen = pathlen; } else { /* otherwise crop the prior segment */ do { --pathlen; } while (pathlen && path[pathlen - 1] != '/' && path[pathlen - 1] != '\\'); /* Now test if we are above where we started and back up * the keptlen offset to reflect the added/altered path. */ if (pathlen < keptlen) { if (flags & APR_FILEPATH_SECUREROOTTEST) return APR_EABOVEROOT; keptlen = pathlen; } } } else /* not empty or dots */ { #ifndef NETWARE if (fixunc) { const char *testpath = path; const char *testroot; apr_status_t testtype; apr_size_t i = (addpath[segend] != '\0'); /* This isn't legal unless the unc path is complete! */ if (seglen < segend) return APR_EBADPATH; if (pathlen + seglen + 1 >= sizeof(path)) return APR_ENAMETOOLONG; memcpy(path + pathlen, addpath, seglen + i); /* Always add the trailing slash to a UNC segment */ path[pathlen + seglen] = ((flags & APR_FILEPATH_NATIVE) ? '\\' : '/'); pathlen += seglen + 1; /* Recanonicalize the UNC root with the new UNC segment, * and if we succeed, reset this test and the rootlen, * and replace our path with the canonical UNC root path */ path[pathlen] = '\0'; /* This call _should_ test the path */ testtype = apr_filepath_root(&testroot, &testpath, APR_FILEPATH_TRUENAME | (flags & APR_FILEPATH_NATIVE), p); if (testtype == APR_SUCCESS) { rootlen = pathlen = (testpath - path); memcpy(path, testroot, pathlen); fixunc = 0; } else if (testtype != APR_EINCOMPLETE) { /* apr_filepath_root was very unexpected so fail already */ return testtype; } } else #endif { /* An actual segment, append it to the destination path */ apr_size_t i = (addpath[segend] != '\0'); if (pathlen + seglen + i >= sizeof(path)) return APR_ENAMETOOLONG; memcpy(path + pathlen, addpath, seglen + i); if (i) path[pathlen + seglen] = ((flags & APR_FILEPATH_NATIVE) ? '\\' : '/'); pathlen += seglen + i; } } /* Skip over trailing slash to the next segment */ if (addpath[segend]) ++segend; addpath += segend; } /* keptlen will be the baselen unless the addpath contained * backpath elements. If so, and APR_FILEPATH_NOTABOVEROOT * is specified (APR_FILEPATH_SECUREROOTTEST was caught above), * compare the string beyond the root to assure the result path * is still within given basepath. Note that the root path * segment is thoroughly tested prior to path parsing. */ if ((flags & APR_FILEPATH_NOTABOVEROOT) && baselen) { if (memcmp(basepath, path + rootlen, baselen) != 0) return APR_EABOVEROOT; /* Ahem... if we have a basepath without a trailing slash, * we better be sure that /foo wasn't replaced with /foobar! */ if (basepath[baselen - 1] != '/' && basepath[baselen - 1] != '\\' && path[rootlen + baselen] && path[rootlen + baselen] != '/' && path[rootlen + baselen] != '\\') return APR_EABOVEROOT; } if (addpath && (flags & APR_FILEPATH_TRUENAME)) { /* We can always skip the root, it's already true-named. */ if (rootlen > keptlen) keptlen = rootlen; if ((path[keptlen] == '/') || (path[keptlen] == '\\')) { /* By rights, keptlen may grown longer than pathlen. * we wont' use it again (in that case) so we don't care. */ ++keptlen; } /* Go through all the new segments */ while (keptlen < pathlen) { apr_finfo_t finfo; char saveslash = 0; seglen = 0; /* find any slash and set it aside for a minute. */ for (seglen = 0; keptlen + seglen < pathlen; ++seglen) { if ((path[keptlen + seglen] == '/') || (path[keptlen + seglen] == '\\')) { saveslash = path[keptlen + seglen]; break; } } /* Null term for stat! */ path[keptlen + seglen] = '\0'; if ((rv = apr_lstat(&finfo, path, APR_FINFO_TYPE | APR_FINFO_NAME, p)) == APR_SUCCESS) { apr_size_t namelen = strlen(finfo.name); #if defined(OS2) /* only has case folding, never aliases that change the length */ if (memcmp(finfo.name, path + keptlen, seglen) != 0) { memcpy(path + keptlen, finfo.name, namelen); } #else /* WIN32 || NETWARE; here there be aliases that gire and gimble and change length */ if ((namelen != seglen) || (memcmp(finfo.name, path + keptlen, seglen) != 0)) { if (namelen <= seglen) { memcpy(path + keptlen, finfo.name, namelen); if ((namelen < seglen) && saveslash) { memmove(path + keptlen + namelen + 1, path + keptlen + seglen + 1, pathlen - keptlen - seglen); pathlen += namelen - seglen; seglen = namelen; } } else { /* namelen > seglen */ if (pathlen + namelen - seglen >= sizeof(path)) return APR_ENAMETOOLONG; if (saveslash) { memmove(path + keptlen + namelen + 1, path + keptlen + seglen + 1, pathlen - keptlen - seglen); } memcpy(path + keptlen, finfo.name, namelen); pathlen += namelen - seglen; seglen = namelen; } } #endif /* !OS2 (Whatever that alias was we're over it) */ /* That's it, the rest is path info. * I don't know how we aught to handle this. Should * we define a new error to indicate 'more info'? * Should we split out the rest of the path? */ if ((finfo.filetype != APR_DIR) && (finfo.filetype != APR_LNK) && saveslash) rv = APR_ENOTDIR; #ifdef XXX_FIGURE_THIS_OUT { /* the example inserts a null between the end of * the filename and the next segment, and increments * the path length so we would return both segments. */ if (saveslash) { keptlen += seglen; path[keptlen] = saveslash; if (pathlen + 1 >= sizeof(path)) return APR_ENAMETOOLONG; memmove(path + keptlen + 1, path + keptlen, pathlen - keptlen); path[keptlen] = '\0'; ++pathlen; break; } } #endif } /* put back the '/' */ if (saveslash) { path[keptlen + seglen] = saveslash; ++seglen; } keptlen += seglen; if (rv != APR_SUCCESS) { if (APR_STATUS_IS_ENOENT(rv)) break; if (APR_STATUS_IS_EPATHWILD(rv)) /* This path included wildcards. The path elements * that did not contain wildcards are canonicalized, * so we will return the path, although later elements * don't necessarily exist, and aren't canonical. */ break; else if (APR_STATUS_IS_ENOTDIR(rv)) /* This is a little more serious, we just added a name * onto a filename (think http's PATH_INFO) * If the caller is foolish enough to do this, we expect * the've already canonicalized the root) that they knew * what they are doing :( */ break; else return rv; } } } *newpath = apr_pmemdup(p, path, pathlen + 1); (*newpath)[pathlen] = '\0'; return APR_SUCCESS; } APR_DECLARE(apr_status_t) apr_filepath_list_split(apr_array_header_t **pathelts, const char *liststr, apr_pool_t *p) { return apr_filepath_list_split_impl(pathelts, liststr, ';', p); } APR_DECLARE(apr_status_t) apr_filepath_list_merge(char **liststr, apr_array_header_t *pathelts, apr_pool_t *p) { return apr_filepath_list_merge_impl(liststr, pathelts, ';', p); } APR_DECLARE(apr_status_t) apr_filepath_encoding(int *style, apr_pool_t *p) { #if APR_HAS_UNICODE_FS IF_WIN_OS_IS_UNICODE { *style = APR_FILEPATH_ENCODING_UTF8; return APR_SUCCESS; } #endif *style = APR_FILEPATH_ENCODING_LOCALE; return APR_SUCCESS; }