#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 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 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: " RFC1123 "\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: " RFC1123 "\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) { 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 (req->mtime[0] != '\0') { req->lres += sprintf(req->hres+req->lres, "Last-Modified: %s\r\n", req->mtime); if (-1 != lifespan) { expires = req->bst.st_mtime + lifespan; req->lres += strftime(req->hres+req->lres,80, "Expires: " RFC1123 "\r\n", gmtime(&expires)); } } req->lres += strftime(req->hres+req->lres,80, "Date: " RFC1123 "\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: " RFC1123 "\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 (;;) */ }