You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
557 lines
14 KiB
C
557 lines
14 KiB
C
#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 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 (;;) */
|
|
}
|