2008-12-30 00:57:07 +01:00
|
|
|
// httpconnection.c -- Manage state machine for HTTP connections
|
2009-01-02 07:09:13 +01:00
|
|
|
// Copyright (C) 2008-2009 Markus Gutschke <markus@shellinabox.com>
|
2008-12-30 00:57:07 +01:00
|
|
|
//
|
|
|
|
// 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
|
|
|
|
|
2009-02-12 00:25:15 +01:00
|
|
|
#include "config.h"
|
|
|
|
|
2008-12-30 00:57:07 +01:00
|
|
|
#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>
|
2009-02-02 01:55:15 +01:00
|
|
|
#include <sys/socket.h>
|
|
|
|
#include <sys/types.h>
|
2008-12-30 00:57:07 +01:00
|
|
|
#include <unistd.h>
|
|
|
|
|
|
|
|
#include "libhttp/httpconnection.h"
|
|
|
|
#include "logging/logging.h"
|
|
|
|
|
|
|
|
#define MAX_HEADER_LENGTH (64<<10)
|
|
|
|
#define CONNECTION_TIMEOUT (10*60)
|
|
|
|
|
|
|
|
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")) {
|
2009-02-02 01:55:15 +01:00
|
|
|
httpSendReply(http, 405, "Method Not Allowed", NO_MSG);
|
2008-12-30 00:57:07 +01:00
|
|
|
return HTTP_DONE;
|
|
|
|
} else {
|
2009-02-02 01:55:15 +01:00
|
|
|
httpSendReply(http, 501, "Method Not Implemented", NO_MSG);
|
2008-12-30 00:57:07 +01:00
|
|
|
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')) {
|
2009-02-02 01:55:15 +01:00
|
|
|
httpSendReply(http, 400, "Bad Request", NO_MSG);
|
2008-12-30 00:57:07 +01:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
2009-02-02 01:55:15 +01:00
|
|
|
httpSendReply(http, 404, "File Not Found", NO_MSG);
|
2008-12-30 00:57:07 +01:00
|
|
|
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("");
|
|
|
|
}
|
2009-02-02 01:55:15 +01:00
|
|
|
httpSendReply(http, 400, "Bad Request", NO_MSG);
|
2008-12-30 00:57:07 +01:00
|
|
|
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) {
|
2009-02-02 01:55:15 +01:00
|
|
|
httpSendReply(http, 400, "Bad Request", NO_MSG);
|
2008-12-30 00:57:07 +01:00
|
|
|
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) {
|
2009-02-02 01:55:15 +01:00
|
|
|
httpSendReply(http, 413, "Header too big", NO_MSG);
|
2008-12-30 00:57:07 +01:00
|
|
|
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;
|
2009-02-15 21:01:53 +01:00
|
|
|
if (fmt != NULL && strcmp(fmt, NO_MSG)) {
|
2008-12-30 00:57:07 +01:00
|
|
|
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",
|
2009-02-15 21:01:53 +01:00
|
|
|
title ? title : msg, fmt && strcmp(fmt, NO_MSG) ? details : msg);
|
2008-12-30 00:57:07 +01:00
|
|
|
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"
|
2009-01-08 00:35:40 +01:00
|
|
|
"Content-Length: %ld\r\n"
|
2008-12-30 00:57:07 +01:00
|
|
|
"\r\n",
|
|
|
|
code, msg,
|
|
|
|
code != 200 ? "Connection: close\r\n" : "",
|
2009-01-08 00:35:40 +01:00
|
|
|
(long)strlen(body));
|
2008-12-30 00:57:07 +01:00
|
|
|
}
|
|
|
|
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;
|
|
|
|
}
|