Imported Upstream version 2.5.11
[libapache-mod-security.git] / apache2 / msc_multipart.c
1 /*
2  * ModSecurity for Apache 2.x, http://www.modsecurity.org/
3  * Copyright (c) 2004-2009 Breach Security, Inc. (http://www.breach.com/)
4  *
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.
8  *
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
12  * distribution.
13  *
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.
17  *
18  */
19 #include <ctype.h>
20 #include <sys/stat.h>
21
22 #include "mod_security2_config.h"
23 #include "msc_multipart.h"
24 #include "msc_util.h"
25 #include "msc_parsers.h"
26
27
28 #if 0
29 static char *multipart_construct_filename(modsec_rec *msr) {
30     char c, *p, *q = msr->mpd->mpp->filename;
31     char *filename;
32
33     /* find the last backward slash and consider the
34      * filename to be only what's right from it
35      */
36     p = strrchr(q, '\\');
37     if (p != NULL) q = p + 1;
38
39     /* do the same for the forward slash */
40     p = strrchr(q, '/');
41     if (p != NULL) q = p + 1;
42
43     /* allow letters, digits and dots, replace
44      * everything else with underscores
45      */
46     p = filename = apr_pstrdup(msr->mp, q);
47     while((c = *p) != 0) {
48         if (!( isalnum(c) || (c == '.') )) *p = '_';
49         p++;
50     }
51
52     return filename;
53 }
54 #endif
55
56 /**
57  *
58  */
59 static int multipart_parse_content_disposition(modsec_rec *msr, char *c_d_value) {
60     char *p = NULL, *t = NULL;
61
62     /* accept only what we understand */
63     if (strncmp(c_d_value, "form-data", 9) != 0) {
64         return -1;
65     }
66
67     /* see if there are any other parts to parse */
68
69     p = c_d_value + 9;
70     while((*p == '\t') || (*p == ' ')) p++;
71     if (*p == '\0') return 1; /* this is OK */
72
73     if (*p != ';') return -2;
74     p++;
75
76     /* parse the appended parts */
77
78     while(*p != '\0') {
79         char *name = NULL, *value = NULL, *start = NULL;
80
81         /* go over the whitespace */
82         while((*p == '\t') || (*p == ' ')) p++;
83         if (*p == '\0') return -3;
84
85         start = p;
86         while((*p != '\0') && (*p != '=') && (*p != '\t') && (*p != ' ')) p++;
87         if (*p == '\0') return -4;
88
89         name = apr_pstrmemdup(msr->mp, start, (p - start));
90
91         while((*p == '\t') || (*p == ' ')) p++;
92         if (*p == '\0') return -5;
93
94         if (*p != '=') return -13;
95         p++;
96
97         while((*p == '\t') || (*p == ' ')) p++;
98         if (*p == '\0') return -6;
99
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.
103          */
104         if ((*p == '"') || (*p == '\'')) {
105             /* quoted */
106             char quote = *p;
107
108             if (quote == '\'') {
109                 msr->mpd->flag_invalid_quoting = 1;
110             }
111
112             p++;
113             if (*p == '\0') return -7;
114
115             start = p;
116             value = apr_pstrdup(msr->mp, p);
117             t = value;
118
119             while(*p != '\0') {
120                 if (*p == '\\') {
121                     if (*(p + 1) == '\0') {
122                         /* improper escaping */
123                         return -8;
124                     }
125                     /* only quote and \ can be escaped */
126                     if ((*(p + 1) == quote) || (*(p + 1) == '\\')) {
127                         p++;
128                     }
129                     else {
130                         /* improper escaping */
131
132                         /* We allow for now because IE sends
133                          * improperly escaped content and there's
134                          * nothing we can do about it.
135                          *
136                          * return -9;
137                          */
138                     }
139                 }
140                 else if (*p == quote) {
141                     *t = '\0';
142                     break;
143                 }
144
145                 *(t++) = *(p++);
146             }
147             if (*p == '\0') return -10;
148
149             p++; /* go over the quote at the end */
150
151         } else {
152             /* not quoted */
153
154             start = p;
155             while((*p != '\0') && (is_token_char(*p))) p++;
156             value = apr_pstrmemdup(msr->mp, start, (p - start));
157         }
158
159         /* evaluate part */
160
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));
165                 return -14;
166             }
167             msr->mpd->mpp->name = value;
168
169             if (msr->txcfg->debuglog_level >= 9) {
170                 msr_log(msr, 9, "Multipart: Content-Disposition name: %s",
171                     log_escape_nq(msr->mp, value));
172             }
173         }
174         else
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));
179                 return -15;
180             }
181             msr->mpd->mpp->filename = value;
182
183             if (msr->txcfg->debuglog_level >= 9) {
184                 msr_log(msr, 9, "Multipart: Content-Disposition filename: %s",
185                     log_escape_nq(msr->mp, value));
186             }
187         }
188         else return -11;
189
190         if (*p != '\0') {
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 */
196         }
197
198         /* loop will stop when (*p == '\0') */
199     }
200
201     return 1;
202 }
203
204 /**
205  *
206  */
207 static int multipart_process_part_header(modsec_rec *msr, char **error_msg) {
208     int i, len, rc;
209
210     if (error_msg == NULL) return -1;
211     *error_msg = NULL;
212
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.");
218             return -1;
219         }
220     }
221
222     /* The buffer is data so increase the data length counter. */
223     msr->msc_reqbody_no_files_length += (MULTIPART_BUF_SIZE - msr->mpd->bufleft);
224
225     if (len > 1) {
226         if (msr->mpd->buf[len - 2] == '\r') {
227             msr->mpd->flag_crlf_line = 1;
228         } else {
229             msr->mpd->flag_lf_line = 1;
230         }
231     } else {
232         msr->mpd->flag_lf_line = 1;
233     }
234
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') ) )
241     { /* Empty line. */
242         char *header_value = NULL;
243
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.");
247             return -1;
248         }
249
250         rc = multipart_parse_content_disposition(msr, header_value);
251         if (rc < 0) {
252             *error_msg = apr_psprintf(msr->mp, "Multipart: Invalid Content-Disposition header (%d): %s.",
253                 rc, log_escape_nq(msr->mp, header_value));
254             return -1;
255         }
256
257         if (msr->mpd->mpp->name == NULL) {
258             *error_msg = apr_psprintf(msr->mp, "Multipart: Content-Disposition header missing name field.");
259             return -1;
260         }
261
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.
266              */
267             if (strstr(header_value, "filename=") == NULL) {
268                 *error_msg = apr_psprintf(msr->mp, "Multipart: Invalid Content-Disposition header (filename).");
269                 return -1;
270             }
271
272             msr->mpd->mpp->type = MULTIPART_FILE;
273         } else {
274             msr->mpd->mpp->type = MULTIPART_FORMDATA;
275         }
276
277         msr->mpd->mpp_state = 1;
278         msr->mpd->mpp->last_header_name = NULL;
279     } else {
280         /* Header line. */
281
282         if ((msr->mpd->buf[0] == '\t') || (msr->mpd->buf[0] == ' ')) {
283             char *header_value, *new_value, *data;
284
285             /* header folding, add data to the header we are building */
286             msr->mpd->flag_header_folding = 1;
287
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).");
291                 return -1;
292             }
293
294             /* locate the beginning of data */
295             data = msr->mpd->buf;
296             while((*data == '\t') || (*data == ' ')) data++;
297
298             new_value = apr_pstrdup(msr->mp, data);
299             remove_lf_crlf_inplace(new_value);
300
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);
305
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));
310             }
311
312             if (strlen(new_value) > MULTIPART_BUF_SIZE) {
313                 *error_msg = apr_psprintf(msr->mp, "Multipart: Part header too long.");
314                 return -1;
315             }
316         } else {
317             char *header_name, *header_value, *data;
318
319             /* new header */
320
321             data = msr->mpd->buf;
322             while((*data != ':') && (*data != '\0')) data++;
323             if (*data == '\0') {
324                 *error_msg = apr_psprintf(msr->mp, "Multipart: Invalid part header (colon missing): %s.",
325                     log_escape_nq(msr->mp, msr->mpd->buf));
326                 return -1;
327             }
328
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).");
333
334                  return -1;
335             }
336
337             /* extract the value value */
338             data++;
339             while((*data == '\t') || (*data == ' ')) data++;
340             header_value = apr_pstrdup(msr->mp, data);
341             remove_lf_crlf_inplace(header_value);
342
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));
347                 return -1;
348             }
349
350             apr_table_setn(msr->mpd->mpp->headers, header_name, header_value);
351             msr->mpd->mpp->last_header_name = header_name;
352
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));
357             }
358         }
359     }
360
361     return 1;
362 }
363
364 /**
365  *
366  */
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;
371
372     if (error_msg == NULL) return -1;
373     *error_msg = NULL;
374
375     /* Preserve some bytes for later. */
376     if (   ((MULTIPART_BUF_SIZE - msr->mpd->bufleft) >= 1)
377         && (*(p - 1) == '\n') )
378     {
379         if (   ((MULTIPART_BUF_SIZE - msr->mpd->bufleft) >= 2)
380             && (*(p - 2) == '\r') )
381         {
382             /* Two bytes. */
383             bytes_reserved = 2;
384             localreserve[0] = *(p - 2);
385             localreserve[1] = *(p - 1);
386             msr->mpd->bufleft += 2;
387             *(p - 2) = 0;
388         } else {
389             /* Only one byte. */
390             bytes_reserved = 1;
391             localreserve[0] = *(p - 1);
392             localreserve[1] = 0;
393             msr->mpd->bufleft += 1;
394             *(p - 1) = 0;
395         }
396     }
397
398     /* add data to the part we are building */
399     if (msr->mpd->mpp->type == MULTIPART_FILE) {
400
401         /* remember where we started */
402         if (msr->mpd->mpp->length == 0) {
403             msr->mpd->mpp->offset = msr->mpd->buf_offset;
404         }
405
406         /* only store individual files on disk if we are going
407          * to keep them or if we need to have them approved later
408          */
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);
416
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));
421                     return -1;
422                 }
423
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));
427                 }
428             }
429
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));
435                     return -1;
436                 }
437
438                 msr->mpd->mpp->tmp_file_size += msr->mpd->reserve[0];
439                 msr->mpd->mpp->length += msr->mpd->reserve[0];
440             }
441
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))
445             {
446                 *error_msg = apr_psprintf(msr->mp, "Multipart: writing to \"%s\" failed",
447                     log_escape(msr->mp, msr->mpd->mpp->tmp_file_name));
448                 return -1;
449             }
450
451             msr->mpd->mpp->tmp_file_size += (MULTIPART_BUF_SIZE - msr->mpd->bufleft);
452             msr->mpd->mpp->length += (MULTIPART_BUF_SIZE - msr->mpd->bufleft);
453         } else {
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];
457         }
458     }
459     else if (msr->mpd->mpp->type == MULTIPART_FORMDATA) {
460         value_part_t *value_part = apr_pcalloc(msr->mp, sizeof(value_part_t));
461
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];
464
465         /* add this part to the list of parts */
466
467         /* remember where we started */
468         if (msr->mpd->mpp->length == 0) {
469             msr->mpd->mpp->offset = msr->mpd->buf_offset;
470         }
471
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));
476
477             value_part->length = (MULTIPART_BUF_SIZE - msr->mpd->bufleft) + msr->mpd->reserve[0];
478             msr->mpd->mpp->length += value_part->length;
479         } else {
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;
483         }
484
485         *(value_part_t **)apr_array_push(msr->mpd->mpp->value_parts) = value_part;
486
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));
490         }
491     }
492     else {
493         *error_msg = apr_psprintf(msr->mp, "Multipart: unknown part type %d", msr->mpd->mpp->type);
494         return -1;
495     }
496
497     /* store the reserved bytes to the multipart
498      * context so that they don't get lost
499      */
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;
505     }
506     else {
507         msr->mpd->buf_offset -= msr->mpd->reserve[0];
508         msr->mpd->reserve[0] = 0;
509     }
510
511     return 1;
512 }
513
514 /**
515  *
516  */
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;
521     int i;
522
523     if (rval == NULL) return NULL;
524
525     offset = 0;
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;
531         }
532     }
533     rval[offset] = '\0';
534
535     return rval;
536 }
537
538 /**
539  *
540  */
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))
548         {
549             close(msr->mpd->mpp->tmp_file_fd);
550         }
551
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;
556         }
557
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);
568                 }
569             }
570             else {
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);
575                 }
576             }
577         }
578         else {
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);
582         }
583
584         msr->mpd->mpp = NULL;
585     }
586
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;
593
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;
597
598         msr->mpd->reserve[0] = 0;
599         msr->mpd->reserve[1] = 0;
600         msr->mpd->reserve[2] = 0;
601         msr->mpd->reserve[3] = 0;
602
603         msr->mpd->mpp->value_parts = apr_array_make(msr->mp, 10, sizeof(value_part_t *));
604     }
605
606     return 1;
607 }
608
609 static int multipart_boundary_characters_valid(char *boundary) {
610     unsigned char *p = (unsigned char *)boundary;
611     unsigned char c;
612
613     if (p == NULL) return -1;
614
615     while((c = *p) != '\0') {
616         /* Control characters and space not allowed. */
617         if (c < 32) {
618             return 0;
619         }
620
621         /* Non-ASCII characters not allowed. */
622         if (c > 126) {
623             return 0;
624         }
625
626         switch(c) {
627             /* Special characters not allowed. */
628             case '(' :
629             case ')' :
630             case '<' :
631             case '>' :
632             case '@' :
633             case ',' :
634             case ';' :
635             case ':' :
636             case '\\' :
637             case '"' :
638             case '/' :
639             case '[' :
640             case ']' :
641             case '?' :
642             case '=' :
643                 return 0;
644                 break;
645
646             default :
647                 /* Do nothing. */
648                 break;
649         }
650
651         p++;
652     }
653
654     return 1;
655 }
656
657 static int multipart_count_boundary_params(apr_pool_t *mp, const char *header_value) {
658     char *duplicate = NULL;
659     char *s = NULL;
660     int count = 0;
661
662     if (header_value == NULL) return -1;
663     duplicate = apr_pstrdup(mp, header_value);
664     if (duplicate == NULL) return -1;
665
666     /* Performing a case-insensitive search. */
667     strtolower_inplace((unsigned char *)duplicate);
668
669     s = duplicate;
670     while((s = strstr(s, "boundary")) != NULL) {
671         s += 8;
672
673         if (strchr(s, '=') != NULL) {
674             count++;
675         }
676     }
677
678     return count;
679 }
680
681 /**
682  *
683  */
684 int multipart_init(modsec_rec *msr, char **error_msg) {
685     if (error_msg == NULL) return -1;
686     *error_msg = NULL;
687
688     msr->mpd = (multipart_data *)apr_pcalloc(msr->mp, sizeof(multipart_data));
689     if (msr->mpd == NULL) return -1;
690
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;
696
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.");
700         return -1;
701     }
702
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).");
706         return -1;
707     }
708
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.");
712         return -1;
713     }
714
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.");
719         return -1;
720     }
721
722     msr->mpd->boundary = strstr(msr->request_content_type, "boundary");
723     if (msr->mpd->boundary != NULL) {
724         char *p = NULL;
725         char *b = NULL;
726         int seen_semicolon = 0;
727         int len = 0;
728
729         /* Check for extra characters before the boundary. */
730         for (p = (char *)(msr->request_content_type + 19); p < msr->mpd->boundary; p++) {
731             if (!isspace(*p)) {
732                 if ((seen_semicolon == 0) && (*p == ';')) {
733                     seen_semicolon = 1; /* It is OK to have one semicolon. */
734                 } else {
735                     msr->mpd->flag_error = 1;
736                     *error_msg = apr_psprintf(msr->mp, "Multipart: Invalid boundary in C-T (malformed).");
737                     return -1;
738                 }
739             }
740         }
741
742         /* Have we seen the semicolon in the header? */
743         if (seen_semicolon == 0) {
744             msr->mpd->flag_missing_semicolon = 1;
745         }
746
747         b = strchr(msr->mpd->boundary + 8, '=');
748         if (b == NULL) {
749             msr->mpd->flag_error = 1;
750             *error_msg = apr_psprintf(msr->mp, "Multipart: Invalid boundary in C-T (malformed).");
751             return -1;
752         }
753
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.
758              */
759             for (p = msr->mpd->boundary + 8; p < b; p++) {
760                 if (isspace(*p)) {
761                     /* Flag for whitespace after parameter name. */
762                     msr->mpd->flag_boundary_whitespace = 1;
763                 } else {
764                     msr->mpd->flag_error = 1;
765                     *error_msg = apr_psprintf(msr->mp, "Multipart: Invalid boundary in C-T (parameter name).");
766                     return -1;
767                 }
768             }
769         }
770
771         b++; /* Go over the = character. */
772         len = strlen(b);
773
774         /* Flag for whitespace before parameter value. */
775         if (isspace(*b)) {
776             msr->mpd->flag_boundary_whitespace = 1;
777         }
778
779         /* Is the boundary quoted? */
780         if ((len >= 2) && (*b == '"') && (*(b + len - 1) == '"')) {
781             /* Quoted. */
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;
785         } else {
786             /* Not quoted. */
787
788             /* Test for partial quoting. */
789             if (   (*b == '"')
790                 || ((len >= 2) && (*(b + len - 1) == '"')) )
791             {
792                 msr->mpd->flag_error = 1;
793                 *error_msg = apr_psprintf(msr->mp, "Multipart: Invalid boundary in C-T (quote).");
794                 return -1;
795             }
796
797             msr->mpd->boundary = apr_pstrdup(msr->mp, b);
798             if (msr->mpd->boundary == NULL) return -1;
799             msr->mpd->flag_boundary_quoted = 0;
800         }
801
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).");
806             return -1;
807         }
808
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).");
813             return -1;
814         }
815
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));
820         }
821
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).");
825             return -1;
826         }
827     }
828     else { /* Could not find boundary in the C-T header. */
829         msr->mpd->flag_error = 1;
830
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).");
834             return -1;
835         }
836
837         *error_msg = apr_psprintf(msr->mp, "Multipart: Boundary not found in C-T.");
838         return -1;
839     }
840
841     return 1;
842 }
843
844 /**
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.
847  */
848 int multipart_complete(modsec_rec *msr, char **error_msg) {
849     if (msr->mpd == NULL) return 1;
850
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.");
854         }
855
856         if (msr->mpd->flag_data_after) {
857             msr_log(msr, 4, "Multipart: Warning: seen data after last boundary.");
858         }
859
860         if (msr->mpd->flag_boundary_quoted) {
861             msr_log(msr, 4, "Multipart: Warning: boundary was quoted.");
862         }
863
864         if (msr->mpd->flag_boundary_whitespace) {
865             msr_log(msr, 4, "Multipart: Warning: boundary whitespace in C-T header.");
866         }
867
868         if (msr->mpd->flag_header_folding) {
869             msr_log(msr, 4, "Multipart: Warning: header folding used.");
870         }        
871
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).");
874         }
875         else if (msr->mpd->flag_lf_line) {
876             msr_log(msr, 4, "Multipart: Warning: incorrect line endings used (LF).");
877         }
878
879         if (msr->mpd->flag_missing_semicolon) {
880             msr_log(msr, 4, "Multipart: Warning: missing semicolon in C-T header.");
881         }
882     }
883
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.
888              */
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) == '-') )
896                 {
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;
900                         return -1;
901                     }
902
903                     /* The payload is complete after all. */
904                     msr->mpd->is_complete = 1;
905                 }
906             }
907
908             if (msr->mpd->is_complete == 0) {
909                 *error_msg = apr_psprintf(msr->mp, "Multipart: Final boundary missing.");
910                 return -1;
911             }
912         } else {
913             *error_msg = apr_psprintf(msr->mp, "Multipart: No boundaries found in payload.");
914             return -1;
915         }
916     }
917
918     return 1;
919 }
920
921 /**
922  *
923  */
924 int multipart_process_chunk(modsec_rec *msr, const char *buf,
925     unsigned int size, char **error_msg)
926 {
927     char *inptr = (char *)buf;
928     unsigned int inleft = size;
929
930     if (error_msg == NULL) return -1;
931     *error_msg = NULL;
932
933     if (size == 0) return 1;
934
935     msr->mpd->seen_data = 1;
936
937     if (msr->mpd->is_complete) {
938         msr->mpd->flag_data_before = 1;
939
940         if (msr->txcfg->debuglog_level >= 4) {
941             msr_log(msr, 4, "Multipart: Ignoring data after last boundary (received %u bytes)", size);
942         }
943
944         return 1;
945     }
946
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");
951         return -1;
952     }
953
954     /* here we loop through the available data, one byte at a time */
955     while(inleft > 0) {
956         char c = *inptr;
957         int process_buffer = 0;
958
959         if ((c == '\r') && (msr->mpd->bufleft == 1)) {
960             /* we don't want to take \r as the last byte in the buffer */
961             process_buffer = 1;
962         } else {
963             inptr++;
964             inleft = inleft - 1;
965
966             *(msr->mpd->bufptr) = c;
967             msr->mpd->bufptr++;
968             msr->mpd->bufleft--;
969         }
970
971         /* until we either reach the end of the line
972          * or the end of our internal buffer
973          */
974         if ((c == '\n') || (msr->mpd->bufleft == 0) || (process_buffer)) {
975             int processed_as_boundary = 0;
976
977             *(msr->mpd->bufptr) = 0;
978
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) == '-') )
984             {
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) )
988                 {
989                     char *boundary_end = msr->mpd->buf + 2 + strlen(msr->mpd->boundary);
990                     int is_final = 0;
991
992                     /* Is this the final boundary? */
993                     if ((*boundary_end == '-') && (*(boundary_end + 1)== '-')) {
994                         is_final = 1;
995                         boundary_end += 2;
996
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).");
1001                             return -1;
1002                         }
1003                     }
1004
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') ) )
1011                     {
1012                         if (*boundary_end == '\n') {
1013                             msr->mpd->flag_lf_line = 1;
1014                         } else {
1015                             msr->mpd->flag_crlf_line = 1;
1016                         }
1017
1018                         if (multipart_process_boundary(msr, (is_final ? 1 : 0), error_msg) < 0) {
1019                             msr->mpd->flag_error = 1;
1020                             return -1;
1021                         }
1022
1023                         if (is_final) {
1024                             msr->mpd->is_complete = 1;
1025                         }
1026
1027                         processed_as_boundary = 1;
1028                         msr->mpd->boundary_count++;
1029                     }
1030                     else {
1031                         /* error */
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));
1036                         return -1;
1037                     }
1038                 } else { /* It looks like a boundary but we couldn't match it. */
1039                     char *p = NULL;
1040
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)
1046                     ) {
1047                         msr->mpd->flag_error = 1;
1048                         *error_msg = apr_psprintf(msr->mp, "Multipart: Invalid boundary (quotes).");
1049                         return -1;
1050                     }
1051
1052                     /* Check the beginning of the boundary for whitespace. */
1053                     p = msr->mpd->buf + 2;
1054                     while(isspace(*p)) {
1055                         p++;
1056                     }
1057
1058                     if (   (p != msr->mpd->buf + 2)
1059                         && (strncmp(p, msr->mpd->boundary, strlen(msr->mpd->boundary)) == 0)
1060                     ) {
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).");
1064                         return -1;
1065                     }
1066
1067                     msr->mpd->flag_unmatched_boundary = 1;
1068                 }
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.
1072                  */
1073                 if (msr->mpd->buf_contains_line) {
1074                     int i, len = (MULTIPART_BUF_SIZE - msr->mpd->bufleft);
1075                     char *p = msr->mpd->buf;
1076
1077                     for(i = 0; i < len; i++) {
1078                         if ((p[i] == '-') && (i + 1 < len) && (p[i + 1] == '-'))
1079                         {
1080                             if (strncmp(p + i + 2, msr->mpd->boundary, strlen(msr->mpd->boundary)) == 0) {
1081                                 msr->mpd->flag_unmatched_boundary = 1;
1082                                 break;
1083                             }
1084                         }
1085                     }
1086                 }
1087             }
1088
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;
1093
1094                     if (msr->txcfg->debuglog_level >= 4) {
1095                         msr_log(msr, 4, "Multipart: Ignoring data before first boundary.");
1096                     }
1097                 } else {
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
1102                              */
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);
1107                             return -1;
1108                         }
1109
1110                         if (multipart_process_part_header(msr, error_msg) < 0) {
1111                             msr->mpd->flag_error = 1;
1112                             return -1;
1113                         }
1114                     } else {
1115                         if (multipart_process_part_data(msr, error_msg) < 0) {
1116                             msr->mpd->flag_error = 1;
1117                             return -1;
1118                         }
1119                     }
1120                 }
1121             }
1122
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.
1126              */
1127             msr->mpd->buf_offset += (MULTIPART_BUF_SIZE - msr->mpd->bufleft);
1128
1129             /* reset the pointer to the beginning of the buffer
1130              * and continue to accept input data
1131              */
1132             msr->mpd->bufptr = msr->mpd->buf;
1133             msr->mpd->bufleft = MULTIPART_BUF_SIZE;
1134             msr->mpd->buf_contains_line = (c == 0x0a) ? 1 : 0;
1135         }
1136
1137         if ((msr->mpd->is_complete) && (inleft != 0)) {
1138             msr->mpd->flag_data_after = 1;
1139
1140             if (msr->txcfg->debuglog_level >= 4) {
1141                 msr_log(msr, 4, "Multipart: Ignoring data after last boundary (%u bytes left)", inleft);
1142             }
1143
1144             return 1;
1145         }
1146     }
1147
1148     return 1;
1149 }
1150
1151 /**
1152  *
1153  */
1154 apr_status_t multipart_cleanup(modsec_rec *msr) {
1155     int keep_files = 0;
1156
1157     if (msr->mpd == NULL) return -1;
1158
1159     if (msr->txcfg->debuglog_level >= 4) {
1160         msr_log(msr, 4, "Multipart: Cleanup started (remove files %d).", msr->upload_remove_files);
1161     }
1162
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.");
1167         } else {
1168             keep_files = 1;
1169         }
1170     }
1171
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
1176      * such a request.
1177      */
1178     if (keep_files == 0) {
1179         multipart_part **parts;
1180         int i;
1181
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;
1190                     }
1191
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));
1195                     } else {
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));
1199                         }
1200                     }
1201                 }
1202             }
1203         }
1204     } else {
1205         /* delete empty files, move the others to the upload dir */
1206         multipart_part **parts;
1207         int i;
1208
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))
1213             {
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;
1220                     }
1221
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));
1225                     } else {
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));
1229                         }
1230                     }
1231                 }
1232             } else {
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;
1237
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;
1242                     }
1243
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,
1247                         new_basename);
1248                     if (new_filename == NULL) return -1;
1249
1250                     if (apr_file_rename(parts[i]->tmp_file_name, new_filename,
1251                         msr->msc_reqbody_mp) != APR_SUCCESS)
1252                     {
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));
1256                         return -1;
1257                     } else {
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));
1262                         }
1263                     }
1264                 }
1265             }
1266         }
1267     }
1268
1269     return 1;
1270 }
1271
1272 /**
1273  *
1274  */
1275 int multipart_get_arguments(modsec_rec *msr, char *origin, apr_table_t *arguments) {
1276     multipart_part **parts;
1277     int i;
1278
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;
1284
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;
1292
1293             add_argument(msr, arguments, arg);
1294         }
1295     }
1296
1297     return 1;
1298 }
1299
1300 /**
1301  *
1302  */
1303 char *multipart_reconstruct_urlencoded_body_sanitise(modsec_rec *msr) {
1304     multipart_part **parts;
1305     char *body;
1306     unsigned int body_len;
1307     int i;
1308
1309     if (msr->mpd == NULL) return NULL;
1310
1311     /* calculate the size of the buffer */
1312     body_len = 1;
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) {
1316             body_len += 4;
1317             body_len += strlen(parts[i]->name) * 3;
1318             body_len += strlen(parts[i]->value) * 3;
1319         }
1320     }
1321
1322     /* allocate the buffer */
1323     body = apr_palloc(msr->mp, body_len + 1);
1324     if ((body == NULL) || (body_len + 1 == 0)) return NULL;
1325     *body = 0;
1326
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) {
1330             if (*body != 0) {
1331                 strncat(body, "&", body_len - strlen(body));
1332             }
1333             strnurlencat(body, parts[i]->name, body_len - strlen(body));
1334             strncat(body, "=", body_len - strlen(body));
1335
1336             /* Sanitise the variable. Since we are only doing this for
1337              * the logging we will actually write over the data we keep
1338              * in the memory.
1339              */
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));
1343                 }
1344             }
1345             strnurlencat(body, parts[i]->value, body_len - strlen(body));
1346         }
1347     }
1348
1349     return body;
1350 }