Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

1138 lines (979 sloc) 33.204 kB
/*
Onion HTTP server library
Copyright (C) 2010-2014 David Moreno Montero and othes
This library is free software; you can redistribute it and/or
modify it under the terms of, at your choice:
a. the Apache License Version 2.0.
b. the GNU General Public License as published by the
Free Software Foundation; either version 2.0 of the License,
or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of both libraries, if not see
<http://www.gnu.org/licenses/> and
<http://www.apache.org/licenses/LICENSE-2.0>.
*/
#include <string.h>
#include <stdlib.h>
#include <libgen.h>
#include <ctype.h>
#include <fcntl.h>
#include <unistd.h>
#include <assert.h>
#include "dict.h"
#include "request.h"
#include "types_internal.h"
#include "codecs.h"
#include "log.h"
#include "block.h"
#include "low.h"
#include "ptr_list.h"
/**
* @short Known token types. This is merged with onion_connection_status as return value at token readers.
* @private
*/
typedef enum{
STRING=1001, // I start on 1000, to prevent collission with error codes
KEY=1002,
LINE=1003,
NEW_LINE=1004,
URLENCODE=1005,
MULTIPART_BOUNDARY=1006,
MULTIPART_END=1007,
MULTIPART_NEXT=1008,
STRING_NEW_LINE=1009,
}onion_token_token;
/// @private
typedef struct onion_token_s{
char str[256*4*8];
off_t pos;
char *extra; // Only used when need some previous data, like at header value, i need the key
size_t extra_size;
}onion_token;
/// @private
typedef struct onion_buffer_s{
const char *data;
size_t size;
off_t pos;
}onion_buffer;
/**
* @short This struct is mapped at token->extra. The token, _start and data are maped to token->extra area too.
* @private
*
* This is a bit juggling with pointers, but needed to keep the deallocation simple, and the token object minimal in most cases.
*
* token->pos is the pointer to the free area and token->extra_size is the real size.
*/
typedef struct onion_multipart_buffer_s{
char *boundary; /// Pointer to where the boundary is stored
size_t size; /// Size of the boundary
off_t pos; /// Pos at the boundarycheck
off_t startpos; /// Where did the boundary started (\n\r vs \n)
char *data; /// When need a new buffered data (always from stream), get it from here. This advances as used.
char *name; /// point to a in data position
char *filename; /// point to a in data position
size_t file_total_size; /// Total size of all file read data
size_t post_total_size; /// Total size of post, to check limits
int fd; /// If file, the file descriptor.
}onion_multipart_buffer;
static void onion_request_parse_query_to_dict(onion_dict *dict, char *p);
static int onion_request_parse_query(onion_request *req);
static onion_connection_status prepare_POST(onion_request *req);
static onion_connection_status prepare_CONTENT_LENGTH(onion_request *req);
static onion_connection_status prepare_PUT(onion_request *req);
/// Reads a string until a non-string char. Returns an onion_token
int token_read_STRING(onion_token *token, onion_buffer *data){
if (data->pos>=data->size)
return OCS_NEED_MORE_DATA;
char c=data->data[data->pos++];
while (!isspace(c)){
token->str[token->pos++]=c;
if (data->pos>=data->size)
return OCS_NEED_MORE_DATA;
if (token->pos>=(sizeof(token->str)-1)){
char tmp[16];
strncpy(tmp, token->str, 16);
tmp[15]='\0';
ONION_ERROR("Token too long to parse it. Part read start as %s (%d bytes)",tmp,token->pos);
return OCS_INTERNAL_ERROR;
}
c=data->data[data->pos++];
}
int ret;
if (c=='\n')
ret=STRING_NEW_LINE;
else
ret=STRING;
token->str[token->pos]='\0';
token->pos=0;
//ONION_DEBUG0("Found STRING token %s",token->str);
return ret;
}
/**
* @short Reads a string until a delimiter is found. Returns an onion_token. Also detects an empty line.
*
* @returns error code | STRING | STRING_NEW_LINE | NEW_LINE
*
* STRING is a delimited string, string new line is that the string finished witha new line, new line is that I fuond only a new line char.
*/
///
int token_read_until(onion_token *token, onion_buffer *data, char delimiter){
if (data->pos>=data->size)
return OCS_NEED_MORE_DATA;
//ONION_DEBUG0("Read data %d bytes, at token pos %d",data->size-data->pos, token->pos);
char c=data->data[data->pos++];
while (c!=delimiter && c!='\n'){
if (c!='\r') // Just ignore it here
token->str[token->pos++]=c;
if (data->pos>=data->size){
return OCS_NEED_MORE_DATA;
}
if (token->pos>=(sizeof(token->str)-1)){
ONION_ERROR("Token too long to parse it. Part read is %s (%d bytes)",token->str,token->pos);
return OCS_INTERNAL_ERROR;
}
c=data->data[data->pos++];
}
int ret=STRING;
token->str[token->pos]='\0';
if (c!=delimiter){
if ( token->pos==0 && c=='\n' ){
ret=NEW_LINE;
}
else{ // only option left.
ret=STRING_NEW_LINE;
}
}
token->pos=0;
//ONION_DEBUG0("Found KEY token %s",token->str);
return ret;
}
/// Reads a key, that is a string ended with ':'.
int token_read_KEY(onion_token *token, onion_buffer *data){
int res=token_read_until(token, data, ':');
if (res==STRING)
return KEY;
if (res==STRING_NEW_LINE){
ONION_ERROR("When parsing header, found a non valid delimited string token: '%s'",token->str);
return OCS_INTERNAL_ERROR;
}
return res;
}
/// Reads a string until a '\n|\r\n' is found. Returns an onion_token.
int token_read_LINE(onion_token *token, onion_buffer *data){
if (data->pos>=data->size)
return OCS_NEED_MORE_DATA;
char c=data->data[data->pos++];
int ignore_to_end=0;
while (c!='\n'){
if (!ignore_to_end && (token->pos>=(sizeof(token->str)-1))){
ONION_WARNING("Token too long to parse it. Ignoring remaining. ");
#ifdef __DEBUG__
char tmp[16];
strncpy(tmp, token->str, 16);
tmp[15]='\0';
ONION_DEBUG("Long token starts with: %s...",tmp);
#endif
ignore_to_end=1;
}
if (!ignore_to_end)
token->str[token->pos++]=c;
if (data->pos>=data->size)
return OCS_NEED_MORE_DATA;
c=data->data[data->pos++];
}
if (token->str[token->pos-1]=='\r')
token->str[token->pos-1]='\0';
else
token->str[token->pos]='\0';
//token->pos=0;
//ONION_DEBUG0("Found LINE token %s",token->str);
return LINE;
}
/// Reads a string until a '\n|\r\n' is found. Returns an onion_token.
int token_read_URLENCODE(onion_token *token, onion_buffer *data){
int l=data->size-data->pos;
if (l > (token->extra_size-token->pos))
l=token->extra_size-token->pos;
//ONION_DEBUG0("Feed %d bytes",l);
memcpy(&token->extra[token->pos], &data->data[data->pos], l);
token->pos+=l;
data->size-=l;
if (token->extra_size==token->pos){
token->extra[token->pos]='\0';
return URLENCODE;
}
return OCS_NEED_MORE_DATA;
}
/// Reads as much as possible from the boundary.
int token_read_MULTIPART_BOUNDARY(onion_token *token, onion_buffer *data){
onion_multipart_buffer *multipart=(onion_multipart_buffer*)token->extra;
while (multipart->boundary[multipart->pos] && multipart->boundary[multipart->pos]==data->data[data->pos]){
multipart->pos++;
data->pos++;
if (data->pos>=data->size)
return OCS_NEED_MORE_DATA;
}
if (multipart->boundary[multipart->pos]){
ONION_ERROR("Expecting multipart boundary, but not here (pos %d) (%c!=%c)", multipart->pos, data->data[data->pos], multipart->boundary[multipart->pos]);
return OCS_INTERNAL_ERROR;
}
return MULTIPART_BOUNDARY;
}
/// Reads as much as possible from the boundary.
int token_read_MULTIPART_next(onion_token *token, onion_buffer *data){
//onion_multipart_buffer *multipart=(onion_multipart_buffer*)token->extra;
while (token->pos<2 && (data->size-data->pos)>0){
token->str[token->pos++]=data->data[data->pos++];
if (token->pos==1 && token->str[0]=='\n'){
token->pos=0;
return MULTIPART_NEXT;
}
}
//ONION_DEBUG("pos %d, %d %d (%c %c)",token->pos, token->str[0], token->str[1], token->str[0], token->str[1]);
if (token->pos==2){
token->pos=0;
if (token->str[0]=='-' && token->str[1]=='-')
return MULTIPART_END;
if (token->str[0]=='\r' && token->str[1]=='\n')
return MULTIPART_NEXT;
return OCS_INTERNAL_ERROR;
}
return OCS_NEED_MORE_DATA;
}
int token_read_NEW_LINE(onion_token *token, onion_buffer *data){
while (token->pos<2 && (data->size-data->pos)>0){
token->str[token->pos++]=data->data[data->pos++];
if (token->pos==1 && token->str[0]=='\n'){
token->pos=0;
return NEW_LINE;
}
}
//ONION_DEBUG("pos %d, %d %d (%c %c)",token->pos, token->str[0], token->str[1], token->str[0], token->str[1]);
int res=OCS_NEED_MORE_DATA;
if (token->pos==2){
token->pos=0;
if (token->str[0]=='\r' && token->str[1]=='\n')
res=NEW_LINE;
else
res=OCS_INTERNAL_ERROR;
}
token->pos=0;
return res;
}
static char token_peek_next_char(onion_buffer *data){
if (data->pos>=data->size)
return '\0'; // If not enought data, go to safe side.
return data->data[data->pos];
}
static onion_connection_status parse_POST_multipart_next(onion_request *req, onion_buffer *data);
/**
* @short Reads from the data to fulfill content-length data.
*/
static onion_connection_status parse_CONTENT_LENGTH(onion_request *req, onion_buffer *data){
ONION_DEBUG0("Adding data to request->data (non form POST)");
onion_token *token=req->parser_data;
int length=data->size-data->pos;
int exit=0;
if (length>=token->extra_size-token->pos){
exit=1;
length=token->extra_size-token->pos;
}
onion_block_add_data(req->data, &data->data[data->pos], length);
data->pos+=length;
if (exit)
return onion_request_process(req);
return OCS_NEED_MORE_DATA;
}
/**
* @short Reads from the data to fulfill content-length data.
*
* All data is writen a temporal file, which will be removed later.
*/
static onion_connection_status parse_PUT(onion_request *req, onion_buffer *data){
onion_token *token=req->parser_data;
int length=data->size-data->pos;
int exit=0;
if (length>=token->extra_size-token->pos){
exit=1;
length=token->extra_size-token->pos;
}
//ONION_DEBUG0("Writing %d. %d / %d bytes", length, token->pos+length, token->extra_size);
int *fd=(int*)token->extra;
ssize_t w=write(*fd, &data->data[data->pos], length);
if (w<0){
ONION_ERROR("Could not write all data to temporal file.");
return OCS_INTERNAL_ERROR;
}
data->pos+=length;
token->pos+=length;
#if __DEBUG__
const char *filename=onion_block_data(req->data);
ONION_DEBUG0("Done with PUT. Created %s (%d bytes)", filename, token->pos);
#endif
if (exit){
close (*fd);
onion_low_free(token->extra);
token->extra=NULL;
return onion_request_process(req);
}
return OCS_NEED_MORE_DATA;
}
/**
* Hard parser as I must set into the file as I read, until i found the boundary token (or start), try to parse, and if fail,
* write to the file.
*
* The boundary may be in two or N parts.
*/
static onion_connection_status parse_POST_multipart_file(onion_request *req, onion_buffer *data){
char tmp[1024]; /// Read 1024 bytes fo the read on this call, and write. If read less write as read.
int tmppos=0;
onion_token *token=req->parser_data;
onion_multipart_buffer *multipart=(onion_multipart_buffer*)token->extra;
const char *p=data->data+data->pos;
for (;data->pos<data->size;data->pos++){
//ONION_DEBUG("*p %d boundary %d (%s)",*p,multipart->boundary[multipart->pos],multipart->boundary);
if (multipart->pos==0){
if (*p=='\n') // \r is optional.
multipart->startpos=multipart->pos=1;
else
multipart->startpos=0;
}
if (*p==multipart->boundary[multipart->pos]){
multipart->pos++;
if (multipart->pos==multipart->size){
multipart->startpos=multipart->pos=0;
data->pos++; // Not sure why this is needed. FIXME.
int w=write(multipart->fd,tmp,tmppos);
if (w!=tmppos){
ONION_ERROR("Error writing multipart data to file. Check permissions on temp directory, and availabe disk.");
close(multipart->fd);
return OCS_INTERNAL_ERROR;
}
close(multipart->fd);
req->parser=parse_POST_multipart_next;
return OCS_NEED_MORE_DATA;
}
}
else{
if (multipart->file_total_size>req->connection.listen_point->server->max_file_size){
ONION_ERROR("Files on this post too big. Aborting.");
return OCS_INTERNAL_ERROR;
}
if (multipart->pos!=0){
multipart->file_total_size+=multipart->pos;
int r=multipart->pos-multipart->startpos;
//ONION_DEBUG0("Write %d bytes",r);
int w=write(multipart->fd,tmp,tmppos);
if (w!=tmppos){
ONION_ERROR("Error writing multipart data to file. Check permissions on temp directory, and availabe disk.");
close(multipart->fd);
return OCS_INTERNAL_ERROR;
}
tmppos=0;
w=write(multipart->fd, multipart->boundary+multipart->startpos, r);
if (w!=r){
ONION_ERROR("Error writing multipart data to file. Check permissions on temp directory, and availabe disk.");
close(multipart->fd);
return OCS_INTERNAL_ERROR;
}
multipart->startpos=multipart->pos=0;
data->pos--; // Ignore read charater, try again. May be start of boundary.
continue;
}
//ONION_DEBUG0("Write 1 byte");
tmp[tmppos++]=*p;
if (tmppos==sizeof(tmp)){
int w=write(multipart->fd,tmp,tmppos);
if (w!=tmppos){
ONION_ERROR("Error writing multipart data to file. Check permissions on temp directory, and availabe disk.");
close(multipart->fd);
return OCS_INTERNAL_ERROR;
}
tmppos=0;
}
}
multipart->file_total_size++;
p++;
}
int w=write(multipart->fd,tmp,tmppos);
if (w!=tmppos){
ONION_ERROR("Error writing multipart data to file. Check permissions on temp directory, and availabe disk.");
close(multipart->fd);
return OCS_INTERNAL_ERROR;
}
return OCS_NEED_MORE_DATA;
}
/**
* Hard parser as I must set into the file as I read, until i found the boundary token (or start), try to parse, and if fail,
* write to the file.
*
* The boundary may be in two or N parts.
*/
static onion_connection_status parse_POST_multipart_data(onion_request *req, onion_buffer *data){
onion_token *token=req->parser_data;
onion_multipart_buffer *multipart=(onion_multipart_buffer*)token->extra;
const char *p=data->data+data->pos;
char *d=&multipart->data[token->pos];
for (;data->pos<data->size;data->pos++){
if (multipart->pos==0 && *p=='\n') // \r is optional.
multipart->pos=1;
if (*p==multipart->boundary[multipart->pos]){ // Check boundary
multipart->pos++;
if (multipart->pos==multipart->size){
multipart->pos=0;
data->pos++; // Not sure why this is needed. FIXME.
if (token->pos>0 && *(d-1)=='\n'){
token->pos--;
d--;
}
if (token->pos>0 && *(d-1)=='\r')
d--;
*d='\0';
ONION_DEBUG0("Adding POST data '%s'",multipart->name);
onion_dict_add(req->POST, multipart->name, multipart->data, 0);
multipart->data=multipart->data+token->pos+1;
token->pos=0;
req->parser=parse_POST_multipart_next;
return OCS_NEED_MORE_DATA;
}
}
else{ // No boundary
if (multipart->pos!=0){// Maybe i was checking before... so I need to copy as much as I wrongly thought I got.
if (multipart->post_total_size<=multipart->pos){
ONION_ERROR("No space left for this post.");
return OCS_INTERNAL_ERROR;
}
multipart->post_total_size-=multipart->pos;
memcpy(d,p,multipart->pos);
d+=multipart->pos;
token->pos+=multipart->pos;
multipart->pos=0;
}
if (multipart->post_total_size<=0){
ONION_ERROR("No space left for this post.");
return OCS_INTERNAL_ERROR;
}
multipart->post_total_size--;
//ONION_DEBUG0("%d %d",multipart->post_total_size,token->extra_size - (int) (d-token->extra));
*d=*p;
d++;
token->pos++;
}
p++;
}
return OCS_NEED_MORE_DATA;
}
static onion_connection_status parse_POST_multipart_headers_key(onion_request *req, onion_buffer *data);
static onion_connection_status parse_POST_multipart_content_type(onion_request *req, onion_buffer *data){
onion_token *token=req->parser_data;
int res=token_read_until(token, data,';');
if (res<=1000)
return res;
//ONION_DEBUG("Got content type %s",token->str);
onion_multipart_buffer *multipart=(onion_multipart_buffer*)token->extra;
const char *name;
name=strstr(token->str, "filename=");
if (name){
int l=strlen(token->str)-9;
if (l>multipart->post_total_size){
ONION_ERROR("Post buffer exhausted. content-Length wrong passed.");
return OCS_INTERNAL_ERROR;
}
multipart->filename=multipart->data;
memcpy(multipart->filename, name+9, l);
multipart->filename[l]=0;
multipart->data=multipart->data+l+1;
if (*multipart->filename=='"' && multipart->filename[l-2]=='"'){
multipart->filename[l-2]='\0';
multipart->filename++;
}
ONION_DEBUG0("Set filename '%s'",multipart->filename);
}
else{
name=strstr(token->str, "name=");
if (name){
int l=strlen(token->str)-5;
if (l>multipart->post_total_size){
ONION_ERROR("Post buffer exhausted. Content-Length had wrong size.");
return OCS_INTERNAL_ERROR;
}
multipart->name=multipart->data;
memcpy(multipart->name, name+5, l);
multipart->name[l]=0;
if (*multipart->name=='"' && multipart->name[l-2]=='"'){
l-=2;
multipart->name[l]='\0';
multipart->name++;
}
multipart->data=multipart->name+l+1;
ONION_DEBUG0("Set field name '%s'",multipart->name);
}
}
if (res==STRING_NEW_LINE){
req->parser=parse_POST_multipart_headers_key;
return OCS_NEED_MORE_DATA;
}
return OCS_NEED_MORE_DATA;
}
static onion_connection_status parse_POST_multipart_ignore_header(onion_request *req, onion_buffer *data){
onion_token *token=req->parser_data;
int res=token_read_LINE(token, data);
if (res<=1000)
return res;
token->pos=0;
req->parser=parse_POST_multipart_headers_key;
return OCS_NEED_MORE_DATA;
}
static onion_connection_status parse_POST_multipart_headers_key(onion_request *req, onion_buffer *data){
onion_token *token=req->parser_data;
int res=token_read_KEY(token, data);
if (res<=1000)
return res;
if (res==NEW_LINE){
if (!req->POST)
req->POST=onion_dict_new();
ONION_DEBUG("New line");
onion_multipart_buffer *multipart=(onion_multipart_buffer*)token->extra;
multipart->pos=0;
if (multipart->filename){
char filename[]="/tmp/onion-XXXXXX";
multipart->fd=mkstemp(filename);
if (multipart->fd<0)
ONION_ERROR("Could not create temporal file at %s.", filename);
if (!req->FILES)
req->FILES=onion_dict_new();
onion_dict_add(req->POST,multipart->name,multipart->filename, 0);
onion_dict_add(req->FILES,multipart->name, filename, OD_DUP_VALUE);
ONION_DEBUG0("Created temporal file %s",filename);
req->parser=parse_POST_multipart_file;
return parse_POST_multipart_file(req, data);
}
else{
req->parser=parse_POST_multipart_data;
return parse_POST_multipart_data(req, data);
}
}
// Only interested in one header
if (strcmp(token->str,"Content-Disposition")==0){
req->parser=parse_POST_multipart_content_type;
return parse_POST_multipart_content_type(req,data);
}
ONION_DEBUG("Not interested in header '%s'",token->str);
req->parser=parse_POST_multipart_ignore_header;
return parse_POST_multipart_ignore_header(req,data);
}
static onion_connection_status parse_POST_multipart_next(onion_request *req, onion_buffer *data){
onion_token *token=req->parser_data;
int res=token_read_MULTIPART_next(token, data);
if (res<=1000)
return res;
//ONION_DEBUG("Found next token: %d",res);
if (res==MULTIPART_END)
return onion_request_process(req);
onion_multipart_buffer *multipart=(onion_multipart_buffer*)token->extra;
multipart->filename=NULL;
multipart->name=NULL;
req->parser=parse_POST_multipart_headers_key;
return parse_POST_multipart_headers_key(req, data);
}
static onion_connection_status parse_POST_multipart_start(onion_request *req, onion_buffer *data){
onion_token *token=req->parser_data;
int res=token_read_MULTIPART_BOUNDARY(token, data);
if (res<=1000)
return res;
req->parser=parse_POST_multipart_next;
return parse_POST_multipart_next(req, data);
}
static onion_connection_status parse_POST_urlencode(onion_request *req, onion_buffer *data){
onion_token *token=req->parser_data;
int res=token_read_URLENCODE(token, data);
if (res<=1000)
return res;
req->POST=onion_dict_new();
onion_request_parse_query_to_dict(req->POST, token->extra);
token->extra=NULL; // At query to dict, it keeps the pointer and free it when POST is freed.
return onion_request_process(req);
}
static onion_connection_status parse_headers_KEY(onion_request *req, onion_buffer *data);
static onion_connection_status parse_headers_VALUE(onion_request *req, onion_buffer *data);
static onion_connection_status parse_headers_VALUE_skip_leading_whitespace(onion_request *req, onion_buffer *data){
while (data->pos<data->size){
char peek=data->data[data->pos++];
if (peek!='\t' && peek!=' '){
onion_token *token=req->parser_data;
if (peek=='\n')
return OCS_NEED_MORE_DATA;
token->str[token->pos++]=' ';
token->str[token->pos++]=peek;
req->parser=parse_headers_VALUE;
return parse_headers_VALUE(req, data);
}
}
return OCS_NEED_MORE_DATA;
}
static onion_connection_status parse_headers_VALUE_multiline_if_space(onion_request *req, onion_buffer *data){
char peek=token_peek_next_char(data);
ONION_DEBUG0("Multiline if space, peek %d",peek);
if (peek==0){
req->parser=parse_headers_VALUE_multiline_if_space;
return OCS_NEED_MORE_DATA;
}
if (peek==' ' || peek=='\t'){
req->parser=parse_headers_VALUE_skip_leading_whitespace;
return parse_headers_VALUE_skip_leading_whitespace(req, data);
}
onion_token *token=req->parser_data;
token->pos=0;
char *p=token->str; // skips leading spaces
while (isspace(*p)) p++;
ONION_DEBUG0("Adding header %s : %s",token->extra,p);
onion_dict_add(req->headers,token->extra,p, OD_DUP_VALUE|OD_FREE_KEY);
token->extra=NULL; // It will be freed at onion_dict_free.
req->parser=parse_headers_KEY;
return OCS_NEED_MORE_DATA; // Get back recursion if any, to prevent too long callstack (on long headers) and stack overflow.
}
static onion_connection_status parse_headers_VALUE(onion_request *req, onion_buffer *data){
onion_token *token=req->parser_data;
int res=token_read_LINE(token, data);
if (res<=1000)
return res;
return parse_headers_VALUE_multiline_if_space(req, data);
}
static onion_connection_status parse_headers_KEY(onion_request *req, onion_buffer *data){
onion_token *token=req->parser_data;
int res=token_read_KEY(token, data);
if (res<=1000)
return res;
ONION_DEBUG0("Got %d at KEY",res);
if ( res == NEW_LINE ){
if ((req->flags&OR_METHODS)==OR_POST){
const char *content_type=onion_request_get_header(req, "Content-Type");
if (content_type && (strstr(content_type,"application/x-www-form-urlencoded") || strstr(content_type, "boundary")))
return prepare_POST(req);
}
if ((req->flags&OR_METHODS)==OR_PUT)
return prepare_PUT(req);
if (onion_request_get_header(req, "Content-Length")){ // Some length, not POST, get data.
int n=atoi(onion_request_get_header(req, "Content-Length"));
if (n>0)
return prepare_CONTENT_LENGTH(req);
}
return onion_request_process(req);
}
assert(token->extra==NULL);
token->extra=onion_low_strdup(token->str);
req->parser=parse_headers_VALUE;
return parse_headers_VALUE(req, data);
}
static onion_connection_status parse_headers_KEY_skip_NL(onion_request *req, onion_buffer *data){
onion_token *token=req->parser_data;
int r=token_read_NEW_LINE(token,data);
if (r<=1000)
return r;
req->parser=parse_headers_KEY;
return parse_headers_KEY(req, data);
}
static onion_connection_status parse_headers_VERSION(onion_request *req, onion_buffer *data){
onion_token *token=req->parser_data;
int res=token_read_STRING(token, data);
if (res<=1000)
return res;
if (strcmp(token->str,"HTTP/1.1")==0)
req->flags|=OR_HTTP11;
if (!req->GET)
req->GET=onion_dict_new();
if (res==STRING){
req->parser=parse_headers_KEY_skip_NL;
return parse_headers_KEY_skip_NL(req, data);
}
else{ // STRING_NEW_LINE, only when \n as STRING separator, not \r\n.
req->parser=parse_headers_KEY;
return parse_headers_KEY(req, data);
}
}
static onion_connection_status parse_headers_URL(onion_request *req, onion_buffer *data){
onion_token *token=req->parser_data;
int res=token_read_STRING(token, data);
if (res<=1000)
return res;
req->fullpath=onion_low_strdup(token->str);
onion_request_parse_query(req);
ONION_DEBUG0("URL path is %s", req->fullpath);
if (res==STRING_NEW_LINE){ // Old HTTP/0? or lazy user, dont set the HTTP version, straigth new line.
req->parser=parse_headers_KEY;
return parse_headers_KEY(req, data);
}
else{
req->parser=parse_headers_VERSION;
return parse_headers_VERSION(req, data);
}
}
static onion_connection_status parse_headers_GET(onion_request *req, onion_buffer *data){
onion_token *token=req->parser_data;
int res=token_read_STRING(token, data);
if (res<=1000)
return res;
int i;
for (i=0;i<16;i++){
if (!onion_request_methods[i]){
ONION_ERROR("Unknown method '%s' (%d known methods)",token->str, i);
return OCS_NOT_IMPLEMENTED;
}
if (strcmp(onion_request_methods[i], token->str)==0){
ONION_DEBUG0("Method is %s", token->str);
req->flags=(req->flags&~0x0F)+i;
break;
}
}
req->parser=parse_headers_URL;
return parse_headers_URL(req, data);
}
/**
* @short Write some data into the request, and passes it line by line to onion_request_fill
*
* It features a state machine, from req->parse_state.
*
* Depending on the state input is redirected to a diferent parser, one for headers, POST url encoded data...
*
* @return Returns the number of bytes writen, or <=0 if connection should close, according to onion_connection_status
* @see onion_connection_status
*/
onion_connection_status onion_request_write(onion_request *req, const char *data, size_t size){
if (!req->parser_data){
req->parser_data=onion_low_calloc(1, sizeof(onion_token));
req->parser=parse_headers_GET;
}
onion_connection_status (*parse)(onion_request *req, onion_buffer *data);
parse=req->parser;
if (parse){
onion_buffer odata={ data, size, 0};
while (odata.size>odata.pos){
int r=parse(req, &odata);
if (r!=OCS_NEED_MORE_DATA){
return r;
}
parse=req->parser;
}
return OCS_NEED_MORE_DATA;
}
return OCS_INTERNAL_ERROR;
}
/**
* @short Parses the query to unquote the path and get the query.
*/
static int onion_request_parse_query(onion_request *req){
if (!req->fullpath)
return 0;
if (req->GET) // already done
return 1;
char *p=req->fullpath;
char have_query=0;
while(*p){
if (*p=='?'){
have_query=1;
break;
}
p++;
}
*p='\0';
onion_unquote_inplace(req->fullpath);
if (have_query){ // There are querys.
p++;
req->GET=onion_dict_new();
onion_request_parse_query_to_dict(req->GET, p);
}
return 1;
}
/**
* @short Parses the query part to a given dictionary.
*
* The data is overwriten as necessary. It is NOT dupped, so if you free this char *p, please free the tree too.
*/
static void onion_request_parse_query_to_dict(onion_dict *dict, char *p){
ONION_DEBUG0("Query to dict %s",p);
char *key=NULL, *value=NULL;
int state=0; // 0 key, 1 value
key=p;
while(*p){
if (state==0){
if (*p=='='){
*p='\0';
value=p+1;
state=1;
}
else if (*p=='&'){
*p='\0';
onion_unquote_inplace(key);
ONION_DEBUG0("Adding key %s",key);
onion_dict_add(dict, key, "", 0);
key=p+1;
state=0;
}
}
else{
if (*p=='&'){
*p='\0';
onion_unquote_inplace(key);
onion_unquote_inplace(value);
ONION_DEBUG0("Adding key %s=%-16s",key,value);
onion_dict_add(dict, key, value, 0);
key=p+1;
state=0;
}
}
p++;
}
if (state==0){
if (key[0]!='\0'){
onion_unquote_inplace(key);
ONION_DEBUG0("Adding key %s",key);
onion_dict_add(dict, key, "", 0);
}
}
else{
onion_unquote_inplace(key);
onion_unquote_inplace(value);
ONION_DEBUG0("Adding key %s=%-16s",key,value);
onion_dict_add(dict, key, value, 0);
}
}
/**
* @short Prepares the POST
*/
static onion_connection_status prepare_POST(onion_request *req){
// ok post
onion_token *token=req->parser_data;
const char *content_type=onion_dict_get(req->headers, "Content-Type");
const char *content_size=onion_dict_get(req->headers, "Content-Length");
if (!content_size){
ONION_ERROR("I need the content size header to support POST data");
return OCS_INTERNAL_ERROR;
}
size_t cl=atol(content_size);
if (cl==0)
return onion_request_process(req);
//ONION_DEBUG("Content type %s",content_type);
if (!content_type || (strstr(content_type, "application/x-www-form-urlencoded"))){
if (cl>req->connection.listen_point->server->max_post_size){
ONION_ERROR("Asked to send much POST data. Limit %d. Failing.",req->connection.listen_point->server->max_post_size);
return OCS_INTERNAL_ERROR;
}
assert(token->extra==NULL);
token->extra=onion_low_scalar_malloc(cl+1); // Cl + \0
token->extra_size=cl;
req->free_list=onion_ptr_list_add(req->free_list, token->extra); // Free when the request is freed.
req->parser=parse_POST_urlencode;
return OCS_NEED_MORE_DATA;
}
// multipart.
const char *mp_token=strstr(content_type, "boundary=");
if (!mp_token){
ONION_ERROR("No boundary set at content-type");
return OCS_INTERNAL_ERROR;
}
mp_token+=9;
if (cl>req->connection.listen_point->server->max_post_size) // I hope the missing part is files, else error later.
cl=req->connection.listen_point->server->max_post_size;
int mp_token_size=strlen(mp_token);
token->extra_size=cl; // Max size of the multipart->data
onion_multipart_buffer *multipart=onion_low_malloc(token->extra_size+sizeof(onion_multipart_buffer)+mp_token_size+2);
assert(token->extra==NULL);
token->extra=(char*)multipart;
multipart->boundary=(char*)multipart+sizeof(onion_multipart_buffer)+1;
multipart->size=mp_token_size+4;
multipart->pos=2; // First boundary already have [\r]\n readen
multipart->post_total_size=cl;
multipart->file_total_size=0;
multipart->boundary[0]='\r';
multipart->boundary[1]='\n';
multipart->boundary[2]='-';
multipart->boundary[3]='-';
strcpy(&multipart->boundary[4],mp_token);
multipart->data=(char*)multipart+sizeof(onion_multipart_buffer)+multipart->size+1;
//ONION_DEBUG("Multipart POST boundary '%s'",multipart->boundary);
req->parser=parse_POST_multipart_start;
return OCS_NEED_MORE_DATA;
}
/**
* @short Prepares the CONTENT LENGTH
*/
static onion_connection_status prepare_CONTENT_LENGTH(onion_request *req){
onion_token *token=req->parser_data;
const char *content_size=onion_dict_get(req->headers, "Content-Length");
if (!content_size){
ONION_ERROR("I need the Content-Length header to get data");
return OCS_INTERNAL_ERROR;
}
size_t cl=atol(content_size);
if (cl>req->connection.listen_point->server->max_post_size){
ONION_ERROR("Trying to set more data at server than allowed %d", req->connection.listen_point->server->max_post_size);
return OCS_INTERNAL_ERROR;
}
req->data=onion_block_new();
assert(token->extra==NULL);
//token->extra=NULL; // Should be already null, as should have no data.
token->extra_size=cl;
token->pos=0;
req->parser=parse_CONTENT_LENGTH;
return OCS_NEED_MORE_DATA;
}
/**
* @short Prepares the PUT
*
* It saves the data to a temporal file, which name is stored at data.
*/
static onion_connection_status prepare_PUT(onion_request *req){
onion_token *token=req->parser_data;
const char *content_size=onion_dict_get(req->headers, "Content-Length");
if (!content_size){
ONION_ERROR("I need the Content-Length header to get data");
return OCS_INTERNAL_ERROR;
}
size_t cl=atol(content_size);
if (cl>req->connection.listen_point->server->max_file_size){
ONION_ERROR("Trying to PUT a file bigger than allowed size");
return OCS_INTERNAL_ERROR;
}
req->data=onion_block_new();
char filename[]="/tmp/onion-XXXXXX";
int fd=mkstemp(filename);
if (fd<0)
ONION_ERROR("Could not create temporary file at %s.", filename);
onion_block_add_str(req->data, filename);
ONION_DEBUG0("Creating PUT file %s (%d bytes long)", filename, token->extra_size);
if (!req->FILES){
req->FILES=onion_dict_new();
}
{
const char *filename=onion_block_data(req->data);
onion_dict_add(req->FILES,"filename", filename, 0);
}
if (cl==0){
ONION_DEBUG0("Created 0 length file");
close(fd);
return onion_request_process(req);
}
int *pfd=onion_low_scalar_malloc(sizeof(fd));
*pfd=fd;
assert(token->extra==NULL);
token->extra=(char*)pfd;
token->extra_size=cl;
token->pos=0;
req->parser=parse_PUT;
return OCS_NEED_MORE_DATA;
}
/**
* @short Frees the parser data.
*/
void onion_request_parser_data_free(void *t){
ONION_DEBUG0("Free parser data");
onion_token *token=t;
if (token->extra){
onion_low_free(token->extra);
token->extra=NULL;
}
onion_low_free(token);
}
Jump to Line
Something went wrong with that request. Please try again.