aboutsummaryrefslogtreecommitdiffstats
path: root/response.c
diff options
context:
space:
mode:
Diffstat (limited to 'response.c')
-rw-r--r--response.c558
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 (;;) */
+}