#include "http.h" #include "server.h" #include "slog.h" #include "cmd.h" #include #include #include struct http_client * http_client_new(int fd, struct server *s) { struct http_client *c = calloc(1, sizeof(struct http_client)); c->fd = fd; c->s = s; c->settings.on_path = http_on_path; c->settings.on_body = http_on_body; c->settings.on_message_complete = http_on_complete; c->settings.on_header_field = http_on_header_name; c->settings.on_header_value = http_on_header_value; http_parser_init(&c->parser, HTTP_REQUEST); c->parser.data = c; return c; } void http_client_read(int fd, short event, void *ctx) { struct http_client *c = ctx; char buffer[64*1024]; int ret, nparsed; (void)fd; (void)event; ret = read(c->fd, buffer, sizeof(buffer)); if(ret <= 0) { /* broken connection, bye */ http_client_free(c); return; } c->buffer = realloc(c->buffer, c->sz + ret); memcpy(c->buffer + c->sz, buffer, ret); c->sz += ret; /* TODO: http parse. */ nparsed = http_parser_execute(&c->parser, &c->settings, buffer, ret); if(c->parser.upgrade) { /* TODO */ } else if(nparsed != ret) { /* invalid */ http_client_free(c); return; } if(!c->executing) { http_client_serve(c); } } static void http_client_cleanup(struct http_client *c) { free(c->path.s); memset(&c->path, 0, sizeof(str_t)); free(c->body.s); memset(&c->body, 0, sizeof(str_t)); free(c->header_connection.s); memset(&c->header_connection, 0, sizeof(str_t)); free(c->header_if_none_match.s); memset(&c->header_if_none_match, 0, sizeof(str_t)); free(c->out_content_type.s); memset(&c->out_content_type, 0, sizeof(str_t)); free(c->out_etag.s); memset(&c->out_etag, 0, sizeof(str_t)); free(c->buffer); c->buffer = NULL; c->sz = 0; memset(&c->verb, 0, sizeof(c->verb)); c->executing = 0; } void http_client_free(struct http_client *c) { event_del(&c->ev); close(c->fd); http_client_cleanup(c); free(c); } static int http_client_keep_alive(struct http_client *c) { /* check disconnection */ int disconnect = 0; if(c->parser.http_major == 1 && c->parser.http_minor == 0) { disconnect = 1; /* HTTP 1.0: disconnect by default */ } if(c->header_connection.s) { if(strncasecmp(c->header_connection.s, "Keep-Alive", 10) == 0) { disconnect = 0; } else if(strncasecmp(c->header_connection.s, "Close", 5) == 0) { disconnect = 1; } } return disconnect ? 0 : 1; } void http_client_reset(struct http_client *c) { if(!http_client_keep_alive(c)) { http_client_free(c); return; } http_client_cleanup(c); http_parser_init(&c->parser, HTTP_REQUEST); } /** * Add read event callback */ void http_client_serve(struct http_client *c) { event_set(&c->ev, c->fd, EV_READ, http_client_read, c); event_base_set(c->s->base, &c->ev); event_add(&c->ev, NULL); } int http_on_path(http_parser *p, const char *at, size_t length) { struct http_client *c = p->data; c->path.s = calloc(length+1, 1); memcpy(c->path.s, at, length); c->path.sz = length; /* save HTTP verb as well */ c->verb = (enum http_method)p->method; return 0; } int http_on_body(http_parser *p, const char *at, size_t length) { struct http_client *c = p->data; c->body.s = calloc(length+1, 1); memcpy(c->body.s, at, length); c->body.sz = length; return 0; } /* Adobe flash cross-domain request */ static int http_crossdomain(struct http_client *c) { struct http_response resp; char content_length[10]; char out[] = "\n" "\n" "\n" "\n" "\n"; http_response_init(&resp, 200, "OK"); http_response_set_header(&resp, "Content-Type", "application/xml"); sprintf(content_length, "%zd", sizeof(content_length)); http_response_set_header(&resp, "Content-Length", content_length); http_response_set_body(&resp, out, sizeof(out)-1); http_response_send(&resp, c->fd); http_client_reset(c); return 0; } /* reply to OPTIONS HTTP verb */ static int http_options(struct http_client *c) { struct http_response resp; http_response_init(&resp, 200, "OK"); http_response_set_header(&resp, "Content-Type", "text/html"); http_response_set_header(&resp, "Allow", "GET,POST,OPTIONS"); http_response_set_header(&resp, "Content-Length", "0"); /* Cross-Origin Resource Sharing, CORS. */ http_response_set_header(&resp, "Access-Control-Allow-Origin", "*"); http_response_send(&resp, c->fd); http_client_reset(c); return 0; } int http_on_complete(http_parser *p) { struct http_client *c = p->data; int ret = -1; c->executing = 1; /* check that the command can be executed */ switch(c->verb) { case HTTP_GET: if(c->path.sz == 16 && memcmp(c->path.s, "/crossdomain.xml", 16) == 0) { return http_crossdomain(c); } /* slog(s, WEBDIS_DEBUG, uri); */ ret = cmd_run(c->s, c, 1+c->path.s, c->path.sz-1, NULL, 0); break; case HTTP_POST: /*slog(s, WEBDIS_DEBUG, uri);*/ ret = cmd_run(c->s, c, 1+c->body.s, c->body.sz-1, NULL, 0); break; case HTTP_PUT: /* slog(s, WEBDIS_DEBUG, uri); */ ret = cmd_run(c->s, c, 1+c->path.s, c->path.sz-1, c->body.s, c->body.sz); break; case HTTP_OPTIONS: return http_options(c); default: slog(c->s, WEBDIS_DEBUG, "405"); /* evhttp_send_reply(rq, 405, "Method Not Allowed", NULL); */ return 0; } return ret; } void http_send_reply(struct http_client *c, short code, const char *msg, const char *body, size_t body_len) { struct http_response resp; char content_length[10]; const char *ct = c->out_content_type.s; if(!ct) { ct = "text/html"; } /* respond */ http_response_init(&resp, code, msg); if(!http_client_keep_alive(c)) { http_response_set_header(&resp, "Connection", "Close"); } else if(code == 200) { http_response_set_header(&resp, "Connection", "Keep-Alive"); } sprintf(content_length, "%zd", body_len); http_response_set_header(&resp, "Content-Length", content_length); if(body_len) { http_response_set_header(&resp, "Content-Type", ct); } if(code == 200 && c->out_etag.s) { http_response_set_header(&resp, "ETag", c->out_etag.s); } http_response_set_body(&resp, body, body_len); if(http_response_send(&resp, c->fd)) { http_client_free(c); } else { if(code == 200){ http_client_reset(c); http_client_serve(c); } else { http_client_free(c); } } } void http_set_header(str_t *h, const char *p) { size_t sz = strlen(p); h->s = calloc(1, 1+sz); memcpy(h->s, p, sz); h->sz = sz; } /** * Called when a header name is read */ int http_on_header_name(http_parser *p, const char *at, size_t length) { struct http_client *c = p->data; c->last_header_name.s = calloc(length+1, 1); memcpy(c->last_header_name.s, at, length); c->last_header_name.sz = length; return 0; } /** * Called when a header value is read */ int http_on_header_value(http_parser *p, const char *at, size_t length) { struct http_client *c = p->data; if(strncmp("Connection", c->last_header_name.s, c->last_header_name.sz) == 0) { c->header_connection.s = calloc(length+1, 1); memcpy(c->header_connection.s, at, length); c->header_connection.sz = length; } else if(strncmp("If-None-Match", c->last_header_name.s, c->last_header_name.sz) == 0) { c->header_if_none_match.s = calloc(length+1, 1); memcpy(c->header_if_none_match.s, at, length); c->header_if_none_match.sz = length; } free(c->last_header_name.s); c->last_header_name.s = NULL; return 0; } /* HTTP Response */ void http_response_init(struct http_response *r, int code, const char *msg) { memset(r, 0, sizeof(struct http_response)); r->code = code; r->msg = msg; http_response_set_header(r, "Server", "Webdis"); } void http_response_set_header(struct http_response *r, const char *k, const char *v) { size_t sz; char *s; sz = strlen(k) + 2 + strlen(v) + 2; s = calloc(sz + 1, 1); sprintf(s, "%s: %s\r\n", k, v); r->headers = realloc(r->headers, sizeof(str_t)*(r->header_count + 1)); r->headers[r->header_count].s = s; r->headers[r->header_count].sz = sz; r->header_count++; } void http_response_set_body(struct http_response *r, const char *body, size_t body_len) { r->body = body; r->body_len = body_len; } int http_response_send(struct http_response *r, int fd) { char *s = NULL, *p; size_t sz = 0; int i, ret; sz = sizeof("HTTP/1.x xxx ")-1 + strlen(r->msg) + 2; s = calloc(sz + 1, 1); ret = sprintf(s, "HTTP/1.1 %d %s\r\n", r->code, r->msg); p = s; // + ret - 3; for(i = 0; i < r->header_count; ++i) { s = realloc(s, sz + r->headers[i].sz); p = s + sz; memcpy(p, r->headers[i].s, r->headers[i].sz); p += r->headers[i].sz; sz += r->headers[i].sz; } /* end of headers */ s = realloc(s, sz + 2); memcpy(s + sz, "\r\n", 2); sz += 2; if(r->body && r->body_len) { s = realloc(s, sz + r->body_len); memcpy(s + sz, r->body, r->body_len); sz += r->body_len; } /* printf("sz=%zd, s=["); fflush(stdout); write(1, s, sz); printf("]\n"); */ ret = write(fd, s, sz); free(s); /* cleanup response object */ for(i = 0; i < r->header_count; ++i) { free(r->headers[i].s); } free(r->headers); return ret == (int)sz ? 0 : 1; }