/* ==================================================================== * The Apache Software License, Version 1.1 * * Copyright (c) 2000-2001 The Apache Software Foundation. All rights * reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. The end-user documentation included with the redistribution, * if any, must include the following acknowledgment: * "This product includes software developed by the * Apache Software Foundation (http://www.apache.org/)." * Alternately, this acknowledgment may appear in the software itself, * if and wherever such third-party acknowledgments normally appear. * * 4. The names "Apache" and "Apache Software Foundation" must * not be used to endorse or promote products derived from this * software without prior written permission. For written * permission, please contact apache@apache.org. * * 5. Products derived from this software may not be called "Apache", * nor may "Apache" appear in their name, without prior written * permission of the Apache Software Foundation. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * ==================================================================== * * This software consists of voluntary contributions made by many * individuals on behalf of the Apache Software Foundation. For more * information on the Apache Software Foundation, please see * . */ #include "apr.h" #include "fileio.h" #include "apr_file_io.h" #include "apr_strings.h" #if APR_HAVE_UNISTD_H #include #endif #include /* Any OS that requires/refuses trailing slashes should be delt with here. */ APR_DECLARE(apr_status_t) apr_filepath_get(char **defpath, apr_pool_t *p) { char path[APR_PATH_MAX]; if (!getcwd(path, sizeof(path))) { if (errno == ERANGE) return APR_ENAMETOOLONG; else return errno; } *defpath = apr_pstrdup(p, path); return APR_SUCCESS; } /* Any OS that requires/refuses trailing slashes should be delt with here */ APR_DECLARE(apr_status_t) apr_filepath_set(const char *path, apr_pool_t *p) { if (chdir(path) != 0) return errno; return APR_SUCCESS; } APR_DECLARE(apr_status_t) apr_filepath_parse_root(const char **rootpath, const char **inpath, apr_pool_t *p) { if (**inpath == '/') { *rootpath = apr_pstrdup(p, "/"); ++*inpath; return APR_EABSOLUTE; } return APR_ERELATIVE; } APR_DECLARE(apr_status_t) apr_filepath_merge(char **newpath, const char *rootpath, const char *addpath, apr_int32_t flags, apr_pool_t *p) { char path[APR_PATH_MAX]; apr_size_t rootlen; /* is the original rootpath len */ apr_size_t newseg; /* is the path offset to the added path */ apr_size_t addseg; /* is the path offset we are appending at */ apr_size_t endseg; /* is the end of the current segment */ apr_status_t rv; /* Treat null as an empty path. */ if (!addpath) addpath = ""; if (addpath[0] == '/') { /* If addpath is rooted, then rootpath is unused. * Ths violates any APR_FILEPATH_SECUREROOTTEST and * APR_FILEPATH_NOTABSOLUTE flags specified. */ if (flags & APR_FILEPATH_SECUREROOTTEST) return APR_EABOVEROOT; if (flags & APR_FILEPATH_NOTABSOLUTE) return APR_EABSOLUTE; /* 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 (!rootpath && !(flags & APR_FILEPATH_NOTABOVEROOT)) rootpath = ""; } else { /* If APR_FILEPATH_NOTABSOLUTE is specified, the caller * requires a relative result. If the rootpath is * ommitted, we do not retrieve the working path, * if rootpath was supplied as absolute then fail. */ if (flags & APR_FILEPATH_NOTABSOLUTE) { if (!rootpath) rootpath = ""; else if (rootpath[0] == '/') return APR_EABSOLUTE; } } if (!rootpath) { /* 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. */ char *getpath; rv = apr_filepath_get(&getpath, p); rootpath = getpath; if (rv != APR_SUCCESS) return errno; /* XXX: Any kernel subject to goofy, uncanonical results * must run the rootpath against the user's given flags. * Simplest would be a recursive call to apr_filepath_merge * with an empty (not null) rootpath and addpath of the cwd. */ } rootlen = strlen(rootpath); if (addpath[0] == '/') { /* Ignore the given root path, strip off leading * '/'s to a single leading '/' from the addpath, * and leave addpath at the first non-'/' character. */ newseg = 0; while (addpath[0] == '/') ++addpath; strcpy (path, "/"); addseg = 1; } else { /* If both paths are relative, fail early */ if (rootpath[0] != '/' && (flags & APR_FILEPATH_NOTRELATIVE)) return APR_ERELATIVE; /* Base the result path on the rootpath */ newseg = rootlen; if (rootlen >= sizeof(path)) return APR_ENAMETOOLONG; strcpy(path, rootpath); /* Always '/' terminate the given root path */ if (newseg && path[newseg - 1] != '/') { if (newseg + 1 >= sizeof(path)) return APR_ENAMETOOLONG; path[newseg++] = '/'; path[newseg] = '\0'; } addseg = newseg; } while (*addpath) { /* Parse each segment, find the closing '/' */ endseg = 0; while (addpath[endseg] && addpath[endseg] != '/') ++endseg; if (endseg == 0 || (endseg == 1 && addpath[0] == '.')) { /* noop segment (/ or ./) so skip it */ } else if (endseg == 2 && addpath[0] == '.' && addpath[1] == '.') { /* backpath (../) */ if (addseg == 1 && path[0] == '/') { /* 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. * Flag that rootpath was entirely replaced. */ newseg = 0; } else if (addseg == 0 || (addseg >= 3 && strcmp(path + addseg - 3, "../") == 0)) { /* Path is already backpathed or empty, if the * APR_FILEPATH_SECUREROOTTEST.was given die now. */ if (flags & APR_FILEPATH_SECUREROOTTEST) return APR_EABOVEROOT; /* Otherwise append another backpath. */ if (addseg + 3 >= sizeof(path)) return APR_ENAMETOOLONG; strcpy(path + addseg, "../"); addseg += 3; } else { /* otherwise crop the prior segment */ do { --addseg; } while (addseg && path[addseg - 1] != '/'); path[addseg] = '\0'; } /* Now test if we are above where we started and back up * the newseg offset to reflect the added/altered path. */ if (addseg < newseg) { if (flags & APR_FILEPATH_SECUREROOTTEST) return APR_EABOVEROOT; newseg = addseg; } } else { /* An actual segment, append it to the destination path */ apr_size_t i = (addpath[endseg] != '\0'); if (addseg + endseg + i >= sizeof(path)) return APR_ENAMETOOLONG; strncpy(path + addseg, addpath, endseg + i); addseg += endseg + i; } /* Skip over trailing slash to the next segment */ if (addpath[endseg]) ++endseg; addpath += endseg; } /* newseg will be the rootlen unless the addpath contained * backpath elements. If so, and APR_FILEPATH_NOTABOVEROOT * is specified (APR_FILEPATH_SECUREROOTTEST was caught above), * compare the original root to assure the result path is * still within given root path. */ if ((flags & APR_FILEPATH_NOTABOVEROOT) && newseg < rootlen) { if (strncmp(rootpath, path, rootlen)) return APR_EABOVEROOT; if (rootpath[rootlen - 1] != '/' && path[rootlen] && path[rootlen] != '/') return APR_EABOVEROOT; } *newpath = apr_pstrdup(p, path); return (newpath ? APR_SUCCESS : APR_ENOMEM); }