// 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;
}
