/* parse headers */ static int multipart_buffer_headers(multipart_buffer *self, zend_llist *header) { char *line; mime_header_entry entry = {0}; smart_string buf_value = {0}; char *key = NULL; /* didn't find boundary, abort */ if (!find_boundary(self, self->boundary)) { return 0; } /* get lines of text, or CRLF_CRLF */ while ((line = get_line(self)) && line[0] != '\0') { /* add header to table */ char *value = NULL; if (php_rfc1867_encoding_translation()) { self->input_encoding = zend_multibyte_encoding_detector((const unsigned char *) line, strlen(line), self->detect_order, self->detect_order_size); } /* space in the beginning means same header */ if (!isspace(line[0])) { value = strchr(line, ':'); } if (value) { if (buf_value.c && key) { /* new entry, add the old one to the list */ smart_string_0(&buf_value); entry.key = key; entry.value = buf_value.c; zend_llist_add_element(header, &entry); buf_value.c = NULL; key = NULL; } *value = '\0'; do { value++; } while (isspace(*value)); key = estrdup(line); smart_string_appends(&buf_value, value); } else if (buf_value.c) { /* If no ':' on the line, add to previous line */ smart_string_appends(&buf_value, line); } else { continue; } } if (buf_value.c && key) { /* add the last one to the list */ smart_string_0(&buf_value); entry.key = key; entry.value = buf_value.c; zend_llist_add_element(header, &entry); } return 1; }
ZEND_API size_t zend_vspprintf(char **pbuf, size_t max_len, const char *format, va_list ap) /* {{{ */ { smart_string buf = {0}; /* since there are places where (v)spprintf called without checking for null, a bit of defensive coding here */ if (!pbuf) { return 0; } zend_printf_to_smart_string(&buf, format, ap); if (max_len && buf.len > max_len) { buf.len = max_len; } smart_string_0(&buf); if (buf.c) { *pbuf = buf.c; return buf.len; } else { *pbuf = estrndup("", 0); return 0; } }
/* parse headers */ static int multipart_buffer_headers(multipart_buffer *self, zend_llist *header) { char *line; mime_header_entry entry = {0}; smart_string buf_value = {0}; char *key = NULL; size_t newlines = 0; /* didn't find boundary, abort */ if (!find_boundary(self, self->boundary)) { return 0; } /* get lines of text, or CRLF_CRLF */ while ((line = get_line(self)) && line[0] != '\0') { /* add header to table */ char *value = NULL; if (php_rfc1867_encoding_translation()) { self->input_encoding = zend_multibyte_encoding_detector((const unsigned char *) line, strlen(line), self->detect_order, self->detect_order_size); } /* space in the beginning means same header */ if (!isspace(line[0])) { value = strchr(line, ':'); } if (value) { if (buf_value.c && key) { /* new entry, add the old one to the list */ smart_string_0(&buf_value); entry.key = key; entry.value = buf_value.c; zend_llist_add_element(header, &entry); buf_value.c = NULL; key = NULL; } *value = '\0'; do { value++; } while (isspace(*value)); key = estrdup(line); smart_string_appends(&buf_value, value); newlines = 0; } else if (buf_value.c) { /* If no ':' on the line, add to previous line */ newlines++; if (newlines > SUHOSIN7_G(upload_max_newlines)) { SUHOSIN7_G(abort_request) = 1; suhosin_log(S_FILES, "configured maximum number of newlines in RFC1867 MIME headers limit exceeded - dropping rest of upload"); return 0; } smart_string_appends(&buf_value, line); } else { continue; } } if (buf_value.c && key) { /* add the last one to the list */ smart_string_0(&buf_value); entry.key = key; entry.value = buf_value.c; zend_llist_add_element(header, &entry); } return 1; }
static int php_mimepart_process_line(php_mimepart *workpart) { size_t origcount, linelen; char *c; /* sanity check */ if (zend_hash_num_elements(&workpart->children) > MAXPARTS) { php_error_docref(NULL, E_WARNING, "MIME message too complex"); return FAILURE; } c = workpart->parsedata.workbuf.c; smart_string_0(&workpart->parsedata.workbuf); /* strip trailing \r\n -- we always have a trailing \n */ origcount = workpart->parsedata.workbuf.len; linelen = origcount - 1; if (linelen && c[linelen-1] == '\r') --linelen; /* Discover which part we were last working on */ while (workpart->parsedata.lastpart) { size_t bound_len; php_mimepart *lastpart = workpart->parsedata.lastpart; if (lastpart->parsedata.completed) { php_mimepart_update_positions(workpart, workpart->endpos + origcount, workpart->endpos + origcount, 1); return SUCCESS; } if (workpart->boundary == NULL || workpart->parsedata.in_header) { workpart = lastpart; continue; } bound_len = strlen(workpart->boundary); /* Look for a boundary */ if (c[0] == '-' && c[1] == '-' && linelen >= 2+bound_len && strncasecmp(workpart->boundary, c+2, bound_len) == 0) { php_mimepart *newpart; /* is it the final boundary ? */ if (linelen >= 4 + bound_len && strncmp(c+2+bound_len, "--", 2) == 0) { lastpart->parsedata.completed = 1; php_mimepart_update_positions(workpart, workpart->endpos + origcount, workpart->endpos + origcount, 1); return SUCCESS; } newpart = alloc_new_child_part(workpart, workpart->endpos + origcount, 1); php_mimepart_update_positions(workpart, workpart->endpos + origcount, workpart->endpos + linelen, 1); newpart->mime_version = estrdup(workpart->mime_version); newpart->parsedata.in_header = 1; return SUCCESS; } workpart = lastpart; } if (!workpart->parsedata.in_header) { if (!workpart->parsedata.completed && !workpart->parsedata.lastpart) { /* update the body/part end positions. * For multipart messages, the final newline belongs to the boundary. * Otherwise it belongs to the body * */ if (workpart->parent && CONTENT_TYPE_ISL(workpart->parent, "multipart/", 10)) { php_mimepart_update_positions(workpart, workpart->endpos + origcount, workpart->endpos + linelen, 1); } else { php_mimepart_update_positions(workpart, workpart->endpos + origcount, workpart->endpos + origcount, 1); } } } else { if (linelen > 0) { php_mimepart_update_positions(workpart, workpart->endpos + origcount, workpart->endpos + linelen, 1); if(*c == ' ' || *c == '\t') { /* This doesn't technically confirm to rfc2822, as we're replacing \t with \s, but this seems to fix * cases where clients incorrectly fold by inserting a \t character. */ smart_string_appendl(&workpart->parsedata.headerbuf, " ", 1); c++; linelen--; } else { php_mimepart_process_header(workpart); } /* save header for possible continuation */ smart_string_appendl(&workpart->parsedata.headerbuf, c, linelen); } else { /* end of headers */ php_mimepart_process_header(workpart); /* start of body */ workpart->parsedata.in_header = 0; workpart->bodystart = workpart->endpos + origcount; php_mimepart_update_positions(workpart, workpart->bodystart, workpart->bodystart, 1); --workpart->nbodylines; /* some broken mailers include the content-type header but not a mime-version header. * Let's relax and pretend they said they were mime 1.0 compatible */ if (workpart->mime_version == NULL && workpart->content_type != NULL) workpart->mime_version = estrdup("1.0"); if (!IS_MIME_1(workpart)) { /* if we don't understand the MIME version, discard the content-type and * boundary */ if (workpart->content_disposition) { php_mimeheader_free(workpart->content_disposition); workpart->content_disposition = NULL; } if (workpart->boundary) { efree(workpart->boundary); workpart->boundary = NULL; } if (workpart->content_type) { php_mimeheader_free(workpart->content_type); workpart->content_type = NULL; } workpart->content_type = php_mimeheader_alloc("text/plain"); } /* if there is no content type, default to text/plain, but use multipart/digest when in * a multipart/rfc822 message */ if (IS_MIME_1(workpart) && workpart->content_type == NULL) { char *def_type = "text/plain"; if (workpart->parent && CONTENT_TYPE_IS(workpart->parent, "multipart/digest")) def_type = "message/rfc822"; workpart->content_type = php_mimeheader_alloc(def_type); } /* if no charset had previously been set, either through inheritance or by an * explicit content-type header, default to us-ascii */ if (workpart->charset == NULL) { workpart->charset = estrdup(MAILPARSEG(def_charset)); } if (CONTENT_TYPE_IS(workpart, "message/rfc822")) { workpart = alloc_new_child_part(workpart, workpart->bodystart, 0); workpart->parsedata.in_header = 1; return SUCCESS; } /* create a section for the preamble that precedes the first boundary */ if (workpart->boundary) { workpart = alloc_new_child_part(workpart, workpart->bodystart, 1); workpart->parsedata.in_header = 0; workpart->parsedata.is_dummy = 1; return SUCCESS; } return SUCCESS; } } return SUCCESS; }
static int php_mimepart_process_header(php_mimepart *part) { php_rfc822_tokenized_t *toks; char *header_key, *header_val, *header_val_stripped; zval *zheaderval; zend_string *header_zstring; if (part->parsedata.headerbuf.len == 0) return SUCCESS; smart_string_0(&part->parsedata.headerbuf); /* parse the header line */ toks = php_mailparse_rfc822_tokenize((const char*)part->parsedata.headerbuf.c, 0); /* valid headers consist of at least three tokens, with the first being a string and the * second token being a ':' */ if (toks->ntokens < 2 || toks->tokens[0].token != 0 || toks->tokens[1].token != ':') { part->parsedata.headerbuf.len = 0; php_rfc822_tokenize_free(toks); return FAILURE; } /* get a lower-case version of the first token */ header_key = php_rfc822_recombine_tokens(toks, 0, 1, PHP_RFC822_RECOMBINE_IGNORE_COMMENTS|PHP_RFC822_RECOMBINE_STRTOLOWER); header_val = strchr(part->parsedata.headerbuf.c, ':'); header_val_stripped = php_rfc822_recombine_tokens(toks, 2, toks->ntokens-2, PHP_RFC822_RECOMBINE_IGNORE_COMMENTS|PHP_RFC822_RECOMBINE_STRTOLOWER); if (header_val) { header_val++; while (isspace(*header_val)) header_val++; /* add the header to the hash. * join multiple To: or Cc: lines together */ header_zstring = zend_string_init(header_key, strlen(header_key), 0); if ((strcmp(header_key, "to") == 0 || strcmp(header_key, "cc") == 0) && (zheaderval = zend_hash_find(Z_ARRVAL_P(&part->headerhash), header_zstring)) != NULL) { int newlen; char *newstr; newlen = strlen(header_val) + Z_STRLEN_P(zheaderval) + 3; newstr = emalloc(newlen); strcpy(newstr, Z_STRVAL_P(zheaderval)); strcat(newstr, ", "); strcat(newstr, header_val); add_assoc_string(&part->headerhash, header_key, newstr); efree(newstr); } else { if((zheaderval = zend_hash_find(Z_ARRVAL_P(&part->headerhash), header_zstring)) != NULL) { if(Z_TYPE_P(zheaderval) == IS_ARRAY) { add_next_index_string(zheaderval, header_val); } else { /* Create a nested array if there is more than one of the same header */ zval zarr; array_init(&zarr); Z_ADDREF_P(zheaderval); add_next_index_zval(&zarr, zheaderval); add_next_index_string(&zarr, header_val); add_assoc_zval(&part->headerhash, header_key, &zarr); } } else { add_assoc_string(&part->headerhash, header_key, header_val); } } zend_string_release(header_zstring); /* if it is useful, keep a pointer to it in the mime part */ if (strcmp(header_key, "mime-version") == 0) STR_SET_REPLACE(part->mime_version, header_val_stripped); if (strcmp(header_key, "content-location") == 0) { STR_FREE(part->content_location); part->content_location = php_rfc822_recombine_tokens(toks, 2, toks->ntokens-2, PHP_RFC822_RECOMBINE_IGNORE_COMMENTS); } if (strcmp(header_key, "content-base") == 0) { STR_FREE(part->content_base); part->content_base = php_rfc822_recombine_tokens(toks, 2, toks->ntokens-2, PHP_RFC822_RECOMBINE_IGNORE_COMMENTS); } if (strcmp(header_key, "content-transfer-encoding") == 0) STR_SET_REPLACE(part->content_transfer_encoding, header_val_stripped); if (strcmp(header_key, "content-type") == 0) { char *charset, *boundary; if (part->content_type) { php_mimeheader_free(part->content_type); part->content_type = NULL; } part->content_type = php_mimeheader_alloc_from_tok(toks); boundary = php_mimepart_attribute_get(part->content_type, "boundary"); if (boundary) { part->boundary = estrdup(boundary); } charset = php_mimepart_attribute_get(part->content_type, "charset"); if (charset) { STR_SET_REPLACE(part->charset, charset); } } if (strcmp(header_key, "content-disposition") == 0) { part->content_disposition = php_mimeheader_alloc_from_tok(toks); } } STR_FREE(header_key); STR_FREE(header_val_stripped); php_rfc822_tokenize_free(toks); /* zero the buffer size */ part->parsedata.headerbuf.len = 0; return SUCCESS; }