forked from Mirrors/freeswitch
0488a5109a
git-svn-id: http://svn.freeswitch.org/svn/freeswitch/trunk@3733 d0543943-73ff-0310-b7d9-9358b9ac24b2
986 lines
37 KiB
C
986 lines
37 KiB
C
/* 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 <string.h>
|
|
#include <ctype.h>
|
|
|
|
#ifdef NETWARE
|
|
#include <unistd.h>
|
|
#include <fsio.h>
|
|
#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_stat(&finfo, path,
|
|
APR_FINFO_LINK | 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;
|
|
}
|