diff options
author | Patrick Monnerat <patrick@monnerat.net> | 2017-09-02 17:47:10 +0100 |
---|---|---|
committer | Patrick Monnerat <patrick@monnerat.net> | 2017-09-02 17:47:10 +0100 |
commit | ce0881edee3c78609eae49665fb70264d8786d29 (patch) | |
tree | e4f5cda7865f3e73664dca5c4eb6a7a648c1e2d2 /lib | |
parent | 5bae72734b45a01c6337eb3b2c40020c4e904415 (diff) |
mime: new MIME API.
Available in HTTP, SMTP and IMAP.
Deprecates the FORM API.
See CURLOPT_MIMEPOST.
Lib code and associated documentation.
Diffstat (limited to 'lib')
-rw-r--r-- | lib/Makefile.inc | 5 | ||||
-rw-r--r-- | lib/formdata.c | 845 | ||||
-rw-r--r-- | lib/formdata.h | 54 | ||||
-rw-r--r-- | lib/http.c | 259 | ||||
-rw-r--r-- | lib/http.h | 4 | ||||
-rw-r--r-- | lib/http2.c | 1 | ||||
-rw-r--r-- | lib/imap.c | 48 | ||||
-rw-r--r-- | lib/mime.c | 1497 | ||||
-rw-r--r-- | lib/mime.h | 119 | ||||
-rw-r--r-- | lib/non-ascii.c | 25 | ||||
-rw-r--r-- | lib/non-ascii.h | 4 | ||||
-rw-r--r-- | lib/smtp.c | 37 | ||||
-rw-r--r-- | lib/transfer.c | 24 | ||||
-rw-r--r-- | lib/url.c | 22 | ||||
-rw-r--r-- | lib/urldata.h | 5 |
15 files changed, 1995 insertions, 954 deletions
diff --git a/lib/Makefile.inc b/lib/Makefile.inc index 19f58000a..5c0b930cf 100644 --- a/lib/Makefile.inc +++ b/lib/Makefile.inc @@ -53,7 +53,8 @@ LIB_CFILES = file.c timeval.c base64.c hostip.c progress.c formdata.c \ http_proxy.c non-ascii.c asyn-ares.c asyn-thread.c curl_gssapi.c \ http_ntlm.c curl_ntlm_wb.c curl_ntlm_core.c curl_sasl.c rand.c \ curl_multibyte.c hostcheck.c conncache.c pipeline.c dotdot.c \ - x509asn1.c http2.c smb.c curl_endian.c curl_des.c system_win32.c + x509asn1.c http2.c smb.c curl_endian.c curl_des.c system_win32.c \ + mime.c LIB_HFILES = arpa_telnet.h netrc.h file.h timeval.h hostip.h progress.h \ formdata.h cookie.h http.h sendf.h ftp.h url.h dict.h if2ip.h \ @@ -72,7 +73,7 @@ LIB_HFILES = arpa_telnet.h netrc.h file.h timeval.h hostip.h progress.h \ curl_sasl.h curl_multibyte.h hostcheck.h conncache.h \ curl_setup_once.h multihandle.h setup-vms.h pipeline.h dotdot.h \ x509asn1.h http2.h sigpipe.h smb.h curl_endian.h curl_des.h \ - curl_printf.h system_win32.h rand.h + curl_printf.h system_win32.h rand.h mime.h LIB_RCFILES = libcurl.rc diff --git a/lib/formdata.c b/lib/formdata.c index e48a6276c..129086dee 100644 --- a/lib/formdata.c +++ b/lib/formdata.c @@ -32,6 +32,8 @@ #include "urldata.h" /* for struct Curl_easy */ #include "formdata.h" +#include "mime.h" +#include "non-ascii.h" #include "vtls/vtls.h" #include "strcase.h" #include "sendf.h" @@ -42,13 +44,6 @@ #include "curl_memory.h" #include "memdebug.h" -#ifndef HAVE_BASENAME -static char *Curl_basename(char *path); -#define basename(x) Curl_basename((x)) -#endif - -static size_t readfromfile(struct Form *form, char *buffer, size_t size); -static CURLcode formboundary(struct Curl_easy *data, char *buffer, size_t len); /* What kind of Content-Type to use on un-specified files with unrecognized extensions. */ @@ -746,211 +741,6 @@ CURLFORMcode curl_formadd(struct curl_httppost **httppost, return result; } -#ifdef __VMS -#include <fabdef.h> -/* - * get_vms_file_size does what it takes to get the real size of the file - * - * For fixed files, find out the size of the EOF block and adjust. - * - * For all others, have to read the entire file in, discarding the contents. - * Most posted text files will be small, and binary files like zlib archives - * and CD/DVD images should be either a STREAM_LF format or a fixed format. - * - */ -curl_off_t VmsRealFileSize(const char *name, - const struct_stat *stat_buf) -{ - char buffer[8192]; - curl_off_t count; - int ret_stat; - FILE * file; - - file = fopen(name, FOPEN_READTEXT); /* VMS */ - if(file == NULL) - return 0; - - count = 0; - ret_stat = 1; - while(ret_stat > 0) { - ret_stat = fread(buffer, 1, sizeof(buffer), file); - if(ret_stat != 0) - count += ret_stat; - } - fclose(file); - - return count; -} - -/* - * - * VmsSpecialSize checks to see if the stat st_size can be trusted and - * if not to call a routine to get the correct size. - * - */ -static curl_off_t VmsSpecialSize(const char *name, - const struct_stat *stat_buf) -{ - switch(stat_buf->st_fab_rfm) { - case FAB$C_VAR: - case FAB$C_VFC: - return VmsRealFileSize(name, stat_buf); - break; - default: - return stat_buf->st_size; - } -} - -#endif - -#ifndef __VMS -#define filesize(name, stat_data) (stat_data.st_size) -#else - /* Getting the expected file size needs help on VMS */ -#define filesize(name, stat_data) VmsSpecialSize(name, &stat_data) -#endif - -/* - * AddFormData() adds a chunk of data to the FormData linked list. - * - * size is incremented by the chunk length, unless it is NULL - */ -static CURLcode AddFormData(struct FormData **formp, - enum formtype type, - const void *line, - curl_off_t length, - curl_off_t *size) -{ - struct FormData *newform; - char *alloc2 = NULL; - CURLcode result = CURLE_OK; - if(length < 0 || (size && *size < 0)) - return CURLE_BAD_FUNCTION_ARGUMENT; - - newform = malloc(sizeof(struct FormData)); - if(!newform) - return CURLE_OUT_OF_MEMORY; - newform->next = NULL; - - if(type <= FORM_CONTENT) { - /* we make it easier for plain strings: */ - if(!length) - length = strlen((char *)line); -#if (SIZEOF_SIZE_T < CURL_SIZEOF_CURL_OFF_T) - else if(length >= (curl_off_t)(size_t)-1) { - result = CURLE_BAD_FUNCTION_ARGUMENT; - goto error; - } -#endif - if(type != FORM_DATAMEM) { - newform->line = malloc((size_t)length+1); - if(!newform->line) { - result = CURLE_OUT_OF_MEMORY; - goto error; - } - alloc2 = newform->line; - memcpy(newform->line, line, (size_t)length); - - /* zero terminate for easier debugging */ - newform->line[(size_t)length]=0; - } - else { - newform->line = (char *)line; - type = FORM_DATA; /* in all other aspects this is just FORM_DATA */ - } - newform->length = (size_t)length; - } - else - /* For callbacks and files we don't have any actual data so we just keep a - pointer to whatever this points to */ - newform->line = (char *)line; - - newform->type = type; - - if(size) { - if(type != FORM_FILE) - /* for static content as well as callback data we add the size given - as input argument */ - *size += length; - else { - /* Since this is a file to be uploaded here, add the size of the actual - file */ - if(strcmp("-", newform->line)) { - struct_stat file; - if(!stat(newform->line, &file) && !S_ISDIR(file.st_mode)) - *size += filesize(newform->line, file); - else { - result = CURLE_BAD_FUNCTION_ARGUMENT; - goto error; - } - } - } - } - - if(*formp) { - (*formp)->next = newform; - *formp = newform; - } - else - *formp = newform; - - return CURLE_OK; - error: - if(newform) - free(newform); - if(alloc2) - free(alloc2); - return result; -} - -/* - * AddFormDataf() adds printf()-style formatted data to the formdata chain. - */ - -static CURLcode AddFormDataf(struct FormData **formp, - curl_off_t *size, - const char *fmt, ...) -{ - char *s; - CURLcode result; - va_list ap; - va_start(ap, fmt); - s = curl_mvaprintf(fmt, ap); - va_end(ap); - - if(!s) - return CURLE_OUT_OF_MEMORY; - - result = AddFormData(formp, FORM_DATAMEM, s, 0, size); - if(result) - free(s); - - return result; -} - -/* - * Curl_formclean() is used from http.c, this cleans a built FormData linked - * list - */ -void Curl_formclean(struct FormData **form_ptr) -{ - struct FormData *next, *form; - - form = *form_ptr; - if(!form) - return; - - do { - next=form->next; /* the following form line */ - if(form->type <= FORM_CONTENT) - free(form->line); /* free the line */ - free(form); /* free the struct */ - form = next; - } while(form); /* continue */ - - *form_ptr = NULL; -} - /* * curl_formget() * Serialize a curl_httppost struct. @@ -962,42 +752,34 @@ int curl_formget(struct curl_httppost *form, void *arg, curl_formget_callback append) { CURLcode result; - curl_off_t size; - struct FormData *data, *ptr; + struct Curl_mimepart toppart; - result = Curl_getformdata(NULL, &data, form, NULL, &size); - if(result) - return (int)result; - - for(ptr = data; ptr; ptr = ptr->next) { - if((ptr->type == FORM_FILE) || (ptr->type == FORM_CALLBACK)) { - char buffer[8192]; - size_t nread; - struct Form temp; - - Curl_FormInit(&temp, ptr); - - do { - nread = readfromfile(&temp, buffer, sizeof(buffer)); - if((nread == (size_t) -1) || - (nread > sizeof(buffer)) || - (nread != append(arg, buffer, nread))) { - if(temp.fp) - fclose(temp.fp); - Curl_formclean(&data); - return -1; - } - } while(nread); - } - else { - if(ptr->length != append(arg, ptr->line, ptr->length)) { - Curl_formclean(&data); - return -1; - } + Curl_mime_initpart(&toppart, NULL); /* default form is empty */ + result = Curl_getformdata(NULL, &toppart, form, NULL); + if(!result) + result = Curl_mime_prepare_headers(&toppart, "multipart/form-data", + NULL, MIMESTRATEGY_FORM); + + while(!result) { + char buffer[8192]; + size_t nread = Curl_mime_read(buffer, 1, sizeof buffer, &toppart); + + if(!nread) + break; + + switch(nread) { + default: + if(append(arg, buffer, nread) != nread) + result = CURLE_READ_ERROR; + break; + case CURL_READFUNC_ABORT: + case CURL_READFUNC_PAUSE: + break; } } - Curl_formclean(&data); - return 0; + + Curl_mime_cleanpart(&toppart); + return (int) result; } /* @@ -1031,118 +813,11 @@ void curl_formfree(struct curl_httppost *form) } while(form); /* continue */ } -#ifndef HAVE_BASENAME -/* - (Quote from The Open Group Base Specifications Issue 6 IEEE Std 1003.1, 2004 - Edition) - - The basename() function shall take the pathname pointed to by path and - return a pointer to the final component of the pathname, deleting any - trailing '/' characters. - - If the string pointed to by path consists entirely of the '/' character, - basename() shall return a pointer to the string "/". If the string pointed - to by path is exactly "//", it is implementation-defined whether '/' or "//" - is returned. - - If path is a null pointer or points to an empty string, basename() shall - return a pointer to the string ".". - - The basename() function may modify the string pointed to by path, and may - return a pointer to static storage that may then be overwritten by a - subsequent call to basename(). - - The basename() function need not be reentrant. A function that is not - required to be reentrant is not required to be thread-safe. - -*/ -static char *Curl_basename(char *path) -{ - /* Ignore all the details above for now and make a quick and simple - implementaion here */ - char *s1; - char *s2; - - s1=strrchr(path, '/'); - s2=strrchr(path, '\\'); - - if(s1 && s2) { - path = (s1 > s2? s1 : s2)+1; - } - else if(s1) - path = s1 + 1; - else if(s2) - path = s2 + 1; - - return path; -} -#endif - -static char *strippath(const char *fullfile) -{ - char *filename; - char *base; - filename = strdup(fullfile); /* duplicate since basename() may ruin the - buffer it works on */ - if(!filename) - return NULL; - base = strdup(basename(filename)); - - free(filename); /* free temporary buffer */ - - return base; /* returns an allocated string or NULL ! */ -} - -static CURLcode formdata_add_filename(const struct curl_httppost *file, - struct FormData **form, - curl_off_t *size) -{ - CURLcode result = CURLE_OK; - char *filename = file->showfilename; - char *filebasename = NULL; - char *filename_escaped = NULL; - - if(!filename) { - filebasename = strippath(file->contents); - if(!filebasename) - return CURLE_OUT_OF_MEMORY; - filename = filebasename; - } - - if(strchr(filename, '\\') || strchr(filename, '"')) { - char *p0, *p1; - - /* filename need be escaped */ - filename_escaped = malloc(strlen(filename)*2+1); - if(!filename_escaped) { - free(filebasename); - return CURLE_OUT_OF_MEMORY; - } - p0 = filename_escaped; - p1 = filename; - while(*p1) { - if(*p1 == '\\' || *p1 == '"') - *p0++ = '\\'; - *p0++ = *p1++; - } - *p0 = '\0'; - filename = filename_escaped; - } - result = AddFormDataf(form, size, - "; filename=\"%s\"", - filename); - free(filename_escaped); - free(filebasename); - return result; -} /* - * Curl_getformdata() converts a linked list of "meta data" into a complete - * (possibly huge) multipart formdata. The input list is in 'post', while the - * output resulting linked lists gets stored in '*finalform'. *sizep will get - * the total size of the whole POST. - * A multipart/form_data content-type is built, unless a custom content-type - * is passed in 'custom_content_type'. + * Curl_getformdata() converts a linked list of "meta data" into a mime + * structure. The input list is in 'post', while the output is stored in + * mime part at '*finalform'. * * This function will not do a failf() for the potential memory failures but * should for all other errors it spots. Just note that this function MAY get @@ -1150,422 +825,114 @@ static CURLcode formdata_add_filename(const struct curl_httppost *file, */ CURLcode Curl_getformdata(struct Curl_easy *data, - struct FormData **finalform, + struct Curl_mimepart *finalform, struct curl_httppost *post, - const char *custom_content_type, - curl_off_t *sizep) + curl_read_callback fread_func) { - struct FormData *form = NULL; - struct FormData *firstform; - struct curl_httppost *file; CURLcode result = CURLE_OK; - curl_off_t size = 0; /* support potentially ENORMOUS formposts */ - char fileboundary[42]; - struct curl_slist *curList; - char boundary[42]; + struct Curl_mime *form = NULL; + struct Curl_mime *multipart; + struct Curl_mimepart *part; + struct curl_httppost *file; - *finalform = NULL; /* default form is empty */ + Curl_mime_cleanpart(finalform); /* default form is empty */ if(!post) return result; /* no input => no output! */ - result = formboundary(data, boundary, sizeof(boundary)); - if(result) - return result; - - /* Make the first line of the output */ - result = AddFormDataf(&form, NULL, - "%s; boundary=%s\r\n", - custom_content_type?custom_content_type: - "Content-Type: multipart/form-data", - boundary); - - if(result) { - return result; - } - /* we DO NOT include that line in the total size of the POST, since it'll be - part of the header! */ - - firstform = form; - - do { - - if(size) { - result = AddFormDataf(&form, &size, "\r\n"); - if(result) - break; - } - - /* boundary */ - result = AddFormDataf(&form, &size, "--%s\r\n", boundary); - if(result) - break; - - /* Maybe later this should be disabled when a custom_content_type is - passed, since Content-Disposition is not meaningful for all multipart - types. - */ - result = AddFormDataf(&form, &size, - "Content-Disposition: form-data; name=\""); - if(result) - break; - - result = AddFormData(&form, FORM_DATA, post->name, post->namelength, - &size); - if(result) - break; + form = curl_mime_init(data); + if(!form) + result = CURLE_OUT_OF_MEMORY; - result = AddFormDataf(&form, &size, "\""); - if(result) - break; + if(!result) + result = curl_mime_subparts(finalform, form); + /* Process each top part. */ + for(; !result && post; post = post->next) { + /* If we have more than a file here, create a mime subpart and fill it. */ + multipart = form; if(post->more) { - /* If used, this is a link to more file names, we must then do - the magic to include several files with the same field name */ - - result = formboundary(data, fileboundary, sizeof(fileboundary)); - if(result) { - break; + part = curl_mime_addpart(form); + if(!part) + result = CURLE_OUT_OF_MEMORY; + if(!result) + result = curl_mime_name(part, post->name, + post->namelength? post->namelength: -1); + if(!result) { + multipart = curl_mime_init(data); + if(!multipart) + result = CURLE_OUT_OF_MEMORY; } - - result = AddFormDataf(&form, &size, - "\r\nContent-Type: multipart/mixed;" - " boundary=%s\r\n", - fileboundary); - if(result) - break; + if(!result) + result = curl_mime_subparts(part, multipart); } - file = post; - - do { - - /* If 'showfilename' is set, that is a faked name passed on to us - to use to in the formpost. If that is not set, the actually used - local file name should be added. */ + /* Generate all the part contents. */ + for(file = post; !result && file; file = file->more) { + /* Create the part. */ + part = curl_mime_addpart(multipart); + if(!part) + result = CURLE_OUT_OF_MEMORY; - if(post->more) { - /* if multiple-file */ - result = AddFormDataf(&form, &size, - "\r\n--%s\r\nContent-Disposition: " - "attachment", - fileboundary); - if(result) - break; - result = formdata_add_filename(file, &form, &size); - if(result) - break; - } - else if(post->flags & (HTTPPOST_FILENAME|HTTPPOST_BUFFER| - HTTPPOST_CALLBACK)) { - /* it should be noted that for the HTTPPOST_FILENAME and - HTTPPOST_CALLBACK cases the ->showfilename struct member is always - assigned at this point */ - if(post->showfilename || (post->flags & HTTPPOST_FILENAME)) { - result = formdata_add_filename(post, &form, &size); - } + /* Set the headers. */ + if(!result) + result = curl_mime_headers(part, file->contentheader, 0); - if(result) - break; - } + /* Set the content type. */ + if(!result &&file->contenttype) + result = curl_mime_type(part, file->contenttype); - if(file->contenttype) { - /* we have a specified type */ - result = AddFormDataf(&form, &size, - "\r\nContent-Type: %s", - file->contenttype); - if(result) - break; - } + /* Set field name. */ + if(!result && !post->more) + result = curl_mime_name(part, post->name, + post->namelength? post->namelength: -1); - curList = file->contentheader; - while(curList) { - /* Process the additional headers specified for this form */ - result = AddFormDataf(&form, &size, "\r\n%s", curList->data); - if(result) - break; - curList = curList->next; - } - if(result) - break; + /* Process contents. */ + if(!result) { + curl_off_t clen = post->contentslength; - result = AddFormDataf(&form, &size, "\r\n\r\n"); - if(result) - break; + if(post->flags & CURL_HTTPPOST_LARGE) + clen = post->contentlen; + if(!clen) + clen = -1; - if((post->flags & HTTPPOST_FILENAME) || - (post->flags & HTTPPOST_READFILE)) { - /* we should include the contents from the specified file */ - FILE *fileread; - - fileread = !strcmp("-", file->contents)? - stdin:fopen(file->contents, "rb"); /* binary read for win32 */ - - /* - * VMS: This only allows for stream files on VMS. Stream files are - * OK, as are FIXED & VAR files WITHOUT implied CC For implied CC, - * every record needs to have a \n appended & 1 added to SIZE - */ - - if(fileread) { - if(fileread != stdin) { - /* close the file */ - fclose(fileread); - /* add the file name only - for later reading from this */ - result = AddFormData(&form, FORM_FILE, file->contents, 0, &size); - } - else { - /* When uploading from stdin, we can't know the size of the file, - * thus must read the full file as before. We *could* use chunked - * transfer-encoding, but that only works for HTTP 1.1 and we - * can't be sure we work with such a server. - */ - size_t nread; - char buffer[512]; - while((nread = fread(buffer, 1, sizeof(buffer), fileread)) != 0) { - result = AddFormData(&form, FORM_CONTENT, buffer, nread, &size); - if(result || feof(fileread) || ferror(fileread)) - break; - } - } + if(post->flags & (HTTPPOST_FILENAME | HTTPPOST_READFILE)) { + result = curl_mime_filedata(part, file->contents); + if(!result && (post->flags & HTTPPOST_READFILE)) + result = curl_mime_filename(part, NULL); } + else if(post->flags & HTTPPOST_BUFFER) + result = curl_mime_data(part, post->buffer, + post->bufferlength? post->bufferlength: -1); + else if(post->flags & HTTPPOST_CALLBACK) + /* the contents should be read with the callback and the size is set + with the contentslength */ + result = curl_mime_data_cb(part, clen, + fread_func, NULL, NULL, post->userp); else { - if(data) - failf(data, "couldn't open file \"%s\"", file->contents); - *finalform = NULL; - result = CURLE_READ_ERROR; + result = curl_mime_data(part, post->contents, (ssize_t) clen); +#ifdef CURL_DOES_CONVERSIONS + /* Convert textual contents now. */ + if(!result && data && part->datasize) + result = Curl_convert_to_network(data, part->data, part->datasize); +#endif } } - else if(post->flags & HTTPPOST_BUFFER) - /* include contents of buffer */ - result = AddFormData(&form, FORM_CONTENT, post->buffer, - post->bufferlength, &size); - else if(post->flags & HTTPPOST_CALLBACK) - /* the contents should be read with the callback and the size is set - with the contentslength */ - result = AddFormData(&form, FORM_CALLBACK, post->userp, - post->flags&CURL_HTTPPOST_LARGE? - post->contentlen:post->contentslength, &size); - else - /* include the contents we got */ - result = AddFormData(&form, FORM_CONTENT, post->contents, - post->flags&CURL_HTTPPOST_LARGE? - post->contentlen:post->contentslength, &size); - file = file->more; - } while(file && !result); /* for each specified file for this field */ - - if(result) - break; - if(post->more) { - /* this was a multiple-file inclusion, make a termination file - boundary: */ - result = AddFormDataf(&form, &size, - "\r\n--%s--", - fileboundary); - if(result) - break; + /* Set fake file name. */ + if(!result && post->showfilename) + if(post->more || (post->flags & (HTTPPOST_FILENAME | HTTPPOST_BUFFER | + HTTPPOST_CALLBACK))) + result = curl_mime_filename(part, post->showfilename); } - post = post->next; - } while(post); /* for each field */ - - /* end-boundary for everything */ - if(!result) - result = AddFormDataf(&form, &size, "\r\n--%s--\r\n", boundary); - - if(result) { - Curl_formclean(&firstform); - return result; } - *sizep = size; - *finalform = firstform; + if(result) + Curl_mime_cleanpart(finalform); return result; } -/* - * Curl_FormInit() inits the struct 'form' points to with the 'formdata' - * and resets the 'sent' counter. - */ -int Curl_FormInit(struct Form *form, struct FormData *formdata) -{ - if(!formdata) - return 1; /* error */ - - form->data = formdata; - form->sent = 0; - form->fp = NULL; - form->fread_func = ZERO_NULL; - - return 0; -} - -#ifndef __VMS -# define fopen_read fopen -#else - /* - * vmsfopenread - * - * For upload to work as expected on VMS, different optional - * parameters must be added to the fopen command based on - * record format of the file. - * - */ -# define fopen_read vmsfopenread -static FILE * vmsfopenread(const char *file, const char *mode) -{ - struct_stat statbuf; - int result; - - result = stat(file, &statbuf); - - switch(statbuf.st_fab_rfm) { - case FAB$C_VAR: - case FAB$C_VFC: - case FAB$C_STMCR: - return fopen(file, FOPEN_READTEXT); /* VMS */ - break; - default: - return fopen(file, FOPEN_READTEXT, "rfm=stmlf", "ctx=stm"); - } -} -#endif - -/* - * readfromfile() - * - * The read callback that this function may use can return a value larger than - * 'size' (which then this function returns) that indicates a problem and it - * must be properly dealt with - */ -static size_t readfromfile(struct Form *form, char *buffer, - size_t size) -{ - size_t nread; - bool callback = (form->data->type == FORM_CALLBACK)?TRUE:FALSE; - - if(callback) { - if(form->fread_func == ZERO_NULL) - return 0; - nread = form->fread_func(buffer, 1, size, form->data->line); - } - else { - if(!form->fp) { - /* this file hasn't yet been opened */ - form->fp = fopen_read(form->data->line, "rb"); /* b is for binary */ - if(!form->fp) - return (size_t)-1; /* failure */ - } - nread = fread(buffer, 1, size, form->fp); - } - if(!nread) { - /* this is the last chunk from the file, move on */ - if(form->fp) { - fclose(form->fp); - form->fp = NULL; - } - form->data = form->data->next; - } - - return nread; -} - -/* - * Curl_FormReader() is the fread() emulation function that will be used to - * deliver the formdata to the transfer loop and then sent away to the peer. - */ -size_t Curl_FormReader(char *buffer, - size_t size, - size_t nitems, - FILE *mydata) -{ - struct Form *form; - size_t wantedsize; - size_t gotsize = 0; - - form=(struct Form *)mydata; - - wantedsize = size * nitems; - - if(!form->data) - return 0; /* nothing, error, empty */ - - if((form->data->type == FORM_FILE) || - (form->data->type == FORM_CALLBACK)) { - gotsize = readfromfile(form, buffer, wantedsize); - - if(gotsize) - /* If positive or -1, return. If zero, continue! */ - return gotsize; - } - do { - - if((form->data->length - form->sent) > wantedsize - gotsize) { - - memcpy(buffer + gotsize, form->data->line + form->sent, - wantedsize - gotsize); - - form->sent += wantedsize-gotsize; - - return wantedsize; - } - - memcpy(buffer+gotsize, - form->data->line + form->sent, - (form->data->length - form->sent) ); - gotsize += form->data->length - form->sent; - - form->sent = 0; - - form->data = form->data->next; /* advance */ - - } while(form->data && (form->data->type < FORM_CALLBACK)); - /* If we got an empty line and we have more data, we proceed to the next - line immediately to avoid returning zero before we've reached the end. */ - - return gotsize; -} - -/* - * Curl_formpostheader() returns the first line of the formpost, the - * request-header part (which is not part of the request-body like the rest of - * the post). - */ -char *Curl_formpostheader(void *formp, size_t *len) -{ - char *header; - struct Form *form=(struct Form *)formp; - - if(!form->data) - return NULL; /* nothing, ERROR! */ - - header = form->data->line; - *len = form->data->length; - - form->data = form->data->next; /* advance */ - - return header; -} - -/* - * formboundary() creates a suitable boundary string and returns an allocated - * one. - */ -static CURLcode formboundary(struct Curl_easy *data, - char *buffer, size_t buflen) -{ - /* 24 dashes and 16 hexadecimal digits makes 64 bit (18446744073709551615) - combinations */ - if(buflen < 41) - return CURLE_BAD_FUNCTION_ARGUMENT; - - memset(buffer, '-', 24); - Curl_rand_hex(data, (unsigned char *)&buffer[24], 17); - - return CURLE_OK; -} - #else /* CURL_DISABLE_HTTP */ CURLFORMcode curl_formadd(struct curl_httppost **httppost, struct curl_httppost **last_post, diff --git a/lib/formdata.h b/lib/formdata.h index 69629f628..46642fb63 100644 --- a/lib/formdata.h +++ b/lib/formdata.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, <daniel@haxx.se>, et al. + * Copyright (C) 1998 - 2017, Daniel Stenberg, <daniel@haxx.se>, et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms @@ -22,32 +22,6 @@ * ***************************************************************************/ -enum formtype { - FORM_DATAMEM, /* already allocated FORM_DATA memory */ - FORM_DATA, /* form metadata (convert to network encoding if necessary) */ - FORM_CONTENT, /* form content (never convert) */ - FORM_CALLBACK, /* 'line' points to the custom pointer we pass to the callback - */ - FORM_FILE /* 'line' points to a file name we should read from - to create the form data (never convert) */ -}; - -/* plain and simple linked list with lines to send */ -struct FormData { - struct FormData *next; - enum formtype type; - char *line; - size_t length; -}; - -struct Form { - struct FormData *data; /* current form line to send */ - size_t sent; /* number of bytes of the current line that has - already been sent in a previous invoke */ - FILE *fp; /* file to read from */ - curl_read_callback fread_func; /* fread callback pointer */ -}; - /* used by FormAdd for temporary storage */ typedef struct FormInfo { char *name; @@ -69,31 +43,9 @@ typedef struct FormInfo { struct FormInfo *more; } FormInfo; -int Curl_FormInit(struct Form *form, struct FormData *formdata); - CURLcode Curl_getformdata(struct Curl_easy *data, - struct FormData **, + struct Curl_mimepart *, struct curl_httppost *post, - const char *custom_contenttype, - curl_off_t *size); - -/* fread() emulation */ -size_t Curl_FormReader(char *buffer, - size_t size, - size_t nitems, - FILE *mydata); - -/* - * Curl_formpostheader() returns the first line of the formpost, the - * request-header part (which is not part of the request-body like the rest of - * the post). - */ -char *Curl_formpostheader(void *formp, size_t *len); - -char *Curl_FormBoundary(void); - -void Curl_formclean(struct FormData **); - -CURLcode Curl_formconvert(struct Curl_easy *, struct FormData *); + curl_read_callback fread_func); #endif /* HEADER_CURL_FORMDATA_H */ diff --git a/lib/http.c b/lib/http.c index cf50a7cac..f4a1faac3 100644 --- a/lib/http.c +++ b/lib/http.c @@ -50,6 +50,7 @@ #include "transfer.h" #include "sendf.h" #include "formdata.h" +#include "mime.h" #include "progress.h" #include "curl_base64.h" #include "cookie.h" @@ -162,6 +163,7 @@ CURLcode Curl_http_setup_conn(struct connectdata *conn) if(!http) return CURLE_OUT_OF_MEMORY; + Curl_mime_initpart(&http->form, conn->data); conn->data->req.protop = http; Curl_http2_setup_conn(conn); @@ -427,6 +429,7 @@ static CURLcode http_perhapsrewind(struct connectdata *conn) expectsend = data->state.infilesize; break; case HTTPREQ_POST_FORM: + case HTTPREQ_POST_MIME: expectsend = http->postsize; break; default: @@ -1470,18 +1473,17 @@ CURLcode Curl_http_done(struct connectdata *conn, Curl_http2_done(conn, premature); - if(HTTPREQ_POST_FORM == data->set.httpreq) { - data->req.bytecount = http->readbytecount + http->writebytecount; + Curl_mime_cleanpart(&http->form); - Curl_formclean(&http->sendit); /* Now free that whole lot */ - if(http->form.fp) { - /* a file being uploaded was left opened, close it! */ - fclose(http->form.fp); - http->form.fp = NULL; - } - } - else if(HTTPREQ_PUT == data->set.httpreq) + switch(data->set.httpreq) { + case HTTPREQ_PUT: + case HTTPREQ_POST_FORM: + case HTTPREQ_POST_MIME: data->req.bytecount = http->readbytecount + http->writebytecount; + break; + default: + break; + } if(status) return status; @@ -1637,6 +1639,10 @@ CURLcode Curl_add_custom_headers(struct connectdata *conn, /* this header (extended by formdata.c) is sent later */ checkprefix("Content-Type:", headers->data)) ; + else if(data->set.httpreq == HTTPREQ_POST_MIME && + /* this header is sent later */ + checkprefix("Content-Type:", headers->data)) + ; else if(conn->bits.authneg && /* while doing auth neg, don't allow the custom length since we will force length zero then */ @@ -1775,7 +1781,7 @@ CURLcode Curl_http(struct connectdata *conn, bool *done) const char *httpstring; Curl_send_buffer *req_buffer; curl_off_t postsize = 0; /* curl_off_t to handle large file sizes */ - int seekerr = CURL_SEEKFUNC_OK; + int seekerr = CURL_SEEKFUNC_CANTSEEK; /* Always consider the DO phase done after this function call, even if there may be parts of the request that is not yet sent, since we can deal with @@ -1848,6 +1854,7 @@ CURLcode Curl_http(struct connectdata *conn, bool *done) switch(httpreq) { case HTTPREQ_POST: case HTTPREQ_POST_FORM: + case HTTPREQ_POST_MIME: request = "POST"; break; case HTTPREQ_PUT: @@ -1942,6 +1949,48 @@ CURLcode Curl_http(struct connectdata *conn, bool *done) } #endif + switch(httpreq) { + case HTTPREQ_POST_MIME: + http->sendit = &data->set.mimepost; + break; + case HTTPREQ_POST_FORM: + /* Convert the form structure into a mime structure. */ + Curl_mime_cleanpart(&http->form); + result = Curl_getformdata(data, &http->form, data->set.httppost, + data->state.fread_func); + if(result) + return result; + http->sendit = &http->form; + break; + default: + http->sendit = NULL; + } + + if(http->sendit) { + const char *cthdr = Curl_checkheaders(conn, "Content-Type:"); + + /* Read and seek body only. */ + http->sendit->flags |= MIME_BODY_ONLY; + + /* Prepare the mime structure headers & set content type. */ + + if(cthdr) + for(cthdr += 13; *cthdr == ' '; cthdr++) + ; + else if(http->sendit->kind == MIMEKIND_MULTIPART) + cthdr = "multipart/form-data"; + + curl_mime_headers(http->sendit, data->set.headers, 0); + result = Curl_mime_prepare_headers(http->sendit, cthdr, + NULL, MIMESTRATEGY_FORM); + curl_mime_headers(http->sendit, NULL, 0); + if(!result) + result = Curl_mime_rewind(http->sendit); + if(result) + return result; + http->postsize = Curl_mime_size(http->sendit); + } + ptr = Curl_checkheaders(conn, "Transfer-Encoding:"); if(ptr) { /* Some kind of TE is requested, check if 'chunked' is chosen */ @@ -1949,9 +1998,10 @@ CURLcode Curl_http(struct connectdata *conn, bool *done) Curl_compareheader(ptr, "Transfer-Encoding:", "chunked"); } else { - if((conn->handler->protocol&PROTO_FAMILY_HTTP) && - data->set.upload && - (data->state.infilesize == -1)) { + if((conn->handler->protocol & PROTO_FAMILY_HTTP) && + (((httpreq == HTTPREQ_POST_MIME || httpreq == HTTPREQ_POST_FORM) && + http->postsize < 0) || + (data->set.upload && data->state.infilesize == -1))) { if(conn->bits.authneg) /* don't enable chunked during auth neg */ ; @@ -2123,21 +2173,9 @@ CURLcode Curl_http(struct connectdata *conn, bool *done) } #endif /* CURL_DISABLE_PROXY */ - if(HTTPREQ_POST_FORM == httpreq) { - /* we must build the whole post sequence first, so that we have a size of - the whole transfer before we start to send it */ - result = Curl_getformdata(data, &http->sendit, data->set.httppost, - Curl_checkheaders(conn, "Content-Type:"), - &http->postsize); - if(result) - return result; - } - http->p_accept = Curl_checkheaders(conn, "Accept:")?NULL:"Accept: */*\r\n"; - if(( (HTTPREQ_POST == httpreq) || - (HTTPREQ_POST_FORM == httpreq) || - (HTTPREQ_PUT == httpreq) ) && + if((HTTPREQ_POST == httpreq || HTTPREQ_PUT == httpreq) && data->state.resume_from) { /********************************************************************** * Resuming upload in HTTP means that we PUT or POST and that we have @@ -2145,6 +2183,7 @@ CURLcode Curl_http(struct connectdata *conn, bool *done) * a Range: header that will be passed along. We need to "fast forward" * the file the given number of bytes and decrease the assume upload * file size before we continue this venture in the dark lands of HTTP. + * Resuming mime/form posting at an offset > 0 has no sense and is ignored. *********************************************************************/ if(data->state.resume_from < 0) { @@ -2219,7 +2258,7 @@ CURLcode Curl_http(struct connectdata *conn, bool *done) conn->allocptr.rangeline = aprintf("Range: bytes=%s\r\n", data->state.range); } - else if((httpreq != HTTPREQ_GET) && + else if((httpreq == HTTPREQ_POST || httpreq == HTTPREQ_PUT) && !Curl_checkheaders(conn, "Content-Range:")) { /* if a line like this was already allocated, free the previous one */ @@ -2415,117 +2454,79 @@ CURLcode Curl_http(struct connectdata *conn, bool *done) switch(httpreq) { - case HTTPREQ_POST_FORM: - if(!http->sendit || conn->bits.authneg) { - /* nothing to post! */ - result = Curl_add_bufferf(req_buffer, "Content-Length: 0\r\n\r\n"); - if(result) - return result; - - result = Curl_add_buffer_send(req_buffer, conn, - &data->info.request_size, 0, FIRSTSOCKET); - if(result) - failf(data, "Failed sending POST request"); - else - /* setup variables for the upcoming transfer */ - Curl_setup_transfer(conn, FIRSTSOCKET, -1, TRUE, &http->readbytecount, - -1, NULL); - break; - } - - if(Curl_FormInit(&http->form, http->sendit)) { - failf(data, "Internal HTTP POST error!"); - return CURLE_HTTP_POST_ERROR; - } - - /* Get the currently set callback function pointer and store that in the - form struct since we might want the actual user-provided callback later - on. The data->set.fread_func pointer itself will be changed for the - multipart case to the function that returns a multipart formatted - stream. */ - http->form.fread_func = data->state.fread_func; - - /* Set the read function to read from the generated form data */ - data->state.fread_func = (curl_read_callback)Curl_FormReader; - data->state.in = &http->form; + case HTTPREQ_PUT: /* Let's PUT the data to the server! */ - http->sending = HTTPSEND_BODY; + if(conn->bits.authneg) + postsize = 0; + else + postsize = data->state.infilesize; - if(!data->req.upload_chunky && - !Curl_checkheaders(conn, "Content-Length:")) { + if((postsize != -1) && !data->req.upload_chunky && + (conn->bits.authneg || !Curl_checkheaders(conn, "Content-Length:"))) { /* only add Content-Length if not uploading chunked */ result = Curl_add_bufferf(req_buffer, "Content-Length: %" CURL_FORMAT_CURL_OFF_T - "\r\n", http->postsize); + "\r\n", postsize); if(result) return result; } - result = expect100(data, conn, req_buffer); - if(result) - return result; - - { - - /* Get Content-Type: line from Curl_formpostheader. - */ - char *contentType; - size_t linelength=0; - contentType = Curl_formpostheader((void *)&http->form, - &linelength); - if(!contentType) { - failf(data, "Could not get Content-Type header line!"); - return CURLE_HTTP_POST_ERROR; - } - - result = Curl_add_buffer(req_buffer, contentType, linelength); + if(postsize != 0) { + result = expect100(data, conn, req_buffer); if(result) return result; } - /* make the request end in a true CRLF */ - result = Curl_add_buffer(req_buffer, "\r\n", 2); + result = Curl_add_buffer(req_buffer, "\r\n", 2); /* end of headers */ if(result) return result; - /* set upload size to the progress meter */ - Curl_pgrsSetUploadSize(data, http->postsize); + /* set the upload size to the progress meter */ + Curl_pgrsSetUploadSize(data, postsize); - /* fire away the whole request to the server */ + /* this sends the buffer and frees all the buffer resources */ result = Curl_add_buffer_send(req_buffer, conn, &data->info.request_size, 0, FIRSTSOCKET); if(result) - failf(data, "Failed sending POST request"); + failf(data, "Failed sending PUT request"); else - /* setup variables for the upcoming transfer */ + /* prepare for transfer */ Curl_setup_transfer(conn, FIRSTSOCKET, -1, TRUE, - &http->readbytecount, FIRSTSOCKET, - &http->writebytecount); - - if(result) { - Curl_formclean(&http->sendit); /* free that whole lot */ - return result; - } - - /* convert the form data */ - result = Curl_convert_form(data, http->sendit); - if(result) { - Curl_formclean(&http->sendit); /* free that whole lot */ + &http->readbytecount, postsize?FIRSTSOCKET:-1, + postsize?&http->writebytecount:NULL); + if(result) return result; - } - break; - case HTTPREQ_PUT: /* Let's PUT the data to the server! */ + case HTTPREQ_POST_FORM: + case HTTPREQ_POST_MIME: + /* This is form posting using mime data. */ + if(conn->bits.authneg) { + /* nothing to post! */ + result = Curl_add_bufferf(req_buffer, "Content-Length: 0\r\n\r\n"); + if(result) + return result; - if(conn->bits.authneg) - postsize = 0; - else - postsize = data->state.infilesize; + result = Curl_add_buffer_send(req_buffer, conn, + &data->info.request_size, 0, FIRSTSOCKET); + if(result) + failf(data, "Failed sending POST request"); + else + /* setup variables for the upcoming transfer */ + Curl_setup_transfer(conn, FIRSTSOCKET, -1, TRUE, &http->readbytecount, + -1, NULL); + break; + } - if((postsize != -1) && !data->req.upload_chunky && + postsize = http->postsize; + + /* We only set Content-Length and allow a custom Content-Length if + we don't upload data chunked, as RFC2616 forbids us to set both + kinds of headers (Transfer-Encoding: chunked and Content-Length) */ + if(postsize != -1 && !data->req.upload_chunky && (conn->bits.authneg || !Curl_checkheaders(conn, "Content-Length:"))) { - /* only add Content-Length if not uploading chunked */ + /* we allow replacing this header if not during auth negotiation, + although it isn't very wise to actually set your own */ result = Curl_add_bufferf(req_buffer, "Content-Length: %" CURL_FORMAT_CURL_OFF_T "\r\n", postsize); @@ -2533,24 +2534,52 @@ CURLcode Curl_http(struct connectdata *conn, bool *done) return result; } - if(postsize != 0) { + /* Output mime-generated headers. */ + { + struct curl_slist *hdr; + + for(hdr = http->sendit->curlheaders; hdr; hdr = hdr->next) { + result = Curl_add_bufferf(req_buffer, "%s\r\n", hdr->data); + if(result) + return result; + } + } + + /* For really small posts we don't use Expect: headers at all, and for + the somewhat bigger ones we allow the app to disable it. Just make + sure that the expect100header is always set to the preferred value + here. */ + ptr = Curl_checkheaders(conn, "Expect:"); + if(ptr) { + data->state.expect100header = + Curl_compareheader(ptr, "Expect:", "100-continue"); + } + else if(postsize > EXPECT_100_THRESHOLD || postsize < 0) { result = expect100(data, conn, req_buffer); if(result) return result; } + else + data->state.expect100header = FALSE; - result = Curl_add_buffer(req_buffer, "\r\n", 2); /* end of headers */ + /* make the request end in a true CRLF */ + result = Curl_add_buffer(req_buffer, "\r\n", 2); if(result) return result; /* set the upload size to the progress meter */ Curl_pgrsSetUploadSize(data, postsize); + /* Read from mime structure. */ + data->state.fread_func = (curl_read_callback) Curl_mime_read; + data->state.in = (void *) http->sendit; + http->sending = HTTPSEND_BODY; + /* this sends the buffer and frees all the buffer resources */ result = Curl_add_buffer_send(req_buffer, conn, &data->info.request_size, 0, FIRSTSOCKET); if(result) - failf(data, "Failed sending PUT request"); + failf(data, "Failed sending POST request"); else /* prepare for transfer */ Curl_setup_transfer(conn, FIRSTSOCKET, -1, TRUE, @@ -2558,6 +2587,7 @@ CURLcode Curl_http(struct connectdata *conn, bool *done) postsize?&http->writebytecount:NULL); if(result) return result; + break; case HTTPREQ_POST: @@ -3171,6 +3201,7 @@ CURLcode Curl_http_readwrite_headers(struct Curl_easy *data, case HTTPREQ_PUT: case HTTPREQ_POST: case HTTPREQ_POST_FORM: + case HTTPREQ_POST_MIME: /* We got an error response. If this happened before the whole * request body has been sent we stop sending and mark the * connection for closure after we've read the entire response. diff --git a/lib/http.h b/lib/http.h index a845f564d..04c58522c 100644 --- a/lib/http.h +++ b/lib/http.h @@ -130,7 +130,7 @@ CURLcode Curl_http_perhapsrewind(struct connectdata *conn); * HTTP unique setup ***************************************************************************/ struct HTTP { - struct FormData *sendit; + struct Curl_mimepart *sendit; curl_off_t postsize; /* off_t to handle large file sizes */ const char *postdata; @@ -140,7 +140,7 @@ struct HTTP { curl_off_t writebytecount; /* For FORM posting */ - struct Form form; + struct Curl_mimepart form; struct back { curl_read_callback fread_func; /* backup storage for fread pointer */ diff --git a/lib/http2.c b/lib/http2.c index 8beaabf17..d466ccaa0 100644 --- a/lib/http2.c +++ b/lib/http2.c @@ -1955,6 +1955,7 @@ static ssize_t http2_send(struct connectdata *conn, int sockindex, switch(conn->data->set.httpreq) { case HTTPREQ_POST: case HTTPREQ_POST_FORM: + case HTTPREQ_POST_MIME: case HTTPREQ_PUT: if(conn->data->state.infilesize != -1) stream->upload_left = conn->data->state.infilesize; diff --git a/lib/imap.c b/lib/imap.c index 21fe0870f..d37113aae 100644 --- a/lib/imap.c +++ b/lib/imap.c @@ -68,6 +68,7 @@ #include "http.h" /* for HTTP proxy tunnel stuff */ #include "socks.h" #include "imap.h" +#include "mime.h" #include "strtoofft.h" #include "strcase.h" #include "vtls/vtls.h" @@ -708,18 +709,48 @@ static CURLcode imap_perform_fetch(struct connectdata *conn) static CURLcode imap_perform_append(struct connectdata *conn) { CURLcode result = CURLE_OK; - struct IMAP *imap = conn->data->req.protop; + struct Curl_easy *data = conn->data; + struct IMAP *imap = data->req.protop; char *mailbox; /* Check we have a mailbox */ if(!imap->mailbox) { - failf(conn->data, "Cannot APPEND without a mailbox."); + failf(data, "Cannot APPEND without a mailbox."); return CURLE_URL_MALFORMAT; } + /* Prepare the mime data if some. */ + if(data->set.mimepost.kind != MIMEKIND_NONE) { + /* Use the whole structure as data. */ + data->set.mimepost.flags &= ~MIME_BODY_ONLY; + + /* Add external headers and mime version. */ + curl_mime_headers(&data->set.mimepost, data->set.headers, 0); + result = Curl_mime_prepare_headers(&data->set.mimepost, NULL, + NULL, MIMESTRATEGY_MAIL); + + if(!result) + if(!Curl_checkheaders(conn, "Mime-Version")) + result = Curl_mime_add_header(&data->set.mimepost.curlheaders, + "Mime-Version: 1.0"); + + /* Make sure we will read the entire mime structure. */ + if(!result) + result = Curl_mime_rewind(&data->set.mimepost); + + if(result) + return result; + + data->state.infilesize = Curl_mime_size(&data->set.mimepost); + + /* Read from mime structure. */ + data->state.fread_func = (curl_read_callback) Curl_mime_read; + data->state.in = (void *) &data->set.mimepost; + } + /* Check we know the size of the upload */ - if(conn->data->state.infilesize < 0) { - failf(conn->data, "Cannot APPEND with unknown input file size\n"); + if(data->state.infilesize < 0) { + failf(data, "Cannot APPEND with unknown input file size\n"); return CURLE_UPLOAD_FAILED; } @@ -730,7 +761,7 @@ static CURLcode imap_perform_append(struct connectdata *conn) /* Send the APPEND command */ result = imap_sendf(conn, "APPEND %s (\\Seen) {%" CURL_FORMAT_CURL_OFF_T "}", - mailbox, conn->data->state.infilesize); + mailbox, data->state.infilesize); free(mailbox); @@ -1423,9 +1454,10 @@ static CURLcode imap_done(struct connectdata *conn, CURLcode status, result = status; /* use the already set error code */ } else if(!data->set.connect_only && !imap->custom && - (imap->uid || data->set.upload)) { + (imap->uid || data->set.upload || + data->set.mimepost.kind != MIMEKIND_NONE)) { /* Handle responses after FETCH or APPEND transfer has finished */ - if(!data->set.upload) + if(!data->set.upload && data->set.mimepost.kind == MIMEKIND_NONE) state(conn, IMAP_FETCH_FINAL); else { /* End the APPEND command first by sending an empty line */ @@ -1495,7 +1527,7 @@ static CURLcode imap_perform(struct connectdata *conn, bool *connected, selected = TRUE; /* Start the first command in the DO phase */ - if(conn->data->set.upload) + if(conn->data->set.upload || data->set.mimepost.kind != MIMEKIND_NONE) /* APPEND can be executed directly */ result = imap_perform_append(conn); else if(imap->custom && (selected || !imap->mailbox)) diff --git a/lib/mime.c b/lib/mime.c new file mode 100644 index 000000000..759ade085 --- /dev/null +++ b/lib/mime.c @@ -0,0 +1,1497 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2017, Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.haxx.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ***************************************************************************/ + +#include "curl_setup.h" + +#include <curl/curl.h> + +#include "mime.h" +#include "non-ascii.h" + +#if !defined(CURL_DISABLE_HTTP) || !defined(CURL_DISABLE_SMTP) || \ + !defined(CURL_DISABLE_IMAP) + +#if defined(HAVE_LIBGEN_H) && defined(HAVE_BASENAME) +#include <libgen.h> +#endif + +#include "rand.h" +#include "slist.h" +#include "strcase.h" +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +#ifdef WIN32 +# ifndef R_OK +# define R_OK 4 +# endif +#endif + + +#define FILE_CONTENTTYPE_DEFAULT "application/octet-stream" +#define MULTIPART_CONTENTTYPE_DEFAULT "multipart/mixed" +#define DISPOSITION_DEFAULT "attachment" + +#define READ_ERROR ((size_t) -1) + + +#ifndef __VMS +#define filesize(name, stat_data) (stat_data.st_size) +#define fopen_read fopen + +#else + +#include <fabdef.h> +/* + * get_vms_file_size does what it takes to get the real size of the file + * + * For fixed files, find out the size of the EOF block and adjust. + * + * For all others, have to read the entire file in, discarding the contents. + * Most posted text files will be small, and binary files like zlib archives + * and CD/DVD images should be either a STREAM_LF format or a fixed format. + * + */ +curl_off_t VmsRealFileSize(const char *name, + const struct_stat *stat_buf) +{ + char buffer[8192]; + curl_off_t count; + int ret_stat; + FILE * file; + + file = fopen(name, FOPEN_READTEXT); /* VMS */ + if(file == NULL) + return 0; + + count = 0; + ret_stat = 1; + while(ret_stat > 0) { + ret_stat = fread(buffer, 1, sizeof(buffer), file); + if(ret_stat != 0) + count += ret_stat; + } + fclose(file); + + return count; +} + +/* + * + * VmsSpecialSize checks to see if the stat st_size can be trusted and + * if not to call a routine to get the correct size. + * + */ +static curl_off_t VmsSpecialSize(const char *name, + const struct_stat *stat_buf) +{ + switch(stat_buf->st_fab_rfm) { + case FAB$C_VAR: + case FAB$C_VFC: + return VmsRealFileSize(name, stat_buf); + break; + default: + return stat_buf->st_size; + } +} + +#define filesize(name, stat_data) VmsSpecialSize(name, &stat_data) + +/* + * vmsfopenread + * + * For upload to work as expected on VMS, different optional + * parameters must be added to the fopen command based on + * record format of the file. + * + */ +static FILE * vmsfopenread(const char *file, const char *mode) +{ + struct_stat statbuf; + int result; + + result = stat(file, &statbuf); + + switch(statbuf.st_fab_rfm) { + case FAB$C_VAR: + case FAB$C_VFC: + case FAB$C_STMCR: + return fopen(file, FOPEN_READTEXT); /* VMS */ + break; + default: + return fopen(file, FOPEN_READTEXT, "rfm=stmlf", "ctx=stm"); + } +} + +#define fopen_read vmsfopenread +#endif + + +#ifndef HAVE_BASENAME +/* + (Quote from The Open Group Base Specifications Issue 6 IEEE Std 1003.1, 2004 + Edition) + + The basename() function shall take the pathname pointed to by path and + return a pointer to the final component of the pathname, deleting any + trailing '/' characters. + + If the string pointed to by path consists entirely of the '/' character, + basename() shall return a pointer to the string "/". If the string pointed + to by path is exactly "//", it is implementation-defined whether '/' or "//" + is returned. + + If path is a null pointer or points to an empty string, basename() shall + return a pointer to the string ".". + + The basename() function may modify the string pointed to by path, and may + return a pointer to static storage that may then be overwritten by a + subsequent call to basename(). + + The basename() function need not be reentrant. A function that is not + required to be reentrant is not required to be thread-safe. + +*/ +static char *Curl_basename(char *path) +{ + /* Ignore all the details above for now and make a quick and simple + implementaion here */ + char *s1; + char *s2; + + s1=strrchr(path, '/'); + s2=strrchr(path, '\\'); + + if(s1 && s2) { + path = (s1 > s2? s1 : s2)+1; + } + else if(s1) + path = s1 + 1; + else if(s2) + path = s2 + 1; + + return path; +} + +#define basename(x) Curl_basename((x)) +#endif + + +/* Set readback state. */ +static void mimesetstate(struct mime_state *state, + enum mimestate tok, void *ptr) +{ + state->state = tok; + state->ptr = ptr; + state->offset = 0; +} + + +/* Escape header string into allocated memory. */ +static char *escape_string(const char *src, ssize_t srclen) +{ + size_t len = srclen >= 0? (size_t) srclen: strlen(src); + size_t bytecount; + size_t i; + char *dst; + + bytecount = len; + for(i = 0; i < len; i++) + if(src[i] == '"' || src[i] == '\\' || !src[i]) + bytecount++; + + dst = malloc(bytecount + 1); + if(!dst) + return NULL; + + for(i = 0; len; len--) { + char c = *src++; + + if(c == '"' || c == '\\' || !c) + dst[i++] = '\\'; + dst[i++] = c? c: '0'; + } + + dst[i] = '\0'; + return dst; +} + +/* Check if header matches. */ +static char *match_header(struct curl_slist *hdr, const char *lbl, size_t len) +{ + char *value = NULL; + + if(strncasecompare(hdr->data, lbl, len) && hdr->data[len] == ':') + for(value = hdr->data + len + 1; *value == ' '; value++) + ; + return value; +} + +/* Get a header from an slist. */ +static char *search_header(struct curl_slist *hdrlist, const char *hdr) +{ + size_t len = strlen(hdr); + char *value = NULL; + + for(; !value && hdrlist; hdrlist = hdrlist->next) + value = match_header(hdrlist, hdr, len); + + return value; +} + +static char *strippath(const char *fullfile) +{ + char *filename; + char *base; + filename = strdup(fullfile); /* duplicate since basename() may ruin the + buffer it works on */ + if(!filename) + return NULL; + base = strdup(basename(filename)); + + free(filename); /* free temporary buffer */ + + return base; /* returns an allocated string or NULL ! */ +} + + +/* In-memory data callbacks. */ +/* Argument is a pointer to the mime part. */ +static size_t mime_mem_read(char *buffer, size_t size, size_t nitems, + void *instream) +{ + struct Curl_mimepart *part = (struct Curl_mimepart *) instream; + size_t sz = part->datasize - part->state.offset; + + (void) size; /* Always 1.*/ + + if(sz > nitems) + sz = nitems; + + if(sz) + memcpy(buffer, (char *) part->data, sz); + + part->state.offset += sz; + return sz; +} + +static int mime_mem_seek(void *instream, curl_off_t offset, int whence) +{ + struct Curl_mimepart *part = (struct Curl_mimepart *) instream; + + switch(whence) { + case SEEK_CUR: + offset += part->state.offset; + break; + case SEEK_END: + offset += part->datasize; + break; + } + + if(offset < 0 || offset > part->datasize) + return CURL_SEEKFUNC_FAIL; + + part->state.offset = offset; + return CURL_SEEKFUNC_OK; +} + +static void mime_mem_free(void *ptr) +{ + Curl_safefree(((struct Curl_mimepart *) ptr)->data); +} + + +/* Open file callbacks. */ +/* Argument is the FILE pointer. */ +static size_t mime_file_read(char *buffer, size_t size, size_t nitems, + void *instream) +{ + return (size_t) fread(buffer, size, nitems, instream); +} + +static int mime_file_seek(void *instream, curl_off_t offset, int whence) +{ + FILE *fp = (FILE *) instream; + + return fseek(fp, (long) offset, whence)? + CURL_SEEKFUNC_CANTSEEK: CURL_SEEKFUNC_OK; +} + + +/* Named file callbacks. */ +/* Argument is a pointer to the mime part. */ +static int mime_open_namedfile(struct Curl_mimepart * part) +{ + /* Open a MIMEKIND_NAMEDFILE part. */ + + if(part->namedfp) + return 0; + part->namedfp = fopen_read(part->data, "rb"); + return part->namedfp? 0: -1; +} + +static size_t mime_namedfile_read(char *buffer, size_t size, size_t nitems, + void *instream) +{ + struct Curl_mimepart *part = (struct Curl_mimepart *) instream; + + if(mime_open_namedfile(part)) + return READ_ERROR; + + return mime_file_read(buffer, size, nitems, part->namedfp); +} + +static int mime_namedfile_seek(void *instream, curl_off_t offset, int whence) +{ + struct Curl_mimepart *part = (struct Curl_mimepart *) instream; + + switch(whence) { + case SEEK_CUR: + if(part->namedfp) + offset += ftell(part->namedfp); + break; + case SEEK_END: + offset += part->datasize; + break; + } + + if(!offset && !part->namedfp) + return CURL_SEEKFUNC_OK; + + if(mime_open_namedfile(part)) + return CURL_SEEKFUNC_FAIL; + + return mime_file_seek(part->namedfp, offset, SEEK_SET); +} + +static void mime_namedfile_free(void *ptr) +{ + struct Curl_mimepart *part = (struct Curl_mimepart *) ptr; + + if(part->namedfp) { + fclose(part->namedfp); + part->namedfp = NULL; + } + Curl_safefree(part->data); + part->data = NULL; +} + + +/* Subparts callbacks. */ +/* Argument is a pointer to the mime structure. */ + +/* Readback a byte string segment. */ +static size_t readback_bytes(struct mime_state *state, + char *buffer, size_t bufsize, + const char *bytes, size_t numbytes, + const char *trail) +{ + size_t sz; + + sz = numbytes - state->offset; + + if(numbytes > state->offset) { + sz = numbytes - state->offset; + bytes += state->offset; + } + else { + size_t tsz = strlen(trail); + + sz = state->offset - numbytes; + if(sz >= tsz) + return 0; + bytes = trail + sz; + sz = tsz - sz; + } + + if(sz > bufsize) + sz = bufsize; + + memcpy(buffer, bytes, sz); + state->offset += sz; + return sz; +} + +/* Readback a mime part. */ +static size_t readback_part(struct Curl_mimepart *part, + char *buffer, size_t bufsize) +{ + size_t cursize = 0; + size_t sz; + struct curl_slist *hdr; +#ifdef CURL_DOES_CONVERSIONS + char *convbuf = buffer; +#endif + + /* Readback from part. */ + + while(bufsize) { + sz = 0; + hdr = (struct curl_slist *) part->state.ptr; + switch(part->state.state) { + case MIMESTATE_BEGIN: + mimesetstate(&part->state, part->flags & MIME_BODY_ONLY? MIMESTATE_BODY: + MIMESTATE_CURLHEADERS, part->curlheaders); + break; + case MIMESTATE_USERHEADERS: + if(!hdr) { + mimesetstate(&part->state, MIMESTATE_EOH, NULL); + break; + } + if(match_header(hdr, "Content-Type", 12)) { + mimesetstate(&part->state, MIMESTATE_USERHEADERS, hdr->next); + break; + } + /* FALLTHROUGH */ + case MIMESTATE_CURLHEADERS: + if(!hdr) + mimesetstate(&part->state, MIMESTATE_USERHEADERS, part->userheaders); + else { + sz = readback_bytes(&part->state, buffer, bufsize, + hdr->data, strlen(hdr->data), "\r\n"); + if(!sz) + mimesetstate(&part->state, part->state.state, hdr->next); + } + break; + case MIMESTATE_EOH: + sz = readback_bytes(&part->state, buffer, bufsize, "\r\n", 2, ""); + if(!sz) + mimesetstate(&part->state, MIMESTATE_BODY, NULL); + break; + case MIMESTATE_BODY: +#ifdef CURL_DOES_CONVERSIONS + if(part->easy && convbuf < buffer) { + CURLcode result = Curl_convert_to_network(part->easy, convbuf, + buffer - convbuf); + if(result) + return READ_ERROR; + convbuf = buffer; + } +#endif + mimesetstate(&part->state, MIMESTATE_CONTENT, NULL); + break; + case MIMESTATE_CONTENT: + if(part->readfunc) + sz = part->readfunc(buffer, 1, bufsize, part->arg); + switch(sz) { + case 0: + mimesetstate(&part->state, MIMESTATE_END, NULL); + /* Try sparing open file descriptors. */ + if(part->kind == MIMEKIND_NAMEDFILE && part->namedfp) { + fclose(part->namedfp); + part->namedfp = NULL; + } + /* FALLTHROUGH */ + case CURL_READFUNC_ABORT: + case CURL_READFUNC_PAUSE: + case READ_ERROR: + return cursize? cursize: sz; + } + break; + case MIMESTATE_END: + return cursize; + default: + break; /* Other values not in part state. */ + } + + /* Bump buffer and counters according to read size. */ + cursize += sz; + buffer += sz; + bufsize -= sz; + } + +#ifdef CURL_DOES_CONVERSIONS + if(part->easy && convbuf < buffer && + part->state.state < MIMESTATE_BODY) { + CURLcode result = Curl_convert_to_network(part->easy, convbuf, + buffer - convbuf); + if(result) + return READ_ERROR; + } +#endif + + return cursize; +} + +/* Readback from mime. */ +static size_t mime_subparts_read(char *buffer, size_t size, size_t nitems, + void *instream) +{ + struct Curl_mime *mime = (struct Curl_mime *) instream; + size_t cursize = 0; + size_t sz; + struct Curl_mimepart *part; +#ifdef CURL_DOES_CONVERSIONS + char *convbuf = buffer; +#endif + + (void) size; /* Always 1. */ + + while(nitems) { + sz = 0; + part = mime->state.ptr; + switch(mime->state.state) { + case MIMESTATE_BEGIN: + case MIMESTATE_BODY: +#ifdef CURL_DOES_CONVERSIONS + convbuf = buffer; +#endif + mimesetstate(&mime->state, MIMESTATE_BOUNDARY1, mime->firstpart); + /* The first boundary always follows the header termination empty line, + so is always preceded by a CRLK. We can then spare 2 characters + by skipping the leading CRLF in boundary. */ + mime->state.offset += 2; + break; + case MIMESTATE_BOUNDARY1: + sz = readback_bytes(&mime->state, buffer, nitems, "\r\n--", 4, ""); + if(!sz) + mimesetstate(&mime->state, MIMESTATE_BOUNDARY2, part); + break; + case MIMESTATE_BOUNDARY2: + sz = readback_bytes(&mime->state, buffer, nitems, mime->boundary, + strlen(mime->boundary), part? "\r\n": "--\r\n"); + if(!sz) { +#ifdef CURL_DOES_CONVERSIONS + if(mime->easy && convbuf < buffer) { + CURLcode result = Curl_convert_to_network(mime->easy, convbuf, + buffer - convbuf); + if(result) + return READ_ERROR; + convbuf = buffer; + } +#endif + mimesetstate(&mime->state, + part? MIMESTATE_CONTENT: MIMESTATE_END, part); + } + break; + case MIMESTATE_CONTENT: + sz = readback_part(part, buffer, nitems); + switch(sz) { + case CURL_READFUNC_ABORT: + case CURL_READFUNC_PAUSE: + case READ_ERROR: + return cursize? cursize: sz; + case 0: +#ifdef CURL_DOES_CONVERSIONS + convbuf = buffer; +#endif + mimesetstate(&mime->state, MIMESTATE_BOUNDARY1, part->nextpart); + break; + } + break; + case MIMESTATE_END: + return cursize; + default: + break; /* other values not used in mime state. */ + } + + /* Bump buffer and counters according to read size. */ + cursize += sz; + buffer += sz; + nitems -= sz; + } + +#ifdef CURL_DOES_CONVERSIONS + if(mime->easy && convbuf < buffer && + mime->state.state <= MIMESTATE_CONTENT) { + CURLcode result = Curl_convert_to_network(mime->easy, convbuf, + buffer - convbuf); + if(result) + return READ_ERROR; + } +#endif + + return cursize; +} + +static int mime_part_rewind(struct Curl_mimepart *part) +{ + int res = CURL_SEEKFUNC_OK; + enum mimestate targetstate = MIMESTATE_BEGIN; + + if(part->flags & MIME_BODY_ONLY) + targetstate = MIMESTATE_BODY; + if(part->state.state > targetstate) { + res = CURL_SEEKFUNC_CANTSEEK; + if(part->seekfunc) + res = part->seekfunc(part->arg, part->origin, SEEK_SET); + } + + if(res == CURL_SEEKFUNC_OK) + mimesetstate(&part->state, targetstate, NULL); + + return res; +} + +static int mime_subparts_seek(void *instream, curl_off_t offset, int whence) +{ + struct Curl_mime *mime = (struct Curl_mime *) instream; + struct Curl_mimepart *part; + int result = CURL_SEEKFUNC_OK; + int res; + + if(whence != SEEK_SET || offset) + return CURL_SEEKFUNC_CANTSEEK; /* Only support full rewind. */ + + if(mime->state.state == MIMESTATE_BEGIN) + return CURL_SEEKFUNC_OK; /* Already rewound. */ + + for(part = mime->firstpart; part; part = part->nextpart) { + res = mime_part_rewind(part); + if(res != CURL_SEEKFUNC_OK) + result = res; + } + + if(result == CURL_SEEKFUNC_OK) + mimesetstate(&mime->state, MIMESTATE_BEGIN, NULL); + + return result; +} + +static void mime_subparts_free(void *ptr) +{ + struct Curl_mime *mime = (struct Curl_mime *) ptr; + curl_mime_free(mime); +} + + +/* Release part content. */ +static void cleanup_part_content(struct Curl_mimepart *part) +{ + if(part->freefunc) + part->freefunc(part->arg); + + part->readfunc = NULL; + part->seekfunc = NULL; + part->freefunc = NULL; + part->arg = NULL; + part->data = NULL; + part->namedfp = NULL; + part->origin = 0; + part->datasize = (curl_off_t) 0; /* No size yet. */ + part->kind = MIMEKIND_NONE; +} + +void Curl_mime_cleanpart(struct Curl_mimepart *part) +{ + cleanup_part_content(part); + curl_slist_free_all(part->curlheaders); + if(part->flags & MIME_USERHEADERS_OWNER) + curl_slist_free_all(part->userheaders); + Curl_safefree(part->mimetype); + Curl_safefree(part->name); + Curl_safefree(part->filename); + Curl_mime_initpart(part, part->easy); +} + +/* Recursively delete a mime handle and its parts. */ +void curl_mime_free(struct Curl_mime *mime) +{ + struct Curl_mimepart *part; + + if(mime) { + while(mime->firstpart) { + part = mime->firstpart; + mime->firstpart = part->nextpart; + Curl_mime_cleanpart(part); + free(part); + } + + free(mime->boundary); + free(mime); + } +} + +/* + * Mime build functions. + */ + +/* Create a mime handle. */ +struct Curl_mime *curl_mime_init(struct Curl_easy *easy) +{ + struct Curl_mime *mime; + + mime = (struct Curl_mime *) malloc(sizeof *mime); + + if(mime) { + mime->easy = easy; + mime->parent = NULL; + mime->firstpart = NULL; + mime->lastpart = NULL; + + /* Get a part boundary. */ + mime->boundary = malloc(24 + MIME_RAND_BOUNDARY_CHARS + 1); + if(!mime->boundary) { + free(mime); + return NULL; + } + + memset(mime->boundary, '-', 24); + Curl_rand_hex(easy, (unsigned char *) mime->boundary + 24, + MIME_RAND_BOUNDARY_CHARS + 1); + mimesetstate(&mime->state, MIMESTATE_BEGIN, NULL); + } + + return mime; +} + +/* Initialize a mime part. */ +void Curl_mime_initpart(struct Curl_mimepart *part, struct Curl_easy *easy) +{ + memset((char *) part, 0, sizeof *part); + part->easy = easy; + mimesetstate(&part->state, MIMESTATE_BEGIN, NULL); +} + +/* Create a mime part and append it to a mime handle's part list. */ +struct Curl_mimepart *curl_mime_addpart(struct Curl_mime *mime) +{ + struct Curl_mimepart *part; + + if(!mime) + return NULL; + + part = (struct Curl_mimepart *) malloc(sizeof *part); + + if(part) { + Curl_mime_initpart(part, mime->easy); + part->parent = mime; + + if(mime->lastpart) + mime->lastpart->nextpart = part; + else + mime->firstpart = part; + + mime->lastpart = part; + } + + return part; +} + +/* Set mime part name. */ +CURLcode curl_mime_name(struct Curl_mimepart *part, + const char *name, ssize_t namesize) +{ + if(!part) + return CURLE_BAD_FUNCTION_ARGUMENT; + + Curl_safefree(part->name); + part->name = NULL; + part->namesize = 0; + + if(name) { + if(namesize < 0) + namesize = strlen(name); + part->name = malloc(namesize + 1); + if(!part->name) + return CURLE_OUT_OF_MEMORY; + if(namesize) + memcpy(part->name, name, namesize); + part->name[namesize] = '\0'; + part->namesize = namesize; + } + + return CURLE_OK; +} + +/* Set mime part remote file name. */ +CURLcode curl_mime_filename(struct Curl_mimepart *part, const char *filename) +{ + if(!part) + return CURLE_BAD_FUNCTION_ARGUMENT; + + Curl_safefree(part->filename); + part->filename = NULL; + + if(filename) { + part->filename = strdup(filename); + if(!part->filename) + return CURLE_OUT_OF_MEMORY; + } + + return CURLE_OK; +} + +/* Set mime part content from memory data. */ +CURLcode curl_mime_data(struct Curl_mimepart *part, + const char *data, ssize_t datasize) +{ + if(!part) + return CURLE_BAD_FUNCTION_ARGUMENT; + + cleanup_part_content(part); + + if(data) { + if(datasize < 0) + datasize = strlen(data); + + part->data = malloc(datasize + 1); + if(!part->data) + return CURLE_OUT_OF_MEMORY; + + part->datasize = datasize; + + if(datasize) + memcpy(part->data, data, datasize); + part->data[datasize] = '\0'; /* Set a nul terminator as sentinel. */ + + part->readfunc = mime_mem_read; + part->seekfunc = mime_mem_seek; + part->freefunc = mime_mem_free; + part->arg = part; + part->kind = MIMEKIND_DATA; + } + + return CURLE_OK; +} + +/* Set mime part content from opened file. */ +CURLcode Curl_mime_file(struct Curl_mimepart *part, + FILE *fp, int closewhendone) +{ + if(!part || !fp) + return CURLE_BAD_FUNCTION_ARGUMENT; + + cleanup_part_content(part); + + part->arg = fp; + part->readfunc = (curl_read_callback) mime_file_read; + if(closewhendone) + part->freefunc = (curl_free_callback) fclose; + part->origin = ftell(fp); + /* Check if file is seekable and get its size. */ + part->datasize = (curl_off_t) -1; /* Unknown size. */ + if(!fseek(fp, 0L, SEEK_END)) { + part->datasize = ftell(fp); + if(part->datasize >= 0) { + if(part->datasize < part->origin) + part->datasize = 0; + else + part->datasize -= part->origin; + part->seekfunc = mime_file_seek; + } + fseek(fp, (long) part->origin, SEEK_SET); + } + part->kind = MIMEKIND_FILE; + return CURLE_OK; +} + +/* Set mime part content from named local file. */ +CURLcode curl_mime_filedata(struct Curl_mimepart *part, const char *filename) +{ + CURLcode result = CURLE_OK; + struct_stat sbuf; + char *base; + + if(!part || !filename) + return CURLE_BAD_FUNCTION_ARGUMENT; + + if(!strcmp(filename, "-")) + return Curl_mime_file(part, stdin, 0); + + if(stat(filename, &sbuf) || access(filename, R_OK)) + result = CURLE_READ_ERROR; + + cleanup_part_content(part); + + part->data = strdup(filename); + if(!part->data) + result = CURLE_OUT_OF_MEMORY; + + part->datasize = -1; + if(!result && S_ISREG(sbuf.st_mode)) { + part->datasize = filesize(filename, sbuf); + part->seekfunc = mime_namedfile_seek; + } + + part->readfunc = mime_namedfile_read; + part->freefunc = mime_namedfile_free; + part->arg = part; + part->kind = MIMEKIND_NAMEDFILE; + + /* As a side effect, set the filename to the current file's base name. + It is possible to withdraw this by explicitly calling curl_mime_filename() + with a NULL filename argument after the current call. */ + base = strippath(filename); + if(!base) + result = CURLE_OUT_OF_MEMORY; + else { + CURLcode res = curl_mime_filename(part, base); + + if(res) + result = res; + free(base); + } + return result; +} + +/* Set mime part type. */ +CURLcode curl_mime_type(struct Curl_mimepart *part, const char *mimetype) +{ + if(!part) + return CURLE_BAD_FUNCTION_ARGUMENT; + + Curl_safefree(part->mimetype); + part->mimetype = NULL; + + if(mimetype) { + part->mimetype = strdup(mimetype); + if(!part->mimetype) + return CURLE_OUT_OF_MEMORY; + } + + return CURLE_OK; +} + +/* Set mime data transfer encoder. */ +CURLcode curl_mime_encoder(struct Curl_mimepart *part, const char *encoding) +{ + CURLcode result = CURLE_OK; + + /* Encoding feature not yet implemented. */ + + if(!part) + return CURLE_BAD_FUNCTION_ARGUMENT; + + if(encoding) + return CURLE_BAD_FUNCTION_ARGUMENT; + + return result; +} + +/* Set mime part headers. */ +CURLcode curl_mime_headers(struct Curl_mimepart *part, + struct curl_slist *headers, int take_ownership) +{ + if(!part) + return CURLE_BAD_FUNCTION_ARGUMENT; + + if(part->flags & MIME_USERHEADERS_OWNER) { + curl_slist_free_all(part->userheaders); + part->flags &= ~MIME_USERHEADERS_OWNER; + } + part->userheaders = headers; + if(headers && take_ownership) + part->flags |= MIME_USERHEADERS_OWNER; + return CURLE_OK; +} + +/* Set mime part content from callback. */ +CURLcode curl_mime_data_cb(struct Curl_mimepart *part, curl_off_t datasize, + curl_read_callback readfunc, + curl_seek_callback seekfunc, + curl_free_callback freefunc, void *arg) +{ + if(!part) + return CURLE_BAD_FUNCTION_ARGUMENT; + + cleanup_part_content(part); + + if(readfunc) { + part->readfunc = readfunc; + part->seekfunc = seekfunc; + part->freefunc = freefunc; + part->arg = arg; + part->datasize = datasize; + part->kind = MIMEKIND_CALLBACK; + } + + return CURLE_OK; +} + +/* Set mime part content from subparts. */ +CURLcode curl_mime_subparts(struct Curl_mimepart *part, + struct Curl_mime *subparts) +{ + if(!part) + return CURLE_BAD_FUNCTION_ARGUMENT; + + /* Accept setting twice the same subparts. */ + if(part->kind == MIMEKIND_MULTIPART && part->arg == subparts) + return CURLE_OK; + + cleanup_part_content(part); + + if(subparts) { + /* Must belong to the same data handle. */ + if(part->easy && subparts->easy && part->easy != subparts->easy) + return CURLE_BAD_FUNCTION_ARGUMENT; + + /* Should not have been attached already. */ + if(subparts->parent) + return CURLE_BAD_FUNCTION_ARGUMENT; + + subparts->parent = part; + part->readfunc = mime_subparts_read; + part->seekfunc = mime_subparts_seek; + part->freefunc = mime_subparts_free; + part->arg = subparts; + part->datasize = -1; + part->kind = MIMEKIND_MULTIPART; + } + + return CURLE_OK; +} + + +/* Readback from top mime. */ +/* Argument is the dummy top part. */ +size_t Curl_mime_read(char *buffer, size_t size, size_t nitems, void *instream) +{ + struct Curl_mimepart *part = (struct Curl_mimepart *) instream; + + (void) size; /* Always 1. */ + return readback_part(part, buffer, nitems); +} + +/* Rewind mime stream. */ +CURLcode Curl_mime_rewind(struct Curl_mimepart *part) +{ + return mime_part_rewind(part) == CURL_SEEKFUNC_OK? + CURLE_OK: CURLE_SEND_FAIL_REWIND; +} + +/* Compute header list size. */ +static size_t slist_size(struct curl_slist *s, + size_t overhead, const char *skip) +{ + size_t size = 0; + size_t skiplen = skip? strlen(skip): 0; + + for(; s; s = s->next) + if(!skip || !match_header(s, skip, skiplen)) + size += strlen(s->data) + overhead; + return size; +} + +/* Get/compute multipart size. */ +static curl_off_t multipart_size(struct Curl_mime *mime) +{ + curl_off_t size; + curl_off_t sz; + size_t boundarysize; + struct Curl_mimepart *part; + + if(!mime) + return 0; /* Not present -> empty. */ + + boundarysize = 4 + strlen(mime->boundary) + 2; + size = boundarysize; /* Final boundary - CRLF after headers. */ + + for(part = mime->firstpart; part; part = part->nextpart) { + sz = Curl_mime_size(part); + + if(sz < 0) + size = sz; + + if(size >= 0) + size += boundarysize + sz; + } + + return size; +} + +/* Get/compute mime size. */ +curl_off_t Curl_mime_size(struct Curl_mimepart *part) +{ + curl_off_t size; + + if(part->datasize < 0 && part->kind == MIMEKIND_MULTIPART) + part->datasize = multipart_size(part->arg); + + size = part->datasize; + if(size >= 0 && !(part->flags & MIME_BODY_ONLY)) { + /* Compute total part size. */ + size += slist_size(part->curlheaders, 2, NULL); + size += slist_size(part->userheaders, 2, "Content-Type"); + size += 2; /* CRLF after headers. */ + } + return size; +} + +/* Add a header. */ +/* VARARGS2 */ +CURLcode Curl_mime_add_header(struct curl_slist **slp, const char *fmt, ...) +{ + struct curl_slist *hdr = NULL; + char *s = NULL; + va_list ap; + + va_start(ap, fmt); + s = curl_mvaprintf(fmt, ap); + va_end(ap); + + if(s) { + hdr = Curl_slist_append_nodup(*slp, s); + if(hdr) + *slp = hdr; + else + free(s); + } + + return hdr? CURLE_OK: CURLE_OUT_OF_MEMORY; +} + +/* Add a content type header. */ +static CURLcode add_content_type(struct curl_slist **slp, + const char *type, const char *boundary) +{ + return Curl_mime_add_header(slp, "Content-Type: %s%s%s", type, + boundary? "; boundary=": "", + boundary? boundary: ""); +} + + +static const char *ContentTypeForFilename(const char *filename) +{ + unsigned int i; + + /* + * If no content type was specified, we scan through a few well-known + * extensions and pick the first we match! + */ + struct ContentType { + const char *extension; + const char *type; + }; + static const struct ContentType ctts[] = { + {".gif", "image/gif"}, + {".jpg", "image/jpeg"}, + {".jpeg", "image/jpeg"}, + {".png", "image/png"}, + {".svg", "image/svg+xml"}, + {".txt", "text/plain"}, + {".htm", "text/html"}, + {".html", "text/html"}, + {".pdf", "application/pdf"}, + {".xml", "application/xml"} + }; + + if(filename) { + size_t len1 = strlen(filename); + const char *nameend = filename + len1; + + for(i = 0; i < sizeof ctts / sizeof ctts[0]; i++) { + size_t len2 = strlen(ctts[i].extension); + + if(len1 >= len2 && strcasecompare(nameend - len2, ctts[i].extension)) + return ctts[i].type; + } + } + return NULL; +} + +CURLcode Curl_mime_prepare_headers(struct Curl_mimepart *part, + const char *contenttype, + const char *disposition, + enum mimestrategy strategy) +{ + struct Curl_mime *mime = NULL; + const char *boundary = NULL; + char *s; + CURLcode ret = CURLE_OK; + + /* Get rid of previously prepared headers. */ + curl_slist_free_all(part->curlheaders); + part->curlheaders = NULL; + + /* Be sure we won't access old headers later. */ + if(part->state.state == MIMESTATE_CURLHEADERS) + mimesetstate(&mime->state, MIMESTATE_CURLHEADERS, NULL); + + /* Build the content-type header. */ + s = search_header(part->userheaders, "Content-Type"); + if(s) + contenttype = s; + if(part->mimetype) + contenttype = part->mimetype; + if(!contenttype) { + switch(part->kind) { + case MIMEKIND_MULTIPART: + contenttype = MULTIPART_CONTENTTYPE_DEFAULT; + break; + case MIMEKIND_NAMEDFILE: + contenttype = ContentTypeForFilename(part->filename); + if(!contenttype) + contenttype = ContentTypeForFilename(part->data); + if(!contenttype && part->filename) + contenttype = FILE_CONTENTTYPE_DEFAULT; + break; + case MIMEKIND_FILE: + contenttype = ContentTypeForFilename(part->filename); + if(!contenttype && part->filename) + contenttype = FILE_CONTENTTYPE_DEFAULT; + break; + default: + contenttype = ContentTypeForFilename(part->filename); + break; + } + } + + if(part->kind == MIMEKIND_MULTIPART) { + mime = (struct Curl_mime *) part->arg; + if(mime) + boundary = mime->boundary; + } + else if(contenttype && strcasecompare(contenttype, "text/plain")) + if(strategy == MIMESTRATEGY_MAIL || !part->filename) + contenttype = NULL; + + /* Issue content-disposition header only if not already set by caller. */ + if(!search_header(part->userheaders, "Content-Disposition")) { + if(!disposition) + if(part->filename || part->name || + (contenttype && !strncasecompare(contenttype, "multipart/", 10))) + disposition = DISPOSITION_DEFAULT; + if(disposition && curl_strequal(disposition, "attachment") && + !part->name && !part->filename) + disposition = NULL; + if(disposition) { + char *name = NULL; + char *filename = NULL; + + if(part->name) { + name = escape_string(part->name, part->namesize); + if(!name) + ret = CURLE_OUT_OF_MEMORY; + } + if(!ret && part->filename) { + filename = escape_string(part->filename, -1); + if(!filename) + ret = CURLE_OUT_OF_MEMORY; + } + if(!ret) + ret = Curl_mime_add_header(&part->curlheaders, + "Content-Disposition: %s%s%s%s%s%s%s", + disposition, + name? "; name=\"": "", + name? name: "", + name? "\"": "", + filename? "; filename=\"": "", + filename? filename: "", + filename? "\"": ""); + Curl_safefree(name); + Curl_safefree(filename); + if(ret) + return ret; + } + } + + /* Issue Content-Type header. */ + if(contenttype) { + ret = add_content_type(&part->curlheaders, contenttype, boundary); + if(ret) + return ret; + } + + /* Content-Transfer-Encoding header. */ + if(contenttype && strategy == MIMESTRATEGY_MAIL && + part->kind != MIMEKIND_MULTIPART && + !search_header(part->userheaders, "Content-Transfer-Encoding")) { + ret = Curl_mime_add_header(&part->curlheaders, + "Content-Transfer-Encoding: 8bit"); + if(ret) + return ret; + } + + /* If we were reading curl-generated headers, restart with new ones (this + should not occur). */ + if(part->state.state == MIMESTATE_CURLHEADERS) + mimesetstate(&mime->state, MIMESTATE_CURLHEADERS, part->curlheaders); + + /* Process subparts. */ + if(part->kind == MIMEKIND_MULTIPART && mime) { + struct Curl_mimepart *subpart; + + disposition = NULL; + if(strcasecompare(contenttype, "multipart/form-data")) + disposition = "form-data"; + for(subpart = mime->firstpart; subpart; subpart = subpart->nextpart) { + ret = Curl_mime_prepare_headers(subpart, NULL, disposition, strategy); + if(ret) + return ret; + } + } + return ret; +} + +#else /* !CURL_DISABLE_HTTP || !CURL_DISABLE_SMTP || !CURL_DISABLE_IMAP */ + +/* Mime not compiled in: define stubs for externally-referenced functions. */ +curl_mime *curl_mime_init(CURL *easy) +{ + (void) easy; + return NULL; +} + +void curl_mime_free(curl_mime *mime) +{ + (void) mime; +} + +curl_mimepart *curl_mime_addpart(curl_mime *mime) +{ + (void) mime; + return NULL; +} + +CURLcode curl_mime_name(curl_mimepart *part, + const char *name, ssize_t namesize) +{ + (void) part; + (void) name; + (void) namesize; + return CURLE_NOT_BUILT_IN; +} + +CURLcode curl_mime_filename(curl_mimepart *part, const char *filename) +{ + (void) part; + (void) filename; + return CURLE_NOT_BUILT_IN; +} + +CURLcode curl_mime_type(curl_mimepart *part, const char *mimetype) +{ + (void) part; + (void) mimetype; + return CURLE_NOT_BUILT_IN; +} + +CURLcode curl_mime_encoder(struct Curl_mimepart *part, const char *encoding) +{ + (void) part; + (void) encoding; + return CURLE_NOT_BUILT_IN; +} + +CURLcode curl_mime_data(curl_mimepart *part, + const char *data, ssize_t datasize) +{ + (void) part; + (void) data; + (void) datasize; + return CURLE_NOT_BUILT_IN; +} + +CURLcode Curl_mime_file(curl_mimepart *part, FILE *fp, int closewhendone) +{ + (void) part; + (void) fp; + (void) closewhendone; + return CURLE_NOT_BUILT_IN; +} + +CURLcode curl_mime_filedata(curl_mimepart *part, const char *filename) +{ + (void) part; + (void) filename; + return CURLE_NOT_BUILT_IN; +} + +CURLcode curl_mime_data_cb(curl_mimepart *part, + curl_off_t datasize, + curl_read_callback readfunc, + curl_seek_callback seekfunc, + curl_free_callback freefunc, + void *arg) +{ + (void) part; + (void) datasize; + (void) readfunc; + (void) seekfunc; + (void) freefunc; + (void) arg; + return CURLE_NOT_BUILT_IN; +} + +CURLcode curl_mime_subparts(curl_mimepart *part, curl_mime *subparts) +{ + (void) part; + (void) subparts; + return CURLE_NOT_BUILT_IN; +} + +CURLcode curl_mime_headers(curl_mimepart *part, + struct curl_slist *headers, int take_ownership) +{ + (void) part; + (void) headers; + (void) take_ownership; + return CURLE_NOT_BUILT_IN; +} + +void Curl_mime_initpart(struct Curl_mimepart *part, struct Curl_easy *easy) +{ + (void) part; + (void) data; +} + +void Curl_mime_cleanpart(struct Curl_mimepart *part) +{ + (void) part; +} + +CURLcode Curl_mime_prepare_headers(struct Curl_mimepart *part, + const char *contenttype, + const char *disposition, + enum mimestrategy strategy) +{ + (void) part; + (void) contenttype; + (void) disposition; + (void) strategy; + return CURLE_NOT_BUILT_IN; +} + +curl_off_t Curl_mime_size(struct Curl_mimepart *part) +{ + (void) part; + return (curl_off_t) -1; +} + +size_t Curl_mime_read(char *buffer, size_t size, size_t nitems, void *instream) +{ + (void) buffer; + (void) size; + (void) nitems; + (void) instream; + return 0; +} + +CURLcode Curl_mime_rewind(struct Curl_mimepart *part) +{ + (void) part; + return CURLE_NOT_BUILT_IN; +} + +/* VARARGS2 */ +CURLcode Curl_mime_add_header(struct curl_slist **slp, const char *fmt, ...) +{ + (void) slp; + (void) fmt; + return CURLE_NOT_BUILT_IN; +} + +#endif /* !CURL_DISABLE_HTTP || !CURL_DISABLE_SMTP || !CURL_DISABLE_IMAP */ diff --git a/lib/mime.h b/lib/mime.h new file mode 100644 index 000000000..4d52b72fb --- /dev/null +++ b/lib/mime.h @@ -0,0 +1,119 @@ +#ifndef HEADER_CURL_MIME_H +#define HEADER_CURL_MIME_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2017, Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.haxx.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ***************************************************************************/ + +#define MIME_RAND_BOUNDARY_CHARS 16 /* Nb. of random boundary chars. */ + +/* Part flags. */ +#define MIME_USERHEADERS_OWNER (1 << 0) +#define MIME_BODY_ONLY (1 << 1) + +/* Part source kinds. */ +enum mimekind { + MIMEKIND_NONE = 0, /* Part not set. */ + MIMEKIND_DATA, /* Allocated mime data. */ + MIMEKIND_NAMEDFILE, /* Data from named file. */ + MIMEKIND_FILE, /* Data from file pointer. */ + MIMEKIND_CALLBACK, /* Data from `read' callback. */ + MIMEKIND_MULTIPART, /* Data is a mime subpart. */ + MIMEKIND_LAST +}; + +/* Readback state tokens. */ +enum mimestate { + MIMESTATE_BEGIN, /* Readback has not yet started. */ + MIMESTATE_CURLHEADERS, /* In curl-generated headers. */ + MIMESTATE_USERHEADERS, /* In caller's supplied headers. */ + MIMESTATE_EOH, /* End of headers. */ + MIMESTATE_BODY, /* Placeholder. */ + MIMESTATE_BOUNDARY1, /* In boundary prefix. */ + MIMESTATE_BOUNDARY2, /* In boundary. */ + MIMESTATE_CONTENT, /* In content. */ + MIMESTATE_END, /* End of part reached. */ + MIMESTATE_LAST +}; + +/* Mime headers strategies. */ +enum mimestrategy { + MIMESTRATEGY_MAIL, /* Mime mail. */ + MIMESTRATEGY_FORM, /* HTTP post form. */ + MIMESTRATEGY_LAST +}; + +/* Mime readback state. */ +struct mime_state { + enum mimestate state; /* Current state token. */ + void * ptr; /* State-dependent pointer. */ + size_t offset; /* State-dependent offset. */ +}; + +/* A mime context. */ +struct Curl_mime { + struct Curl_easy * easy; /* The associated easy handle. */ + struct Curl_mimepart * parent; /* Parent part. */ + struct Curl_mimepart * firstpart; /* First part. */ + struct Curl_mimepart * lastpart; /* Last part. */ + char * boundary; /* The part boundary. */ + struct mime_state state; /* Current readback state. */ +}; + +/* A mime part. */ +struct Curl_mimepart { + struct Curl_easy * easy; /* The associated easy handle. */ + struct Curl_mime * parent; /* Parent mime structure. */ + struct Curl_mimepart * nextpart; /* Forward linked list. */ + enum mimekind kind; /* The part kind. */ + char * data; /* Memory data or file name. */ + curl_read_callback readfunc; /* Read function. */ + curl_seek_callback seekfunc; /* Seek function. */ + curl_free_callback freefunc; /* Argument free function. */ + void * arg; /* Argument to callback functions. */ + FILE * namedfp; /* Named file pointer. */ + struct curl_slist * curlheaders; /* Part headers. */ + struct curl_slist * userheaders; /* Part headers. */ + char * mimetype; /* Part mime type. */ + char * filename; /* Remote file name. */ + char * name; /* Data name. */ + size_t namesize; /* Data name size. */ + curl_off_t origin; /* Origin file offset. */ + curl_off_t datasize; /* Expected data size. */ + unsigned int flags; /* Flags. */ + struct mime_state state; /* Current readback state. */ +}; + + +/* Prototypes. */ +void Curl_mime_initpart(struct Curl_mimepart *part, struct Curl_easy *easy); +void Curl_mime_cleanpart(struct Curl_mimepart *part); +CURLcode Curl_mime_prepare_headers(struct Curl_mimepart *part, + const char *contenttype, + const char *disposition, + enum mimestrategy strategy); +curl_off_t Curl_mime_size(struct Curl_mimepart *part); +size_t Curl_mime_read(char *buffer, size_t size, size_t nitems, + void *instream); +CURLcode Curl_mime_rewind(struct Curl_mimepart *part); +CURLcode Curl_mime_add_header(struct curl_slist **slp, const char *fmt, ...); +CURLcode Curl_mime_file(curl_mimepart *part, FILE *fp, int closewhendone); + +#endif /* HEADER_CURL_MIME_H */ diff --git a/lib/non-ascii.c b/lib/non-ascii.c index 0a768bd91..b8ddb02e5 100644 --- a/lib/non-ascii.c +++ b/lib/non-ascii.c @@ -319,29 +319,4 @@ void Curl_convert_close(struct Curl_easy *data) #endif /* HAVE_ICONV */ } -/* - * Curl_convert_form() is used from http.c, this converts any form items that - need to be sent in the network encoding. Returns CURLE_OK on success. - */ -CURLcode Curl_convert_form(struct Curl_easy *data, struct FormData *form) -{ - CURLcode result; - - if(!data) - return CURLE_BAD_FUNCTION_ARGUMENT; - - while(form) { - if(form->type == FORM_DATA) { - result = Curl_convert_to_network(data, form->line, form->length); - /* Curl_convert_to_network calls failf if unsuccessful */ - if(result) - return result; - } - - form = form->next; - } - - return CURLE_OK; -} - #endif /* CURL_DOES_CONVERSIONS */ diff --git a/lib/non-ascii.h b/lib/non-ascii.h index e27f1f41f..5fb5771e0 100644 --- a/lib/non-ascii.h +++ b/lib/non-ascii.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2011, Daniel Stenberg, <daniel@haxx.se>, et al. + * Copyright (C) 1998 - 2017, Daniel Stenberg, <daniel@haxx.se>, et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms @@ -48,7 +48,6 @@ CURLcode Curl_convert_from_network(struct Curl_easy *data, char *buffer, size_t length); CURLcode Curl_convert_from_utf8(struct Curl_easy *data, char *buffer, size_t length); -CURLcode Curl_convert_form(struct Curl_easy *data, struct FormData *form); #else #define Curl_convert_clone(a,b,c,d) ((void)a, CURLE_OK) #define Curl_convert_init(x) Curl_nop_stmt @@ -57,7 +56,6 @@ CURLcode Curl_convert_form(struct Curl_easy *data, struct FormData *form); #define Curl_convert_to_network(a,b,c) ((void)a, CURLE_OK) #define Curl_convert_from_network(a,b,c) ((void)a, CURLE_OK) #define Curl_convert_from_utf8(a,b,c) ((void)a, CURLE_OK) -#define Curl_convert_form(a,b) CURLE_OK #endif #endif /* HEADER_CURL_NON_ASCII_H */ diff --git a/lib/smtp.c b/lib/smtp.c index 4e2c3361e..67dc04f28 100644 --- a/lib/smtp.c +++ b/lib/smtp.c @@ -67,6 +67,7 @@ #include "transfer.h" #include "escape.h" #include "http.h" /* for HTTP proxy tunnel stuff */ +#include "mime.h" #include "socks.h" #include "smtp.h" #include "strtoofft.h" @@ -530,8 +531,37 @@ static CURLcode smtp_perform_mail(struct connectdata *conn) } } + /* Prepare the mime data if some. */ + if(data->set.mimepost.kind != MIMEKIND_NONE) { + /* Use the whole structure as data. */ + data->set.mimepost.flags &= ~MIME_BODY_ONLY; + + /* Add external headers and mime version. */ + curl_mime_headers(&data->set.mimepost, data->set.headers, 0); + result = Curl_mime_prepare_headers(&data->set.mimepost, NULL, + NULL, MIMESTRATEGY_MAIL); + + if(!result) + if(!Curl_checkheaders(conn, "Mime-Version")) + result = Curl_mime_add_header(&data->set.mimepost.curlheaders, + "Mime-Version: 1.0"); + + /* Make sure we will read the entire mime structure. */ + if(!result) + result = Curl_mime_rewind(&data->set.mimepost); + + if(result) + return result; + + data->state.infilesize = Curl_mime_size(&data->set.mimepost); + + /* Read from mime structure. */ + data->state.fread_func = (curl_read_callback) Curl_mime_read; + data->state.in = (void *) &data->set.mimepost; + } + /* Calculate the optional SIZE parameter */ - if(conn->proto.smtpc.size_supported && conn->data->state.infilesize > 0) { + if(conn->proto.smtpc.size_supported && data->state.infilesize > 0) { size = aprintf("%" CURL_FORMAT_CURL_OFF_T, data->state.infilesize); if(!size) { @@ -1159,7 +1189,8 @@ static CURLcode smtp_done(struct connectdata *conn, CURLcode status, connclose(conn, "SMTP done with bad status"); /* marked for closure */ result = status; /* use the already set error code */ } - else if(!data->set.connect_only && data->set.upload && data->set.mail_rcpt) { + else if(!data->set.connect_only && data->set.mail_rcpt && + (data->set.upload || data->set.mimepost.kind)) { /* Calculate the EOB taking into account any terminating CRLF from the previous line of the email or the CRLF of the DATA command when there is "no mail data". RFC-5321, sect. 4.1.1.4. @@ -1249,7 +1280,7 @@ static CURLcode smtp_perform(struct connectdata *conn, bool *connected, smtp->rcpt = data->set.mail_rcpt; /* Start the first command in the DO phase */ - if(data->set.upload && data->set.mail_rcpt) + if((data->set.upload || data->set.mimepost.kind) && data->set.mail_rcpt) /* MAIL transfer */ result = smtp_perform_mail(conn); else diff --git a/lib/transfer.c b/lib/transfer.c index e7625ef77..4c160ef7e 100644 --- a/lib/transfer.c +++ b/lib/transfer.c @@ -73,6 +73,7 @@ #include "connect.h" #include "non-ascii.h" #include "http2.h" +#include "mime.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" @@ -241,6 +242,7 @@ CURLcode Curl_fillreadbuffer(struct connectdata *conn, int bytes, int *nreadp) CURLcode Curl_readrewind(struct connectdata *conn) { struct Curl_easy *data = conn->data; + struct Curl_mimepart *mimepart = &data->set.mimepost; conn->bits.rewindaftersend = FALSE; /* we rewind now */ @@ -253,9 +255,21 @@ CURLcode Curl_readrewind(struct connectdata *conn) /* We have sent away data. If not using CURLOPT_POSTFIELDS or CURLOPT_HTTPPOST, call app to rewind */ - if(data->set.postfields || - (data->set.httpreq == HTTPREQ_POST_FORM)) + if(conn->handler->protocol & PROTO_FAMILY_HTTP) { + struct HTTP *http = data->req.protop; + + if(http->sendit) + mimepart = http->sendit; + } + if(data->set.postfields) ; /* do nothing */ + else if(data->set.httpreq == HTTPREQ_POST_MIME || + data->set.httpreq == HTTPREQ_POST_FORM) { + if(Curl_mime_rewind(mimepart)) { + failf(data, "Cannot rewind mime/post data"); + return CURLE_SEND_FAIL_REWIND; + } + } else { if(data->set.seek_func) { int err; @@ -1826,7 +1840,8 @@ CURLcode Curl_follow(struct Curl_easy *data, * can be overridden with CURLOPT_POSTREDIR. */ if((data->set.httpreq == HTTPREQ_POST - || data->set.httpreq == HTTPREQ_POST_FORM) + || data->set.httpreq == HTTPREQ_POST_FORM + || data->set.httpreq == HTTPREQ_POST_MIME) && !(data->set.keep_post & CURL_REDIR_POST_301)) { infof(data, "Switch from POST to GET\n"); data->set.httpreq = HTTPREQ_GET; @@ -1850,7 +1865,8 @@ CURLcode Curl_follow(struct Curl_easy *data, * can be overridden with CURLOPT_POSTREDIR. */ if((data->set.httpreq == HTTPREQ_POST - || data->set.httpreq == HTTPREQ_POST_FORM) + || data->set.httpreq == HTTPREQ_POST_FORM + || data->set.httpreq == HTTPREQ_POST_MIME) && !(data->set.keep_post & CURL_REDIR_POST_302)) { infof(data, "Switch from POST to GET\n"); data->set.httpreq = HTTPREQ_GET; @@ -71,6 +71,7 @@ bool curl_win32_idn_to_ascii(const char *in, char **out); #include "netrc.h" #include "formdata.h" +#include "mime.h" #include "vtls/vtls.h" #include "hostip.h" #include "transfer.h" @@ -479,6 +480,8 @@ CURLcode Curl_close(struct Curl_easy *data) Curl_http2_cleanup_dependencies(data); Curl_convert_close(data); + Curl_mime_cleanpart(&data->set.mimepost); + /* No longer a dirty share, if it exists */ if(data->share) { Curl_share_lock(data, CURL_LOCK_DATA_SHARE, CURL_LOCK_ACCESS_SINGLE); @@ -668,6 +671,8 @@ CURLcode Curl_open(struct Curl_easy **curl) result = CURLE_OUT_OF_MEMORY; } + Curl_mime_initpart(&data->set.mimepost, data); + data->state.headerbuff = malloc(HEADERSIZE); if(!data->state.headerbuff) { DEBUGF(fprintf(stderr, "Error: malloc of headerbuff failed\n")); @@ -1148,6 +1153,19 @@ CURLcode Curl_setopt(struct Curl_easy *data, CURLoption option, data->set.opt_no_body = FALSE; /* this is implied */ break; + case CURLOPT_MIMEPOST: + /* + * Set to make us do MIME/form POST + */ + result = curl_mime_subparts(&data->set.mimepost, + va_arg(param, curl_mime *)); + if(!result) { + data->set.mimepost.freefunc = NULL; /* Avoid free upon easy cleanup. */ + data->set.httpreq = HTTPREQ_POST_MIME; + data->set.opt_no_body = FALSE; /* this is implied */ + } + break; + case CURLOPT_REFERER: /* * String to set in the HTTP Referer: field. @@ -1875,13 +1893,13 @@ CURLcode Curl_setopt(struct Curl_easy *data, CURLoption option, break; case CURLOPT_RESUME_FROM: /* - * Resume transfer at the give file position + * Resume transfer at the given file position */ data->set.set_resume_from = va_arg(param, long); break; case CURLOPT_RESUME_FROM_LARGE: /* - * Resume transfer at the give file position + * Resume transfer at the given file position */ data->set.set_resume_from = va_arg(param, curl_off_t); break; diff --git a/lib/urldata.h b/lib/urldata.h index ae73ba66a..3f087a588 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -103,6 +103,7 @@ #include "hash.h" #include "splay.h" +#include "mime.h" #include "imap.h" #include "pop3.h" #include "smtp.h" @@ -1140,6 +1141,7 @@ typedef enum { HTTPREQ_GET, HTTPREQ_POST, HTTPREQ_POST_FORM, /* we make a difference internally */ + HTTPREQ_POST_MIME, /* we make a difference internally */ HTTPREQ_PUT, HTTPREQ_HEAD, HTTPREQ_OPTIONS, @@ -1557,7 +1559,8 @@ struct UserDefined { curl_off_t set_resume_from; /* continue [ftp] transfer from here */ struct curl_slist *headers; /* linked list of extra headers */ struct curl_slist *proxyheaders; /* linked list of extra CONNECT headers */ - struct curl_httppost *httppost; /* linked list of POST data */ + struct curl_httppost *httppost; /* linked list of old POST data */ + struct Curl_mimepart mimepost; /* MIME/POST data. */ bool sep_headers; /* handle host and proxy headers separately */ bool cookiesession; /* new cookie session? */ bool crlf; /* convert crlf on ftp upload(?) */ |