1 /***************************************************************************
2  *                                  _   _ ____  _
3  *  Project                     ___| | | |  _ \| |
4  *                             / __| | | | |_) | |
5  *                            | (__| |_| |  _ <| |___
6  *                             \___|\___/|_| \_\_____|
7  *
8  * Copyright (C) 1998 - 2020, Daniel Stenberg, <daniel@haxx.se>, et al.
9  *
10  * This software is licensed as described in the file COPYING, which
11  * you should have received as part of this distribution. The terms
12  * are also available at https://curl.haxx.se/docs/copyright.html.
13  *
14  * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15  * copies of the Software, and permit persons to whom the Software is
16  * furnished to do so, under the terms of the COPYING file.
17  *
18  * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19  * KIND, either express or implied.
20  *
21  ***************************************************************************/
22 
23 #include "curl_setup.h"
24 
25 #include <curl/curl.h>
26 
27 #include "dotdot.h"
28 #include "curl_memory.h"
29 
30 /* The last #include file should be: */
31 #include "memdebug.h"
32 
33 /*
34  * "Remove Dot Segments"
35  * https://tools.ietf.org/html/rfc3986#section-5.2.4
36  */
37 
38 /*
39  * Curl_dedotdotify()
40  * @unittest: 1395
41  *
42  * This function gets a null-terminated path with dot and dotdot sequences
43  * passed in and strips them off according to the rules in RFC 3986 section
44  * 5.2.4.
45  *
46  * The function handles a query part ('?' + stuff) appended but it expects
47  * that fragments ('#' + stuff) have already been cut off.
48  *
49  * RETURNS
50  *
51  * an allocated dedotdotified output string
52  */
Curl_dedotdotify(const char * input)53 char *Curl_dedotdotify(const char *input)
54 {
55   size_t inlen = strlen(input);
56   char *clone;
57   size_t clen = inlen; /* the length of the cloned input */
58   char *out = malloc(inlen + 1);
59   char *outptr;
60   char *orgclone;
61   char *queryp;
62   if(!out)
63     return NULL; /* out of memory */
64 
65   *out = 0; /* null-terminates, for inputs like "./" */
66 
67   /* get a cloned copy of the input */
68   clone = strdup(input);
69   if(!clone) {
70     free(out);
71     return NULL;
72   }
73   orgclone = clone;
74   outptr = out;
75 
76   if(!*clone) {
77     /* zero length string, return that */
78     free(out);
79     return clone;
80   }
81 
82   /*
83    * To handle query-parts properly, we must find it and remove it during the
84    * dotdot-operation and then append it again at the end to the output
85    * string.
86    */
87   queryp = strchr(clone, '?');
88   if(queryp)
89     *queryp = 0;
90 
91   do {
92 
93     /*  A.  If the input buffer begins with a prefix of "../" or "./", then
94         remove that prefix from the input buffer; otherwise, */
95 
96     if(!strncmp("./", clone, 2)) {
97       clone += 2;
98       clen -= 2;
99     }
100     else if(!strncmp("../", clone, 3)) {
101       clone += 3;
102       clen -= 3;
103     }
104 
105     /*  B.  if the input buffer begins with a prefix of "/./" or "/.", where
106         "."  is a complete path segment, then replace that prefix with "/" in
107         the input buffer; otherwise, */
108     else if(!strncmp("/./", clone, 3)) {
109       clone += 2;
110       clen -= 2;
111     }
112     else if(!strcmp("/.", clone)) {
113       clone[1]='/';
114       clone++;
115       clen -= 1;
116     }
117 
118     /*  C.  if the input buffer begins with a prefix of "/../" or "/..", where
119         ".." is a complete path segment, then replace that prefix with "/" in
120         the input buffer and remove the last segment and its preceding "/" (if
121         any) from the output buffer; otherwise, */
122 
123     else if(!strncmp("/../", clone, 4)) {
124       clone += 3;
125       clen -= 3;
126       /* remove the last segment from the output buffer */
127       while(outptr > out) {
128         outptr--;
129         if(*outptr == '/')
130           break;
131       }
132       *outptr = 0; /* null-terminate where it stops */
133     }
134     else if(!strcmp("/..", clone)) {
135       clone[2]='/';
136       clone += 2;
137       clen -= 2;
138       /* remove the last segment from the output buffer */
139       while(outptr > out) {
140         outptr--;
141         if(*outptr == '/')
142           break;
143       }
144       *outptr = 0; /* null-terminate where it stops */
145     }
146 
147     /*  D.  if the input buffer consists only of "." or "..", then remove
148         that from the input buffer; otherwise, */
149 
150     else if(!strcmp(".", clone) || !strcmp("..", clone)) {
151       *clone = 0;
152       *out = 0;
153     }
154 
155     else {
156       /*  E.  move the first path segment in the input buffer to the end of
157           the output buffer, including the initial "/" character (if any) and
158           any subsequent characters up to, but not including, the next "/"
159           character or the end of the input buffer. */
160 
161       do {
162         *outptr++ = *clone++;
163         clen--;
164       } while(*clone && (*clone != '/'));
165       *outptr = 0;
166     }
167 
168   } while(*clone);
169 
170   if(queryp) {
171     size_t qlen;
172     /* There was a query part, append that to the output. The 'clone' string
173        may now have been altered so we copy from the original input string
174        from the correct index. */
175     size_t oindex = queryp - orgclone;
176     qlen = strlen(&input[oindex]);
177     memcpy(outptr, &input[oindex], qlen + 1); /* include the end zero byte */
178   }
179 
180   free(orgclone);
181   return out;
182 }
183