diff options
Diffstat (limited to 'response.c')
-rw-r--r-- | response.c | 558 |
1 files changed, 558 insertions, 0 deletions
diff --git a/response.c b/response.c new file mode 100644 index 0000000..8f5523d --- /dev/null +++ b/response.c @@ -0,0 +1,558 @@ +#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 <limits.h> +#include <inttypes.h> + +#include <sys/time.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netinet/tcp.h> + +#include "httpd.h" + +/* ---------------------------------------------------------------------- */ +/* os-specific sendfile() wrapper */ + +/* + * int xsendfile(out,in,offset,bytes) + * + * out - outgoing filedescriptor (i.e. the socket) + * in - incoming filedescriptor (i.e. the file to send out) + * offset - file offset (where to start) + * bytes - number of bytes to send + * + * return value + * on error: -1 and errno set. + * on success: the number of successfully written bytes (which might + * be smaller than bytes, we are doing nonblocking I/O). + * extra hint: much like write(2) works. + * + */ + +static inline size_t off_to_size(off_t off_bytes) +{ + if (off_bytes > SSIZE_MAX) + return SSIZE_MAX; + return off_bytes; +} + +#if defined(__linux__) && !defined(NO_SENDFILE) + +# include <sys/sendfile.h> +static ssize_t xsendfile(int out, int in, off_t offset, off_t off_bytes) +{ + size_t bytes = off_to_size(off_bytes); + return sendfile(out, in, &offset, bytes); +} + +#elif defined(__FreeBSD__) && !defined(NO_SENDFILE) + +static ssize_t xsendfile(int out, int in, off_t offset, off_t off_bytes) +{ + size_t bytes = off_to_size(off_bytes); + off_t nbytes = 0; + + if (-1 == sendfile(in, out, offset, bytes, NULL, &nbytes, 0)) { + /* Why the heck FreeBSD returns an /error/ if it has done a partial + write? With non-blocking I/O this absolutely normal behavoir and + no error at all. Stupid. */ + if (errno == EAGAIN && nbytes > 0) + return nbytes; + return -1; + } + return nbytes; +} +#else + +# warning using slow sendfile() emulation. + +/* Poor man's sendfile() implementation. Performance sucks, but it works. */ +# define BUFSIZE 16384 + +static ssize_t xsendfile(int out, int in, off_t offset, off_t off_bytes) +{ + char buf[BUFSIZE]; + ssize_t nread; + ssize_t nsent, nsent_total; + size_t bytes = off_to_size(off_bytes); + + if (lseek(in, offset, SEEK_SET) == -1) { + if (debug) + perror("lseek"); + return -1; + } + + nsent = nsent_total = 0; + for (;bytes > 0;) { + /* read a block */ + nread = read(in, buf, (bytes < BUFSIZE) ? bytes : BUFSIZE); + if (-1 == nread) { + if (debug) + perror("read"); + return nsent_total ? nsent_total : -1; + } + if (0 == nread) + break; + + /* write it out */ + nsent = write(out, buf, nread); + if (-1 == nsent) + return nsent_total ? nsent_total : -1; + + nsent_total += nsent; + if (nsent < nread) + /* that was a partial write only. Queue full. Bailout here, + the next write would return EAGAIN anyway... */ + break; + + bytes -= nread; + } + return nsent_total; +} + +#endif + +/* ---------------------------------------------------------------------- */ + +#ifdef USE_SSL + +static inline int wrap_xsendfile(struct REQUEST *req, off_t off, off_t bytes) +{ + if (with_ssl) + return ssl_blk_write(req, off, off_to_size(bytes)); + else + return xsendfile(req->fd, req->bfd, off, bytes); +} + +static inline int wrap_write(struct REQUEST *req, void *buf, off_t bytes) +{ + if (with_ssl) + return ssl_write(req, buf, off_to_size(bytes)); + else + return write(req->fd, buf, off_to_size(bytes)); +} + +#else +# define wrap_xsendfile(req,off,bytes) xsendfile(req->fd,req->bfd,off,bytes) +# define wrap_write(req,buf,bytes) write(req->fd,buf,bytes); +#endif + +/* ---------------------------------------------------------------------- */ + +static struct HTTP_STATUS { + int status; + char *head; + char *body; +} http[] = { + { 200, "200 OK", NULL }, + { 206, "206 Partial Content", NULL }, + { 304, "304 Not Modified", NULL }, + { 400, "400 Bad Request", "*PLONK*\n" }, + { 401, "401 Authentication required", "Authentication required\n" }, + { 403, "403 Forbidden", "Access denied\n" }, + { 404, "404 Not Found", "File or directory not found\n" }, + { 408, "408 Request Timeout", "Request Timeout\n" }, + { 412, "412 Precondition failed.", "Precondition failed\n" }, + { 500, "500 Internal Server Error", "Sorry folks\n" }, + { 501, "501 Not Implemented", "Sorry folks\n" }, + { 0, NULL, NULL } +}; + +/* ---------------------------------------------------------------------- */ + +#define RESPONSE_START \ + "HTTP/1.1 %s\r\n" \ + "Server: %s\r\n" \ + "Connection: %s\r\n" \ + "Accept-Ranges: bytes\r\n" +#define RFCTIME \ + "%a, %d %b %Y %H:%M:%S GMT" +#define BOUNDARY \ + "XXX_CUT_HERE_%ld_XXX" + +void +mkerror(struct REQUEST *req, int status, int ka) +{ + int i; + for (i = 0; http[i].status != 0; i++) + if (http[i].status == status) + break; + req->status = status; + req->body = http[i].body; + req->lbody = strlen(req->body); + if (!ka) + req->keep_alive = 0; + req->lres = sprintf(req->hres, + RESPONSE_START + "Content-Type: text/plain\r\n" + "Content-Length: %" PRId64 "\r\n", + http[i].head,server_name, + req->keep_alive ? "Keep-Alive" : "Close", + (int64_t)req->lbody); + if (401 == status) + req->lres += sprintf(req->hres+req->lres, + "WWW-Authenticate: Basic realm=\"webfs\"\r\n"); + req->lres += strftime(req->hres+req->lres,80, + "Date: " RFCTIME "\r\n\r\n", + gmtime(&now)); + req->state = STATE_WRITE_HEADER; + if (debug) + fprintf(stderr,"%03d: error: %d, connection=%s\n", + req->fd, status, req->keep_alive ? "Keep-Alive" : "Close"); +} + +void +mkredirect(struct REQUEST *req) +{ + req->status = 302; + req->body = req->path; + req->lbody = strlen(req->body); + req->lres = sprintf(req->hres, + RESPONSE_START + "Location: http://%s:%d%s\r\n" + "Content-Type: text/plain\r\n" + "Content-Length: %" PRId64 "\r\n", + "302 Redirect",server_name, + req->keep_alive ? "Keep-Alive" : "Close", + req->hostname,tcp_port,quote(req->path,9999), + (int64_t)req->lbody); + req->lres += strftime(req->hres+req->lres,80, + "Date: " RFCTIME "\r\n\r\n", + gmtime(&now)); + req->state = STATE_WRITE_HEADER; + if (debug) + fprintf(stderr,"%03d: 302 redirect: %s, connection=%s\n", + req->fd, req->path, req->keep_alive ? "Keep-Alive" : "Close"); +} + +static int +mkmulti(struct REQUEST *req, int i) +{ + req->r_hlen[i] = sprintf(req->r_head+i*BR_HEADER, + "\r\n--" BOUNDARY "\r\n" + "Content-type: %s\r\n" + "Content-range: bytes %" PRId64 "-%" PRId64 "/%" PRId64 "\r\n" + "\r\n", + now, req->mime, + (int64_t)req->r_start[i], + (int64_t)req->r_end[i]-1, + (int64_t)req->bst.st_size); + if (debug > 1) + fprintf(stderr,"%03d: send range: %" PRId64 "-%" PRId64 "/%" PRId64 " (%" PRId64 " byte)\n", + req->fd, + (int64_t)req->r_start[i], + (int64_t)req->r_end[i], + (int64_t)req->bst.st_size, + (int64_t)(req->r_end[i]-req->r_start[i])); + return req->r_hlen[i]; +} + +void +mkheader(struct REQUEST *req, int status, time_t mtime) +{ + int i; + off_t len; + time_t expires; + + for (i = 0; http[i].status != 0; i++) + if (http[i].status == status) + break; + req->status = status; + req->lres = sprintf(req->hres, + RESPONSE_START, + http[i].head,server_name, + req->keep_alive ? "Keep-Alive" : "Close"); + if (req->ranges == 0) { + req->lres += sprintf(req->hres+req->lres, + "Content-Type: %s\r\n" + "Content-Length: %" PRId64 "\r\n", + req->mime, + (int64_t)(req->body ? req->lbody : req->bst.st_size)); + } else if (req->ranges == 1) { + req->lres += sprintf(req->hres+req->lres, + "Content-Type: %s\r\n" + "Content-Range: bytes %" PRId64 "-%" PRId64 "/%" PRId64 "\r\n" + "Content-Length: %" PRId64 "\r\n", + req->mime, + (int64_t)req->r_start[0], + (int64_t)req->r_end[0]-1, + (int64_t)req->bst.st_size, + (int64_t)(req->r_end[0]-req->r_start[0])); + } else { + for (i = 0, len = 0; i < req->ranges; i++) { + len += mkmulti(req,i); + len += req->r_end[i]-req->r_start[i]; + } + req->r_hlen[i] = sprintf(req->r_head+i*BR_HEADER, + "\r\n--" BOUNDARY "--\r\n", + now); + len += req->r_hlen[i]; + req->lres += sprintf(req->hres+req->lres, + "Content-Type: multipart/byteranges;" + " boundary=" BOUNDARY "\r\n" + "Content-Length: %" PRId64 "\r\n", + now, (int64_t)len); + } + if (mtime != -1) { + req->lres += strftime(req->hres+req->lres,80, + "Last-Modified: " RFCTIME "\r\n", + gmtime(&mtime)); + if (-1 != lifespan) { + expires = mtime + lifespan; + req->lres += strftime(req->hres+req->lres,80, + "Expires: " RFCTIME "\r\n", + gmtime(&expires)); + } + } + req->lres += strftime(req->hres+req->lres,80, + "Date: " RFCTIME "\r\n\r\n", + gmtime(&now)); + req->state = STATE_WRITE_HEADER; + if (debug) + fprintf(stderr,"%03d: %d, connection=%s\n", + req->fd, status, req->keep_alive ? "Keep-Alive" : "Close"); +} + +void +mkcgi(struct REQUEST *req, char *status, struct strlist *header) +{ + req->status = atoi(status); + req->keep_alive = 0; + req->lres = sprintf(req->hres, + RESPONSE_START, + status, server_name,"Close"); + for (; NULL != header; header = header->next) + req->lres += sprintf(req->hres+req->lres,"%s\r\n",header->line); + req->lres += strftime(req->hres+req->lres,80, + "Date: " RFCTIME "\r\n\r\n", + gmtime(&now)); + req->state = STATE_WRITE_HEADER; +} + +/* ---------------------------------------------------------------------- */ + +void write_request(struct REQUEST *req) +{ + int rc; + + for (;;) { + switch (req->state) { + case STATE_WRITE_HEADER: +#ifdef TCP_CORK + if (0 == req->tcp_cork && !req->head_only) { + req->tcp_cork = 1; + if (debug) + fprintf(stderr,"%03d: tcp_cork=%d\n",req->fd,req->tcp_cork); + setsockopt(req->fd,SOL_TCP,TCP_CORK,&req->tcp_cork,sizeof(int)); + } +#endif + rc = wrap_write(req,req->hres + req->written, + req->lres - req->written); + switch (rc) { + case -1: + if (errno == EAGAIN) + return; + if (errno == EINTR) + continue; + xperror(LOG_INFO,"write",req->peerhost); + /* fall through */ + case 0: + req->state = STATE_CLOSE; + return; + default: + req->written += rc; + req->bc += rc; + if (req->written != req->lres) + return; + } + req->written = 0; + if (req->head_only) { + req->state = STATE_FINISHED; + return; + } else if (req->cgipid) { + req->state = (req->cgipos != req->cgilen) ? + STATE_CGI_BODY_OUT : STATE_CGI_BODY_IN; + } else if (req->body) { + req->state = STATE_WRITE_BODY; + } else if (req->ranges == 1) { + req->state = STATE_WRITE_RANGES; + req->rh = -1; + req->rb = 0; + req->written = req->r_start[0]; + } else if (req->ranges > 1) { + req->state = STATE_WRITE_RANGES; + req->rh = 0; + req->rb = -1; + } else { + req->state = STATE_WRITE_FILE; + } + break; + case STATE_WRITE_BODY: + rc = wrap_write(req,req->body + req->written, + req->lbody - req->written); + switch (rc) { + case -1: + if (errno == EAGAIN) + return; + if (errno == EINTR) + continue; + xperror(LOG_INFO,"write",req->peerhost); + /* fall through */ + case 0: + req->state = STATE_CLOSE; + return; + default: + req->written += rc; + req->bc += rc; + if (req->written != req->lbody) + return; + } + req->state = STATE_FINISHED; + return; + case STATE_WRITE_FILE: + rc = wrap_xsendfile(req, req->written, + req->bst.st_size - req->written); + switch (rc) { + case -1: + if (errno == EAGAIN) + return; + if (errno == EINTR) + continue; + xperror(LOG_INFO,"sendfile",req->peerhost); + /* fall through */ + case 0: + req->state = STATE_CLOSE; + return; + default: + if (debug > 1) + fprintf(stderr,"%03d: %" PRId64 "/%" PRId64 " (%d%%)\r",req->fd, + (int64_t)req->written,(int64_t)req->bst.st_size, + (int)(req->written*100/req->bst.st_size)); + req->written += rc; + req->bc += rc; + if (req->written != req->bst.st_size) + return; + } + req->state = STATE_FINISHED; + return; + case STATE_WRITE_RANGES: + if (-1 != req->rh) { + /* write header */ + rc = wrap_write(req, + req->r_head + req->rh*BR_HEADER + req->written, + req->r_hlen[req->rh] - req->written); + switch (rc) { + case -1: + if (errno == EAGAIN) + return; + if (errno == EINTR) + continue; + xperror(LOG_INFO,"write",req->peerhost); + /* fall through */ + case 0: + req->state = STATE_CLOSE; + return; + default: + req->written += rc; + req->bc += rc; + if (req->written != req->r_hlen[req->rh]) + return; + } + if (req->rh == req->ranges) { + /* done -- no more ranges */ + req->state = STATE_FINISHED; + return; + } + /* prepare for body writeout */ + req->rb = req->rh; + req->rh = -1; + req->written = req->r_start[req->rb]; + } + if (-1 != req->rb) { + /* write body */ + rc = wrap_xsendfile(req, req->written, + req->r_end[req->rb] - req->written); + switch (rc) { + case -1: + if (errno == EAGAIN) + return; + if (errno == EINTR) + continue; + xperror(LOG_INFO,"sendfile",req->peerhost); + /* fall through */ + case 0: + req->state = STATE_CLOSE; + return; + default: + req->written += rc; + req->bc += rc; + if (req->written != req->r_end[req->rb]) + return; + } + /* prepare for next subheader writeout */ + req->rh = req->rb+1; + req->rb = -1; + req->written = 0; + if (req->ranges == 1) { + /* single range only */ + req->state = STATE_FINISHED; + return; + } + } + break; + case STATE_CGI_BODY_IN: + rc = read(req->cgipipe, req->cgibuf, MAX_HEADER); + switch (rc) { + case -1: + if (errno == EAGAIN) + return; + if (errno == EINTR) + continue; + xperror(LOG_INFO,"cgi read",req->peerhost); + /* fall through */ + case 0: + req->state = STATE_FINISHED; + return; + default: + if (debug) + fprintf(stderr,"%03d: cgi: in %d\n",req->fd,rc); + req->cgipos = 0; + req->cgilen = rc; + break; + } + req->state = STATE_CGI_BODY_OUT; + break; + case STATE_CGI_BODY_OUT: + rc = wrap_write(req,req->cgibuf + req->cgipos, + req->cgilen - req->cgipos); + switch (rc) { + case -1: + if (errno == EAGAIN) + return; + if (errno == EINTR) + continue; + xperror(LOG_INFO,"write",req->peerhost); + /* fall through */ + case 0: + req->state = STATE_CLOSE; + return; + default: + if (debug) + fprintf(stderr,"%03d: cgi: out %d\n",req->fd,rc); + req->cgipos += rc; + req->bc += rc; + if (req->cgipos != req->cgilen) + return; + } + req->state = STATE_CGI_BODY_IN; + break; + } /* switch(state) */ + } /* for (;;) */ +} |