diff options
author | kraxel <kraxel> | 2004-06-08 12:51:57 +0000 |
---|---|---|
committer | kraxel <kraxel> | 2004-06-08 12:51:57 +0000 |
commit | 996cd0bab7bfff049402fe3eb3ca5de88d69ab41 (patch) | |
tree | 79d9e47fe25645bec085a0fecd1d7dda50a95918 /request.c | |
download | webfs-996cd0bab7bfff049402fe3eb3ca5de88d69ab41.tar.gz |
Initial revision
Diffstat (limited to 'request.c')
-rw-r--r-- | request.c | 631 |
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; +} |