shellinabox/libhttp/httpconnection.c
zodiac 90d9d492b7 Added better compatibility with different versions of compilers and libaries.
In particular, work around a problem with gcc complaining about NULL format
strings. And added additional system header files that might be required on
some platforms.

This should fix some of the problems reported when compiling on BSD-style
systems. But we are still using SysV style session management code. This
probably needs to be rewritten before ShellInABox can be run on BSD-style
system.

In particular, we rely on grantpt(), we use the utmpx API, and we access
/dev/urandom.


git-svn-id: https://shellinabox.googlecode.com/svn/trunk@55 0da03de8-d603-11dd-86c2-0f8696b7b6f9
2009-02-02 00:55:15 +00:00

1378 lines
45 KiB
C

// httpconnection.c -- Manage state machine for HTTP connections
// Copyright (C) 2008-2009 Markus Gutschke <markus@shellinabox.com>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
// In addition to these license terms, the author grants the following
// additional rights:
//
// If you modify this program, or any covered work, by linking or
// combining it with the OpenSSL project's OpenSSL library (or a
// modified version of that library), containing parts covered by the
// terms of the OpenSSL or SSLeay licenses, the author
// grants you additional permission to convey the resulting work.
// Corresponding Source for a non-source form of such a combination
// shall include the source code for the parts of OpenSSL used as well
// as that of the covered work.
//
// You may at your option choose to remove this additional permission from
// the work, or from any part of it.
//
// It is possible to build this program in a way that it loads OpenSSL
// libraries at run-time. If doing so, the following notices are required
// by the OpenSSL and SSLeay licenses:
//
// This product includes software developed by the OpenSSL Project
// for use in the OpenSSL Toolkit. (http://www.openssl.org/)
//
// This product includes cryptographic software written by Eric Young
// (eay@cryptsoft.com)
//
//
// The most up-to-date version of this program is always available from
// http://shellinabox.com
#include <errno.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/poll.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include "libhttp/httpconnection.h"
#include "logging/logging.h"
#define MAX_HEADER_LENGTH (64<<10)
#define CONNECTION_TIMEOUT (10*60)
static const char *NO_MSG;
static int httpPromoteToSSL(struct HttpConnection *http, const char *buf,
int len) {
if (http->ssl->enabled && !http->sslHndl) {
debug("Switching to SSL (replaying %d+%d bytes)",
http->partialLength, len);
if (http->partial && len > 0) {
check(http->partial = realloc(http->partial,
http->partialLength + len));
memcpy(http->partial + http->partialLength, buf, len);
http->partialLength += len;
}
int rc = sslPromoteToSSL(
http->ssl, &http->sslHndl, http->fd,
http->partial ? http->partial : buf,
http->partial ? http->partialLength : len);
if (http->sslHndl) {
check(!rc);
SSL_set_app_data(http->sslHndl, http);
}
free(http->partial);
http->partialLength = 0;
return rc;
} else {
errno = EINVAL;
return -1;
}
}
static ssize_t httpRead(struct HttpConnection *http, char *buf, ssize_t len) {
sslBlockSigPipe();
int rc;
if (http->sslHndl) {
dcheck(!ERR_peek_error());
rc = SSL_read(http->sslHndl, buf, len);
switch (rc) {
case 0:
case -1:
switch (http->lastError = SSL_get_error(http->sslHndl, rc)) {
case SSL_ERROR_WANT_READ:
case SSL_ERROR_WANT_WRITE:
errno = EAGAIN;
rc = -1;
break;
default:
errno = EINVAL;
break;
}
ERR_clear_error();
break;
default:
break;
}
dcheck(!ERR_peek_error());
} else {
rc = NOINTR(read(http->fd, buf, len));
}
sslUnblockSigPipe();
if (rc > 0) {
serverSetTimeout(httpGetServerConnection(http), CONNECTION_TIMEOUT);
}
return rc;
}
static ssize_t httpWrite(struct HttpConnection *http, const char *buf,
ssize_t len) {
sslBlockSigPipe();
int rc;
if (http->sslHndl) {
dcheck(!ERR_peek_error());
rc = SSL_write(http->sslHndl, buf, len);
switch (rc) {
case 0:
case -1:
switch (http->lastError = SSL_get_error(http->sslHndl, rc)) {
case SSL_ERROR_WANT_READ:
case SSL_ERROR_WANT_WRITE:
errno = EAGAIN;
rc = -1;
break;
default:
errno = EINVAL;
break;
}
ERR_clear_error();
break;
default:
break;
}
dcheck(!ERR_peek_error());
} else {
rc = NOINTR(write(http->fd, buf, len));
}
sslUnblockSigPipe();
return rc;
}
static int httpShutdown(struct HttpConnection *http, int how) {
if (http->sslHndl) {
int rc = 0;
if (how != SHUT_RD) {
dcheck(!ERR_peek_error());
for (int i = 0; i < 10; i++) {
sslBlockSigPipe();
rc = SSL_shutdown(http->sslHndl);
int sPipe = sslUnblockSigPipe();
if (rc > 0) {
rc = 0;
break;
} else {
rc = -1;
// Retry a few times in order to prefer a clean bidirectional
// shutdown. But don't bother if the other side already closed
// the connection.
if (sPipe) {
break;
}
}
}
sslFreeHndl(&http->sslHndl);
}
return rc;
}
return shutdown(http->fd, how);
}
static void httpCloseRead(struct HttpConnection *http) {
if (!http->closed) {
httpShutdown(http, SHUT_RD);
http->closed = 1;
}
}
static int httpFinishCommand(struct HttpConnection *http) {
int rc = HTTP_DONE;
if (http->callback && !http->done) {
rc = http->callback(http, http->arg, NULL, 0);
check(rc != HTTP_SUSPEND);
check(rc != HTTP_PARTIAL_REPLY);
http->callback = NULL;
http->arg = NULL;
if (rc == HTTP_ERROR) {
httpCloseRead(http);
}
}
if (logIsInfo()) {
check(http->method);
check(http->path);
check(http->version);
if (http->peerName) {
time_t t = currentTime;
struct tm *ltime;
check (ltime = localtime(&t));
char timeBuf[80];
char lengthBuf[40];
check(strftime(timeBuf, sizeof(timeBuf),
"[%d/%b/%Y:%H:%M:%S %z]", ltime));
if (http->totalWritten > 0) {
sprintf(lengthBuf, "%d", http->totalWritten);
} else {
strcpy(lengthBuf, "-");
}
info("%s - - %s \"%s %s %s\" %d %s",
http->peerName, timeBuf, http->method, http->path, http->version,
http->code, lengthBuf);
}
}
return rc;
}
static void httpDestroyHeaders(void *arg, char *key, char *value) {
free(key);
free(value);
}
static char *getPeerName(int fd, int *port, int numericHosts) {
struct sockaddr peerAddr;
socklen_t sockLen = sizeof(peerAddr);
if (getpeername(fd, &peerAddr, &sockLen)) {
if (port) {
*port = -1;
}
return NULL;
}
char host[256];
if (numericHosts ||
getnameinfo(&peerAddr, sockLen, host, sizeof(host), NULL, 0, NI_NOFQDN)){
check(inet_ntop(peerAddr.sa_family,
&((struct sockaddr_in *)&peerAddr)->sin_addr,
host, sizeof(host)));
}
if (port) {
*port = ntohs(((struct sockaddr_in *)&peerAddr)->sin_port);
}
return strdup(host);
}
static void httpSetState(struct HttpConnection *http, int state) {
if (state == http->state) {
return;
}
if (state == COMMAND) {
if (http->state != SNIFFING_SSL) {
int rc = httpFinishCommand(http);
check(rc != HTTP_SUSPEND);
check(rc != HTTP_PARTIAL_REPLY);
}
check(!http->private);
free(http->url);
free(http->method);
free(http->path);
free(http->matchedPath);
free(http->pathInfo);
free(http->query);
free(http->version);
http->done = 0;
http->url = NULL;
http->method = NULL;
http->path = NULL;
http->matchedPath = NULL;
http->pathInfo = NULL;
http->query = NULL;
http->version = NULL;
destroyHashMap(&http->header);
initHashMap(&http->header, httpDestroyHeaders, NULL);
http->headerLength = 0;
http->callback = NULL;
http->arg = NULL;
http->totalWritten = 0;
http->code = 200;
}
http->state = state;
}
struct HttpConnection *newHttpConnection(struct Server *server, int fd,
int port, struct SSLSupport *ssl,
int numericHosts) {
struct HttpConnection *http;
check(http = malloc(sizeof(struct HttpConnection)));
initHttpConnection(http, server, fd, port, ssl, numericHosts);
return http;
}
void initHttpConnection(struct HttpConnection *http, struct Server *server,
int fd, int port, struct SSLSupport *ssl,
int numericHosts) {
http->server = server;
http->connection = NULL;
http->fd = fd;
http->port = port;
http->closed = 0;
http->isSuspended = 0;
http->isPartialReply = 0;
http->done = 0;
http->state = ssl ? SNIFFING_SSL : COMMAND;
http->peerName = getPeerName(fd, &http->peerPort, numericHosts);
http->url = NULL;
http->method = NULL;
http->path = NULL;
http->matchedPath = NULL;
http->pathInfo = NULL;
http->query = NULL;
http->version = NULL;
initHashMap(&http->header, httpDestroyHeaders, NULL);
http->headerLength = 0;
http->key = NULL;
http->partial = NULL;
http->partialLength = 0;
http->msg = NULL;
http->msgLength = 0;
http->msgOffset = 0;
http->totalWritten = 0;
http->expecting = 0;
http->callback = NULL;
http->arg = NULL;
http->private = NULL;
http->code = 200;
http->ssl = ssl;
http->sslHndl = NULL;
http->lastError = 0;
if (logIsInfo()) {
debug("Accepted connection from %s:%d",
http->peerName ? http->peerName : "???", http->peerPort);
}
}
void destroyHttpConnection(struct HttpConnection *http) {
if (http) {
if (http->isSuspended || http->isPartialReply) {
if (http->callback && !http->done) {
http->callback(http, http->arg, NULL, 0);
}
http->isSuspended = 0;
http->isPartialReply = 0;
}
httpSetState(http, COMMAND);
if (logIsInfo()) {
debug("Closing connection to %s:%d",
http->peerName ? http->peerName : "???", http->peerPort);
}
httpShutdown(http, http->closed ? SHUT_WR : SHUT_RDWR);
dcheck(!close(http->fd));
free(http->peerName);
free(http->url);
free(http->method);
free(http->path);
free(http->matchedPath);
free(http->pathInfo);
free(http->query);
free(http->version);
destroyHashMap(&http->header);
free(http->partial);
free(http->msg);
}
}
void deleteHttpConnection(struct HttpConnection *http) {
destroyHttpConnection(http);
free(http);
}
void httpTransfer(struct HttpConnection *http, char *msg, int len) {
check(msg);
check(len >= 0);
if (!http->totalWritten) {
// Perform some basic sanity checks. This does not necessarily catch all
// possible problems, though.
int l = len;
for (char *eol, *lastLine = NULL, *line = msg;
l > 0 && (eol = memchr(line, '\n', l)) != NULL; ) {
// All lines end in CR LF
check(eol[-1] == '\r');
if (!lastLine) {
// The first line looks like "HTTP/1.x STATUS\r\n"
check(eol - line > 11);
check(!memcmp(line, "HTTP/1.", 7));
check(line[7] >= '0' && line[7] <= '9' &&
(line[8] == ' ' || line[8] == '\t'));
int i = eol - line - 9;
for (char *ptr = line + 9; i-- > 0; ) {
char ch = *ptr++;
if (ch < '0' || ch > '9') {
check(ptr > line + 10);
check(ch == ' ' || ch == '\t');
break;
}
}
check(i > 1);
} else if (line + 1 == eol) {
// Don't send any data with HEAD requests
check(l == 2 || strcmp(http->method, "HEAD"));
break;
} else {
// Header lines either contain a colon, or they are continuation
// lines
if (*line != ' ' && *line != '\t') {
check(memchr(line, ':', eol - line));
}
}
lastLine = line;
l -= eol - line + 1;
line = eol + 1;
}
}
http->totalWritten += len;
if (!len) {
free(msg);
} else if (http->msg) {
check(http->msg = realloc(http->msg,
http->msgLength - http->msgOffset + len));
if (http->msgOffset) {
memmove(http->msg, http->msg + http->msgOffset,
http->msgLength - http->msgOffset);
http->msgLength -= http->msgOffset;
http->msgOffset = 0;
}
memcpy(http->msg + http->msgLength, msg, len);
http->msgLength += len;
free(msg);
} else {
check(!http->msgOffset);
http->msg = msg;
http->msgLength = len;
}
// The caller can suspend the connection, so that it can send an
// asynchronous reply. Once the reply has been sent, the connection
// gets reactivated. Normally, this means it would go back to listening
// for commands.
// Similarly, the caller can indicate that this is a partial message and
// return additional data in subsequent calls to the callback handler.
if (http->isSuspended || http->isPartialReply) {
if (http->msg && http->msgLength > 0) {
int wrote = httpWrite(http, http->msg, http->msgLength);
if (wrote < 0 && errno != EAGAIN) {
httpCloseRead(http);
free(http->msg);
http->msgLength = 0;
http->msg = NULL;
} else if (wrote > 0) {
if (wrote == http->msgLength) {
free(http->msg);
http->msgLength = 0;
http->msg = NULL;
} else {
memmove(http->msg, http->msg + wrote, http->msgLength - wrote);
http->msgLength -= wrote;
}
}
}
check(http->state == PAYLOAD || http->state == DISCARD_PAYLOAD);
if (!http->isPartialReply) {
if (http->expecting < 0) {
// If we do not know the length of the content, close the connection.
debug("Closing previously suspended connection");
httpCloseRead(http);
httpSetState(http, DISCARD_PAYLOAD);
} else if (http->expecting == 0) {
httpSetState(http, COMMAND);
http->isSuspended = 0;
struct ServerConnection *connection = httpGetServerConnection(http);
if (!serverGetTimeout(connection)) {
serverSetTimeout(connection, CONNECTION_TIMEOUT);
}
serverConnectionSetEvents(http->server, connection,
http->msgLength ? POLLIN|POLLOUT : POLLIN);
}
}
}
}
void httpTransferPartialReply(struct HttpConnection *http, char *msg, int len){
check(!http->isSuspended);
http->isPartialReply = 1;
if (http->state != PAYLOAD && http->state != DISCARD_PAYLOAD) {
check(http->state == HEADERS);
httpSetState(http, PAYLOAD);
}
httpTransfer(http, msg, len);
}
static int httpHandleCommand(struct HttpConnection *http,
const struct Trie *handlers) {
debug("Handling \"%s\" \"%s\"", http->method, http->path);
const char *contentLength = getFromHashMap(&http->header,
"content-length");
if (contentLength != NULL && *contentLength) {
char *endptr;
http->expecting = strtol(contentLength,
&endptr, 10);
if (*endptr) {
// Invalid length. Read until end of stream and then close
// connection.
http->expecting = -1;
}
} else {
// Unknown length. Read until end of stream and then close
// connection.
http->expecting = -1;
}
if (!strcmp(http->method, "OPTIONS")) {
char *response = stringPrintf(NULL,
"HTTP/1.1 200 OK\r\n"
"Content-Length: 0\r\n"
"Allow: GET, POST, OPTIONS\r\n"
"\r\n");
httpTransfer(http, response, strlen(response));
if (http->expecting < 0) {
http->expecting = 0;
}
return HTTP_READ_MORE;
} else if (!strcmp(http->method, "GET")) {
if (http->expecting < 0) {
http->expecting = 0;
}
} else if (!strcmp(http->method, "POST")) {
} else if (!strcmp(http->method, "HEAD")) {
if (http->expecting < 0) {
http->expecting = 0;
}
} else if (!strcmp(http->method, "PUT") ||
!strcmp(http->method, "DELETE") ||
!strcmp(http->method, "TRACE") ||
!strcmp(http->method, "CONNECT")) {
httpSendReply(http, 405, "Method Not Allowed", NO_MSG);
return HTTP_DONE;
} else {
httpSendReply(http, 501, "Method Not Implemented", NO_MSG);
return HTTP_DONE;
}
const char *host = getFromHashMap(&http->header,
"host");
if (host) {
for (char ch; (ch = *host) != '\000'; host++) {
if (ch == ':') {
*(char *)host = '\000';
break;
}
if (ch != '-' && ch != '.' &&
(ch < '0' ||(ch > '9' && ch < 'A') ||
(ch > 'Z' && ch < 'a')|| ch > 'z')) {
httpSendReply(http, 400, "Bad Request", NO_MSG);
return HTTP_DONE;
}
}
}
char *diff;
struct HttpHandler *h = (struct HttpHandler *)getFromTrie(handlers,
http->path, &diff);
if (h) {
check(diff);
while (diff > http->path && diff[-1] == '/') {
diff--;
}
if (!*diff || *diff == '/' || *diff == '?' || *diff == '#') {
check(!http->matchedPath);
check(!http->pathInfo);
check(!http->query);
check(http->matchedPath = malloc(diff - http->path + 1));
memcpy(http->matchedPath, http->path, diff - http->path);
http->matchedPath[diff - http->path] = '\000';
const char *query = strchr(diff, '?');
if (*diff && *diff != '?') {
const char *endOfInfo = query
? query : strrchr(diff, '\000');
check(http->pathInfo = malloc(endOfInfo - diff + 1));
memcpy(http->pathInfo, diff, endOfInfo - diff);
http->pathInfo[endOfInfo - diff] = '\000';
}
if (query) {
http->query = strdup(query + 1);
}
return h->handler(http, h->arg);
}
}
httpSendReply(http, 404, "File Not Found", NO_MSG);
return HTTP_DONE;
}
static int httpGetChar(struct HttpConnection *http, const char *buf,
int size, int *offset) {
if (*offset < 0) {
return (unsigned char)http->partial[http->partialLength + (*offset)++];
} else if (*offset < size) {
return (unsigned char)buf[(*offset)++];
} else {
return -1;
}
}
static int httpParseCommand(struct HttpConnection *http, int offset,
const char *buf, int bytes, int firstSpace,
int lastSpace, int lineLength) {
if (firstSpace < 1 || lastSpace < 0) {
bad_request:
if (!http->method) {
http->method = strdup("");
}
if (!http->path) {
http->path = strdup("");
}
if (!http->version) {
http->version = strdup("");
}
httpSendReply(http, 400, "Bad Request", NO_MSG);
httpSetState(http, COMMAND);
return 0;
}
check(!http->method);
check(http->method = malloc(firstSpace + 1));
int i = offset;
int j = 0;
for (; j < firstSpace; j++) {
int ch = httpGetChar(http, buf, bytes, &i);
if (ch >= 'a' && ch <= 'z') {
ch &= ~0x20;
}
http->method[j] = ch;
}
http->method[j] = '\000';
check(!http->path);
check(http->path = malloc(lastSpace - firstSpace));
j = 0;
while (i < offset + lastSpace) {
int ch = httpGetChar(http, buf, bytes, &i);
if ((ch != ' ' && ch != '\t') || j) {
http->path[j++] = ch;
}
}
http->path[j] = '\000';
if (*http->path != '/' &&
(strcmp(http->method, "OPTIONS") || strcmp(http->path, "*"))) {
goto bad_request;
}
check(!http->version);
check(http->version = malloc(lineLength - lastSpace + 1));
j = 0;
while (i < offset + lineLength) {
int ch = httpGetChar(http, buf, bytes, &i);
if (ch == '\r') {
break;
}
if (ch >= 'a' && ch <= 'z') {
ch &= ~0x20;
}
if ((ch != ' ' && ch != '\t') || j) {
http->version[j] = ch;
j++;
}
}
http->version[j] = '\000';
if (memcmp(http->version, "HTTP/", 5) ||
(http->version[5] < '1' || http->version[5] > '9')) {
goto bad_request;
}
httpSetState(http, HEADERS);
return 1;
}
static int httpParseHeaders(struct HttpConnection *http,
const struct Trie *handlers, int offset,
const char *buf, int bytes, int colon,
int lineLength) {
int i = offset;
int ch = httpGetChar(http, buf, bytes, &i);
if (ch == ' ' || ch == '\t') {
if (http->key) {
char **oldValue = getRefFromHashMap(&http->header, http->key);
check(oldValue);
int oldLength = strlen(*oldValue);
check(*oldValue = realloc(*oldValue,
oldLength + lineLength + 1));
int j = oldLength;
int end = oldLength + lineLength;
(*oldValue)[j++] = ' ';
for (; j < end; j++) {
ch = httpGetChar(http, buf, bytes, &i);
if (ch == ' ' || ch == '\t') {
end--;
j--;
continue;
} else if (ch == '\r' && j == end - 1) {
break;
}
(*oldValue)[j] = ch;
}
(*oldValue)[j] = '\000';
}
} else if ((ch == '\r' &&
httpGetChar(http, buf, bytes, &i) == '\n') ||
ch == '\n' || ch == -1) {
check(!http->expecting);
http->callback = NULL;
http->arg = NULL;
int rc = httpHandleCommand(http, handlers);
retry:;
struct ServerConnection *connection = httpGetServerConnection(http);
switch (rc) {
case HTTP_DONE:
case HTTP_ERROR:
if (http->expecting < 0 || rc == HTTP_ERROR) {
httpCloseRead(http);
}
http->done = 1;
http->isSuspended = 0;
http->isPartialReply = 0;
if (!serverGetTimeout(connection)) {
serverSetTimeout(connection, CONNECTION_TIMEOUT);
}
httpSetState(http, http->expecting ? DISCARD_PAYLOAD : COMMAND);
break;
case HTTP_READ_MORE:
http->isSuspended = 0;
http->isPartialReply = 0;
if (!serverGetTimeout(connection)) {
serverSetTimeout(connection, CONNECTION_TIMEOUT);
}
check(!http->done);
if (!http->expecting && http->callback) {
rc = http->callback(http, http->arg, "", 0);
if (rc != HTTP_READ_MORE) {
goto retry;
}
}
httpSetState(http, http->expecting ? PAYLOAD : COMMAND);
break;
case HTTP_SUSPEND:
http->isSuspended = 1;
http->isPartialReply = 0;
serverSetTimeout(connection, 0);
if (http->state != PAYLOAD && http->state != DISCARD_PAYLOAD) {
check(http->state == HEADERS);
httpSetState(http, PAYLOAD);
}
break;
case HTTP_PARTIAL_REPLY:
http->isSuspended = 0;
http->isPartialReply = 1;
if (http->state != PAYLOAD && http->state != DISCARD_PAYLOAD) {
check(http->state == HEADERS);
httpSetState(http, PAYLOAD);
}
break;
default:
check(0);
}
if (ch == -1) {
httpCloseRead(http);
}
} else {
if (colon <= 0) {
httpSendReply(http, 400, "Bad Request", NO_MSG);
return 0;
}
check(colon < lineLength);
check(http->key = malloc(colon + 1));
int i = offset;
for (int j = 0; j < colon; j++) {
ch = httpGetChar(http, buf, bytes, &i);
if (ch >= 'A' && ch <= 'Z') {
ch |= 0x20;
}
http->key[j] = ch;
}
http->key[colon] = '\000';
char *value;
check(value = malloc(lineLength - colon));
i++;
int j = 0;
for (int k = 0; k < lineLength - colon - 1; j++, k++) {
int ch = httpGetChar(http, buf, bytes, &i);
if ((ch == ' ' || ch == '\t') && j == 0) {
j--;
} else if (ch == '\r' && k == lineLength - colon - 2) {
break;
} else {
value[j] = ch;
}
}
value[j] = '\000';
if (getRefFromHashMap(&http->header, http->key)) {
debug("Dropping duplicate header \"%s\"", http->key);
free(http->key);
free(value);
http->key = NULL;
} else {
addToHashMap(&http->header, http->key, value);
}
}
return 1;
}
static int httpConsumePayload(struct HttpConnection *http, const char *buf,
int len) {
if (http->expecting >= 0) {
// If positive, we know the expected length of payload and
// can keep the connection open.
// If negative, allow unlimited payload, but close connection
// when done.
if (len > http->expecting) {
len = http->expecting;
}
http->expecting -= len;
}
if (http->callback) {
check(!http->done);
int rc = http->callback(http, http->arg, buf, len);
struct ServerConnection *connection = httpGetServerConnection(http);
switch (rc) {
case HTTP_DONE:
case HTTP_ERROR:
if (http->expecting < 0 || rc == HTTP_ERROR) {
httpCloseRead(http);
}
http->done = 1;
http->isSuspended = 0;
http->isPartialReply = 0;
if (!serverGetTimeout(connection)) {
serverSetTimeout(connection, CONNECTION_TIMEOUT);
}
httpSetState(http, http->expecting ? DISCARD_PAYLOAD : COMMAND);
break;
case HTTP_READ_MORE:
http->isSuspended = 0;
http->isPartialReply = 0;
if (!serverGetTimeout(connection)) {
serverSetTimeout(connection, CONNECTION_TIMEOUT);
}
if (!http->expecting) {
httpSetState(http, COMMAND);
}
break;
case HTTP_SUSPEND:
http->isSuspended = 1;
http->isPartialReply = 0;
serverSetTimeout(connection, 0);
if (http->state != PAYLOAD && http->state != DISCARD_PAYLOAD) {
check(http->state == HEADERS);
httpSetState(http, PAYLOAD);
}
break;
case HTTP_PARTIAL_REPLY:
http->isSuspended = 0;
http->isPartialReply = 1;
if (http->state != PAYLOAD && http->state != DISCARD_PAYLOAD) {
check(http->state == HEADERS);
httpSetState(http, PAYLOAD);
}
break;
default:
check(0);
}
} else {
// If we do not have a callback for handling the payload, and we also do
// not know how long the payload is (because there was not Content-Length),
// we now close the connection.
if (http->expecting < 0) {
http->expecting = 0;
httpCloseRead(http);
httpSetState(http, COMMAND);
}
}
return len;
}
static int httpParsePayload(struct HttpConnection *http, int offset,
const char *buf, int bytes) {
int consumed = 0;
if (offset < 0) {
check(-offset <= http->partialLength);
if (http->expecting) {
consumed = httpConsumePayload(http,
http->partial + http->partialLength + offset,
-offset);
if (consumed == http->partialLength) {
free(http->partial);
http->partial = NULL;
http->partialLength = 0;
} else {
memmove(http->partial, http->partial + consumed,
http->partialLength - consumed);
http->partialLength -= consumed;
}
offset += consumed;
}
}
if (http->expecting && bytes - offset > 0) {
check(offset >= 0);
consumed += httpConsumePayload(http, buf + offset,
bytes - offset);
}
return consumed;
}
int httpHandleConnection(struct ServerConnection *connection, void *http_,
short *events, short revents) {
struct HttpConnection *http = (struct HttpConnection *)http_;
struct Trie *handlers = serverGetHttpHandlers(http->server);
http->connection = connection;
int bytes;
do {
bytes = 0;
*events = 0;
char buf[4096];
int eof = http->closed;
if ((revents & POLLIN) && !http->closed) {
bytes = httpRead(http, buf, sizeof(buf));
if (bytes > 0) {
http->headerLength += bytes;
if (http->headerLength > MAX_HEADER_LENGTH) {
httpSendReply(http, 413, "Header too big", NO_MSG);
bytes = 0;
eof = 1;
}
} else {
if (bytes == 0 || errno != EAGAIN) {
httpCloseRead(http);
eof = 1;
} else {
if (http->sslHndl && http->lastError == SSL_ERROR_WANT_WRITE) {
*events |= POLLOUT;
}
}
bytes = 0;
}
}
if (bytes > 0 && http->state == SNIFFING_SSL) {
// Assume that all legitimate HTTP commands start with a sequence of
// letters followed by a space character. If we don't see this pattern,
// or if the method does not match one of the known methods, we try
// switching to SSL, instead.
int isSSL = 0;
char method[12] = { 0 };
for (int i = -http->partialLength, j = 0, ch;
(ch = httpGetChar(http, buf, bytes, &i)) != -1;
j++) {
if ((j > 0 && (ch == ' ' || ch == '\t')) ||
ch == '\r' || ch == '\n') {
isSSL = strcmp(method, "OPTIONS") &&
strcmp(method, "GET") &&
strcmp(method, "HEAD") &&
strcmp(method, "POST") &&
strcmp(method, "PUT") &&
strcmp(method, "DELETE") &&
strcmp(method, "TRACE") &&
strcmp(method, "CONNECT");
http->state = COMMAND;
break;
} else if (j >= sizeof(method)-1 ||
ch < 'A' || (ch > 'Z' && ch < 'a') || ch > 'z') {
isSSL = 1;
http->state = COMMAND;
break;
} else {
method[j] = ch & ~0x20;
}
}
if (isSSL) {
if (httpPromoteToSSL(http, buf, bytes) < 0) {
httpCloseRead(http);
bytes = 0;
eof = 1;
} else {
http->headerLength = 0;
*events |= POLLIN;
continue;
}
}
}
if (bytes > 0 || (eof && http->partial)) {
check(!!http->partial == !!http->partialLength);
int offset = -http->partialLength;
int eob = 0;
do {
int pushBack = 0;
int consumed = 0;
if (http->state == SNIFFING_SSL || http->state == COMMAND ||
http->state == HEADERS) {
check(!http->expecting);
int lineLength = 0;
int colon = -1;
int firstSpace = -1;
int lastSpace = -1;
int fullLine = 1;
for (int i = offset; ; lineLength++) {
int ch = httpGetChar(http, buf, bytes, &i);
if (ch == ':') {
if (colon < 0) {
colon = lineLength;
}
} else if (ch == ' ' || ch == '\t') {
if (firstSpace < 0) {
firstSpace = lineLength;
} else {
lastSpace = lineLength;
}
} else if (ch == '\n') {
break;
} else if (ch == -1) {
fullLine = 0;
eob = 1;
break;
}
}
if (fullLine || eof) {
consumed = lineLength + 1;
if (lineLength) {
if (http->state == SNIFFING_SSL || http->state == COMMAND) {
if (!httpParseCommand(http, offset, buf, bytes, firstSpace,
lastSpace, lineLength)) {
break;
}
} else {
check(http->state == HEADERS);
if (!httpParseHeaders(http, handlers, offset, buf, bytes,
colon, lineLength)) {
break;
}
}
}
} else {
pushBack = lineLength;
}
} else if (http->state == PAYLOAD ||
http->state == DISCARD_PAYLOAD) {
if (http->expecting) {
int len = bytes - offset;
if (http->expecting > 0 &&
bytes > http->expecting) {
len = http->expecting;
}
if (http->state == PAYLOAD) {
len = httpParsePayload(http, offset, buf,
len + offset);
}
consumed = len;
pushBack = bytes - offset - len;
}
}
if (pushBack) {
check(offset + pushBack == bytes);
if (offset >= 0) {
check(http->partial = realloc(http->partial, pushBack));
memcpy(http->partial, buf + offset, pushBack);
} else if (pushBack != http->partialLength) {
char *partial;
check(partial = malloc(pushBack));
for (int i = offset, j = 0; j < pushBack; j++) {
partial[j] = httpGetChar(http, buf, bytes, &i);
}
free(http->partial);
http->partial = partial;
}
http->partialLength = pushBack;
offset = -pushBack;
break;
} else {
offset += consumed;
eob |= offset >= bytes;
}
} while (!eob && !http->closed);
if (http->closed || offset >= 0) {
free(http->partial);
http->partial = NULL;
http->partialLength = 0;
} else if (-offset != http->partialLength) {
check(-offset < http->partialLength);
memmove(http->partial, http->partial + http->partialLength + offset,
-offset);
http->partialLength = -offset;
}
}
// If the peer closed the connection, clean up now.
if (eof) {
check(!http->partial);
switch (http->state) {
case SNIFFING_SSL:
case COMMAND:
break;
case HEADERS:
check(!http->expecting);
http->callback = NULL;
http->arg = NULL;
httpHandleCommand(http, handlers);
httpCloseRead(http);
httpSetState(http, COMMAND);
break;
case PAYLOAD:
case DISCARD_PAYLOAD:
http->expecting = 0;
httpCloseRead(http);
httpSetState(http, COMMAND);
break;
}
}
for (;;) {
// Try to write any pending outgoing data
if (http->msg && http->msgLength > 0) {
int wrote = httpWrite(http, http->msg,
http->msgLength);
if (wrote < 0 && errno != EAGAIN) {
httpCloseRead(http);
free(http->msg);
http->msgLength = 0;
http->msg = NULL;
break;
} else if (wrote > 0) {
if (wrote == http->msgLength) {
free(http->msg);
http->msgLength = 0;
http->msg = NULL;
} else {
memmove(http->msg, http->msg + wrote, http->msgLength - wrote);
http->msgLength -= wrote;
}
}
// SSL might require reading in order to write
else if (wrote < 0 && errno == EAGAIN && http->sslHndl) {
if (http->lastError == SSL_ERROR_WANT_READ && !http->closed) {
*events |= POLLIN;
}
}
}
// If the callback only provided partial data, refill the outgoing
// buffer whenever it runs low.
if (http->isPartialReply && (!http->msg || http->msgLength <= 0)) {
httpConsumePayload(http, "", 0);
} else {
break;
}
}
*events |=
(*events & ~(POLLIN|POLLOUT)) |
(!http->closed && ((http->state != PAYLOAD &&
http->state != DISCARD_PAYLOAD) ||
http->expecting) ? POLLIN : 0) |
(http->msg || http->isPartialReply ? POLLOUT : 0);
connection = httpGetServerConnection(http);
int timedOut = serverGetTimeout(connection) < 0;
if (timedOut) {
free(http->partial);
http->partial = NULL;
http->partialLength = 0;
free(http->msg);
http->msg = NULL;
http->msgLength = 0;
}
if ((!(*events || http->isSuspended) || timedOut) && http->sslHndl) {
*events = 0;
serverSetTimeout(connection, 1);
int wasAlreadyClosed = http->closed;
httpCloseRead(http);
dcheck(!ERR_peek_error());
sslBlockSigPipe();
int rc = SSL_shutdown(http->sslHndl);
switch (rc) {
case 1:
sslFreeHndl(&http->sslHndl);
break;
case 0:
if (!wasAlreadyClosed) {
*events |= POLLIN;
}
break;
case -1:
switch (SSL_get_error(http->sslHndl, rc)) {
case SSL_ERROR_WANT_READ:
if (!wasAlreadyClosed) {
*events |= POLLIN;
}
break;
case SSL_ERROR_WANT_WRITE:
*events |= POLLOUT;
break;
}
break;
}
ERR_clear_error();
dcheck(!ERR_peek_error());
if (sslUnblockSigPipe()) {
*events = 0;
sslFreeHndl(&http->sslHndl);
}
} else if (!http->sslHndl && timedOut) {
*events = 0;
serverSetTimeout(connection, 0);
httpCloseRead(http);
}
revents = POLLIN | POLLOUT;
} while (bytes > 0 && *events & POLLIN && !http->closed);
return (*events & (POLLIN|POLLOUT)) ||
(!http->closed && http->isSuspended);
}
void httpSetCallback(struct HttpConnection *http,
int (*callback)(struct HttpConnection *, void *,
const char *, int), void *arg) {
http->callback = callback;
http->arg = arg;
}
void *httpGetPrivate(struct HttpConnection *http) {
return http->private;
}
void *httpSetPrivate(struct HttpConnection *http, void *private) {
void *old = http->private;
http->private = private;
return old;
}
void httpSendReply(struct HttpConnection *http, int code,
const char *msg, const char *fmt, ...) {
http->code = code;
char *body;
char *title = code != 200 ? stringPrintf(NULL, "%d %s", code, msg) : NULL;
char *details = NULL;
if (fmt != NULL) {
va_list ap;
va_start(ap, fmt);
details = vStringPrintf(NULL, fmt, ap);
va_end(ap);
}
body = stringPrintf(NULL,
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
"<!DOCTYPE html PUBLIC "
"\"-//W3C//DTD XHTML 1.0 Transitional//EN\" "
"\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n"
"<html xmlns=\"http://www.w3.org/1999/xhtml\" "
"xmlns:v=\"urn:schemas-microsoft-com:vml\" "
"xml:lang=\"en\" lang=\"en\">\n"
"<head>\n"
"<title>%s</title>\n"
"</head>\n"
"<body>\n"
"%s\n"
"</body>\n"
"</html>\n",
title ? title : msg, fmt ? details : msg);
free(details);
free(title);
char *response = NULL;
if (code) {
response = stringPrintf(NULL,
"HTTP/1.1 %d %s\r\n"
"%s"
"Content-Type: text/html; charset=utf-8\r\n"
"Content-Length: %ld\r\n"
"\r\n",
code, msg,
code != 200 ? "Connection: close\r\n" : "",
(long)strlen(body));
}
int isHead = !strcmp(http->method, "HEAD");
if (!isHead) {
response = stringPrintf(response, "%s", body);
}
free(body);
httpTransfer(http, response, strlen(response));
if (code != 200 || isHead) {
httpCloseRead(http);
}
}
void httpExitLoop(struct HttpConnection *http, int exitAll) {
serverExitLoop(http->server, exitAll);
}
struct Server *httpGetServer(const struct HttpConnection *http) {
return http->server;
}
struct ServerConnection *httpGetServerConnection(const struct HttpConnection *
http) {
struct HttpConnection *httpW = (struct HttpConnection *)http;
httpW->connection = serverGetConnection(http->server, http->connection,
http->fd);
return http->connection;
}
int httpGetFd(const HttpConnection *http) {
return http->fd;
}
const char *httpGetPeerName(const struct HttpConnection *http) {
return http->peerName;
}
const char *httpGetMethod(const struct HttpConnection *http) {
return http->method;
}
const char *httpGetProtocol(const struct HttpConnection *http) {
return http->sslHndl ? "https" : "http";
}
const char *httpGetHost(const struct HttpConnection *http) {
const char *host = getFromHashMap(&http->header, "host");
if (!host || !*host) {
host = "localhost";
}
return host;
}
int httpGetPort(const struct HttpConnection *http) {
return http->port;
}
const char *httpGetPath(const struct HttpConnection *http) {
return http->matchedPath;
}
const char *httpGetPathInfo(const struct HttpConnection *http) {
return http->pathInfo ? http->pathInfo : "";
}
const char *httpGetQuery(const struct HttpConnection *http) {
return http->query ? http->query : "";
}
const char *httpGetURL(const struct HttpConnection *http) {
if (!http->url) {
const char *host = httpGetHost(http);
check(*(char **)&http->url = malloc(8 + strlen(host) + 25 +
strlen(http->path) + 1));
strcpy(http->url, http->sslHndl ? "https://" : "http://");
strcat(http->url, host);
if (http->port != (http->sslHndl ? 443 : 80)) {
sprintf(strrchr(http->url, '\000'), ":%d", http->port);
}
strcat(http->url, http->path);
}
return http->url;
}
const char *httpGetVersion(const struct HttpConnection *http) {
return http->version;
}
const struct HashMap *httpGetHeaders(const struct HttpConnection *http) {
return &http->header;
}