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 #defined ATTR_UNUSED __attribute__((unused))
76 #defined UNUSED(x) do { } while (0)
79 #define UNUSED(x) do { (void)(x); } while (0)
82 #include "libhttp/httpconnection.h"
83 #include "logging/logging.h"
85 #define MAX_HEADER_LENGTH (64<<10)
86 #define CONNECTION_TIMEOUT (10*60)
88 static int httpPromoteToSSL(struct HttpConnection *http, const char *buf,
90 if (http->ssl->enabled && !http->sslHndl) {
91 debug("Switching to SSL (replaying %d+%d bytes)",
92 http->partialLength, len);
93 if (http->partial && len > 0) {
94 check(http->partial = realloc(http->partial,
95 http->partialLength + len));
96 memcpy(http->partial + http->partialLength, buf, len);
97 http->partialLength += len;
99 int rc = sslPromoteToSSL(
100 http->ssl, &http->sslHndl, http->fd,
101 http->partial ? http->partial : buf,
102 http->partial ? http->partialLength : len);
105 SSL_set_app_data(http->sslHndl, http);
108 http->partialLength = 0;
116 static ssize_t httpRead(struct HttpConnection *http, char *buf, ssize_t len) {
120 dcheck(!ERR_peek_error());
121 rc = SSL_read(http->sslHndl, buf, len);
125 switch (http->lastError = SSL_get_error(http->sslHndl, rc)) {
126 case SSL_ERROR_WANT_READ:
127 case SSL_ERROR_WANT_WRITE:
140 dcheck(!ERR_peek_error());
142 rc = NOINTR(read(http->fd, buf, len));
146 serverSetTimeout(httpGetServerConnection(http), CONNECTION_TIMEOUT);
151 static ssize_t httpWrite(struct HttpConnection *http, const char *buf,
156 dcheck(!ERR_peek_error());
157 rc = SSL_write(http->sslHndl, buf, len);
161 switch (http->lastError = SSL_get_error(http->sslHndl, rc)) {
162 case SSL_ERROR_WANT_READ:
163 case SSL_ERROR_WANT_WRITE:
176 dcheck(!ERR_peek_error());
178 rc = NOINTR(write(http->fd, buf, len));
184 static int httpShutdown(struct HttpConnection *http, int how) {
187 if (how != SHUT_RD) {
188 dcheck(!ERR_peek_error());
189 for (int i = 0; i < 10; i++) {
191 rc = SSL_shutdown(http->sslHndl);
192 int sPipe = sslUnblockSigPipe();
198 // Retry a few times in order to prefer a clean bidirectional
199 // shutdown. But don't bother if the other side already closed
206 sslFreeHndl(&http->sslHndl);
209 return shutdown(http->fd, how);
212 static void httpCloseRead(struct HttpConnection *http) {
214 httpShutdown(http, SHUT_RD);
219 #ifndef HAVE_STRCASESTR
220 static char *strcasestr(const char *haystack, const char *needle) {
221 // This algorithm is O(len(haystack)*len(needle)). Much better algorithms
222 // are available, but this code is much simpler and performance is not
223 // critical for our workloads.
224 int len = strlen(needle);
226 if (!strncasecmp(haystack, needle, len)) {
229 } while (*haystack++);
234 static int httpFinishCommand(struct HttpConnection *http) {
236 if ((http->callback || http->websocketHandler) && !http->done) {
237 rc = http->callback ? http->callback(http, http->arg, NULL, 0)
238 : http->websocketHandler(http, http->arg, WS_CONNECTION_CLOSED, NULL,0);
239 check(rc != HTTP_SUSPEND);
240 check(rc != HTTP_PARTIAL_REPLY);
241 http->callback = NULL;
243 if (rc == HTTP_ERROR) {
248 const char *con = getFromHashMap(&http->header, "connection");
249 if ((con && strcasestr(con, "close")) ||
250 !http->version || strcmp(http->version, "HTTP/1.1") < 0) {
257 check(http->version);
258 if (http->peerName) {
259 time_t t = currentTime;
261 check (ltime = localtime(&t));
264 check(strftime(timeBuf, sizeof(timeBuf),
265 "[%d/%b/%Y:%H:%M:%S %z]", ltime));
266 if (http->totalWritten > 0) {
267 snprintf(lengthBuf, sizeof(lengthBuf), "%d", http->totalWritten);
270 strncat(lengthBuf, "-", sizeof(lengthBuf)-1);
272 info("%s - - %s \"%s %s %s\" %d %s",
273 http->peerName, timeBuf, http->method, http->path, http->version,
274 http->code, lengthBuf);
280 static void httpDestroyHeaders(void *arg ATTR_UNUSED, char *key, char *value) {
286 static char *getPeerName(int fd, int *port, int numericHosts) {
287 struct sockaddr peerAddr;
288 socklen_t sockLen = sizeof(peerAddr);
289 if (getpeername(fd, &peerAddr, &sockLen)) {
297 getnameinfo(&peerAddr, sockLen, host, sizeof(host), NULL, 0, NI_NOFQDN)){
298 check(inet_ntop(peerAddr.sa_family,
299 &((struct sockaddr_in *)&peerAddr)->sin_addr,
300 host, sizeof(host)));
303 *port = ntohs(((struct sockaddr_in *)&peerAddr)->sin_port);
306 check(ret = strdup(host));
310 static void httpSetState(struct HttpConnection *http, int state) {
311 if (state == (int)http->state) {
315 if (state == COMMAND) {
316 if (http->state != SNIFFING_SSL) {
317 int rc = httpFinishCommand(http);
318 check(rc != HTTP_SUSPEND);
319 check(rc != HTTP_PARTIAL_REPLY);
321 check(!http->private);
325 free(http->matchedPath);
326 free(http->pathInfo);
333 http->matchedPath = NULL;
334 http->pathInfo = NULL;
336 http->version = NULL;
337 destroyHashMap(&http->header);
338 initHashMap(&http->header, httpDestroyHeaders, NULL);
339 http->headerLength = 0;
340 http->callback = NULL;
342 http->totalWritten = 0;
348 struct HttpConnection *newHttpConnection(struct Server *server, int fd,
349 int port, struct SSLSupport *ssl,
351 struct HttpConnection *http;
352 check(http = malloc(sizeof(struct HttpConnection)));
353 initHttpConnection(http, server, fd, port, ssl, numericHosts);
357 void initHttpConnection(struct HttpConnection *http, struct Server *server,
358 int fd, int port, struct SSLSupport *ssl,
360 http->server = server;
361 http->connection = NULL;
365 http->isSuspended = 0;
366 http->isPartialReply = 0;
368 http->state = ssl ? SNIFFING_SSL : COMMAND;
369 http->peerName = getPeerName(fd, &http->peerPort, numericHosts);
373 http->matchedPath = NULL;
374 http->pathInfo = NULL;
376 http->version = NULL;
377 initHashMap(&http->header, httpDestroyHeaders, NULL);
378 http->headerLength = 0;
380 http->partial = NULL;
381 http->partialLength = 0;
385 http->totalWritten = 0;
387 http->websocketType = WS_UNDEFINED;
388 http->callback = NULL;
389 http->websocketHandler = NULL;
391 http->private = NULL;
394 http->sslHndl = NULL;
397 debug("Accepted connection from %s:%d",
398 http->peerName ? http->peerName : "???", http->peerPort);
402 void destroyHttpConnection(struct HttpConnection *http) {
404 if (http->isSuspended || http->isPartialReply) {
406 if (http->callback) {
407 http->callback(http, http->arg, NULL, 0);
408 } else if (http->websocketHandler) {
409 http->websocketHandler(http, http->arg, WS_CONNECTION_CLOSED,NULL,0);
412 http->callback = NULL;
413 http->isSuspended = 0;
414 http->isPartialReply = 0;
416 httpSetState(http, COMMAND);
418 debug("Closing connection to %s:%d",
419 http->peerName ? http->peerName : "???", http->peerPort);
421 httpShutdown(http, http->closed ? SHUT_WR : SHUT_RDWR);
422 dcheck(!close(http->fd));
423 free(http->peerName);
427 free(http->matchedPath);
428 free(http->pathInfo);
431 destroyHashMap(&http->header);
437 void deleteHttpConnection(struct HttpConnection *http) {
438 destroyHttpConnection(http);
443 static int httpAcceptsEncoding(struct HttpConnection *http,
444 const char *encoding) {
445 int encodingLength = strlen(encoding);
446 const char *accepts = getFromHashMap(&http->header, "accept-encoding");
453 while (*accepts == ' ' || *accepts == '\t' ||
454 *accepts == '\r' || *accepts == '\n') {
457 const char *ptr = accepts;
458 while (*ptr && *ptr != ',' && *ptr != ';' &&
459 *ptr != ' ' && *ptr != '\t' &&
460 *ptr != '\r' && *ptr != '\n') {
463 int isAll = ptr - accepts == 1 && *accepts == '*';
464 int isMatch = ptr - accepts == encodingLength &&
465 !strncasecmp(accepts, encoding, encodingLength);
466 while (*ptr && *ptr != ';' && *ptr != ',') {
472 while (*ptr == ' ' || *ptr == '\t' || *ptr == '\r' || *ptr == '\n') {
475 if ((*ptr | 0x20) == 'q') {
477 while (*ptr == ' ' || *ptr == '\t' || *ptr == '\r' || *ptr == '\n') {
481 val = strtod(ptr + 1, (char **)&ptr);
485 if (isnan(val) || val == -HUGE_VAL || val < 0) {
487 } else if (val == HUGE_VAL || val > 1.0) {
492 } else if (isMatch) {
495 while (*ptr && *ptr != ',') {
498 while (*ptr == ',') {
511 static void removeHeader(char *header, int *headerLength, const char *id) {
514 check(*headerLength >= 0);
516 check(strchr(id, ':'));
517 int idLength = strlen(id);
521 for (char *ptr = header; header + *headerLength - ptr >= idLength; ) {
524 end = memchr(end, '\n', header + *headerLength - end);
526 end = header + *headerLength;
530 } while (end < header + *headerLength && *end == ' ');
531 if (!strncasecmp(ptr, id, idLength)) {
532 memmove(ptr, end, header + *headerLength - end);
533 *headerLength -= end - ptr;
540 static void addHeader(char **header, int *headerLength, const char *fmt, ...) {
543 check(*headerLength >= 0);
544 check(strstr(fmt, "\r\n"));
548 char *tmp = vStringPrintf(NULL, fmt, ap);
550 int tmpLength = strlen(tmp);
552 if (*headerLength >= 2 && !memcmp(*header + *headerLength - 2, "\r\n", 2)) {
555 check(*header = realloc(*header, *headerLength + tmpLength + 2));
557 memcpy(*header + *headerLength, tmp, tmpLength);
558 memcpy(*header + *headerLength + tmpLength, "\r\n", 2);
559 *headerLength += tmpLength + 2;
563 void httpTransfer(struct HttpConnection *http, char *msg, int len) {
567 // Internet Explorer seems to have difficulties with compressed data. It
568 // also has difficulties with SSL connections that are being proxied.
570 const char *userAgent = getFromHashMap(&http->header, "user-agent");
571 const char *msie = userAgent ? strstr(userAgent, "MSIE ") : NULL;
577 int headerLength = 0;
581 if (!http->totalWritten) {
582 // Perform some basic sanity checks. This does not necessarily catch all
583 // possible problems, though.
586 for (char *eol, *lastLine = NULL;
587 l > 0 && (eol = memchr(line, '\n', l)) != NULL; ) {
588 // All lines end in CR LF
589 check(eol[-1] == '\r');
591 // The first line looks like "HTTP/1.x STATUS\r\n"
592 check(eol - line > 11);
593 check(!memcmp(line, "HTTP/1.", 7));
594 check(line[7] >= '0' && line[7] <= '9' &&
595 (line[8] == ' ' || line[8] == '\t'));
596 int i = eol - line - 9;
597 for (char *ptr = line + 9; i-- > 0; ) {
599 if (ch < '0' || ch > '9') {
600 check(ptr > line + 10);
601 check(ch == ' ' || ch == '\t');
606 } else if (line + 1 == eol) {
607 // Found the end of the headers.
609 // Check that we don't send any data with HEAD requests
610 int isHead = !strcmp(http->method, "HEAD");
611 check(l == 2 || !isHead);
614 // Compress replies that might exceed the size of a single IP packet
615 compress = !ieBug && !isHead &&
616 !http->isPartialReply &&
618 httpAcceptsEncoding(http, "deflate");
622 // Header lines either contain a colon, or they are continuation
624 if (*line != ' ' && *line != '\t') {
625 check(memchr(line, ':', eol - line));
633 if (ieBug || compress) {
634 if (l >= 2 && !memcmp(line, "\r\n", 2)) {
638 headerLength = line - msg;
639 bodyOffset = headerLength;
640 check(header = malloc(headerLength));
641 memcpy(header, msg, headerLength);
644 removeHeader(header, &headerLength, "connection:");
645 addHeader(&header, &headerLength, "Connection: close\r\n");
650 // Compress the message
652 check(compressed = malloc(len));
653 check(len >= bodyOffset + 2);
654 z_stream strm = { .zalloc = Z_NULL,
658 .next_in = (unsigned char *)line,
660 .next_out = (unsigned char *)compressed
662 if (deflateInit(&strm, Z_DEFAULT_COMPRESSION) == Z_OK) {
663 if (deflate(&strm, Z_FINISH) == Z_STREAM_END) {
664 // Compression was successful and resulted in reduction in size
665 debug("Compressed response from %d to %d", len, len-strm.avail_out);
668 len -= strm.avail_out;
670 removeHeader(header, &headerLength, "content-length:");
671 removeHeader(header, &headerLength, "content-encoding:");
672 addHeader(&header, &headerLength, "Content-Length: %d\r\n", len);
673 addHeader(&header, &headerLength, "Content-Encoding: deflate\r\n");
685 http->totalWritten += headerLength + (len - bodyOffset);
688 } else if (http->msg) {
689 check(http->msg = realloc(http->msg,
690 http->msgLength - http->msgOffset +
691 max(http->msgOffset, headerLength)));
692 if (http->msgOffset) {
693 memmove(http->msg, http->msg + http->msgOffset,
694 http->msgLength - http->msgOffset);
695 http->msgLength -= http->msgOffset;
698 memcpy(http->msg + http->msgLength, header, headerLength);
699 http->msgLength += headerLength;
702 check(!http->msgOffset);
704 http->msgLength = headerLength;
707 if (len <= bodyOffset) {
709 } else if (http->msg) {
710 check(http->msg = realloc(http->msg,
711 http->msgLength - http->msgOffset +
712 max(http->msgOffset, len - bodyOffset)));
713 if (http->msgOffset) {
714 memmove(http->msg, http->msg + http->msgOffset,
715 http->msgLength - http->msgOffset);
716 http->msgLength -= http->msgOffset;
719 memcpy(http->msg + http->msgLength, msg + bodyOffset, len - bodyOffset);
720 http->msgLength += len - bodyOffset;
723 check(!http->msgOffset);
725 memmove(msg, msg + bodyOffset, len - bodyOffset);
728 http->msgLength = len - bodyOffset;
731 // The caller can suspend the connection, so that it can send an
732 // asynchronous reply. Once the reply has been sent, the connection
733 // gets reactivated. Normally, this means it would go back to listening
735 // Similarly, the caller can indicate that this is a partial message and
736 // return additional data in subsequent calls to the callback handler.
737 if (http->isSuspended || http->isPartialReply) {
738 if (http->msg && http->msgLength > 0) {
739 int wrote = httpWrite(http, http->msg, http->msgLength);
740 if (wrote < 0 && errno != EAGAIN) {
745 } else if (wrote > 0) {
746 if (wrote == http->msgLength) {
751 memmove(http->msg, http->msg + wrote, http->msgLength - wrote);
752 http->msgLength -= wrote;
757 check(http->state == PAYLOAD || http->state == DISCARD_PAYLOAD);
758 if (!http->isPartialReply) {
759 if (http->expecting < 0) {
760 // If we do not know the length of the content, close the connection.
761 debug("Closing previously suspended connection");
763 httpSetState(http, DISCARD_PAYLOAD);
764 } else if (http->expecting == 0) {
765 httpSetState(http, COMMAND);
766 http->isSuspended = 0;
767 struct ServerConnection *connection = httpGetServerConnection(http);
768 if (!serverGetTimeout(connection)) {
769 serverSetTimeout(connection, CONNECTION_TIMEOUT);
771 serverConnectionSetEvents(http->server, connection, http->fd,
772 http->msgLength ? POLLIN|POLLOUT : POLLIN);
782 void httpTransferPartialReply(struct HttpConnection *http, char *msg, int len){
783 check(!http->isSuspended);
784 http->isPartialReply = 1;
785 if (http->state != PAYLOAD && http->state != DISCARD_PAYLOAD) {
786 check(http->state == HEADERS);
787 httpSetState(http, PAYLOAD);
789 httpTransfer(http, msg, len);
792 static int httpHandleCommand(struct HttpConnection *http,
793 const struct Trie *handlers) {
794 debug("Handling \"%s\" \"%s\"", http->method, http->path);
795 const char *contentLength = getFromHashMap(&http->header,
797 if (contentLength != NULL && *contentLength) {
799 http->expecting = strtol(contentLength,
802 // Invalid length. Read until end of stream and then close
804 http->expecting = -1;
807 // Unknown length. Read until end of stream and then close
809 http->expecting = -1;
811 if (!strcmp(http->method, "OPTIONS")) {
812 char *response = stringPrintf(NULL,
813 "HTTP/1.1 200 OK\r\n"
814 "Content-Length: 0\r\n"
815 "Allow: GET, POST, OPTIONS\r\n"
817 httpTransfer(http, response, strlen(response));
818 if (http->expecting < 0) {
821 return HTTP_READ_MORE;
822 } else if (!strcmp(http->method, "GET")) {
823 if (http->expecting < 0) {
826 } else if (!strcmp(http->method, "POST")) {
827 } else if (!strcmp(http->method, "HEAD")) {
828 if (http->expecting < 0) {
831 } else if (!strcmp(http->method, "PUT") ||
832 !strcmp(http->method, "DELETE") ||
833 !strcmp(http->method, "TRACE") ||
834 !strcmp(http->method, "CONNECT")) {
835 httpSendReply(http, 405, "Method Not Allowed", NO_MSG);
838 httpSendReply(http, 501, "Method Not Implemented", NO_MSG);
841 const char *host = getFromHashMap(&http->header,
844 for (char ch, *ptr = (char *)host; (ch = *ptr) != '\000'; ptr++) {
849 if (ch != '-' && ch != '.' &&
850 (ch < '0' ||(ch > '9' && ch < 'A') ||
851 (ch > 'Z' && ch < 'a')||(ch > 'z' && ch <= 0x7E))) {
852 httpSendReply(http, 400, "Bad Request", NO_MSG);
859 struct HttpHandler *h = (struct HttpHandler *)getFromTrie(handlers,
863 if (h->websocketHandler) {
864 // Check for WebSocket handshake
865 const char *upgrade = getFromHashMap(&http->header,
867 if (upgrade && !strcmp(upgrade, "WebSocket")) {
868 const char *connection = getFromHashMap(&http->header,
870 if (connection && !strcmp(connection, "Upgrade")) {
871 const char *origin = getFromHashMap(&http->header,
874 for (const char *ptr = origin; *ptr; ptr++) {
875 if ((unsigned char)*ptr < ' ') {
880 const char *protocol = getFromHashMap(&http->header,
881 "websocket-protocol");
883 for (const char *ptr = protocol; *ptr; ptr++) {
884 if ((unsigned char)*ptr < ' ') {
890 if (http->port != (http->sslHndl ? 443 : 80)) {
891 port = stringPrintf(NULL,
894 char *response = stringPrintf(NULL,
895 "HTTP/1.1 101 Web Socket Protocol Handshake\r\n"
896 "Upgrade: WebSocket\r\n"
897 "Connection: Upgrade\r\n"
898 "WebSocket-Origin: %s\r\n"
899 "WebSocket-Location: %s://%s%s%s\r\n"
903 http->sslHndl ? "wss" : "ws", host && *host ? host : "localhost",
904 port ? port : "", http->path,
905 protocol ? "WebSocket-Protocol: " : "",
906 protocol ? protocol : "",
907 protocol ? "\r\n" : "");
909 debug("Switching to WebSockets");
910 httpTransfer(http, response, strlen(response));
911 if (http->expecting < 0) {
914 http->websocketHandler = h->websocketHandler;
915 httpSetState(http, WEBSOCKET);
916 return HTTP_READ_MORE;
925 while (diff > http->path && diff[-1] == '/') {
928 if (!*diff || *diff == '/' || *diff == '?' || *diff == '#') {
929 check(!http->matchedPath);
930 check(!http->pathInfo);
933 check(http->matchedPath = malloc(diff - http->path + 1));
934 memcpy(http->matchedPath, http->path, diff - http->path);
935 http->matchedPath[diff - http->path] = '\000';
937 const char *query = strchr(diff, '?');
938 if (*diff && *diff != '?') {
939 const char *endOfInfo = query
940 ? query : strrchr(diff, '\000');
941 check(http->pathInfo = malloc(endOfInfo - diff + 1));
942 memcpy(http->pathInfo, diff, endOfInfo - diff);
943 http->pathInfo[endOfInfo - diff] = '\000';
947 check(http->query = strdup(query + 1));
949 return h->handler(http, h->arg);
953 httpSendReply(http, 404, "File Not Found", NO_MSG);
957 static int httpGetChar(struct HttpConnection *http, const char *buf,
958 int size, int *offset) {
960 return (unsigned char)http->partial[http->partialLength + (*offset)++];
961 } else if (*offset < size) {
962 return (unsigned char)buf[(*offset)++];
968 static int httpParseCommand(struct HttpConnection *http, int offset,
969 const char *buf, int bytes, int firstSpace,
970 int lastSpace, int lineLength) {
971 if (firstSpace < 1 || lastSpace < 0) {
974 check(http->method = strdup(""));
977 check(http->path = strdup(""));
979 if (!http->version) {
980 check(http->version = strdup(""));
982 httpSendReply(http, 400, "Bad Request", NO_MSG);
983 httpSetState(http, COMMAND);
986 check(!http->method);
987 check(http->method = malloc(firstSpace + 1));
990 for (; j < firstSpace; j++) {
991 int ch = httpGetChar(http, buf, bytes, &i);
992 if (ch >= 'a' && ch <= 'z') {
995 http->method[j] = ch;
997 http->method[j] = '\000';
999 check(http->path = malloc(lastSpace - firstSpace));
1001 while (i < offset + lastSpace) {
1002 int ch = httpGetChar(http, buf, bytes, &i);
1003 if ((ch != ' ' && ch != '\t') || j) {
1004 http->path[j++] = ch;
1007 http->path[j] = '\000';
1008 if (*http->path != '/' &&
1009 (strcmp(http->method, "OPTIONS") || strcmp(http->path, "*"))) {
1012 check(!http->version);
1013 check(http->version = malloc(lineLength - lastSpace + 1));
1015 while (i < offset + lineLength) {
1016 int ch = httpGetChar(http, buf, bytes, &i);
1020 if (ch >= 'a' && ch <= 'z') {
1023 if ((ch != ' ' && ch != '\t') || j) {
1024 http->version[j] = ch;
1028 http->version[j] = '\000';
1029 if (memcmp(http->version, "HTTP/", 5) ||
1030 (http->version[5] < '1' || http->version[5] > '9')) {
1033 httpSetState(http, HEADERS);
1037 static int httpParseHeaders(struct HttpConnection *http,
1038 const struct Trie *handlers, int offset,
1039 const char *buf, int bytes, int colon,
1042 int ch = httpGetChar(http, buf, bytes, &i);
1043 if (ch == ' ' || ch == '\t') {
1045 char **oldValue = getRefFromHashMap(&http->header, http->key);
1047 int oldLength = strlen(*oldValue);
1048 check(*oldValue = realloc(*oldValue,
1049 oldLength + lineLength + 1));
1051 int end = oldLength + lineLength;
1052 (*oldValue)[j++] = ' ';
1053 for (; j < end; j++) {
1054 ch = httpGetChar(http, buf, bytes, &i);
1055 if (ch == ' ' || ch == '\t') {
1059 } else if (ch == '\r' && j == end - 1) {
1062 (*oldValue)[j] = ch;
1064 (*oldValue)[j] = '\000';
1066 } else if ((ch == '\r' &&
1067 httpGetChar(http, buf, bytes, &i) == '\n') ||
1068 ch == '\n' || ch == -1) {
1069 check(!http->expecting);
1070 http->callback = NULL;
1072 int rc = httpHandleCommand(http, handlers);
1074 struct ServerConnection *connection = httpGetServerConnection(http);
1078 if (http->expecting < 0 || rc == HTTP_ERROR) {
1079 httpCloseRead(http);
1082 http->isSuspended = 0;
1083 http->isPartialReply = 0;
1084 if (!serverGetTimeout(connection)) {
1085 serverSetTimeout(connection, CONNECTION_TIMEOUT);
1087 httpSetState(http, http->expecting ? DISCARD_PAYLOAD : COMMAND);
1089 case HTTP_READ_MORE:
1090 http->isSuspended = 0;
1091 http->isPartialReply = 0;
1092 if (!serverGetTimeout(connection)) {
1093 serverSetTimeout(connection, CONNECTION_TIMEOUT);
1096 if (!http->expecting) {
1097 if (http->callback) {
1098 rc = http->callback(http, http->arg, "", 0);
1099 if (rc != HTTP_READ_MORE) {
1102 } else if (http->websocketHandler) {
1103 http->websocketHandler(http, http->arg, WS_CONNECTION_OPENED,
1107 if (http->state != WEBSOCKET) {
1108 httpSetState(http, http->expecting ? PAYLOAD : COMMAND);
1112 http->isSuspended = 1;
1113 http->isPartialReply = 0;
1114 serverSetTimeout(connection, 0);
1115 if (http->state != PAYLOAD && http->state != DISCARD_PAYLOAD) {
1116 check(http->state == HEADERS);
1117 httpSetState(http, PAYLOAD);
1120 case HTTP_PARTIAL_REPLY:
1121 http->isSuspended = 0;
1122 http->isPartialReply = 1;
1123 if (http->state != PAYLOAD && http->state != DISCARD_PAYLOAD) {
1124 check(http->state == HEADERS);
1125 httpSetState(http, PAYLOAD);
1132 httpCloseRead(http);
1136 httpSendReply(http, 400, "Bad Request", NO_MSG);
1139 check(colon < lineLength);
1140 check(http->key = malloc(colon + 1));
1142 for (int j = 0; j < colon; j++) {
1143 ch = httpGetChar(http, buf, bytes, &i);
1144 if (ch >= 'A' && ch <= 'Z') {
1149 http->key[colon] = '\000';
1151 check(value = malloc(lineLength - colon));
1154 for (int k = 0; k < lineLength - colon - 1; j++, k++) {
1155 int ch = httpGetChar(http, buf, bytes, &i);
1156 if ((ch == ' ' || ch == '\t') && j == 0) {
1158 } else if (ch == '\r' && k == lineLength - colon - 2) {
1165 if (getRefFromHashMap(&http->header, http->key)) {
1166 debug("Dropping duplicate header \"%s\"", http->key);
1171 addToHashMap(&http->header, http->key, value);
1177 static int httpConsumePayload(struct HttpConnection *http, const char *buf,
1179 if (http->expecting >= 0) {
1180 // If positive, we know the expected length of payload and
1181 // can keep the connection open.
1182 // If negative, allow unlimited payload, but close connection
1184 if (len > http->expecting) {
1185 len = http->expecting;
1187 http->expecting -= len;
1189 if (http->callback) {
1191 int rc = http->callback(http, http->arg, buf, len);
1192 struct ServerConnection *connection = httpGetServerConnection(http);
1196 if (http->expecting < 0 || rc == HTTP_ERROR) {
1197 httpCloseRead(http);
1200 http->isSuspended = 0;
1201 http->isPartialReply = 0;
1202 if (!serverGetTimeout(connection)) {
1203 serverSetTimeout(connection, CONNECTION_TIMEOUT);
1205 httpSetState(http, http->expecting ? DISCARD_PAYLOAD : COMMAND);
1207 case HTTP_READ_MORE:
1208 http->isSuspended = 0;
1209 http->isPartialReply = 0;
1210 if (!serverGetTimeout(connection)) {
1211 serverSetTimeout(connection, CONNECTION_TIMEOUT);
1213 if (!http->expecting) {
1214 httpSetState(http, COMMAND);
1218 http->isSuspended = 1;
1219 http->isPartialReply = 0;
1220 serverSetTimeout(connection, 0);
1221 if (http->state != PAYLOAD && http->state != DISCARD_PAYLOAD) {
1222 check(http->state == HEADERS);
1223 httpSetState(http, PAYLOAD);
1226 case HTTP_PARTIAL_REPLY:
1227 http->isSuspended = 0;
1228 http->isPartialReply = 1;
1229 if (http->state != PAYLOAD && http->state != DISCARD_PAYLOAD) {
1230 check(http->state == HEADERS);
1231 httpSetState(http, PAYLOAD);
1238 // If we do not have a callback for handling the payload, and we also do
1239 // not know how long the payload is (because there was not Content-Length),
1240 // we now close the connection.
1241 if (http->expecting < 0) {
1242 http->expecting = 0;
1243 httpCloseRead(http);
1244 httpSetState(http, COMMAND);
1250 static int httpParsePayload(struct HttpConnection *http, int offset,
1251 const char *buf, int bytes) {
1254 check(-offset <= http->partialLength);
1255 if (http->expecting) {
1256 consumed = httpConsumePayload(http,
1257 http->partial + http->partialLength + offset,
1259 if (consumed == http->partialLength) {
1260 free(http->partial);
1261 http->partial = NULL;
1262 http->partialLength = 0;
1264 memmove(http->partial, http->partial + consumed,
1265 http->partialLength - consumed);
1266 http->partialLength -= consumed;
1271 if (http->expecting && bytes - offset > 0) {
1273 consumed += httpConsumePayload(http, buf + offset,
1279 static int httpHandleWebSocket(struct HttpConnection *http, int offset,
1280 const char *buf, int bytes) {
1281 check(http->websocketHandler);
1283 while (bytes > offset) {
1284 if (http->websocketType & WS_UNDEFINED) {
1285 ch = httpGetChar(http, buf, bytes, &offset);
1287 if (http->websocketType & 0xFF) {
1288 // Reading another byte of length information.
1289 if (http->expecting > 0xFFFFFF) {
1292 http->expecting = (128 * http->expecting) + (ch & 0x7F);
1293 if ((ch & 0x80) == 0) {
1294 // Done reading length information.
1295 http->websocketType &= ~WS_UNDEFINED;
1297 // ch is used to detect when we read the terminating byte in text
1298 // mode. In binary mode, it must be set to something other than 0xFF.
1302 // Reading first byte of frame.
1303 http->websocketType = (ch & 0xFF) | WS_START_OF_FRAME;
1305 // For binary data, we have to read the length before we can start
1306 // processing payload.
1307 http->websocketType |= WS_UNDEFINED;
1308 http->expecting = 0;
1311 } else if (http->websocketType & 0x80) {
1313 if (http->expecting) {
1316 check(-offset <= http->partialLength);
1318 if (len >= http->expecting) {
1319 len = http->expecting;
1320 http->websocketType |= WS_END_OF_FRAME;
1323 http->websocketHandler(http, http->arg, http->websocketType,
1324 http->partial + http->partialLength + offset,
1325 len) != HTTP_DONE) {
1330 // In text mode, we jump to handle_partial, when we find the
1331 // terminating 0xFF byte. If so, we should try to consume it now.
1332 if (len < http->partialLength) {
1334 http->websocketType = WS_UNDEFINED;
1338 if (len == http->partialLength) {
1339 free(http->partial);
1340 http->partial = NULL;
1341 http->partialLength = 0;
1343 memmove(http->partial, http->partial + len,
1344 http->partialLength - len);
1345 http->partialLength -= len;
1348 http->expecting -= len;
1351 int len = bytes - offset;
1352 if (len >= http->expecting) {
1353 len = http->expecting;
1354 http->websocketType |= WS_END_OF_FRAME;
1357 http->websocketHandler(http, http->arg, http->websocketType,
1358 buf + offset, len) != HTTP_DONE) {
1363 // In text mode, we jump to handle_buffered, when we find the
1364 // terminating 0xFF byte. If so, we should consume it now.
1365 check(offset + len < bytes);
1367 http->websocketType = WS_UNDEFINED;
1370 http->expecting -= len;
1372 http->websocketType &= ~(WS_START_OF_FRAME | WS_END_OF_FRAME);
1374 // Read all data. Go back to looking for a new frame header.
1375 http->websocketType = WS_UNDEFINED;
1378 // Process text data until we find a 0xFF bytes.
1381 // If we have partial data, process that first.
1383 ch = httpGetChar(http, buf, bytes, &i);
1386 // Terminate when we either find the 0xFF, or we have reached the end
1388 if (ch == 0xFF || !i) {
1389 // Set WS_END_OF_FRAME, iff we have found the 0xFF marker.
1390 http->expecting = i - offset - (ch == 0xFF);
1391 goto handle_partial;
1395 // Read all remaining buffered bytes (i.e. positive offset).
1397 ch = httpGetChar(http, buf, bytes, &i);
1400 // Terminate when we either find the 0xFF, or we have reached the end
1401 // of buffered data.
1402 if (ch == 0xFF || bytes == i) {
1403 // Set WS_END_OF_FRAME, iff we have found the 0xFF marker.
1404 http->expecting = i - offset - (ch == 0xFF);
1405 goto handle_buffered;
1413 int httpHandleConnection(struct ServerConnection *connection, void *http_,
1414 short *events, short revents) {
1415 struct HttpConnection *http = (struct HttpConnection *)http_;
1416 struct Trie *handlers = serverGetHttpHandlers(http->server);
1417 http->connection = connection;
1423 int eof = http->closed;
1424 if ((revents & POLLIN) && !http->closed) {
1425 bytes = httpRead(http, buf, sizeof(buf));
1427 http->headerLength += bytes;
1428 if (http->headerLength > MAX_HEADER_LENGTH) {
1429 httpSendReply(http, 413, "Header too big", NO_MSG);
1434 if (bytes == 0 || errno != EAGAIN) {
1435 httpCloseRead(http);
1438 if (http->sslHndl && http->lastError == SSL_ERROR_WANT_WRITE) {
1446 if (bytes > 0 && http->state == SNIFFING_SSL) {
1447 // Assume that all legitimate HTTP commands start with a sequence of
1448 // letters followed by a space character. If we don't see this pattern,
1449 // or if the method does not match one of the known methods, we try
1450 // switching to SSL, instead.
1452 char method[12] = { 0 };
1453 for (int i = -http->partialLength, j = 0, ch;
1454 (ch = httpGetChar(http, buf, bytes, &i)) != -1;
1456 if ((j > 0 && (ch == ' ' || ch == '\t')) ||
1457 ch == '\r' || ch == '\n') {
1458 isSSL = strcmp(method, "OPTIONS") &&
1459 strcmp(method, "GET") &&
1460 strcmp(method, "HEAD") &&
1461 strcmp(method, "POST") &&
1462 strcmp(method, "PUT") &&
1463 strcmp(method, "DELETE") &&
1464 strcmp(method, "TRACE") &&
1465 strcmp(method, "CONNECT");
1466 http->state = COMMAND;
1468 } else if (j >= (int)sizeof(method)-1 ||
1469 ch < 'A' || (ch > 'Z' && ch < 'a') || ch > 'z') {
1471 http->state = COMMAND;
1474 method[j] = ch & ~0x20;
1478 if (httpPromoteToSSL(http, buf, bytes) < 0) {
1479 httpCloseRead(http);
1483 http->headerLength = 0;
1490 if (bytes > 0 || (eof && http->partial)) {
1491 check(!!http->partial == !!http->partialLength);
1492 int offset = -http->partialLength;
1497 if (http->state == SNIFFING_SSL || http->state == COMMAND ||
1498 http->state == HEADERS) {
1499 check(!http->expecting);
1502 int firstSpace = -1;
1505 for (int i = offset; ; lineLength++) {
1506 int ch = httpGetChar(http, buf, bytes, &i);
1511 } else if (ch == ' ' || ch == '\t') {
1512 if (firstSpace < 0) {
1513 firstSpace = lineLength;
1515 lastSpace = lineLength;
1517 } else if (ch == '\n') {
1519 } else if (ch == -1) {
1525 if (fullLine || eof) {
1526 consumed = lineLength + 1;
1528 if (http->state == SNIFFING_SSL || http->state == COMMAND) {
1529 if (!httpParseCommand(http, offset, buf, bytes, firstSpace,
1530 lastSpace, lineLength)) {
1534 check(http->state == HEADERS);
1535 if (!httpParseHeaders(http, handlers, offset, buf, bytes,
1536 colon, lineLength)) {
1542 pushBack = lineLength;
1544 } else if (http->state == PAYLOAD ||
1545 http->state == DISCARD_PAYLOAD) {
1546 if (http->expecting) {
1547 int len = bytes - offset;
1548 if (http->expecting > 0 &&
1549 len > http->expecting) {
1550 len = http->expecting;
1552 if (http->state == PAYLOAD) {
1553 len = httpParsePayload(http, offset, buf,
1557 pushBack = bytes - offset - len;
1559 } else if (http->state == WEBSOCKET) {
1560 if (!httpHandleWebSocket(http, offset, buf, bytes)) {
1561 httpCloseRead(http);
1564 consumed += bytes - offset;
1571 check(offset + pushBack == bytes);
1573 check(http->partial = realloc(http->partial, pushBack));
1574 memcpy(http->partial, buf + offset, pushBack);
1575 } else if (pushBack != http->partialLength) {
1577 check(partial = malloc(pushBack));
1578 for (int i = offset, j = 0; j < pushBack; j++) {
1579 partial[j] = httpGetChar(http, buf, bytes, &i);
1581 free(http->partial);
1582 http->partial = partial;
1584 http->partialLength = pushBack;
1588 eob |= offset >= bytes;
1590 } while (!eob && !http->closed);
1591 if (http->closed || offset >= 0) {
1592 free(http->partial);
1593 http->partial = NULL;
1594 http->partialLength = 0;
1595 } else if (-offset != http->partialLength) {
1596 check(-offset < http->partialLength);
1597 memmove(http->partial, http->partial + http->partialLength + offset,
1599 http->partialLength = -offset;
1603 // If the peer closed the connection, clean up now.
1605 check(!http->partial);
1606 switch (http->state) {
1611 check(!http->expecting);
1612 http->callback = NULL;
1614 httpHandleCommand(http, handlers);
1615 httpCloseRead(http);
1616 httpSetState(http, COMMAND);
1619 case DISCARD_PAYLOAD:
1621 http->expecting = 0;
1622 httpCloseRead(http);
1623 httpSetState(http, COMMAND);
1629 // Try to write any pending outgoing data
1630 if (http->msg && http->msgLength > 0) {
1631 int wrote = httpWrite(http, http->msg,
1633 if (wrote < 0 && errno != EAGAIN) {
1634 httpCloseRead(http);
1636 http->msgLength = 0;
1639 } else if (wrote > 0) {
1640 if (wrote == http->msgLength) {
1642 http->msgLength = 0;
1645 memmove(http->msg, http->msg + wrote, http->msgLength - wrote);
1646 http->msgLength -= wrote;
1649 // SSL might require reading in order to write
1650 else if (wrote < 0 && errno == EAGAIN && http->sslHndl) {
1651 if (http->lastError == SSL_ERROR_WANT_READ && !http->closed) {
1657 // If the callback only provided partial data, refill the outgoing
1658 // buffer whenever it runs low.
1659 if (http->isPartialReply && (!http->msg || http->msgLength <= 0)) {
1660 httpConsumePayload(http, "", 0);
1667 (*events & ~(POLLIN|POLLOUT)) |
1668 (!http->closed && ((http->state != PAYLOAD &&
1669 http->state != DISCARD_PAYLOAD) ||
1670 http->expecting) ? POLLIN : 0) |
1671 (http->msg || http->isPartialReply ? POLLOUT : 0);
1673 connection = httpGetServerConnection(http);
1674 int timedOut = serverGetTimeout(connection) < 0;
1676 free(http->partial);
1677 http->partial = NULL;
1678 http->partialLength = 0;
1681 http->msgLength = 0;
1684 if ((!(*events || http->isSuspended) || timedOut) && http->sslHndl) {
1686 serverSetTimeout(connection, 1);
1687 int wasAlreadyClosed = http->closed;
1688 httpCloseRead(http);
1689 dcheck(!ERR_peek_error());
1691 int rc = SSL_shutdown(http->sslHndl);
1694 sslFreeHndl(&http->sslHndl);
1697 if (!wasAlreadyClosed) {
1702 switch (SSL_get_error(http->sslHndl, rc)) {
1703 case SSL_ERROR_WANT_READ:
1704 if (!wasAlreadyClosed) {
1708 case SSL_ERROR_WANT_WRITE:
1715 dcheck(!ERR_peek_error());
1716 if (sslUnblockSigPipe()) {
1718 sslFreeHndl(&http->sslHndl);
1720 } else if (!http->sslHndl && timedOut) {
1722 serverSetTimeout(connection, 0);
1723 httpCloseRead(http);
1725 revents = POLLIN | POLLOUT;
1726 } while (bytes > 0 && *events & POLLIN && !http->closed);
1727 return (*events & (POLLIN|POLLOUT)) ||
1728 (!http->closed && http->isSuspended);
1731 void httpSetCallback(struct HttpConnection *http,
1732 int (*callback)(struct HttpConnection *, void *,
1733 const char *, int), void *arg) {
1734 http->callback = callback;
1738 void *httpGetPrivate(struct HttpConnection *http) {
1739 return http->private;
1742 void *httpSetPrivate(struct HttpConnection *http, void *private) {
1743 void *old = http->private;
1744 http->private = private;
1748 void httpSendReply(struct HttpConnection *http, int code,
1749 const char *msg, const char *fmt, ...) {
1752 char *title = code != 200 ? stringPrintf(NULL, "%d %s", code, msg) : NULL;
1753 char *details = NULL;
1754 if (fmt != NULL && strcmp(fmt, NO_MSG)) {
1757 details = vStringPrintf(NULL, fmt, ap);
1760 body = stringPrintf(NULL,
1761 "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
1762 "<!DOCTYPE html PUBLIC "
1763 "\"-//W3C//DTD XHTML 1.0 Transitional//EN\" "
1764 "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n"
1765 "<html xmlns=\"http://www.w3.org/1999/xhtml\" "
1766 "xmlns:v=\"urn:schemas-microsoft-com:vml\" "
1767 "xml:lang=\"en\" lang=\"en\">\n"
1769 "<title>%s</title>\n"
1775 title ? title : msg, fmt && strcmp(fmt, NO_MSG) ? details : msg);
1778 char *response = NULL;
1780 response = stringPrintf(NULL,
1781 "HTTP/1.1 %d %s\r\n"
1783 "Content-Type: text/html; charset=utf-8\r\n"
1784 "Content-Length: %ld\r\n"
1787 code != 200 ? "Connection: close\r\n" : "",
1788 (long)strlen(body));
1790 int isHead = !strcmp(http->method, "HEAD");
1792 response = stringPrintf(response, "%s", body);
1795 httpTransfer(http, response, strlen(response));
1796 if (code != 200 || isHead) {
1797 httpCloseRead(http);
1801 void httpSendWebSocketTextMsg(struct HttpConnection *http, int type,
1802 const char *fmt, ...) {
1803 check(type >= 0 && type <= 0x7F);
1808 if (strcmp(fmt, BINARY_MSG)) {
1809 // Send a printf() style text message
1810 buf = vStringPrintf(NULL, fmt, ap);
1813 // Send a binary message
1814 len = va_arg(ap, int);
1815 buf = va_arg(ap, char *);
1818 check(len >= 0 && len < 0x60000000);
1820 // We assume that all input data is directly mapped in the range 0..255
1821 // (e.g. ISO-8859-1). In order to transparently send it over a web socket,
1822 // we have to encode it in UTF-8.
1823 int utf8Len = len + 2;
1824 for (int i = 0; i < len; ++i) {
1825 if (buf[i] & 0x80) {
1830 check(utf8 = malloc(utf8Len));
1832 for (int i = 0, j = 1; i < len; ++i) {
1833 unsigned char ch = buf[i];
1835 utf8[j++] = 0xC0 + (ch >> 6);
1836 utf8[j++] = 0x80 + (ch & 0x3F);
1842 utf8[utf8Len-1] = '\xFF';
1844 // Free our temporary buffer, if we actually did allocate one.
1845 if (strcmp(fmt, BINARY_MSG)) {
1850 httpTransfer(http, utf8, utf8Len);
1853 void httpSendWebSocketBinaryMsg(struct HttpConnection *http, int type,
1854 const void *buf, int len) {
1855 check(type >= 0x80 && type <= 0xFF);
1856 check(len > 0 && len < 0x7FFFFFF0);
1858 // Allocate buffer for header and payload.
1860 check(data = malloc(len + 6));
1863 // Convert length to base-128.
1867 data[++i] = 0x80 + (l & 0x7F);
1872 // Reverse digits, so that they are big-endian.
1873 for (int j = 0; j < i/2; ++j) {
1874 char ch = data[1+j];
1875 data[1+j] = data[i-j];
1879 // Transmit header and payload.
1880 memmove(data + i + 1, buf, len);
1881 httpTransfer(http, data, len + i + 1);
1884 void httpExitLoop(struct HttpConnection *http, int exitAll) {
1885 serverExitLoop(http->server, exitAll);
1888 struct Server *httpGetServer(const struct HttpConnection *http) {
1889 return http->server;
1892 struct ServerConnection *httpGetServerConnection(const struct HttpConnection *
1894 struct HttpConnection *httpW = (struct HttpConnection *)http;
1895 httpW->connection = serverGetConnection(http->server, http->connection,
1897 return http->connection;
1900 int httpGetFd(const HttpConnection *http) {
1904 const char *httpGetPeerName(const struct HttpConnection *http) {
1905 return http->peerName;
1908 const char *httpGetMethod(const struct HttpConnection *http) {
1909 return http->method;
1912 const char *httpGetProtocol(const struct HttpConnection *http) {
1913 return http->sslHndl ? "https" : "http";
1916 const char *httpGetHost(const struct HttpConnection *http) {
1917 const char *host = getFromHashMap(&http->header, "host");
1918 if (!host || !*host) {
1924 int httpGetPort(const struct HttpConnection *http) {
1928 const char *httpGetPath(const struct HttpConnection *http) {
1929 return http->matchedPath;
1932 const char *httpGetPathInfo(const struct HttpConnection *http) {
1933 return http->pathInfo ? http->pathInfo : "";
1936 const char *httpGetQuery(const struct HttpConnection *http) {
1937 return http->query ? http->query : "";
1940 const char *httpGetURL(const struct HttpConnection *http) {
1942 const char *host = httpGetHost(http);
1943 int s_size = 8 + strlen(host) + 25 + strlen(http->path);
1944 check(*(char **)&http->url = malloc(s_size + 1));
1945 *http->url = '\000';
1946 strncat(http->url, http->sslHndl ? "https://" : "http://", s_size);
1947 strncat(http->url, host, s_size);
1948 if (http->port != (http->sslHndl ? 443 : 80)) {
1949 snprintf(strrchr(http->url, '\000'), 25, ":%d", http->port);
1951 strncat(http->url, http->path, s_size);
1956 const char *httpGetVersion(const struct HttpConnection *http) {
1957 return http->version;
1960 const struct HashMap *httpGetHeaders(const struct HttpConnection *http) {
1961 return &http->header;