1/*************************************************************************** 2 * _ _ ____ _ 3 * Project ___| | | | _ \| | 4 * / __| | | | |_) | | 5 * | (__| |_| | _ <| |___ 6 * \___|\___/|_| \_\_____| 7 * 8 * Copyright (C) 1998 - 2013, 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 http://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 */ 43static 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 143int formparse(struct Configurable *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->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, "Illegally formatted content-type field!\n"); 210 Curl_safefree(contents); 211 FreeMultiInfo(&multi_start, &multi_current); 212 return 2; /* illegal content-type syntax! */ 213 } 214 215 /* now point beyond the content-type specifier */ 216 sep = (char *)type + strlen(type_major)+strlen(type_minor)+1; 217 218 /* there's a semicolon following - we check if it is a filename 219 specified and if not we simply assume that it is text that 220 the user wants included in the type and include that too up 221 to the next sep. */ 222 ptr = sep; 223 if(*sep==';') { 224 if(!checkprefix(";filename=", sep)) { 225 ptr = sep + 1; 226 (void)get_param_word(&ptr, &sep); 227 semicolon = (';' == *ptr) ? TRUE : FALSE; 228 } 229 } 230 else 231 semicolon = FALSE; 232 233 if(*sep) 234 *sep = '\0'; /* zero terminate type string */ 235 } 236 else if(checkprefix("filename=", ptr)) { 237 ptr += 9; 238 filename = get_param_word(&ptr, &word_end); 239 semicolon = (';' == *ptr) ? TRUE : FALSE; 240 *word_end = '\0'; 241 } 242 else { 243 /* unknown prefix, skip to next block */ 244 char *unknown = NULL; 245 unknown = get_param_word(&ptr, &word_end); 246 semicolon = (';' == *ptr) ? TRUE : FALSE; 247 if(*unknown) { 248 *word_end = '\0'; 249 warnf(config, "skip unknown form field: %s\n", unknown); 250 } 251 } 252 } 253 /* now ptr point to comma or string end */ 254 255 256 /* if type == NULL curl_formadd takes care of the problem */ 257 258 if(*contp && !AddMultiFiles(contp, type, filename, &multi_start, 259 &multi_current)) { 260 warnf(config, "Error building form post!\n"); 261 Curl_safefree(contents); 262 FreeMultiInfo(&multi_start, &multi_current); 263 return 3; 264 } 265 266 /* *ptr could be '\0', so we just check with the string end */ 267 } while(ptr < end); /* loop if there's another file name */ 268 269 /* now we add the multiple files section */ 270 if(multi_start) { 271 struct curl_forms *forms = NULL; 272 struct multi_files *start = multi_start; 273 unsigned int i, count = 0; 274 while(start) { 275 start = start->next; 276 ++count; 277 } 278 forms = malloc((count+1)*sizeof(struct curl_forms)); 279 if(!forms) { 280 fprintf(config->errors, "Error building form post!\n"); 281 Curl_safefree(contents); 282 FreeMultiInfo(&multi_start, &multi_current); 283 return 4; 284 } 285 for(i = 0, start = multi_start; i < count; ++i, start = start->next) { 286 forms[i].option = start->form.option; 287 forms[i].value = start->form.value; 288 } 289 forms[count].option = CURLFORM_END; 290 FreeMultiInfo(&multi_start, &multi_current); 291 if(curl_formadd(httppost, last_post, 292 CURLFORM_COPYNAME, name, 293 CURLFORM_ARRAY, forms, CURLFORM_END) != 0) { 294 warnf(config, "curl_formadd failed!\n"); 295 Curl_safefree(forms); 296 Curl_safefree(contents); 297 return 5; 298 } 299 Curl_safefree(forms); 300 } 301 } 302 else { 303 struct curl_forms info[4]; 304 int i = 0; 305 char *ct = literal_value ? NULL : strstr(contp, ";type="); 306 307 info[i].option = CURLFORM_COPYNAME; 308 info[i].value = name; 309 i++; 310 311 if(ct) { 312 info[i].option = CURLFORM_CONTENTTYPE; 313 info[i].value = &ct[6]; 314 i++; 315 ct[0] = '\0'; /* zero terminate here */ 316 } 317 318 if(contp[0]=='<' && !literal_value) { 319 info[i].option = CURLFORM_FILECONTENT; 320 info[i].value = contp+1; 321 i++; 322 info[i].option = CURLFORM_END; 323 324 if(curl_formadd(httppost, last_post, 325 CURLFORM_ARRAY, info, CURLFORM_END ) != 0) { 326 warnf(config, "curl_formadd failed, possibly the file %s is bad!\n", 327 contp+1); 328 Curl_safefree(contents); 329 return 6; 330 } 331 } 332 else { 333#ifdef CURL_DOES_CONVERSIONS 334 if(convert_to_network(contp, strlen(contp))) { 335 warnf(config, "curl_formadd failed!\n"); 336 Curl_safefree(contents); 337 return 7; 338 } 339#endif 340 info[i].option = CURLFORM_COPYCONTENTS; 341 info[i].value = contp; 342 i++; 343 info[i].option = CURLFORM_END; 344 if(curl_formadd(httppost, last_post, 345 CURLFORM_ARRAY, info, CURLFORM_END) != 0) { 346 warnf(config, "curl_formadd failed!\n"); 347 Curl_safefree(contents); 348 return 8; 349 } 350 } 351 } 352 353 } 354 else { 355 warnf(config, "Illegally formatted input field!\n"); 356 return 1; 357 } 358 Curl_safefree(contents); 359 return 0; 360} 361 362