Path Traversal Bypass in file:// URLs Due to Incomplete URL-Encoded Path Normalization
What the report claims
Path traversal bypass in file:// URLs due to incomplete URL-encoded path normalization in dedotdotify()
Compare with curl's recorded outcome
Labeled AI slop
curl's maintainer lists this report as AI slop
Shown for comparison after analysis. The pipeline did not see this label.
ProveIt's finding
The available evidence does not settle the report. A maintainer should investigate.
See the withdrawn model reasoning
The following reasoning failed the citation check and does not support the final finding.
- dedotdotify() uses ISSLASH macro that only matches literal '/', so %2f%2e%2e%2f (encoded /../) bypasses normalization entirely
- Unit test unit1395.c:44 confirms this behavior: input "%2f%2e%2e%2f" produces identical output
- After bypassing dedotdotify, Curl_urldecode in file.c:192-193 decodes the path to /../../etc/passwd
- The decoded path is passed directly to curlx_open at file.c:263 with no validation for '..' sequences
- This enables arbitrary file read via file:// URLs when libcurl processes user-controlled URLs (SSRF scenario)
- RFC 3986 normalization order requires percent-decoding before dot-segment removal; curl does it in reverse
How this analysis ran 6 claims · 10 citations verified
Evidence check: judge quoted an external source that does not say that (https://rfc-editor.org/errata/eid2525)
Checked against curl master on 2025-12-15 (6f3124eaa2c4). Dev evaluation split. Citation verification checks that a referenced line was in the evidence; it does not prove the model interpreted it correctly.
Inspect the withdrawn draft reply
This draft belongs to the withdrawn judgment. It may contain unsupported claims.
Thank you for the detailed report. The code confirms that dedotdotify() only checks for literal '/' via ISSLASH, so URL-encoded traversal sequences like %2f%2e%2e%2f bypass normalization. The unit test at unit1395.c:44 documents this behavior. After decoding in file.c, the path is used in curlx_open with no validation. This is a valid security issue because it bypasses curl's documented path traversal protection and enables arbitrary file read in SSRF scenarios. We will address this by either running dedotdotify after percent-decoding or making it handle encoded slashes.
Inspect all claims and source code 6 claims · 11 code excerpts
This model judgment was withdrawn. The claim assessments below are preserved for inspection and may be wrong.
Report claims
dedotdotify() only recognizes literal forward slashes and does not handle URL-encoded slashes (%2f or %2F)
Read the reasoning
The ISSLASH macro at line 777 explicitly checks only for literal '/'. The dedotdotify loop at line 836 uses this macro, so URL-encoded slashes (%2f/%2F) are not treated as segment delimiters and the traversal sequences pass through unchanged.
lib/urlapi.c:836 in dedotdotify() function
Read the reasoning
Line 836 is the exact location where dedotdotify checks for slashes using ISSLASH, confirming the claim's location reference.
Attacker provides a file:// URL with URL-encoded path traversal sequences such as %2f%2e%2e%2f
Read the reasoning
The attack vector is plausible: an attacker provides a file:// URL with percent-encoded traversal sequences. The path reaches dedotdotify while still encoded (urlapi.c:1233), bypasses normalization, then gets decoded in file.c:192-193.
URL-encoded traversal sequences bypass normalization and result in arbitrary file read after URL decoding
Read the reasoning
The flow is confirmed: dedotdotify runs on encoded path (misses %2f%2e%2e%2f), then Curl_urldecode decodes it to /../../etc/passwd, and curlx_open uses it directly with no intermediate validation.
Unit test tests/unit/unit1395.c line 44 shows that "%2f%2e%2e%2f" is not normalized and passes through unchanged
Read the reasoning
The deterministic check confirms this test case exists and shows the encoded traversal sequence passes through dedotdotify unchanged.
After URL decoding in lib/file.c:192-193 the path becomes /../../etc/passwd and is used directly in curlx_open() at line 263 with no validation
Read the reasoning
Lines 192-193 decode the path, and line 263 uses the decoded path directly in curlx_open. No validation (strstr, strchr, or custom checks for '..') exists in file.c between these points.
Deterministic checks that failed
- line none of ISSLASH, while, Curl_urldecode, curlx_open appear near line 44 at 6f3124eaa2c4; it reads: '{ "%2f%2e%2e%2f", "%2f%2e%2e%2f" },'
What the report says about other projects
confirmed The `dedotdotify()` function is described as removing path traversal sequences according to RFC 3986.
“The `dedotdotify()` function is described as removing path traversal sequences according to RFC 3986.” source
confirmed RFC 3986 section 6.2 specifies that percent-encoding normalization should happen before path segment normalization during URI comparison.
“Percent encoding normalization: Next, any percent-encoded triplets that correspond to unreserved characters can be decoded. Path segment normalization: Any dot-segments can be removed from the path.” source
Read the original report (excerpt)
Source code
| 752 | return result; |
| 753 | } |
| 754 | |
| 755 | /* |
| 756 | * "Remove Dot Segments" |
| 757 | * https://datatracker.ietf.org/doc/html/rfc3986#section-5.2.4 |
| 758 | */ |
| 759 | |
| 760 | static bool is_dot(const char **str, size_t *clen) |
| 761 | { |
| 762 | const char *p = *str; |
| 763 | if(*p == '.') { |
| 764 | (*str)++; |
| 765 | (*clen)--; |
| 766 | return TRUE; |
| 767 | } |
| 768 | else if((*clen >= 3) && |
| 769 | (p[0] == '%') && (p[1] == '2') && ((p[2] | 0x20) == 'e')) { |
| 770 | *str += 3; |
| 771 | *clen -= 3; |
| 772 | return TRUE; |
| 773 | } |
| 774 | return FALSE; |
| 775 | } |
| 776 | |
| 777 | #define ISSLASH(x) ((x) == '/') |
| C1: ISSLASH macro defined as ((x) == '/') - only matches literal forward slash | |
| 778 | |
| 779 | /* |
| 780 | * dedotdotify() |
| 781 | * @unittest: 1395 |
| 782 | * |
| 783 | * This function gets a null-terminated path with dot and dotdot sequences |
| 784 | * passed in and strips them off according to the rules in RFC 3986 section |
| 785 | * 5.2.4. |
| 786 | * |
| 787 | * The function handles a path. It should not contain the query nor fragment. |
| 788 | * |
| 789 | * RETURNS |
| 790 | * |
| 791 | * Zero for success and 'out' set to an allocated dedotdotified string. |
| 792 | */ |
| 793 | UNITTEST int dedotdotify(const char *input, size_t clen, char **outp); |
| 794 | UNITTEST int dedotdotify(const char *input, size_t clen, char **outp) |
| 795 | { |
| 796 | struct dynbuf out; |
| 797 | CURLcode result = CURLE_OK; |
| 798 | |
| 799 | *outp = NULL; |
| 800 | /* the path always starts with a slash, and a slash has not dot */ |
| 801 | if(clen < 2) |
| 802 | return 0; |
| 803 | |
| 804 | curlx_dyn_init(&out, clen + 1); |
| 805 | |
| 806 | /* A. If the input buffer begins with a prefix of "../" or "./", then |
| 807 | remove that prefix from the input buffer; otherwise, */ |
| 808 | if(is_dot(&input, &clen)) { |
| 809 | const char *p = input; |
| 810 | size_t blen = clen; |
| 811 | |
| 812 | if(!clen) |
| 813 | /* . [end] */ |
| 814 | goto end; |
| 815 | else if(ISSLASH(*p)) { |
| 816 | /* one dot followed by a slash */ |
| 817 | input = p + 1; |
| 818 | clen--; |
| 819 | } |
| 820 | |
| 821 | /* D. if the input buffer consists only of "." or "..", then remove |
| 822 | that from the input buffer; otherwise, */ |
| 823 | else if(is_dot(&p, &blen)) { |
| 824 | if(!blen) |
| 825 | /* .. [end] */ |
| 826 | goto end; |
| 827 | else if(ISSLASH(*p)) { |
| 828 | /* ../ */ |
| 829 | input = p + 1; |
| 830 | clen = blen - 1; |
| 831 | } |
| 832 | } |
| 833 | } |
| 834 | |
| 835 | while(clen && !result) { /* until end of path content */ |
| 836 | if(ISSLASH(*input)) { |
| C1: dedotdotify() uses ISSLASH(*input) to detect path segments, so %2f sequences are not recognized | |
| C2: while(clen && !result) { if(ISSLASH(*input)) { - the main loop that processes path segments | |
| 837 | const char *p = &input[1]; |
| 838 | size_t blen = clen - 1; |
| 839 | /* B. if the input buffer begins with a prefix of "/./" or "/.", where |
| 840 | "." is a complete path segment, then replace that prefix with "/" in |
| 841 | the input buffer; otherwise, */ |
| 842 | if(is_dot(&p, &blen)) { |
| 843 | if(!blen) { /* /. */ |
| 844 | result = curlx_dyn_addn(&out, "/", 1); |
| 845 | break; |
| 846 | } |
| 847 | else if(ISSLASH(*p)) { /* /./ */ |
| 848 | input = p; |
| 849 | clen = blen; |
| 850 | continue; |
| 851 | } |
| 852 | |
| 853 | /* C. if the input buffer begins with a prefix of "/../" or "/..", |
| 854 | where ".." is a complete path segment, then replace that prefix |
| 855 | with "/" in the input buffer and remove the last segment and its |
| 856 | preceding "/" (if any) from the output buffer; otherwise, */ |
| 857 | else if(is_dot(&p, &blen) && (ISSLASH(*p) || !blen)) { |
| 858 | /* remove the last segment from the output buffer */ |
| 859 | size_t len = curlx_dyn_len(&out); |
| 860 | if(len) { |
| 861 | char *ptr = curlx_dyn_ptr(&out); |
| 862 | char *last = memrchr(ptr, '/', len); |
| 863 | if(last) |
| 864 | /* trim the output at the slash */ |
| 865 | curlx_dyn_setlen(&out, last - ptr); |
| 866 | } |
| 867 | |
| 868 | if(blen) { /* /../ */ |
| 869 | input = p; |
| 870 | clen = blen; |
| 871 | continue; |
| 872 | } |
| 873 | result = curlx_dyn_addn(&out, "/", 1); |
| 874 | break; |
| 875 | } |
| 876 | } |
| 877 | } |
| 878 | |
| 879 | /* E. move the first path segment in the input buffer to the end of |
| 880 | the output buffer, including the initial "/" character (if any) and |
| 881 | any subsequent characters up to, but not including, the next "/" |
| 882 | character or the end of the input buffer. */ |
| 883 | |
| 884 | result = curlx_dyn_addn(&out, input, 1); |
| 885 | input++; |
| 886 | clen--; |
| 887 | } |
| 888 | end: |
| 889 | if(!result) { |
| 890 | if(curlx_dyn_len(&out)) |
| 891 | *outp = curlx_dyn_ptr(&out); |
| 892 | else { |
| 893 | *outp = curlx_strdup(""); |
| 894 | if(!*outp) |
| 895 | return 1; |
| 896 | } |
| 897 | } |
| 898 | return result ? 1 : 0; /* success */ |
| 899 | } |
| 811 | |
| 812 | if(!clen) |
| 813 | /* . [end] */ |
| 814 | goto end; |
| 815 | else if(ISSLASH(*p)) { |
| 816 | /* one dot followed by a slash */ |
| 817 | input = p + 1; |
| 818 | clen--; |
| 819 | } |
| 820 | |
| 821 | /* D. if the input buffer consists only of "." or "..", then remove |
| 822 | that from the input buffer; otherwise, */ |
| 823 | else if(is_dot(&p, &blen)) { |
| 824 | if(!blen) |
| 825 | /* .. [end] */ |
| 826 | goto end; |
| 827 | else if(ISSLASH(*p)) { |
| 828 | /* ../ */ |
| 829 | input = p + 1; |
| 830 | clen = blen - 1; |
| 831 | } |
| 832 | } |
| 833 | } |
| 834 | |
| 835 | while(clen && !result) { /* until end of path content */ |
| 836 | if(ISSLASH(*input)) { |
| C1: dedotdotify() uses ISSLASH(*input) to detect path segments, so %2f sequences are not recognized | |
| C2: while(clen && !result) { if(ISSLASH(*input)) { - the main loop that processes path segments | |
| 837 | const char *p = &input[1]; |
| 838 | size_t blen = clen - 1; |
| 839 | /* B. if the input buffer begins with a prefix of "/./" or "/.", where |
| 840 | "." is a complete path segment, then replace that prefix with "/" in |
| 841 | the input buffer; otherwise, */ |
| 842 | if(is_dot(&p, &blen)) { |
| 843 | if(!blen) { /* /. */ |
| 844 | result = curlx_dyn_addn(&out, "/", 1); |
| 845 | break; |
| 846 | } |
| 847 | else if(ISSLASH(*p)) { /* /./ */ |
| 848 | input = p; |
| 849 | clen = blen; |
| 850 | continue; |
| 851 | } |
| 852 | |
| 853 | /* C. if the input buffer begins with a prefix of "/../" or "/..", |
| 854 | where ".." is a complete path segment, then replace that prefix |
| 855 | with "/" in the input buffer and remove the last segment and its |
| 856 | preceding "/" (if any) from the output buffer; otherwise, */ |
| 857 | else if(is_dot(&p, &blen) && (ISSLASH(*p) || !blen)) { |
| 858 | /* remove the last segment from the output buffer */ |
| 859 | size_t len = curlx_dyn_len(&out); |
| 860 | if(len) { |
| 861 | char *ptr = curlx_dyn_ptr(&out); |
| 167 | */ |
| 168 | static CURLcode file_connect(struct Curl_easy *data, bool *done) |
| 169 | { |
| 170 | char *real_path; |
| 171 | struct FILEPROTO *file = Curl_meta_get(data, CURL_META_FILE_EASY); |
| 172 | int fd; |
| 173 | #ifdef DOS_FILESYSTEM |
| 174 | size_t i; |
| 175 | char *actual_path; |
| 176 | #endif |
| 177 | size_t real_path_len; |
| 178 | CURLcode result; |
| 179 | |
| 180 | if(!file) |
| 181 | return CURLE_FAILED_INIT; |
| 182 | |
| 183 | if(file->path) { |
| 184 | /* already connected. |
| 185 | * the handler->connect_it() is normally only called once, but |
| 186 | * FILE does a special check on setting up the connection which |
| 187 | * calls this explicitly. */ |
| 188 | *done = TRUE; |
| 189 | return CURLE_OK; |
| 190 | } |
| 191 | |
| 192 | result = Curl_urldecode(data->state.up.path, 0, &real_path, |
| C3: Curl_urldecode decodes the path after dedotdotify has run | |
| C4: Curl_urldecode(data->state.up.path, 0, &real_path, &real_path_len, REJECT_ZERO) - decodes %2f to /, %2e to . | |
| C6: Curl_urldecode call that produces the decoded path | |
| 193 | &real_path_len, REJECT_ZERO); |
| 194 | if(result) |
| 195 | return result; |
| 196 | |
| 197 | #ifdef DOS_FILESYSTEM |
| 198 | /* If the first character is a slash, and there is |
| 199 | something that looks like a drive at the beginning of |
| 200 | the path, skip the slash. If we remove the initial |
| 201 | slash in all cases, paths without drive letters end up |
| 202 | relative to the current directory which is not how |
| 203 | browsers work. |
| 204 | |
| 205 | Some browsers accept | instead of : as the drive letter |
| 206 | separator, so we do too. |
| 207 | |
| 208 | On other platforms, we need the slash to indicate an |
| 209 | absolute pathname. On Windows, absolute paths start |
| 210 | with a drive letter. |
| 211 | */ |
| 212 | actual_path = real_path; |
| 213 | if((actual_path[0] == '/') && |
| 214 | actual_path[1] && |
| 215 | (actual_path[2] == ':' || actual_path[2] == '|')) { |
| 216 | actual_path[2] = ':'; |
| 217 | actual_path++; |
| 218 | real_path_len--; |
| 1208 | goto fail; |
| 1209 | pathlen = curlx_dyn_len(&enc); |
| 1210 | path = u->path = curlx_dyn_ptr(&enc); |
| 1211 | } |
| 1212 | |
| 1213 | if(pathlen <= 1) { |
| 1214 | /* there is no path left or just the slash, unset */ |
| 1215 | path = NULL; |
| 1216 | } |
| 1217 | else { |
| 1218 | if(!u->path) { |
| 1219 | u->path = Curl_memdup0(path, pathlen); |
| 1220 | if(!u->path) { |
| 1221 | result = CURLUE_OUT_OF_MEMORY; |
| 1222 | goto fail; |
| 1223 | } |
| 1224 | path = u->path; |
| 1225 | } |
| 1226 | else if(flags & CURLU_URLENCODE) |
| 1227 | /* it might have encoded more than just the path so cut it */ |
| 1228 | u->path[pathlen] = 0; |
| 1229 | |
| 1230 | if(!(flags & CURLU_PATH_AS_IS)) { |
| 1231 | /* remove ../ and ./ sequences according to RFC3986 */ |
| 1232 | char *dedot; |
| 1233 | int err = dedotdotify(path, pathlen, &dedot); |
| C3: dedotdotify(path, pathlen, &dedot) called on URL-encoded path during URL parsing | |
| 1234 | if(err) { |
| 1235 | result = CURLUE_OUT_OF_MEMORY; |
| 1236 | goto fail; |
| 1237 | } |
| 1238 | if(dedot) { |
| 1239 | curlx_free(u->path); |
| 1240 | u->path = dedot; |
| 1241 | } |
| 1242 | } |
| 1243 | } |
| 1244 | |
| 1245 | u->host = curlx_dyn_ptr(&host); |
| 1246 | |
| 1247 | return result; |
| 1248 | fail: |
| 1249 | curlx_dyn_free(&host); |
| 1250 | free_urlhandle(u); |
| 1251 | return result; |
| 1252 | } |
| 1253 | |
| 1254 | /* |
| 1255 | * Parse the URL and, if successful, replace everything in the Curl_URL struct. |
| 1256 | */ |
| 1257 | static CURLUcode parseurl_and_replace(const char *url, CURLU *u, |
| 1258 | unsigned int flags) |
| 238 | |
| 239 | #ifdef AMIGA_FILESYSTEM |
| 240 | /* |
| 241 | * A leading slash in an AmigaDOS path denotes the parent |
| 242 | * directory, and hence we block this as it is relative. |
| 243 | * Absolute paths start with 'volumename:', so we check for |
| 244 | * this first. Failing that, we treat the path as a real Unix |
| 245 | * path, but only if the application was compiled with -lunix. |
| 246 | */ |
| 247 | fd = -1; |
| 248 | file->path = real_path; |
| 249 | |
| 250 | if(real_path[0] == '/') { |
| 251 | extern int __unix_path_semantics; |
| 252 | if(strchr(real_path + 1, ':')) { |
| 253 | /* Amiga absolute path */ |
| 254 | fd = curlx_open(real_path + 1, O_RDONLY); |
| 255 | file->path++; |
| 256 | } |
| 257 | else if(__unix_path_semantics) { |
| 258 | /* -lunix fallback */ |
| 259 | fd = curlx_open(real_path, O_RDONLY); |
| 260 | } |
| 261 | } |
| 262 | #else |
| 263 | fd = curlx_open(real_path, O_RDONLY); |
| C4: fd = curlx_open(real_path, O_RDONLY) - decoded path used directly with no validation | |
| C6: curlx_open(real_path, O_RDONLY) with no validation between decode and open | |
| 264 | file->path = real_path; |
| 265 | #endif |
| 266 | #endif |
| 267 | curlx_free(file->freepath); |
| 268 | file->freepath = real_path; /* free this when done */ |
| 269 | |
| 270 | file->fd = fd; |
| 271 | if(!data->state.upload && (fd == -1)) { |
| 272 | failf(data, "Could not open file %s", data->state.up.path); |
| 273 | file_done(data, CURLE_FILE_COULDNT_READ_FILE, FALSE); |
| 274 | return CURLE_FILE_COULDNT_READ_FILE; |
| 275 | } |
| 276 | *done = TRUE; |
| 277 | |
| 278 | return CURLE_OK; |
| 279 | } |
| 280 | |
| 281 | static CURLcode file_done(struct Curl_easy *data, |
| 282 | CURLcode status, bool premature) |
| 283 | { |
| 284 | struct FILEPROTO *file = Curl_meta_get(data, CURL_META_FILE_EASY); |
| 285 | (void)status; |
| 286 | (void)premature; |
| 287 | |
| 288 | if(file) |
| 107 | CURLcode Curl_urldecode(const char *string, size_t length, |
| 108 | char **ostring, size_t *olen, |
| 109 | enum urlreject ctrl) |
| 110 | { |
| 111 | size_t alloc; |
| 112 | char *ns; |
| 113 | |
| 114 | DEBUGASSERT(string); |
| 115 | DEBUGASSERT(ctrl >= REJECT_NADA); /* crash on TRUE/FALSE */ |
| 116 | |
| 117 | alloc = (length ? length : strlen(string)); |
| 118 | ns = curlx_malloc(alloc + 1); |
| 119 | |
| 120 | if(!ns) |
| 121 | return CURLE_OUT_OF_MEMORY; |
| 122 | |
| 123 | /* store output string */ |
| 124 | *ostring = ns; |
| 125 | |
| 126 | while(alloc) { |
| 127 | unsigned char in = (unsigned char)*string; |
| 128 | if(('%' == in) && (alloc > 2) && |
| C4: Percent-decoding loop converts %2f/%2F to '/' and %2e/%2E to '.' | |
| 129 | ISXDIGIT(string[1]) && ISXDIGIT(string[2])) { |
| 130 | /* this is two hexadecimal digits following a '%' */ |
| 131 | in = (unsigned char)((Curl_hexval(string[1]) << 4) | |
| 132 | Curl_hexval(string[2])); |
| 133 | string += 3; |
| 134 | alloc -= 3; |
| 135 | } |
| 136 | else { |
| 137 | string++; |
| 138 | alloc--; |
| 139 | } |
| 140 | |
| 141 | if(((ctrl == REJECT_CTRL) && (in < 0x20)) || |
| 142 | ((ctrl == REJECT_ZERO) && (in == 0))) { |
| 143 | Curl_safefree(*ostring); |
| 144 | return CURLE_URL_MALFORMAT; |
| 145 | } |
| 146 | |
| 147 | *ns++ = (char)in; |
| 148 | } |
| 149 | *ns = 0; /* terminate it */ |
| 150 | |
| 151 | if(olen) |
| 152 | /* store output size */ |
| 153 | *olen = ns - *ostring; |
| 154 | |
| 155 | return CURLE_OK; |
| 156 | } |
Show 50 lines (not cited by the judge)
| 112 | CURLcode Curl_urldecode(const char *string, size_t length, |
| 113 | char **ostring, size_t *olen, |
| 114 | enum urlreject ctrl) |
| 115 | { |
| 116 | size_t alloc; |
| 117 | char *ns; |
| 118 | |
| 119 | DEBUGASSERT(string); |
| 120 | DEBUGASSERT(ctrl >= REJECT_NADA); /* crash on TRUE/FALSE */ |
| 121 | |
| 122 | alloc = (length ? length : strlen(string)); |
| 123 | ns = malloc(alloc + 1); |
| 124 | |
| 125 | if(!ns) |
| 126 | return CURLE_OUT_OF_MEMORY; |
| 127 | |
| 128 | /* store output string */ |
| 129 | *ostring = ns; |
| 130 | |
| 131 | while(alloc) { |
| 132 | unsigned char in = (unsigned char)*string; |
| 133 | if(('%' == in) && (alloc > 2) && |
| 134 | ISXDIGIT(string[1]) && ISXDIGIT(string[2])) { |
| 135 | /* this is two hexadecimal digits following a '%' */ |
| 136 | in = (unsigned char)((Curl_hexval(string[1]) << 4) | |
| 137 | Curl_hexval(string[2])); |
| 138 | string += 3; |
| 139 | alloc -= 3; |
| 140 | } |
| 141 | else { |
| 142 | string++; |
| 143 | alloc--; |
| 144 | } |
| 145 | |
| 146 | if(((ctrl == REJECT_CTRL) && (in < 0x20)) || |
| 147 | ((ctrl == REJECT_ZERO) && (in == 0))) { |
| 148 | Curl_safefree(*ostring); |
| 149 | return CURLE_URL_MALFORMAT; |
| 150 | } |
| 151 | |
| 152 | *ns++ = (char)in; |
| 153 | } |
| 154 | *ns = 0; /* terminate it */ |
| 155 | |
| 156 | if(olen) |
| 157 | /* store output size */ |
| 158 | *olen = ns - *ostring; |
| 159 | |
| 160 | return CURLE_OK; |
| 161 | } |
Show 1 lines (not cited by the judge)
| 45 | #define curlx_open curlx_win32_open |
Show 1 lines (not cited by the judge)
| 43 | #define curlx_open curlx_win32_open |
Show 106 lines (not cited by the judge)
| 798 | UNITTEST int dedotdotify(const char *input, size_t clen, char **outp) |
| 799 | { |
| 800 | struct dynbuf out; |
| 801 | CURLcode result = CURLE_OK; |
| 802 | |
| 803 | *outp = NULL; |
| 804 | /* the path always starts with a slash, and a slash has not dot */ |
| 805 | if(clen < 2) |
| 806 | return 0; |
| 807 | |
| 808 | curlx_dyn_init(&out, clen + 1); |
| 809 | |
| 810 | /* A. If the input buffer begins with a prefix of "../" or "./", then |
| 811 | remove that prefix from the input buffer; otherwise, */ |
| 812 | if(is_dot(&input, &clen)) { |
| 813 | const char *p = input; |
| 814 | size_t blen = clen; |
| 815 | |
| 816 | if(!clen) |
| 817 | /* . [end] */ |
| 818 | goto end; |
| 819 | else if(ISSLASH(*p)) { |
| 820 | /* one dot followed by a slash */ |
| 821 | input = p + 1; |
| 822 | clen--; |
| 823 | } |
| 824 | |
| 825 | /* D. if the input buffer consists only of "." or "..", then remove |
| 826 | that from the input buffer; otherwise, */ |
| 827 | else if(is_dot(&p, &blen)) { |
| 828 | if(!blen) |
| 829 | /* .. [end] */ |
| 830 | goto end; |
| 831 | else if(ISSLASH(*p)) { |
| 832 | /* ../ */ |
| 833 | input = p + 1; |
| 834 | clen = blen - 1; |
| 835 | } |
| 836 | } |
| 837 | } |
| 838 | |
| 839 | while(clen && !result) { /* until end of path content */ |
| 840 | if(ISSLASH(*input)) { |
| 841 | const char *p = &input[1]; |
| 842 | size_t blen = clen - 1; |
| 843 | /* B. if the input buffer begins with a prefix of "/./" or "/.", where |
| 844 | "." is a complete path segment, then replace that prefix with "/" in |
| 845 | the input buffer; otherwise, */ |
| 846 | if(is_dot(&p, &blen)) { |
| 847 | if(!blen) { /* /. */ |
| 848 | result = curlx_dyn_addn(&out, "/", 1); |
| 849 | break; |
| 850 | } |
| 851 | else if(ISSLASH(*p)) { /* /./ */ |
| 852 | input = p; |
| 853 | clen = blen; |
| 854 | continue; |
| 855 | } |
| 856 | |
| 857 | /* C. if the input buffer begins with a prefix of "/../" or "/..", |
| 858 | where ".." is a complete path segment, then replace that prefix |
| 859 | with "/" in the input buffer and remove the last segment and its |
| 860 | preceding "/" (if any) from the output buffer; otherwise, */ |
| 861 | else if(is_dot(&p, &blen) && (ISSLASH(*p) || !blen)) { |
| 862 | /* remove the last segment from the output buffer */ |
| 863 | size_t len = curlx_dyn_len(&out); |
| 864 | if(len) { |
| 865 | char *ptr = curlx_dyn_ptr(&out); |
| 866 | char *last = memrchr(ptr, '/', len); |
| 867 | if(last) |
| 868 | /* trim the output at the slash */ |
| 869 | curlx_dyn_setlen(&out, last - ptr); |
| 870 | } |
| 871 | |
| 872 | if(blen) { /* /../ */ |
| 873 | input = p; |
| 874 | clen = blen; |
| 875 | continue; |
| 876 | } |
| 877 | result = curlx_dyn_addn(&out, "/", 1); |
| 878 | break; |
| 879 | } |
| 880 | } |
| 881 | } |
| 882 | |
| 883 | /* E. move the first path segment in the input buffer to the end of |
| 884 | the output buffer, including the initial "/" character (if any) and |
| 885 | any subsequent characters up to, but not including, the next "/" |
| 886 | character or the end of the input buffer. */ |
| 887 | |
| 888 | result = curlx_dyn_addn(&out, input, 1); |
| 889 | input++; |
| 890 | clen--; |
| 891 | } |
| 892 | end: |
| 893 | if(!result) { |
| 894 | if(curlx_dyn_len(&out)) |
| 895 | *outp = curlx_dyn_ptr(&out); |
| 896 | else { |
| 897 | *outp = strdup(""); |
| 898 | if(!*outp) |
| 899 | return 1; |
| 900 | } |
| 901 | } |
| 902 | return result ? 1 : 0; /* success */ |
| 903 | } |
Show 42 lines (not cited by the judge)
| 81 | while(<STDIN>) { |
| 82 | my $l = $_; |
| 83 | if($fl) { |
| 84 | # continued deprecation |
| 85 | if($l =~ /(.*)\),/) { |
| 86 | $fl .= $1; |
| 87 | |
| 88 | # the end |
| 89 | my @p=split(/, */, $fl); |
| 90 | add($p[0], $p[1], $p[2]); |
| 91 | undef $fl; |
| 92 | } |
| 93 | else { |
| 94 | # another line to append |
| 95 | chomp $l; |
| 96 | $fl .= $l; |
| 97 | } |
| 98 | } |
| 99 | |
| 100 | if(/^ *CURLOPTDEPRECATED\((.*)/) { |
| 101 | $fl = $1; |
| 102 | chomp $fl; |
| 103 | } |
| 104 | |
| 105 | if(/^ *CURLOPT\(([^,]*), ([^,]*), (\d+)\)/) { |
| 106 | my($opt, $type, $num)=($1,$2,$3); |
| 107 | add($opt, $type, $num); |
| 108 | } |
| 109 | |
| 110 | # alias for an older option |
| 111 | # old = new |
| 112 | if(/^#define (CURLOPT_[^ ]*) *(CURLOPT_\S*)/) { |
| 113 | my ($o, $n)=($1, $2); |
| 114 | # skip obsolete ones |
| 115 | if(($n !~ /OBSOLETE/) && ($o !~ /OBSOLETE/)) { |
| 116 | $o =~ s/^CURLOPT_//; |
| 117 | $n =~ s/^CURLOPT_//; |
| 118 | $alias{$o} = $n; |
| 119 | push @names, $o; |
| 120 | } |
| 121 | } |
| 122 | } |