]> granicus.if.org Git - apache/blob - server/apreq_parser_multipart.c
Follow up to r1853874: CHANGES entry.
[apache] / server / apreq_parser_multipart.c
1 /*
2 **  Licensed to the Apache Software Foundation (ASF) under one or more
3 ** contributor license agreements.  See the NOTICE file distributed with
4 ** this work for additional information regarding copyright ownership.
5 ** The ASF licenses this file to You under the Apache License, Version 2.0
6 ** (the "License"); you may not use this file except in compliance with
7 ** the License.  You may obtain a copy of the License at
8 **
9 **      http://www.apache.org/licenses/LICENSE-2.0
10 **
11 **  Unless required by applicable law or agreed to in writing, software
12 **  distributed under the License is distributed on an "AS IS" BASIS,
13 **  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 **  See the License for the specific language governing permissions and
15 **  limitations under the License.
16 */
17
18 #include "apreq_parser.h"
19 #include "apreq_error.h"
20 #include "apreq_util.h"
21 #include "apr_strings.h"
22 #include "apr_strmatch.h"
23
24 #ifndef CRLF
25 #define CRLF    "\015\012"
26 #endif
27
28 #define MIN(a, b) (((a) < (b)) ? (a) : (b))
29
30 #define PARSER_STATUS_CHECK(PREFIX)   do {         \
31     if (ctx->status == PREFIX##_ERROR)             \
32         return APREQ_ERROR_GENERAL;                \
33     else if (ctx->status == PREFIX##_COMPLETE)     \
34         return APR_SUCCESS;                        \
35     else if (bb == NULL)                           \
36         return APR_INCOMPLETE;                     \
37 } while (0);
38
39 /* maximum recursion level in the mfd parser */
40 #define MAX_LEVEL 8
41
42 struct mfd_ctx {
43     apr_table_t                 *info;
44     apr_bucket_brigade          *in;
45     apr_bucket_brigade          *bb;
46     apreq_parser_t              *hdr_parser;
47     apreq_parser_t              *next_parser;
48     const apr_strmatch_pattern  *pattern;
49     char                        *bdry;
50     enum {
51         MFD_INIT,
52         MFD_NEXTLINE,
53         MFD_HEADER,
54         MFD_POST_HEADER,
55         MFD_PARAM,
56         MFD_UPLOAD,
57         MFD_MIXED,
58         MFD_COMPLETE,
59         MFD_ERROR
60     }                            status;
61     apr_bucket                  *eos;
62     const char                  *param_name;
63     apreq_param_t               *upload;
64     unsigned                    level;
65 };
66
67
68 /********************* multipart/form-data *********************/
69
70 APR_INLINE
71 static apr_status_t brigade_start_string(apr_bucket_brigade *bb,
72                                          const char *start_string)
73 {
74     apr_bucket *e;
75     apr_size_t slen = strlen(start_string);
76
77     for (e = APR_BRIGADE_FIRST(bb); e != APR_BRIGADE_SENTINEL(bb);
78          e = APR_BUCKET_NEXT(e))
79     {
80         const char *buf;
81         apr_status_t s, bytes_to_check;
82         apr_size_t blen;
83
84         if (slen == 0)
85             return APR_SUCCESS;
86
87         if (APR_BUCKET_IS_EOS(e))
88             return APR_EOF;
89
90         s = apr_bucket_read(e, &buf, &blen, APR_BLOCK_READ);
91
92         if (s != APR_SUCCESS)
93             return s;
94
95         if (blen == 0)
96             continue;
97
98         bytes_to_check = MIN(slen,blen);
99
100         if (strncmp(buf,start_string,bytes_to_check) != 0)
101             return APREQ_ERROR_GENERAL;
102
103         slen -= bytes_to_check;
104         start_string += bytes_to_check;
105     }
106
107     /* slen > 0, so brigade isn't large enough yet */
108     return APR_INCOMPLETE;
109 }
110
111
112 static apr_status_t split_on_bdry(apr_bucket_brigade *out,
113                                   apr_bucket_brigade *in,
114                                   const apr_strmatch_pattern *pattern,
115                                   const char *bdry)
116 {
117     apr_bucket *e = APR_BRIGADE_FIRST(in);
118     apr_size_t blen = strlen(bdry), off = 0;
119
120     while ( e != APR_BRIGADE_SENTINEL(in) ) {
121         apr_ssize_t idx;
122         apr_size_t len;
123         const char *buf;
124         apr_status_t s;
125
126         if (APR_BUCKET_IS_EOS(e))
127             return APR_EOF;
128
129         s = apr_bucket_read(e, &buf, &len, APR_BLOCK_READ);
130         if (s != APR_SUCCESS)
131             return s;
132
133         if (len == 0) {
134             apr_bucket *f = e;
135             e = APR_BUCKET_NEXT(e);
136             apr_bucket_delete(f);
137             continue;
138         }
139
140     look_for_boundary_up_front:
141         if (strncmp(bdry + off, buf, MIN(len, blen - off)) == 0) {
142             if ( len >= blen - off ) {
143                 /* complete match */
144                 if (len > blen - off)
145                     apr_bucket_split(e, blen - off);
146                 e = APR_BUCKET_NEXT(e);
147
148                 do {
149                     apr_bucket *f = APR_BRIGADE_FIRST(in);
150                     apr_bucket_delete(f);
151                 } while (APR_BRIGADE_FIRST(in) != e);
152
153                 return APR_SUCCESS;
154             }
155             /* partial match */
156             off += len;
157             e = APR_BUCKET_NEXT(e);
158             continue;
159         }
160         else if (off > 0) {
161             /* prior (partial) strncmp failed,
162              * so we can move previous buckets across
163              * and retest buf against the full bdry.
164              */
165
166             /* give hints to GCC by making the brigade volatile, otherwise the
167              * loop below will end up being endless. See:
168              * https://bugzilla.redhat.com/bugzilla/show_bug.cgi?id=193740
169              */
170             apr_bucket_brigade * volatile in_v = in;
171
172             do {
173                 apr_bucket *f = APR_BRIGADE_FIRST(in_v);
174                 APR_BUCKET_REMOVE(f);
175                 APR_BRIGADE_INSERT_TAIL(out, f);
176             } while (e != APR_BRIGADE_FIRST(in_v));
177             off = 0;
178             goto look_for_boundary_up_front;
179         }
180
181         if (pattern != NULL && len >= blen) {
182             const char *match = apr_strmatch(pattern, buf, len);
183             if (match != NULL)
184                 idx = match - buf;
185             else {
186                 idx = apreq_index(buf + len-blen, blen, bdry, blen,
187                                   APREQ_MATCH_PARTIAL);
188                 if (idx >= 0)
189                     idx += len-blen;
190             }
191         }
192         else
193             idx = apreq_index(buf, len, bdry, blen, APREQ_MATCH_PARTIAL);
194
195         /* Theoretically idx should never be 0 here, because we
196          * already tested the front of the brigade for a potential match.
197          * However, it doesn't hurt to allow for the possibility,
198          * since this will just start the whole loop over again.
199          */
200         if (idx >= 0)
201             apr_bucket_split(e, idx);
202
203         APR_BUCKET_REMOVE(e);
204         APR_BRIGADE_INSERT_TAIL(out, e);
205         e = APR_BRIGADE_FIRST(in);
206     }
207
208     return APR_INCOMPLETE;
209 }
210
211
212 static
213 struct mfd_ctx * create_multipart_context(const char *content_type,
214                                           apr_pool_t *pool,
215                                           apr_bucket_alloc_t *ba,
216                                           apr_size_t brigade_limit,
217                                           const char *temp_dir,
218                                           unsigned level)
219
220 {
221     apr_status_t s;
222     apr_size_t blen;
223     struct mfd_ctx *ctx = apr_palloc(pool, sizeof *ctx);
224     char *ct = apr_pstrdup(pool, content_type);
225
226     ct = strchr(ct, ';');
227     if (ct == NULL)
228         return NULL; /* missing semicolon */
229
230     *ct++ = 0;
231     s = apreq_header_attribute(ct, "boundary", 8,
232                                (const char **)&ctx->bdry, &blen);
233
234     if (s != APR_SUCCESS)
235         return NULL; /* missing boundary */
236
237     ctx->bdry[blen] = 0;
238
239     *--ctx->bdry = '-';
240     *--ctx->bdry = '-';
241     *--ctx->bdry = '\n';
242     *--ctx->bdry = '\r';
243
244     ctx->status = MFD_INIT;
245     ctx->pattern = apr_strmatch_precompile(pool, ctx->bdry, 1);
246     ctx->hdr_parser = apreq_parser_make(pool, ba, "",
247                                         apreq_parse_headers,
248                                         brigade_limit,
249                                         temp_dir, NULL, NULL);
250     ctx->info = NULL;
251     ctx->bb = apr_brigade_create(pool, ba);
252     ctx->in = apr_brigade_create(pool, ba);
253     ctx->eos = apr_bucket_eos_create(ba);
254     ctx->next_parser = NULL;
255     ctx->param_name = NULL;
256     ctx->upload = NULL;
257     ctx->level = level;
258
259     return ctx;
260 }
261
262 APREQ_DECLARE_PARSER(apreq_parse_multipart)
263 {
264     apr_pool_t *pool = parser->pool;
265     apr_bucket_alloc_t *ba = parser->bucket_alloc;
266     struct mfd_ctx *ctx = parser->ctx;
267     apr_status_t s;
268
269     if (ctx == NULL) {
270         ctx = create_multipart_context(parser->content_type,
271                                        pool, ba,
272                                        parser->brigade_limit,
273                                        parser->temp_dir, 1);
274         if (ctx == NULL)
275             return APREQ_ERROR_GENERAL;
276
277
278         parser->ctx = ctx;
279     }
280
281     PARSER_STATUS_CHECK(MFD);
282     APR_BRIGADE_CONCAT(ctx->in, bb);
283
284  mfd_parse_brigade:
285
286     switch (ctx->status) {
287
288     case MFD_INIT:
289         {
290             s = split_on_bdry(ctx->bb, ctx->in, NULL, ctx->bdry + 2);
291             if (s != APR_SUCCESS) {
292                 apreq_brigade_setaside(ctx->in, pool);
293                 apreq_brigade_setaside(ctx->bb, pool);
294                 return s;
295             }
296             ctx->status = MFD_NEXTLINE;
297             /* Be polite and return any preamble text to the caller. */
298             APR_BRIGADE_CONCAT(bb, ctx->bb);
299         }
300
301         /* fall through */
302
303     case MFD_NEXTLINE:
304         {
305             s = split_on_bdry(ctx->bb, ctx->in, NULL, CRLF);
306             if (s == APR_EOF) {
307                 ctx->status = MFD_COMPLETE;
308                 return APR_SUCCESS;
309             }
310             if (s != APR_SUCCESS) {
311                 apreq_brigade_setaside(ctx->in, pool);
312                 apreq_brigade_setaside(ctx->bb, pool);
313                 return s;
314             }
315             if (!APR_BRIGADE_EMPTY(ctx->bb)) {
316                 char *line;
317                 apr_size_t len;
318                 apr_brigade_pflatten(ctx->bb, &line, &len, pool);
319
320                 if (len >= 2 && strncmp(line, "--", 2) == 0) {
321                     APR_BRIGADE_CONCAT(bb, ctx->in);
322                     ctx->status = MFD_COMPLETE;
323                     return APR_SUCCESS;
324                 }
325                 apr_brigade_cleanup(ctx->bb);
326             }
327
328             ctx->status = MFD_HEADER;
329             ctx->info = NULL;
330         }
331         /* fall through */
332
333     case MFD_HEADER:
334         {
335             if (ctx->info == NULL) {
336                 ctx->info = apr_table_make(pool, APREQ_DEFAULT_NELTS);
337                 /* flush out header parser internal structs for reuse */
338                 ctx->hdr_parser->ctx = NULL;
339             }
340             s = apreq_parser_run(ctx->hdr_parser, ctx->info, ctx->in);
341             switch (s) {
342             case APR_SUCCESS:
343                 ctx->status = MFD_POST_HEADER;
344                 break;
345             case APR_INCOMPLETE:
346                 apreq_brigade_setaside(ctx->in, pool);
347                 return APR_INCOMPLETE;
348             default:
349                 ctx->status = MFD_ERROR;
350                 return s;
351             }
352         }
353         /* fall through */
354
355     case MFD_POST_HEADER:
356         {
357             /*  Must handle special case of missing CRLF (mainly
358              *  coming from empty file uploads). See RFC2065 S5.1.1:
359              *
360              *    body-part = MIME-part-header [CRLF *OCTET]
361              *
362              *  So the CRLF we already matched in MFD_HEADER may have been
363              *  part of the boundary string! Both Konqueror (v??) and
364              *  Mozilla-0.97 are known to emit such blocks.
365              *
366              *  Here we first check for this condition with
367              *  brigade_start_string, and prefix the brigade with
368              *  an additional CRLF bucket if necessary.
369              */
370
371             const char *cd, *ct, *name, *filename;
372             apr_size_t nlen, flen;
373             apr_bucket *e;
374
375             switch (brigade_start_string(ctx->in, ctx->bdry + 2)) {
376
377             case APR_INCOMPLETE:
378                 apreq_brigade_setaside(ctx->in, pool);
379                 return APR_INCOMPLETE;
380
381             case APR_SUCCESS:
382                 /* part has no body- return CRLF to front */
383                 e = apr_bucket_immortal_create(CRLF, 2,
384                                                 ctx->bb->bucket_alloc);
385                 APR_BRIGADE_INSERT_HEAD(ctx->in, e);
386                 break;
387
388             default:
389                 ; /* has body, ok */
390             }
391
392             cd = apr_table_get(ctx->info, "Content-Disposition");
393
394             /*  First check to see if must descend into a new multipart
395              *  block.  If we do, create a new parser and pass control
396              *  to it.
397              */
398
399             ct = apr_table_get(ctx->info, "Content-Type");
400
401             if (ct != NULL && strncmp(ct, "multipart/", 10) == 0) {
402                 struct mfd_ctx *next_ctx;
403
404                 if (ctx->level >= MAX_LEVEL) {
405                     ctx->status = MFD_ERROR;
406                     goto mfd_parse_brigade;
407                 }
408
409                 next_ctx = create_multipart_context(ct, pool, ba,
410                                                     parser->brigade_limit,
411                                                     parser->temp_dir,
412                                                     ctx->level + 1);
413
414                 next_ctx->param_name = "";
415
416                 if (cd != NULL) {
417                     s = apreq_header_attribute(cd, "name", 4,
418                                                &name, &nlen);
419                     if (s == APR_SUCCESS) {
420                         next_ctx->param_name
421                             = apr_pstrmemdup(pool, name, nlen);
422                     }
423                     else {
424                         const char *cid = apr_table_get(ctx->info,
425                                                         "Content-ID");
426                         if (cid != NULL)
427                             next_ctx->param_name = apr_pstrdup(pool, cid);
428                     }
429
430                 }
431
432                 ctx->next_parser = apreq_parser_make(pool, ba, ct,
433                                                      apreq_parse_multipart,
434                                                      parser->brigade_limit,
435                                                      parser->temp_dir,
436                                                      parser->hook,
437                                                      next_ctx);
438                 ctx->status = MFD_MIXED;
439                 goto mfd_parse_brigade;
440
441             }
442
443             /* Look for a normal form-data part. */
444
445             if (cd != NULL && strncmp(cd, "form-data", 9) == 0) {
446                 s = apreq_header_attribute(cd, "name", 4, &name, &nlen);
447                 if (s != APR_SUCCESS) {
448                     ctx->status = MFD_ERROR;
449                     goto mfd_parse_brigade;
450                 }
451
452                 s = apreq_header_attribute(cd, "filename",
453                                            8, &filename, &flen);
454                 if (s == APR_SUCCESS) {
455                     apreq_param_t *param;
456
457                     param = apreq_param_make(pool, name, nlen,
458                                              filename, flen);
459                     apreq_param_tainted_on(param);
460                     param->info = ctx->info;
461                     param->upload
462                         = apr_brigade_create(pool, ctx->bb->bucket_alloc);
463                     ctx->upload = param;
464                     ctx->status = MFD_UPLOAD;
465                     goto mfd_parse_brigade;
466                 }
467                 else {
468                     ctx->param_name = apr_pstrmemdup(pool, name, nlen);
469                     ctx->status = MFD_PARAM;
470                     /* fall thru */
471                 }
472             }
473
474             /* else check for a file part in a multipart section */
475             else if (cd != NULL && strncmp(cd, "file", 4) == 0) {
476                 apreq_param_t *param;
477
478                 s = apreq_header_attribute(cd, "filename",
479                                            8, &filename, &flen);
480                 if (s != APR_SUCCESS || ctx->param_name == NULL) {
481                     ctx->status = MFD_ERROR;
482                     goto mfd_parse_brigade;
483                 }
484                 name = ctx->param_name;
485                 nlen = strlen(name);
486                 param = apreq_param_make(pool, name, nlen,
487                                          filename, flen);
488                 apreq_param_tainted_on(param);
489                 param->info = ctx->info;
490                 param->upload = apr_brigade_create(pool,
491                                                    ctx->bb->bucket_alloc);
492                 ctx->upload = param;
493                 ctx->status = MFD_UPLOAD;
494                 goto mfd_parse_brigade;
495             }
496
497             /* otherwise look for Content-ID in multipart/mixed case */
498             else {
499                 const char *cid = apr_table_get(ctx->info, "Content-ID");
500                 apreq_param_t *param;
501
502                 if (cid != NULL) {
503                     name = cid;
504                     nlen = strlen(name);
505                 }
506                 else {
507                     name = "";
508                     nlen = 0;
509                 }
510
511                 filename = "";
512                 flen = 0;
513                 param = apreq_param_make(pool, name, nlen,
514                                          filename, flen);
515                 apreq_param_tainted_on(param);
516                 param->info = ctx->info;
517                 param->upload = apr_brigade_create(pool,
518                                                ctx->bb->bucket_alloc);
519                 ctx->upload = param;
520                 ctx->status = MFD_UPLOAD;
521                 goto mfd_parse_brigade;
522             }
523         }
524         /* fall through */
525
526     case MFD_PARAM:
527         {
528             apreq_param_t *param;
529             apreq_value_t *v;
530             apr_size_t len;
531             apr_off_t off;
532
533             s = split_on_bdry(ctx->bb, ctx->in, ctx->pattern, ctx->bdry);
534
535             switch (s) {
536
537             case APR_INCOMPLETE:
538                 apreq_brigade_setaside(ctx->in, pool);
539                 apreq_brigade_setaside(ctx->bb, pool);
540                 return s;
541
542             case APR_SUCCESS:
543                 s = apr_brigade_length(ctx->bb, 1, &off);
544                 if (s != APR_SUCCESS) {
545                     ctx->status = MFD_ERROR;
546                     return s;
547                 }
548                 len = off;
549                 param = apreq_param_make(pool, ctx->param_name,
550                                          strlen(ctx->param_name),
551                                          NULL, len);
552                 apreq_param_tainted_on(param);
553                 param->info = ctx->info;
554
555                 *(const apreq_value_t **)&v = &param->v;
556                 apr_brigade_flatten(ctx->bb, v->data, &len);
557                 v->data[len] = 0;
558
559                 if (parser->hook != NULL) {
560                     s = apreq_hook_run(parser->hook, param, NULL);
561                     if (s != APR_SUCCESS) {
562                         ctx->status = MFD_ERROR;
563                         return s;
564                     }
565                 }
566
567                 apreq_param_charset_set(param,
568                                         apreq_charset_divine(v->data, len));
569                 apreq_value_table_add(v, t);
570                 ctx->status = MFD_NEXTLINE;
571                 ctx->param_name = NULL;
572                 apr_brigade_cleanup(ctx->bb);
573                 goto mfd_parse_brigade;
574
575             default:
576                 ctx->status = MFD_ERROR;
577                 return s;
578             }
579
580
581         }
582         break;  /* not reached */
583
584     case MFD_UPLOAD:
585         {
586             apreq_param_t *param = ctx->upload;
587
588             s = split_on_bdry(ctx->bb, ctx->in, ctx->pattern, ctx->bdry);
589             switch (s) {
590
591             case APR_INCOMPLETE:
592                 if (parser->hook != NULL) {
593                     s = apreq_hook_run(parser->hook, param, ctx->bb);
594                     if (s != APR_SUCCESS) {
595                         ctx->status = MFD_ERROR;
596                         return s;
597                     }
598                 }
599                 apreq_brigade_setaside(ctx->bb, pool);
600                 apreq_brigade_setaside(ctx->in, pool);
601                 s = apreq_brigade_concat(pool, parser->temp_dir,
602                                          parser->brigade_limit,
603                                          param->upload, ctx->bb);
604                 return (s == APR_SUCCESS) ? APR_INCOMPLETE : s;
605
606             case APR_SUCCESS:
607                 if (parser->hook != NULL) {
608                     APR_BRIGADE_INSERT_TAIL(ctx->bb, ctx->eos);
609                     s = apreq_hook_run(parser->hook, param, ctx->bb);
610                     APR_BUCKET_REMOVE(ctx->eos);
611                     if (s != APR_SUCCESS) {
612                         ctx->status = MFD_ERROR;
613                         return s;
614                     }
615                 }
616                 apreq_value_table_add(&param->v, t);
617                 apreq_brigade_setaside(ctx->bb, pool);
618                 s = apreq_brigade_concat(pool, parser->temp_dir,
619                                          parser->brigade_limit,
620                                          param->upload, ctx->bb);
621
622                 if (s != APR_SUCCESS)
623                     return s;
624
625                 ctx->status = MFD_NEXTLINE;
626                 goto mfd_parse_brigade;
627
628             default:
629                 ctx->status = MFD_ERROR;
630                 return s;
631             }
632
633         }
634         break;  /* not reached */
635
636
637     case MFD_MIXED:
638         {
639             s = apreq_parser_run(ctx->next_parser, t, ctx->in);
640             switch (s) {
641             case APR_SUCCESS:
642                 ctx->status = MFD_INIT;
643                 ctx->param_name = NULL;
644                 goto mfd_parse_brigade;
645             case APR_INCOMPLETE:
646                 APR_BRIGADE_CONCAT(bb, ctx->in);
647                 return APR_INCOMPLETE;
648             default:
649                 ctx->status = MFD_ERROR;
650                 return s;
651             }
652
653         }
654         break; /* not reached */
655
656     default:
657         return APREQ_ERROR_GENERAL;
658     }
659
660     return APR_INCOMPLETE;
661 }