aisl/library/parser.c

491 lines
10 KiB
C

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <cStuff/str-utils.h>
#include <aisl/http.h>
#include "parser.h"
#include "globals.h"
#include "stream.h"
/* length(multipart/form-data; boundary=) = 30 */
#define B_OFFSET 30
/* common HTTP headers */
static const char cCookie[] = "Cookie";
static const char cContentType[] = "Content-Type";
static const char cContentLength[] = "Content-Length";
/*
static const char cConnection[] = "Connection";
static const char cHost[] = "Host";
static const char cUserAgent[] = "User-Agent";
static const char cAccept[] = "Accept";
static const char cAcceptLanguage[] = "Accept-Language";
static const char cAcceptEncoding[] = "Accept-Encoding";
*/
#define CLI_STREAM(x) ( ((stream_t)list_index(x->streams,x->istream)) )
/*
static void
debug(const char * label, char * buffer, int len)
{
printf("<<< %s [", label);
fwrite(buffer, 1, len, stdout);
printf("]\n");
}
*/
static pair_t
pair_new(const char * key, int length)
{
pair_t p = calloc(1, sizeof(struct pair));
if (p)
{
p->key = str_ncopy(key, length);
if (!p->key)
{
free(p);
p = NULL;
}
}
return p;
}
/* HTTP METHOD -------------------------------------------------------------- */
parser_status_t
parse_request_method(client_t cli, char ** b_ptr, int *b_len)
{
char * cur = memchr(*b_ptr, ' ', *b_len);
if (!cur)
{
if (*b_len < 8)
return PARSER_HUNGRY;
else
return PARSER_FAILED;
}
int l = (int) (cur - *b_ptr);
stream_t s = list_index(cli->streams, cli->istream);
switch( l )
{
case 3:
if (strncmp(*b_ptr, "GET", l)==0)
ASTREAM(s)->request_method = AISL_HTTP_GET;
else if (strncmp(*b_ptr, "PRI", l)==0)
ASTREAM(s)->request_method = AISL_HTTP_PRI;
else if (strncmp(*b_ptr, "PUT", l)==0)
ASTREAM(s)->request_method = AISL_HTTP_PUT;
else
return PARSER_FAILED;
break;
case 4:
if (strncmp(*b_ptr, "POST", l)==0)
ASTREAM(s)->request_method = AISL_HTTP_POST;
else if (strncmp(*b_ptr, "HEAD", l)==0)
ASTREAM(s)->request_method = AISL_HTTP_HEAD;
else
return PARSER_FAILED;
break;
case 5:
if (strncmp(*b_ptr, "TRACE", l)==0)
ASTREAM(s)->request_method = AISL_HTTP_TRACE;
else
return PARSER_FAILED;
break;
case 6:
if (strncmp(*b_ptr, "DELETE", l)==0)
ASTREAM(s)->request_method = AISL_HTTP_DELETE;
else
return PARSER_FAILED;
break;
case 7:
if (strncmp(*b_ptr, "OPTIONS", l)==0)
ASTREAM(s)->request_method = AISL_HTTP_OPTIONS;
else if (strncmp(*b_ptr, "CONNECT", l)==0)
ASTREAM(s)->request_method = AISL_HTTP_CONNECT;
else
return PARSER_FAILED;
break;
default:
return PARSER_FAILED;
}
*b_ptr += ++l;
*b_len -= l; /* count method + space character */
s->state = STREAM_REQUEST_PATH;
return PARSER_PENDING;
}
/* HTTP REQUEST_URI and HOST ------------------------------------------------ */
static void
str_to_lower( char * src )
{
while (*src)
{
*src = tolower(*src);
src++;
}
}
parser_status_t
parse_request_path(client_t cli, char ** b_ptr, int *b_len)
{
parser_status_t result = PARSER_PENDING;
stream_t s = list_index(cli->streams, cli->istream);
int i;
char * host = NULL,
* path = NULL,
* query = NULL;
for ( i=0; i<*b_len; i++)
{
switch( (*b_ptr)[i] )
{
case ':':
if (host) /* if host is set, we parse host and it could not contain : */
{
result = PARSER_FAILED;
break;
}
else /* could be protocol separator */
{
if (i==5)
{
if (*b_len > 7 && strncmp(*b_ptr, "http://", 7)==0 ) /* protocol defined */
{
host = &(*b_ptr)[i+3];
i+=2;
continue;
}
result = PARSER_FAILED; /* something is wrong */
break;
}
}
continue;
case '?':
query = (*b_ptr) +i;
continue;
case ' ':
if (! path) path = *b_ptr;
if (query)
{
ASTREAM(s)->path = str_ncopy(path, (uint32_t) (query-path));
query++;
ASTREAM(s)->query = str_ncopy(query, (uint32_t)(&(*b_ptr)[i]-query));
}
else
ASTREAM(s)->path = str_ncopy(path, (uint32_t) (&(*b_ptr)[i]-path));
*b_len -= ++i;
*b_ptr += i;
s->state = STREAM_REQUEST_PROTOCOL;
return PARSER_PENDING;
break;
case '/':
if (host)
{
/* debug(" > host", host, (int) (&(*b_ptr)[i] - host)); */
pair_t p = malloc(sizeof(struct pair));
if (p)
{
p->key = str_copy("host");
p->value = str_ncopy(host, (uint32_t) (&(*b_ptr)[i] - host));
}
s->headers = list_new(AISL_MIN_HEADERS);
list_append(s->headers, p);
host = NULL;
path = &(*b_ptr)[i];
}
default:
continue;
}
break;
}
if (result == PARSER_PENDING) /* end space was not found */
result = PARSER_HUNGRY;
/*
if (result == PARSER_HUNGRY && *b_len == gBuffer->size)
result = PARSER_FAILED;*/ /* buffer is overloaded */
return result;
}
/* HTTP VERSION ------------------------------------------------------------- */
parser_status_t
parse_request_protocol(client_t cli, char ** b_ptr, int *b_len)
{
stream_t stream = CLI_STREAM(cli);
/* HTTP/X.X = 8 characters minimal */
if (*b_len < 8) return PARSER_HUNGRY;
char * ptr = memchr(*b_ptr, '\n', *b_len);
if (!ptr) return PARSER_HUNGRY;
int l = (int) (ptr - *b_ptr);
if (strncmp(*b_ptr, "HTTP/", 5)==0)
{
if (strncmp(&(*b_ptr)[5], "2.0", 3)==0) cli->protocol = AISL_HTTP_2_0; else
if (strncmp(&(*b_ptr)[5], "1.1", 3)==0) cli->protocol = AISL_HTTP_1_1; else
if (strncmp(&(*b_ptr)[5], "1.0", 3)==0) cli->protocol = AISL_HTTP_1_0; else
return PARSER_FAILED;
if ( (l==10 && *b_ptr[8]=='\r') || (l==9) )
{
/*r->version = str_ncopy(*b_ptr, 8); */
*b_ptr += ++l;
*b_len -=l;
stream->state=STREAM_REQUEST_HEADER_KEY;
aisl_raise_event(
cli->server->owner,
stream,
AISL_STREAM_OPEN,
ASTREAM(stream)->request_method,
ASTREAM(stream)->path,
ASTREAM(stream)->query
);
if (!stream->headers)
stream->headers = list_new(AISL_MIN_HEADERS);
else if (stream->headers->count == 1)
{
/* raise event for Host header */
pair_t p = list_index(stream->headers, 0);
aisl_raise_event(
cli->server->owner,
stream,
AISL_STREAM_HEADER,
p->key, p->value
);
}
return PARSER_PENDING;
}
}
return PARSER_FAILED;
}
/* HTTP HEADER KEY ---------------------------------------------------------- */
parser_status_t
parse_request_header_key(client_t cli, char ** b_ptr, int *b_len)
{
stream_t stream = CLI_STREAM(cli);
int l;
/* check end of headers */
switch (*b_ptr[0])
{
case '\r':
if (*b_len>1)
{
if( strncmp(*b_ptr, "\r\n", 2)==0 )
{
l=2;
}
else
return PARSER_FAILED;
}
else
return PARSER_HUNGRY;
break;
case '\n':
l=1;
break;
default:
l=0;
}
if (l)
{
/* end of headers */
*b_len -= l;
*b_ptr += l;
stream->state = STREAM_REQUEST_CONTENT;
/* aisl_raise_event(cli->server->owner, CLI_STREAM(cli), AISL_STREAM_OPEN);
* */
return PARSER_PENDING;
}
/* header key */
char * ptr = memchr(*b_ptr, ':', *b_len);
if (!ptr)
{
/*
if (*b_len == gBuffer->size)
return PARSER_FAILED;
*/
return PARSER_HUNGRY;
}
l = (int) (ptr-*b_ptr);
pair_t ppp = pair_new(*b_ptr, l);
str_to_lower(ppp->key);
if (ppp)
list_append(stream->headers, ppp);
*b_len -= ++l;
*b_ptr += l;
stream->state=STREAM_REQUEST_HEADER_VALUE;
return PARSER_PENDING;
}
/* HTTP HEADER VALUE -------------------------------------------------------- */
parser_status_t
parse_request_header_value(client_t cli, char ** b_ptr, int *b_len)
{
stream_t stream = CLI_STREAM(cli);
/* skip first space */
if (*b_len)
{
if ((*b_ptr)[0]==' ')
{
(*b_ptr)++;
(*b_len)--;
if (*b_len == 0)
return PARSER_HUNGRY;
}
}
else
return PARSER_HUNGRY;
char * ptr = memchr(*b_ptr, '\n', *b_len);
int l;
l = (ptr) ? (int) (ptr-*b_ptr) : *b_len;
uint32_t index = stream->headers->count -1;
pair_t p = list_index(stream->headers, index);
p->value = str_ncat(p->value, *b_ptr, (l && (*b_ptr)[l-1]=='\r') ? l-1 : l);
*b_len -= ++l;
*b_ptr += l;
stream->state=STREAM_REQUEST_HEADER_KEY;
/* todo: add limit for maximal header length */
if (ptr)
{
if (str_cmpi(p->key, cCookie )==0)
{
/* parse cookies */
}
else if (str_cmpi(p->key, cContentType )==0)
{
/* CLI(r)->c_type_index = index; */
}
else if (str_cmpi(p->key, cContentLength )==0)
{
stream->c_length = strtol(p->value, NULL, 0);
}
/* CLI(r)->c_length = strtol(p->value, NULL, 10); */
aisl_raise_event(
cli->server->owner,
stream,
AISL_STREAM_HEADER,
p->key, p->value
);
return PARSER_PENDING;
}
else
return PARSER_HUNGRY;
}
parser_status_t
parse_request_content(client_t cli, char ** b_ptr, int *b_len)
{
stream_t stream = CLI_STREAM(cli);
/*
fprintf(stdout, "AISL [%d]> ", CLI_STREAM(cli)->c_length);
fwrite(*b_ptr, 1, *b_len, stdout);
fprintf(stdout, "\n<");
*/
if (stream->c_length)
{
int l = *b_len;
aisl_raise_event(
cli->server->owner,
stream,
AISL_STREAM_INPUT,
*b_ptr,
l
);
*b_ptr += l;
*b_len = 0;
stream->c_length -= l;
}
else
goto request_ready;
if ( stream->c_length == 0 )
{
request_ready:
stream->state=STREAM_REQUEST_READY;
aisl_raise_event( cli->server->owner, stream, AISL_STREAM_REQUEST );
return PARSER_FINISHED;
}
else
return PARSER_HUNGRY;
}