1/* Licensed to the Apache Software Foundation (ASF) under one or more
2 * contributor license agreements.  See the NOTICE file distributed with
3 * this work for additional information regarding copyright ownership.
4 * The ASF licenses this file to You under the Apache License, Version 2.0
5 * (the "License"); you may not use this file except in compliance with
6 * the License.  You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include "apr.h"
18#include "apr_private.h"
19#include "apr_arch_file_io.h"
20#include "apr_file_io.h"
21#include "apr_strings.h"
22#define APR_WANT_STRFUNC
23#include "apr_want.h"
24#if APR_HAVE_UNISTD_H
25#include <unistd.h>
26#endif
27
28/* Win32 malpropism that can go away once everyone believes this
29 * code is golden, and I'm not testing it anymore :-)
30 */
31#if APR_HAVE_DIRENT_H
32#include <dirent.h>
33#endif
34
35/* Any OS that requires/refuses trailing slashes should be dealt with here.
36 */
37APR_DECLARE(apr_status_t) apr_filepath_get(char **defpath, apr_int32_t flags,
38                                           apr_pool_t *p)
39{
40    char path[APR_PATH_MAX];
41
42    if (!getcwd(path, sizeof(path))) {
43        if (errno == ERANGE)
44            return APR_ENAMETOOLONG;
45        else
46            return errno;
47    }
48    *defpath = apr_pstrdup(p, path);
49
50    return APR_SUCCESS;
51}
52
53
54/* Any OS that requires/refuses trailing slashes should be dealt with here
55 */
56APR_DECLARE(apr_status_t) apr_filepath_set(const char *path, apr_pool_t *p)
57{
58    if (chdir(path) != 0)
59        return errno;
60
61    return APR_SUCCESS;
62}
63
64APR_DECLARE(apr_status_t) apr_filepath_root(const char **rootpath,
65                                            const char **inpath,
66                                            apr_int32_t flags,
67                                            apr_pool_t *p)
68{
69    if (**inpath == '/') {
70        *rootpath = apr_pstrdup(p, "/");
71        do {
72            ++(*inpath);
73        } while (**inpath == '/');
74
75        return APR_SUCCESS;
76    }
77
78    return APR_ERELATIVE;
79}
80
81APR_DECLARE(apr_status_t) apr_filepath_merge(char **newpath,
82                                             const char *rootpath,
83                                             const char *addpath,
84                                             apr_int32_t flags,
85                                             apr_pool_t *p)
86{
87    char *path;
88    apr_size_t rootlen; /* is the length of the src rootpath */
89    apr_size_t maxlen;  /* maximum total path length */
90    apr_size_t keptlen; /* is the length of the retained rootpath */
91    apr_size_t pathlen; /* is the length of the result path */
92    apr_size_t seglen;  /* is the end of the current segment */
93    apr_status_t rv;
94
95    /* Treat null as an empty path.
96     */
97    if (!addpath)
98        addpath = "";
99
100    if (addpath[0] == '/') {
101        /* If addpath is rooted, then rootpath is unused.
102         * Ths violates any APR_FILEPATH_SECUREROOTTEST and
103         * APR_FILEPATH_NOTABSOLUTE flags specified.
104         */
105        if (flags & APR_FILEPATH_SECUREROOTTEST)
106            return APR_EABOVEROOT;
107        if (flags & APR_FILEPATH_NOTABSOLUTE)
108            return APR_EABSOLUTE;
109
110        /* If APR_FILEPATH_NOTABOVEROOT wasn't specified,
111         * we won't test the root again, it's ignored.
112         * Waste no CPU retrieving the working path.
113         */
114        if (!rootpath && !(flags & APR_FILEPATH_NOTABOVEROOT))
115            rootpath = "";
116    }
117    else {
118        /* If APR_FILEPATH_NOTABSOLUTE is specified, the caller
119         * requires a relative result.  If the rootpath is
120         * ommitted, we do not retrieve the working path,
121         * if rootpath was supplied as absolute then fail.
122         */
123        if (flags & APR_FILEPATH_NOTABSOLUTE) {
124            if (!rootpath)
125                rootpath = "";
126            else if (rootpath[0] == '/')
127                return APR_EABSOLUTE;
128        }
129    }
130
131    if (!rootpath) {
132        /* Start with the current working path.  This is bass akwards,
133         * but required since the compiler (at least vc) doesn't like
134         * passing the address of a char const* for a char** arg.
135         */
136        char *getpath;
137        rv = apr_filepath_get(&getpath, flags, p);
138        rootpath = getpath;
139        if (rv != APR_SUCCESS)
140            return errno;
141
142        /* XXX: Any kernel subject to goofy, uncanonical results
143         * must run the rootpath against the user's given flags.
144         * Simplest would be a recursive call to apr_filepath_merge
145         * with an empty (not null) rootpath and addpath of the cwd.
146         */
147    }
148
149    rootlen = strlen(rootpath);
150    maxlen = rootlen + strlen(addpath) + 4; /* 4 for slashes at start, after
151                                             * root, and at end, plus trailing
152                                             * null */
153    if (maxlen > APR_PATH_MAX) {
154        return APR_ENAMETOOLONG;
155    }
156    path = (char *)apr_palloc(p, maxlen);
157
158    if (addpath[0] == '/') {
159        /* Ignore the given root path, strip off leading
160         * '/'s to a single leading '/' from the addpath,
161         * and leave addpath at the first non-'/' character.
162         */
163        keptlen = 0;
164        while (addpath[0] == '/')
165            ++addpath;
166        path[0] = '/';
167        pathlen = 1;
168    }
169    else {
170        /* If both paths are relative, fail early
171         */
172        if (rootpath[0] != '/' && (flags & APR_FILEPATH_NOTRELATIVE))
173            return APR_ERELATIVE;
174
175        /* Base the result path on the rootpath
176         */
177        keptlen = rootlen;
178        memcpy(path, rootpath, rootlen);
179
180        /* Always '/' terminate the given root path
181         */
182        if (keptlen && path[keptlen - 1] != '/') {
183            path[keptlen++] = '/';
184        }
185        pathlen = keptlen;
186    }
187
188    while (*addpath) {
189        /* Parse each segment, find the closing '/'
190         */
191        const char *next = addpath;
192        while (*next && (*next != '/')) {
193            ++next;
194        }
195        seglen = next - addpath;
196
197        if (seglen == 0 || (seglen == 1 && addpath[0] == '.')) {
198            /* noop segment (/ or ./) so skip it
199             */
200        }
201        else if (seglen == 2 && addpath[0] == '.' && addpath[1] == '.') {
202            /* backpath (../) */
203            if (pathlen == 1 && path[0] == '/') {
204                /* Attempt to move above root.  Always die if the
205                 * APR_FILEPATH_SECUREROOTTEST flag is specified.
206                 */
207                if (flags & APR_FILEPATH_SECUREROOTTEST) {
208                    return APR_EABOVEROOT;
209                }
210
211                /* Otherwise this is simply a noop, above root is root.
212                 * Flag that rootpath was entirely replaced.
213                 */
214                keptlen = 0;
215            }
216            else if (pathlen == 0
217                     || (pathlen == 3
218                         && !memcmp(path + pathlen - 3, "../", 3))
219                     || (pathlen  > 3
220                         && !memcmp(path + pathlen - 4, "/../", 4))) {
221                /* Path is already backpathed or empty, if the
222                 * APR_FILEPATH_SECUREROOTTEST.was given die now.
223                 */
224                if (flags & APR_FILEPATH_SECUREROOTTEST) {
225                    return APR_EABOVEROOT;
226                }
227
228                /* Otherwise append another backpath, including
229                 * trailing slash if present.
230                 */
231                memcpy(path + pathlen, "../", *next ? 3 : 2);
232                pathlen += *next ? 3 : 2;
233            }
234            else {
235                /* otherwise crop the prior segment
236                 */
237                do {
238                    --pathlen;
239                } while (pathlen && path[pathlen - 1] != '/');
240            }
241
242            /* Now test if we are above where we started and back up
243             * the keptlen offset to reflect the added/altered path.
244             */
245            if (pathlen < keptlen) {
246                if (flags & APR_FILEPATH_SECUREROOTTEST) {
247                    return APR_EABOVEROOT;
248                }
249                keptlen = pathlen;
250            }
251        }
252        else {
253            /* An actual segment, append it to the destination path
254             */
255            if (*next) {
256                seglen++;
257            }
258            memcpy(path + pathlen, addpath, seglen);
259            pathlen += seglen;
260        }
261
262        /* Skip over trailing slash to the next segment
263         */
264        if (*next) {
265            ++next;
266        }
267
268        addpath = next;
269    }
270    path[pathlen] = '\0';
271
272    /* keptlen will be the rootlen unless the addpath contained
273     * backpath elements.  If so, and APR_FILEPATH_NOTABOVEROOT
274     * is specified (APR_FILEPATH_SECUREROOTTEST was caught above),
275     * compare the original root to assure the result path is
276     * still within given root path.
277     */
278    if ((flags & APR_FILEPATH_NOTABOVEROOT) && keptlen < rootlen) {
279        if (strncmp(rootpath, path, rootlen)) {
280            return APR_EABOVEROOT;
281        }
282        if (rootpath[rootlen - 1] != '/'
283            && path[rootlen] && path[rootlen] != '/') {
284            return APR_EABOVEROOT;
285        }
286    }
287
288    *newpath = path;
289    return APR_SUCCESS;
290}
291
292APR_DECLARE(apr_status_t) apr_filepath_list_split(apr_array_header_t **pathelts,
293                                                  const char *liststr,
294                                                  apr_pool_t *p)
295{
296    return apr_filepath_list_split_impl(pathelts, liststr, ':', p);
297}
298
299APR_DECLARE(apr_status_t) apr_filepath_list_merge(char **liststr,
300                                                  apr_array_header_t *pathelts,
301                                                  apr_pool_t *p)
302{
303    return apr_filepath_list_merge_impl(liststr, pathelts, ':', p);
304}
305
306APR_DECLARE(apr_status_t) apr_filepath_encoding(int *style, apr_pool_t *p)
307{
308#if defined(DARWIN)
309    *style = APR_FILEPATH_ENCODING_UTF8;
310#else
311    *style = APR_FILEPATH_ENCODING_LOCALE;
312#endif
313    return APR_SUCCESS;
314}
315