2 * ModSecurity for Apache 2.x, http://www.modsecurity.org/
3 * Copyright (c) 2004-2009 Breach Security, Inc. (http://www.breach.com/)
5 * This product is released under the terms of the General Public Licence,
6 * version 2 (GPLv2). Please refer to the file LICENSE (included with this
7 * distribution) which contains the complete text of the licence.
9 * There are special exceptions to the terms and conditions of the GPL
10 * as it is applied to this software. View the full text of the exception in
11 * file MODSECURITY_LICENSING_EXCEPTION in the directory of this software
14 * If any of the files related to licensing are missing or if you have any
15 * other questions related to licensing please contact Breach Security, Inc.
16 * directly using the email address support@breach.com.
22 #include "mod_security2_config.h"
23 #include "msc_multipart.h"
25 #include "msc_parsers.h"
29 static char *multipart_construct_filename(modsec_rec *msr) {
30 char c, *p, *q = msr->mpd->mpp->filename;
33 /* find the last backward slash and consider the
34 * filename to be only what's right from it
37 if (p != NULL) q = p + 1;
39 /* do the same for the forward slash */
41 if (p != NULL) q = p + 1;
43 /* allow letters, digits and dots, replace
44 * everything else with underscores
46 p = filename = apr_pstrdup(msr->mp, q);
47 while((c = *p) != 0) {
48 if (!( isalnum(c) || (c == '.') )) *p = '_';
59 static int multipart_parse_content_disposition(modsec_rec *msr, char *c_d_value) {
60 char *p = NULL, *t = NULL;
62 /* accept only what we understand */
63 if (strncmp(c_d_value, "form-data", 9) != 0) {
67 /* see if there are any other parts to parse */
70 while((*p == '\t') || (*p == ' ')) p++;
71 if (*p == '\0') return 1; /* this is OK */
73 if (*p != ';') return -2;
76 /* parse the appended parts */
79 char *name = NULL, *value = NULL, *start = NULL;
81 /* go over the whitespace */
82 while((*p == '\t') || (*p == ' ')) p++;
83 if (*p == '\0') return -3;
86 while((*p != '\0') && (*p != '=') && (*p != '\t') && (*p != ' ')) p++;
87 if (*p == '\0') return -4;
89 name = apr_pstrmemdup(msr->mp, start, (p - start));
91 while((*p == '\t') || (*p == ' ')) p++;
92 if (*p == '\0') return -5;
94 if (*p != '=') return -13;
97 while((*p == '\t') || (*p == ' ')) p++;
98 if (*p == '\0') return -6;
100 /* Accept both quotes as some backends will accept them, but
101 * technically "'" is invalid and so flag_invalid_quoting is
102 * set so the user can deal with it in the rules if they so wish.
104 if ((*p == '"') || (*p == '\'')) {
109 msr->mpd->flag_invalid_quoting = 1;
113 if (*p == '\0') return -7;
116 value = apr_pstrdup(msr->mp, p);
121 if (*(p + 1) == '\0') {
122 /* improper escaping */
125 /* only quote and \ can be escaped */
126 if ((*(p + 1) == quote) || (*(p + 1) == '\\')) {
130 /* improper escaping */
132 /* We allow for now because IE sends
133 * improperly escaped content and there's
134 * nothing we can do about it.
140 else if (*p == quote) {
147 if (*p == '\0') return -10;
149 p++; /* go over the quote at the end */
155 while((*p != '\0') && (is_token_char(*p))) p++;
156 value = apr_pstrmemdup(msr->mp, start, (p - start));
161 if (strcmp(name, "name") == 0) {
162 if (msr->mpd->mpp->name != NULL) {
163 msr_log(msr, 4, "Multipart: Warning: Duplicate Content-Disposition name: %s",
164 log_escape_nq(msr->mp, value));
167 msr->mpd->mpp->name = value;
169 if (msr->txcfg->debuglog_level >= 9) {
170 msr_log(msr, 9, "Multipart: Content-Disposition name: %s",
171 log_escape_nq(msr->mp, value));
175 if (strcmp(name, "filename") == 0) {
176 if (msr->mpd->mpp->filename != NULL) {
177 msr_log(msr, 4, "Multipart: Warning: Duplicate Content-Disposition filename: %s",
178 log_escape_nq(msr->mp, value));
181 msr->mpd->mpp->filename = value;
183 if (msr->txcfg->debuglog_level >= 9) {
184 msr_log(msr, 9, "Multipart: Content-Disposition filename: %s",
185 log_escape_nq(msr->mp, value));
191 while((*p == '\t') || (*p == ' ')) p++;
192 /* the next character must be a zero or a semi-colon */
193 if (*p == '\0') return 1; /* this is OK */
194 if (*p != ';') return -12;
195 p++; /* move over the semi-colon */
198 /* loop will stop when (*p == '\0') */
207 static int multipart_process_part_header(modsec_rec *msr, char **error_msg) {
210 if (error_msg == NULL) return -1;
213 /* Check for nul bytes. */
214 len = MULTIPART_BUF_SIZE - msr->mpd->bufleft;
215 for(i = 0; i < len; i++) {
216 if (msr->mpd->buf[i] == '\0') {
217 *error_msg = apr_psprintf(msr->mp, "Multipart: Nul byte in part headers.");
222 /* The buffer is data so increase the data length counter. */
223 msr->msc_reqbody_no_files_length += (MULTIPART_BUF_SIZE - msr->mpd->bufleft);
226 if (msr->mpd->buf[len - 2] == '\r') {
227 msr->mpd->flag_crlf_line = 1;
229 msr->mpd->flag_lf_line = 1;
232 msr->mpd->flag_lf_line = 1;
235 /* Is this an empty line? */
236 if ( ((msr->mpd->buf[0] == '\r')
237 &&(msr->mpd->buf[1] == '\n')
238 &&(msr->mpd->buf[2] == '\0') )
239 || ((msr->mpd->buf[0] == '\n')
240 &&(msr->mpd->buf[1] == '\0') ) )
242 char *header_value = NULL;
244 header_value = (char *)apr_table_get(msr->mpd->mpp->headers, "Content-Disposition");
245 if (header_value == NULL) {
246 *error_msg = apr_psprintf(msr->mp, "Multipart: Part missing Content-Disposition header.");
250 rc = multipart_parse_content_disposition(msr, header_value);
252 *error_msg = apr_psprintf(msr->mp, "Multipart: Invalid Content-Disposition header (%d): %s.",
253 rc, log_escape_nq(msr->mp, header_value));
257 if (msr->mpd->mpp->name == NULL) {
258 *error_msg = apr_psprintf(msr->mp, "Multipart: Content-Disposition header missing name field.");
262 if (msr->mpd->mpp->filename != NULL) {
263 /* Some parsers use crude methods to extract the name and filename
264 * values from the C-D header. We need to check for the case where they
265 * didn't understand C-D but we did.
267 if (strstr(header_value, "filename=") == NULL) {
268 *error_msg = apr_psprintf(msr->mp, "Multipart: Invalid Content-Disposition header (filename).");
272 msr->mpd->mpp->type = MULTIPART_FILE;
274 msr->mpd->mpp->type = MULTIPART_FORMDATA;
277 msr->mpd->mpp_state = 1;
278 msr->mpd->mpp->last_header_name = NULL;
282 if ((msr->mpd->buf[0] == '\t') || (msr->mpd->buf[0] == ' ')) {
283 char *header_value, *new_value, *data;
285 /* header folding, add data to the header we are building */
286 msr->mpd->flag_header_folding = 1;
288 if (msr->mpd->mpp->last_header_name == NULL) {
289 /* we are not building a header at this moment */
290 *error_msg = apr_psprintf(msr->mp, "Multipart: Invalid part header (folding error).");
294 /* locate the beginning of data */
295 data = msr->mpd->buf;
296 while((*data == '\t') || (*data == ' ')) data++;
298 new_value = apr_pstrdup(msr->mp, data);
299 remove_lf_crlf_inplace(new_value);
301 /* update the header value in the table */
302 header_value = (char *)apr_table_get(msr->mpd->mpp->headers, msr->mpd->mpp->last_header_name);
303 new_value = apr_pstrcat(msr->mp, header_value, " ", new_value, NULL);
304 apr_table_set(msr->mpd->mpp->headers, msr->mpd->mpp->last_header_name, new_value);
306 if (msr->txcfg->debuglog_level >= 9) {
307 msr_log(msr, 9, "Multipart: Continued folder header \"%s\" with \"%s\"",
308 log_escape(msr->mp, msr->mpd->mpp->last_header_name),
309 log_escape(msr->mp, data));
312 if (strlen(new_value) > MULTIPART_BUF_SIZE) {
313 *error_msg = apr_psprintf(msr->mp, "Multipart: Part header too long.");
317 char *header_name, *header_value, *data;
321 data = msr->mpd->buf;
322 while((*data != ':') && (*data != '\0')) data++;
324 *error_msg = apr_psprintf(msr->mp, "Multipart: Invalid part header (colon missing): %s.",
325 log_escape_nq(msr->mp, msr->mpd->buf));
329 /* extract header name */
330 header_name = apr_pstrmemdup(msr->mp, msr->mpd->buf, (data - msr->mpd->buf));
331 if (data == msr->mpd->buf) {
332 *error_msg = apr_psprintf(msr->mp, "Multipart: Invalid part header (header name missing).");
337 /* extract the value value */
339 while((*data == '\t') || (*data == ' ')) data++;
340 header_value = apr_pstrdup(msr->mp, data);
341 remove_lf_crlf_inplace(header_value);
343 /* error if the name already exists */
344 if (apr_table_get(msr->mpd->mpp->headers, header_name) != NULL) {
345 *error_msg = apr_psprintf(msr->mp, "Multipart: Duplicate part header: %s.",
346 log_escape_nq(msr->mp, header_name));
350 apr_table_setn(msr->mpd->mpp->headers, header_name, header_value);
351 msr->mpd->mpp->last_header_name = header_name;
353 if (msr->txcfg->debuglog_level >= 9) {
354 msr_log(msr, 9, "Multipart: Added part header \"%s\" \"%s\"",
355 log_escape(msr->mp, header_name),
356 log_escape(msr->mp, header_value));
367 static int multipart_process_part_data(modsec_rec *msr, char **error_msg) {
368 char *p = msr->mpd->buf + (MULTIPART_BUF_SIZE - msr->mpd->bufleft);
369 char localreserve[2] = { '\0', '\0' }; /* initialized to quiet warning */
370 int bytes_reserved = 0;
372 if (error_msg == NULL) return -1;
375 /* Preserve some bytes for later. */
376 if ( ((MULTIPART_BUF_SIZE - msr->mpd->bufleft) >= 1)
377 && (*(p - 1) == '\n') )
379 if ( ((MULTIPART_BUF_SIZE - msr->mpd->bufleft) >= 2)
380 && (*(p - 2) == '\r') )
384 localreserve[0] = *(p - 2);
385 localreserve[1] = *(p - 1);
386 msr->mpd->bufleft += 2;
391 localreserve[0] = *(p - 1);
393 msr->mpd->bufleft += 1;
398 /* add data to the part we are building */
399 if (msr->mpd->mpp->type == MULTIPART_FILE) {
401 /* remember where we started */
402 if (msr->mpd->mpp->length == 0) {
403 msr->mpd->mpp->offset = msr->mpd->buf_offset;
406 /* only store individual files on disk if we are going
407 * to keep them or if we need to have them approved later
409 if (msr->upload_extract_files) {
410 /* first create a temporary file if we don't have it already */
411 if (msr->mpd->mpp->tmp_file_fd == 0) {
412 /* construct temporary file name */
413 msr->mpd->mpp->tmp_file_name = apr_psprintf(msr->mp, "%s/%s-%s-file-XXXXXX",
414 msr->txcfg->tmp_dir, current_filetime(msr->mp), msr->txid);
415 msr->mpd->mpp->tmp_file_fd = msc_mkstemp_ex(msr->mpd->mpp->tmp_file_name, msr->txcfg->upload_filemode);
417 /* do we have an opened file? */
418 if (msr->mpd->mpp->tmp_file_fd < 0) {
419 *error_msg = apr_psprintf(msr->mp, "Multipart: Failed to create file: %s",
420 log_escape_nq(msr->mp, msr->mpd->mpp->tmp_file_name));
424 if (msr->txcfg->debuglog_level >= 4) {
425 msr_log(msr, 4, "Multipart: Created temporary file: %s",
426 log_escape_nq(msr->mp, msr->mpd->mpp->tmp_file_name));
430 /* write the reserve first */
431 if (msr->mpd->reserve[0] != 0) {
432 if (write(msr->mpd->mpp->tmp_file_fd, &msr->mpd->reserve[1], msr->mpd->reserve[0]) != msr->mpd->reserve[0]) {
433 *error_msg = apr_psprintf(msr->mp, "Multipart: writing to \"%s\" failed",
434 log_escape(msr->mp, msr->mpd->mpp->tmp_file_name));
438 msr->mpd->mpp->tmp_file_size += msr->mpd->reserve[0];
439 msr->mpd->mpp->length += msr->mpd->reserve[0];
442 /* write data to the file */
443 if (write(msr->mpd->mpp->tmp_file_fd, msr->mpd->buf, MULTIPART_BUF_SIZE - msr->mpd->bufleft)
444 != (MULTIPART_BUF_SIZE - msr->mpd->bufleft))
446 *error_msg = apr_psprintf(msr->mp, "Multipart: writing to \"%s\" failed",
447 log_escape(msr->mp, msr->mpd->mpp->tmp_file_name));
451 msr->mpd->mpp->tmp_file_size += (MULTIPART_BUF_SIZE - msr->mpd->bufleft);
452 msr->mpd->mpp->length += (MULTIPART_BUF_SIZE - msr->mpd->bufleft);
454 /* just keep track of the file size */
455 msr->mpd->mpp->tmp_file_size += (MULTIPART_BUF_SIZE - msr->mpd->bufleft) + msr->mpd->reserve[0];
456 msr->mpd->mpp->length += (MULTIPART_BUF_SIZE - msr->mpd->bufleft) + msr->mpd->reserve[0];
459 else if (msr->mpd->mpp->type == MULTIPART_FORMDATA) {
460 value_part_t *value_part = apr_pcalloc(msr->mp, sizeof(value_part_t));
462 /* The buffer contains data so increase the data length counter. */
463 msr->msc_reqbody_no_files_length += (MULTIPART_BUF_SIZE - msr->mpd->bufleft) + msr->mpd->reserve[0];
465 /* add this part to the list of parts */
467 /* remember where we started */
468 if (msr->mpd->mpp->length == 0) {
469 msr->mpd->mpp->offset = msr->mpd->buf_offset;
472 if (msr->mpd->reserve[0] != 0) {
473 value_part->data = apr_palloc(msr->mp, (MULTIPART_BUF_SIZE - msr->mpd->bufleft) + msr->mpd->reserve[0]);
474 memcpy(value_part->data, &(msr->mpd->reserve[1]), msr->mpd->reserve[0]);
475 memcpy(value_part->data + msr->mpd->reserve[0], msr->mpd->buf, (MULTIPART_BUF_SIZE - msr->mpd->bufleft));
477 value_part->length = (MULTIPART_BUF_SIZE - msr->mpd->bufleft) + msr->mpd->reserve[0];
478 msr->mpd->mpp->length += value_part->length;
480 value_part->length = (MULTIPART_BUF_SIZE - msr->mpd->bufleft);
481 value_part->data = apr_pstrmemdup(msr->mp, msr->mpd->buf, value_part->length);
482 msr->mpd->mpp->length += value_part->length;
485 *(value_part_t **)apr_array_push(msr->mpd->mpp->value_parts) = value_part;
487 if (msr->txcfg->debuglog_level >= 9) {
488 msr_log(msr, 9, "Multipart: Added data to variable: %s",
489 log_escape_nq_ex(msr->mp, value_part->data, value_part->length));
493 *error_msg = apr_psprintf(msr->mp, "Multipart: unknown part type %d", msr->mpd->mpp->type);
497 /* store the reserved bytes to the multipart
498 * context so that they don't get lost
500 if (bytes_reserved) {
501 msr->mpd->reserve[0] = bytes_reserved;
502 msr->mpd->reserve[1] = localreserve[0];
503 msr->mpd->reserve[2] = localreserve[1];
504 msr->mpd->buf_offset += bytes_reserved;
507 msr->mpd->buf_offset -= msr->mpd->reserve[0];
508 msr->mpd->reserve[0] = 0;
517 static char *multipart_combine_value_parts(modsec_rec *msr, apr_array_header_t *value_parts) {
518 value_part_t **parts = NULL;
519 char *rval = apr_palloc(msr->mp, msr->mpd->mpp->length + 1);
520 unsigned long int offset;
523 if (rval == NULL) return NULL;
526 parts = (value_part_t **)value_parts->elts;
527 for(i = 0; i < value_parts->nelts; i++) {
528 if (offset + parts[i]->length <= msr->mpd->mpp->length) {
529 memcpy(rval + offset, parts[i]->data, parts[i]->length);
530 offset += parts[i]->length;
541 static int multipart_process_boundary(modsec_rec *msr, int last_part, char **error_log) {
542 /* if there was a part being built finish it */
543 if (msr->mpd->mpp != NULL) {
544 /* close the temp file */
545 if ((msr->mpd->mpp->type == MULTIPART_FILE)
546 &&(msr->mpd->mpp->tmp_file_name != NULL)
547 &&(msr->mpd->mpp->tmp_file_fd != 0))
549 close(msr->mpd->mpp->tmp_file_fd);
552 if (msr->mpd->mpp->type != MULTIPART_FILE) {
553 /* now construct a single string out of the parts */
554 msr->mpd->mpp->value = multipart_combine_value_parts(msr, msr->mpd->mpp->value_parts);
555 if (msr->mpd->mpp->value == NULL) return -1;
558 if (msr->mpd->mpp->name) {
559 /* add the part to the list of parts */
560 *(multipart_part **)apr_array_push(msr->mpd->parts) = msr->mpd->mpp;
561 if (msr->mpd->mpp->type == MULTIPART_FILE) {
562 if (msr->txcfg->debuglog_level >= 9) {
563 msr_log(msr, 9, "Multipart: Added file part %pp to the list: name \"%s\" "
564 "file name \"%s\" (offset %u, length %u)",
565 msr->mpd->mpp, log_escape(msr->mp, msr->mpd->mpp->name),
566 log_escape(msr->mp, msr->mpd->mpp->filename),
567 msr->mpd->mpp->offset, msr->mpd->mpp->length);
571 if (msr->txcfg->debuglog_level >= 9) {
572 msr_log(msr, 9, "Multipart: Added part %pp to the list: name \"%s\" "
573 "(offset %u, length %u)", msr->mpd->mpp, log_escape(msr->mp, msr->mpd->mpp->name),
574 msr->mpd->mpp->offset, msr->mpd->mpp->length);
579 msr_log(msr, 3, "Multipart: Skipping invalid part %pp (part name missing): "
580 "(offset %u, length %u)", msr->mpd->mpp,
581 msr->mpd->mpp->offset, msr->mpd->mpp->length);
584 msr->mpd->mpp = NULL;
587 if (last_part == 0) {
588 /* start building a new part */
589 msr->mpd->mpp = (multipart_part *)apr_pcalloc(msr->mp, sizeof(multipart_part));
590 if (msr->mpd->mpp == NULL) return -1;
591 msr->mpd->mpp->type = MULTIPART_FORMDATA;
592 msr->mpd->mpp_state = 0;
594 msr->mpd->mpp->headers = apr_table_make(msr->mp, 10);
595 if (msr->mpd->mpp->headers == NULL) return -1;
596 msr->mpd->mpp->last_header_name = NULL;
598 msr->mpd->reserve[0] = 0;
599 msr->mpd->reserve[1] = 0;
600 msr->mpd->reserve[2] = 0;
601 msr->mpd->reserve[3] = 0;
603 msr->mpd->mpp->value_parts = apr_array_make(msr->mp, 10, sizeof(value_part_t *));
609 static int multipart_boundary_characters_valid(char *boundary) {
610 unsigned char *p = (unsigned char *)boundary;
613 if (p == NULL) return -1;
615 while((c = *p) != '\0') {
616 /* Control characters and space not allowed. */
621 /* Non-ASCII characters not allowed. */
627 /* Special characters not allowed. */
657 static int multipart_count_boundary_params(apr_pool_t *mp, const char *header_value) {
658 char *duplicate = NULL;
662 if (header_value == NULL) return -1;
663 duplicate = apr_pstrdup(mp, header_value);
664 if (duplicate == NULL) return -1;
666 /* Performing a case-insensitive search. */
667 strtolower_inplace((unsigned char *)duplicate);
670 while((s = strstr(s, "boundary")) != NULL) {
673 if (strchr(s, '=') != NULL) {
684 int multipart_init(modsec_rec *msr, char **error_msg) {
685 if (error_msg == NULL) return -1;
688 msr->mpd = (multipart_data *)apr_pcalloc(msr->mp, sizeof(multipart_data));
689 if (msr->mpd == NULL) return -1;
691 msr->mpd->parts = apr_array_make(msr->mp, 10, sizeof(multipart_part *));
692 msr->mpd->bufleft = MULTIPART_BUF_SIZE;
693 msr->mpd->bufptr = msr->mpd->buf;
694 msr->mpd->buf_contains_line = 1;
695 msr->mpd->mpp = NULL;
697 if (msr->request_content_type == NULL) {
698 msr->mpd->flag_error = 1;
699 *error_msg = apr_psprintf(msr->mp, "Multipart: Content-Type header not available.");
703 if (strlen(msr->request_content_type) > 1024) {
704 msr->mpd->flag_error = 1;
705 *error_msg = apr_psprintf(msr->mp, "Multipart: Invalid boundary in C-T (length).");
709 if (strncasecmp(msr->request_content_type, "multipart/form-data", 19) != 0) {
710 msr->mpd->flag_error = 1;
711 *error_msg = apr_psprintf(msr->mp, "Multipart: Invalid MIME type.");
715 /* Count how many times the word "boundary" appears in the C-T header. */
716 if (multipart_count_boundary_params(msr->mp, msr->request_content_type) > 1) {
717 msr->mpd->flag_error = 1;
718 *error_msg = apr_psprintf(msr->mp, "Multipart: Multiple boundary parameters in C-T.");
722 msr->mpd->boundary = strstr(msr->request_content_type, "boundary");
723 if (msr->mpd->boundary != NULL) {
726 int seen_semicolon = 0;
729 /* Check for extra characters before the boundary. */
730 for (p = (char *)(msr->request_content_type + 19); p < msr->mpd->boundary; p++) {
732 if ((seen_semicolon == 0) && (*p == ';')) {
733 seen_semicolon = 1; /* It is OK to have one semicolon. */
735 msr->mpd->flag_error = 1;
736 *error_msg = apr_psprintf(msr->mp, "Multipart: Invalid boundary in C-T (malformed).");
742 /* Have we seen the semicolon in the header? */
743 if (seen_semicolon == 0) {
744 msr->mpd->flag_missing_semicolon = 1;
747 b = strchr(msr->mpd->boundary + 8, '=');
749 msr->mpd->flag_error = 1;
750 *error_msg = apr_psprintf(msr->mp, "Multipart: Invalid boundary in C-T (malformed).");
754 /* Check parameter name ends well. */
755 if (b != (msr->mpd->boundary + 8)) {
756 /* Check all characters between the end of the boundary
757 * and the = character.
759 for (p = msr->mpd->boundary + 8; p < b; p++) {
761 /* Flag for whitespace after parameter name. */
762 msr->mpd->flag_boundary_whitespace = 1;
764 msr->mpd->flag_error = 1;
765 *error_msg = apr_psprintf(msr->mp, "Multipart: Invalid boundary in C-T (parameter name).");
771 b++; /* Go over the = character. */
774 /* Flag for whitespace before parameter value. */
776 msr->mpd->flag_boundary_whitespace = 1;
779 /* Is the boundary quoted? */
780 if ((len >= 2) && (*b == '"') && (*(b + len - 1) == '"')) {
782 msr->mpd->boundary = apr_pstrndup(msr->mp, b + 1, len - 2);
783 if (msr->mpd->boundary == NULL) return -1;
784 msr->mpd->flag_boundary_quoted = 1;
788 /* Test for partial quoting. */
790 || ((len >= 2) && (*(b + len - 1) == '"')) )
792 msr->mpd->flag_error = 1;
793 *error_msg = apr_psprintf(msr->mp, "Multipart: Invalid boundary in C-T (quote).");
797 msr->mpd->boundary = apr_pstrdup(msr->mp, b);
798 if (msr->mpd->boundary == NULL) return -1;
799 msr->mpd->flag_boundary_quoted = 0;
802 /* Case-insensitive test for the string "boundary" in the boundary. */
803 if (multipart_count_boundary_params(msr->mp, msr->mpd->boundary) != 0) {
804 msr->mpd->flag_error = 1;
805 *error_msg = apr_psprintf(msr->mp, "Multipart: Invalid boundary in C-T (content).");
809 /* Validate the characters used in the boundary. */
810 if (multipart_boundary_characters_valid(msr->mpd->boundary) != 1) {
811 msr->mpd->flag_error = 1;
812 *error_msg = apr_psprintf(msr->mp, "Multipart: Invalid boundary in C-T (characters).");
816 if (msr->txcfg->debuglog_level >= 9) {
817 msr_log(msr, 9, "Multipart: Boundary%s: %s",
818 (msr->mpd->flag_boundary_quoted ? " (quoted)" : ""),
819 log_escape_nq(msr->mp, msr->mpd->boundary));
822 if (strlen(msr->mpd->boundary) == 0) {
823 msr->mpd->flag_error = 1;
824 *error_msg = apr_psprintf(msr->mp, "Multipart: Invalid boundary in C-T (empty).");
828 else { /* Could not find boundary in the C-T header. */
829 msr->mpd->flag_error = 1;
831 /* Test for case-insensitive boundary. Allowed by the RFC but highly unusual. */
832 if (multipart_count_boundary_params(msr->mp, msr->request_content_type) > 0) {
833 *error_msg = apr_psprintf(msr->mp, "Multipart: Invalid boundary in C-T (case sensitivity).");
837 *error_msg = apr_psprintf(msr->mp, "Multipart: Boundary not found in C-T.");
845 * Finalise multipart processing. This method is invoked at the end, when it
846 * is clear that there is no more data to be processed.
848 int multipart_complete(modsec_rec *msr, char **error_msg) {
849 if (msr->mpd == NULL) return 1;
851 if (msr->txcfg->debuglog_level >= 4) {
852 if (msr->mpd->flag_data_before) {
853 msr_log(msr, 4, "Multipart: Warning: seen data before first boundary.");
856 if (msr->mpd->flag_data_after) {
857 msr_log(msr, 4, "Multipart: Warning: seen data after last boundary.");
860 if (msr->mpd->flag_boundary_quoted) {
861 msr_log(msr, 4, "Multipart: Warning: boundary was quoted.");
864 if (msr->mpd->flag_boundary_whitespace) {
865 msr_log(msr, 4, "Multipart: Warning: boundary whitespace in C-T header.");
868 if (msr->mpd->flag_header_folding) {
869 msr_log(msr, 4, "Multipart: Warning: header folding used.");
872 if (msr->mpd->flag_crlf_line && msr->mpd->flag_lf_line) {
873 msr_log(msr, 4, "Multipart: Warning: mixed line endings used (CRLF/LF).");
875 else if (msr->mpd->flag_lf_line) {
876 msr_log(msr, 4, "Multipart: Warning: incorrect line endings used (LF).");
879 if (msr->mpd->flag_missing_semicolon) {
880 msr_log(msr, 4, "Multipart: Warning: missing semicolon in C-T header.");
884 if ((msr->mpd->seen_data != 0) && (msr->mpd->is_complete == 0)) {
885 if (msr->mpd->boundary_count > 0) {
886 /* Check if we have the final boundary (that we haven't
887 * processed yet) in the buffer.
889 if (msr->mpd->buf_contains_line) {
890 if ( ((unsigned int)(MULTIPART_BUF_SIZE - msr->mpd->bufleft) == (4 + strlen(msr->mpd->boundary)))
891 && (*(msr->mpd->buf) == '-')
892 && (*(msr->mpd->buf + 1) == '-')
893 && (strncmp(msr->mpd->buf + 2, msr->mpd->boundary, strlen(msr->mpd->boundary)) == 0)
894 && (*(msr->mpd->buf + 2 + strlen(msr->mpd->boundary)) == '-')
895 && (*(msr->mpd->buf + 2 + strlen(msr->mpd->boundary) + 1) == '-') )
897 /* Looks like the final boundary - process it. */
898 if (multipart_process_boundary(msr, 1 /* final */, error_msg) < 0) {
899 msr->mpd->flag_error = 1;
903 /* The payload is complete after all. */
904 msr->mpd->is_complete = 1;
908 if (msr->mpd->is_complete == 0) {
909 *error_msg = apr_psprintf(msr->mp, "Multipart: Final boundary missing.");
913 *error_msg = apr_psprintf(msr->mp, "Multipart: No boundaries found in payload.");
924 int multipart_process_chunk(modsec_rec *msr, const char *buf,
925 unsigned int size, char **error_msg)
927 char *inptr = (char *)buf;
928 unsigned int inleft = size;
930 if (error_msg == NULL) return -1;
933 if (size == 0) return 1;
935 msr->mpd->seen_data = 1;
937 if (msr->mpd->is_complete) {
938 msr->mpd->flag_data_before = 1;
940 if (msr->txcfg->debuglog_level >= 4) {
941 msr_log(msr, 4, "Multipart: Ignoring data after last boundary (received %u bytes)", size);
947 if (msr->mpd->bufleft == 0) {
948 msr->mpd->flag_error = 1;
949 *error_msg = apr_psprintf(msr->mp,
950 "Multipart: Internal error in process_chunk: no space left in the buffer");
954 /* here we loop through the available data, one byte at a time */
957 int process_buffer = 0;
959 if ((c == '\r') && (msr->mpd->bufleft == 1)) {
960 /* we don't want to take \r as the last byte in the buffer */
966 *(msr->mpd->bufptr) = c;
971 /* until we either reach the end of the line
972 * or the end of our internal buffer
974 if ((c == '\n') || (msr->mpd->bufleft == 0) || (process_buffer)) {
975 int processed_as_boundary = 0;
977 *(msr->mpd->bufptr) = 0;
979 /* Do we have something that looks like a boundary? */
980 if ( msr->mpd->buf_contains_line
981 && (strlen(msr->mpd->buf) > 3)
982 && (*(msr->mpd->buf) == '-')
983 && (*(msr->mpd->buf + 1) == '-') )
985 /* Does it match our boundary? */
986 if ( (strlen(msr->mpd->buf) >= strlen(msr->mpd->boundary) + 2)
987 && (strncmp(msr->mpd->buf + 2, msr->mpd->boundary, strlen(msr->mpd->boundary)) == 0) )
989 char *boundary_end = msr->mpd->buf + 2 + strlen(msr->mpd->boundary);
992 /* Is this the final boundary? */
993 if ((*boundary_end == '-') && (*(boundary_end + 1)== '-')) {
997 if (msr->mpd->is_complete != 0) {
998 msr->mpd->flag_error = 1;
999 *error_msg = apr_psprintf(msr->mp,
1000 "Multipart: Invalid boundary (final duplicate).");
1005 /* Allow for CRLF and LF line endings. */
1006 if ( ( (*boundary_end == '\r')
1007 && (*(boundary_end + 1) == '\n')
1008 && (*(boundary_end + 2) == '\0') )
1009 || ( (*boundary_end == '\n')
1010 && (*(boundary_end + 1) == '\0') ) )
1012 if (*boundary_end == '\n') {
1013 msr->mpd->flag_lf_line = 1;
1015 msr->mpd->flag_crlf_line = 1;
1018 if (multipart_process_boundary(msr, (is_final ? 1 : 0), error_msg) < 0) {
1019 msr->mpd->flag_error = 1;
1024 msr->mpd->is_complete = 1;
1027 processed_as_boundary = 1;
1028 msr->mpd->boundary_count++;
1032 msr->mpd->flag_error = 1;
1033 *error_msg = apr_psprintf(msr->mp,
1034 "Multipart: Invalid boundary: %s",
1035 log_escape_nq(msr->mp, msr->mpd->buf));
1038 } else { /* It looks like a boundary but we couldn't match it. */
1041 /* Check if an attempt to use quotes around the boundary was made. */
1042 if ( (msr->mpd->flag_boundary_quoted)
1043 && (strlen(msr->mpd->buf) >= strlen(msr->mpd->boundary) + 3)
1044 && (*(msr->mpd->buf + 2) == '"')
1045 && (strncmp(msr->mpd->buf + 3, msr->mpd->boundary, strlen(msr->mpd->boundary)) == 0)
1047 msr->mpd->flag_error = 1;
1048 *error_msg = apr_psprintf(msr->mp, "Multipart: Invalid boundary (quotes).");
1052 /* Check the beginning of the boundary for whitespace. */
1053 p = msr->mpd->buf + 2;
1054 while(isspace(*p)) {
1058 if ( (p != msr->mpd->buf + 2)
1059 && (strncmp(p, msr->mpd->boundary, strlen(msr->mpd->boundary)) == 0)
1061 /* Found whitespace in front of a boundary. */
1062 msr->mpd->flag_error = 1;
1063 *error_msg = apr_psprintf(msr->mp, "Multipart: Invalid boundary (whitespace).");
1067 msr->mpd->flag_unmatched_boundary = 1;
1069 } else { /* We do not think the buffer contains a boundary. */
1070 /* Look into the buffer to see if there's anything
1071 * there that resembles a boundary.
1073 if (msr->mpd->buf_contains_line) {
1074 int i, len = (MULTIPART_BUF_SIZE - msr->mpd->bufleft);
1075 char *p = msr->mpd->buf;
1077 for(i = 0; i < len; i++) {
1078 if ((p[i] == '-') && (i + 1 < len) && (p[i + 1] == '-'))
1080 if (strncmp(p + i + 2, msr->mpd->boundary, strlen(msr->mpd->boundary)) == 0) {
1081 msr->mpd->flag_unmatched_boundary = 1;
1089 /* Process as data if it was not a boundary. */
1090 if (processed_as_boundary == 0) {
1091 if (msr->mpd->mpp == NULL) {
1092 msr->mpd->flag_data_before = 1;
1094 if (msr->txcfg->debuglog_level >= 4) {
1095 msr_log(msr, 4, "Multipart: Ignoring data before first boundary.");
1098 if (msr->mpd->mpp_state == 0) {
1099 if ((msr->mpd->bufleft == 0) || (process_buffer)) {
1100 /* part header lines must be shorter than
1101 * MULTIPART_BUF_SIZE bytes
1103 msr->mpd->flag_error = 1;
1104 *error_msg = apr_psprintf(msr->mp,
1105 "Multipart: Part header line over %d bytes long",
1106 MULTIPART_BUF_SIZE);
1110 if (multipart_process_part_header(msr, error_msg) < 0) {
1111 msr->mpd->flag_error = 1;
1115 if (multipart_process_part_data(msr, error_msg) < 0) {
1116 msr->mpd->flag_error = 1;
1123 /* Update the offset of the data we are about
1124 * to process. This is to allow us to know the
1125 * offsets of individual files and variables.
1127 msr->mpd->buf_offset += (MULTIPART_BUF_SIZE - msr->mpd->bufleft);
1129 /* reset the pointer to the beginning of the buffer
1130 * and continue to accept input data
1132 msr->mpd->bufptr = msr->mpd->buf;
1133 msr->mpd->bufleft = MULTIPART_BUF_SIZE;
1134 msr->mpd->buf_contains_line = (c == 0x0a) ? 1 : 0;
1137 if ((msr->mpd->is_complete) && (inleft != 0)) {
1138 msr->mpd->flag_data_after = 1;
1140 if (msr->txcfg->debuglog_level >= 4) {
1141 msr_log(msr, 4, "Multipart: Ignoring data after last boundary (%u bytes left)", inleft);
1154 apr_status_t multipart_cleanup(modsec_rec *msr) {
1157 if (msr->mpd == NULL) return -1;
1159 if (msr->txcfg->debuglog_level >= 4) {
1160 msr_log(msr, 4, "Multipart: Cleanup started (remove files %d).", msr->upload_remove_files);
1163 if (msr->upload_remove_files == 0) {
1164 if (msr->txcfg->upload_dir == NULL) {
1165 msr_log(msr, 1, "Input filter: SecUploadDir is undefined, unable to store "
1166 "multipart files.");
1172 /* Loop through the list of parts
1173 * and delete the temporary files, but only if
1174 * file storage was not requested, or if storage
1175 * of relevant files was requested and this isn't
1178 if (keep_files == 0) {
1179 multipart_part **parts;
1182 parts = (multipart_part **)msr->mpd->parts->elts;
1183 for(i = 0; i < msr->mpd->parts->nelts; i++) {
1184 if (parts[i]->type == MULTIPART_FILE) {
1185 if (parts[i]->tmp_file_name != NULL) {
1186 /* make sure it is closed first */
1187 if (parts[i]->tmp_file_fd > 0) {
1188 close(parts[i]->tmp_file_fd);
1189 parts[i]->tmp_file_fd = -1;
1192 if (unlink(parts[i]->tmp_file_name) < 0) {
1193 msr_log(msr, 1, "Multipart: Failed to delete file (part) \"%s\" because %d(%s)",
1194 log_escape(msr->mp, parts[i]->tmp_file_name), errno, strerror(errno));
1196 if (msr->txcfg->debuglog_level >= 4) {
1197 msr_log(msr, 4, "Multipart: Deleted file (part) \"%s\"",
1198 log_escape(msr->mp, parts[i]->tmp_file_name));
1205 /* delete empty files, move the others to the upload dir */
1206 multipart_part **parts;
1209 parts = (multipart_part **)msr->mpd->parts->elts;
1210 for(i = 0; i < msr->mpd->parts->nelts; i++) {
1211 if ( (parts[i]->type == MULTIPART_FILE)
1212 && (parts[i]->tmp_file_size == 0))
1214 /* Delete empty file. */
1215 if (parts[i]->tmp_file_name != NULL) {
1216 /* make sure it is closed first */
1217 if (parts[i]->tmp_file_fd > 0) {
1218 close(parts[i]->tmp_file_fd);
1219 parts[i]->tmp_file_fd = -1;
1222 if (unlink(parts[i]->tmp_file_name) < 0) {
1223 msr_log(msr, 1, "Multipart: Failed to delete empty file (part) \"%s\" because %d(%s)",
1224 log_escape(msr->mp, parts[i]->tmp_file_name), errno, strerror(errno));
1226 if (msr->txcfg->debuglog_level >= 4) {
1227 msr_log(msr, 4, "Multipart: Deleted empty file (part) \"%s\"",
1228 log_escape(msr->mp, parts[i]->tmp_file_name));
1233 /* Move file to the upload dir. */
1234 if (parts[i]->tmp_file_name != NULL) {
1235 const char *new_filename = NULL;
1236 const char *new_basename = NULL;
1238 /* make sure it is closed first */
1239 if (parts[i]->tmp_file_fd > 0) {
1240 close(parts[i]->tmp_file_fd);
1241 parts[i]->tmp_file_fd = -1;
1244 new_basename = file_basename(msr->mp, parts[i]->tmp_file_name);
1245 if (new_basename == NULL) return -1;
1246 new_filename = apr_psprintf(msr->mp, "%s/%s", msr->txcfg->upload_dir,
1248 if (new_filename == NULL) return -1;
1250 if (apr_file_rename(parts[i]->tmp_file_name, new_filename,
1251 msr->msc_reqbody_mp) != APR_SUCCESS)
1253 msr_log(msr, 1, "Input filter: Failed to rename file from \"%s\" to \"%s\".",
1254 log_escape(msr->mp, parts[i]->tmp_file_name),
1255 log_escape(msr->mp, new_filename));
1258 if (msr->txcfg->debuglog_level >= 4) {
1259 msr_log(msr, 4, "Input filter: Moved file from \"%s\" to \"%s\".",
1260 log_escape(msr->mp, parts[i]->tmp_file_name),
1261 log_escape(msr->mp, new_filename));
1275 int multipart_get_arguments(modsec_rec *msr, char *origin, apr_table_t *arguments) {
1276 multipart_part **parts;
1279 parts = (multipart_part **)msr->mpd->parts->elts;
1280 for(i = 0; i < msr->mpd->parts->nelts; i++) {
1281 if (parts[i]->type == MULTIPART_FORMDATA) {
1282 msc_arg *arg = (msc_arg *)apr_pcalloc(msr->mp, sizeof(msc_arg));
1283 if (arg == NULL) return -1;
1285 arg->name = parts[i]->name;
1286 arg->name_len = strlen(parts[i]->name);
1287 arg->value = parts[i]->value;
1288 arg->value_len = parts[i]->length;
1289 arg->value_origin_offset = parts[i]->offset;
1290 arg->value_origin_len = parts[i]->length;
1291 arg->origin = origin;
1293 add_argument(msr, arguments, arg);
1303 char *multipart_reconstruct_urlencoded_body_sanitise(modsec_rec *msr) {
1304 multipart_part **parts;
1306 unsigned int body_len;
1309 if (msr->mpd == NULL) return NULL;
1311 /* calculate the size of the buffer */
1313 parts = (multipart_part **)msr->mpd->parts->elts;
1314 for(i = 0; i < msr->mpd->parts->nelts; i++) {
1315 if (parts[i]->type == MULTIPART_FORMDATA) {
1317 body_len += strlen(parts[i]->name) * 3;
1318 body_len += strlen(parts[i]->value) * 3;
1322 /* allocate the buffer */
1323 body = apr_palloc(msr->mp, body_len + 1);
1324 if ((body == NULL) || (body_len + 1 == 0)) return NULL;
1327 parts = (multipart_part **)msr->mpd->parts->elts;
1328 for(i = 0; i < msr->mpd->parts->nelts; i++) {
1329 if (parts[i]->type == MULTIPART_FORMDATA) {
1331 strncat(body, "&", body_len - strlen(body));
1333 strnurlencat(body, parts[i]->name, body_len - strlen(body));
1334 strncat(body, "=", body_len - strlen(body));
1336 /* Sanitise the variable. Since we are only doing this for
1337 * the logging we will actually write over the data we keep
1340 if (msr->phase >= PHASE_LOGGING) {
1341 if (apr_table_get(msr->arguments_to_sanitise, parts[i]->name) != NULL) {
1342 memset(parts[i]->value, '*', strlen(parts[i]->value));
1345 strnurlencat(body, parts[i]->value, body_len - strlen(body));