proxlib/proxy_parse.c

605 lines
14 KiB
C

/*
proxy_parse.c -- a HTTP Request Parsing Library.
COS 461
*/
#include "proxy_parse.h"
#define DEFAULT_NHDRS 8
#define MAX_REQ_LEN 65535
#define MIN_REQ_LEN 4
static const char *root_abs_path = "/";
/* private function declartions */
int ParsedRequest_printRequestLine(struct ParsedRequest *pr,
char * buf, size_t buflen,
size_t *tmp);
size_t ParsedRequest_requestLineLen(struct ParsedRequest *pr);
/*
* debug() prints out debugging info if DEBUG is set to 1
*
* parameter format: same as printf
*
*/
void debug(const char * format, ...) {
va_list args;
if (DEBUG) {
va_start(args, format);
vfprintf(stderr, format, args);
va_end(args);
}
}
/*
* ParsedHeader Public Methods
*/
/* Set a header with key and value */
int ParsedHeader_set(struct ParsedRequest *pr,
const char * key, const char * value)
{
struct ParsedHeader *ph;
ParsedHeader_remove (pr, key);
if (pr->headerslen <= pr->headersused+1) {
pr->headerslen = pr->headerslen * 2;
pr->headers =
(struct ParsedHeader *)realloc(pr->headers,
pr->headerslen * sizeof(struct ParsedHeader));
if (!pr->headers)
return -1;
}
ph = pr->headers + pr->headersused;
pr->headersused += 1;
ph->key = (char *)malloc(strlen(key)+1);
memcpy(ph->key, key, strlen(key));
ph->key[strlen(key)] = '\0';
ph->value = (char *)malloc(strlen(value)+1);
memcpy(ph->value, value, strlen(value));
ph->value[strlen(value)] = '\0';
ph->keylen = strlen(key)+1;
ph->valuelen = strlen(value)+1;
return 0;
}
/* get the parsedHeader with the specified key or NULL */
struct ParsedHeader* ParsedHeader_get(struct ParsedRequest *pr,
const char * key)
{
size_t i = 0;
struct ParsedHeader * tmp;
while(pr->headersused > i)
{
tmp = pr->headers + i;
if(tmp->key && key && strcmp(tmp->key, key) == 0)
{
return tmp;
}
i++;
}
return NULL;
}
/* remove the specified key from parsedHeader */
int ParsedHeader_remove(struct ParsedRequest *pr, const char *key)
{
struct ParsedHeader *tmp;
tmp = ParsedHeader_get(pr, key);
if(tmp == NULL)
return -1;
free(tmp->key);
free(tmp->value);
tmp->key = NULL;
return 0;
}
/* modify the header with given key, giving it a new value
* return 1 on success and 0 if no such header found
*
int ParsedHeader_modify(struct ParsedRequest *pr, const char * key,
const char *newValue)
{
struct ParsedHeader *tmp;
tmp = ParsedHeader_get(pr, key);
if(tmp != NULL)
{
if(tmp->valuelen < strlen(newValue)+1)
{
tmp->valuelen = strlen(newValue)+1;
tmp->value = (char *) realloc(tmp->value,
tmp->valuelen * sizeof(char));
}
strcpy(tmp->value, newValue);
return 1;
}
return 0;
}
*/
/*
ParsedHeader Private Methods
*/
void ParsedHeader_create(struct ParsedRequest *pr)
{
pr->headers =
(struct ParsedHeader *)malloc(sizeof(struct ParsedHeader)*DEFAULT_NHDRS);
pr->headerslen = DEFAULT_NHDRS;
pr->headersused = 0;
}
size_t ParsedHeader_lineLen(struct ParsedHeader * ph)
{
if(ph->key != NULL)
{
return strlen(ph->key)+strlen(ph->value)+4;
}
return 0;
}
size_t ParsedHeader_headersLen(struct ParsedRequest *pr)
{
if (!pr || !pr->buf)
return 0;
size_t i = 0;
int len = 0;
while(pr->headersused > i)
{
len += ParsedHeader_lineLen(pr->headers + i);
i++;
}
len += 2;
return len;
}
int ParsedHeader_printHeaders(struct ParsedRequest * pr, char * buf,
size_t len)
{
char * current = buf;
struct ParsedHeader * ph;
size_t i = 0;
if(len < ParsedHeader_headersLen(pr))
{
debug("buffer for printing headers too small\n");
return -1;
}
while(pr->headersused > i)
{
ph = pr->headers+i;
if (ph->key) {
memcpy(current, ph->key, strlen(ph->key));
memcpy(current+strlen(ph->key), ": ", 2);
memcpy(current+strlen(ph->key) +2 , ph->value,
strlen(ph->value));
memcpy(current+strlen(ph->key) +2+strlen(ph->value) ,
"\r\n", 2);
current += strlen(ph->key)+strlen(ph->value)+4;
}
i++;
}
memcpy(current, "\r\n",2);
return 0;
}
void ParsedHeader_destroyOne(struct ParsedHeader * ph)
{
if(ph->key != NULL)
{
free(ph->key);
ph->key = NULL;
free(ph->value);
ph->value = NULL;
ph->keylen = 0;
ph->valuelen = 0;
}
}
void ParsedHeader_destroy(struct ParsedRequest * pr)
{
size_t i = 0;
while(pr->headersused > i)
{
ParsedHeader_destroyOne(pr->headers + i);
i++;
}
pr->headersused = 0;
free(pr->headers);
pr->headerslen = 0;
}
int ParsedHeader_parse(struct ParsedRequest * pr, char * line)
{
char * key;
char * value;
char * index1;
char * index2;
index1 = index(line, ':');
if(index1 == NULL)
{
debug("No colon found\n");
return -1;
}
key = (char *)malloc((index1-line+1)*sizeof(char));
memcpy(key, line, index1-line);
key[index1-line]='\0';
index1 += 2;
index2 = strstr(index1, "\r\n");
value = (char *) malloc((index2-index1+1)*sizeof(char));
memcpy(value, index1, (index2-index1));
value[index2-index1] = '\0';
ParsedHeader_set(pr, key, value);
free(key);
free(value);
return 0;
}
/*
ParsedRequest Public Methods
*/
void ParsedRequest_destroy(struct ParsedRequest *pr)
{
if(pr->buf != NULL)
{
free(pr->buf);
}
if (pr->path != NULL) {
free(pr->path);
}
if(pr->headerslen > 0)
{
ParsedHeader_destroy(pr);
}
free(pr);
}
struct ParsedRequest* ParsedRequest_create()
{
struct ParsedRequest *pr;
pr = (struct ParsedRequest *)malloc(sizeof(struct ParsedRequest));
if (pr != NULL)
{
ParsedHeader_create(pr);
pr->buf = NULL;
pr->method = NULL;
pr->protocol = NULL;
pr->host = NULL;
pr->path = NULL;
pr->version = NULL;
pr->buf = NULL;
pr->buflen = 0;
}
return pr;
}
/*
Recreate the entire buffer from a parsed request object.
buf must be allocated
*/
int ParsedRequest_unparse(struct ParsedRequest *pr, char *buf,
size_t buflen)
{
if (!pr || !pr->buf)
return -1;
size_t tmp;
if (ParsedRequest_printRequestLine(pr, buf, buflen, &tmp) < 0)
return -1;
if (ParsedHeader_printHeaders(pr, buf+tmp, buflen-tmp) < 0)
return -1;
return 0;
}
/*
Recreate the headers from a parsed request object.
buf must be allocated
*/
int ParsedRequest_unparse_headers(struct ParsedRequest *pr, char *buf,
size_t buflen)
{
if (!pr || !pr->buf)
return -1;
if (ParsedHeader_printHeaders(pr, buf, buflen) < 0)
return -1;
return 0;
}
/* Size of the headers if unparsed into a string */
size_t ParsedRequest_totalLen(struct ParsedRequest *pr)
{
if (!pr || !pr->buf)
return 0;
return ParsedRequest_requestLineLen(pr)+ParsedHeader_headersLen(pr);
}
/*
Parse request buffer
Parameters:
parse: ptr to a newly created ParsedRequest object
buf: ptr to the buffer containing the request (need not be NUL terminated)
and the trailing \r\n\r\n
buflen: length of the buffer including the trailing \r\n\r\n
Return values:
-1: failure
0: success
*/
int
ParsedRequest_parse(struct ParsedRequest * parse, const char *buf,
int buflen)
{
char *full_addr;
char *saveptr;
char *index;
char *currentHeader;
if (parse->buf != NULL) {
debug("parse object already assigned to a request\n");
return -1;
}
if (buflen < MIN_REQ_LEN || buflen > MAX_REQ_LEN) {
debug("invalid buflen %d", buflen);
return -1;
}
/* Create NUL terminated tmp buffer */
char *tmp_buf = (char *)malloc(buflen + 1); /* including NUL */
memcpy(tmp_buf, buf, buflen);
tmp_buf[buflen] = '\0';
index = strstr(tmp_buf, "\r\n\r\n");
if (index == NULL) {
debug("invalid request line, no end of header\n");
free(tmp_buf);
return -1;
}
/* Copy request line into parse->buf */
index = strstr(tmp_buf, "\r\n");
if (parse->buf == NULL) {
parse->buf = (char *) malloc((index-tmp_buf)+1);
parse->buflen = (index-tmp_buf)+1;
}
memcpy(parse->buf, tmp_buf, index-tmp_buf);
parse->buf[index-tmp_buf] = '\0';
/* Parse request line */
parse->method = strtok_r(parse->buf, " ", &saveptr);
if (parse->method == NULL) {
debug( "invalid request line, no whitespace\n");
free(tmp_buf);
free(parse->buf);
parse->buf = NULL;
return -1;
}
if (strcmp (parse->method, "GET")) {
debug( "invalid request line, method not 'GET': %s\n",
parse->method);
free(tmp_buf);
free(parse->buf);
parse->buf = NULL;
return -1;
}
full_addr = strtok_r(NULL, " ", &saveptr);
if (full_addr == NULL) {
debug( "invalid request line, no full address\n");
free(tmp_buf);
free(parse->buf);
parse->buf = NULL;
return -1;
}
parse->version = full_addr + strlen(full_addr) + 1;
if (parse->version == NULL) {
debug( "invalid request line, missing version\n");
free(tmp_buf);
free(parse->buf);
parse->buf = NULL;
return -1;
}
if (strncmp (parse->version, "HTTP/", 5)) {
debug( "invalid request line, unsupported version %s\n",
parse->version);
free(tmp_buf);
free(parse->buf);
parse->buf = NULL;
return -1;
}
parse->protocol = strtok_r(full_addr, "://", &saveptr);
if (parse->protocol == NULL) {
debug( "invalid request line, missing host\n");
free(tmp_buf);
free(parse->buf);
parse->buf = NULL;
return -1;
}
const char *rem = full_addr + strlen(parse->protocol) + strlen("://");
size_t abs_uri_len = strlen(rem);
parse->host = strtok_r(NULL, "/", &saveptr);
if (parse->host == NULL) {
debug( "invalid request line, missing host\n");
free(tmp_buf);
free(parse->buf);
parse->buf = NULL;
return -1;
}
if (strlen(parse->host) == abs_uri_len) {
debug("invalid request line, missing absolute path\n");
free(tmp_buf);
free(parse->buf);
parse->buf = NULL;
return -1;
}
parse->path = strtok_r(NULL, " ", &saveptr);
if (parse->path == NULL) { // replace empty abs_path with "/"
int rlen = strlen(root_abs_path);
parse->path = (char *)malloc(rlen + 1);
strncpy(parse->path, root_abs_path, rlen + 1);
} else if (strncmp(parse->path, root_abs_path, strlen(root_abs_path)) == 0) {
debug("invalid request line, path cannot begin "
"with two slash characters\n");
free(tmp_buf);
free(parse->buf);
parse->buf = NULL;
parse->path = NULL;
return -1;
} else {
// copy parse->path, prefix with a slash
char *tmp_path = parse->path;
int rlen = strlen(root_abs_path);
int plen = strlen(parse->path);
parse->path = (char *)malloc(rlen + plen + 1);
strncpy(parse->path, root_abs_path, rlen);
strncpy(parse->path + rlen, tmp_path, plen + 1);
}
parse->host = strtok_r(parse->host, ":", &saveptr);
parse->port = strtok_r(NULL, "/", &saveptr);
if (parse->host == NULL) {
debug( "invalid request line, missing host\n");
free(tmp_buf);
free(parse->buf);
free(parse->path);
parse->buf = NULL;
parse->path = NULL;
return -1;
}
if (parse->port != NULL) {
int port = strtol (parse->port, (char **)NULL, 10);
if (port == 0 && errno == EINVAL) {
debug("invalid request line, bad port: %s\n", parse->port);
free(tmp_buf);
free(parse->buf);
free(parse->path);
parse->buf = NULL;
parse->path = NULL;
return -1;
}
}
/* Parse headers */
int ret = 0;
currentHeader = strstr(tmp_buf, "\r\n")+2;
while (currentHeader[0] != '\0' &&
!(currentHeader[0] == '\r' && currentHeader[1] == '\n')) {
//debug("line %s %s", parse->version, currentHeader);
if (ParsedHeader_parse(parse, currentHeader)) {
ret = -1;
break;
}
currentHeader = strstr(currentHeader, "\r\n");
if (currentHeader == NULL || strlen (currentHeader) < 2)
break;
currentHeader += 2;
}
free(tmp_buf);
return ret;
}
/*
ParsedRequest Private Methods
*/
size_t ParsedRequest_requestLineLen(struct ParsedRequest *pr)
{
if (!pr || !pr->buf)
return 0;
size_t len =
strlen(pr->method) + 1 + strlen(pr->protocol) + 3 +
strlen(pr->host) + 1 + strlen(pr->version) + 2;
if(pr->port != NULL)
{
len += strlen(pr->port)+1;
}
/* path is at least a slash */
len += strlen(pr->path);
return len;
}
int ParsedRequest_printRequestLine(struct ParsedRequest *pr,
char * buf, size_t buflen,
size_t *tmp)
{
char * current = buf;
if(buflen < ParsedRequest_requestLineLen(pr))
{
debug("not enough memory for first line\n");
return -1;
}
memcpy(current, pr->method, strlen(pr->method));
current += strlen(pr->method);
current[0] = ' ';
current += 1;
memcpy(current, pr->protocol, strlen(pr->protocol));
current += strlen(pr->protocol);
memcpy(current, "://", 3);
current += 3;
memcpy(current, pr->host, strlen(pr->host));
current += strlen(pr->host);
if(pr->port != NULL)
{
current[0] = ':';
current += 1;
memcpy(current, pr->port, strlen(pr->port));
current += strlen(pr->port);
}
/* path is at least a slash */
memcpy(current, pr->path, strlen(pr->path));
current += strlen(pr->path);
current[0] = ' ';
current += 1;
memcpy(current, pr->version, strlen(pr->version));
current += strlen(pr->version);
memcpy(current, "\r\n", 2);
current +=2;
*tmp = current-buf;
return 0;
}