aboutsummaryrefslogtreecommitdiffstats
path: root/request.c
diff options
context:
space:
mode:
authorkraxel <kraxel>2004-06-08 12:51:57 +0000
committerkraxel <kraxel>2004-06-08 12:51:57 +0000
commit996cd0bab7bfff049402fe3eb3ca5de88d69ab41 (patch)
tree79d9e47fe25645bec085a0fecd1d7dda50a95918 /request.c
downloadwebfs-996cd0bab7bfff049402fe3eb3ca5de88d69ab41.tar.gz
Initial revision
Diffstat (limited to 'request.c')
-rw-r--r--request.c631
1 files changed, 631 insertions, 0 deletions
diff --git a/request.c b/request.c
new file mode 100644
index 0000000..2bc4edb
--- /dev/null
+++ b/request.c
@@ -0,0 +1,631 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <syslog.h>
+#include <time.h>
+#include <ctype.h>
+#include <pwd.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+
+#include "httpd.h"
+
+/* ---------------------------------------------------------------------- */
+
+void
+read_request(struct REQUEST *req, int pipelined)
+{
+ int rc;
+ char *h;
+
+ restart:
+#ifdef USE_SSL
+ if (with_ssl)
+ rc = ssl_read(req, req->hreq + req->hdata, MAX_HEADER - req->hdata);
+ else
+#endif
+ rc = read(req->fd, req->hreq + req->hdata, MAX_HEADER - req->hdata);
+ switch (rc) {
+ case -1:
+ if (errno == EAGAIN) {
+ if (pipelined)
+ break; /* check if there is already a full request */
+ else
+ return;
+ }
+ if (errno == EINTR)
+ goto restart;
+ xperror(LOG_INFO,"read",req->peerhost);
+ /* fall through */
+ case 0:
+ req->state = STATE_CLOSE;
+ return;
+ default:
+ req->hdata += rc;
+ req->hreq[req->hdata] = 0;
+ }
+
+ /* check if this looks like a http request after
+ the first few bytes... */
+ if (req->hdata < 5)
+ return;
+ if (strncmp(req->hreq,"GET ",4) != 0 &&
+ strncmp(req->hreq,"PUT ",4) != 0 &&
+ strncmp(req->hreq,"HEAD ",5) != 0 &&
+ strncmp(req->hreq,"POST ",5) != 0) {
+ mkerror(req,400,0);
+ return;
+ }
+
+ /* header complete ?? */
+ if (NULL != (h = strstr(req->hreq,"\r\n\r\n")) ||
+ NULL != (h = strstr(req->hreq,"\n\n"))) {
+ if (*h == '\r') {
+ h += 4;
+ *(h-2) = 0;
+ } else {
+ h += 2;
+ *(h-1) = 0;
+ }
+ req->lreq = h - req->hreq;
+ req->state = STATE_PARSE_HEADER;
+ return;
+ }
+
+ if (req->hdata == MAX_HEADER) {
+ /* oops: buffer full, but found no complete request ... */
+ mkerror(req,400,0);
+ return;
+ }
+ return;
+}
+
+/* ---------------------------------------------------------------------- */
+
+static time_t
+parse_date(char *line)
+{
+ static char *m[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
+ char month[4];
+ struct tm tm;
+ int i;
+
+ line = strchr(line,' '); /* skip weekday */
+ if (NULL == line)
+ return -1;
+ line++;
+
+ /* first: RFC 1123 date ... */
+ if (6 != sscanf(line,"%2d %3s %4d %2d:%2d:%2d GMT",
+ &tm.tm_mday,month,&tm.tm_year,
+ &tm.tm_hour,&tm.tm_min,&tm.tm_sec))
+ /* second: RFC 1036 date ... */
+ if (6 != sscanf(line,"%2d-%3s-%2d %2d:%2d:%2d GMT",
+ &tm.tm_mday,month,&tm.tm_year,
+ &tm.tm_hour,&tm.tm_min,&tm.tm_sec))
+ /* third: asctime() format */
+ if (6 != sscanf(line,"%3s %2d %2d:%2d:%2d %4d",
+ month,&tm.tm_mday,
+ &tm.tm_hour,&tm.tm_min,&tm.tm_sec,
+ &tm.tm_year))
+ /* none worked :-( */
+ return -1;
+ for (i = 0; i <= 11; i++)
+ if (0 == strcmp(month,m[i]))
+ break;
+ tm.tm_mon = i;
+ if (tm.tm_year > 1900)
+ tm.tm_year -= 1900;
+
+ return mktime(&tm);
+}
+
+static off_t
+parse_off_t(char *str, int *pos)
+{
+ off_t value = 0;
+
+ while (isdigit(str[*pos])) {
+ value *= 10;
+ value += str[*pos] - '0';
+ (*pos)++;
+ }
+ return value;
+}
+
+static int
+parse_ranges(struct REQUEST *req)
+{
+ char *h,*line = req->range_hdr;
+ int i,off;
+
+ if (req->if_range > 0 && req->if_range != req->bst.st_mtime)
+ return 0;
+
+ for (h = line, req->ranges=1; *h != '\n' && *h != '\0'; h++)
+ if (*h == ',')
+ req->ranges++;
+ if (debug)
+ fprintf(stderr,"%03d: %d ranges:",req->fd,req->ranges);
+ req->r_start = malloc(req->ranges*sizeof(off_t));
+ req->r_end = malloc(req->ranges*sizeof(off_t));
+ req->r_head = malloc((req->ranges+1)*BR_HEADER);
+ req->r_hlen = malloc((req->ranges+1)*sizeof(int));
+ if (NULL == req->r_start || NULL == req->r_end ||
+ NULL == req->r_head || NULL == req->r_hlen) {
+ if (req->r_start) free(req->r_start);
+ if (req->r_end) free(req->r_end);
+ if (req->r_head) free(req->r_head);
+ if (req->r_hlen) free(req->r_hlen);
+ if (debug)
+ fprintf(stderr,"oom\n");
+ return 500;
+ }
+ for (i = 0, off=0; i < req->ranges; i++) {
+ if (line[off] == '-') {
+ off++;
+ if (!isdigit(line[off]))
+ goto parse_error;
+ req->r_start[i] = req->bst.st_size - parse_off_t(line,&off);
+ req->r_end[i] = req->bst.st_size;
+ } else {
+ if (!isdigit(line[off]))
+ goto parse_error;
+ req->r_start[i] = parse_off_t(line,&off);
+ if (line[off] != '-')
+ goto parse_error;
+ off++;
+ if (isdigit(line[off]))
+ req->r_end[i] = parse_off_t(line,&off) +1;
+ else
+ req->r_end[i] = req->bst.st_size;
+ }
+ off++; /* skip "," */
+ /* ranges ok? */
+ if (debug)
+ fprintf(stderr," %d-%d",
+ (int)(req->r_start[i]),
+ (int)(req->r_end[i]));
+ if (req->r_start[i] > req->r_end[i] ||
+ req->r_end[i] > req->bst.st_size)
+ goto parse_error;
+ }
+ if (debug)
+ fprintf(stderr," ok\n");
+ return 0;
+
+ parse_error:
+ req->ranges = 0;
+ if (debug)
+ fprintf(stderr," range error\n");
+ return 400;
+}
+
+static int
+unhex(unsigned char c)
+{
+ if (c < '@')
+ return c - '0';
+ return (c & 0x0f) + 9;
+}
+
+/* handle %hex quoting, also split path / querystring */
+static void
+unquote(unsigned char *path, unsigned char *qs, unsigned char *src)
+{
+ int q;
+ unsigned char *dst;
+
+ q=0;
+ dst = path;
+ while (src[0] != 0) {
+ if (!q && *src == '?') {
+ q = 1;
+ *dst = 0;
+ dst = qs;
+ src++;
+ continue;
+ }
+ if (q && *src == '+') {
+ *dst = ' ';
+ } else if ((*src == '%') && isxdigit(src[1]) && isxdigit(src[2])) {
+ *dst = (unhex(src[1]) << 4) | unhex(src[2]);
+ src += 2;
+ } else {
+ *dst = *src;
+ }
+ dst++;
+ src++;
+ }
+ *dst = 0;
+}
+
+/* delete unneeded path elements */
+static void
+fixpath(char *path)
+{
+ char *dst = path;
+ char *src = path;
+
+ for (;*src;) {
+ if (0 == strncmp(src,"//",2)) {
+ src++;
+ continue;
+ }
+ if (0 == strncmp(src,"/./",3)) {
+ src+=2;
+ continue;
+ }
+ *(dst++) = *(src++);
+ }
+ *dst = 0;
+}
+
+static int base64_table[] = {
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
+ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
+ -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
+ -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
+ 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
+};
+
+static void
+decode_base64(unsigned char *dest, unsigned char *src, int maxlen)
+{
+ int a,b,d;
+
+ for (a=0, b=0, d=0; *src != 0 && d < maxlen; src++) {
+ if (*src >= 128 || -1 == base64_table[*src])
+ break;
+ a = (a<<6) | base64_table[*src];
+ b += 6;
+ if (b >= 8) {
+ b -= 8;
+ dest[d++] = (a >> b) & 0xff;
+ }
+ }
+ dest[d] = 0;
+}
+
+static int sanity_checks(struct REQUEST *req)
+{
+ int i;
+
+ /* path: must start with a '/' */
+ if (req->path[0] != '/') {
+ mkerror(req,400,0);
+ return -1;
+ }
+
+ /* path: must not contain "/../" */
+ if (strstr(req->path,"/../")) {
+ mkerror(req,403,1);
+ return -1;
+ }
+
+ if (req->hostname[0] == '\0')
+ /* no hostname specified */
+ return 0;
+
+ /* validate hostname */
+ for (i = 0; req->hostname[i] != '\0'; i++) {
+ switch (req->hostname[i]) {
+ case 'A' ... 'Z':
+ req->hostname[i] += 32; /* lowercase */
+ case 'a' ... 'z':
+ case '0' ... '9':
+ case '-':
+ /* these are fine as-is */
+ break;
+ case '.':
+ /* some extra checks */
+ if (0 == i) {
+ /* don't allow a dot as first character */
+ mkerror(req,400,0);
+ return -1;
+ }
+ if ('.' == req->hostname[i-1]) {
+ /* don't allow two dots in sequence */
+ mkerror(req,400,0);
+ return -1;
+ }
+ break;
+ default:
+ /* invalid character */
+ mkerror(req,400,0);
+ return -1;
+ }
+ }
+ return 0;
+}
+
+void
+parse_request(struct REQUEST *req)
+{
+ char filename[MAX_PATH+1], proto[MAX_MISC+1], *h;
+ int port, rc, len;
+ time_t t;
+ struct passwd *pw=NULL;
+
+ if (debug > 2)
+ fprintf(stderr,"%s\n",req->hreq);
+
+ /* parse request. Hehe, scanf is powerfull :-) */
+ if (4 != sscanf(req->hreq,
+ "%" S(MAX_MISC) "[A-Z] "
+ "%" S(MAX_PATH) "[^ \t\r\n] HTTP/%d.%d",
+ req->type, filename, &(req->major),&(req->minor))) {
+ mkerror(req,400,0);
+ return;
+ }
+ if (filename[0] == '/') {
+ strncpy(req->uri,filename,sizeof(req->uri)-1);
+ } else {
+ port = 0;
+ *proto = 0;
+ if (4 != sscanf(filename,
+ "%" S(MAX_MISC) "[a-zA-Z]://"
+ "%" S(MAX_HOST) "[a-zA-Z0-9.-]:%d"
+ "%" S(MAX_PATH) "[^ \t\r\n]",
+ proto, req->hostname, &port, req->uri) &&
+ 3 != sscanf(filename,
+ "%" S(MAX_MISC) "[a-zA-Z]://"
+ "%" S(MAX_HOST) "[a-zA-Z0-9.-]"
+ "%" S(MAX_PATH) "[^ \t\r\n]",
+ proto, req->hostname, req->uri)) {
+ mkerror(req,400,0);
+ return;
+ }
+ if (*proto != 0 && 0 != strcasecmp(proto,"http")) {
+ mkerror(req,400,0);
+ return;
+ }
+ }
+
+ unquote(req->path,req->query,req->uri);
+ fixpath(req->path);
+ if (debug)
+ fprintf(stderr,"%03d: %s \"%s\" HTTP/%d.%d\n",
+ req->fd, req->type, req->path, req->major, req->minor);
+
+ if (0 != strcmp(req->type,"GET") &&
+ 0 != strcmp(req->type,"HEAD")) {
+ mkerror(req,501,0);
+ return;
+ }
+
+ if (0 == strcmp(req->type,"HEAD")) {
+ req->head_only = 1;
+ }
+
+ /* parse header lines */
+ req->keep_alive = req->minor;
+ for (h = req->hreq; h - req->hreq < req->lreq;) {
+ h = strchr(h,'\n');
+ if (NULL == h)
+ break;
+ h++;
+
+ h[-2] = 0;
+ h[-1] = 0;
+ list_add(&req->header,h,0);
+
+ if (0 == strncasecmp(h,"Connection: ",12)) {
+ req->keep_alive = (0 == strncasecmp(h+12,"Keep-Alive",10));
+
+ } else if (0 == strncasecmp(h,"Host: ",6)) {
+ if (2 != sscanf(h+6,"%" S(MAX_HOST) "[a-zA-Z0-9.-]:%d",
+ req->hostname,&port))
+ sscanf(h+6,"%" S(MAX_HOST) "[a-zA-Z0-9.-]",
+ req->hostname);
+
+ } else if (0 == strncasecmp(h,"If-Modified-Since: ",19)) {
+ if (-1 != (t = parse_date(h+19))) {
+ req->if_modified = t;
+ if (debug)
+ fprintf(stderr,"%03d: if-modified-since: %s",
+ req->fd,ctime(&t));
+ }
+
+ } else if (0 == strncasecmp(h,"If-Unmodified-Since: ",21)) {
+ if (-1 != (t = parse_date(h+21))) {
+ req->if_unmodified = t;
+ if (debug)
+ fprintf(stderr,"%03d: if-unmodified-since: %s",
+ req->fd,ctime(&t));
+ }
+
+ } else if (0 == strncasecmp(h,"If-Range: ",10)) {
+ if (-1 != (t = parse_date(h+10))) {
+ req->if_range = t;
+ if (debug)
+ fprintf(stderr,"%03d: if-range: %s\n",req->fd,ctime(&t));
+ }
+
+ } else if (0 == strncasecmp(h,"Authorization: Basic ",21)) {
+ decode_base64(req->auth,h+21,sizeof(req->auth)-1);
+ if (debug)
+ fprintf(stderr,"%03d: auth: %s\n",req->fd,req->auth);
+
+ } else if (0 == strncasecmp(h,"Range: bytes=",13)) {
+ /* parsing must be done after fstat, we need the file size
+ for the boundary checks */
+ req->range_hdr = h+13;
+ }
+ }
+
+ /* take care about the hostname */
+ if (virtualhosts) {
+ if (req->hostname[0] == 0) {
+ if (req->minor > 0) {
+ /* HTTP/1.1 clients MUST specify a hostname */
+ mkerror(req,400,0);
+ return;
+ }
+ strncpy(req->hostname,server_host,sizeof(req->hostname)-1);
+ }
+ } else {
+ if (req->hostname[0] == '\0' || canonicalhost)
+ strncpy(req->hostname,server_host,sizeof(req->hostname)-1);
+ }
+
+ /* checks */
+ if (0 != sanity_checks(req))
+ return;
+
+ /* check basic auth */
+ if (NULL != userpass && 0 != strcmp(userpass,req->auth)) {
+ mkerror(req,401,1);
+ return;
+ }
+
+ /* is CGI ? */
+ if (NULL != cgipath &&
+ 0 == strncmp(req->path,cgipath,strlen(cgipath))) {
+ cgi_request(req);
+ return;
+ }
+
+ /* build filename */
+ if (userdir && '~' == req->path[1]) {
+ /* expand user directories, i.e.
+ /~user/path/file => $HOME/public_html/path/file */
+ h = strchr(req->path+2,'/');
+ if (NULL == h) {
+ mkerror(req,404,1);
+ return;
+ }
+ *h = 0;
+ pw = getpwnam(req->path+2);
+ *h = '/';
+ if (NULL == pw) {
+ mkerror(req,404,1);
+ return;
+ }
+ len = snprintf(filename, sizeof(filename)-1,
+ "%s/%s/%s", pw->pw_dir, userdir, h+1);
+ } else {
+ len = snprintf(filename, sizeof(filename)-1,
+ "%s%s%s%s",
+ do_chroot ? "" : doc_root,
+ virtualhosts ? "/" : "",
+ virtualhosts ? req->hostname : "",
+ req->path);
+ }
+
+ h = filename +len -1;
+ if (*h == '/') {
+ /* looks like the client asks for a directory */
+ if (indexhtml) {
+ /* check for index file */
+ strncpy(h+1, indexhtml, sizeof(filename) -len -1);
+ if (-1 != (req->bfd = open(filename,O_RDONLY))) {
+ /* ok, we have one */
+ close_on_exec(req->bfd);
+ goto regular_file;
+ } else {
+ if (errno == ENOENT) {
+ /* no such file or directory => listing */
+ h[1] = '\0';
+ } else {
+ mkerror(req,403,1);
+ return;
+ }
+ }
+ }
+
+ if (no_listing) {
+ mkerror(req,403,1);
+ return;
+ };
+
+ if (-1 == stat(filename,&(req->bst))) {
+ if (errno == EACCES) {
+ mkerror(req,403,1);
+ } else {
+ mkerror(req,404,1);
+ }
+ return;
+ }
+ req->mime = "text/html";
+ req->dir = get_dir(req,filename);
+ t = req->dir->add;
+ if (NULL == req->body) {
+ /* We arrive here if opendir failed, probably due to -EPERM
+ * It does exist (see the stat() call above) */
+ mkerror(req,403,1);
+ return;
+ } else if (req->if_modified > 0 && req->if_modified == t) {
+ /* 304 not modified */
+ mkheader(req,304,t);
+ req->head_only = 1;
+ } else {
+ /* 200 OK */
+ mkheader(req,200,t);
+ }
+ return;
+ }
+
+ /* it is /probably/ a regular file */
+ if (-1 == (req->bfd = open(filename,O_RDONLY))) {
+ if (errno == EACCES) {
+ mkerror(req,403,1);
+ } else {
+ mkerror(req,404,1);
+ }
+ return;
+ }
+
+ regular_file:
+ fstat(req->bfd,&(req->bst));
+ if (req->range_hdr)
+ if (0 != (rc = parse_ranges(req))) {
+ mkerror(req,rc,1);
+ return;
+ }
+
+ if (!S_ISREG(req->bst.st_mode)) {
+ /* /not/ a regular file */
+ close(req->bfd);
+ req->bfd = -1;
+ if (S_ISDIR(req->bst.st_mode)) {
+ /* oops: a directory without trailing slash */
+ strcat(req->path,"/");
+ mkredirect(req);
+ } else {
+ /* anything else is'nt allowed here */
+ mkerror(req,403,1);
+ }
+ return;
+ }
+
+ /* it is /really/ a regular file */
+ req->mime = get_mime(filename);
+ if (req->if_range > 0 && req->if_range != req->bst.st_mtime)
+ /* mtime mismatch -> no ranges */
+ req->ranges = 0;
+ if (req->if_unmodified > 0 && req->if_unmodified != req->bst.st_mtime) {
+ /* 412 precondition failed */
+ mkerror(req,412,1);
+ } else if (req->if_modified > 0 && req->if_modified == req->bst.st_mtime) {
+ /* 304 not modified */
+ mkheader(req,304,req->bst.st_mtime);
+ req->head_only = 1;
+ } else if (req->ranges > 0) {
+ /* send byte range(s) */
+ mkheader(req,206,req->bst.st_mtime);
+ } else {
+ /* normal */
+ mkheader(req,200,req->bst.st_mtime);
+ }
+ return;
+}