Skip to content
This repository
Browse code

Added initial support for POST files.

Not done in the right way yet as it loads the file in memory (max_post_size) instead of straigth to disk (max_file_size). Still some more issues left.
  • Loading branch information...
commit 7f7ecc0b0003256b3b8ca855132c857bcaf1618d 1 parent 81e3060
David Moreno Montero authored

Showing 3 changed files with 255 additions and 178 deletions. Show diff stats Hide diff stats

  1. +2 1  Doxyfile
  2. +231 173 src/onion/request.c
  3. +22 4 tests/03-methods/01-methods.c
3  Doxyfile
@@ -650,7 +650,8 @@ RECURSIVE = YES
650 650 # subdirectory from a directory tree whose root is specified with the INPUT tag.
651 651
652 652 EXCLUDE = src/onion/onion_types_internal.h \
653   - html
  653 + doc \
  654 + build*
654 655
655 656 # The EXCLUDE_SYMLINKS tag can be used select whether or not files or
656 657 # directories that are symbolic links (a Unix filesystem feature) are excluded
404 src/onion/request.c
@@ -21,6 +21,8 @@
21 21 #include <stdlib.h>
22 22 #include <libgen.h>
23 23 #include <ctype.h>
  24 +#include <fcntl.h>
  25 +#include <unistd.h>
24 26
25 27 #include "server.h"
26 28 #include "dict.h"
@@ -36,6 +38,9 @@
36 38 static int onion_request_parse_query(onion_request *req);
37 39 static onion_connection_status onion_request_write_post_urlencoded(onion_request *req, const char *data, size_t length);
38 40 static onion_connection_status onion_request_write_post_multipart(onion_request *req, const char *data, size_t length);
  41 +static void onion_request_parse_query_to_dict(onion_dict *dict, char *p);
  42 +static onion_connection_status onion_request_parse_multipart(onion_request *req);
  43 +static onion_connection_status onion_request_parse_multipart_part(onion_request *req, char *init_of_part, size_t length);
39 44
40 45 /// Used by req->parse_state, states are:
41 46 typedef enum parse_state_e{
@@ -94,178 +99,6 @@ void onion_request_free(onion_request *req){
94 99 free(req);
95 100 }
96 101
97   -/**
98   - * @short Parses a delimited multipart part.
99   - *
100   - * It overwrites as needed the init_of_part buffer, to set the right data at req->post. This dict uses the
101   - * data at init_of_part so dont use it after deleting init_of_part.
102   - */
103   -static onion_connection_status onion_request_parse_multipart_part(onion_request *req, char *init_of_part){
104   - //ONION_DEBUG("Parse multipart part:\n%s",init_of_part);
105   -
106   - // First parse the headers
107   - onion_dict *headers=onion_dict_new();
108   - int in_headers=1;
109   - char *p=init_of_part;
110   - char *lp=p;
111   - const char *key=NULL;
112   - const char *value=NULL;
113   -
114   - while(in_headers){
115   - if (*p=='\0'){
116   - ONION_ERROR("Unexpected end of multipart part headers");
117   - goto error_part;
118   - }
119   - if (!key){
120   - if (lp==p && ( *p=='\r' || *p=='\n' ) )
121   - in_headers=0;
122   - else if (*p==':'){
123   - value=p+1;
124   - *p='\0';
125   - key=lp;
126   - }
127   - }
128   - else{ // value
129   - if (*p=='\n' || *p=='\r'){
130   - *p='\0';
131   - while (*value==' ') value++;
132   - while (*key==' ' || *key=='\r' || *key=='\n') key++;
133   - ONION_DEBUG0("Found header <%s> = <%s>",key,value);
134   - onion_dict_add(headers, key, value, 0);
135   - key=NULL;
136   - value=NULL;
137   - lp=p+1;
138   - }
139   - }
140   - p++;
141   - }
142   -
143   - // Remove heading and trailing [\r][\n].
144   - if (*p=='\r') p++;
145   - if (*p=='\n') p++;
146   - int len=strlen(p);
147   - if (*(p+len-1)=='\n'){ *(p+len-1)='\0'; len--; }
148   - if (*(p+len-1)=='\r') *(p+len-1)='\0';
149   - ONION_DEBUG0("Data is <%s>",p);
150   -
151   - // Get field name
152   - char *name=(char*)onion_dict_get(headers, "Content-Disposition");
153   - if (!name){
154   - ONION_ERROR("Unnamed POST field");
155   - return OCS_INTERNAL_ERROR;
156   - }
157   - name=strstr(name,"name=")+5;
158   -
159   - if (name[0]=='"'){
160   - name++;
161   - name[strlen(name)-1]='\0';
162   - }
163   -
164   - ONION_DEBUG0("Set POST data %s=%s",name,p);
165   - onion_dict_add(req->post, name, p, 0);
166   -
167   - onion_dict_free(headers);
168   - return 0;
169   -error_part: // I dont like, but maybe its best solution...
170   - onion_dict_free(headers);
171   - return OCS_INTERNAL_ERROR;
172   -}
173   -
174   -/**
175   - * @short Parses the multipart post
176   - *
177   - * Gets the data from req->post_buffer, and at req->buffer is the marker. Reserves memory for req->post always.
178   - *
179   - * It overwrites the data at req->post_buffer and uses it to fill (and not copy) data as needed, so dont free(req->post)
180   - * if you plan to use the POST data.
181   - *
182   - * @return a close error or 0 if everything ok.
183   - */
184   -static onion_connection_status onion_request_parse_multipart(onion_request *req){
185   - if (!req->post)
186   - req->post=onion_dict_new();
187   -
188   - ONION_DEBUG("multipart:\n%s",req->post_buffer);
189   -
190   - const char *token=req->buffer;
191   - unsigned int token_length=strlen(token); // token have -- at head, and maybe -- at end.
192   - unsigned int i;
193   - char *p=req->post_buffer;
194   - char *init_of_part=NULL;
195   - int post_size=req->post_buffer_pos-4-token_length; // size, minumum, without the final token.
196   - ONION_DEBUG0("Final POST size without the final token: %d",post_size);
197   - for (i=0;i<post_size;i++){
198   - if (*p=='-' && *(p+1)=='-' && memcmp(p+2,token,token_length)==0){ // found token.
199   - *p=0;
200   - ONION_DEBUG0("Found part");
201   - if (init_of_part){ // Ok, i have it delimited, parse a part
202   - int r=onion_request_parse_multipart_part(req, init_of_part);
203   - if (r){
204   - ONION_DEBUG("Return from parse part. Should not be error (%d)",r);
205   - return r;
206   - }
207   - }
208   - p+=2+token_length; // skip the token.
209   - if (*p=='-' && *(p+1)=='-'){
210   - p+=2;
211   - ONION_DEBUG("All parsed");
212   - break;
213   - }
214   - while (*p=='\n' || *p=='\r') p++;
215   - init_of_part=p;
216   - }
217   - else
218   - p++;
219   - }
220   - while (*p=='\n' || *p=='\r') p++;
221   - if (*p!='\0'){
222   - ONION_ERROR("At end of multipart message, found more data: %-16s...",p);
223   - return OCS_INTERNAL_ERROR;
224   - }
225   -
226   - ONION_DEBUG("Multiparts parsed ok");
227   - return 0;
228   -}
229   -
230   -
231   -/**
232   - * @short Parses the query part to a given dictionary.
233   - *
234   - * The data is overwriten as necessary. It is NOT dupped, so if you free this char *p, please free the tree too.
235   - */
236   -static void onion_request_parse_query_to_dict(onion_dict *dict, char *p){
237   - ONION_DEBUG0("Query to dict %s",p);
238   - char *key, *value;
239   - int state=0; // 0 key, 1 value
240   - key=p;
241   - while(*p){
242   - if (state==0){
243   - if (*p=='='){
244   - *p='\0';
245   - value=p+1;
246   - state=1;
247   - }
248   - }
249   - else{
250   - if (*p=='&'){
251   - *p='\0';
252   - onion_unquote_inplace(key);
253   - onion_unquote_inplace(value);
254   - ONION_DEBUG0("Adding key %s=%-16s",key,value);
255   - onion_dict_add(dict, key, value, 0);
256   - key=p+1;
257   - state=0;
258   - }
259   - }
260   - p++;
261   - }
262   - if (state!=0){
263   - onion_unquote_inplace(key);
264   - onion_unquote_inplace(value);
265   - ONION_DEBUG0("Adding key %s=%-16s",key,value);
266   - onion_dict_add(dict, key, value, 0);
267   - }
268   -}
269 102
270 103 /// Parses the first query line, GET / HTTP/1.1
271 104 static onion_connection_status onion_request_fill_query(onion_request *req, const char *data){
@@ -521,7 +354,8 @@ static onion_connection_status onion_request_write_post_multipart(onion_request
521 354 }
522 355 content_length=atoi(cl);
523 356 if (req->server->max_post_size<content_length){
524   - ONION_WARNING("Onion POST limit is %ld bytes of data (this have %ld). Increase limit with onion_server_set_max_post_size.",(unsigned long)req->server->max_post_size,(unsigned long)content_length);
  357 + ONION_WARNING("Onion POST limit is %ld bytes of data (this have %ld). Increase limit with onion_server_set_max_post_size.",
  358 + (unsigned long)req->server->max_post_size,(unsigned long)content_length);
525 359 return OCS_INTERNAL_ERROR;
526 360 }
527 361
@@ -604,6 +438,230 @@ onion_connection_status onion_request_write(onion_request *req, const char *data
604 438 }
605 439
606 440
  441 +/**
  442 + * @short Parses a delimited multipart part.
  443 + *
  444 + * It overwrites as needed the init_of_part buffer, to set the right data at req->post. This dict uses the
  445 + * data at init_of_part so dont use it after deleting init_of_part.
  446 + */
  447 +static onion_connection_status onion_request_parse_multipart_part(onion_request *req, char *init_of_part, size_t length){
  448 + // First parse the headers
  449 + onion_dict *headers=onion_dict_new();
  450 + int in_headers=1;
  451 + char *p=init_of_part;
  452 + char *lp=p;
  453 + const char *key=NULL;
  454 + const char *value=NULL;
  455 + int header_length=0;
  456 +
  457 + while(in_headers){
  458 + if (*p=='\0'){
  459 + ONION_ERROR("Unexpected end of multipart part headers");
  460 + onion_dict_free(headers);
  461 + return OCS_INTERNAL_ERROR;
  462 + }
  463 + if (!key){
  464 + if (lp==p && ( *p=='\r' || *p=='\n' ) )
  465 + in_headers=0;
  466 + else if (*p==':'){
  467 + value=p+1;
  468 + *p='\0';
  469 + key=lp;
  470 + }
  471 + }
  472 + else{ // value
  473 + if (*p=='\n' || *p=='\r'){
  474 + *p='\0';
  475 + while (*value==' ') value++;
  476 + while (*key==' ' || *key=='\r' || *key=='\n') key++;
  477 + ONION_DEBUG0("Found header <%s> = <%s>",key,value);
  478 + onion_dict_add(headers, key, value, 0);
  479 + key=NULL;
  480 + value=NULL;
  481 + lp=p+1;
  482 + }
  483 + }
  484 + p++;
  485 + header_length++;
  486 + }
  487 +
  488 + // Remove heading and trailing [\r][\n].
  489 + if (*p=='\r') p++;
  490 + if (*p=='\n') p++;
  491 + int len=strlen(p);
  492 + if (*(p+len-1)=='\n'){ *(p+len-1)='\0'; len--; }
  493 + if (*(p+len-1)=='\r') *(p+len-1)='\0';
  494 + ONION_DEBUG0("Data is <%s> (%d bytes)",p, length-header_length);
  495 +
  496 + // Get field name
  497 + char *name=(char*)onion_dict_get(headers, "Content-Disposition");
  498 + if (!name){
  499 + ONION_ERROR("Unnamed POST field");
  500 + return OCS_INTERNAL_ERROR;
  501 + }
  502 + name=strstr(name,"name=")+5;
  503 +
  504 + char *filename=NULL;
  505 +
  506 + if (name[0]=='"'){
  507 + name++;
  508 + char *q=name;
  509 + while (*q!='\0'){
  510 + if (*q=='"'){
  511 + *q='\0';
  512 + break;
  513 + }
  514 + q++;
  515 + }
  516 + q++;
  517 + filename=strstr(q,"filename=");
  518 + if (filename){ // Im in a FILE post.
  519 + filename+=9;
  520 + if (*filename=='"'){
  521 + filename++;
  522 + q=filename;
  523 + while (*q!='\0'){
  524 + if (*q=='"'){
  525 + *q='\0';
  526 + break;
  527 + }
  528 + q++;
  529 + }
  530 + }
  531 + }
  532 + }
  533 + if (filename){
  534 + ONION_DEBUG0("Set FILE data %s=%s, %d bytes",name,filename, length-header_length);
  535 + char tfilename[256];
  536 + const char *tmp=getenv("TEMP");
  537 + tmp=tmp ? tmp : "/tmp/";
  538 +
  539 + snprintf(tfilename,sizeof(tfilename),"%s/%s-XXXXXX",tmp,filename);
  540 + int fd=mkstemp(tfilename);
  541 + if (fd<0){
  542 + ONION_ERROR("Could not open temporary file %s",tfilename);
  543 + onion_dict_free(headers);
  544 + return OCS_INTERNAL_ERROR;
  545 + }
  546 + size_t w=0;
  547 + w+=write(fd,p, length-header_length);
  548 + close(fd);
  549 +
  550 + ONION_DEBUG("Saved temporal FILE data at %s",tfilename);
  551 + if (w!=length-header_length){
  552 + ONION_ERROR("Could not save completly the POST file. Saved %d of %d bytes.",w,length-header_length);
  553 + onion_dict_free(headers);
  554 + return OCS_INTERNAL_ERROR;
  555 + }
  556 + if (!req->files)
  557 + req->files=onion_dict_new();
  558 + onion_dict_add(req->files, filename, tfilename, OD_DUP_VALUE); // Temporal file at files
  559 + onion_dict_add(req->post, name, filename, 0); // filename at post
  560 + }
  561 + else{
  562 + ONION_DEBUG0("Set POST data %s=%s",name,p);
  563 + onion_dict_add(req->post, name, p, 0);
  564 + }
  565 +
  566 + onion_dict_free(headers);
  567 + return 0;
  568 +}
  569 +
  570 +/**
  571 + * @short Parses the multipart post
  572 + *
  573 + * Gets the data from req->post_buffer, and at req->buffer is the marker. Reserves memory for req->post always.
  574 + *
  575 + * It overwrites the data at req->post_buffer and uses it to fill (and not copy) data as needed, so dont free(req->post)
  576 + * if you plan to use the POST data.
  577 + *
  578 + * @return a close error or 0 if everything ok.
  579 + */
  580 +static onion_connection_status onion_request_parse_multipart(onion_request *req){
  581 + if (!req->post)
  582 + req->post=onion_dict_new();
  583 +
  584 + ONION_DEBUG("multipart:\n%s",req->post_buffer);
  585 +
  586 + const char *token=req->buffer;
  587 + unsigned int token_length=strlen(token); // token have -- at head, and maybe -- at end.
  588 + char *p=req->post_buffer;
  589 + char *end_p=p + (req->post_buffer_pos);
  590 + char *init_of_part=NULL;
  591 + ONION_DEBUG0("Final POST size without the final token: %ld",(long int)(end_p-p));
  592 + while (p<end_p){ // equal important, as the last token is really the last if everything ok.
  593 + if (*p=='-' && *(p+1)=='-' && memcmp(p+2,token,token_length)==0){ // found token.
  594 + *p='\0';
  595 + ONION_DEBUG0("Found part");
  596 + if (init_of_part){ // Ok, i have it delimited, parse a part
  597 + int r=onion_request_parse_multipart_part(req, init_of_part, (size_t)((p-4)-init_of_part)); // It assumes \r\n, but maybe broken client only \n. FIXME.
  598 + if (r){
  599 + ONION_DEBUG("Return from parse part. Should not be error, but it is (%d)",r);
  600 + return r;
  601 + }
  602 + }
  603 + p+=2+token_length; // skip the token.
  604 + if (*p=='-' && *(p+1)=='-'){
  605 + p+=2;
  606 + ONION_DEBUG("All parsed");
  607 + break;
  608 + }
  609 + while (*p=='\n' || *p=='\r'){ p++; }
  610 + init_of_part=p;
  611 + }
  612 + else
  613 + p++;
  614 + }
  615 + while (*p=='\n' || *p=='\r'){ p++; }
  616 + if (p!=end_p){
  617 + ONION_ERROR("At end of multipart message, found more data (read %d, have %d): %-16s...",(int)(p-req->post_buffer),req->post_buffer_pos,p);
  618 + return OCS_INTERNAL_ERROR;
  619 + }
  620 +
  621 + ONION_DEBUG("Multiparts parsed ok");
  622 + return 0;
  623 +}
  624 +
  625 +
  626 +/**
  627 + * @short Parses the query part to a given dictionary.
  628 + *
  629 + * The data is overwriten as necessary. It is NOT dupped, so if you free this char *p, please free the tree too.
  630 + */
  631 +static void onion_request_parse_query_to_dict(onion_dict *dict, char *p){
  632 + ONION_DEBUG0("Query to dict %s",p);
  633 + char *key, *value;
  634 + int state=0; // 0 key, 1 value
  635 + key=p;
  636 + while(*p){
  637 + if (state==0){
  638 + if (*p=='='){
  639 + *p='\0';
  640 + value=p+1;
  641 + state=1;
  642 + }
  643 + }
  644 + else{
  645 + if (*p=='&'){
  646 + *p='\0';
  647 + onion_unquote_inplace(key);
  648 + onion_unquote_inplace(value);
  649 + ONION_DEBUG0("Adding key %s=%-16s",key,value);
  650 + onion_dict_add(dict, key, value, 0);
  651 + key=p+1;
  652 + state=0;
  653 + }
  654 + }
  655 + p++;
  656 + }
  657 + if (state!=0){
  658 + onion_unquote_inplace(key);
  659 + onion_unquote_inplace(value);
  660 + ONION_DEBUG0("Adding key %s=%-16s",key,value);
  661 + onion_dict_add(dict, key, value, 0);
  662 + }
  663 +}
  664 +
607 665 /// Returns a pointer to the string with the current path. Its a const and should not be trusted for long time.
608 666 const char *onion_request_get_path(onion_request *req){
609 667 return req->path;
26 tests/03-methods/01-methods.c
@@ -24,7 +24,11 @@
24 24 #include <onion/response.h>
25 25
26 26 void print_dict_element(const char *key, const char *value, onion_response *res){
27   - onion_response_printf(res,"<li>%s = %s</li>",key,value);
  27 + onion_response_write0(res,"<li> ");
  28 + onion_response_write0(res,key);
  29 + onion_response_write0(res," = ");
  30 + onion_response_write0(res,value);
  31 + onion_response_write0(res,"</li>\n");
28 32 }
29 33
30 34 onion_connection_status method(void *ignore, onion_request *req){
@@ -61,9 +65,23 @@ onion_connection_status method(void *ignore, onion_request *req){
61 65 onion_response_printf(res,"</ul></li>\n");
62 66
63 67 onion_response_write0(res,"<p>\n");
64   - onion_response_write0(res,"<form method=\"GET\"><input type=\"text\" name=\"test\"><input type=\"submit\" name=\"submit\" value=\"GET\"></form><p>\n");
65   - onion_response_write0(res,"<form method=\"POST\" enctype=\"application/x-www-form-urlencoded\"><input type=\"text\" name=\"test\"><input type=\"submit\" name=\"submit\" value=\"POST urlencoded\"></form><p>\n");
66   - onion_response_write0(res,"<form method=\"POST\" enctype=\"multipart/form-data\"><input type=\"text\" name=\"test\"><input type=\"submit\" name=\"submit\" value=\"POST multipart\"></form><p>\n");
  68 + onion_response_write0(res,"<form method=\"GET\">"
  69 + "<input type=\"text\" name=\"test\">"
  70 + "<input type=\"submit\" name=\"submit\" value=\"GET\">"
  71 + "</form><p>\n");
  72 + onion_response_write0(res,"<form method=\"POST\" enctype=\"application/x-www-form-urlencoded\">"
  73 + "<textarea name=\"text\"></textarea>"
  74 + "<input type=\"text\" name=\"test\">"
  75 + "<input type=\"submit\" name=\"submit\" value=\"POST urlencoded\">"
  76 + "</form>"
  77 + "<p>\n");
  78 + onion_response_write0(res,"<form method=\"POST\" enctype=\"multipart/form-data\">"
  79 + "<input type=\"file\" name=\"file\">"
  80 + "<textarea name=\"text\"></textarea>"
  81 + "<input type=\"text\" name=\"test\">"
  82 + "<input type=\"submit\" name=\"submit\" value=\"POST multipart\">"
  83 + "</form>"
  84 + "<p>\n");
67 85
68 86 onion_response_write0(res,"</body></html>");
69 87

0 comments on commit 7f7ecc0

Please sign in to comment.
Something went wrong with that request. Please try again.