1 // httpconnection.c -- Manage state machine for HTTP connections
2 // Copyright (C) 2008-2010 Markus Gutschke <markus@shellinabox.com>
4 // This program is free software; you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License version 2 as
6 // published by the Free Software Foundation.
8 // This program is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 // GNU General Public License for more details.
13 // You should have received a copy of the GNU General Public License along
14 // with this program; if not, write to the Free Software Foundation, Inc.,
15 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17 // In addition to these license terms, the author grants the following
20 // If you modify this program, or any covered work, by linking or
21 // combining it with the OpenSSL project's OpenSSL library (or a
22 // modified version of that library), containing parts covered by the
23 // terms of the OpenSSL or SSLeay licenses, the author
24 // grants you additional permission to convey the resulting work.
25 // Corresponding Source for a non-source form of such a combination
26 // shall include the source code for the parts of OpenSSL used as well
27 // as that of the covered work.
29 // You may at your option choose to remove this additional permission from
30 // the work, or from any part of it.
32 // It is possible to build this program in a way that it loads OpenSSL
33 // libraries at run-time. If doing so, the following notices are required
34 // by the OpenSSL and SSLeay licenses:
36 // This product includes software developed by the OpenSSL Project
37 // for use in the OpenSSL Toolkit. (http://www.openssl.org/)
39 // This product includes cryptographic software written by Eric Young
40 // (eay@cryptsoft.com)
43 // The most up-to-date version of this program is always available from
44 // http://shellinabox.com
50 #include <arpa/inet.h>
53 #include <netinet/in.h>
58 #include <sys/socket.h>
59 #include <sys/types.h>
67 #define strncat(a,b,c) ({ char *_a = (a); strlcat(_a, (b), (c)+1); _a; })
70 #define isnan(x) ({ typeof(x) _x = (x); _x != _x; })
72 #define max(a, b) ({ typeof(a) _a = (a); typeof(b) _b = (b); \
75 #include "libhttp/httpconnection.h"
76 #include "logging/logging.h"
78 #define MAX_HEADER_LENGTH (64<<10)
79 #define CONNECTION_TIMEOUT (10*60)
81 static int httpPromoteToSSL(struct HttpConnection *http, const char *buf,
83 if (http->ssl->enabled && !http->sslHndl) {
84 debug("Switching to SSL (replaying %d+%d bytes)",
85 http->partialLength, len);
86 if (http->partial && len > 0) {
87 check(http->partial = realloc(http->partial,
88 http->partialLength + len));
89 memcpy(http->partial + http->partialLength, buf, len);
90 http->partialLength += len;
92 int rc = sslPromoteToSSL(
93 http->ssl, &http->sslHndl, http->fd,
94 http->partial ? http->partial : buf,
95 http->partial ? http->partialLength : len);
98 SSL_set_app_data(http->sslHndl, http);
101 http->partialLength = 0;
109 static ssize_t httpRead(struct HttpConnection *http, char *buf, ssize_t len) {
113 dcheck(!ERR_peek_error());
114 rc = SSL_read(http->sslHndl, buf, len);
118 switch (http->lastError = SSL_get_error(http->sslHndl, rc)) {
119 case SSL_ERROR_WANT_READ:
120 case SSL_ERROR_WANT_WRITE:
133 dcheck(!ERR_peek_error());
135 rc = NOINTR(read(http->fd, buf, len));
139 serverSetTimeout(httpGetServerConnection(http), CONNECTION_TIMEOUT);
144 static ssize_t httpWrite(struct HttpConnection *http, const char *buf,
149 dcheck(!ERR_peek_error());
150 rc = SSL_write(http->sslHndl, buf, len);
154 switch (http->lastError = SSL_get_error(http->sslHndl, rc)) {
155 case SSL_ERROR_WANT_READ:
156 case SSL_ERROR_WANT_WRITE:
169 dcheck(!ERR_peek_error());
171 rc = NOINTR(write(http->fd, buf, len));
177 static int httpShutdown(struct HttpConnection *http, int how) {
180 if (how != SHUT_RD) {
181 dcheck(!ERR_peek_error());
182 for (int i = 0; i < 10; i++) {
184 rc = SSL_shutdown(http->sslHndl);
185 int sPipe = sslUnblockSigPipe();
191 // Retry a few times in order to prefer a clean bidirectional
192 // shutdown. But don't bother if the other side already closed
199 sslFreeHndl(&http->sslHndl);
202 return shutdown(http->fd, how);
205 static void httpCloseRead(struct HttpConnection *http) {
207 httpShutdown(http, SHUT_RD);
212 #ifndef HAVE_STRCASESTR
213 static char *strcasestr(const char *haystack, const char *needle) {
214 // This algorithm is O(len(haystack)*len(needle)). Much better algorithms
215 // are available, but this code is much simpler and performance is not
216 // critical for our workloads.
217 int len = strlen(needle);
219 if (!strncasecmp(haystack, needle, len)) {
222 } while (*haystack++);
227 static int httpFinishCommand(struct HttpConnection *http) {
229 if ((http->callback || http->websocketHandler) && !http->done) {
230 rc = http->callback ? http->callback(http, http->arg, NULL, 0)
231 : http->websocketHandler(http, http->arg, WS_CONNECTION_CLOSED, NULL,0);
232 check(rc != HTTP_SUSPEND);
233 check(rc != HTTP_PARTIAL_REPLY);
234 http->callback = NULL;
236 if (rc == HTTP_ERROR) {
241 const char *con = getFromHashMap(&http->header, "connection");
242 if ((con && strcasestr(con, "close")) ||
243 !http->version || strcmp(http->version, "HTTP/1.1") < 0) {
250 check(http->version);
251 if (http->peerName) {
252 time_t t = currentTime;
254 check (ltime = localtime(&t));
257 check(strftime(timeBuf, sizeof(timeBuf),
258 "[%d/%b/%Y:%H:%M:%S %z]", ltime));
259 if (http->totalWritten > 0) {
260 snprintf(lengthBuf, sizeof(lengthBuf), "%d", http->totalWritten);
263 strncat(lengthBuf, "-", sizeof(lengthBuf)-1);
265 info("%s - - %s \"%s %s %s\" %d %s",
266 http->peerName, timeBuf, http->method, http->path, http->version,
267 http->code, lengthBuf);
273 static void httpDestroyHeaders(void *arg, char *key, char *value) {
279 static char *getPeerName(int fd, int *port, int numericHosts) {
280 struct sockaddr peerAddr;
281 socklen_t sockLen = sizeof(peerAddr);
282 if (getpeername(fd, &peerAddr, &sockLen)) {
290 getnameinfo(&peerAddr, sockLen, host, sizeof(host), NULL, 0, NI_NOFQDN)){
291 check(inet_ntop(peerAddr.sa_family,
292 &((struct sockaddr_in *)&peerAddr)->sin_addr,
293 host, sizeof(host)));
296 *port = ntohs(((struct sockaddr_in *)&peerAddr)->sin_port);
299 check(ret = strdup(host));
303 static void httpSetState(struct HttpConnection *http, int state) {
304 if (state == (int)http->state) {
308 if (state == COMMAND) {
309 if (http->state != SNIFFING_SSL) {
310 int rc = httpFinishCommand(http);
311 check(rc != HTTP_SUSPEND);
312 check(rc != HTTP_PARTIAL_REPLY);
314 check(!http->private);
318 free(http->matchedPath);
319 free(http->pathInfo);
326 http->matchedPath = NULL;
327 http->pathInfo = NULL;
329 http->version = NULL;
330 destroyHashMap(&http->header);
331 initHashMap(&http->header, httpDestroyHeaders, NULL);
332 http->headerLength = 0;
333 http->callback = NULL;
335 http->totalWritten = 0;
341 struct HttpConnection *newHttpConnection(struct Server *server, int fd,
342 int port, struct SSLSupport *ssl,
344 struct HttpConnection *http;
345 check(http = malloc(sizeof(struct HttpConnection)));
346 initHttpConnection(http, server, fd, port, ssl, numericHosts);
350 void initHttpConnection(struct HttpConnection *http, struct Server *server,
351 int fd, int port, struct SSLSupport *ssl,
353 http->server = server;
354 http->connection = NULL;
358 http->isSuspended = 0;
359 http->isPartialReply = 0;
361 http->state = ssl ? SNIFFING_SSL : COMMAND;
362 http->peerName = getPeerName(fd, &http->peerPort, numericHosts);
366 http->matchedPath = NULL;
367 http->pathInfo = NULL;
369 http->version = NULL;
370 initHashMap(&http->header, httpDestroyHeaders, NULL);
371 http->headerLength = 0;
373 http->partial = NULL;
374 http->partialLength = 0;
378 http->totalWritten = 0;
380 http->websocketType = WS_UNDEFINED;
381 http->callback = NULL;
382 http->websocketHandler = NULL;
384 http->private = NULL;
387 http->sslHndl = NULL;
390 debug("Accepted connection from %s:%d",
391 http->peerName ? http->peerName : "???", http->peerPort);
395 void destroyHttpConnection(struct HttpConnection *http) {
397 if (http->isSuspended || http->isPartialReply) {
399 if (http->callback) {
400 http->callback(http, http->arg, NULL, 0);
401 } else if (http->websocketHandler) {
402 http->websocketHandler(http, http->arg, WS_CONNECTION_CLOSED,NULL,0);
405 http->callback = NULL;
406 http->isSuspended = 0;
407 http->isPartialReply = 0;
409 httpSetState(http, COMMAND);
411 debug("Closing connection to %s:%d",
412 http->peerName ? http->peerName : "???", http->peerPort);
414 httpShutdown(http, http->closed ? SHUT_WR : SHUT_RDWR);
415 dcheck(!close(http->fd));
416 free(http->peerName);
420 free(http->matchedPath);
421 free(http->pathInfo);
424 destroyHashMap(&http->header);
430 void deleteHttpConnection(struct HttpConnection *http) {
431 destroyHttpConnection(http);
436 static int httpAcceptsEncoding(struct HttpConnection *http,
437 const char *encoding) {
438 int encodingLength = strlen(encoding);
439 const char *accepts = getFromHashMap(&http->header, "accept-encoding");
446 while (*accepts == ' ' || *accepts == '\t' ||
447 *accepts == '\r' || *accepts == '\n') {
450 const char *ptr = accepts;
451 while (*ptr && *ptr != ',' && *ptr != ';' &&
452 *ptr != ' ' && *ptr != '\t' &&
453 *ptr != '\r' && *ptr != '\n') {
456 int isAll = ptr - accepts == 1 && *accepts == '*';
457 int isMatch = ptr - accepts == encodingLength &&
458 !strncasecmp(accepts, encoding, encodingLength);
459 while (*ptr && *ptr != ';' && *ptr != ',') {
465 while (*ptr == ' ' || *ptr == '\t' || *ptr == '\r' || *ptr == '\n') {
468 if ((*ptr | 0x20) == 'q') {
470 while (*ptr == ' ' || *ptr == '\t' || *ptr == '\r' || *ptr == '\n') {
474 val = strtod(ptr + 1, (char **)&ptr);
478 if (isnan(val) || val == -HUGE_VAL || val < 0) {
480 } else if (val == HUGE_VAL || val > 1.0) {
485 } else if (isMatch) {
488 while (*ptr && *ptr != ',') {
491 while (*ptr == ',') {
504 static void removeHeader(char *header, int *headerLength, const char *id) {
507 check(*headerLength >= 0);
509 check(strchr(id, ':'));
510 int idLength = strlen(id);
514 for (char *ptr = header; header + *headerLength - ptr >= idLength; ) {
517 end = memchr(end, '\n', header + *headerLength - end);
519 end = header + *headerLength;
523 } while (end < header + *headerLength && *end == ' ');
524 if (!strncasecmp(ptr, id, idLength)) {
525 memmove(ptr, end, header + *headerLength - end);
526 *headerLength -= end - ptr;
533 static void addHeader(char **header, int *headerLength, const char *fmt, ...) {
536 check(*headerLength >= 0);
537 check(strstr(fmt, "\r\n"));
541 char *tmp = vStringPrintf(NULL, fmt, ap);
543 int tmpLength = strlen(tmp);
545 if (*headerLength >= 2 && !memcmp(*header + *headerLength - 2, "\r\n", 2)) {
548 check(*header = realloc(*header, *headerLength + tmpLength + 2));
550 memcpy(*header + *headerLength, tmp, tmpLength);
551 memcpy(*header + *headerLength + tmpLength, "\r\n", 2);
552 *headerLength += tmpLength + 2;
556 void httpTransfer(struct HttpConnection *http, char *msg, int len) {
560 // Internet Explorer seems to have difficulties with compressed data. It
561 // also has difficulties with SSL connections that are being proxied.
563 const char *userAgent = getFromHashMap(&http->header, "user-agent");
564 const char *msie = userAgent ? strstr(userAgent, "MSIE ") : NULL;
570 int headerLength = 0;
574 if (!http->totalWritten) {
575 // Perform some basic sanity checks. This does not necessarily catch all
576 // possible problems, though.
579 for (char *eol, *lastLine = NULL;
580 l > 0 && (eol = memchr(line, '\n', l)) != NULL; ) {
581 // All lines end in CR LF
582 check(eol[-1] == '\r');
584 // The first line looks like "HTTP/1.x STATUS\r\n"
585 check(eol - line > 11);
586 check(!memcmp(line, "HTTP/1.", 7));
587 check(line[7] >= '0' && line[7] <= '9' &&
588 (line[8] == ' ' || line[8] == '\t'));
589 int i = eol - line - 9;
590 for (char *ptr = line + 9; i-- > 0; ) {
592 if (ch < '0' || ch > '9') {
593 check(ptr > line + 10);
594 check(ch == ' ' || ch == '\t');
599 } else if (line + 1 == eol) {
600 // Found the end of the headers.
602 // Check that we don't send any data with HEAD requests
603 int isHead = !strcmp(http->method, "HEAD");
604 check(l == 2 || !isHead);
607 // Compress replies that might exceed the size of a single IP packet
608 compress = !ieBug && !isHead &&
609 !http->isPartialReply &&
611 httpAcceptsEncoding(http, "deflate");
615 // Header lines either contain a colon, or they are continuation
617 if (*line != ' ' && *line != '\t') {
618 check(memchr(line, ':', eol - line));
626 if (ieBug || compress) {
627 if (l >= 2 && !memcmp(line, "\r\n", 2)) {
631 headerLength = line - msg;
632 bodyOffset = headerLength;
633 check(header = malloc(headerLength));
634 memcpy(header, msg, headerLength);
637 removeHeader(header, &headerLength, "connection:");
638 addHeader(&header, &headerLength, "Connection: close\r\n");
643 // Compress the message
645 check(compressed = malloc(len));
646 check(len >= bodyOffset + 2);
647 z_stream strm = { .zalloc = Z_NULL,
651 .next_in = (unsigned char *)line,
653 .next_out = (unsigned char *)compressed
655 if (deflateInit(&strm, Z_DEFAULT_COMPRESSION) == Z_OK) {
656 if (deflate(&strm, Z_FINISH) == Z_STREAM_END) {
657 // Compression was successful and resulted in reduction in size
658 debug("Compressed response from %d to %d", len, len-strm.avail_out);
661 len -= strm.avail_out;
663 removeHeader(header, &headerLength, "content-length:");
664 removeHeader(header, &headerLength, "content-encoding:");
665 addHeader(&header, &headerLength, "Content-Length: %d\r\n", len);
666 addHeader(&header, &headerLength, "Content-Encoding: deflate\r\n");
678 http->totalWritten += headerLength + (len - bodyOffset);
681 } else if (http->msg) {
682 check(http->msg = realloc(http->msg,
683 http->msgLength - http->msgOffset +
684 max(http->msgOffset, headerLength)));
685 if (http->msgOffset) {
686 memmove(http->msg, http->msg + http->msgOffset,
687 http->msgLength - http->msgOffset);
688 http->msgLength -= http->msgOffset;
691 memcpy(http->msg + http->msgLength, header, headerLength);
692 http->msgLength += headerLength;
695 check(!http->msgOffset);
697 http->msgLength = headerLength;
700 if (len <= bodyOffset) {
702 } else if (http->msg) {
703 check(http->msg = realloc(http->msg,
704 http->msgLength - http->msgOffset +
705 max(http->msgOffset, len - bodyOffset)));
706 if (http->msgOffset) {
707 memmove(http->msg, http->msg + http->msgOffset,
708 http->msgLength - http->msgOffset);
709 http->msgLength -= http->msgOffset;
712 memcpy(http->msg + http->msgLength, msg + bodyOffset, len - bodyOffset);
713 http->msgLength += len - bodyOffset;
716 check(!http->msgOffset);
718 memmove(msg, msg + bodyOffset, len - bodyOffset);
721 http->msgLength = len - bodyOffset;
724 // The caller can suspend the connection, so that it can send an
725 // asynchronous reply. Once the reply has been sent, the connection
726 // gets reactivated. Normally, this means it would go back to listening
728 // Similarly, the caller can indicate that this is a partial message and
729 // return additional data in subsequent calls to the callback handler.
730 if (http->isSuspended || http->isPartialReply) {
731 if (http->msg && http->msgLength > 0) {
732 int wrote = httpWrite(http, http->msg, http->msgLength);
733 if (wrote < 0 && errno != EAGAIN) {
738 } else if (wrote > 0) {
739 if (wrote == http->msgLength) {
744 memmove(http->msg, http->msg + wrote, http->msgLength - wrote);
745 http->msgLength -= wrote;
750 check(http->state == PAYLOAD || http->state == DISCARD_PAYLOAD);
751 if (!http->isPartialReply) {
752 if (http->expecting < 0) {
753 // If we do not know the length of the content, close the connection.
754 debug("Closing previously suspended connection");
756 httpSetState(http, DISCARD_PAYLOAD);
757 } else if (http->expecting == 0) {
758 httpSetState(http, COMMAND);
759 http->isSuspended = 0;
760 struct ServerConnection *connection = httpGetServerConnection(http);
761 if (!serverGetTimeout(connection)) {
762 serverSetTimeout(connection, CONNECTION_TIMEOUT);
764 serverConnectionSetEvents(http->server, connection,
765 http->msgLength ? POLLIN|POLLOUT : POLLIN);
775 void httpTransferPartialReply(struct HttpConnection *http, char *msg, int len){
776 check(!http->isSuspended);
777 http->isPartialReply = 1;
778 if (http->state != PAYLOAD && http->state != DISCARD_PAYLOAD) {
779 check(http->state == HEADERS);
780 httpSetState(http, PAYLOAD);
782 httpTransfer(http, msg, len);
785 static int httpHandleCommand(struct HttpConnection *http,
786 const struct Trie *handlers) {
787 debug("Handling \"%s\" \"%s\"", http->method, http->path);
788 const char *contentLength = getFromHashMap(&http->header,
790 if (contentLength != NULL && *contentLength) {
792 http->expecting = strtol(contentLength,
795 // Invalid length. Read until end of stream and then close
797 http->expecting = -1;
800 // Unknown length. Read until end of stream and then close
802 http->expecting = -1;
804 if (!strcmp(http->method, "OPTIONS")) {
805 char *response = stringPrintf(NULL,
806 "HTTP/1.1 200 OK\r\n"
807 "Content-Length: 0\r\n"
808 "Allow: GET, POST, OPTIONS\r\n"
810 httpTransfer(http, response, strlen(response));
811 if (http->expecting < 0) {
814 return HTTP_READ_MORE;
815 } else if (!strcmp(http->method, "GET")) {
816 if (http->expecting < 0) {
819 } else if (!strcmp(http->method, "POST")) {
820 } else if (!strcmp(http->method, "HEAD")) {
821 if (http->expecting < 0) {
824 } else if (!strcmp(http->method, "PUT") ||
825 !strcmp(http->method, "DELETE") ||
826 !strcmp(http->method, "TRACE") ||
827 !strcmp(http->method, "CONNECT")) {
828 httpSendReply(http, 405, "Method Not Allowed", NO_MSG);
831 httpSendReply(http, 501, "Method Not Implemented", NO_MSG);
834 const char *host = getFromHashMap(&http->header,
837 for (char ch, *ptr = (char *)host; (ch = *ptr) != '\000'; ptr++) {
842 if (ch != '-' && ch != '.' &&
843 (ch < '0' ||(ch > '9' && ch < 'A') ||
844 (ch > 'Z' && ch < 'a')||(ch > 'z' && ch <= 0x7E))) {
845 httpSendReply(http, 400, "Bad Request", NO_MSG);
852 struct HttpHandler *h = (struct HttpHandler *)getFromTrie(handlers,
856 if (h->websocketHandler) {
857 // Check for WebSocket handshake
858 const char *upgrade = getFromHashMap(&http->header,
860 if (upgrade && !strcmp(upgrade, "WebSocket")) {
861 const char *connection = getFromHashMap(&http->header,
863 if (connection && !strcmp(connection, "Upgrade")) {
864 const char *origin = getFromHashMap(&http->header,
867 for (const char *ptr = origin; *ptr; ptr++) {
868 if ((unsigned char)*ptr < ' ') {
873 const char *protocol = getFromHashMap(&http->header,
874 "websocket-protocol");
876 for (const char *ptr = protocol; *ptr; ptr++) {
877 if ((unsigned char)*ptr < ' ') {
883 if (http->port != (http->sslHndl ? 443 : 80)) {
884 port = stringPrintf(NULL,
887 char *response = stringPrintf(NULL,
888 "HTTP/1.1 101 Web Socket Protocol Handshake\r\n"
889 "Upgrade: WebSocket\r\n"
890 "Connection: Upgrade\r\n"
891 "WebSocket-Origin: %s\r\n"
892 "WebSocket-Location: %s://%s%s%s\r\n"
896 http->sslHndl ? "wss" : "ws", host && *host ? host : "localhost",
897 port ? port : "", http->path,
898 protocol ? "WebSocket-Protocol: " : "",
899 protocol ? protocol : "",
900 protocol ? "\r\n" : "");
902 debug("Switching to WebSockets");
903 httpTransfer(http, response, strlen(response));
904 if (http->expecting < 0) {
907 http->websocketHandler = h->websocketHandler;
908 httpSetState(http, WEBSOCKET);
909 return HTTP_READ_MORE;
918 while (diff > http->path && diff[-1] == '/') {
921 if (!*diff || *diff == '/' || *diff == '?' || *diff == '#') {
922 check(!http->matchedPath);
923 check(!http->pathInfo);
926 check(http->matchedPath = malloc(diff - http->path + 1));
927 memcpy(http->matchedPath, http->path, diff - http->path);
928 http->matchedPath[diff - http->path] = '\000';
930 const char *query = strchr(diff, '?');
931 if (*diff && *diff != '?') {
932 const char *endOfInfo = query
933 ? query : strrchr(diff, '\000');
934 check(http->pathInfo = malloc(endOfInfo - diff + 1));
935 memcpy(http->pathInfo, diff, endOfInfo - diff);
936 http->pathInfo[endOfInfo - diff] = '\000';
940 check(http->query = strdup(query + 1));
942 return h->handler(http, h->arg);
946 httpSendReply(http, 404, "File Not Found", NO_MSG);
950 static int httpGetChar(struct HttpConnection *http, const char *buf,
951 int size, int *offset) {
953 return (unsigned char)http->partial[http->partialLength + (*offset)++];
954 } else if (*offset < size) {
955 return (unsigned char)buf[(*offset)++];
961 static int httpParseCommand(struct HttpConnection *http, int offset,
962 const char *buf, int bytes, int firstSpace,
963 int lastSpace, int lineLength) {
964 if (firstSpace < 1 || lastSpace < 0) {
967 check(http->method = strdup(""));
970 check(http->path = strdup(""));
972 if (!http->version) {
973 check(http->version = strdup(""));
975 httpSendReply(http, 400, "Bad Request", NO_MSG);
976 httpSetState(http, COMMAND);
979 check(!http->method);
980 check(http->method = malloc(firstSpace + 1));
983 for (; j < firstSpace; j++) {
984 int ch = httpGetChar(http, buf, bytes, &i);
985 if (ch >= 'a' && ch <= 'z') {
988 http->method[j] = ch;
990 http->method[j] = '\000';
992 check(http->path = malloc(lastSpace - firstSpace));
994 while (i < offset + lastSpace) {
995 int ch = httpGetChar(http, buf, bytes, &i);
996 if ((ch != ' ' && ch != '\t') || j) {
997 http->path[j++] = ch;
1000 http->path[j] = '\000';
1001 if (*http->path != '/' &&
1002 (strcmp(http->method, "OPTIONS") || strcmp(http->path, "*"))) {
1005 check(!http->version);
1006 check(http->version = malloc(lineLength - lastSpace + 1));
1008 while (i < offset + lineLength) {
1009 int ch = httpGetChar(http, buf, bytes, &i);
1013 if (ch >= 'a' && ch <= 'z') {
1016 if ((ch != ' ' && ch != '\t') || j) {
1017 http->version[j] = ch;
1021 http->version[j] = '\000';
1022 if (memcmp(http->version, "HTTP/", 5) ||
1023 (http->version[5] < '1' || http->version[5] > '9')) {
1026 httpSetState(http, HEADERS);
1030 static int httpParseHeaders(struct HttpConnection *http,
1031 const struct Trie *handlers, int offset,
1032 const char *buf, int bytes, int colon,
1035 int ch = httpGetChar(http, buf, bytes, &i);
1036 if (ch == ' ' || ch == '\t') {
1038 char **oldValue = getRefFromHashMap(&http->header, http->key);
1040 int oldLength = strlen(*oldValue);
1041 check(*oldValue = realloc(*oldValue,
1042 oldLength + lineLength + 1));
1044 int end = oldLength + lineLength;
1045 (*oldValue)[j++] = ' ';
1046 for (; j < end; j++) {
1047 ch = httpGetChar(http, buf, bytes, &i);
1048 if (ch == ' ' || ch == '\t') {
1052 } else if (ch == '\r' && j == end - 1) {
1055 (*oldValue)[j] = ch;
1057 (*oldValue)[j] = '\000';
1059 } else if ((ch == '\r' &&
1060 httpGetChar(http, buf, bytes, &i) == '\n') ||
1061 ch == '\n' || ch == -1) {
1062 check(!http->expecting);
1063 http->callback = NULL;
1065 int rc = httpHandleCommand(http, handlers);
1067 struct ServerConnection *connection = httpGetServerConnection(http);
1071 if (http->expecting < 0 || rc == HTTP_ERROR) {
1072 httpCloseRead(http);
1075 http->isSuspended = 0;
1076 http->isPartialReply = 0;
1077 if (!serverGetTimeout(connection)) {
1078 serverSetTimeout(connection, CONNECTION_TIMEOUT);
1080 httpSetState(http, http->expecting ? DISCARD_PAYLOAD : COMMAND);
1082 case HTTP_READ_MORE:
1083 http->isSuspended = 0;
1084 http->isPartialReply = 0;
1085 if (!serverGetTimeout(connection)) {
1086 serverSetTimeout(connection, CONNECTION_TIMEOUT);
1089 if (!http->expecting) {
1090 if (http->callback) {
1091 rc = http->callback(http, http->arg, "", 0);
1092 if (rc != HTTP_READ_MORE) {
1095 } else if (http->websocketHandler) {
1096 http->websocketHandler(http, http->arg, WS_CONNECTION_OPENED,
1100 if (http->state != WEBSOCKET) {
1101 httpSetState(http, http->expecting ? PAYLOAD : COMMAND);
1105 http->isSuspended = 1;
1106 http->isPartialReply = 0;
1107 serverSetTimeout(connection, 0);
1108 if (http->state != PAYLOAD && http->state != DISCARD_PAYLOAD) {
1109 check(http->state == HEADERS);
1110 httpSetState(http, PAYLOAD);
1113 case HTTP_PARTIAL_REPLY:
1114 http->isSuspended = 0;
1115 http->isPartialReply = 1;
1116 if (http->state != PAYLOAD && http->state != DISCARD_PAYLOAD) {
1117 check(http->state == HEADERS);
1118 httpSetState(http, PAYLOAD);
1125 httpCloseRead(http);
1129 httpSendReply(http, 400, "Bad Request", NO_MSG);
1132 check(colon < lineLength);
1133 check(http->key = malloc(colon + 1));
1135 for (int j = 0; j < colon; j++) {
1136 ch = httpGetChar(http, buf, bytes, &i);
1137 if (ch >= 'A' && ch <= 'Z') {
1142 http->key[colon] = '\000';
1144 check(value = malloc(lineLength - colon));
1147 for (int k = 0; k < lineLength - colon - 1; j++, k++) {
1148 int ch = httpGetChar(http, buf, bytes, &i);
1149 if ((ch == ' ' || ch == '\t') && j == 0) {
1151 } else if (ch == '\r' && k == lineLength - colon - 2) {
1158 if (getRefFromHashMap(&http->header, http->key)) {
1159 debug("Dropping duplicate header \"%s\"", http->key);
1164 addToHashMap(&http->header, http->key, value);
1170 static int httpConsumePayload(struct HttpConnection *http, const char *buf,
1172 if (http->expecting >= 0) {
1173 // If positive, we know the expected length of payload and
1174 // can keep the connection open.
1175 // If negative, allow unlimited payload, but close connection
1177 if (len > http->expecting) {
1178 len = http->expecting;
1180 http->expecting -= len;
1182 if (http->callback) {
1184 int rc = http->callback(http, http->arg, buf, len);
1185 struct ServerConnection *connection = httpGetServerConnection(http);
1189 if (http->expecting < 0 || rc == HTTP_ERROR) {
1190 httpCloseRead(http);
1193 http->isSuspended = 0;
1194 http->isPartialReply = 0;
1195 if (!serverGetTimeout(connection)) {
1196 serverSetTimeout(connection, CONNECTION_TIMEOUT);
1198 httpSetState(http, http->expecting ? DISCARD_PAYLOAD : COMMAND);
1200 case HTTP_READ_MORE:
1201 http->isSuspended = 0;
1202 http->isPartialReply = 0;
1203 if (!serverGetTimeout(connection)) {
1204 serverSetTimeout(connection, CONNECTION_TIMEOUT);
1206 if (!http->expecting) {
1207 httpSetState(http, COMMAND);
1211 http->isSuspended = 1;
1212 http->isPartialReply = 0;
1213 serverSetTimeout(connection, 0);
1214 if (http->state != PAYLOAD && http->state != DISCARD_PAYLOAD) {
1215 check(http->state == HEADERS);
1216 httpSetState(http, PAYLOAD);
1219 case HTTP_PARTIAL_REPLY:
1220 http->isSuspended = 0;
1221 http->isPartialReply = 1;
1222 if (http->state != PAYLOAD && http->state != DISCARD_PAYLOAD) {
1223 check(http->state == HEADERS);
1224 httpSetState(http, PAYLOAD);
1231 // If we do not have a callback for handling the payload, and we also do
1232 // not know how long the payload is (because there was not Content-Length),
1233 // we now close the connection.
1234 if (http->expecting < 0) {
1235 http->expecting = 0;
1236 httpCloseRead(http);
1237 httpSetState(http, COMMAND);
1243 static int httpParsePayload(struct HttpConnection *http, int offset,
1244 const char *buf, int bytes) {
1247 check(-offset <= http->partialLength);
1248 if (http->expecting) {
1249 consumed = httpConsumePayload(http,
1250 http->partial + http->partialLength + offset,
1252 if (consumed == http->partialLength) {
1253 free(http->partial);
1254 http->partial = NULL;
1255 http->partialLength = 0;
1257 memmove(http->partial, http->partial + consumed,
1258 http->partialLength - consumed);
1259 http->partialLength -= consumed;
1264 if (http->expecting && bytes - offset > 0) {
1266 consumed += httpConsumePayload(http, buf + offset,
1272 static int httpHandleWebSocket(struct HttpConnection *http, int offset,
1273 const char *buf, int bytes) {
1274 check(http->websocketHandler);
1276 while (bytes > offset) {
1277 if (http->websocketType & WS_UNDEFINED) {
1278 ch = httpGetChar(http, buf, bytes, &offset);
1280 if (http->websocketType & 0xFF) {
1281 // Reading another byte of length information.
1282 if (http->expecting > 0xFFFFFF) {
1285 http->expecting = (128 * http->expecting) + (ch & 0x7F);
1286 if ((ch & 0x80) == 0) {
1287 // Done reading length information.
1288 http->websocketType &= ~WS_UNDEFINED;
1290 // ch is used to detect when we read the terminating byte in text
1291 // mode. In binary mode, it must be set to something other than 0xFF.
1295 // Reading first byte of frame.
1296 http->websocketType = (ch & 0xFF) | WS_START_OF_FRAME;
1298 // For binary data, we have to read the length before we can start
1299 // processing payload.
1300 http->websocketType |= WS_UNDEFINED;
1301 http->expecting = 0;
1304 } else if (http->websocketType & 0x80) {
1306 if (http->expecting) {
1309 check(-offset <= http->partialLength);
1311 if (len >= http->expecting) {
1312 len = http->expecting;
1313 http->websocketType |= WS_END_OF_FRAME;
1316 http->websocketHandler(http, http->arg, http->websocketType,
1317 http->partial + http->partialLength + offset,
1318 len) != HTTP_DONE) {
1323 // In text mode, we jump to handle_partial, when we find the
1324 // terminating 0xFF byte. If so, we should try to consume it now.
1325 if (len < http->partialLength) {
1327 http->websocketType = WS_UNDEFINED;
1331 if (len == http->partialLength) {
1332 free(http->partial);
1333 http->partial = NULL;
1334 http->partialLength = 0;
1336 memmove(http->partial, http->partial + len,
1337 http->partialLength - len);
1338 http->partialLength -= len;
1341 http->expecting -= len;
1344 int len = bytes - offset;
1345 if (len >= http->expecting) {
1346 len = http->expecting;
1347 http->websocketType |= WS_END_OF_FRAME;
1350 http->websocketHandler(http, http->arg, http->websocketType,
1351 buf + offset, len) != HTTP_DONE) {
1356 // In text mode, we jump to handle_buffered, when we find the
1357 // terminating 0xFF byte. If so, we should consume it now.
1358 check(offset + len < bytes);
1360 http->websocketType = WS_UNDEFINED;
1363 http->expecting -= len;
1365 http->websocketType &= ~(WS_START_OF_FRAME | WS_END_OF_FRAME);
1367 // Read all data. Go back to looking for a new frame header.
1368 http->websocketType = WS_UNDEFINED;
1371 // Process text data until we find a 0xFF bytes.
1374 // If we have partial data, process that first.
1376 ch = httpGetChar(http, buf, bytes, &i);
1379 // Terminate when we either find the 0xFF, or we have reached the end
1381 if (ch == 0xFF || !i) {
1382 // Set WS_END_OF_FRAME, iff we have found the 0xFF marker.
1383 http->expecting = i - offset - (ch == 0xFF);
1384 goto handle_partial;
1388 // Read all remaining buffered bytes (i.e. positive offset).
1390 ch = httpGetChar(http, buf, bytes, &i);
1393 // Terminate when we either find the 0xFF, or we have reached the end
1394 // of buffered data.
1395 if (ch == 0xFF || bytes == i) {
1396 // Set WS_END_OF_FRAME, iff we have found the 0xFF marker.
1397 http->expecting = i - offset - (ch == 0xFF);
1398 goto handle_buffered;
1406 int httpHandleConnection(struct ServerConnection *connection, void *http_,
1407 short *events, short revents) {
1408 struct HttpConnection *http = (struct HttpConnection *)http_;
1409 struct Trie *handlers = serverGetHttpHandlers(http->server);
1410 http->connection = connection;
1416 int eof = http->closed;
1417 if ((revents & POLLIN) && !http->closed) {
1418 bytes = httpRead(http, buf, sizeof(buf));
1420 http->headerLength += bytes;
1421 if (http->headerLength > MAX_HEADER_LENGTH) {
1422 httpSendReply(http, 413, "Header too big", NO_MSG);
1427 if (bytes == 0 || errno != EAGAIN) {
1428 httpCloseRead(http);
1431 if (http->sslHndl && http->lastError == SSL_ERROR_WANT_WRITE) {
1439 if (bytes > 0 && http->state == SNIFFING_SSL) {
1440 // Assume that all legitimate HTTP commands start with a sequence of
1441 // letters followed by a space character. If we don't see this pattern,
1442 // or if the method does not match one of the known methods, we try
1443 // switching to SSL, instead.
1445 char method[12] = { 0 };
1446 for (int i = -http->partialLength, j = 0, ch;
1447 (ch = httpGetChar(http, buf, bytes, &i)) != -1;
1449 if ((j > 0 && (ch == ' ' || ch == '\t')) ||
1450 ch == '\r' || ch == '\n') {
1451 isSSL = strcmp(method, "OPTIONS") &&
1452 strcmp(method, "GET") &&
1453 strcmp(method, "HEAD") &&
1454 strcmp(method, "POST") &&
1455 strcmp(method, "PUT") &&
1456 strcmp(method, "DELETE") &&
1457 strcmp(method, "TRACE") &&
1458 strcmp(method, "CONNECT");
1459 http->state = COMMAND;
1461 } else if (j >= (int)sizeof(method)-1 ||
1462 ch < 'A' || (ch > 'Z' && ch < 'a') || ch > 'z') {
1464 http->state = COMMAND;
1467 method[j] = ch & ~0x20;
1471 if (httpPromoteToSSL(http, buf, bytes) < 0) {
1472 httpCloseRead(http);
1476 http->headerLength = 0;
1483 if (bytes > 0 || (eof && http->partial)) {
1484 check(!!http->partial == !!http->partialLength);
1485 int offset = -http->partialLength;
1490 if (http->state == SNIFFING_SSL || http->state == COMMAND ||
1491 http->state == HEADERS) {
1492 check(!http->expecting);
1495 int firstSpace = -1;
1498 for (int i = offset; ; lineLength++) {
1499 int ch = httpGetChar(http, buf, bytes, &i);
1504 } else if (ch == ' ' || ch == '\t') {
1505 if (firstSpace < 0) {
1506 firstSpace = lineLength;
1508 lastSpace = lineLength;
1510 } else if (ch == '\n') {
1512 } else if (ch == -1) {
1518 if (fullLine || eof) {
1519 consumed = lineLength + 1;
1521 if (http->state == SNIFFING_SSL || http->state == COMMAND) {
1522 if (!httpParseCommand(http, offset, buf, bytes, firstSpace,
1523 lastSpace, lineLength)) {
1527 check(http->state == HEADERS);
1528 if (!httpParseHeaders(http, handlers, offset, buf, bytes,
1529 colon, lineLength)) {
1535 pushBack = lineLength;
1537 } else if (http->state == PAYLOAD ||
1538 http->state == DISCARD_PAYLOAD) {
1539 if (http->expecting) {
1540 int len = bytes - offset;
1541 if (http->expecting > 0 &&
1542 bytes > http->expecting) {
1543 len = http->expecting;
1545 if (http->state == PAYLOAD) {
1546 len = httpParsePayload(http, offset, buf,
1550 pushBack = bytes - offset - len;
1552 } else if (http->state == WEBSOCKET) {
1553 if (!httpHandleWebSocket(http, offset, buf, bytes)) {
1554 httpCloseRead(http);
1557 consumed += bytes - offset;
1563 check(offset + pushBack == bytes);
1565 check(http->partial = realloc(http->partial, pushBack));
1566 memcpy(http->partial, buf + offset, pushBack);
1567 } else if (pushBack != http->partialLength) {
1569 check(partial = malloc(pushBack));
1570 for (int i = offset, j = 0; j < pushBack; j++) {
1571 partial[j] = httpGetChar(http, buf, bytes, &i);
1573 free(http->partial);
1574 http->partial = partial;
1576 http->partialLength = pushBack;
1581 eob |= offset >= bytes;
1583 } while (!eob && !http->closed);
1584 if (http->closed || offset >= 0) {
1585 free(http->partial);
1586 http->partial = NULL;
1587 http->partialLength = 0;
1588 } else if (-offset != http->partialLength) {
1589 check(-offset < http->partialLength);
1590 memmove(http->partial, http->partial + http->partialLength + offset,
1592 http->partialLength = -offset;
1596 // If the peer closed the connection, clean up now.
1598 check(!http->partial);
1599 switch (http->state) {
1604 check(!http->expecting);
1605 http->callback = NULL;
1607 httpHandleCommand(http, handlers);
1608 httpCloseRead(http);
1609 httpSetState(http, COMMAND);
1612 case DISCARD_PAYLOAD:
1614 http->expecting = 0;
1615 httpCloseRead(http);
1616 httpSetState(http, COMMAND);
1622 // Try to write any pending outgoing data
1623 if (http->msg && http->msgLength > 0) {
1624 int wrote = httpWrite(http, http->msg,
1626 if (wrote < 0 && errno != EAGAIN) {
1627 httpCloseRead(http);
1629 http->msgLength = 0;
1632 } else if (wrote > 0) {
1633 if (wrote == http->msgLength) {
1635 http->msgLength = 0;
1638 memmove(http->msg, http->msg + wrote, http->msgLength - wrote);
1639 http->msgLength -= wrote;
1642 // SSL might require reading in order to write
1643 else if (wrote < 0 && errno == EAGAIN && http->sslHndl) {
1644 if (http->lastError == SSL_ERROR_WANT_READ && !http->closed) {
1650 // If the callback only provided partial data, refill the outgoing
1651 // buffer whenever it runs low.
1652 if (http->isPartialReply && (!http->msg || http->msgLength <= 0)) {
1653 httpConsumePayload(http, "", 0);
1660 (*events & ~(POLLIN|POLLOUT)) |
1661 (!http->closed && ((http->state != PAYLOAD &&
1662 http->state != DISCARD_PAYLOAD) ||
1663 http->expecting) ? POLLIN : 0) |
1664 (http->msg || http->isPartialReply ? POLLOUT : 0);
1666 connection = httpGetServerConnection(http);
1667 int timedOut = serverGetTimeout(connection) < 0;
1669 free(http->partial);
1670 http->partial = NULL;
1671 http->partialLength = 0;
1674 http->msgLength = 0;
1677 if ((!(*events || http->isSuspended) || timedOut) && http->sslHndl) {
1679 serverSetTimeout(connection, 1);
1680 int wasAlreadyClosed = http->closed;
1681 httpCloseRead(http);
1682 dcheck(!ERR_peek_error());
1684 int rc = SSL_shutdown(http->sslHndl);
1687 sslFreeHndl(&http->sslHndl);
1690 if (!wasAlreadyClosed) {
1695 switch (SSL_get_error(http->sslHndl, rc)) {
1696 case SSL_ERROR_WANT_READ:
1697 if (!wasAlreadyClosed) {
1701 case SSL_ERROR_WANT_WRITE:
1708 dcheck(!ERR_peek_error());
1709 if (sslUnblockSigPipe()) {
1711 sslFreeHndl(&http->sslHndl);
1713 } else if (!http->sslHndl && timedOut) {
1715 serverSetTimeout(connection, 0);
1716 httpCloseRead(http);
1718 revents = POLLIN | POLLOUT;
1719 } while (bytes > 0 && *events & POLLIN && !http->closed);
1720 return (*events & (POLLIN|POLLOUT)) ||
1721 (!http->closed && http->isSuspended);
1724 void httpSetCallback(struct HttpConnection *http,
1725 int (*callback)(struct HttpConnection *, void *,
1726 const char *, int), void *arg) {
1727 http->callback = callback;
1731 void *httpGetPrivate(struct HttpConnection *http) {
1732 return http->private;
1735 void *httpSetPrivate(struct HttpConnection *http, void *private) {
1736 void *old = http->private;
1737 http->private = private;
1741 void httpSendReply(struct HttpConnection *http, int code,
1742 const char *msg, const char *fmt, ...) {
1745 char *title = code != 200 ? stringPrintf(NULL, "%d %s", code, msg) : NULL;
1746 char *details = NULL;
1747 if (fmt != NULL && strcmp(fmt, NO_MSG)) {
1750 details = vStringPrintf(NULL, fmt, ap);
1753 body = stringPrintf(NULL,
1754 "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
1755 "<!DOCTYPE html PUBLIC "
1756 "\"-//W3C//DTD XHTML 1.0 Transitional//EN\" "
1757 "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n"
1758 "<html xmlns=\"http://www.w3.org/1999/xhtml\" "
1759 "xmlns:v=\"urn:schemas-microsoft-com:vml\" "
1760 "xml:lang=\"en\" lang=\"en\">\n"
1762 "<title>%s</title>\n"
1768 title ? title : msg, fmt && strcmp(fmt, NO_MSG) ? details : msg);
1771 char *response = NULL;
1773 response = stringPrintf(NULL,
1774 "HTTP/1.1 %d %s\r\n"
1776 "Content-Type: text/html; charset=utf-8\r\n"
1777 "Content-Length: %ld\r\n"
1780 code != 200 ? "Connection: close\r\n" : "",
1781 (long)strlen(body));
1783 int isHead = !strcmp(http->method, "HEAD");
1785 response = stringPrintf(response, "%s", body);
1788 httpTransfer(http, response, strlen(response));
1789 if (code != 200 || isHead) {
1790 httpCloseRead(http);
1794 void httpSendWebSocketTextMsg(struct HttpConnection *http, int type,
1795 const char *fmt, ...) {
1796 check(type >= 0 && type <= 0x7F);
1801 if (strcmp(fmt, BINARY_MSG)) {
1802 // Send a printf() style text message
1803 buf = vStringPrintf(NULL, fmt, ap);
1806 // Send a binary message
1807 len = va_arg(ap, int);
1808 buf = va_arg(ap, char *);
1811 check(len >= 0 && len < 0x60000000);
1813 // We assume that all input data is directly mapped in the range 0..255
1814 // (e.g. ISO-8859-1). In order to transparently send it over a web socket,
1815 // we have to encode it in UTF-8.
1816 int utf8Len = len + 2;
1817 for (int i = 0; i < len; ++i) {
1818 if (buf[i] & 0x80) {
1823 check(utf8 = malloc(utf8Len));
1825 for (int i = 0, j = 1; i < len; ++i) {
1826 unsigned char ch = buf[i];
1828 utf8[j++] = 0xC0 + (ch >> 6);
1829 utf8[j++] = 0x80 + (ch & 0x3F);
1835 utf8[utf8Len-1] = '\xFF';
1837 // Free our temporary buffer, if we actually did allocate one.
1838 if (strcmp(fmt, BINARY_MSG)) {
1843 httpTransfer(http, utf8, utf8Len);
1846 void httpSendWebSocketBinaryMsg(struct HttpConnection *http, int type,
1847 const void *buf, int len) {
1848 check(type >= 0x80 && type <= 0xFF);
1849 check(len > 0 && len < 0x7FFFFFF0);
1851 // Allocate buffer for header and payload.
1853 check(data = malloc(len + 6));
1856 // Convert length to base-128.
1860 data[++i] = 0x80 + (l & 0x7F);
1865 // Reverse digits, so that they are big-endian.
1866 for (int j = 0; j < i/2; ++j) {
1867 char ch = data[1+j];
1868 data[1+j] = data[i-j];
1872 // Transmit header and payload.
1873 memmove(data + i + 1, buf, len);
1874 httpTransfer(http, data, len + i + 1);
1877 void httpExitLoop(struct HttpConnection *http, int exitAll) {
1878 serverExitLoop(http->server, exitAll);
1881 struct Server *httpGetServer(const struct HttpConnection *http) {
1882 return http->server;
1885 struct ServerConnection *httpGetServerConnection(const struct HttpConnection *
1887 struct HttpConnection *httpW = (struct HttpConnection *)http;
1888 httpW->connection = serverGetConnection(http->server, http->connection,
1890 return http->connection;
1893 int httpGetFd(const HttpConnection *http) {
1897 const char *httpGetPeerName(const struct HttpConnection *http) {
1898 return http->peerName;
1901 const char *httpGetMethod(const struct HttpConnection *http) {
1902 return http->method;
1905 const char *httpGetProtocol(const struct HttpConnection *http) {
1906 return http->sslHndl ? "https" : "http";
1909 const char *httpGetHost(const struct HttpConnection *http) {
1910 const char *host = getFromHashMap(&http->header, "host");
1911 if (!host || !*host) {
1917 int httpGetPort(const struct HttpConnection *http) {
1921 const char *httpGetPath(const struct HttpConnection *http) {
1922 return http->matchedPath;
1925 const char *httpGetPathInfo(const struct HttpConnection *http) {
1926 return http->pathInfo ? http->pathInfo : "";
1929 const char *httpGetQuery(const struct HttpConnection *http) {
1930 return http->query ? http->query : "";
1933 const char *httpGetURL(const struct HttpConnection *http) {
1935 const char *host = httpGetHost(http);
1936 int s_size = 8 + strlen(host) + 25 + strlen(http->path);
1937 check(*(char **)&http->url = malloc(s_size + 1));
1938 *http->url = '\000';
1939 strncat(http->url, http->sslHndl ? "https://" : "http://", s_size);
1940 strncat(http->url, host, s_size);
1941 if (http->port != (http->sslHndl ? 443 : 80)) {
1942 snprintf(strrchr(http->url, '\000'), 25, ":%d", http->port);
1944 strncat(http->url, http->path, s_size);
1949 const char *httpGetVersion(const struct HttpConnection *http) {
1950 return http->version;
1953 const struct HashMap *httpGetHeaders(const struct HttpConnection *http) {
1954 return &http->header;