48a65d6bcb
* Protection against large HTTP requests was fixed by adding some null pointer checks. Too large HTTP requests are now correctly handled by returning error code and closing connection.
1963 lines
64 KiB
C
1963 lines
64 KiB
C
// httpconnection.c -- Manage state machine for HTTP connections
|
|
// Copyright (C) 2008-2010 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
|
|
|
|
#define _GNU_SOURCE
|
|
#include "config.h"
|
|
|
|
#include <errno.h>
|
|
#include <arpa/inet.h>
|
|
#include <math.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>
|
|
|
|
#ifdef HAVE_ZLIB
|
|
#include <zlib.h>
|
|
#endif
|
|
|
|
#ifdef HAVE_STRLCAT
|
|
#define strncat(a,b,c) ({ char *_a = (a); strlcat(_a, (b), (c)+1); _a; })
|
|
#endif
|
|
#ifndef HAVE_ISNAN
|
|
#define isnan(x) ({ typeof(x) _x = (x); _x != _x; })
|
|
#endif
|
|
#define max(a, b) ({ typeof(a) _a = (a); typeof(b) _b = (b); \
|
|
_a > _b ? _a : _b; })
|
|
#ifdef HAVE_UNUSED
|
|
#defined ATTR_UNUSED __attribute__((unused))
|
|
#defined UNUSED(x) do { } while (0)
|
|
#else
|
|
#define ATTR_UNUSED
|
|
#define UNUSED(x) do { (void)(x); } while (0)
|
|
#endif
|
|
|
|
#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("[ssl] 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);
|
|
// Reset renegotiations count for connections promoted to SSL.
|
|
http->ssl->renegotiationCount = 0;
|
|
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());
|
|
|
|
// Shutdown SSL connection, if client initiated renegotiation.
|
|
if (http->ssl->renegotiationCount > 1) {
|
|
debug("[ssl] Connection shutdown due to client initiated renegotiation!");
|
|
rc = 0;
|
|
errno = EINVAL;
|
|
}
|
|
} 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) {
|
|
if (how != SHUT_RD) {
|
|
dcheck(!ERR_peek_error());
|
|
for (int i = 0; i < 10; i++) {
|
|
sslBlockSigPipe();
|
|
int rc = SSL_shutdown(http->sslHndl);
|
|
int sPipe = sslUnblockSigPipe();
|
|
if (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 shutdown(http->fd, how);
|
|
}
|
|
|
|
static void httpCloseRead(struct HttpConnection *http) {
|
|
if (!http->closed) {
|
|
httpShutdown(http, SHUT_RD);
|
|
http->closed = 1;
|
|
}
|
|
}
|
|
|
|
#ifndef HAVE_STRCASESTR
|
|
static char *strcasestr(const char *haystack, const char *needle) {
|
|
// This algorithm is O(len(haystack)*len(needle)). Much better algorithms
|
|
// are available, but this code is much simpler and performance is not
|
|
// critical for our workloads.
|
|
int len = strlen(needle);
|
|
do {
|
|
if (!strncasecmp(haystack, needle, len)) {
|
|
return haystack;
|
|
}
|
|
} while (*haystack++);
|
|
return NULL;
|
|
}
|
|
#endif
|
|
|
|
static int httpFinishCommand(struct HttpConnection *http) {
|
|
int rc = HTTP_DONE;
|
|
if ((http->callback || http->websocketHandler) && !http->done) {
|
|
rc = http->callback ? http->callback(http, http->arg, NULL, 0)
|
|
: http->websocketHandler(http, http->arg, WS_CONNECTION_CLOSED, NULL,0);
|
|
check(rc != HTTP_SUSPEND);
|
|
check(rc != HTTP_PARTIAL_REPLY);
|
|
http->callback = NULL;
|
|
http->arg = NULL;
|
|
if (rc == HTTP_ERROR) {
|
|
httpCloseRead(http);
|
|
}
|
|
}
|
|
if (!http->closed) {
|
|
const char *con = getFromHashMap(&http->header, "connection");
|
|
if ((con && strcasestr(con, "close")) ||
|
|
!http->version || strcmp(http->version, "HTTP/1.1") < 0) {
|
|
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) {
|
|
snprintf(lengthBuf, sizeof(lengthBuf), "%d", http->totalWritten);
|
|
} else {
|
|
*lengthBuf = '\000';
|
|
strncat(lengthBuf, "-", sizeof(lengthBuf)-1);
|
|
}
|
|
info("[http] %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 ATTR_UNUSED, char *key, char *value) {
|
|
UNUSED(arg);
|
|
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 *ret;
|
|
if (peerAddr.sa_family == AF_UNIX) {
|
|
if (port) {
|
|
*port = 0;
|
|
}
|
|
check(ret = strdup("localhost"));
|
|
return ret;
|
|
}
|
|
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);
|
|
}
|
|
check(ret = strdup(host));
|
|
return ret;
|
|
}
|
|
|
|
static void httpSetState(struct HttpConnection *http, int state) {
|
|
if (state == (int)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->websocketType = WS_UNDEFINED;
|
|
http->callback = NULL;
|
|
http->websocketHandler = NULL;
|
|
http->arg = NULL;
|
|
http->private = NULL;
|
|
http->code = 200;
|
|
http->ssl = ssl;
|
|
http->sslHndl = NULL;
|
|
http->lastError = 0;
|
|
if (logIsInfo()) {
|
|
debug("[http] 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->done) {
|
|
if (http->callback) {
|
|
http->callback(http, http->arg, NULL, 0);
|
|
} else if (http->websocketHandler) {
|
|
http->websocketHandler(http, http->arg, WS_CONNECTION_CLOSED,NULL,0);
|
|
}
|
|
}
|
|
http->callback = NULL;
|
|
http->isSuspended = 0;
|
|
http->isPartialReply = 0;
|
|
}
|
|
httpSetState(http, COMMAND);
|
|
if (logIsInfo()) {
|
|
debug("[http] 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);
|
|
}
|
|
|
|
#ifdef HAVE_ZLIB
|
|
static int httpAcceptsEncoding(struct HttpConnection *http,
|
|
const char *encoding) {
|
|
int encodingLength = strlen(encoding);
|
|
const char *accepts = getFromHashMap(&http->header, "accept-encoding");
|
|
if (!accepts) {
|
|
return 0;
|
|
}
|
|
double all = -1.0;
|
|
double match = -1.0;
|
|
while (*accepts) {
|
|
while (*accepts == ' ' || *accepts == '\t' ||
|
|
*accepts == '\r' || *accepts == '\n') {
|
|
accepts++;
|
|
}
|
|
const char *ptr = accepts;
|
|
while (*ptr && *ptr != ',' && *ptr != ';' &&
|
|
*ptr != ' ' && *ptr != '\t' &&
|
|
*ptr != '\r' && *ptr != '\n') {
|
|
ptr++;
|
|
}
|
|
int isAll = ptr - accepts == 1 && *accepts == '*';
|
|
int isMatch = ptr - accepts == encodingLength &&
|
|
!strncasecmp(accepts, encoding, encodingLength);
|
|
while (*ptr && *ptr != ';' && *ptr != ',') {
|
|
ptr++;
|
|
}
|
|
double val = 1.0;
|
|
if (*ptr == ';') {
|
|
ptr++;
|
|
while (*ptr == ' ' || *ptr == '\t' || *ptr == '\r' || *ptr == '\n') {
|
|
ptr++;
|
|
}
|
|
if ((*ptr | 0x20) == 'q') {
|
|
ptr++;
|
|
while (*ptr == ' ' || *ptr == '\t' || *ptr == '\r' || *ptr == '\n') {
|
|
ptr++;
|
|
}
|
|
if (*ptr == '=') {
|
|
val = strtod(ptr + 1, (char **)&ptr);
|
|
}
|
|
}
|
|
}
|
|
if (isnan(val) || val == -HUGE_VAL || val < 0) {
|
|
val = 0;
|
|
} else if (val == HUGE_VAL || val > 1.0) {
|
|
val = 1.0;
|
|
}
|
|
if (isAll) {
|
|
all = val;
|
|
} else if (isMatch) {
|
|
match = val;
|
|
}
|
|
while (*ptr && *ptr != ',') {
|
|
ptr++;
|
|
}
|
|
while (*ptr == ',') {
|
|
ptr++;
|
|
}
|
|
accepts = ptr;
|
|
}
|
|
if (match >= 0.0) {
|
|
return match > 0.0;
|
|
} else {
|
|
return all > 0.0;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static void removeHeader(char *header, int *headerLength, const char *id) {
|
|
check(header);
|
|
check(headerLength);
|
|
check(*headerLength >= 0);
|
|
check(id);
|
|
check(strchr(id, ':'));
|
|
int idLength = strlen(id);
|
|
if (idLength <= 0) {
|
|
return;
|
|
}
|
|
for (char *ptr = header; header + *headerLength - ptr >= idLength; ) {
|
|
char *end = ptr;
|
|
do {
|
|
end = memchr(end, '\n', header + *headerLength - end);
|
|
if (end == NULL) {
|
|
end = header + *headerLength;
|
|
} else {
|
|
++end;
|
|
}
|
|
} while (end < header + *headerLength && *end == ' ');
|
|
if (!strncasecmp(ptr, id, idLength)) {
|
|
memmove(ptr, end, header + *headerLength - end);
|
|
*headerLength -= end - ptr;
|
|
} else {
|
|
ptr = end;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void addHeader(char **header, int *headerLength, const char *fmt, ...) {
|
|
check(header);
|
|
check(headerLength);
|
|
check(*headerLength >= 0);
|
|
check(strstr(fmt, "\r\n"));
|
|
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
char *tmp = vStringPrintf(NULL, fmt, ap);
|
|
va_end(ap);
|
|
int tmpLength = strlen(tmp);
|
|
|
|
if (*headerLength >= 2 && !memcmp(*header + *headerLength - 2, "\r\n", 2)) {
|
|
*headerLength -= 2;
|
|
}
|
|
check(*header = realloc(*header, *headerLength + tmpLength + 2));
|
|
|
|
memcpy(*header + *headerLength, tmp, tmpLength);
|
|
memcpy(*header + *headerLength + tmpLength, "\r\n", 2);
|
|
*headerLength += tmpLength + 2;
|
|
free(tmp);
|
|
}
|
|
|
|
void httpTransfer(struct HttpConnection *http, char *msg, int len) {
|
|
check(msg);
|
|
check(len >= 0);
|
|
|
|
char *header = NULL;
|
|
int headerLength = 0;
|
|
int bodyOffset = 0;
|
|
|
|
int compress = 0;
|
|
if (!http->totalWritten) {
|
|
// Perform some basic sanity checks. This does not necessarily catch all
|
|
// possible problems, though.
|
|
int l = len;
|
|
char *line = msg;
|
|
for (char *eol, *lastLine = NULL;
|
|
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) {
|
|
// Found the end of the headers.
|
|
|
|
// Check that we don't send any data with HEAD requests
|
|
int isHead = http->method && !strcmp(http->method, "HEAD");
|
|
check(l == 2 || !isHead);
|
|
|
|
#ifdef HAVE_ZLIB
|
|
// Compress replies that might exceed the size of a single IP packet
|
|
compress = !isHead &&
|
|
!http->isPartialReply &&
|
|
len > 1400 &&
|
|
httpAcceptsEncoding(http, "gzip");
|
|
#endif
|
|
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;
|
|
}
|
|
|
|
if (compress) {
|
|
if (l >= 2 && !memcmp(line, "\r\n", 2)) {
|
|
line += 2;
|
|
l -= 2;
|
|
}
|
|
headerLength = line - msg;
|
|
bodyOffset = headerLength;
|
|
check(header = malloc(headerLength));
|
|
memcpy(header, msg, headerLength);
|
|
}
|
|
|
|
if (compress) {
|
|
#ifdef HAVE_ZLIB
|
|
// Compress the message
|
|
char *compressed;
|
|
check(compressed = malloc(len));
|
|
check(len >= bodyOffset + 2);
|
|
z_stream strm = { .zalloc = Z_NULL,
|
|
.zfree = Z_NULL,
|
|
.opaque = Z_NULL,
|
|
.avail_in = l,
|
|
.next_in = (unsigned char *)line,
|
|
.avail_out = len,
|
|
.next_out = (unsigned char *)compressed
|
|
};
|
|
if (deflateInit2(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED,
|
|
31, 8, Z_DEFAULT_STRATEGY) == Z_OK) {
|
|
if (deflate(&strm, Z_FINISH) == Z_STREAM_END) {
|
|
// Compression was successful and resulted in reduction in size
|
|
debug("[http] Compressed response from %d to %d", len, len-strm.avail_out);
|
|
free(msg);
|
|
msg = compressed;
|
|
len -= strm.avail_out;
|
|
bodyOffset = 0;
|
|
removeHeader(header, &headerLength, "content-length:");
|
|
removeHeader(header, &headerLength, "content-encoding:");
|
|
addHeader(&header, &headerLength, "Content-Length: %d\r\n", len);
|
|
addHeader(&header, &headerLength, "Content-Encoding: gzip\r\n");
|
|
} else {
|
|
free(compressed);
|
|
}
|
|
deflateEnd(&strm);
|
|
} else {
|
|
free(compressed);
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
http->totalWritten += headerLength + (len - bodyOffset);
|
|
if (!headerLength) {
|
|
free(header);
|
|
} else if (http->msg) {
|
|
check(http->msg = realloc(http->msg,
|
|
http->msgLength - http->msgOffset +
|
|
max(http->msgOffset, headerLength)));
|
|
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, header, headerLength);
|
|
http->msgLength += headerLength;
|
|
free(header);
|
|
} else {
|
|
check(!http->msgOffset);
|
|
http->msg = header;
|
|
http->msgLength = headerLength;
|
|
}
|
|
|
|
if (len <= bodyOffset) {
|
|
free(msg);
|
|
} else if (http->msg) {
|
|
check(http->msg = realloc(http->msg,
|
|
http->msgLength - http->msgOffset +
|
|
max(http->msgOffset, len - bodyOffset)));
|
|
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 + bodyOffset, len - bodyOffset);
|
|
http->msgLength += len - bodyOffset;
|
|
free(msg);
|
|
} else {
|
|
check(!http->msgOffset);
|
|
if (bodyOffset) {
|
|
memmove(msg, msg + bodyOffset, len - bodyOffset);
|
|
}
|
|
http->msg = msg;
|
|
http->msgLength = len - bodyOffset;
|
|
}
|
|
|
|
// 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("[http] 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->fd,
|
|
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("[http] 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, *ptr = (char *)host; (ch = *ptr) != '\000'; ptr++) {
|
|
if (ch == ':') {
|
|
*ptr = '\000';
|
|
break;
|
|
}
|
|
if (ch != '-' && ch != '.' &&
|
|
(ch < '0' ||(ch > '9' && ch < 'A') ||
|
|
(ch > 'Z' && ch < 'a')||(ch > 'z' && ch <= 0x7E))) {
|
|
httpSendReply(http, 400, "Bad Request", NO_MSG);
|
|
return HTTP_DONE;
|
|
}
|
|
}
|
|
}
|
|
|
|
char *diff;
|
|
struct HttpHandler *h = (struct HttpHandler *)getFromTrie(handlers,
|
|
http->path, &diff);
|
|
|
|
if (h) {
|
|
if (h->websocketHandler) {
|
|
// Check for WebSocket handshake
|
|
const char *upgrade = getFromHashMap(&http->header,
|
|
"upgrade");
|
|
if (upgrade && !strcmp(upgrade, "WebSocket")) {
|
|
const char *connection = getFromHashMap(&http->header,
|
|
"connection");
|
|
if (connection && !strcmp(connection, "Upgrade")) {
|
|
const char *origin = getFromHashMap(&http->header,
|
|
"origin");
|
|
if (origin) {
|
|
for (const char *ptr = origin; *ptr; ptr++) {
|
|
if ((unsigned char)*ptr < ' ') {
|
|
goto bad_ws_upgrade;
|
|
}
|
|
}
|
|
|
|
const char *protocol = getFromHashMap(&http->header,
|
|
"websocket-protocol");
|
|
if (protocol) {
|
|
for (const char *ptr = protocol; *ptr; ptr++) {
|
|
if ((unsigned char)*ptr < ' ') {
|
|
goto bad_ws_upgrade;
|
|
}
|
|
}
|
|
}
|
|
char *port = NULL;
|
|
if (http->port != (http->sslHndl ? 443 : 80)) {
|
|
port = stringPrintf(NULL,
|
|
":%d", http->port);
|
|
}
|
|
char *response = stringPrintf(NULL,
|
|
"HTTP/1.1 101 Web Socket Protocol Handshake\r\n"
|
|
"Upgrade: WebSocket\r\n"
|
|
"Connection: Upgrade\r\n"
|
|
"WebSocket-Origin: %s\r\n"
|
|
"WebSocket-Location: %s://%s%s%s\r\n"
|
|
"%s%s%s"
|
|
"\r\n",
|
|
origin,
|
|
http->sslHndl ? "wss" : "ws", host && *host ? host : "localhost",
|
|
port ? port : "", http->path,
|
|
protocol ? "WebSocket-Protocol: " : "",
|
|
protocol ? protocol : "",
|
|
protocol ? "\r\n" : "");
|
|
free(port);
|
|
debug("[http] Switching to WebSockets");
|
|
httpTransfer(http, response, strlen(response));
|
|
if (http->expecting < 0) {
|
|
http->expecting = 0;
|
|
}
|
|
http->websocketHandler = h->websocketHandler;
|
|
httpSetState(http, WEBSOCKET);
|
|
return HTTP_READ_MORE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
bad_ws_upgrade:;
|
|
|
|
if (h->handler) {
|
|
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) {
|
|
check(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) {
|
|
check(http->method = strdup(""));
|
|
}
|
|
if (!http->path) {
|
|
check(http->path = strdup(""));
|
|
}
|
|
if (!http->version) {
|
|
check(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) {
|
|
if (http->callback) {
|
|
rc = http->callback(http, http->arg, "", 0);
|
|
if (rc != HTTP_READ_MORE) {
|
|
goto retry;
|
|
}
|
|
} else if (http->websocketHandler) {
|
|
http->websocketHandler(http, http->arg, WS_CONNECTION_OPENED,
|
|
NULL, 0);
|
|
}
|
|
}
|
|
if (http->state != WEBSOCKET) {
|
|
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("[http] 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;
|
|
}
|
|
|
|
static int httpHandleWebSocket(struct HttpConnection *http, int offset,
|
|
const char *buf, int bytes) {
|
|
check(http->websocketHandler);
|
|
int ch = 0x00;
|
|
while (bytes > offset) {
|
|
if (http->websocketType & WS_UNDEFINED) {
|
|
ch = httpGetChar(http, buf, bytes, &offset);
|
|
check(ch >= 0);
|
|
if (http->websocketType & 0xFF) {
|
|
// Reading another byte of length information.
|
|
if (http->expecting > 0xFFFFFF) {
|
|
return 0;
|
|
}
|
|
http->expecting = (128 * http->expecting) + (ch & 0x7F);
|
|
if ((ch & 0x80) == 0) {
|
|
// Done reading length information.
|
|
http->websocketType &= ~WS_UNDEFINED;
|
|
|
|
// ch is used to detect when we read the terminating byte in text
|
|
// mode. In binary mode, it must be set to something other than 0xFF.
|
|
ch = 0x00;
|
|
}
|
|
} else {
|
|
// Reading first byte of frame.
|
|
http->websocketType = (ch & 0xFF) | WS_START_OF_FRAME;
|
|
if (ch & 0x80) {
|
|
// For binary data, we have to read the length before we can start
|
|
// processing payload.
|
|
http->websocketType |= WS_UNDEFINED;
|
|
http->expecting = 0;
|
|
}
|
|
}
|
|
} else if (http->websocketType & 0x80) {
|
|
// Binary data
|
|
if (http->expecting) {
|
|
if (offset < 0) {
|
|
handle_partial:
|
|
check(-offset <= http->partialLength);
|
|
int len = -offset;
|
|
if (len >= http->expecting) {
|
|
len = http->expecting;
|
|
http->websocketType |= WS_END_OF_FRAME;
|
|
}
|
|
if (len &&
|
|
http->websocketHandler(http, http->arg, http->websocketType,
|
|
http->partial + http->partialLength + offset,
|
|
len) != HTTP_DONE) {
|
|
return 0;
|
|
}
|
|
|
|
if (ch == 0xFF) {
|
|
// In text mode, we jump to handle_partial, when we find the
|
|
// terminating 0xFF byte. If so, we should try to consume it now.
|
|
if (len < http->partialLength) {
|
|
len++;
|
|
http->websocketType = WS_UNDEFINED;
|
|
}
|
|
}
|
|
|
|
if (len == http->partialLength) {
|
|
free(http->partial);
|
|
http->partial = NULL;
|
|
http->partialLength = 0;
|
|
} else {
|
|
memmove(http->partial, http->partial + len,
|
|
http->partialLength - len);
|
|
http->partialLength -= len;
|
|
}
|
|
offset += len;
|
|
http->expecting -= len;
|
|
} else {
|
|
handle_buffered:;
|
|
int len = bytes - offset;
|
|
if (len >= http->expecting) {
|
|
len = http->expecting;
|
|
http->websocketType |= WS_END_OF_FRAME;
|
|
}
|
|
if (len &&
|
|
http->websocketHandler(http, http->arg, http->websocketType,
|
|
buf + offset, len) != HTTP_DONE) {
|
|
return 0;
|
|
}
|
|
|
|
if (ch == 0xFF) {
|
|
// In text mode, we jump to handle_buffered, when we find the
|
|
// terminating 0xFF byte. If so, we should consume it now.
|
|
check(offset + len < bytes);
|
|
len++;
|
|
http->websocketType = WS_UNDEFINED;
|
|
}
|
|
offset += len;
|
|
http->expecting -= len;
|
|
}
|
|
http->websocketType &= ~(WS_START_OF_FRAME | WS_END_OF_FRAME);
|
|
} else {
|
|
// Read all data. Go back to looking for a new frame header.
|
|
http->websocketType = WS_UNDEFINED;
|
|
}
|
|
} else {
|
|
// Process text data until we find a 0xFF bytes.
|
|
int i = offset;
|
|
|
|
// If we have partial data, process that first.
|
|
while (i < 0) {
|
|
ch = httpGetChar(http, buf, bytes, &i);
|
|
check(ch != -1);
|
|
|
|
// Terminate when we either find the 0xFF, or we have reached the end
|
|
// of partial data.
|
|
if (ch == 0xFF || !i) {
|
|
// Set WS_END_OF_FRAME, iff we have found the 0xFF marker.
|
|
http->expecting = i - offset - (ch == 0xFF);
|
|
goto handle_partial;
|
|
}
|
|
}
|
|
|
|
// Read all remaining buffered bytes (i.e. positive offset).
|
|
while (bytes > i) {
|
|
ch = httpGetChar(http, buf, bytes, &i);
|
|
check(ch != -1);
|
|
|
|
// Terminate when we either find the 0xFF, or we have reached the end
|
|
// of buffered data.
|
|
if (ch == 0xFF || bytes == i) {
|
|
// Set WS_END_OF_FRAME, iff we have found the 0xFF marker.
|
|
http->expecting = i - offset - (ch == 0xFF);
|
|
goto handle_buffered;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
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) {
|
|
debug("[http] Connection closed due to exceeded header size!");
|
|
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 >= (int)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 &&
|
|
len > http->expecting) {
|
|
len = http->expecting;
|
|
}
|
|
if (http->state == PAYLOAD) {
|
|
len = httpParsePayload(http, offset, buf,
|
|
len + offset);
|
|
}
|
|
consumed = len;
|
|
pushBack = bytes - offset - len;
|
|
}
|
|
} else if (http->state == WEBSOCKET) {
|
|
if (!httpHandleWebSocket(http, offset, buf, bytes)) {
|
|
httpCloseRead(http);
|
|
break;
|
|
}
|
|
consumed += bytes - offset;
|
|
} else {
|
|
check(0);
|
|
}
|
|
|
|
offset += consumed;
|
|
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 {
|
|
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:
|
|
case WEBSOCKET:
|
|
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 && strcmp(fmt, NO_MSG)) {
|
|
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 && strcmp(fmt, NO_MSG) ? 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 = http->method && !strcmp(http->method, "HEAD");
|
|
if (!isHead) {
|
|
response = stringPrintf(response, "%s", body);
|
|
}
|
|
free(body);
|
|
check(response);
|
|
httpTransfer(http, response, strlen(response));
|
|
if (code != 200 || isHead) {
|
|
httpCloseRead(http);
|
|
}
|
|
}
|
|
|
|
void httpSendWebSocketTextMsg(struct HttpConnection *http, int type,
|
|
const char *fmt, ...) {
|
|
check(type >= 0 && type <= 0x7F);
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
char *buf;
|
|
int len;
|
|
if (strcmp(fmt, BINARY_MSG)) {
|
|
// Send a printf() style text message
|
|
buf = vStringPrintf(NULL, fmt, ap);
|
|
len = strlen(buf);
|
|
} else {
|
|
// Send a binary message
|
|
len = va_arg(ap, int);
|
|
buf = va_arg(ap, char *);
|
|
}
|
|
va_end(ap);
|
|
check(len >= 0 && len < 0x60000000);
|
|
|
|
// We assume that all input data is directly mapped in the range 0..255
|
|
// (e.g. ISO-8859-1). In order to transparently send it over a web socket,
|
|
// we have to encode it in UTF-8.
|
|
int utf8Len = len + 2;
|
|
for (int i = 0; i < len; ++i) {
|
|
if (buf[i] & 0x80) {
|
|
++utf8Len;
|
|
}
|
|
}
|
|
char *utf8;
|
|
check(utf8 = malloc(utf8Len));
|
|
utf8[0] = type;
|
|
for (int i = 0, j = 1; i < len; ++i) {
|
|
unsigned char ch = buf[i];
|
|
if (ch & 0x80) {
|
|
utf8[j++] = 0xC0 + (ch >> 6);
|
|
utf8[j++] = 0x80 + (ch & 0x3F);
|
|
} else {
|
|
utf8[j++] = ch;
|
|
}
|
|
check(j < utf8Len);
|
|
}
|
|
utf8[utf8Len-1] = '\xFF';
|
|
|
|
// Free our temporary buffer, if we actually did allocate one.
|
|
if (strcmp(fmt, BINARY_MSG)) {
|
|
free(buf);
|
|
}
|
|
|
|
// Send to browser.
|
|
httpTransfer(http, utf8, utf8Len);
|
|
}
|
|
|
|
void httpSendWebSocketBinaryMsg(struct HttpConnection *http, int type,
|
|
const void *buf, int len) {
|
|
check(type >= 0x80 && type <= 0xFF);
|
|
check(len > 0 && len < 0x7FFFFFF0);
|
|
|
|
// Allocate buffer for header and payload.
|
|
char *data;
|
|
check(data = malloc(len + 6));
|
|
data[0] = type;
|
|
|
|
// Convert length to base-128.
|
|
int i = 0;
|
|
int l = len;
|
|
do {
|
|
data[++i] = 0x80 + (l & 0x7F);
|
|
l /= 128;
|
|
} while (l);
|
|
data[i] &= 0x7F;
|
|
|
|
// Reverse digits, so that they are big-endian.
|
|
for (int j = 0; j < i/2; ++j) {
|
|
char ch = data[1+j];
|
|
data[1+j] = data[i-j];
|
|
data[i-j] = ch;
|
|
}
|
|
|
|
// Transmit header and payload.
|
|
memmove(data + i + 1, buf, len);
|
|
httpTransfer(http, data, len + i + 1);
|
|
}
|
|
|
|
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 *httpGetRealIP(const struct HttpConnection *http) {
|
|
return getFromHashMap(&http->header, "x-real-ip");
|
|
}
|
|
|
|
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);
|
|
int s_size = 8 + strlen(host) + 25 + strlen(http->path);
|
|
check(*(char **)&http->url = malloc(s_size + 1));
|
|
*http->url = '\000';
|
|
strncat(http->url, http->sslHndl ? "https://" : "http://", s_size);
|
|
strncat(http->url, host, s_size);
|
|
if (http->port != (http->sslHndl ? 443 : 80)) {
|
|
snprintf(strrchr(http->url, '\000'), 25, ":%d", http->port);
|
|
}
|
|
strncat(http->url, http->path, s_size);
|
|
}
|
|
return http->url;
|
|
}
|
|
|
|
const char *httpGetVersion(const struct HttpConnection *http) {
|
|
return http->version;
|
|
}
|
|
|
|
const struct HashMap *httpGetHeaders(const struct HttpConnection *http) {
|
|
return &http->header;
|
|
}
|