1 /*************************************************************************** 2 * _ _ ____ _ 3 * Project ___| | | | _ \| | 4 * / __| | | | |_) | | 5 * | (__| |_| | _ <| |___ 6 * \___|\___/|_| \_\_____| 7 * 8 * Copyright (C) 1998 - 2016, Daniel Stenberg, <daniel (at) 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 #include "tool_setup.h" 23 24 #include "rawstr.h" 25 26 #define ENABLE_CURLX_PRINTF 27 /* use our own printf() functions */ 28 #include "curlx.h" 29 30 #include "tool_cfgable.h" 31 #include "tool_mfiles.h" 32 #include "tool_msgs.h" 33 #include "tool_formparse.h" 34 35 #include "memdebug.h" /* keep this as LAST include */ 36 37 38 /* 39 * helper function to get a word from form param 40 * after call get_parm_word, str either point to string end 41 * or point to any of end chars. 42 */ 43 static char *get_param_word(char **str, char **end_pos) 44 { 45 char *ptr = *str; 46 char *word_begin = NULL; 47 char *ptr2; 48 char *escape = NULL; 49 const char *end_chars = ";,"; 50 51 /* the first non-space char is here */ 52 word_begin = ptr; 53 if(*ptr == '"') { 54 ++ptr; 55 while(*ptr) { 56 if(*ptr == '\\') { 57 if(ptr[1] == '\\' || ptr[1] == '"') { 58 /* remember the first escape position */ 59 if(!escape) 60 escape = ptr; 61 /* skip escape of back-slash or double-quote */ 62 ptr += 2; 63 continue; 64 } 65 } 66 if(*ptr == '"') { 67 *end_pos = ptr; 68 if(escape) { 69 /* has escape, we restore the unescaped string here */ 70 ptr = ptr2 = escape; 71 do { 72 if(*ptr == '\\' && (ptr[1] == '\\' || ptr[1] == '"')) 73 ++ptr; 74 *ptr2++ = *ptr++; 75 } 76 while(ptr < *end_pos); 77 *end_pos = ptr2; 78 } 79 while(*ptr && NULL==strchr(end_chars, *ptr)) 80 ++ptr; 81 *str = ptr; 82 return word_begin+1; 83 } 84 ++ptr; 85 } 86 /* end quote is missing, treat it as non-quoted. */ 87 ptr = word_begin; 88 } 89 90 while(*ptr && NULL==strchr(end_chars, *ptr)) 91 ++ptr; 92 *str = *end_pos = ptr; 93 return word_begin; 94 } 95 96 /*************************************************************************** 97 * 98 * formparse() 99 * 100 * Reads a 'name=value' parameter and builds the appropriate linked list. 101 * 102 * Specify files to upload with 'name=@filename', or 'name=@"filename"' 103 * in case the filename contain ',' or ';'. Supports specified 104 * given Content-Type of the files. Such as ';type=<content-type>'. 105 * 106 * If literal_value is set, any initial '@' or '<' in the value string 107 * loses its special meaning, as does any embedded ';type='. 108 * 109 * You may specify more than one file for a single name (field). Specify 110 * multiple files by writing it like: 111 * 112 * 'name=@filename,filename2,filename3' 113 * 114 * or use double-quotes quote the filename: 115 * 116 * 'name=@"filename","filename2","filename3"' 117 * 118 * If you want content-types specified for each too, write them like: 119 * 120 * 'name=@filename;type=image/gif,filename2,filename3' 121 * 122 * If you want custom headers added for a single part, write them in a separate 123 * file and do like this: 124 * 125 * 'name=foo;headers=@headerfile' or why not 126 * 'name=@filemame;headers=@headerfile' 127 * 128 * To upload a file, but to fake the file name that will be included in the 129 * formpost, do like this: 130 * 131 * 'name=@filename;filename=/dev/null' or quote the faked filename like: 132 * 'name=@filename;filename="play, play, and play.txt"' 133 * 134 * If filename/path contains ',' or ';', it must be quoted by double-quotes, 135 * else curl will fail to figure out the correct filename. if the filename 136 * tobe quoted contains '"' or '\', '"' and '\' must be escaped by backslash. 137 * 138 * This function uses curl_formadd to fulfill it's job. Is heavily based on 139 * the old curl_formparse code. 140 * 141 ***************************************************************************/ 142 143 int formparse(struct OperationConfig *config, 144 const char *input, 145 struct curl_httppost **httppost, 146 struct curl_httppost **last_post, 147 bool literal_value) 148 { 149 /* nextarg MUST be a string in the format 'name=contents' and we'll 150 build a linked list with the info */ 151 char name[256]; 152 char *contents = NULL; 153 char type_major[128] = ""; 154 char type_minor[128] = ""; 155 char *contp; 156 const char *type = NULL; 157 char *sep; 158 159 if((1 == sscanf(input, "%255[^=]=", name)) && 160 ((contp = strchr(input, '=')) != NULL)) { 161 /* the input was using the correct format */ 162 163 /* Allocate the contents */ 164 contents = strdup(contp+1); 165 if(!contents) { 166 fprintf(config->global->errors, "out of memory\n"); 167 return 1; 168 } 169 contp = contents; 170 171 if('@' == contp[0] && !literal_value) { 172 173 /* we use the @-letter to indicate file name(s) */ 174 175 struct multi_files *multi_start = NULL; 176 struct multi_files *multi_current = NULL; 177 178 char *ptr = contp; 179 char *end = ptr + strlen(ptr); 180 181 do { 182 /* since this was a file, it may have a content-type specifier 183 at the end too, or a filename. Or both. */ 184 char *filename = NULL; 185 char *word_end; 186 bool semicolon; 187 188 type = NULL; 189 190 ++ptr; 191 contp = get_param_word(&ptr, &word_end); 192 semicolon = (';' == *ptr) ? TRUE : FALSE; 193 *word_end = '\0'; /* terminate the contp */ 194 195 /* have other content, continue parse */ 196 while(semicolon) { 197 /* have type or filename field */ 198 ++ptr; 199 while(*ptr && (ISSPACE(*ptr))) 200 ++ptr; 201 202 if(checkprefix("type=", ptr)) { 203 /* set type pointer */ 204 type = &ptr[5]; 205 206 /* verify that this is a fine type specifier */ 207 if(2 != sscanf(type, "%127[^/]/%127[^;,\n]", 208 type_major, type_minor)) { 209 warnf(config->global, 210 "Illegally formatted content-type field!\n"); 211 Curl_safefree(contents); 212 FreeMultiInfo(&multi_start, &multi_current); 213 return 2; /* illegal content-type syntax! */ 214 } 215 216 /* now point beyond the content-type specifier */ 217 sep = (char *)type + strlen(type_major)+strlen(type_minor)+1; 218 219 /* there's a semicolon following - we check if it is a filename 220 specified and if not we simply assume that it is text that 221 the user wants included in the type and include that too up 222 to the next sep. */ 223 ptr = sep; 224 if(*sep==';') { 225 if(!checkprefix(";filename=", sep)) { 226 ptr = sep + 1; 227 (void)get_param_word(&ptr, &sep); 228 semicolon = (';' == *ptr) ? TRUE : FALSE; 229 } 230 } 231 else 232 semicolon = FALSE; 233 234 if(*sep) 235 *sep = '\0'; /* zero terminate type string */ 236 } 237 else if(checkprefix("filename=", ptr)) { 238 ptr += 9; 239 filename = get_param_word(&ptr, &word_end); 240 semicolon = (';' == *ptr) ? TRUE : FALSE; 241 *word_end = '\0'; 242 } 243 else { 244 /* unknown prefix, skip to next block */ 245 char *unknown = NULL; 246 unknown = get_param_word(&ptr, &word_end); 247 semicolon = (';' == *ptr) ? TRUE : FALSE; 248 if(*unknown) { 249 *word_end = '\0'; 250 warnf(config->global, "skip unknown form field: %s\n", unknown); 251 } 252 } 253 } 254 /* now ptr point to comma or string end */ 255 256 257 /* if type == NULL curl_formadd takes care of the problem */ 258 259 if(*contp && !AddMultiFiles(contp, type, filename, &multi_start, 260 &multi_current)) { 261 warnf(config->global, "Error building form post!\n"); 262 Curl_safefree(contents); 263 FreeMultiInfo(&multi_start, &multi_current); 264 return 3; 265 } 266 267 /* *ptr could be '\0', so we just check with the string end */ 268 } while(ptr < end); /* loop if there's another file name */ 269 270 /* now we add the multiple files section */ 271 if(multi_start) { 272 struct curl_forms *forms = NULL; 273 struct multi_files *start = multi_start; 274 unsigned int i, count = 0; 275 while(start) { 276 start = start->next; 277 ++count; 278 } 279 forms = malloc((count+1)*sizeof(struct curl_forms)); 280 if(!forms) { 281 fprintf(config->global->errors, "Error building form post!\n"); 282 Curl_safefree(contents); 283 FreeMultiInfo(&multi_start, &multi_current); 284 return 4; 285 } 286 for(i = 0, start = multi_start; i < count; ++i, start = start->next) { 287 forms[i].option = start->form.option; 288 forms[i].value = start->form.value; 289 } 290 forms[count].option = CURLFORM_END; 291 FreeMultiInfo(&multi_start, &multi_current); 292 if(curl_formadd(httppost, last_post, 293 CURLFORM_COPYNAME, name, 294 CURLFORM_ARRAY, forms, CURLFORM_END) != 0) { 295 warnf(config->global, "curl_formadd failed!\n"); 296 Curl_safefree(forms); 297 Curl_safefree(contents); 298 return 5; 299 } 300 Curl_safefree(forms); 301 } 302 } 303 else { 304 struct curl_forms info[4]; 305 int i = 0; 306 char *ct = literal_value ? NULL : strstr(contp, ";type="); 307 308 info[i].option = CURLFORM_COPYNAME; 309 info[i].value = name; 310 i++; 311 312 if(ct) { 313 info[i].option = CURLFORM_CONTENTTYPE; 314 info[i].value = &ct[6]; 315 i++; 316 ct[0] = '\0'; /* zero terminate here */ 317 } 318 319 if(contp[0]=='<' && !literal_value) { 320 info[i].option = CURLFORM_FILECONTENT; 321 info[i].value = contp+1; 322 i++; 323 info[i].option = CURLFORM_END; 324 325 if(curl_formadd(httppost, last_post, 326 CURLFORM_ARRAY, info, CURLFORM_END) != 0) { 327 warnf(config->global, "curl_formadd failed, possibly the file %s is " 328 "bad!\n", contp + 1); 329 Curl_safefree(contents); 330 return 6; 331 } 332 } 333 else { 334 #ifdef CURL_DOES_CONVERSIONS 335 if(convert_to_network(contp, strlen(contp))) { 336 warnf(config->global, "curl_formadd failed!\n"); 337 Curl_safefree(contents); 338 return 7; 339 } 340 #endif 341 info[i].option = CURLFORM_COPYCONTENTS; 342 info[i].value = contp; 343 i++; 344 info[i].option = CURLFORM_END; 345 if(curl_formadd(httppost, last_post, 346 CURLFORM_ARRAY, info, CURLFORM_END) != 0) { 347 warnf(config->global, "curl_formadd failed!\n"); 348 Curl_safefree(contents); 349 return 8; 350 } 351 } 352 } 353 354 } 355 else { 356 warnf(config->global, "Illegally formatted input field!\n"); 357 return 1; 358 } 359 Curl_safefree(contents); 360 return 0; 361 } 362