CVE-2020-8285: FTP wildcard stack overflow
What the report claims
FTP wildcard stack overflow
Compare with curl's recorded outcome
Known vulnerability
A real vulnerability: CVE-2020-8285 FTP wildcard stack overflow
Shown for comparison after analysis. The pipeline did not see this label.
ProveIt's finding
The code supports the reported flaw. A maintainer should review its impact and decide what to do next.
Why this finding?
- wc_statemach uses recursion instead of iteration for state transitions, causing stack depth linear in the number of files processed
- An attacker controlling the FTP server can create a directory with tens of thousands of files, each triggering a recursive call
- The stack trace confirms the recursion between CURLWC_DOWNLOADING (line 3856) and CURLWC_SKIP (line 3894) cases
- This is a denial-of-service vulnerability reachable via server-controlled input
How this analysis ran 5 claims · 9 citations verified
Checked against curl master on 2020-11-27 (c7b02c5d6800). Dev evaluation split. Citation verification checks that a referenced line was in the evidence; it does not prove the model interpreted it correctly.
Review a draft reply to the reporter
Edit and verify before sending.
The report is valid. The wc_statemach function in lib/ftp.c uses unbounded recursion (lines 3820, 3856, 3864, 3894) for state transitions during wildcard FTP directory processing. An attacker controlling the FTP server can create a directory with many files (40,000 as shown), causing stack overflow. The fix is to convert the recursion to a while loop. This affects all versions including the latest release.
Inspect all claims and source code 5 claims · 8 code excerpts
Report claims
FTP wildcard stack overflow
Read the reasoning
The wc_statemach function uses unbounded recursion for state transitions. Each file processed in the wildcard matching can cause a recursive call, leading to stack depth proportional to the number of files. With 40,000 files as demonstrated, this overflows the stack.
wc_statemach
Read the reasoning
The flaw is in the wc_statemach function in ftp.c, confirmed by the stack trace and source code.
adding/crafting files on the server
Read the reasoning
The file list is populated from the FTP server's directory listing (LIST command response). An attacker controlling the server can create arbitrarily many files, each triggering a recursive call in wc_statemach.
stack overflow
Read the reasoning
The stack trace shows alternating calls at lines 3856 and 3894 (DOWNLOADING->SKIP->DOWNLOADING...), confirming unbounded recursion depth. The crash in curl_dbg_log via dynbuf allocation is a consequence of stack exhaustion.
build 6255.c and run it with a debugger
Read the reasoning
Reproduction steps reference an external test file (6255.c) not provided. The code analysis alone confirms the recursive pattern that would cause stack overflow with many files.
Deterministic checks that failed
- line none of curl_fnmatch, fnmatch, wc_statemach, curl_dbg_log appear near line 846 at c7b02c5d6800; it reads: 'OUTCHAR(*str++);'
- line none of curl_fnmatch, fnmatch, wc_statemach, curl_dbg_log appear near line 3836 at c7b02c5d6800; it reads: 'char *tmp_path = aprintf("%s%s", wildcard->path, finfo->filename);'
Read the original report (excerpt)
Source code
| 3793 | static CURLcode wc_statemach(struct connectdata *conn) |
| C2: Definition of wc_statemach function | |
| 3794 | { |
| 3795 | struct WildcardData * const wildcard = &(conn->data->wildcard); |
| 3796 | CURLcode result = CURLE_OK; |
| 3797 | |
| 3798 | switch(wildcard->state) { |
| 3799 | case CURLWC_INIT: |
| 3800 | result = init_wc_data(conn); |
| 3801 | if(wildcard->state == CURLWC_CLEAN) |
| 3802 | /* only listing! */ |
| 3803 | break; |
| 3804 | wildcard->state = result ? CURLWC_ERROR : CURLWC_MATCHING; |
| 3805 | break; |
| 3806 | |
| 3807 | case CURLWC_MATCHING: { |
| 3808 | /* In this state is LIST response successfully parsed, so lets restore |
| 3809 | previous WRITEFUNCTION callback and WRITEDATA pointer */ |
| 3810 | struct ftp_wc *ftpwc = wildcard->protdata; |
| 3811 | conn->data->set.fwrite_func = ftpwc->backup.write_function; |
| 3812 | conn->data->set.out = ftpwc->backup.file_descriptor; |
| 3813 | ftpwc->backup.write_function = ZERO_NULL; |
| 3814 | ftpwc->backup.file_descriptor = NULL; |
| 3815 | wildcard->state = CURLWC_DOWNLOADING; |
| 3816 | |
| 3817 | if(Curl_ftp_parselist_geterror(ftpwc->parser)) { |
| 3818 | /* error found in LIST parsing */ |
| 3819 | wildcard->state = CURLWC_CLEAN; |
| 3820 | return wc_statemach(conn); |
| C1: CURLWC_MATCHING case recursively calls wc_statemach | |
| 3821 | } |
| 3822 | if(wildcard->filelist.size == 0) { |
| 3823 | /* no corresponding file */ |
| 3824 | wildcard->state = CURLWC_CLEAN; |
| 3825 | return CURLE_REMOTE_FILE_NOT_FOUND; |
| 3826 | } |
| 3827 | return wc_statemach(conn); |
| 3828 | } |
| 3829 | |
| 3830 | case CURLWC_DOWNLOADING: { |
| 3831 | /* filelist has at least one file, lets get first one */ |
| 3832 | struct ftp_conn *ftpc = &conn->proto.ftpc; |
| 3833 | struct curl_fileinfo *finfo = wildcard->filelist.head->ptr; |
| C3: finfo comes from wildcard->filelist.head->ptr, populated from server directory listing | |
| 3834 | struct FTP *ftp = conn->data->req.p.ftp; |
| 3835 | |
| 3836 | char *tmp_path = aprintf("%s%s", wildcard->path, finfo->filename); |
| 3837 | if(!tmp_path) |
| 3838 | return CURLE_OUT_OF_MEMORY; |
| 3839 | |
| 3840 | /* switch default ftp->path and tmp_path */ |
| 3841 | free(ftp->pathalloc); |
| 3842 | ftp->pathalloc = ftp->path = tmp_path; |
| 3843 | |
| 3844 | infof(conn->data, "Wildcard - START of \"%s\"\n", finfo->filename); |
| 3845 | if(conn->data->set.chunk_bgn) { |
| 3846 | long userresponse; |
| 3847 | Curl_set_in_callback(conn->data, true); |
| 3848 | userresponse = conn->data->set.chunk_bgn( |
| 3849 | finfo, wildcard->customptr, (int)wildcard->filelist.size); |
| 3850 | Curl_set_in_callback(conn->data, false); |
| 3851 | switch(userresponse) { |
| 3852 | case CURL_CHUNK_BGN_FUNC_SKIP: |
| 3853 | infof(conn->data, "Wildcard - \"%s\" skipped by user\n", |
| 3854 | finfo->filename); |
| 3855 | wildcard->state = CURLWC_SKIP; |
| 3856 | return wc_statemach(conn); |
| C1: CURLWC_DOWNLOADING case (SKIP) recursively calls wc_statemach | |
| C4: Recursive call from DOWNLOADING to SKIP | |
| 3857 | case CURL_CHUNK_BGN_FUNC_FAIL: |
| 3858 | return CURLE_CHUNK_FAILED; |
| 3859 | } |
| 3860 | } |
| 3861 | |
| 3862 | if(finfo->filetype != CURLFILETYPE_FILE) { |
| 3863 | wildcard->state = CURLWC_SKIP; |
| 3864 | return wc_statemach(conn); |
| C1: CURLWC_DOWNLOADING case (non-file) recursively calls wc_statemach | |
| 3865 | } |
| 3866 | |
| 3867 | if(finfo->flags & CURLFINFOFLAG_KNOWN_SIZE) |
| 3868 | ftpc->known_filesize = finfo->size; |
| 3869 | |
| 3870 | result = ftp_parse_url_path(conn); |
| 3871 | if(result) |
| 3872 | return result; |
| 3873 | |
| 3874 | /* we don't need the Curl_fileinfo of first file anymore */ |
| 3875 | Curl_llist_remove(&wildcard->filelist, wildcard->filelist.head, NULL); |
| 3876 | |
| 3877 | if(wildcard->filelist.size == 0) { /* remains only one file to down. */ |
| 3878 | wildcard->state = CURLWC_CLEAN; |
| 3879 | /* after that will be ftp_do called once again and no transfer |
| 3880 | will be done because of CURLWC_CLEAN state */ |
| 3881 | return CURLE_OK; |
| 3882 | } |
| 3883 | } break; |
| 3884 | |
| 3885 | case CURLWC_SKIP: { |
| 3886 | if(conn->data->set.chunk_end) { |
| 3887 | Curl_set_in_callback(conn->data, true); |
| 3888 | conn->data->set.chunk_end(conn->data->wildcard.customptr); |
| 3889 | Curl_set_in_callback(conn->data, false); |
| 3890 | } |
| 3891 | Curl_llist_remove(&wildcard->filelist, wildcard->filelist.head, NULL); |
| C3: Curl_llist_remove processes each file in the server-provided list | |
| 3892 | wildcard->state = (wildcard->filelist.size == 0) ? |
| 3893 | CURLWC_CLEAN : CURLWC_DOWNLOADING; |
| 3894 | return wc_statemach(conn); |
| C1: CURLWC_SKIP case recursively calls wc_statemach | |
| C4: Recursive call from SKIP back to DOWNLOADING (if list not empty) | |
| 3895 | } |
| 3896 | |
| 3897 | case CURLWC_CLEAN: { |
| 3898 | struct ftp_wc *ftpwc = wildcard->protdata; |
| 3899 | result = CURLE_OK; |
| 3900 | if(ftpwc) |
| 3901 | result = Curl_ftp_parselist_geterror(ftpwc->parser); |
| 3902 | |
| 3903 | wildcard->state = result ? CURLWC_ERROR : CURLWC_DONE; |
| 3904 | } break; |
| 3905 | |
| 3906 | case CURLWC_DONE: |
| 3907 | case CURLWC_ERROR: |
| 3908 | case CURLWC_CLEAR: |
| 3909 | if(wildcard->dtor) |
| 3910 | wildcard->dtor(wildcard->protdata); |
| 3911 | break; |
| 3912 | } |
| 3913 | |
| 3914 | return result; |
| 3915 | } |
| 3916 | |
| 3917 | /*********************************************************************** |
| 3918 | * |
| 3919 | * ftp_do() |
| 412 | if(source) |
| 413 | curl_dbg_log("FILE %s:%d fdopen(\"%d\",\"%s\") = %p\n", |
| 414 | source, line, filedes, mode, (void *)res); |
| 415 | return res; |
| 416 | } |
| 417 | |
| 418 | int curl_dbg_fclose(FILE *file, int line, const char *source) |
| 419 | { |
| 420 | int res; |
| 421 | |
| 422 | DEBUGASSERT(file != NULL); |
| 423 | |
| 424 | if(source) |
| 425 | curl_dbg_log("FILE %s:%d fclose(%p)\n", |
| 426 | source, line, (void *)file); |
| 427 | |
| 428 | res = fclose(file); |
| 429 | |
| 430 | return res; |
| 431 | } |
| 432 | |
| 433 | #define LOGLINE_BUFSIZE 1024 |
| 434 | |
| 435 | /* this does the writing to the memory tracking log file */ |
| 436 | void curl_dbg_log(const char *format, ...) |
| 437 | { |
| 438 | char *buf; |
| 439 | int nchars; |
| 440 | va_list ap; |
| 441 | |
| 442 | if(!curl_dbg_logfile) |
| 443 | return; |
| 444 | |
| 445 | buf = (Curl_cmalloc)(LOGLINE_BUFSIZE); |
| 446 | if(!buf) |
| 447 | return; |
| 448 | |
| 449 | va_start(ap, format); |
| 450 | nchars = mvsnprintf(buf, LOGLINE_BUFSIZE, format, ap); |
| 451 | va_end(ap); |
| 452 | |
| 453 | if(nchars > LOGLINE_BUFSIZE - 1) |
| 454 | nchars = LOGLINE_BUFSIZE - 1; |
| 455 | |
| 456 | if(nchars > 0) |
| 457 | fwrite(buf, 1, (size_t)nchars, curl_dbg_logfile); |
| 458 | |
| 459 | (Curl_cfree)(buf); |
| 460 | } |
| 461 | |
| 462 | #endif /* CURLDEBUG */ |
Show 53 lines (not cited by the judge)
| 220 | void *curl_dbg_realloc(void *ptr, size_t wantedsize, |
| 221 | int line, const char *source) |
| 222 | { |
| 223 | struct memdebug *mem = NULL; |
| 224 | |
| 225 | size_t size = sizeof(struct memdebug) + wantedsize; |
| 226 | |
| 227 | DEBUGASSERT(wantedsize != 0); |
| 228 | |
| 229 | if(countcheck("realloc", line, source)) |
| 230 | return NULL; |
| 231 | |
| 232 | #ifdef __INTEL_COMPILER |
| 233 | # pragma warning(push) |
| 234 | # pragma warning(disable:1684) |
| 235 | /* 1684: conversion from pointer to same-sized integral type */ |
| 236 | #endif |
| 237 | |
| 238 | if(ptr) |
| 239 | mem = (void *)((char *)ptr - offsetof(struct memdebug, mem)); |
| 240 | |
| 241 | #ifdef __INTEL_COMPILER |
| 242 | # pragma warning(pop) |
| 243 | #endif |
| 244 | |
| 245 | mem = (Curl_crealloc)(mem, size); |
| 246 | if(source) |
| 247 | curl_dbg_log("MEM %s:%d realloc(%p, %zu) = %p\n", |
| 248 | source, line, (void *)ptr, wantedsize, |
| 249 | mem ? (void *)mem->mem : (void *)0); |
| 250 | |
| 251 | if(mem) { |
| 252 | mem->size = wantedsize; |
| 253 | return mem->mem; |
| 254 | } |
| 255 | |
| 256 | return NULL; |
| 257 | } |
| 258 | |
| 259 | void curl_dbg_free(void *ptr, int line, const char *source) |
| 260 | { |
| 261 | if(ptr) { |
| 262 | struct memdebug *mem; |
| 263 | |
| 264 | #ifdef __INTEL_COMPILER |
| 265 | # pragma warning(push) |
| 266 | # pragma warning(disable:1684) |
| 267 | /* 1684: conversion from pointer to same-sized integral type */ |
| 268 | #endif |
| 269 | |
| 270 | mem = (void *)((char *)ptr - offsetof(struct memdebug, mem)); |
| 271 | |
| 272 | #ifdef __INTEL_COMPILER |
Show 59 lines (not cited by the judge)
| 65 | static CURLcode dyn_nappend(struct dynbuf *s, |
| 66 | const unsigned char *mem, size_t len) |
| 67 | { |
| 68 | size_t indx = s->leng; |
| 69 | size_t a = s->allc; |
| 70 | size_t fit = len + indx + 1; /* new string + old string + zero byte */ |
| 71 | |
| 72 | /* try to detect if there's rubbish in the struct */ |
| 73 | DEBUGASSERT(s->init == DYNINIT); |
| 74 | DEBUGASSERT(s->toobig); |
| 75 | DEBUGASSERT(indx < s->toobig); |
| 76 | DEBUGASSERT(!s->leng || s->bufr); |
| 77 | |
| 78 | if(fit > s->toobig) { |
| 79 | Curl_dyn_free(s); |
| 80 | return CURLE_OUT_OF_MEMORY; |
| 81 | } |
| 82 | else if(!a) { |
| 83 | DEBUGASSERT(!indx); |
| 84 | /* first invoke */ |
| 85 | if(fit < MIN_FIRST_ALLOC) |
| 86 | a = MIN_FIRST_ALLOC; |
| 87 | else |
| 88 | a = fit; |
| 89 | } |
| 90 | else { |
| 91 | while(a < fit) |
| 92 | a *= 2; |
| 93 | } |
| 94 | |
| 95 | if(a != s->allc) { |
| 96 | /* this logic is not using Curl_saferealloc() to make the tool not have to |
| 97 | include that as well when it uses this code */ |
| 98 | void *p = realloc(s->bufr, a); |
| 99 | if(!p) { |
| 100 | Curl_safefree(s->bufr); |
| 101 | s->leng = s->allc = 0; |
| 102 | return CURLE_OUT_OF_MEMORY; |
| 103 | } |
| 104 | s->bufr = p; |
| 105 | s->allc = a; |
| 106 | } |
| 107 | |
| 108 | if(len) |
| 109 | memcpy(&s->bufr[indx], mem, len); |
| 110 | s->leng = indx + len; |
| 111 | s->bufr[s->leng] = 0; |
| 112 | return CURLE_OK; |
| 113 | } |
| 114 | |
| 115 | /* |
| 116 | * Clears the string, keeps the allocation. This can also be called on a |
| 117 | * buffer that already was freed. |
| 118 | */ |
| 119 | void Curl_dyn_reset(struct dynbuf *s) |
| 120 | { |
| 121 | DEBUGASSERT(s); |
| 122 | DEBUGASSERT(s->init == DYNINIT); |
| 123 | DEBUGASSERT(!s->leng || s->bufr); |
Show 51 lines (not cited by the judge)
| 139 | if(trail > s->leng) |
| 140 | return CURLE_BAD_FUNCTION_ARGUMENT; |
| 141 | else if(trail == s->leng) |
| 142 | return CURLE_OK; |
| 143 | else if(!trail) { |
| 144 | Curl_dyn_reset(s); |
| 145 | } |
| 146 | else { |
| 147 | memmove(&s->bufr[0], &s->bufr[s->leng - trail], trail); |
| 148 | s->leng = trail; |
| 149 | s->bufr[s->leng] = 0; |
| 150 | } |
| 151 | return CURLE_OK; |
| 152 | |
| 153 | } |
| 154 | #endif |
| 155 | |
| 156 | /* |
| 157 | * Appends a buffer with length. |
| 158 | */ |
| 159 | CURLcode Curl_dyn_addn(struct dynbuf *s, const void *mem, size_t len) |
| 160 | { |
| 161 | DEBUGASSERT(s); |
| 162 | DEBUGASSERT(s->init == DYNINIT); |
| 163 | DEBUGASSERT(!s->leng || s->bufr); |
| 164 | return dyn_nappend(s, mem, len); |
| 165 | } |
| 166 | |
| 167 | /* |
| 168 | * Append a null-terminated string at the end. |
| 169 | */ |
| 170 | CURLcode Curl_dyn_add(struct dynbuf *s, const char *str) |
| 171 | { |
| 172 | size_t n = strlen(str); |
| 173 | DEBUGASSERT(s); |
| 174 | DEBUGASSERT(s->init == DYNINIT); |
| 175 | DEBUGASSERT(!s->leng || s->bufr); |
| 176 | return dyn_nappend(s, (unsigned char *)str, n); |
| 177 | } |
| 178 | |
| 179 | /* |
| 180 | * Append a string vprintf()-style |
| 181 | */ |
| 182 | CURLcode Curl_dyn_vaddf(struct dynbuf *s, const char *fmt, va_list ap) |
| 183 | { |
| 184 | #ifdef BUILDING_LIBCURL |
| 185 | int rc; |
| 186 | DEBUGASSERT(s); |
| 187 | DEBUGASSERT(s->init == DYNINIT); |
| 188 | DEBUGASSERT(!s->leng || s->bufr); |
| 189 | rc = Curl_dyn_vprintf(s, fmt, ap); |
Show 101 lines (not cited by the judge)
| 1020 | if(info.max == info.length) |
| 1021 | /* we're at maximum, scrap the last letter */ |
| 1022 | info.buffer[-1] = 0; |
| 1023 | else |
| 1024 | info.buffer[0] = 0; |
| 1025 | } |
| 1026 | return retcode; |
| 1027 | } |
| 1028 | |
| 1029 | int curl_msnprintf(char *buffer, size_t maxlength, const char *format, ...) |
| 1030 | { |
| 1031 | int retcode; |
| 1032 | va_list ap_save; /* argument pointer */ |
| 1033 | va_start(ap_save, format); |
| 1034 | retcode = curl_mvsnprintf(buffer, maxlength, format, ap_save); |
| 1035 | va_end(ap_save); |
| 1036 | return retcode; |
| 1037 | } |
| 1038 | |
| 1039 | /* fputc() look-alike */ |
| 1040 | static int alloc_addbyter(int output, FILE *data) |
| 1041 | { |
| 1042 | struct asprintf *infop = (struct asprintf *)data; |
| 1043 | unsigned char outc = (unsigned char)output; |
| 1044 | |
| 1045 | if(Curl_dyn_addn(infop->b, &outc, 1)) { |
| 1046 | infop->fail = 1; |
| 1047 | return -1; /* fail */ |
| 1048 | } |
| 1049 | return outc; /* fputc() returns like this on success */ |
| 1050 | } |
| 1051 | |
| 1052 | extern int Curl_dyn_vprintf(struct dynbuf *dyn, |
| 1053 | const char *format, va_list ap_save); |
| 1054 | |
| 1055 | /* appends the formatted string, returns 0 on success, 1 on error */ |
| 1056 | int Curl_dyn_vprintf(struct dynbuf *dyn, const char *format, va_list ap_save) |
| 1057 | { |
| 1058 | int retcode; |
| 1059 | struct asprintf info; |
| 1060 | info.b = dyn; |
| 1061 | info.fail = 0; |
| 1062 | |
| 1063 | retcode = dprintf_formatf(&info, alloc_addbyter, format, ap_save); |
| 1064 | if((-1 == retcode) || info.fail) { |
| 1065 | Curl_dyn_free(info.b); |
| 1066 | return 1; |
| 1067 | } |
| 1068 | return 0; |
| 1069 | } |
| 1070 | |
| 1071 | char *curl_mvaprintf(const char *format, va_list ap_save) |
| 1072 | { |
| 1073 | int retcode; |
| 1074 | struct asprintf info; |
| 1075 | struct dynbuf dyn; |
| 1076 | info.b = &dyn; |
| 1077 | Curl_dyn_init(info.b, DYN_APRINTF); |
| 1078 | info.fail = 0; |
| 1079 | |
| 1080 | retcode = dprintf_formatf(&info, alloc_addbyter, format, ap_save); |
| 1081 | if((-1 == retcode) || info.fail) { |
| 1082 | Curl_dyn_free(info.b); |
| 1083 | return NULL; |
| 1084 | } |
| 1085 | if(Curl_dyn_len(info.b)) |
| 1086 | return Curl_dyn_ptr(info.b); |
| 1087 | return strdup(""); |
| 1088 | } |
| 1089 | |
| 1090 | char *curl_maprintf(const char *format, ...) |
| 1091 | { |
| 1092 | va_list ap_save; |
| 1093 | char *s; |
| 1094 | va_start(ap_save, format); |
| 1095 | s = curl_mvaprintf(format, ap_save); |
| 1096 | va_end(ap_save); |
| 1097 | return s; |
| 1098 | } |
| 1099 | |
| 1100 | static int storebuffer(int output, FILE *data) |
| 1101 | { |
| 1102 | char **buffer = (char **)data; |
| 1103 | unsigned char outc = (unsigned char)output; |
| 1104 | **buffer = outc; |
| 1105 | (*buffer)++; |
| 1106 | return outc; /* act like fputc() ! */ |
| 1107 | } |
| 1108 | |
| 1109 | int curl_msprintf(char *buffer, const char *format, ...) |
| 1110 | { |
| 1111 | va_list ap_save; /* argument pointer */ |
| 1112 | int retcode; |
| 1113 | va_start(ap_save, format); |
| 1114 | retcode = dprintf_formatf(&buffer, storebuffer, format, ap_save); |
| 1115 | va_end(ap_save); |
| 1116 | *buffer = 0; /* we terminate this with a zero byte */ |
| 1117 | return retcode; |
| 1118 | } |
| 1119 | |
| 1120 | int curl_mprintf(const char *format, ...) |
Show 161 lines (not cited by the judge)
| 563 | static int dprintf_formatf( |
| 564 | void *data, /* untouched by format(), just sent to the stream() function in |
| 565 | the second argument */ |
| 566 | /* function pointer called for each output character */ |
| 567 | int (*stream)(int, FILE *), |
| 568 | const char *format, /* %-formatted string */ |
| 569 | va_list ap_save) /* list of parameters */ |
| 570 | { |
| 571 | /* Base-36 digits for numbers. */ |
| 572 | const char *digits = lower_digits; |
| 573 | |
| 574 | /* Pointer into the format string. */ |
| 575 | char *f; |
| 576 | |
| 577 | /* Number of characters written. */ |
| 578 | int done = 0; |
| 579 | |
| 580 | long param; /* current parameter to read */ |
| 581 | long param_num = 0; /* parameter counter */ |
| 582 | |
| 583 | struct va_stack vto[MAX_PARAMETERS]; |
| 584 | char *endpos[MAX_PARAMETERS]; |
| 585 | char **end; |
| 586 | char work[BUFFSIZE]; |
| 587 | struct va_stack *p; |
| 588 | |
| 589 | /* 'workend' points to the final buffer byte position, but with an extra |
| 590 | byte as margin to avoid the (false?) warning Coverity gives us |
| 591 | otherwise */ |
| 592 | char *workend = &work[sizeof(work) - 2]; |
| 593 | |
| 594 | /* Do the actual %-code parsing */ |
| 595 | if(dprintf_Pass1(format, vto, endpos, ap_save)) |
| 596 | return -1; |
| 597 | |
| 598 | end = &endpos[0]; /* the initial end-position from the list dprintf_Pass1() |
| 599 | created for us */ |
| 600 | |
| 601 | f = (char *)format; |
| 602 | while(*f != '\0') { |
| 603 | /* Format spec modifiers. */ |
| 604 | int is_alt; |
| 605 | |
| 606 | /* Width of a field. */ |
| 607 | long width; |
| 608 | |
| 609 | /* Precision of a field. */ |
| 610 | long prec; |
| 611 | |
| 612 | /* Decimal integer is negative. */ |
| 613 | int is_neg; |
| 614 | |
| 615 | /* Base of a number to be written. */ |
| 616 | unsigned long base; |
| 617 | |
| 618 | /* Integral values to be written. */ |
| 619 | mp_uintmax_t num; |
| 620 | |
| 621 | /* Used to convert negative in positive. */ |
| 622 | mp_intmax_t signed_num; |
| 623 | |
| 624 | char *w; |
| 625 | |
| 626 | if(*f != '%') { |
| 627 | /* This isn't a format spec, so write everything out until the next one |
| 628 | OR end of string is reached. */ |
| 629 | do { |
| 630 | OUTCHAR(*f); |
| 631 | } while(*++f && ('%' != *f)); |
| 632 | continue; |
| 633 | } |
| 634 | |
| 635 | ++f; |
| 636 | |
| 637 | /* Check for "%%". Note that although the ANSI standard lists |
| 638 | '%' as a conversion specifier, it says "The complete format |
| 639 | specification shall be `%%'," so we can avoid all the width |
| 640 | and precision processing. */ |
| 641 | if(*f == '%') { |
| 642 | ++f; |
| 643 | OUTCHAR('%'); |
| 644 | continue; |
| 645 | } |
| 646 | |
| 647 | /* If this is a positional parameter, the position must follow immediately |
| 648 | after the %, thus create a %<num>$ sequence */ |
| 649 | param = dprintf_DollarString(f, &f); |
| 650 | |
| 651 | if(!param) |
| 652 | param = param_num; |
| 653 | else |
| 654 | --param; |
| 655 | |
| 656 | param_num++; /* increase this always to allow "%2$s %1$s %s" and then the |
| 657 | third %s will pick the 3rd argument */ |
| 658 | |
| 659 | p = &vto[param]; |
| 660 | |
| 661 | /* pick up the specified width */ |
| 662 | if(p->flags & FLAGS_WIDTHPARAM) { |
| 663 | width = (long)vto[p->width].data.num.as_signed; |
| 664 | param_num++; /* since the width is extracted from a parameter, we |
| 665 | must skip that to get to the next one properly */ |
| 666 | if(width < 0) { |
| 667 | /* "A negative field width is taken as a '-' flag followed by a |
| 668 | positive field width." */ |
| 669 | width = -width; |
| 670 | p->flags |= FLAGS_LEFT; |
| 671 | p->flags &= ~FLAGS_PAD_NIL; |
| 672 | } |
| 673 | } |
| 674 | else |
| 675 | width = p->width; |
| 676 | |
| 677 | /* pick up the specified precision */ |
| 678 | if(p->flags & FLAGS_PRECPARAM) { |
| 679 | prec = (long)vto[p->precision].data.num.as_signed; |
| 680 | param_num++; /* since the precision is extracted from a parameter, we |
| 681 | must skip that to get to the next one properly */ |
| 682 | if(prec < 0) |
| 683 | /* "A negative precision is taken as if the precision were |
| 684 | omitted." */ |
| 685 | prec = -1; |
| 686 | } |
| 687 | else if(p->flags & FLAGS_PREC) |
| 688 | prec = p->precision; |
| 689 | else |
| 690 | prec = -1; |
| 691 | |
| 692 | is_alt = (p->flags & FLAGS_ALT) ? 1 : 0; |
| 693 | |
| 694 | switch(p->type) { |
| 695 | case FORMAT_INT: |
| 696 | num = p->data.num.as_unsigned; |
| 697 | if(p->flags & FLAGS_CHAR) { |
| 698 | /* Character. */ |
| 699 | if(!(p->flags & FLAGS_LEFT)) |
| 700 | while(--width > 0) |
| 701 | OUTCHAR(' '); |
| 702 | OUTCHAR((char) num); |
| 703 | if(p->flags & FLAGS_LEFT) |
| 704 | while(--width > 0) |
| 705 | OUTCHAR(' '); |
| 706 | break; |
| 707 | } |
| 708 | if(p->flags & FLAGS_OCTAL) { |
| 709 | /* Octal unsigned integer. */ |
| 710 | base = 8; |
| 711 | goto unsigned_number; |
| 712 | } |
| 713 | else if(p->flags & FLAGS_HEX) { |
| 714 | /* Hexadecimal unsigned integer. */ |
| 715 | |
| 716 | digits = (p->flags & FLAGS_UPPER)? upper_digits : lower_digits; |
| 717 | base = 16; |
| 718 | goto unsigned_number; |
| 719 | } |
| 720 | else if(p->flags & FLAGS_UNSIGNED) { |
| 721 | /* Decimal unsigned integer. */ |
| 722 | base = 10; |
| 723 | goto unsigned_number; |
Show 123 lines (not cited by the judge)
| 3794 | static CURLcode wc_statemach(struct connectdata *conn) |
| 3795 | { |
| 3796 | struct WildcardData * const wildcard = &(conn->data->wildcard); |
| 3797 | CURLcode result = CURLE_OK; |
| 3798 | |
| 3799 | switch(wildcard->state) { |
| 3800 | case CURLWC_INIT: |
| 3801 | result = init_wc_data(conn); |
| 3802 | if(wildcard->state == CURLWC_CLEAN) |
| 3803 | /* only listing! */ |
| 3804 | break; |
| 3805 | wildcard->state = result ? CURLWC_ERROR : CURLWC_MATCHING; |
| 3806 | break; |
| 3807 | |
| 3808 | case CURLWC_MATCHING: { |
| 3809 | /* In this state is LIST response successfully parsed, so lets restore |
| 3810 | previous WRITEFUNCTION callback and WRITEDATA pointer */ |
| 3811 | struct ftp_wc *ftpwc = wildcard->protdata; |
| 3812 | conn->data->set.fwrite_func = ftpwc->backup.write_function; |
| 3813 | conn->data->set.out = ftpwc->backup.file_descriptor; |
| 3814 | ftpwc->backup.write_function = ZERO_NULL; |
| 3815 | ftpwc->backup.file_descriptor = NULL; |
| 3816 | wildcard->state = CURLWC_DOWNLOADING; |
| 3817 | |
| 3818 | if(Curl_ftp_parselist_geterror(ftpwc->parser)) { |
| 3819 | /* error found in LIST parsing */ |
| 3820 | wildcard->state = CURLWC_CLEAN; |
| 3821 | return wc_statemach(conn); |
| 3822 | } |
| 3823 | if(wildcard->filelist.size == 0) { |
| 3824 | /* no corresponding file */ |
| 3825 | wildcard->state = CURLWC_CLEAN; |
| 3826 | return CURLE_REMOTE_FILE_NOT_FOUND; |
| 3827 | } |
| 3828 | return wc_statemach(conn); |
| 3829 | } |
| 3830 | |
| 3831 | case CURLWC_DOWNLOADING: { |
| 3832 | /* filelist has at least one file, lets get first one */ |
| 3833 | struct ftp_conn *ftpc = &conn->proto.ftpc; |
| 3834 | struct curl_fileinfo *finfo = wildcard->filelist.head->ptr; |
| 3835 | struct FTP *ftp = conn->data->req.protop; |
| 3836 | |
| 3837 | char *tmp_path = aprintf("%s%s", wildcard->path, finfo->filename); |
| 3838 | if(!tmp_path) |
| 3839 | return CURLE_OUT_OF_MEMORY; |
| 3840 | |
| 3841 | /* switch default ftp->path and tmp_path */ |
| 3842 | free(ftp->pathalloc); |
| 3843 | ftp->pathalloc = ftp->path = tmp_path; |
| 3844 | |
| 3845 | infof(conn->data, "Wildcard - START of \"%s\"\n", finfo->filename); |
| 3846 | if(conn->data->set.chunk_bgn) { |
| 3847 | long userresponse; |
| 3848 | Curl_set_in_callback(conn->data, true); |
| 3849 | userresponse = conn->data->set.chunk_bgn( |
| 3850 | finfo, wildcard->customptr, (int)wildcard->filelist.size); |
| 3851 | Curl_set_in_callback(conn->data, false); |
| 3852 | switch(userresponse) { |
| 3853 | case CURL_CHUNK_BGN_FUNC_SKIP: |
| 3854 | infof(conn->data, "Wildcard - \"%s\" skipped by user\n", |
| 3855 | finfo->filename); |
| 3856 | wildcard->state = CURLWC_SKIP; |
| 3857 | return wc_statemach(conn); |
| 3858 | case CURL_CHUNK_BGN_FUNC_FAIL: |
| 3859 | return CURLE_CHUNK_FAILED; |
| 3860 | } |
| 3861 | } |
| 3862 | |
| 3863 | if(finfo->filetype != CURLFILETYPE_FILE) { |
| 3864 | wildcard->state = CURLWC_SKIP; |
| 3865 | return wc_statemach(conn); |
| 3866 | } |
| 3867 | |
| 3868 | if(finfo->flags & CURLFINFOFLAG_KNOWN_SIZE) |
| 3869 | ftpc->known_filesize = finfo->size; |
| 3870 | |
| 3871 | result = ftp_parse_url_path(conn); |
| 3872 | if(result) |
| 3873 | return result; |
| 3874 | |
| 3875 | /* we don't need the Curl_fileinfo of first file anymore */ |
| 3876 | Curl_llist_remove(&wildcard->filelist, wildcard->filelist.head, NULL); |
| 3877 | |
| 3878 | if(wildcard->filelist.size == 0) { /* remains only one file to down. */ |
| 3879 | wildcard->state = CURLWC_CLEAN; |
| 3880 | /* after that will be ftp_do called once again and no transfer |
| 3881 | will be done because of CURLWC_CLEAN state */ |
| 3882 | return CURLE_OK; |
| 3883 | } |
| 3884 | } break; |
| 3885 | |
| 3886 | case CURLWC_SKIP: { |
| 3887 | if(conn->data->set.chunk_end) { |
| 3888 | Curl_set_in_callback(conn->data, true); |
| 3889 | conn->data->set.chunk_end(conn->data->wildcard.customptr); |
| 3890 | Curl_set_in_callback(conn->data, false); |
| 3891 | } |
| 3892 | Curl_llist_remove(&wildcard->filelist, wildcard->filelist.head, NULL); |
| 3893 | wildcard->state = (wildcard->filelist.size == 0) ? |
| 3894 | CURLWC_CLEAN : CURLWC_DOWNLOADING; |
| 3895 | return wc_statemach(conn); |
| 3896 | } |
| 3897 | |
| 3898 | case CURLWC_CLEAN: { |
| 3899 | struct ftp_wc *ftpwc = wildcard->protdata; |
| 3900 | result = CURLE_OK; |
| 3901 | if(ftpwc) |
| 3902 | result = Curl_ftp_parselist_geterror(ftpwc->parser); |
| 3903 | |
| 3904 | wildcard->state = result ? CURLWC_ERROR : CURLWC_DONE; |
| 3905 | } break; |
| 3906 | |
| 3907 | case CURLWC_DONE: |
| 3908 | case CURLWC_ERROR: |
| 3909 | case CURLWC_CLEAR: |
| 3910 | if(wildcard->dtor) |
| 3911 | wildcard->dtor(wildcard->protdata); |
| 3912 | break; |
| 3913 | } |
| 3914 | |
| 3915 | return result; |
| 3916 | } |