Goto sanos source index
//
// httpd.c
//
// HTTP Server
//
// Copyright (C) 2002 Michael Ringgaard. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
// 1. Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// 3. Neither the name of the project nor the names of its contributors
// may be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
// SUCH DAMAGE.
//
#include <os.h>
#include <sys/types.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <inifile.h>
#include <ctype.h>
#include <time.h>
#include <httpd.h>
struct mimetype {
char *ext;
char *mime;
};
struct mimetype mimetypes[] = {
{"txt", "text/plain"},
{"html", "text/html"},
{"htm", "text/html"},
{"rtx", "text/richtext"},
{"css", "text/css"},
{"xml", "text/xml"},
{"dtd", "text/xml"},
{"gif", "image/gif"},
{"jpg", "image/jpeg"},
{"jpeg", "image/jpeg"},
{"tif", "image/tiff"},
{"tiff", "image/tiff"},
{"pbm", "image/x-portable-bitmap"},
{"pgm", "image/x-portable-graymap"},
{"ppm", "image/x-portable-pixmap"},
{"pnm", "image/x-portable-anymap"},
{"xbm", "image/x-xbitmap"},
{"xpm", "image/x-xpixmap"},
{"png", "image/png"},
{"au", "audio/basic"},
{"snd", "audio/basic"},
{"aif", "audio/x-aiff"},
{"aiff", "audio/x-aiff"},
{"aifc", "audio/x-aiff"},
{"ra", "audio/x-pn-realaudio"},
{"ram", "audio/x-pn-realaudio"},
{"rm", "audio/x-pn-realaudio"},
{"rpm", "audio/x-pn-realaudio-plugin"},
{"wav", "audio/wav"},
{"mid", "audio/midi"},
{"midi", "audio/midi"},
{"mpga", "audio/mpeg"},
{"mp2", "audio/mpeg"},
{"mp3", "audio/mpeg"},
{"mpeg", "video/mpeg"},
{"mpg", "video/mpeg"},
{"mpe", "video/mpeg"},
{"qt", "video/quicktime"},
{"mov", "video/quicktime"},
{"avi", "video/x-msvideo"},
{"dll", "application/octet-stream"},
{"exe", "application/octet-stream"},
{"sys", "application/octet-stream"},
{"class","application/java"},
{"js", "application/x-javascript"},
{"eps", "application/postscript"},
{"ps", "application/postscript"},
{"spl", "application/futuresplash"},
{"swf", "application/x-shockwave-flash"},
{"dvi", "application/x-dvi"},
{"gtar", "application/x-gtar"},
{"hqx", "application/mac-binhex40"},
{"latex","application/x-latex"},
{"oda", "application/oda"},
{"pdf", "application/pdf"},
{"rtf", "application/rtf"},
{"sit", "application/x-stuffit"},
{"tar", "application/x-tar"},
{"tex", "application/x-tex"},
{"zip", "application/x-zip-compressed"},
{"doc", "application/msword"},
{"ppt", "application/powerpoint"},
{"wrl", "model/vrml"},
{"vrml", "model/vrml"},
{"mime", "message/rfc822"},
{"ini", "text/plain"},
{"wml", "text/vnd.wap.wml"},
{"wmlc", "application/vnd.wap.wmlc"},
{"wmls", "text/vnd.wap.wmlscript"},
{"wmlsc","application/vnd.wap.wmlscriptc"},
{"wbmp", "image/vnd.wap.wbmp"},
{NULL, NULL}
};
char *statustitle(int status) {
switch (status) {
case 200: return "OK";
case 204: return "No Content";
case 302: return "Moved";
case 304: return "Not Modified";
case 400: return "Bad Request";
case 401: return "Not Authorized";
case 403: return "Forbidden";
case 404: return "Not Found";
case 405: return "Method Not Allowed";
case 500: return "Internal Server Error";
case 501: return "Not Implemented";
case 503: return "Service Unavailable";
case 505: return "HTTP Version Not Supported";
}
return "Internal Error";
}
struct httpd_server *httpd_initialize(struct section *cfg) {
struct httpd_server *server;
char *name;
server = (struct httpd_server *) malloc(sizeof(struct httpd_server));
if (!server) return NULL;
memset(server, 0, sizeof(struct httpd_server));
mkcs(&server->srvlock);
server->cfg = cfg;
server->port = getnumconfig(cfg, "port", 80);
server->num_workers = getnumconfig(cfg, "workerthreads", 1);
server->min_hdrbufsiz = getnumconfig(cfg, "minhdrsize", 1024);
server->max_hdrbufsiz = getnumconfig(cfg, "maxhdrsize", 16 * 1024);
server->reqbufsiz = getnumconfig(cfg, "requestbuffer", 4096);
server->rspbufsiz = getnumconfig(cfg, "responsebuffer", 4096);
server->backlog = getnumconfig(cfg, "backlog", 5);
server->indexname = getstrconfig(cfg, "indexname", "index.htm");
server->swname = getstrconfig(cfg, "swname", gettib()->peb->osname);
server->allowdirbrowse = getnumconfig(cfg, "allowdirbrowse", 1);
parse_log_columns(server, getstrconfig(cfg, "logcolumns", "date time c-ip cs-username s-ip s-port cs-method cs-uri-stem cs-uri-query sc-status cs(user-agent)"));
server->logdir = getstrconfig(cfg, "logdir", NULL);
server->logfd = -1;
if (cfg) {
name = getstrconfig(cfg, "mimemap", "mimetypes");
server->mimemap = find_section(cfg, name);
}
return server;
}
int httpd_terminate(struct httpd_server *server) {
return -ENOSYS;
}
char *httpd_get_mimetype(struct httpd_server *server, char *ext) {
struct property *prop;
struct mimetype *m;
if (!ext) return NULL;
// Find MIME type in servers mime map
if (server && server->mimemap) {
prop = server->mimemap->properties;
while (prop) {
if (stricmp(ext, prop->name) == 0) return prop->value;
prop = prop->next;
}
}
// Find MIME type in default MIME type table
m = mimetypes;
while (m->ext) {
if (stricmp(ext, m->ext) == 0) return m->mime;
m++;
}
return NULL;
}
struct httpd_context *httpd_add_context(struct httpd_server *server, char *alias, httpd_handler handler, void *userdata, struct section *cfg) {
struct httpd_context *context;
context = (struct httpd_context *) malloc(sizeof(struct httpd_context));
if (!context) return NULL;
memset(context, 0, sizeof(struct httpd_context));
context->server = server;
context->cfg = cfg;
context->location = "/";
context->alias = getstrconfig(cfg, "alias", alias);
if (strcmp(context->alias, "/") == 0) context->alias = "";
context->handler = handler;
context->userdata = userdata;
context->next = server->contexts;
server->contexts = context;
return context;
}
struct httpd_context *httpd_add_file_context(struct httpd_server *server, char *alias, char *location, struct section *cfg) {
struct httpd_context *context;
context = httpd_add_context(server, alias, httpd_file_handler, NULL, cfg);
if (!context) return NULL;
context->location = getstrconfig(cfg, "location", location);
if (strcmp(context->location, "/") == 0) context->location = "";
return context;
}
struct httpd_context *httpd_add_resource_context(struct httpd_server *server, char *alias, hmodule_t hmod, struct section *cfg) {
struct httpd_context *context;
char modfn[MAXPATH];
struct stat statbuf;
if (getmodpath(hmod, modfn, MAXPATH) < 0) return NULL;
if (stat(modfn, &statbuf) < 0) return NULL;
context = httpd_add_context(server, alias, httpd_resource_handler, NULL, cfg);
if (!context) return NULL;
context->hmod = hmod;
context->mtime = statbuf.st_mtime;
return context;
}
void httpd_accept(struct httpd_server *server) {
int sock;
httpd_sockaddr addr;
struct httpd_connection *conn;
int addrlen;
addrlen = sizeof(addr);
sock = accept(server->sock, &addr.sa, &addrlen);
if (sock < 0) return;
//printf("connect %s port %d\n", inet_ntoa(addr.sa_in.sin_addr), ntohs(addr.sa_in.sin_port));
conn = (struct httpd_connection *) malloc(sizeof(struct httpd_connection));
if (!conn) return;
memset(conn, 0, sizeof(struct httpd_connection));
conn->server = server;
memcpy(&conn->client_addr, &addr, sizeof(httpd_sockaddr));
conn->sock = sock;
addrlen = sizeof(conn->server_addr);
getsockname(conn->sock, &conn->server_addr.sa, &addrlen);
enter(&server->srvlock);
if (server->connections) server->connections->prev = conn;
conn->next = server->connections;
conn->prev = NULL;
server->connections = conn;
leave(&server->srvlock);
dispatch(server->iomux, conn->sock, IOEVT_READ | IOEVT_CLOSE | IOEVT_ERROR, (int) conn);
}
void httpd_finish_processing(struct httpd_connection *conn) {
if (conn->req) {
if (conn->req->decoded_url) free(conn->req->decoded_url);
if (conn->req->path_translated) free(conn->req->path_translated);
if (conn->req->username) free(conn->req->username);
conn->req = NULL;
}
if (conn->rsp) {
conn->rsp = NULL;
}
}
int httpd_terminate_request(struct httpd_connection *conn)
{
int off = 0;
if (!conn->keep) return 0;
if (conn->fd >= 0) {
close(conn->fd);
conn->fd = -1;
}
conn->fixed_rsp_data = NULL;
conn->fixed_rsp_len = 0;
ioctl(conn->sock, FIONBIO, &off, sizeof(off));
//printf("terminate request, %d bytes in reqhdr\n", buffer_size(&conn->reqhdr));
free_buffer(&conn->reqhdr);
free_buffer(&conn->reqbody);
free_buffer(&conn->rsphdr);
free_buffer(&conn->rspbody);
conn->keep = 0;
conn->hdrsent = 0;
return 1;
}
void httpd_close_connection(struct httpd_connection *conn) {
struct httpd_server *server = conn->server;
//printf("close %s port %d\n", inet_ntoa(conn->client_addr.sa_in.sin_addr), ntohs(conn->client_addr.sa_in.sin_port));
close(conn->sock);
if (conn->fd >= 0) {
close(conn->fd);
conn->fd = -1;
}
free_buffer(&conn->reqhdr);
free_buffer(&conn->reqbody);
free_buffer(&conn->rsphdr);
free_buffer(&conn->rspbody);
enter(&server->srvlock);
if (conn->next) conn->next->prev = conn->prev;
if (conn->prev) conn->prev->next = conn->next;
if (conn == server->connections) server->connections = conn->next;
leave(&server->srvlock);
free(conn);
}
int httpd_recv(struct httpd_request *req, char *data, int len) {
struct httpd_buffer *buf;
int rc;
int n;
// First copy any remaining data in the request header buffer
buf = &req->conn->reqhdr;
n = buf->end - buf->start;
if (n > 0) {
if (n > len) n = len;
memcpy(data, buf->start, n);
buf->start += n;
return n;
}
// Allocate request body buffer if it has not already been done
buf = &req->conn->reqbody;
if (!buf->floor) {
rc = allocate_buffer(buf, req->conn->server->reqbufsiz);
if (rc < 0) return rc;
}
// Receive new data if request body buffer empty
if (buf->end == buf->start) {
n = recv(req->conn->sock, buf->floor, buf->ceil - buf->floor, 0);
if (n < 0) return n;
if (n == 0) {
errno = ECONNRESET;
return -1;
}
buf->start = buf->floor;
buf->end = buf->floor + rc;
}
// Copy data from request body buffer
n = buf->end - buf->start;
if (n > len) n = len;
memcpy(data, buf->start, n);
buf->start += n;
return n;
}
int httpd_send_header(struct httpd_response *rsp, int state, char *title, char *headers) {
int rc;
char buf[2048];
char datebuf[32];
rsp->status = state;
sprintf(buf, "HTTP/%s %d %s\r\nServer: %s\r\nDate: %s\r\n",
rsp->conn->req->http11 ? "1.1" : "1.0",
state, title, rsp->conn->server->swname,
rfctime(time(0), datebuf));
rc = bufcat(&rsp->conn->rsphdr, buf);
if (rc < 0) return rc;
if (rsp->last_modified) {
sprintf(buf, "Last-Modified: %s\r\n", rfctime(rsp->last_modified, datebuf));
rc = bufcat(&rsp->conn->rsphdr, buf);
if (rc < 0) return rc;
}
if (rsp->content_type) {
sprintf(buf, "Content-Type: %s\r\n", rsp->content_type);
rc = bufcat(&rsp->conn->rsphdr, buf);
if (rc < 0) return rc;
}
if (rsp->content_length >= 0) {
sprintf(buf, "Content-Length: %d\r\n", rsp->content_length);
rc = bufcat(&rsp->conn->rsphdr, buf);
if (rc < 0) return rc;
}
if (headers) {
rc = bufcat(&rsp->conn->rsphdr, headers);
if (rc < 0) return rc;
}
if (rsp->content_length < 0) rsp->keep_alive = 0;
if (state >= 500) rsp->keep_alive = 0;
if (rsp->keep_alive) {
rc = bufcat(&rsp->conn->rsphdr, "Connection: Keep-Alive\r\n");
if (rc < 0) return rc;
}
rc = bufcat(&rsp->conn->rsphdr, "\r\n");
if (rc < 0) return rc;
return 0;
}
int httpd_send_error(struct httpd_response *rsp, int state, char *title, char *msg) {
int rc;
char buf[2048];
if (!msg) msg = "";
sprintf(buf, "<HTML><HEAD><TITLE>%d %s</TITLE></HEAD>\n<BODY><H1>%d %s</H1>\n%s\n</BODY></HTML>\n",
state, title, state, title, msg);
rsp->content_type = "text/html";
rsp->content_length = strlen(buf);
rc = httpd_send_header(rsp, state, title ? title : statustitle(state), NULL);
if (rc < 0) return rc;
rc = httpd_send(rsp, buf, strlen(buf));
if (rc < 0) return rc;
return 0;
}
int httpd_redirect(struct httpd_response *rsp, char *newloc) {
char *hdrs;
int rc;
hdrs = (char *) malloc(3 * strlen(newloc) + 16);
if (!hdrs) return -1;
strcpy(hdrs, "Location: ");
encode_url(newloc, hdrs + 10);
strcat(hdrs, "\r\n");
sprintf(hdrs, "Location: %s\r\n", newloc);
rsp->content_length = 0;
rc = httpd_send_header(rsp, 302, "Moved", hdrs);
free(hdrs);
return rc;
}
int httpd_send(struct httpd_response *rsp, char *data, int len) {
struct httpd_buffer *buf = &rsp->conn->rspbody;
int rc;
int left;
int n;
if (len == -1) len = strlen(data);
// Send directly if data larger than buffer
if (len > rsp->conn->server->rspbufsiz) {
rc = httpd_flush(rsp);
if (rc < 0) return rc;
rc = send(rsp->conn->sock, data, len, 0);
return rc;
}
// Allocate response body buffer if not already done
if (!buf->floor) {
rc = allocate_buffer(buf, rsp->conn->server->rspbufsiz);
if (rc < 0) return rc;
}
left = len;
while (left > 0) {
// Send buffer if full
if (buf->ceil == buf->end) {
rc = httpd_flush(rsp);
if (rc < 0) return rc;
}
// Copy data to buffer
n = left;
if (n > buf->ceil - buf->end) n = buf->ceil - buf->end;
memcpy(buf->end, data, n);
buf->end += n;
left -= n;
data += n;
}
return len;
}
int httpd_flush(struct httpd_response *rsp) {
struct httpd_buffer *buf;
int rc;
// Send response header if not already done
if (!rsp->conn->hdrsent) {
buf = &rsp->conn->rsphdr;
// Generate standard header
if (buf->start == buf->end) {
rc = httpd_send_header(rsp, 200, "OK", NULL);
if (rc < 0) return rc;
}
// Send header
if (buf->end != buf->start) {
rc = send(rsp->conn->sock, buf->start, buf->end - buf->start, 0);
if (rc < 0) return rc;
buf->start = buf->end;
}
rsp->conn->hdrsent = 1;
}
// Send response body
buf = &rsp->conn->rspbody;
if (buf->end != buf->start) {
rc = send(rsp->conn->sock, buf->start, buf->end - buf->start, 0);
if (rc < 0) return rc;
buf->start = buf->end = buf->floor;
}
return 0;
}
int httpd_send_file(struct httpd_response *rsp, int fd) {
rsp->conn->fd = fd;
return 0;
}
int httpd_send_fixed_data(struct httpd_response *rsp, char *data, int len) {
rsp->conn->fixed_rsp_data = data;
rsp->conn->fixed_rsp_len = len;
return 0;
}
int httpd_check_header(struct httpd_connection *conn) {
char c;
while (conn->reqhdr.start < conn->reqhdr.end) {
c = *conn->reqhdr.start++;
switch (conn->hdrstate) {
case HDR_STATE_FIRSTWORD:
switch (c) {
case ' ':
case '\t':
conn->hdrstate = HDR_STATE_FIRSTWS;
break;
case '\n':
case '\r':
conn->hdrstate = HDR_STATE_BOGUS;
return -EINVAL;
}
break;
case HDR_STATE_FIRSTWS:
switch (c) {
case ' ':
case '\t':
break;
case '\n':
case '\r':
conn->hdrstate = HDR_STATE_BOGUS;
return -EINVAL;
default:
conn->hdrstate = HDR_STATE_SECONDWORD;
}
break;
case HDR_STATE_SECONDWORD:
switch (c) {
case ' ':
case '\t':
conn->hdrstate = HDR_STATE_SECONDWS;
break;
case '\n':
case '\r':
// The first line has only two words - an HTTP/0.9 request
return 1;
}
break;
case HDR_STATE_SECONDWS:
switch (c) {
case ' ':
case '\t':
break;
case '\n':
case '\r':
conn->hdrstate = HDR_STATE_BOGUS;
return -EINVAL;
default:
conn->hdrstate = HDR_STATE_THIRDWORD;
}
break;
case HDR_STATE_THIRDWORD:
switch (c) {
case ' ':
case '\t':
conn->hdrstate = HDR_STATE_BOGUS;
return -EINVAL;
case '\n':
conn->hdrstate = HDR_STATE_LF;
break;
case '\r':
conn->hdrstate = HDR_STATE_CR;
break;
}
break;
case HDR_STATE_LINE:
switch (c) {
case '\n':
conn->hdrstate = HDR_STATE_LF;
break;
case '\r':
conn->hdrstate = HDR_STATE_CR;
break;
}
break;
case HDR_STATE_LF:
switch (c) {
case '\n':
// Two newlines in a row - a blank line - end of request
return 1;
case '\r':
conn->hdrstate = HDR_STATE_CR;
break;
default:
conn->hdrstate = HDR_STATE_LINE;
}
break;
case HDR_STATE_CR:
switch (c) {
case '\n':
conn->hdrstate = HDR_STATE_CRLF;
break;
case '\r':
// Two returns in a row - end of request
return 1;
default:
conn->hdrstate = HDR_STATE_LINE;
}
break;
case HDR_STATE_CRLF:
switch (c) {
case '\n':
// Two newlines in a row - end of request
return 1;
case '\r':
conn->hdrstate = HDR_STATE_CRLFCR;
break;
default:
conn->hdrstate = HDR_STATE_LINE;
}
break;
case HDR_STATE_CRLFCR:
switch (c) {
case '\n':
case '\r':
// Two CRLFs or two CRs in a row - end of request
return 1;
default:
conn->hdrstate = HDR_STATE_LINE;
}
break;
case HDR_STATE_BOGUS:
return -EINVAL;
}
}
return 0;
}
int httpd_parse_request(struct httpd_request *req) {
struct httpd_buffer *buf;
char *s;
char *l;
int rc;
buf = &req->conn->reqhdr;
buf->start = buf->floor;
s = bufgets(buf);
if (!s) {
errno = EINVAL;
return -1;
}
// Parse method
req->method = s;
s = strchr(s, ' ');
if (!s) {
errno = EINVAL;
return -1;
}
*s++ = 0;
// Parse URL
while (*s == ' ') s++;
req->encoded_url = s;
s = strchr(s, ' ');
// Parse protocol version
if (*s) {
*s++ = 0;
while (*s == ' ') s++;
if (*s) {
req->protocol = s;
} else {
req->protocol = "HTTP/0.9";
}
if (strcmp(req->protocol, "HTTP/1.1") == 0) req->http11 = 1;
}
// Decode and split URL
req->decoded_url = (char *) malloc(strlen(req->encoded_url) + 1);
if (!req->decoded_url) return -1;
rc = decode_url(req->encoded_url, req->decoded_url);
if (rc < 0) return rc;
req->pathinfo = req->decoded_url;
s = strchr(req->pathinfo, '?');
if (s) {
*s++ = 0;
req->query = s;
}
//printf("method: [%s]\n", req->method);
//printf("pathinfo: [%s]\n", req->pathinfo);
//printf("query: [%s]\n", req->query);
//printf("protocol: [%s]\n", req->protocol);
// Parse headers
req->nheaders = 0;
while ((l = bufgets(buf))) {
if (!*l) continue;
s = strchr(l, ':');
if (!s) continue;
*s++ = 0;
while (*s == ' ') s++;
if (!*s) continue;
if (stricmp(l, "Referer") == 0) {
req->referer = s;
} else if (stricmp(l, "User-agent") == 0) {
req->user_agent = s;
} else if (stricmp(l, "Accept") == 0) {
req->accept = s;
} else if (stricmp(l, "Cookie") == 0) {
req->cookie = s;
} else if (stricmp(l, "Authorization") == 0) {
req->authorization = s;
} else if (stricmp(l, "Content-type") == 0) {
req->content_type = s;
} else if (stricmp(l, "Content-length") == 0) {
req->content_length = atoi(s);
} else if (stricmp(l, "Host") == 0) {
req->host = s;
} else if (stricmp(l, "If-modified-since") == 0) {
req->if_modified_since = timerfc(s);
} else if (stricmp(l, "Connection") == 0) {
req->keep_alive = stricmp(s, "keep-alive") == 0;
}
//printf("hdr: %s: %s\n", l, s);
if (req->nheaders < MAX_HTTP_HEADERS) {
req->headers[req->nheaders].name = l;
req->headers[req->nheaders].value = s;
req->nheaders++;
}
}
return 0;
}
int httpd_find_context(struct httpd_request *req) {
int n;
char *s;
struct httpd_context *context = req->conn->server->contexts;
char *pathinfo = req->pathinfo;
while (context) {
n = strlen(context->alias);
s = pathinfo + n;
if (strncmp(context->alias, pathinfo, n) == 0 && (*s == '/' || *s == 0)) {
if (*s) {
req->pathinfo = s + 1;
} else {
req->pathinfo = "";
}
req->context = context;
return 0;
}
context = context->next;
}
return -1;
}
int httpd_translate_path(struct httpd_request *req) {
int rc;
char buf[512];
char path[256];
int loclen;
int pathlen;
char *p;
if (req->context->location == NULL) {
rc = httpd_send_error(req->conn->rsp, 500, "Internal Server Error", "No location for context");
if (rc < 0) return rc;
return 0;
}
loclen = strlen(req->context->location);
pathlen = strlen(req->pathinfo);
if (loclen + 1 + pathlen >= sizeof(buf)) {
rc = httpd_send_error(req->conn->rsp, 400, "Bad Request", "URL to long");
if (rc < 0) return rc;
return 0;
}
memcpy(buf, req->context->location, loclen);
buf[loclen] = '/';
memcpy(buf + loclen + 1, req->pathinfo, pathlen);
buf[loclen + 1 + pathlen] = 0;
rc = canonicalize(buf, path, sizeof(path));
if (rc < 0) {
rc = httpd_send_error(req->conn->rsp, 400, "Bad Request", "Bad URL");
if (rc < 0) return rc;
return 0;
}
p = path;
while (*p) {
if (*p == '\\') *p = '/';
p++;
}
if (loclen > 0 && strnicmp(req->context->location, path, loclen) != 0) {
rc = httpd_send_error(req->conn->rsp, 400, "Bad Request", "Illegal URL");
if (rc < 0) return rc;
return 0;
}
req->path_translated = strdup(path);
if (!req) return -1;
return 1;
}
int httpd_write(struct httpd_connection *conn) {
int left;
int bytes;
int rc;
// Sent any remaining data in response header
left = conn->rsphdr.end - conn->rsphdr.start;
if (left > 0) {
bytes = send(conn->sock, conn->rsphdr.start, left, 0);
if (bytes < 0) return bytes;
conn->rsphdr.start += bytes;
if (bytes < left) return 1;
}
// Sent any remaining fixed response data
left = conn->fixed_rsp_len;
if (left > 0) {
bytes = send(conn->sock, conn->fixed_rsp_data, left, 0);
if (bytes < 0) return bytes;
conn->fixed_rsp_data += bytes;
conn->fixed_rsp_len -= bytes;
if (bytes < left) return 1;
}
// Send response body
while (1) {
// Send any remaining data in response body buffer
left = conn->rspbody.end - conn->rspbody.start;
if (left > 0) {
bytes = send(conn->sock, conn->rspbody.start, left, 0);
if (bytes < 0) return bytes;
conn->rspbody.start += bytes;
if (bytes < left) return 1;
}
// Fill response buffer
if (conn->fd >= 0) {
// Allocate response body buffer if not already done
if (conn->rspbody.floor == NULL) {
rc = allocate_buffer(&conn->rspbody, conn->server->rspbufsiz);
if (rc < 0) return rc;
}
// Read from file
bytes = read(conn->fd, conn->rspbody.floor, buffer_capacity(&conn->rspbody));
if (bytes < 0) return bytes;
if (bytes == 0) return 0;
conn->rspbody.start = conn->rspbody.floor;
conn->rspbody.end = conn->rspbody.floor + bytes;
} else {
return 0;
}
}
}
int httpd_process(struct httpd_connection *conn) {
struct httpd_request req;
struct httpd_response rsp;
int rc;
int size;
int on = 1;
// Initialize new request and response objects
memset(&req, 0, sizeof(struct httpd_request));
memset(&rsp, 0, sizeof(struct httpd_response));
rsp.content_length = -1;
req.conn = conn;
rsp.conn = conn;
conn->req = &req;
conn->rsp = &rsp;
conn->fd = -1;
conn->keep = 0;
conn->hdrsent = 0;
// Parse HTTP request
rc = httpd_parse_request(&req);
if (rc < 0) goto errorexit;
rsp.keep_alive = req.keep_alive;
// Find context handler for request
rc = httpd_find_context(&req);
if (rc < 0) {
// No context found - return error
rc = httpd_send_error(&rsp, 404, "Not Found", "Context does not exist");
if (rc < 0) goto errorexit;
} else {
// Translate path
rc = httpd_translate_path(&req);
if (rc < 0) goto errorexit;
if (rc > 0) {
// Call context handler to handle request
rc = req.context->handler(conn);
if (rc < 0) goto errorexit;
if (rc > 0) {
// Return HTTP error to client
rc = httpd_send_error(&rsp, rc, NULL, NULL);
if (rc < 0) goto errorexit;
}
// Build HTTP header if not already done
if (!conn->hdrsent && buffer_empty(&conn->rsphdr)) {
if (rsp.content_length < 0) {
int len = 0;
len += buffer_size(&conn->rspbody);
len += conn->fixed_rsp_len;
if (conn->fd >= 0) {
size = fstat(conn->fd, NULL);
if (size >= 0) len += size;
}
rsp.content_length = len;
}
rc = httpd_send_header(&rsp, 200, "OK", NULL);
if (rc < 0) goto errorexit;
}
}
}
// Log request
rc = log_request(&req);
if (rc < 0) goto errorexit;
// Prepare for sending back response
rc = ioctl(conn->sock, FIONBIO, &on, sizeof(on));
if (rc < 0) goto errorexit;
conn->keep = rsp.keep_alive;
httpd_finish_processing(conn);
return 0;
errorexit:
httpd_finish_processing(conn);
return -1;
}
int httpd_io(struct httpd_connection *conn) {
int rc;
switch (conn->state) {
case HTTP_STATE_IDLE:
rc = allocate_buffer(&conn->reqhdr, conn->server->min_hdrbufsiz);
if (rc < 0) return rc;
conn->state = HTTP_STATE_READ_REQUEST;
conn->hdrstate = HDR_STATE_FIRSTWORD;
// Fall through
case HTTP_STATE_READ_REQUEST:
if (buffer_capacity(&conn->reqhdr) >= conn->server->max_hdrbufsiz) return -EBUF;
rc = expand_buffer(&conn->reqhdr, 1);
if (rc < 0) return rc;
rc = recv(conn->sock, conn->reqhdr.end, buffer_left(&conn->reqhdr), 0);
if (rc <= 0) {
if (errno == ECONNRESET && buffer_size(&conn->reqhdr) == 0) {
// Keep-Alive connection closed
conn->state = HTTP_STATE_TERMINATED;
httpd_close_connection(conn);
return 1;
}
return rc;
}
conn->reqhdr.end += rc;
rc = httpd_check_header(conn);
if (rc < 0) return rc;
if (rc == 0) {
rc = dispatch(conn->server->iomux, conn->sock, IOEVT_READ | IOEVT_CLOSE | IOEVT_ERROR, (int) conn);
if (rc < 0) return rc;
return 1;
}
conn->state = HTTP_STATE_PROCESSING;
// Fall through
case HTTP_STATE_PROCESSING:
rc = httpd_process(conn);
if (rc < 0) return rc;
conn->state = HTTP_STATE_WRITE_RESPONSE;
// Fall through
case HTTP_STATE_WRITE_RESPONSE:
rc = httpd_write(conn);
if (rc < 0) return rc;
if (rc > 0) {
rc = dispatch(conn->server->iomux, conn->sock, IOEVT_WRITE | IOEVT_CLOSE | IOEVT_ERROR, (int) conn);
if (rc < 0) return rc;
} else {
rc = httpd_terminate_request(conn);
if (rc > 0) {
conn->state = HTTP_STATE_IDLE;
rc = dispatch(conn->server->iomux, conn->sock, IOEVT_READ | IOEVT_CLOSE | IOEVT_ERROR, (int) conn);
if (rc < 0) return rc;
} else {
conn->state = HTTP_STATE_TERMINATED;
httpd_close_connection(conn);
}
}
return 1;
case HTTP_STATE_TERMINATED:
return 1;
default:
errno = EINVAL;
return -1;
}
}
void __stdcall httpd_worker(void *arg) {
struct httpd_server *server = (struct httpd_server *) arg;
struct httpd_connection *conn;
int rc;
while (1) {
rc = waitone(server->iomux, INFINITE);
if (rc < 0) break;
conn = (struct httpd_connection *) rc;
if (conn == NULL) {
httpd_accept(server);
dispatch(server->iomux, server->sock, IOEVT_ACCEPT, 0);
} else {
rc = httpd_io(conn);
if (rc <= 0) {
httpd_close_connection(conn);
}
}
}
}
int httpd_start(struct httpd_server *server) {
int sock;
int rc;
int i;
httpd_sockaddr addr;
int hthread;
sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) return -1;
addr.sa_in.sin_family = AF_INET;
addr.sa_in.sin_port = htons(server->port);
addr.sa_in.sin_addr.s_addr = htonl(INADDR_ANY);
rc = bind(sock, &addr.sa, sizeof(addr));
if (rc < 0) {
close(sock);
return -1;
}
rc = listen(sock, server->backlog);
if (rc < 0) {
close(sock);
return -1;
}
ioctl(sock, FIONBIO, NULL, 0);
server->sock = sock;
server->iomux = mkiomux(0);
dispatch(server->iomux, server->sock, IOEVT_ACCEPT, 0);
for (i = 0; i < server->num_workers; i++) {
hthread = beginthread(httpd_worker, 0, server, 0, "http", NULL);
close(hthread);
}
return 0;
}
#ifdef _MSC_VER
int __stdcall DllMain(hmodule_t hmod, int reason, void *reserved) {
return TRUE;
}
#endif