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