1 /******************************************************************************
2  * qDecoder - http://www.qdecoder.org
3  *
4  * Copyright (c) 2000-2012 Seungyoung Kim.
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions are met:
9  *
10  * 1. Redistributions of source code must retain the above copyright notice,
11  *    this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright notice,
13  *    this list of conditions and the following disclaimer in the documentation
14  *    and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
20  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26  * POSSIBILITY OF SUCH DAMAGE.
27  ******************************************************************************
28  * $Id: qcgireq.c 652 2012-10-01 05:01:15Z seungyoung.kim $
29  ******************************************************************************/
30 
31 /**
32  * @file qcgireq.c CGI Request Parsing API
33  *
34  * qDecoder supports parsing of
35  *
36  *   @li COOKIE
37  *   @li GET method
38  *   @li POST method (application/x-www-form-urlencoded: default FORM encoding)
39  *   @li POST method (multipart/form-data: especially used for file uploading)
40  *
41  * And parsed elements are stored in qentry_t linked-list structure.
42  *
43  * @code
44  *   [HTML sample]
45  *   <form action="your_program.cgi">
46  *     <input type="text" name="color">
47  *     <input type="submit">
48  *   </form>
49  *
50  *   [Your Source]
51  *   qentry_t *req = qcgireq_parse(NULL, 0);  // 0 is equivalent to Q_CGI_ALL.
52  *
53  *   qcgires_setcontenttype(req, "text/plain");
54  *   char *color = req->getstr(req, "color", false);
55  *   if (color != NULL) {
56  *     printf("Color = %s\n", color);
57  *   }
58  *
59  *   req->free(req);
60  * @endcode
61  *
62  * The order of parsing sequence is (1)COOKIE (2)POST (3)GET.
63  * Thus if there is a same query name existing in different methods,
64  * COOKIE values will be stored first than POST, GET values will be added
65  * at the very last into a qentry linked-list.
66  *
67  * Below is an example to parse only two given methods. Please note that
68  * when multiple methods are specified, it'll be parsed in the order of
69  * COOKIE, POST and GET.
70  *
71  * @code
72  *   qentry_t *req = qcgireq_parse(req, Q_CGI_COOKIE | Q_CGI_POST);
73  * @endcode
74  *
75  * To change the order of parsing sequence, you can call qcgireq_parse()
76  * multiple times in the order that you want as below.
77  *
78  * @code
79  *   qentry_t *req;
80  *   req = qcgireq_parse(req, Q_CGI_POST);
81  *   req = qcgireq_parse(req, Q_CGI_GET);
82  *   req = qcgireq_parse(req, Q_CGI_COOKIE);
83  * @endcode
84  *
85  * In terms of multipart/form-data encoding(used for file uploading),
86  * qDecoder can handle that in two different ways internally.
87  *
88  * @li <b>default mode</b> : Uploading file will be processed only in memory.
89  * (see examples/upload.c)
90  * @li <b>file mode</b> : Uploading file will be stored directly into disk.
91  * (see examples/uploadfile.c)
92  *
93  * You can switch to file mode by calling qcgireq_setoption().
94  * @code
95  *   qentry_t *req = qcgireq_setoption(NULL, true, "/tmp", 86400);
96  *   req = qcgireq_parse(req, 0);
97  *   (...your codes here...)
98  *   req->free(req);
99  * @endcode
100  *
101  * Basically, when file is uploaded qDecoder store it's meta information like
102  * below.
103  * @li (VARIABLE_NAME) - In the <b>default mode</b>, this is binary data.
104  *  In the <b>file mode</b> this value is same as "(VARIABLE_NAME).savepath".
105  * @li (VARIABLE_NAME).filename - File name itself, path info will be removed.
106  * @li (VARIABLE_NAME).length   - File size, the number of bytes.
107  * @li (VARIABLE_NAME).contenttype - Mime type like 'text/plain'.
108  * @li (VARIABLE_NAME).savepath - Only appended only in <b>file mode</b>.
109  * The file path where the uploaded file is saved.
110  *
111  * @code
112  *   [default mode example]
113  *   binary = (...binary data...)
114  *   binary.filename = hello.xls
115  *   binary.length = 3292
116  *   binary.contenttype = application/vnd.ms-excel
117  *
118  *   [file mode example]
119  *   binary = tmp/q_wcktIq
120  *   binary.length = 60014
121  *   binary.filename = hello.xls
122  *   binary.contenttype = application/vnd.ms-excel
123  *   binary.savepath = tmp/q_wcktIq
124  * @endcode
125  */
126 
127 #ifdef ENABLE_FASTCGI
128 #include "fcgi_stdio.h"
129 #else
130 #include <stdio.h>
131 #endif
132 #include <stdlib.h>
133 #include <stdbool.h>
134 #include <string.h>
135 #include <unistd.h>
136 #include <sys/stat.h>
137 #include <time.h>
138 #include <limits.h>
139 #ifndef _WIN32
140 #include <dirent.h>
141 #endif
142 #include <errno.h>
143 #include "qdecoder.h"
144 #include "internal.h"
145 
146 #if defined(__MINGW32__) && defined(_WIN32) && !defined(__CYGWIN__)
147 #include "msw_missing.h"
148 #endif
149 
150 #ifndef _DOXYGEN_SKIP
151 static int  _parse_multipart(qentry_t *request);
152 static char *_parse_multipart_value_into_memory(char *boundary, int *valuelen,
153                                                 bool *finish);
154 static char *_parse_multipart_value_into_disk(const char *boundary,
155         const char *savedir, const char *filename, int *filelen, bool *finish);
156 static int _upload_clear_base(const char *upload_basepath, int upload_clearold);
157 static qentry_t *_parse_query(qentry_t *request, const char *query,
158                               char equalchar, char sepchar, int *count);
159 #endif
160 
161 /**
162  * Set request parsing option for file uploading in case of multipart/form-data
163  * encoding.
164  *
165  * @param request   qentry_t container pointer that options will be set.
166  *                  NULL can be used to create a new container.
167  * @param filemode  false for parsing in memory, true for storing attached
168  *                  files into file-system directly.
169  * @param basepath  the base path where the uploaded files are located.
170  *                  Set to NULL if filemode is false.
171  * @param clearold  saved files older than this seconds will be removed
172  *                  automatically. Set to 0 to disable.
173  *
174  * @return  qentry_t container pointer, otherwise returns NULL.
175  *
176  * @note
177  * This method should be called before calling qcgireq_parse().
178  *
179  * @code
180  *   qentry_t *req = qcgireq_setoption(NULL, true, "/tmp", 86400);
181  *   req = qcgireq_parse(req, 0);
182  *   req->free(req);
183  * @endcode
184  */
qcgireq_setoption(qentry_t * request,bool filemode,const char * basepath,int clearold)185 qentry_t *qcgireq_setoption(qentry_t *request, bool filemode,
186                             const char *basepath, int clearold)
187 {
188     // initialize entry structure
189     if (request == NULL) {
190         request = qEntry();
191         if (request == NULL) return NULL;
192     }
193 
194     if (filemode == true) {
195         if (basepath == NULL || access(basepath, R_OK|W_OK|X_OK) != 0) {
196             request->free(request);
197             return NULL;
198         }
199 
200         // clear old files
201         if (clearold > 0 && _upload_clear_base(basepath, clearold) < 0) {
202             request->free(request);
203             return NULL;
204         }
205 
206         // save info
207         request->putstr(request, "_Q_UPLOAD_BASEPATH", basepath, true);
208         request->putint(request, "_Q_UPLOAD_CLEAROLD", clearold, true);
209 
210     } else {
211         request->remove(request, "_Q_UPLOAD_BASEPATH");
212         request->remove(request, "_Q_UPLOAD_CLEAROLD");
213     }
214 
215     return request;
216 }
217 
218 /**
219  * Parse one or more request(COOKIE/POST/GET) queries.
220  *
221  * @param request   qentry_t container pointer that parsed key/value pairs
222  *                  will be stored. NULL can be used to create a new container.
223  * @param method    Target mask consists of one or more of Q_CGI_COOKIE,
224  *                  Q_CGI_POST and Q_CGI_GET. Q_CGI_ALL or 0 can be used for
225  *                  parsing all of those types.
226  *
227  * @return qentry_t* handle if successful, NULL if there was insufficient
228  *         memory to allocate a new object.
229  *
230  * @code
231  *   qentry_t *req = qcgireq_parse(NULL, 0);
232  * @endcode
233  *
234  * @code
235  *   qentry_t *req = qcgireq_parse(req, Q_CGI_COOKIE | Q_CGI_POST);
236  * @endcode
237  *
238  * @note
239  * When multiple methods are specified, it'll be parsed in the order of
240  * (1)COOKIE, (2)POST (3)GET unless you call it separately multiple times.
241  */
qcgireq_parse(qentry_t * request,Q_CGI_T method)242 qentry_t *qcgireq_parse(qentry_t *request, Q_CGI_T method)
243 {
244     // initialize entry structure
245     if (request == NULL) {
246         request = qEntry();
247         if (request == NULL) return NULL;
248     }
249 
250     // parse COOKIE
251     if (method == Q_CGI_ALL || (method & Q_CGI_COOKIE) != 0) {
252         char *query = qcgireq_getquery(Q_CGI_COOKIE);
253         if (query != NULL) {
254             _parse_query(request, query, '=', ';', NULL);
255             free(query);
256         }
257     }
258 
259     //  parse POST method
260     if (method == Q_CGI_ALL || (method & Q_CGI_POST) != 0) {
261         const char *content_type = getenv("CONTENT_TYPE");
262         if (content_type == NULL) content_type = "";
263         if (!strncmp(content_type, "application/x-www-form-urlencoded",
264                      CONST_STRLEN("application/x-www-form-urlencoded"))) {
265             char *query = qcgireq_getquery(Q_CGI_POST);
266             if (query != NULL) {
267                 _parse_query(request, query, '=', '&', NULL);
268                 free(query);
269             }
270         } else if (!strncmp(content_type, "multipart/form-data",
271                             CONST_STRLEN("multipart/form-data"))) {
272             _parse_multipart(request);
273         }
274     }
275 
276     // parse GET method
277     if (method == Q_CGI_ALL || (method & Q_CGI_GET) != 0) {
278         char *query = qcgireq_getquery(Q_CGI_GET);
279         if (query != NULL) {
280             _parse_query(request, query, '=', '&', NULL);
281             free(query);
282         }
283     }
284 
285     return request;
286 }
287 
288 /**
289  * Get raw query string.
290  *
291  * @param method    One of Q_CGI_COOKIE, Q_CGI_POST or Q_CGI_GET.
292  *
293  * @return      malloced query string otherwise returns NULL;
294  *
295  * @code
296  *   char *query = qcgireq_getquery(Q_CGI_GET);
297  *   if(query != NULL) {
298  *     printf("%s\n", query);
299  *     free(query);
300  *   }
301  * @endcode
302  */
qcgireq_getquery(Q_CGI_T method)303 char *qcgireq_getquery(Q_CGI_T method)
304 {
305     if (method == Q_CGI_GET) {
306         char *query_string = getenv("QUERY_STRING");
307         if (query_string == NULL) return NULL;
308         char *req_uri = getenv("REQUEST_URI");
309 
310         char *query = NULL;
311 
312         // SSI query handling
313         if (strlen(query_string) == 0 && req_uri != NULL) {
314             char *cp;
315             for (cp = req_uri; *cp != '\0'; cp++) {
316                 if (*cp == '?') {
317                     cp++;
318                     break;
319                 }
320             }
321             query = strdup(cp);
322         } else {
323             query = strdup(query_string);
324         }
325 
326         return query;
327     } else if (method == Q_CGI_POST) {
328         char *request_method = getenv("REQUEST_METHOD");
329         char *content_length = getenv("CONTENT_LENGTH");
330         if (request_method == NULL ||
331             strcmp(request_method, "POST") ||
332             content_length == NULL) {
333             return NULL;
334         }
335 
336         int i, cl = atoi(content_length);
337         char *query = (char *)malloc(sizeof(char) * (cl + 1));
338         for (i = 0; i < cl; i++)query[i] = fgetc(stdin);
339         query[i] = '\0';
340         return query;
341     } else if (method == Q_CGI_COOKIE) {
342         char *http_cookie = getenv("HTTP_COOKIE");
343         if (http_cookie == NULL) return NULL;
344         char *query = strdup(http_cookie);
345         return query;
346     }
347 
348     return NULL;
349 }
350 
351 #ifndef _DOXYGEN_SKIP
352 
_parse_multipart(qentry_t * request)353 static int _parse_multipart(qentry_t *request)
354 {
355 #ifdef _WIN32
356     setmode(fileno(stdin), _O_BINARY);
357     setmode(fileno(stdout), _O_BINARY);
358 #endif
359 
360     char buf[MAX_LINEBUF];
361     int  amount = 0;
362 
363     /*
364      * For parse multipart/form-data method
365      */
366     char boundary_orig[256];
367     char boundary[256], boundaryEOF[256];
368 
369     // Force to check the boundary string length to defense overflow attack
370     int maxboundarylen = CONST_STRLEN("--");
371     maxboundarylen += strlen(strstr(getenv("CONTENT_TYPE"), "boundary=")
372                              + CONST_STRLEN("boundary="));
373     maxboundarylen += CONST_STRLEN("--");
374     maxboundarylen += CONST_STRLEN("\r\n");
375     if (maxboundarylen >= sizeof(boundary)) {
376         DEBUG("The boundary string is too long(Overflow Attack?). stopping process.");
377         return amount;
378     }
379 
380     // find boundary string - Hidai Kenichi made this patch for handling quoted boundary string
381     _q_strcpy(boundary_orig, sizeof(boundary_orig),
382               strstr(getenv("CONTENT_TYPE"), "boundary=") + CONST_STRLEN("boundary="));
383     _q_strtrim(boundary_orig);
384     _q_strunchar(boundary_orig, '"', '"');
385     snprintf(boundary, sizeof(boundary), "--%s", boundary_orig);
386     snprintf(boundaryEOF, sizeof(boundaryEOF), "--%s--", boundary_orig);
387 
388     // If you want to observe the string from stdin, uncomment this section.
389     /*
390     if (true) {
391         int i, j;
392         qcgires_setcontenttype(request, "text/html");
393 
394         printf("Content Length = %s<br>\n", getenv("CONTENT_LENGTH"));
395         printf("Boundary len %zu : %s<br>\n", strlen(boundary), boundary);
396         for (i = 0; boundary[i] != '\0'; i++) printf("%02X ", boundary[i]);
397         printf("<p>\n");
398 
399         for (j = 1; _q_fgets(buf, sizeof(buf), stdin) != NULL; j++) {
400             printf("Line %d, len %zu : %s<br>\n", j, strlen(buf), buf);
401             //for (i = 0; buf[i] != '\0'; i++) printf("%02X ", buf[i]);
402             printf("<br>\n");
403         }
404         exit(EXIT_SUCCESS);
405     }
406     */
407 
408     // check boundary
409     do {
410         if (_q_fgets(buf, sizeof(buf), stdin) == NULL) {
411             DEBUG("Bbrowser sent a non-HTTP compliant message.");
412             return amount;
413         }
414         _q_strtrim(buf);
415     } while (!strcmp(buf, "")); // skip blank lines
416 
417     // check starting boundary mark
418     if (strcmp(buf, boundaryEOF) == 0) {
419         // empty contents
420         return amount;
421     } else if (strcmp(buf, boundary) != 0) {
422         DEBUG("Invalid string format.");
423         return amount;
424     }
425 
426     // check file save mode
427     bool upload_filesave = false; // false: into memory, true: into file
428     const char *upload_basepath = request->getstr(request, "_Q_UPLOAD_BASEPATH", false);
429     if (upload_basepath != NULL) upload_filesave = true;
430 
431     bool finish;
432     for (finish = false; finish == false; amount++) {
433         char *name = NULL, *value = NULL, *filename = NULL, *contenttype = NULL;
434         int valuelen = 0;
435 
436         // parse header
437         while (_q_fgets(buf, sizeof(buf), stdin)) {
438             _q_strtrim(buf);
439             if (!strcmp(buf, "")) break;
440             else if (!strncasecmp(buf, "Content-Disposition: ", CONST_STRLEN("Content-Disposition: "))) {
441                 int c_count;
442 
443                 // get name field
444                 name = strdup(buf + CONST_STRLEN("Content-Disposition: form-data; name=\""));
445                 for (c_count = 0; (name[c_count] != '\"') && (name[c_count] != '\0'); c_count++);
446                 name[c_count] = '\0';
447 
448                 // get filename field
449                 if (strstr(buf, "; filename=\"") != NULL) {
450                     int erase;
451                     filename = strdup(strstr(buf, "; filename=\"") + CONST_STRLEN("; filename=\""));
452                     for (c_count = 0; (filename[c_count] != '\"') && (filename[c_count] != '\0'); c_count++);
453                     filename[c_count] = '\0';
454                     // remove directory from path, erase '\'
455                     for (erase = 0, c_count = strlen(filename) - 1; c_count >= 0; c_count--) {
456                         if (erase == 1) filename[c_count] = ' ';
457                         else {
458                             if (filename[c_count] == '\\') {
459                                 erase = 1;
460                                 filename[c_count] = ' ';
461                             }
462                         }
463                     }
464                     _q_strtrim(filename);
465 
466                     // empty attachment
467                     if (!strcmp(filename, "")) {
468                         free(filename);
469                         filename = NULL;
470                     }
471                 }
472             } else if (!strncasecmp(buf, "Content-Type: ", CONST_STRLEN("Content-Type: "))) {
473                 contenttype = strdup(buf + CONST_STRLEN("Content-Type: "));
474                 _q_strtrim(contenttype);
475             }
476         }
477 
478         // check
479         if (name == NULL) {
480             DEBUG("bug or invalid format.");
481             continue;
482         }
483 
484         // get value
485         if (filename != NULL && upload_filesave == true) {
486             char *tp, *savename = strdup(filename);
487             for (tp = savename; *tp != '\0'; tp++) {
488                 if (*tp == ' ') *tp = '_'; // replace ' ' to '_'
489             }
490             value = _parse_multipart_value_into_disk(
491                         boundary, upload_basepath, savename, &valuelen, &finish);
492             free(savename);
493 
494             if (value != NULL) request->putstr(request, name, value, false);
495             else request->putstr(request, name, "(parsing failure)", false);
496         } else {
497             value = _parse_multipart_value_into_memory(boundary, &valuelen, &finish);
498 
499             if (value != NULL) request->put(request, name, value, valuelen+1, false);
500             else request->putstr(request, name, "(parsing failure)", false);
501         }
502 
503         // store additional information
504         if (value != NULL && filename != NULL) {
505             char ename[255+10+1];
506 
507             // store data length, 'NAME.length'
508             snprintf(ename, sizeof(ename), "%s.length", name);
509             request->putint(request, ename, valuelen, false);
510 
511             // store filename, 'NAME.filename'
512             snprintf(ename, sizeof(ename), "%s.filename", name);
513             request->putstr(request, ename, filename, false);
514 
515             // store contenttype, 'NAME.contenttype'
516             snprintf(ename, sizeof(ename), "%s.contenttype", name);
517             request->putstr(request, ename, ((contenttype!=NULL)?contenttype:""), false);
518 
519             if (upload_filesave == true) {
520                 snprintf(ename, sizeof(ename), "%s.savepath", name);
521                 request->putstr(request, ename, value, false);
522             }
523         }
524 
525         // free resources
526         if (name != NULL) free(name);
527         if (value != NULL) free(value);
528         if (filename != NULL) free(filename);
529         if (contenttype != NULL) free(contenttype);
530     }
531 
532     return amount;
533 }
534 
535 #define _Q_MULTIPART_CHUNK_SIZE     (16 * 1024)
_parse_multipart_value_into_memory(char * boundary,int * valuelen,bool * finish)536 static char *_parse_multipart_value_into_memory(char *boundary, int *valuelen,
537         bool *finish)
538 {
539     char boundaryEOF[256], rnboundaryEOF[256];
540     char boundaryrn[256], rnboundaryrn[256];
541     int  boundarylen, boundaryEOFlen;
542 
543     char *value;
544     int  length;
545     int  c, c_count, mallocsize;
546 
547     // set boundary strings
548     snprintf(boundaryEOF, sizeof(boundaryEOF), "%s--", boundary);
549     snprintf(rnboundaryEOF, sizeof(rnboundaryEOF), "\r\n%s--", boundary);
550     snprintf(boundaryrn, sizeof(boundaryrn), "%s\r\n", boundary);
551     snprintf(rnboundaryrn, sizeof(rnboundaryrn), "\r\n%s\r\n", boundary);
552 
553     boundarylen    = strlen(boundary);
554     boundaryEOFlen = strlen(boundaryEOF);
555 
556     for (value = NULL, length = 0, mallocsize = _Q_MULTIPART_CHUNK_SIZE, c_count = 0;
557          (c = fgetc(stdin)) != EOF; ) {
558         if (c_count == 0) {
559             value = (char *)malloc(sizeof(char) * mallocsize);
560             if (value == NULL) {
561                 DEBUG("Memory allocation fail.");
562                 *finish = true;
563                 return NULL;
564             }
565         } else if (c_count == mallocsize - 1) {
566             char *valuetmp;
567 
568             mallocsize *= 2;
569 
570             // Here, we do not use realloc(). Because sometimes it is unstable.
571             valuetmp = (char *)malloc(sizeof(char) * mallocsize);
572             if (valuetmp == NULL) {
573                 DEBUG("Memory allocation fail.");
574                 free(value);
575                 *finish = true;
576                 return NULL;
577             }
578             memcpy(valuetmp, value, c_count);
579             free(value);
580             value = valuetmp;
581         }
582         value[c_count++] = (char)c;
583 
584         // check end
585         if ((c == '\n') || (c == '-')) {
586             value[c_count] = '\0';
587 
588             if ((c_count - (2 + boundarylen + 2)) >= 0) {
589                 if (!strcmp(value + (c_count - (2 + boundarylen + 2)), rnboundaryrn)) {
590                     value[c_count - (2 + boundarylen + 2)] = '\0';
591                     length = c_count - (2 + boundarylen + 2);
592                     break;
593                 }
594             }
595             if ((c_count - (2 + boundaryEOFlen)) >= 0) {
596                 if (!strcmp(value + (c_count - (2 + boundaryEOFlen)), rnboundaryEOF)) {
597                     value[c_count - (2 + boundaryEOFlen)] = '\0';
598                     length = c_count - (2 + boundaryEOFlen);
599                     *finish = true;
600                     break;
601                 }
602             }
603 
604             // For MS Explore on MAC
605             if ((c_count - (boundarylen + 2)) == 0) {
606                 if (!strcmp(value, boundaryrn)) {
607                     value[0] = '\0';
608                     length = 0;
609                     break;
610                 }
611             }
612             if ((c_count - boundaryEOFlen) == 0) {
613                 if (!strcmp(value, boundaryEOF)) {
614                     value[0] = '\0';
615                     length = 0;
616                     *finish = true;
617                     break;
618                 }
619             }
620         }
621     }
622 
623     if (c == EOF) {
624         DEBUG("Broken stream.");
625         if (value != NULL) free(value);
626         *finish = true;
627         return NULL;
628     }
629 
630     *valuelen = length;
631     return value;
632 }
633 
_parse_multipart_value_into_disk(const char * boundary,const char * savedir,const char * filename,int * filelen,bool * finish)634 static char *_parse_multipart_value_into_disk(const char *boundary,
635         const char *savedir, const char *filename, int *filelen, bool *finish)
636 {
637     char boundaryEOF[256], rnboundaryEOF[256];
638     char boundaryrn[256], rnboundaryrn[256];
639     int  boundarylen, boundaryEOFlen;
640 
641     // input
642     char buffer[_Q_MULTIPART_CHUNK_SIZE];
643     int  bufc;
644     int  c;
645 
646     // set boundary strings
647     snprintf(boundaryEOF, sizeof(boundaryEOF), "%s--", boundary);
648     snprintf(rnboundaryEOF, sizeof(rnboundaryEOF), "\r\n%s--", boundary);
649     snprintf(boundaryrn, sizeof(boundaryrn), "%s\r\n", boundary);
650     snprintf(rnboundaryrn, sizeof(rnboundaryrn), "\r\n%s\r\n", boundary);
651 
652     boundarylen    = strlen(boundary);
653     boundaryEOFlen = strlen(boundaryEOF);
654 
655     // open temp file
656     char upload_path[PATH_MAX];
657     snprintf(upload_path, sizeof(upload_path), "%s/q_XXXXXX", savedir);
658 
659     int upload_fd = mkstemp(upload_path);
660     if (upload_fd < 0) {
661         DEBUG("Can't open file %s", upload_path);
662         *finish = true;
663         return NULL;
664     }
665 
666     // change permission
667 #if defined(__MINGW32__) && defined(_WIN32) && !defined(__CYGWIN__)
668     chmod(upload_path, DEF_FILE_MODE);
669 #else
670     fchmod(upload_fd, DEF_FILE_MODE);
671 #endif
672 
673     // read stream
674     bool ioerror = false;
675     int upload_length;
676     for (upload_length = 0, bufc = 0; (c = fgetc(stdin)) != EOF; ) {
677         if (bufc == sizeof(buffer) - 1) {
678             // save
679             ssize_t leftsize = boundarylen + 8;
680             ssize_t savesize = bufc - leftsize;
681             ssize_t saved = write(upload_fd, buffer, savesize);
682             if (saved <= 0) {
683                 ioerror = true;
684                 break;
685             }
686             leftsize = bufc - saved;
687             memcpy(buffer, buffer+saved, leftsize);
688             bufc = leftsize;
689         }
690         buffer[bufc++] = (char)c;
691         upload_length++;
692 
693         // check end
694         if ((c == '\n') || (c == '-')) {
695             buffer[bufc] = '\0';
696 
697             if ((bufc - (2 + boundarylen + 2)) >= 0) {
698                 if (!strcmp(buffer + (bufc - (2 + boundarylen + 2)), rnboundaryrn)) {
699                     bufc          -= (2 + boundarylen + 2);
700                     upload_length -= (2 + boundarylen + 2);
701                     break;
702                 }
703             }
704             if ((bufc - (2 + boundaryEOFlen)) >= 0) {
705                 if (!strcmp(buffer + (bufc - (2 + boundaryEOFlen)), rnboundaryEOF)) {
706                     bufc          -= (2 + boundaryEOFlen);
707                     upload_length -= (2 + boundaryEOFlen);
708                     *finish = true;
709                     break;
710                 }
711             }
712 
713             // For MS Explore on MAC
714             if (upload_length == bufc) {
715                 if ((bufc - (boundarylen + 2)) == 0) {
716                     if (!strcmp(buffer, boundaryrn)) {
717                         bufc = 0;
718                         upload_length = 0;
719                         break;
720                     }
721                 }
722                 if ((bufc - boundaryEOFlen) == 0) {
723                     if (!strcmp(buffer, boundaryEOF)) {
724                         bufc = 0;
725                         upload_length = 0;
726                         *finish = true;
727                         break;
728                     }
729                 }
730             }
731         }
732     }
733 
734     // save rest
735     while (bufc > 0) {
736         ssize_t saved = write(upload_fd, buffer, bufc);
737         if (saved <= 0) {
738             ioerror = true;
739             break;
740         }
741         bufc -= saved;
742     }
743     close(upload_fd);
744 
745     // error occured
746     if (c == EOF || ioerror == true) {
747         DEBUG("I/O error. (errno=%d)", (ioerror == true) ? errno : 0);
748         *finish = true;
749         return NULL;
750     }
751 
752     // succeed
753     *filelen = upload_length;
754     return strdup(upload_path);
755 }
756 
_upload_clear_base(const char * upload_basepath,int upload_clearold)757 static int _upload_clear_base(const char *upload_basepath, int upload_clearold)
758 {
759 #ifdef _WIN32
760     return false;
761 #else
762     if (upload_clearold <= 0) return -1;
763 
764     // open upload folder
765     DIR     *dp;
766     if ((dp = opendir(upload_basepath)) == NULL) return false;
767 
768     time_t now = time(NULL);
769     int removed = 0;
770     struct  dirent *dirp;
771     while ((dirp = readdir(dp)) != NULL) {
772         if (!strcmp(dirp->d_name, ".") ||
773             !strcmp(dirp->d_name, "..") ||
774             strncmp(dirp->d_name, "q_", 2) != 0) {
775             continue;
776         }
777 
778         char filepath[PATH_MAX];
779         snprintf(filepath, sizeof(filepath), "%s/%s",
780                  upload_basepath, dirp->d_name);
781 
782         // check file date
783         struct stat filestat;
784         if (stat(filepath, &filestat) != 0) continue;
785         if (filestat.st_mtime >= now + upload_clearold) continue;
786 
787         // remove file
788         if (_q_unlink(filepath) == 0) removed++;
789     }
790     closedir(dp);
791 
792     return removed;
793 #endif
794 }
795 
_parse_query(qentry_t * request,const char * query,char equalchar,char sepchar,int * count)796 static qentry_t *_parse_query(qentry_t *request, const char *query,
797                               char equalchar, char sepchar, int *count)
798 {
799     if (request == NULL) {
800         request = qEntry();
801         if (request == NULL) return NULL;
802     }
803 
804     char *newquery = NULL;
805     int cnt = 0;
806 
807     if (query != NULL) newquery = strdup(query);
808     while (newquery && *newquery) {
809         char *value = _q_makeword(newquery, sepchar);
810         char *name = _q_strtrim(_q_makeword(value, equalchar));
811         _q_urldecode(name);
812         _q_urldecode(value);
813 
814         if (request->putstr(request, name, value, false) == true) cnt++;
815         free(name);
816         free(value);
817     }
818     if (newquery != NULL) free(newquery);
819     if (count != NULL) *count = cnt;
820 
821     return request;
822 }
823 
824 #endif /* _DOXYGEN_SKIP */
825