Squashed 'third_party/seasocks/' content from commit 016dc60

Change-Id: I195fa5bfd0c0e3cc66fbbefcc7b5170bafcf7a36
git-subtree-dir: third_party/seasocks
git-subtree-split: 016dc60b247e0d1d563aea6d22a9075e6884ab9f
diff --git a/src/main/c/Connection.cpp b/src/main/c/Connection.cpp
new file mode 100644
index 0000000..9c250a0
--- /dev/null
+++ b/src/main/c/Connection.cpp
@@ -0,0 +1,1075 @@
+// Copyright (c) 2013, Matt Godbolt
+// All rights reserved.
+// 
+// Redistribution and use in source and binary forms, with or without 
+// modification, are permitted provided that the following conditions are met:
+// 
+// Redistributions of source code must retain the above copyright notice, this 
+// list of conditions and the following disclaimer.
+// 
+// Redistributions in binary form must reproduce the above copyright notice, 
+// this list of conditions and the following disclaimer in the documentation 
+// and/or other materials provided with the distribution.
+// 
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
+// POSSIBILITY OF SUCH DAMAGE.
+
+#include "internal/Config.h"
+#include "internal/Embedded.h"
+#include "internal/HeaderMap.h"
+#include "internal/HybiAccept.h"
+#include "internal/HybiPacketDecoder.h"
+#include "internal/LogStream.h"
+#include "internal/PageRequest.h"
+#include "internal/Version.h"
+
+#include "md5/md5.h"
+
+#include "seasocks/Connection.h"
+#include "seasocks/Credentials.h"
+#include "seasocks/Logger.h"
+#include "seasocks/PageHandler.h"
+#include "seasocks/Server.h"
+#include "seasocks/StringUtil.h"
+#include "seasocks/ToString.h"
+
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <fstream>
+#include <iostream>
+#include <limits>
+#include <sstream>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <unordered_map>
+
+namespace {
+
+uint32_t parseWebSocketKey(const std::string& key) {
+    uint32_t keyNumber = 0;
+    uint32_t numSpaces = 0;
+    for (auto c : key) {
+        if (c >= '0' && c <= '9') {
+            keyNumber = keyNumber * 10 + c - '0';
+        } else if (c == ' ') {
+            ++numSpaces;
+        }
+    }
+    return numSpaces > 0 ? keyNumber / numSpaces : 0;
+}
+
+char* extractLine(uint8_t*& first, uint8_t* last, char** colon = NULL) {
+    for (uint8_t* ptr = first; ptr < last - 1; ++ptr) {
+        if (ptr[0] == '\r' && ptr[1] == '\n') {
+            ptr[0] = 0;
+            uint8_t* result = first;
+            first = ptr + 2;
+            return reinterpret_cast<char*> (result);
+        }
+        if (colon && ptr[0] == ':' && *colon == NULL) {
+            *colon = reinterpret_cast<char*> (ptr);
+        }
+    }
+    return NULL;
+}
+
+std::string webtime(time_t time) {
+    struct tm tm;
+    gmtime_r(&time, &tm);
+    char buf[1024];
+    // Wed, 20 Apr 2011 17:31:28 GMT
+    strftime(buf, sizeof(buf)-1, "%a, %d %b %Y %H:%M:%S %Z", &tm);
+    return buf;
+}
+
+std::string now() {
+    return webtime(time(NULL));
+}
+
+class RaiiFd {
+    int _fd;
+public:
+    RaiiFd(const char* filename) {
+        _fd = ::open(filename, O_RDONLY);
+    }
+    RaiiFd(const RaiiFd&) = delete;
+    RaiiFd& operator=(const RaiiFd&) = delete;
+    ~RaiiFd() {
+        if (_fd != -1) {
+            ::close(_fd);
+        }
+    }
+    bool ok() const {
+        return _fd != -1;
+    }
+    operator int() const {
+        return _fd;
+    }
+};
+
+const std::unordered_map<std::string, std::string> contentTypes = {
+    { "txt", "text/plain" },
+    { "css", "text/css" },
+    { "csv", "text/csv" },
+    { "htm", "text/html" },
+    { "html", "text/html" },
+    { "xml", "text/xml" },
+    { "js", "text/javascript" }, // Technically it should be application/javascript (RFC 4329), but IE8 struggles with that
+    { "xhtml", "application/xhtml+xml" },
+    { "json", "application/json" },
+    { "pdf", "application/pdf" },
+    { "zip", "application/zip" },
+    { "tar", "application/x-tar" },
+    { "gif", "image/gif" },
+    { "jpeg", "image/jpeg" },
+    { "jpg", "image/jpeg" },
+    { "tiff", "image/tiff" },
+    { "tif", "image/tiff" },
+    { "png", "image/png" },
+    { "svg", "image/svg+xml" },
+    { "ico", "image/x-icon" },
+    { "swf", "application/x-shockwave-flash" },
+    { "mp3", "audio/mpeg" },
+    { "wav", "audio/x-wav" },
+    { "ttf", "font/ttf" },
+};
+
+std::string getExt(const std::string& path) {
+    auto lastDot = path.find_last_of('.');
+    if (lastDot != path.npos) {
+        return path.substr(lastDot + 1);
+    }
+    return "";
+}
+
+const std::string& getContentType(const std::string& path) {
+    auto it = contentTypes.find(getExt(path));
+    if (it != contentTypes.end()) {
+        return it->second;
+    }
+    static const std::string defaultType("text/html");
+    return defaultType;
+}
+
+// Cacheability is only set for resources that *REQUIRE* caching for browser support reasons.
+// It's off for everything else to save on browser reload headaches during development, at
+// least until we support ETags or If-Modified-Since: type checking, which we may never do.
+bool isCacheable(const std::string& path) {
+    std::string extension = getExt(path);
+    if (extension == "mp3" || extension == "wav") {
+        return true;
+    }
+    return false;
+}
+
+const size_t MaxBufferSize = 16 * 1024 * 1024;
+const size_t ReadWriteBufferSize = 16 * 1024;
+const size_t MaxWebsocketMessageSize = 16384;
+const size_t MaxHeadersSize = 64 * 1024;
+
+class PrefixWrapper : public seasocks::Logger {
+    std::string _prefix;
+    std::shared_ptr<Logger> _logger;
+public:
+    PrefixWrapper(const std::string& prefix, std::shared_ptr<Logger> logger)
+    : _prefix(prefix), _logger(logger) {}
+
+    virtual void log(Level level, const char* message) {
+        _logger->log(level, (_prefix + message).c_str());
+    }
+};
+
+bool hasConnectionType(const std::string &connection, const std::string &type) {
+    for (auto conType : seasocks::split(connection, ',')) {
+        while (!conType.empty() && isspace(conType[0]))
+            conType = conType.substr(1);
+        if (seasocks::caseInsensitiveSame(conType, type))
+            return true;
+    }
+    return false;
+}
+
+}  // namespace
+
+namespace seasocks {
+
+Connection::Connection(
+        std::shared_ptr<Logger> logger,
+        ServerImpl& server,
+        int fd,
+        const sockaddr_in& address)
+    : _logger(new PrefixWrapper(formatAddress(address) + " : ", logger)),
+      _server(server),
+      _fd(fd),
+      _shutdown(false),
+      _hadSendError(false),
+      _closeOnEmpty(false),
+      _registeredForWriteEvents(false),
+      _address(address),
+      _bytesSent(0),
+      _bytesReceived(0),
+      _shutdownByUser(false),
+      _state(READING_HEADERS) {
+}
+
+Connection::~Connection() {
+    _server.checkThread();
+    finalise();
+}
+
+void Connection::close() {
+    // This is the user-side close requests ONLY! You should Call closeInternal
+    _shutdownByUser = true;
+    closeInternal();
+}
+
+void Connection::closeWhenEmpty() {
+    if (_outBuf.empty()) {
+        closeInternal();
+    } else {
+        _closeOnEmpty = true;
+    }
+}
+
+void Connection::closeInternal() {
+    // It only actually only calls shutdown on the socket,
+    // leaving the close of the FD and the cleanup until the destructor runs.
+    _server.checkThread();
+    if (_fd != -1 && !_shutdown && ::shutdown(_fd, SHUT_RDWR) == -1) {
+        LS_WARNING(_logger, "Unable to shutdown socket : " << getLastError());
+    }
+    _shutdown = true;
+}
+
+
+void Connection::finalise() {
+    if (_webSocketHandler) {
+        _webSocketHandler->onDisconnect(this);
+        _webSocketHandler.reset();
+    }
+    if (_fd != -1) {
+        _server.remove(this);
+        LS_DEBUG(_logger, "Closing socket");
+        ::close(_fd);
+    }
+    _fd = -1;
+}
+
+int Connection::safeSend(const void* data, size_t size) {
+    if (_fd == -1 || _hadSendError || _shutdown) {
+        // Ignore further writes to the socket, it's already closed or has been shutdown
+        return -1;
+    }
+    int sendResult = ::send(_fd, data, size, MSG_NOSIGNAL);
+    if (sendResult == -1) {
+        if (errno == EAGAIN || errno == EWOULDBLOCK) {
+            // Treat this as if zero bytes were written.
+            return 0;
+        }
+        LS_WARNING(_logger, "Unable to write to socket : " << getLastError() << " - disabling further writes");
+        closeInternal();
+    } else {
+        _bytesSent += sendResult;
+    }
+    return sendResult;
+}
+
+bool Connection::write(const void* data, size_t size, bool flushIt) {
+    if (closed() || _closeOnEmpty) {
+        return false;
+    }
+    if (size) {
+        int bytesSent = 0;
+        if (_outBuf.empty() && flushIt) {
+            // Attempt fast path, send directly.
+            bytesSent = safeSend(data, size);
+            if (bytesSent == static_cast<int>(size)) {
+                // We sent directly.
+                return true;
+            }
+            if (bytesSent == -1) {
+                return false;
+            }
+        }
+        size_t bytesToBuffer = size - bytesSent;
+        size_t endOfBuffer = _outBuf.size();
+        size_t newBufferSize = endOfBuffer + bytesToBuffer;
+        if (newBufferSize >= MaxBufferSize) {
+            LS_WARNING(_logger, "Closing connection: buffer size too large (" 
+                    << newBufferSize << " >= " << MaxBufferSize << ")");
+            closeInternal();
+            return false;
+        }
+        _outBuf.resize(newBufferSize);
+        memcpy(&_outBuf[endOfBuffer], reinterpret_cast<const uint8_t*>(data) + bytesSent, bytesToBuffer);
+    }
+    if (flushIt) {
+        return flush();
+    }
+    return true;
+}
+
+bool Connection::bufferLine(const char* line) {
+    static const char crlf[] = { '\r', '\n' };
+    if (!write(line, strlen(line), false)) return false;
+    return write(crlf, 2, false);
+}
+
+bool Connection::bufferLine(const std::string& line) {
+    std::string lineAndCrlf = line + "\r\n";
+    return write(lineAndCrlf.c_str(), lineAndCrlf.length(), false);
+}
+
+void Connection::handleDataReadyForRead() {
+    if (closed()) {
+        return;
+    }
+    size_t curSize = _inBuf.size();
+    _inBuf.resize(curSize + ReadWriteBufferSize);
+    int result = ::read(_fd, &_inBuf[curSize], ReadWriteBufferSize);
+    if (result == -1) {
+        LS_WARNING(_logger, "Unable to read from socket : " << getLastError());
+        return;
+    }
+    if (result == 0) {
+        LS_DEBUG(_logger, "Remote end closed connection");
+        closeInternal();
+        return;
+    }
+    _bytesReceived += result;
+    _inBuf.resize(curSize + result);
+    handleNewData();
+}
+
+void Connection::handleDataReadyForWrite() {
+    if (closed()) {
+        return;
+    }
+    flush();
+}
+
+bool Connection::flush() {
+    if (_outBuf.empty()) {
+        return true;
+    }
+    int numSent = safeSend(&_outBuf[0], _outBuf.size());
+    if (numSent == -1) {
+        return false;
+    }
+    _outBuf.erase(_outBuf.begin(), _outBuf.begin() + numSent);
+    if (_outBuf.size() > 0 && !_registeredForWriteEvents) {
+        if (!_server.subscribeToWriteEvents(this)) {
+            return false;
+        }
+        _registeredForWriteEvents = true;
+    } else if (_outBuf.empty() && _registeredForWriteEvents) {
+        if (!_server.unsubscribeFromWriteEvents(this)) {
+            return false;
+        }
+        _registeredForWriteEvents = false;
+    }
+    if (_outBuf.empty() && !closed() && _closeOnEmpty) {
+        LS_DEBUG(_logger, "Ready for close, now empty");
+        closeInternal();
+    }
+    return true;
+}
+
+bool Connection::closed() const {
+    return _fd == -1 || _shutdown;
+}
+
+void Connection::handleNewData() {
+    switch (_state) {
+    case READING_HEADERS:
+        handleHeaders();
+        break;
+    case READING_WEBSOCKET_KEY3:
+        handleWebSocketKey3();
+        break;
+    case HANDLING_HIXIE_WEBSOCKET:
+        handleHixieWebSocket();
+        break;
+    case HANDLING_HYBI_WEBSOCKET:
+        handleHybiWebSocket();
+        break;
+    case BUFFERING_POST_DATA:
+        handleBufferingPostData();
+        break;
+    default:
+        assert(false);
+        break;
+    }
+}
+
+void Connection::handleHeaders() {
+    if (_inBuf.size() < 4) {
+        return;
+    }
+    for (size_t i = 0; i <= _inBuf.size() - 4; ++i) {
+        if (_inBuf[i] == '\r' &&
+            _inBuf[i+1] == '\n' &&
+            _inBuf[i+2] == '\r' &&
+            _inBuf[i+3] == '\n') {
+            if (!processHeaders(&_inBuf[0], &_inBuf[i + 2])) {
+                closeInternal();
+                return;
+            }
+            _inBuf.erase(_inBuf.begin(), _inBuf.begin() + i + 4);
+            handleNewData();
+            return;
+        }
+    }
+    if (_inBuf.size() > MaxHeadersSize) {
+        sendUnsupportedError("Headers too big");
+    }
+}
+
+void Connection::handleWebSocketKey3() {
+    constexpr auto WebSocketKeyLen = 8u;
+    if (_inBuf.size() < WebSocketKeyLen) {
+        return;
+    }
+
+    struct {
+        uint32_t key1;
+        uint32_t key2;
+        char key3[WebSocketKeyLen];
+    } md5Source;
+
+    auto key1 = parseWebSocketKey(_request->getHeader("Sec-WebSocket-Key1"));
+    auto key2 = parseWebSocketKey(_request->getHeader("Sec-WebSocket-Key2"));
+
+    LS_DEBUG(_logger, "Got a hixie websocket with key1=0x" << std::hex << key1 << ", key2=0x" << key2);
+
+    md5Source.key1 = htonl(key1);
+    md5Source.key2 = htonl(key2);
+    memcpy(&md5Source.key3, &_inBuf[0], WebSocketKeyLen);
+
+    uint8_t digest[16];
+    md5_state_t md5state;
+    md5_init(&md5state);
+    md5_append(&md5state, reinterpret_cast<const uint8_t*>(&md5Source), sizeof(md5Source));
+    md5_finish(&md5state, digest);
+
+    LS_DEBUG(_logger, "Attempting websocket upgrade");
+
+    bufferResponseAndCommonHeaders(ResponseCode::WebSocketProtocolHandshake);
+    bufferLine("Upgrade: websocket");
+    bufferLine("Connection: Upgrade");
+    bool allowCrossOrigin = _server.isCrossOriginAllowed(_request->getRequestUri());
+    if (_request->hasHeader("Origin") && allowCrossOrigin) {
+        bufferLine("Sec-WebSocket-Origin: " +  _request->getHeader("Origin"));
+    }
+    if (_request->hasHeader("Host")) {
+        auto host = _request->getHeader("Host");
+        if (!allowCrossOrigin) {
+            bufferLine("Sec-WebSocket-Origin: http://" + host);
+        }
+        bufferLine("Sec-WebSocket-Location: ws://" + host + _request->getRequestUri());
+    }
+    bufferLine("");
+
+    write(&digest, 16, true);
+
+    _state = HANDLING_HIXIE_WEBSOCKET;
+    _inBuf.erase(_inBuf.begin(), _inBuf.begin() + 8);
+    if (_webSocketHandler) {
+        _webSocketHandler->onConnect(this);
+    }
+}
+
+void Connection::handleBufferingPostData() {
+    if (_request->consumeContent(_inBuf)) {
+        _state = READING_HEADERS;
+        if (!handlePageRequest()) {
+            closeInternal();
+        }
+    }
+}
+
+void Connection::send(const char* webSocketResponse) {
+    _server.checkThread();
+    if (_shutdown) {
+        if (_shutdownByUser) {
+            LS_ERROR(_logger, "Server wrote to connection after closing it");
+        }
+        return;
+    }
+    auto messageLength = strlen(webSocketResponse);
+    if (_state == HANDLING_HIXIE_WEBSOCKET) {
+        uint8_t zero = 0;
+        if (!write(&zero, 1, false)) return;
+        if (!write(webSocketResponse, messageLength, false)) return;
+        uint8_t effeff = 0xff;
+        write(&effeff, 1, true);
+        return;
+    }
+    sendHybi(HybiPacketDecoder::OPCODE_TEXT, reinterpret_cast<const uint8_t*>(webSocketResponse), messageLength);
+}
+
+void Connection::send(const uint8_t* data, size_t length) {
+    _server.checkThread();
+    if (_shutdown) {
+        if (_shutdownByUser) {
+            LS_ERROR(_logger, "Client wrote to connection after closing it");
+        }
+        return;
+    }
+    if (_state == HANDLING_HIXIE_WEBSOCKET) {
+        LS_ERROR(_logger, "Hixie does not support binary");
+        return;
+    }
+    sendHybi(HybiPacketDecoder::OPCODE_BINARY, data, length);
+}
+
+void Connection::sendHybi(int opcode, const uint8_t* webSocketResponse, size_t messageLength) {
+    uint8_t firstByte = 0x80 | opcode;
+    if (!write(&firstByte, 1, false)) return;
+    if (messageLength < 126) {
+        uint8_t nextByte = messageLength; // No MASK bit set.
+        if (!write(&nextByte, 1, false)) return;
+    } else if (messageLength < 65536) {
+        uint8_t nextByte = 126; // No MASK bit set.
+        if (!write(&nextByte, 1, false)) return;
+        auto lengthBytes = htons(messageLength);
+        if (!write(&lengthBytes, 2, false)) return;
+    } else {
+        uint8_t nextByte = 127; // No MASK bit set.
+        if (!write(&nextByte, 1, false)) return;
+        uint64_t lengthBytes = __bswap_64(messageLength);
+        if (!write(&lengthBytes, 8, false)) return;
+    }
+    write(webSocketResponse, messageLength, true);
+}
+
+std::shared_ptr<Credentials> Connection::credentials() const {
+    _server.checkThread();
+    return _request ? _request->credentials() : std::shared_ptr<Credentials>();
+}
+
+void Connection::handleHixieWebSocket() {
+    if (_inBuf.empty()) {
+        return;
+    }
+    size_t messageStart = 0;
+    while (messageStart < _inBuf.size()) {
+        if (_inBuf[messageStart] != 0) {
+            LS_WARNING(_logger, "Error in WebSocket input stream (got " << (int)_inBuf[messageStart] << ")");
+            closeInternal();
+            return;
+        }
+        // TODO: UTF-8
+        size_t endOfMessage = 0;
+        for (size_t i = messageStart + 1; i < _inBuf.size(); ++i) {
+            if (_inBuf[i] == 0xff) {
+                endOfMessage = i;
+                break;
+            }
+        }
+        if (endOfMessage != 0) {
+            _inBuf[endOfMessage] = 0;
+            handleWebSocketTextMessage(reinterpret_cast<const char*>(&_inBuf[messageStart + 1]));
+            messageStart = endOfMessage + 1;
+        } else {
+            break;
+        }
+    }
+    if (messageStart != 0) {
+        _inBuf.erase(_inBuf.begin(), _inBuf.begin() + messageStart);
+    }
+    if (_inBuf.size() > MaxWebsocketMessageSize) {
+        LS_WARNING(_logger, "WebSocket message too long");
+        closeInternal();
+    }
+}
+
+void Connection::handleHybiWebSocket() {
+    if (_inBuf.empty()) {
+        return;
+    }
+    HybiPacketDecoder decoder(*_logger, _inBuf);
+    bool done = false;
+    while (!done) {
+        std::vector<uint8_t> decodedMessage;
+        switch (decoder.decodeNextMessage(decodedMessage)) {
+        default:
+            closeInternal();
+            LS_WARNING(_logger, "Unknown HybiPacketDecoder state");
+            return;
+        case HybiPacketDecoder::Error:
+            closeInternal();
+            return;
+        case HybiPacketDecoder::TextMessage:
+            decodedMessage.push_back(0);  // avoids a copy
+            handleWebSocketTextMessage(reinterpret_cast<const char*>(&decodedMessage[0]));
+            break;
+        case HybiPacketDecoder::BinaryMessage:
+            handleWebSocketBinaryMessage(decodedMessage);
+            break;
+        case HybiPacketDecoder::Ping:
+            sendHybi(HybiPacketDecoder::OPCODE_PONG, &decodedMessage[0], decodedMessage.size());
+            break;
+        case HybiPacketDecoder::NoMessage:
+            done = true;
+            break;
+        case HybiPacketDecoder::Close:
+            LS_DEBUG(_logger, "Received WebSocket close");
+            closeInternal();
+            return;
+        }
+    }
+    if (decoder.numBytesDecoded() != 0) {
+        _inBuf.erase(_inBuf.begin(), _inBuf.begin() + decoder.numBytesDecoded());
+    }
+    if (_inBuf.size() > MaxWebsocketMessageSize) {
+        LS_WARNING(_logger, "WebSocket message too long");
+        closeInternal();
+    }
+}
+
+void Connection::handleWebSocketTextMessage(const char* message) {
+    LS_DEBUG(_logger, "Got text web socket message: '" << message << "'");
+    if (_webSocketHandler) {
+        _webSocketHandler->onData(this, message);
+    }
+}
+
+void Connection::handleWebSocketBinaryMessage(const std::vector<uint8_t>& message) {
+    LS_DEBUG(_logger, "Got binary web socket message (size: " << message.size() << ")");
+    if (_webSocketHandler) {
+        _webSocketHandler->onData(this, &message[0], message.size());
+    }
+}
+
+bool Connection::sendError(ResponseCode errorCode, const std::string& body) {
+    assert(_state != HANDLING_HIXIE_WEBSOCKET);
+    auto errorNumber = static_cast<int>(errorCode);
+    auto message = ::name(errorCode);
+    bufferResponseAndCommonHeaders(errorCode);
+    auto errorContent = findEmbeddedContent("/_error.html");
+    std::string document;
+    if (errorContent) {
+        document.assign(errorContent->data, errorContent->data + errorContent->length);
+        replace(document, "%%ERRORCODE%%", toString(errorNumber));
+        replace(document, "%%MESSAGE%%", message);
+        replace(document, "%%BODY%%", body);
+    } else {
+        std::stringstream documentStr;
+        documentStr << "<html><head><title>" << errorNumber << " - " << message << "</title></head>"
+                << "<body><h1>" << errorNumber << " - " << message << "</h1>"
+                << "<div>" << body << "</div><hr/><div><i>Powered by seasocks</i></div></body></html>";
+        document = documentStr.str();
+    }
+    bufferLine("Content-Length: " + toString(document.length()));
+    bufferLine("Connection: close");
+    bufferLine("");
+    bufferLine(document);
+    if (!flush()) {
+        return false;
+    }
+    closeWhenEmpty();
+    return true;
+}
+
+bool Connection::sendUnsupportedError(const std::string& reason) {
+    return sendError(ResponseCode::NotImplemented, reason);
+}
+
+bool Connection::send404() {
+    auto path = getRequestUri();
+    auto embedded = findEmbeddedContent(path);
+    if (embedded) {
+        return sendData(getContentType(path), embedded->data, embedded->length);
+    } else if (strcmp(path.c_str(), "/_livestats.js") == 0) {
+        auto stats = _server.getStatsDocument();
+        return sendData("text/javascript", stats.c_str(), stats.length());
+    } else {
+        return sendError(ResponseCode::NotFound, "Unable to find resource for: " + path);
+    }
+}
+
+bool Connection::sendBadRequest(const std::string& reason) {
+    return sendError(ResponseCode::BadRequest, reason);
+}
+
+bool Connection::sendISE(const std::string& error) {
+    return sendError(ResponseCode::InternalServerError, error);
+}
+
+bool Connection::processHeaders(uint8_t* first, uint8_t* last) {
+    // Ideally we'd copy off [first, last] now into a header structure here.
+    // Be careful about lifetimes though and multiple requests coming in, should
+    // we ever support HTTP pipelining and/or long-lived requests.
+    char* requestLine = extractLine(first, last);
+    assert(requestLine != NULL);
+
+    LS_ACCESS(_logger, "Request: " << requestLine);
+
+    const char* verbText = shift(requestLine);
+    if (!verbText) {
+        return sendBadRequest("Malformed request line");
+    }
+    auto verb = Request::verb(verbText);
+    if (verb == Request::Verb::Invalid) {
+        return sendBadRequest("Malformed request line");
+    }
+    const char* requestUri = shift(requestLine);
+    if (requestUri == NULL) {
+        return sendBadRequest("Malformed request line");
+    }
+
+    const char* httpVersion = shift(requestLine);
+    if (httpVersion == NULL) {
+        return sendBadRequest("Malformed request line");
+    }
+    if (strcmp(httpVersion, "HTTP/1.1") != 0) {
+        return sendUnsupportedError("Unsupported HTTP version");
+    }
+    if (*requestLine != 0) {
+        return sendBadRequest("Trailing crap after http version");
+    }
+
+    HeaderMap headers(31);
+    while (first < last) {
+        char* colonPos = NULL;
+        char* headerLine = extractLine(first, last, &colonPos);
+        assert(headerLine != NULL);
+        if (colonPos == NULL) {
+            return sendBadRequest("Malformed header");
+        }
+        *colonPos = 0;
+        const char* key = headerLine;
+        const char* value = skipWhitespace(colonPos + 1);
+        LS_DEBUG(_logger, "Key: " << key << " || " << value);
+#if HAVE_UNORDERED_MAP_EMPLACE
+        headers.emplace(key, value);
+#else
+        headers.insert(std::make_pair(key, value));
+#endif
+    }
+
+    if (headers.count("Connection") && headers.count("Upgrade")
+            && hasConnectionType(headers["Connection"], "Upgrade")
+            && caseInsensitiveSame(headers["Upgrade"], "websocket")) {
+        LS_INFO(_logger, "Websocket request for " << requestUri << "'");
+        if (verb != Request::Verb::Get) {
+            return sendBadRequest("Non-GET WebSocket request");
+        }
+        _webSocketHandler = _server.getWebSocketHandler(requestUri);
+        if (!_webSocketHandler) {
+            LS_WARNING(_logger, "Couldn't find WebSocket end point for '" << requestUri << "'");
+            return send404();
+        }
+        verb = Request::Verb::WebSocket;
+    }
+
+    _request.reset(new PageRequest(_address, requestUri, verb, std::move(headers)));
+
+    const EmbeddedContent *embedded = findEmbeddedContent(requestUri);
+    if (verb == Request::Verb::Get && embedded) {
+        // MRG: one day, this could be a request handler.
+        return sendData(getContentType(requestUri), embedded->data, embedded->length);
+    }
+
+    if (_request->contentLength() > MaxBufferSize) {
+        return sendBadRequest("Content length too long");
+    }
+    if (_request->contentLength() == 0) {
+        return handlePageRequest();
+    }
+    _state = BUFFERING_POST_DATA;
+    return true;
+}
+
+bool Connection::handlePageRequest() {
+    std::shared_ptr<Response> response;
+    try {
+        response = _server.handle(*_request);
+    } catch (const std::exception& e) {
+        LS_ERROR(_logger, "page error: " << e.what());
+        return sendISE(e.what());
+    } catch (...) {
+        LS_ERROR(_logger, "page error: (unknown)");
+        return sendISE("(unknown)");
+    }
+    auto uri = _request->getRequestUri();
+    if (!response && _request->verb() == Request::Verb::WebSocket) {
+        _webSocketHandler = _server.getWebSocketHandler(uri.c_str());
+        auto webSocketVersion = atoi(_request->getHeader("Sec-WebSocket-Version").c_str());
+        if (!_webSocketHandler) {
+            LS_WARNING(_logger, "Couldn't find WebSocket end point for '" << uri << "'");
+            return send404();
+        }
+        if (webSocketVersion == 0) {
+            // Hixie
+            _state = READING_WEBSOCKET_KEY3;
+            return true;
+        }
+        auto hybiKey = _request->getHeader("Sec-WebSocket-Key");
+        return handleHybiHandshake(webSocketVersion, hybiKey);
+    }
+    return sendResponse(response);
+}
+
+bool Connection::sendResponse(std::shared_ptr<Response> response) {
+    const auto requestUri = _request->getRequestUri();
+    if (response == Response::unhandled()) {
+        return sendStaticData();
+    }
+    if (response->responseCode() == ResponseCode::NotFound) {
+        // TODO: better here; we use this purely to serve our own embedded content.
+        return send404();
+    } else if (!isOk(response->responseCode())) {
+        return sendError(response->responseCode(), response->payload());
+    }
+
+    bufferResponseAndCommonHeaders(response->responseCode());
+    bufferLine("Content-Length: " + toString(response->payloadSize()));
+    bufferLine("Content-Type: " + response->contentType());
+    if (response->keepConnectionAlive()) {
+        bufferLine("Connection: keep-alive");
+    } else {
+        bufferLine("Connection: close");
+    }
+    bufferLine("Last-Modified: " + now());
+    bufferLine("Cache-Control: no-store");
+    bufferLine("Pragma: no-cache");
+    bufferLine("Expires: " + now());
+    auto headers = response->getAdditionalHeaders();
+    for (auto it = headers.begin(); it != headers.end(); ++it) {
+        bufferLine(it->first + ": " + it->second);
+    }
+    bufferLine("");
+
+    if (!write(response->payload(), response->payloadSize(), true)) {
+        return false;
+    }
+    if (!response->keepConnectionAlive()) {
+        closeWhenEmpty();
+    }
+    return true;
+}
+
+bool Connection::handleHybiHandshake(
+        int webSocketVersion,
+        const std::string& webSocketKey) {
+    if (webSocketVersion != 8 && webSocketVersion != 13) {
+        return sendBadRequest("Invalid websocket version");
+    }
+    LS_DEBUG(_logger, "Got a hybi-8 websocket with key=" << webSocketKey);
+
+    LS_DEBUG(_logger, "Attempting websocket upgrade");
+
+    bufferResponseAndCommonHeaders(ResponseCode::WebSocketProtocolHandshake);
+    bufferLine("Upgrade: websocket");
+    bufferLine("Connection: Upgrade");
+    bufferLine("Sec-WebSocket-Accept: " + getAcceptKey(webSocketKey));
+    bufferLine("");
+    flush();
+
+    if (_webSocketHandler) {
+        _webSocketHandler->onConnect(this);
+    }
+    _state = HANDLING_HYBI_WEBSOCKET;
+    return true;
+}
+
+bool Connection::parseRange(const std::string& rangeStr, Range& range) const {
+    size_t minusPos = rangeStr.find('-');
+    if (minusPos == std::string::npos) {
+        LS_WARNING(_logger, "Bad range: '" << rangeStr << "'");
+        return false;
+    }
+    if (minusPos == 0) {
+        // A range like "-500" means 500 bytes from end of file to end.
+        range.start = atoi(rangeStr.c_str());
+        range.end = std::numeric_limits<long>::max();
+        return true;
+    } else {
+        range.start = atoi(rangeStr.substr(0, minusPos).c_str());
+        if (minusPos == rangeStr.size()-1) {
+            range.end = std::numeric_limits<long>::max();
+        } else {
+            range.end = atoi(rangeStr.substr(minusPos + 1).c_str());
+        }
+        return true;
+    }
+    return false;
+}
+
+bool Connection::parseRanges(const std::string& range, std::list<Range>& ranges) const {
+    static const std::string expectedPrefix = "bytes=";
+    if (range.length() < expectedPrefix.length() || range.substr(0, expectedPrefix.length()) != expectedPrefix) {
+        LS_WARNING(_logger, "Bad range request prefix: '" << range << "'");
+        return false;
+    }
+    auto rangesText = split(range.substr(expectedPrefix.length()), ',');
+    for (auto it = rangesText.begin(); it != rangesText.end(); ++it) {
+        Range r;
+        if (!parseRange(*it, r)) {
+            return false;
+        }
+        ranges.push_back(r);
+    }
+    return !ranges.empty();
+}
+
+// Sends HTTP 200 or 206, content-length, and range info as needed. Returns the actual file ranges
+// needing sending.
+std::list<Connection::Range> Connection::processRangesForStaticData(const std::list<Range>& origRanges, long fileSize) {
+    if (origRanges.empty()) {
+        // Easy case: a non-range request.
+        bufferResponseAndCommonHeaders(ResponseCode::Ok);
+        bufferLine("Content-Length: " + toString(fileSize));
+        return { Range { 0, fileSize - 1 } };
+    }
+
+    // Partial content request.
+    bufferResponseAndCommonHeaders(ResponseCode::PartialContent);
+    int contentLength = 0;
+    std::ostringstream rangeLine;
+    rangeLine << "Content-Range: bytes ";
+    std::list<Range> sendRanges;
+    for (auto rangeIter = origRanges.cbegin(); rangeIter != origRanges.cend(); ++rangeIter) {
+        Range actualRange = *rangeIter;
+        if (actualRange.start < 0) {
+            actualRange.start += fileSize;
+        }
+        if (actualRange.start >= fileSize) {
+            actualRange.start = fileSize - 1;
+        }
+        if (actualRange.end >= fileSize) {
+            actualRange.end = fileSize - 1;
+        }
+        contentLength += actualRange.length();
+        sendRanges.push_back(actualRange);
+        rangeLine << actualRange.start << "-" << actualRange.end;
+    }
+    rangeLine << "/" << fileSize;
+    bufferLine(rangeLine.str());
+    bufferLine("Content-Length: " + toString(contentLength));
+    return sendRanges;
+}
+
+bool Connection::sendStaticData() {
+    // TODO: fold this into the handler way of doing things.
+    std::string path = _server.getStaticPath() + getRequestUri();
+    auto rangeHeader = getHeader("Range");
+    // Trim any trailing queries.
+    size_t queryPos = path.find('?');
+    if (queryPos != path.npos) {
+        path.resize(queryPos);
+    }
+    if (*path.rbegin() == '/') {
+        path += "index.html";
+    }
+    RaiiFd input(path.c_str());
+    struct stat stat;
+    if (!input.ok() || ::fstat(input, &stat) == -1) {
+        return send404();
+    }
+    std::list<Range> ranges;
+    if (!rangeHeader.empty() && !parseRanges(rangeHeader, ranges)) {
+        return sendBadRequest("Bad range header");
+    }
+    ranges = processRangesForStaticData(ranges, stat.st_size);
+    bufferLine("Content-Type: " + getContentType(path));
+    bufferLine("Connection: keep-alive");
+    bufferLine("Accept-Ranges: bytes");
+    bufferLine("Last-Modified: " + webtime(stat.st_mtime));
+    if (!isCacheable(path)) {
+        bufferLine("Cache-Control: no-store");
+        bufferLine("Pragma: no-cache");
+        bufferLine("Expires: " + now());
+    }
+    bufferLine("");
+    if (!flush()) {
+        return false;
+    }
+
+    for (auto rangeIter = ranges.cbegin(); rangeIter != ranges.cend(); ++rangeIter) {
+        if (::lseek(input, rangeIter->start, SEEK_SET) == -1) {
+            // We've (probably) already sent data.
+            return false;
+        }
+        auto bytesLeft = rangeIter->length();
+        while (bytesLeft) {
+            char buf[ReadWriteBufferSize];
+            auto bytesRead = ::read(input, buf, std::min(sizeof(buf), bytesLeft));
+            if (bytesRead <= 0) {
+                const static std::string unexpectedEof("Unexpected EOF");
+                LS_ERROR(_logger, "Error reading file: " << (bytesRead == 0 ? unexpectedEof : getLastError()));
+                // We can't send an error document as we've sent the header.
+                return false;
+            }
+            bytesLeft -= bytesRead;
+            if (!write(buf, bytesRead, true)) {
+                return false;
+            }
+        }
+    }
+    return true;
+}
+
+bool Connection::sendData(const std::string& type, const char* start, size_t size) {
+    bufferResponseAndCommonHeaders(ResponseCode::Ok);
+    bufferLine("Content-Type: " + type);
+    bufferLine("Content-Length: " + toString(size));
+    bufferLine("Connection: keep-alive");
+    bufferLine("");
+    bool result = write(start, size, true);
+    return result;
+}
+
+void Connection::bufferResponseAndCommonHeaders(ResponseCode code) {
+    auto responseCodeInt = static_cast<int>(code);
+    auto responseCodeName = ::name(code);
+    auto response = std::string("HTTP/1.1 " + toString(responseCodeInt) + " " + responseCodeName);
+    LS_ACCESS(_logger, "Response: " << response);
+    bufferLine(response);
+    bufferLine("Server: " SEASOCKS_VERSION_STRING);
+    bufferLine("Date: " + now());
+    bufferLine("Access-Control-Allow-Origin: *");
+}
+
+void Connection::setLinger() {
+    if (_fd == -1) {
+        return;
+    }
+    const int secondsToLinger = 1;
+    struct linger linger = { true, secondsToLinger };
+    if (::setsockopt(_fd, SOL_SOCKET, SO_LINGER, &linger, sizeof(linger)) == -1) {
+        LS_INFO(_logger, "Unable to set linger on socket");
+    }
+}
+
+bool Connection::hasHeader(const std::string& header) const {
+    return _request ? _request->hasHeader(header) : false;
+}
+
+std::string Connection::getHeader(const std::string& header) const {
+    return _request ? _request->getHeader(header) : "";
+}
+
+const std::string& Connection::getRequestUri() const {
+    static const std::string empty;
+    return _request ? _request->getRequestUri() : empty;
+}
+
+}  // seasocks
diff --git a/src/main/c/HybiAccept.cpp b/src/main/c/HybiAccept.cpp
new file mode 100644
index 0000000..8c2dbde
--- /dev/null
+++ b/src/main/c/HybiAccept.cpp
@@ -0,0 +1,49 @@
+// Copyright (c) 2013, Matt Godbolt
+// All rights reserved.
+// 
+// Redistribution and use in source and binary forms, with or without 
+// modification, are permitted provided that the following conditions are met:
+// 
+// Redistributions of source code must retain the above copyright notice, this 
+// list of conditions and the following disclaimer.
+// 
+// Redistributions in binary form must reproduce the above copyright notice, 
+// this list of conditions and the following disclaimer in the documentation 
+// and/or other materials provided with the distribution.
+// 
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
+// POSSIBILITY OF SUCH DAMAGE.
+
+#include "internal/Base64.h"
+#include "internal/HybiAccept.h"
+
+#include "sha1/sha1.h"
+
+#include <arpa/inet.h>
+
+namespace seasocks {
+
+static const std::string magicString("258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
+
+std::string getAcceptKey(const std::string& challenge) {
+    auto fullString = challenge + magicString;
+    SHA1 hasher;
+    hasher.Input(fullString.c_str(), fullString.size());
+    unsigned hash[5];
+    hasher.Result(hash);
+    for (int i = 0; i < 5; ++i) {
+        hash[i] = htonl(hash[i]);
+    }
+    return base64Encode(hash, sizeof(hash));
+}
+
+}
diff --git a/src/main/c/HybiPacketDecoder.cpp b/src/main/c/HybiPacketDecoder.cpp
new file mode 100644
index 0000000..f0fdefe
--- /dev/null
+++ b/src/main/c/HybiPacketDecoder.cpp
@@ -0,0 +1,102 @@
+// Copyright (c) 2013, Matt Godbolt
+// All rights reserved.
+// 
+// Redistribution and use in source and binary forms, with or without 
+// modification, are permitted provided that the following conditions are met:
+// 
+// Redistributions of source code must retain the above copyright notice, this 
+// list of conditions and the following disclaimer.
+// 
+// Redistributions in binary form must reproduce the above copyright notice, 
+// this list of conditions and the following disclaimer in the documentation 
+// and/or other materials provided with the distribution.
+// 
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
+// POSSIBILITY OF SUCH DAMAGE.
+
+#include "internal/HybiPacketDecoder.h"
+#include "internal/LogStream.h"
+
+#include <arpa/inet.h>
+
+namespace seasocks {
+
+HybiPacketDecoder::HybiPacketDecoder(Logger& logger, const std::vector<uint8_t>& buffer) :
+    _logger(logger),
+    _buffer(buffer),
+    _messageStart(0) {
+}
+
+HybiPacketDecoder::MessageState HybiPacketDecoder::decodeNextMessage(std::vector<uint8_t>& messageOut) {
+    if (_messageStart + 1 >= _buffer.size()) {
+        return NoMessage;
+    }
+    if ((_buffer[_messageStart] & 0x80) == 0) {
+        // FIN bit is not clear...
+        // TODO: support
+        LS_WARNING(&_logger, "Received hybi frame without FIN bit set - unsupported");
+        return Error;
+    }
+    if ((_buffer[_messageStart] & (7<<4)) != 0) {
+        LS_WARNING(&_logger, "Received hybi frame with reserved bits set - error");
+        return Error;
+    }
+    auto opcode = _buffer[_messageStart] & 0xf;
+    size_t payloadLength = _buffer[_messageStart + 1] & 0x7f;
+    auto maskBit = _buffer[_messageStart + 1] & 0x80;
+    auto ptr = _messageStart + 2;
+    if (payloadLength == 126) {
+        if (_buffer.size() < 4) { return NoMessage; }
+        payloadLength = htons(*reinterpret_cast<const uint16_t*>(&_buffer[ptr]));
+        ptr += 2;
+    } else if (payloadLength == 127) {
+        if (_buffer.size() < 10) { return NoMessage; }
+        payloadLength = __bswap_64(*reinterpret_cast<const uint64_t*>(&_buffer[ptr]));
+        ptr += 8;
+    }
+    uint32_t mask = 0;
+    if (maskBit) {
+        // MASK is set.
+        if (_buffer.size() < ptr + 4) { return NoMessage; }
+        mask = htonl(*reinterpret_cast<const uint32_t*>(&_buffer[ptr]));
+        ptr += 4;
+    }
+    auto bytesLeftInBuffer = _buffer.size() - ptr;
+    if (payloadLength > bytesLeftInBuffer) { return NoMessage; }
+
+    messageOut.clear();
+    messageOut.reserve(payloadLength);
+    for (auto i = 0u; i < payloadLength; ++i) {
+        auto byteShift = (3 - (i & 3)) * 8;
+        messageOut.push_back(static_cast<char>((_buffer[ptr++] ^ (mask >> byteShift)) & 0xff));
+    }
+    _messageStart = ptr;
+    switch (opcode) {
+    default:
+        LS_WARNING(&_logger, "Received hybi frame with unknown opcode " << opcode);
+        return Error;
+    case OPCODE_TEXT:
+        return TextMessage;
+    case OPCODE_BINARY:
+        return BinaryMessage;
+    case OPCODE_PING:
+        return Ping;
+    case OPCODE_CLOSE:
+        return Close;
+    }
+}
+
+size_t HybiPacketDecoder::numBytesDecoded() const {
+    return _messageStart;
+}
+
+}
diff --git a/src/main/c/Logger.cpp b/src/main/c/Logger.cpp
new file mode 100644
index 0000000..347ae30
--- /dev/null
+++ b/src/main/c/Logger.cpp
@@ -0,0 +1,76 @@
+// Copyright (c) 2013, Matt Godbolt
+// All rights reserved.
+// 
+// Redistribution and use in source and binary forms, with or without 
+// modification, are permitted provided that the following conditions are met:
+// 
+// Redistributions of source code must retain the above copyright notice, this 
+// list of conditions and the following disclaimer.
+// 
+// Redistributions in binary form must reproduce the above copyright notice, 
+// this list of conditions and the following disclaimer in the documentation 
+// and/or other materials provided with the distribution.
+// 
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
+// POSSIBILITY OF SUCH DAMAGE.
+
+#include "internal/Debug.h"
+
+#include "seasocks/Logger.h"
+
+#include <stdarg.h>
+#include <stdio.h>
+
+namespace seasocks {
+
+const int MAX_MESSAGE_LENGTH = 1024;
+
+#define PRINT_TO_MESSAGEBUF() \
+    char messageBuf[MAX_MESSAGE_LENGTH]; \
+    va_list args; \
+    va_start(args, message); \
+    vsnprintf(messageBuf, MAX_MESSAGE_LENGTH, message, args); \
+    va_end(args)
+
+void Logger::debug(const char* message, ...) {
+#ifdef LOG_DEBUG_INFO
+    PRINT_TO_MESSAGEBUF();
+    log(DEBUG, messageBuf);
+#endif
+}
+
+void Logger::access(const char* message, ...) {
+    PRINT_TO_MESSAGEBUF();
+    log(ACCESS, messageBuf);
+}
+
+void Logger::info(const char* message, ...) {
+    PRINT_TO_MESSAGEBUF();
+    log(INFO, messageBuf);
+}
+
+void Logger::warning(const char* message, ...) {
+    PRINT_TO_MESSAGEBUF();
+    log(WARNING, messageBuf);
+}
+
+void Logger::error(const char* message, ...) {
+    PRINT_TO_MESSAGEBUF();
+    log(ERROR, messageBuf);
+}
+
+void Logger::severe(const char* message, ...) {
+    PRINT_TO_MESSAGEBUF();
+    log(SEVERE, messageBuf);
+}
+
+}  // namespace seasocks
diff --git a/src/main/c/PageRequest.cpp b/src/main/c/PageRequest.cpp
new file mode 100644
index 0000000..a2a5313
--- /dev/null
+++ b/src/main/c/PageRequest.cpp
@@ -0,0 +1,62 @@
+// Copyright (c) 2013, Matt Godbolt
+// All rights reserved.
+// 
+// Redistribution and use in source and binary forms, with or without 
+// modification, are permitted provided that the following conditions are met:
+// 
+// Redistributions of source code must retain the above copyright notice, this 
+// list of conditions and the following disclaimer.
+// 
+// Redistributions in binary form must reproduce the above copyright notice, 
+// this list of conditions and the following disclaimer in the documentation 
+// and/or other materials provided with the distribution.
+// 
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
+// POSSIBILITY OF SUCH DAMAGE.
+
+#include "internal/PageRequest.h"
+
+#include <cstdlib>
+#include <cstring>
+
+namespace seasocks {
+
+PageRequest::PageRequest(
+        const sockaddr_in& remoteAddress,
+        const std::string& requestUri,
+        Verb verb,
+        HeaderMap&& headers) :
+            _credentials(std::shared_ptr<Credentials>(new Credentials())),
+            _remoteAddress(remoteAddress),
+            _requestUri(requestUri),
+            _verb(verb),
+            _headers(std::move(headers)),
+            _contentLength(getIntHeader("Content-Length")) {
+}
+
+bool PageRequest::consumeContent(std::vector<uint8_t>& buffer) {
+    if (buffer.size() < _contentLength) return false;
+    if (buffer.size() == _contentLength) {
+        _content.swap(buffer);
+    } else {
+        _content.assign(buffer.begin(), buffer.begin() + _contentLength);
+        buffer.erase(buffer.begin(), buffer.begin() + _contentLength);
+    }
+    return true;
+}
+
+int PageRequest::getIntHeader(const std::string& name) const {
+    auto iter = _headers.find(name);
+    return iter == _headers.end() ? 0 : atoi(iter->second.c_str());
+}
+
+}  // namespace seasocks
diff --git a/src/main/c/Response.cpp b/src/main/c/Response.cpp
new file mode 100644
index 0000000..2eddfe2
--- /dev/null
+++ b/src/main/c/Response.cpp
@@ -0,0 +1,63 @@
+// Copyright (c) 2013, Matt Godbolt
+// All rights reserved.
+// 
+// Redistribution and use in source and binary forms, with or without 
+// modification, are permitted provided that the following conditions are met:
+// 
+// Redistributions of source code must retain the above copyright notice, this 
+// list of conditions and the following disclaimer.
+// 
+// Redistributions in binary form must reproduce the above copyright notice, 
+// this list of conditions and the following disclaimer in the documentation 
+// and/or other materials provided with the distribution.
+// 
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
+// POSSIBILITY OF SUCH DAMAGE.
+
+#include "internal/ConcreteResponse.h"
+
+#include "seasocks/Response.h"
+
+using namespace seasocks;
+
+namespace seasocks {
+
+std::shared_ptr<Response> Response::unhandled() {
+    static std::shared_ptr<Response> unhandled;
+    return unhandled;
+}
+
+std::shared_ptr<Response> Response::notFound() {
+    static std::shared_ptr<Response> notFound(new ConcreteResponse(ResponseCode::NotFound, "Not found", "text/plain", Response::Headers(), false));
+    return notFound;
+}
+
+std::shared_ptr<Response> Response::error(ResponseCode code, const std::string& reason) {
+    return std::shared_ptr<Response>(new ConcreteResponse(code, reason, "text/plain", Response::Headers(), false));
+}
+
+std::shared_ptr<Response> Response::textResponse(const std::string& response) {
+    return std::shared_ptr<Response>(
+            new ConcreteResponse(ResponseCode::Ok, response, "text/plain", Response::Headers(), true));
+}
+
+std::shared_ptr<Response> Response::jsonResponse(const std::string& response) {
+    return std::shared_ptr<Response>(
+            new ConcreteResponse(ResponseCode::Ok, response, "application/json", Response::Headers(), true));
+}
+
+std::shared_ptr<Response> Response::htmlResponse(const std::string& response) {
+    return std::shared_ptr<Response>(
+            new ConcreteResponse(ResponseCode::Ok, response, "text/html", Response::Headers(), true));
+}
+
+}
diff --git a/src/main/c/Server.cpp b/src/main/c/Server.cpp
new file mode 100644
index 0000000..5ad1c30
--- /dev/null
+++ b/src/main/c/Server.cpp
@@ -0,0 +1,568 @@
+// Copyright (c) 2013, Matt Godbolt
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+//
+// Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+
+#include "internal/LogStream.h"
+
+#include "seasocks/Connection.h"
+#include "seasocks/Logger.h"
+#include "seasocks/Server.h"
+#include "seasocks/PageHandler.h"
+#include "seasocks/StringUtil.h"
+#include "seasocks/util/Json.h"
+
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+
+#include <sys/epoll.h>
+#include <sys/eventfd.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/syscall.h>
+
+#include <memory>
+#include <stdexcept>
+#include <string.h>
+#include <unistd.h>
+
+namespace {
+
+struct EventBits {
+    uint32_t bits;
+    explicit EventBits(uint32_t bits) : bits(bits) {}
+};
+
+std::ostream& operator <<(std::ostream& o, const EventBits& b) {
+    uint32_t bits = b.bits;
+#define DO_BIT(NAME) \
+        do { if (bits & (NAME)) { if (bits != b.bits) {o << ", "; } o << #NAME; bits &= ~(NAME); } } while (0)
+    DO_BIT(EPOLLIN);
+    DO_BIT(EPOLLPRI);
+    DO_BIT(EPOLLOUT);
+    DO_BIT(EPOLLRDNORM);
+    DO_BIT(EPOLLRDBAND);
+    DO_BIT(EPOLLWRNORM);
+    DO_BIT(EPOLLWRBAND);
+    DO_BIT(EPOLLMSG);
+    DO_BIT(EPOLLERR);
+    DO_BIT(EPOLLHUP);
+#ifdef EPOLLRDHUP
+    DO_BIT(EPOLLRDHUP);
+#endif
+    DO_BIT(EPOLLONESHOT);
+    DO_BIT(EPOLLET);
+#undef DO_BIT
+    return o;
+}
+
+const int EpollTimeoutMillis = 500;  // Twice a second is ample.
+const int DefaultLameConnectionTimeoutSeconds = 10;
+int gettid() {
+    return syscall(SYS_gettid);
+}
+
+}
+
+namespace seasocks {
+
+Server::Server(std::shared_ptr<Logger> logger)
+: _logger(logger), _listenSock(-1), _epollFd(-1), _eventFd(-1),
+  _maxKeepAliveDrops(0),
+  _lameConnectionTimeoutSeconds(DefaultLameConnectionTimeoutSeconds),
+  _nextDeadConnectionCheck(0), _threadId(0), _terminate(false),
+  _expectedTerminate(false) {
+
+    _epollFd = epoll_create(10);
+    if (_epollFd == -1) {
+        LS_ERROR(_logger, "Unable to create epoll: " << getLastError());
+        return;
+    }
+
+    _eventFd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
+    if (_eventFd == -1) {
+        LS_ERROR(_logger, "Unable to create event FD: " << getLastError());
+        return;
+    }
+
+    epoll_event eventWake = { EPOLLIN, { &_eventFd } };
+    if (epoll_ctl(_epollFd, EPOLL_CTL_ADD, _eventFd, &eventWake) == -1) {
+        LS_ERROR(_logger, "Unable to add wake socket to epoll: " << getLastError());
+        return;
+    }
+}
+
+Server::~Server() {
+    LS_INFO(_logger, "Server destruction");
+    shutdown();
+    // Only shut the eventfd and epoll at the very end
+    if (_eventFd != -1) {
+        close(_eventFd);
+    }
+    if (_epollFd != -1) {
+        close(_epollFd);
+    }
+}
+
+void Server::shutdown() {
+    // Stop listening to any further incoming connections.
+    if (_listenSock != -1) {
+        close(_listenSock);
+        _listenSock = -1;
+    }
+    // Disconnect and close any current connections.
+    while (!_connections.empty()) {
+        // Deleting the connection closes it and removes it from 'this'.
+        Connection* toBeClosed = _connections.begin()->first;
+        toBeClosed->setLinger();
+        delete toBeClosed;
+    }
+}
+
+bool Server::makeNonBlocking(int fd) const {
+    int yesPlease = 1;
+    if (ioctl(fd, FIONBIO, &yesPlease) != 0) {
+        LS_ERROR(_logger, "Unable to make FD non-blocking: " << getLastError());
+        return false;
+    }
+    return true;
+}
+
+bool Server::configureSocket(int fd) const {
+    if (!makeNonBlocking(fd)) {
+        return false;
+    }
+    const int yesPlease = 1;
+    if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yesPlease, sizeof(yesPlease)) == -1) {
+        LS_ERROR(_logger, "Unable to set reuse socket option: " << getLastError());
+        return false;
+    }
+    if (_maxKeepAliveDrops > 0) {
+        if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &yesPlease, sizeof(yesPlease)) == -1) {
+            LS_ERROR(_logger, "Unable to enable keepalive: " << getLastError());
+            return false;
+        }
+        const int oneSecond = 1;
+        if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &oneSecond, sizeof(oneSecond)) == -1) {
+            LS_ERROR(_logger, "Unable to set idle probe: " << getLastError());
+            return false;
+        }
+        if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, &oneSecond, sizeof(oneSecond)) == -1) {
+            LS_ERROR(_logger, "Unable to set idle interval: " << getLastError());
+            return false;
+        }
+        if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT, &_maxKeepAliveDrops, sizeof(_maxKeepAliveDrops)) == -1) {
+            LS_ERROR(_logger, "Unable to set keep alive count: " << getLastError());
+            return false;
+        }
+    }
+    return true;
+}
+
+void Server::terminate() {
+    _expectedTerminate = true;
+    _terminate = true;
+    uint64_t one = 1;
+    if (_eventFd != -1 && ::write(_eventFd, &one, sizeof(one)) == -1) {
+        LS_ERROR(_logger, "Unable to post a wake event: " << getLastError());
+    }
+}
+
+bool Server::startListening(int port) {
+    return startListening(INADDR_ANY, port);
+}
+
+bool Server::startListening(uint32_t hostAddr, int port) {
+    if (_epollFd == -1 || _eventFd == -1) {
+        LS_ERROR(_logger, "Unable to serve, did not initialize properly.");
+        return false;
+    }
+
+    _listenSock = socket(AF_INET, SOCK_STREAM, 0);
+    if (_listenSock == -1) {
+        LS_ERROR(_logger, "Unable to create listen socket: " << getLastError());
+        return false;
+    }
+    if (!configureSocket(_listenSock)) {
+        return false;
+    }
+    sockaddr_in sock;
+    memset(&sock, 0, sizeof(sock));
+    sock.sin_port = htons(port);
+    sock.sin_addr.s_addr = htonl(hostAddr);
+    sock.sin_family = AF_INET;
+    if (bind(_listenSock, reinterpret_cast<const sockaddr*>(&sock), sizeof(sock)) == -1) {
+        LS_ERROR(_logger, "Unable to bind socket: " << getLastError());
+        return false;
+    }
+    if (listen(_listenSock, 5) == -1) {
+        LS_ERROR(_logger, "Unable to listen on socket: " << getLastError());
+        return false;
+    }
+    epoll_event event = { EPOLLIN, { this } };
+    if (epoll_ctl(_epollFd, EPOLL_CTL_ADD, _listenSock, &event) == -1) {
+        LS_ERROR(_logger, "Unable to add listen socket to epoll: " << getLastError());
+        return false;
+    }
+
+    char buf[1024];
+    ::gethostname(buf, sizeof(buf));
+    LS_INFO(_logger, "Listening on http://" << buf << ":" << port << "/");
+
+    return true;
+}
+
+void Server::handlePipe() {
+    uint64_t dummy;
+    while (::read(_eventFd, &dummy, sizeof(dummy)) != -1) {
+        // Spin, draining the pipe until it returns EWOULDBLOCK or similar.
+    }
+    if (errno != EAGAIN || errno != EWOULDBLOCK) {
+        LS_ERROR(_logger, "Error from wakeFd read: " << getLastError());
+        _terminate = true;
+    }
+    // It's a "wake up" event; this will just cause the epoll loop to wake up.
+}
+
+Server::NewState Server::handleConnectionEvents(Connection* connection, uint32_t events) {
+    if (events & ~(EPOLLIN|EPOLLOUT|EPOLLHUP|EPOLLERR)) {
+        LS_WARNING(_logger, "Got unhandled epoll event (" << EventBits(events) << ") on connection: "
+                << formatAddress(connection->getRemoteAddress()));
+        return Close;
+    } else if (events & EPOLLERR) {
+        LS_INFO(_logger, "Error on socket (" << EventBits(events) << "): "
+                << formatAddress(connection->getRemoteAddress()));
+        return Close;
+    } else if (events & EPOLLHUP) {
+        LS_DEBUG(_logger, "Graceful hang-up (" << EventBits(events) << ") of socket: "
+                << formatAddress(connection->getRemoteAddress()));
+        return Close;
+    } else {
+        if (events & EPOLLOUT) {
+            connection->handleDataReadyForWrite();
+        }
+        if (events & EPOLLIN) {
+            connection->handleDataReadyForRead();
+        }
+    }
+    return KeepOpen;
+}
+
+void Server::checkAndDispatchEpoll(int epollMillis) {
+    const int maxEvents = 256;
+    epoll_event events[maxEvents];
+
+    std::list<Connection*> toBeDeleted;
+    int numEvents = epoll_wait(_epollFd, events, maxEvents, epollMillis);
+    if (numEvents == -1) {
+        if (errno != EINTR) {
+            LS_ERROR(_logger, "Error from epoll_wait: " << getLastError());
+        }
+        return;
+    }
+    if (numEvents == maxEvents) {
+        static time_t lastWarnTime = 0;
+        time_t now = time(NULL);
+        if (now - lastWarnTime >= 60) {
+            LS_WARNING(_logger, "Full event queue; may start starving connections. "
+                    "Will warn at most once a minute");
+            lastWarnTime = now;
+        }
+    }
+    for (int i = 0; i < numEvents; ++i) {
+        if (events[i].data.ptr == this) {
+            if (events[i].events & ~EPOLLIN) {
+                LS_SEVERE(_logger, "Got unexpected event on listening socket ("
+                        << EventBits(events[i].events) << ") - terminating");
+                _terminate = true;
+                break;
+            }
+            handleAccept();
+        } else if (events[i].data.ptr == &_eventFd) {
+            if (events[i].events & ~EPOLLIN) {
+                LS_SEVERE(_logger, "Got unexpected event on management pipe ("
+                        << EventBits(events[i].events) << ") - terminating");
+                _terminate = true;
+                break;
+            }
+            handlePipe();
+        } else {
+            auto connection = reinterpret_cast<Connection*>(events[i].data.ptr);
+            if (handleConnectionEvents(connection, events[i].events) == Close) {
+                toBeDeleted.push_back(connection);
+            }
+        }
+    }
+    // The connections are all deleted at the end so we've processed any other subject's
+    // closes etc before we call onDisconnect().
+    for (auto it = toBeDeleted.begin(); it != toBeDeleted.end(); ++it) {
+        auto connection = *it;
+        if (_connections.find(connection) == _connections.end()) {
+            LS_SEVERE(_logger, "Attempt to delete connection we didn't know about: " << (void*)connection
+                    << formatAddress(connection->getRemoteAddress()));
+            _terminate = true;
+            break;
+        }
+        LS_DEBUG(_logger, "Deleting connection: " << formatAddress(connection->getRemoteAddress()));
+        delete connection;
+    }
+}
+
+void Server::setStaticPath(const char* staticPath) {
+    LS_INFO(_logger, "Serving content from " << staticPath);
+    _staticPath = staticPath;
+}
+
+bool Server::serve(const char* staticPath, int port) {
+    setStaticPath(staticPath);
+    if (!startListening(port)) {
+        return false;
+    }
+
+    return loop();
+}
+
+bool Server::loop() {
+    if (_listenSock == -1) {
+        LS_ERROR(_logger, "Server not initialised");
+        return false;
+    }
+
+    // Stash away "the" server thread id.
+    _threadId = gettid();
+
+    while (!_terminate) {
+        // Always process events first to catch start up events.
+        processEventQueue();
+        checkAndDispatchEpoll(EpollTimeoutMillis);
+    }
+    // Reasonable effort to ensure anything enqueued during terminate has a chance to run.
+    processEventQueue();
+    LS_INFO(_logger, "Server terminating");
+    shutdown();
+    return _expectedTerminate;
+}
+
+Server::PollResult Server::poll(int millis) {
+    // Grab the thread ID on the first poll.
+    if (_threadId == 0) _threadId = gettid();
+    if (_threadId != gettid()) {
+        LS_ERROR(_logger, "poll() called from the wrong thread");
+        return PollResult::Error;
+    }
+    if (_listenSock == -1) {
+        LS_ERROR(_logger, "Server not initialised");
+        return PollResult::Error;
+    }
+    processEventQueue();
+    checkAndDispatchEpoll(millis);
+    if (!_terminate) return PollResult::Continue;
+
+    // Reasonable effort to ensure anything enqueued during terminate has a chance to run.
+    processEventQueue();
+    LS_INFO(_logger, "Server terminating");
+    shutdown();
+
+    return _expectedTerminate ? PollResult::Terminated : PollResult::Error;
+}
+
+void Server::processEventQueue() {
+    for (;;) {
+        std::shared_ptr<Runnable> runnable = popNextRunnable();
+        if (!runnable) break;
+        runnable->run();
+    }
+    time_t now = time(NULL);
+    if (now >= _nextDeadConnectionCheck) {
+        std::list<Connection*> toRemove;
+        for (auto it = _connections.cbegin(); it != _connections.cend(); ++it) {
+            time_t numSecondsSinceConnection = now - it->second;
+            auto connection = it->first;
+            if (connection->bytesReceived() == 0 && numSecondsSinceConnection >= _lameConnectionTimeoutSeconds) {
+                LS_INFO(_logger, formatAddress(connection->getRemoteAddress())
+                        << " : Killing lame connection - no bytes received after " << numSecondsSinceConnection << "s");
+                toRemove.push_back(connection);
+            }
+        }
+        for (auto it = toRemove.begin(); it != toRemove.end(); ++it) {
+            delete *it;
+        }
+    }
+}
+
+void Server::handleAccept() {
+    sockaddr_in address;
+    socklen_t addrLen = sizeof(address);
+    int fd = ::accept(_listenSock,
+            reinterpret_cast<sockaddr*>(&address),
+            &addrLen);
+    if (fd == -1) {
+        LS_ERROR(_logger, "Unable to accept: " << getLastError());
+        return;
+    }
+    if (!configureSocket(fd)) {
+        ::close(fd);
+        return;
+    }
+    LS_INFO(_logger, formatAddress(address) << " : Accepted on descriptor " << fd);
+    Connection* newConnection = new Connection(_logger, *this, fd, address);
+    epoll_event event = { EPOLLIN, { newConnection } };
+    if (epoll_ctl(_epollFd, EPOLL_CTL_ADD, fd, &event) == -1) {
+        LS_ERROR(_logger, "Unable to add socket to epoll: " << getLastError());
+        delete newConnection;
+        ::close(fd);
+        return;
+    }
+    _connections.insert(std::make_pair(newConnection, time(NULL)));
+}
+
+void Server::remove(Connection* connection) {
+    checkThread();
+    epoll_event event = { 0, { connection } };
+    if (epoll_ctl(_epollFd, EPOLL_CTL_DEL, connection->getFd(), &event) == -1) {
+        LS_ERROR(_logger, "Unable to remove from epoll: " << getLastError());
+    }
+    _connections.erase(connection);
+}
+
+bool Server::subscribeToWriteEvents(Connection* connection) {
+    epoll_event event = { EPOLLIN | EPOLLOUT, { connection } };
+    if (epoll_ctl(_epollFd, EPOLL_CTL_MOD, connection->getFd(), &event) == -1) {
+        LS_ERROR(_logger, "Unable to subscribe to write events: " << getLastError());
+        return false;
+    }
+    return true;
+}
+
+bool Server::unsubscribeFromWriteEvents(Connection* connection) {
+    epoll_event event = { EPOLLIN, { connection } };
+    if (epoll_ctl(_epollFd, EPOLL_CTL_MOD, connection->getFd(), &event) == -1) {
+        LS_ERROR(_logger, "Unable to unsubscribe from write events: " << getLastError());
+        return false;
+    }
+    return true;
+}
+
+void Server::addWebSocketHandler(const char* endpoint, std::shared_ptr<WebSocket::Handler> handler,
+        bool allowCrossOriginRequests) {
+    _webSocketHandlerMap[endpoint] = { handler, allowCrossOriginRequests };
+}
+
+void Server::addPageHandler(std::shared_ptr<PageHandler> handler) {
+    _pageHandlers.emplace_back(handler);
+}
+
+bool Server::isCrossOriginAllowed(const std::string &endpoint) const {
+    auto splits = split(endpoint, '?');
+    auto iter = _webSocketHandlerMap.find(splits[0]);
+    if (iter == _webSocketHandlerMap.end()) {
+        return false;
+    }
+    return iter->second.allowCrossOrigin;
+}
+
+std::shared_ptr<WebSocket::Handler> Server::getWebSocketHandler(const char* endpoint) const {
+    auto splits = split(endpoint, '?');
+    auto iter = _webSocketHandlerMap.find(splits[0]);
+    if (iter == _webSocketHandlerMap.end()) {
+        return std::shared_ptr<WebSocket::Handler>();
+    }
+    return iter->second.handler;
+}
+
+void Server::execute(std::shared_ptr<Runnable> runnable) {
+    std::unique_lock<decltype(_pendingRunnableMutex)> lock(_pendingRunnableMutex);
+    _pendingRunnables.push_back(runnable);
+    lock.unlock();
+
+    uint64_t one = 1;
+    if (_eventFd != -1 && ::write(_eventFd, &one, sizeof(one)) == -1) {
+        if (errno != EAGAIN && errno != EWOULDBLOCK) {
+            LS_ERROR(_logger, "Unable to post a wake event: " << getLastError());
+        }
+    }
+}
+
+std::shared_ptr<Server::Runnable> Server::popNextRunnable() {
+    std::lock_guard<decltype(_pendingRunnableMutex)> lock(_pendingRunnableMutex);
+    std::shared_ptr<Runnable> runnable;
+    if (!_pendingRunnables.empty()) {
+        runnable = _pendingRunnables.front();
+        _pendingRunnables.pop_front();
+    }
+    return runnable;
+}
+
+std::string Server::getStatsDocument() const {
+    std::ostringstream doc;
+    doc << "clear();" << std::endl;
+    for (auto it = _connections.begin(); it != _connections.end(); ++it) {
+        doc << "connection({";
+        auto connection = it->first;
+        jsonKeyPairToStream(doc,
+                "since", EpochTimeAsLocal(it->second),
+                "fd", connection->getFd(),
+                "id", reinterpret_cast<uint64_t>(connection),
+                "uri", connection->getRequestUri(),
+                "addr", formatAddress(connection->getRemoteAddress()),
+                "user", connection->credentials() ?
+                        connection->credentials()->username : "(not authed)",
+                "input", connection->inputBufferSize(),
+                "read", connection->bytesReceived(),
+                "output", connection->outputBufferSize(),
+                "written", connection->bytesSent()
+        );
+        doc << "});" << std::endl;
+    }
+    return doc.str();
+}
+
+void Server::setLameConnectionTimeoutSeconds(int seconds) {
+    LS_INFO(_logger, "Setting lame connection timeout to " << seconds);
+    _lameConnectionTimeoutSeconds = seconds;
+}
+
+void Server::setMaxKeepAliveDrops(int maxKeepAliveDrops) {
+    LS_INFO(_logger, "Setting max keep alive drops to " << maxKeepAliveDrops);
+    _maxKeepAliveDrops = maxKeepAliveDrops;
+}
+
+void Server::checkThread() const {
+    auto thisTid = gettid();
+    if (thisTid != _threadId) {
+        std::ostringstream o;
+        o << "seasocks called on wrong thread : " << thisTid << " instead of " << _threadId;
+        LS_SEVERE(_logger, o.str());
+        throw std::runtime_error(o.str());
+    }
+}
+
+std::shared_ptr<Response> Server::handle(const Request &request) {
+    for (auto handler : _pageHandlers) {
+        auto result = handler->handle(request);
+        if (result != Response::unhandled()) return result;
+    }
+    return Response::unhandled();
+}
+
+}  // namespace seasocks
diff --git a/src/main/c/StringUtil.cpp b/src/main/c/StringUtil.cpp
new file mode 100644
index 0000000..b7177ce
--- /dev/null
+++ b/src/main/c/StringUtil.cpp
@@ -0,0 +1,109 @@
+// Copyright (c) 2013, Matt Godbolt
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+//
+// Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+
+#include "seasocks/StringUtil.h"
+
+#include <cctype>
+#include <cerrno>
+#include <cstddef>
+#include <cstdio>
+#include <cstring>
+
+namespace seasocks {
+
+char* skipWhitespace(char* str) {
+    while (isspace(*str)) ++str;
+    return str;
+}
+
+char* skipNonWhitespace(char* str) {
+    while (*str && !isspace(*str)) {
+        ++str;
+    }
+    return str;
+}
+
+char* shift(char*& str) {
+    if (str == NULL) {
+        return NULL;
+    }
+    char* startOfWord = skipWhitespace(str);
+    if (*startOfWord == 0) {
+        str = startOfWord;
+        return NULL;
+    }
+    char* endOfWord = skipNonWhitespace(startOfWord);
+    if (*endOfWord != 0) {
+        *endOfWord++ = 0;
+    }
+    str = endOfWord;
+    return startOfWord;
+}
+
+std::string getLastError(){
+    char errbuf[1024];
+    return strerror_r(errno, errbuf, sizeof(errbuf));
+}
+
+std::string formatAddress(const sockaddr_in& address) {
+    char ipBuffer[24];
+    sprintf(ipBuffer,
+            "%d.%d.%d.%d:%d",
+            (address.sin_addr.s_addr >> 0) & 0xff,
+            (address.sin_addr.s_addr >> 8) & 0xff,
+            (address.sin_addr.s_addr >> 16) & 0xff,
+            (address.sin_addr.s_addr >> 24) & 0xff,
+            htons(address.sin_port));
+    return ipBuffer;
+}
+
+std::vector<std::string> split(const std::string& input, char splitChar) {
+    if (input.empty()) return std::vector<std::string>();
+    std::vector<std::string> result;
+    size_t pos = 0;
+    size_t newPos;
+    while ((newPos = input.find(splitChar, pos)) != std::string::npos) {
+        result.push_back(input.substr(pos, newPos - pos));
+        pos = newPos + 1;
+    }
+    result.push_back(input.substr(pos));
+    return result;
+}
+
+void replace(std::string& string, const std::string& find, const std::string& replace) {
+    size_t pos = 0;
+    const size_t findLen = find.length();
+    const size_t replaceLen = replace.length();
+    while ((pos = string.find(find, pos)) != std::string::npos) {
+        string = string.substr(0, pos) + replace + string.substr(pos + findLen);
+        pos += replaceLen;
+    }
+}
+
+bool caseInsensitiveSame(const std::string &lhs, const std::string &rhs) {
+    return strcasecmp(lhs.c_str(), rhs.c_str()) == 0;
+}
+
+}
diff --git a/src/main/c/internal/.gitignore b/src/main/c/internal/.gitignore
new file mode 100644
index 0000000..40ee485
--- /dev/null
+++ b/src/main/c/internal/.gitignore
@@ -0,0 +1 @@
+/Config.h
diff --git a/src/main/c/internal/Base64.cpp b/src/main/c/internal/Base64.cpp
new file mode 100644
index 0000000..3b1cf20
--- /dev/null
+++ b/src/main/c/internal/Base64.cpp
@@ -0,0 +1,50 @@
+// Copyright (c) 2013, Matt Godbolt
+// All rights reserved.
+// 
+// Redistribution and use in source and binary forms, with or without 
+// modification, are permitted provided that the following conditions are met:
+// 
+// Redistributions of source code must retain the above copyright notice, this 
+// list of conditions and the following disclaimer.
+// 
+// Redistributions in binary form must reproduce the above copyright notice, 
+// this list of conditions and the following disclaimer in the documentation 
+// and/or other materials provided with the distribution.
+// 
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
+// POSSIBILITY OF SUCH DAMAGE.
+
+#include "internal/Base64.h"
+
+#include <cstdint>
+
+namespace seasocks {
+
+const char cb64[]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+std::string base64Encode(const void* dataVoid, size_t length) {
+    std::string output;
+    auto data = reinterpret_cast<const uint8_t*>(dataVoid);
+    for (auto i = 0u; i < length; i += 3) {
+        auto bytesLeft = length - i;
+        auto b0 = data[i];
+        auto b1 = bytesLeft > 1 ? data[i + 1] : 0;
+        auto b2 = bytesLeft > 2 ? data[i + 2] : 0;
+        output.push_back(cb64[b0 >> 2]);
+        output.push_back(cb64[((b0 & 0x03) << 4) | ((b1 & 0xf0) >> 4)]);
+        output.push_back((bytesLeft > 1 ? cb64[((b1 & 0x0f) << 2) | ((b2 & 0xc0) >> 6)] : '='));
+        output.push_back((bytesLeft > 2 ? cb64[b2 & 0x3f] : '='));
+    }
+    return output;
+}
+
+} // namespace seasocks
diff --git a/src/main/c/internal/Base64.h b/src/main/c/internal/Base64.h
new file mode 100644
index 0000000..b6dc014
--- /dev/null
+++ b/src/main/c/internal/Base64.h
@@ -0,0 +1,34 @@
+// Copyright (c) 2013, Matt Godbolt
+// All rights reserved.
+// 
+// Redistribution and use in source and binary forms, with or without 
+// modification, are permitted provided that the following conditions are met:
+// 
+// Redistributions of source code must retain the above copyright notice, this 
+// list of conditions and the following disclaimer.
+// 
+// Redistributions in binary form must reproduce the above copyright notice, 
+// this list of conditions and the following disclaimer in the documentation 
+// and/or other materials provided with the distribution.
+// 
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
+// POSSIBILITY OF SUCH DAMAGE.
+
+#pragma once
+
+#include <string>
+
+namespace seasocks {
+
+extern std::string base64Encode(const void* data, size_t length);
+
+}
diff --git a/src/main/c/internal/ConcreteResponse.h b/src/main/c/internal/ConcreteResponse.h
new file mode 100644
index 0000000..d4a68b3
--- /dev/null
+++ b/src/main/c/internal/ConcreteResponse.h
@@ -0,0 +1,67 @@
+// Copyright (c) 2013, Matt Godbolt
+// All rights reserved.
+// 
+// Redistribution and use in source and binary forms, with or without 
+// modification, are permitted provided that the following conditions are met:
+// 
+// Redistributions of source code must retain the above copyright notice, this 
+// list of conditions and the following disclaimer.
+// 
+// Redistributions in binary form must reproduce the above copyright notice, 
+// this list of conditions and the following disclaimer in the documentation 
+// and/or other materials provided with the distribution.
+// 
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
+// POSSIBILITY OF SUCH DAMAGE.
+
+#pragma once
+
+#include "seasocks/Response.h"
+
+namespace seasocks {
+
+class ConcreteResponse : public Response {
+    ResponseCode _responseCode;
+    const std::string _payload;
+    const std::string _contentType;
+    const Headers _headers;
+    const bool _keepAlive;
+public:
+    ConcreteResponse(ResponseCode responseCode, const std::string& payload, const std::string& contentType, const Headers& headers, bool keepAlive) :
+        _responseCode(responseCode), _payload(payload), _contentType(contentType), _headers(headers), _keepAlive(keepAlive) {}
+
+    virtual ResponseCode responseCode() const {
+        return _responseCode;
+    }
+
+    virtual const char* payload() const {
+        return _payload.c_str();
+    }
+
+    virtual size_t payloadSize() const {
+        return _payload.size();
+    }
+
+    virtual bool keepConnectionAlive() const {
+        return _keepAlive;
+    }
+
+    virtual std::string contentType() const {
+        return _contentType;
+    }
+
+    virtual Headers getAdditionalHeaders() const {
+        return _headers;
+    }
+};
+
+}
diff --git a/src/main/c/internal/Config.h.in b/src/main/c/internal/Config.h.in
new file mode 100644
index 0000000..48c5421
--- /dev/null
+++ b/src/main/c/internal/Config.h.in
@@ -0,0 +1,219 @@
+/* src/main/c/internal/Config.h.in.  Generated from configure.ac by autoheader.  */
+
+/* Define to 1 if you have the <arpa/inet.h> header file. */
+#undef HAVE_ARPA_INET_H
+
+/* define if the compiler supports basic C++11 syntax */
+#undef HAVE_CXX11
+
+/* Define to 1 if you have the declaration of `strerror_r', and to 0 if you
+   don't. */
+#undef HAVE_DECL_STRERROR_R
+
+/* Define to 1 if you have the `dup2' function. */
+#undef HAVE_DUP2
+
+/* Define to 1 if you have the `eventfd' function. */
+#undef HAVE_EVENTFD
+
+/* Define to 1 if you have the <fcntl.h> header file. */
+#undef HAVE_FCNTL_H
+
+/* Define to 1 if you have the `fork' function. */
+#undef HAVE_FORK
+
+/* Define to 1 if you have the `gethostname' function. */
+#undef HAVE_GETHOSTNAME
+
+/* Define to 1 if you have the `getopt' function. */
+#undef HAVE_GETOPT
+
+/* Define to 1 if you have the <getopt.h> header file. */
+#undef HAVE_GETOPT_H
+
+/* Define to 1 if you have the <inttypes.h> header file. */
+#undef HAVE_INTTYPES_H
+
+/* Define to 1 if you have the <limits.h> header file. */
+#undef HAVE_LIMITS_H
+
+/* Define to 1 if your system has a GNU libc compatible `malloc' function, and
+   to 0 otherwise. */
+#undef HAVE_MALLOC
+
+/* Define to 1 if you have the <memory.h> header file. */
+#undef HAVE_MEMORY_H
+
+/* Define to 1 if you have the `memset' function. */
+#undef HAVE_MEMSET
+
+/* Define to 1 if you have the <netinet/in.h> header file. */
+#undef HAVE_NETINET_IN_H
+
+/* Define to 1 if the system has the type `ptrdiff_t'. */
+#undef HAVE_PTRDIFF_T
+
+/* Define to 1 if you have the `rmdir' function. */
+#undef HAVE_RMDIR
+
+/* Define to 1 if you have the `socket' function. */
+#undef HAVE_SOCKET
+
+/* Define to 1 if you have the `sqrt' function. */
+#undef HAVE_SQRT
+
+/* Define to 1 if stdbool.h conforms to C99. */
+#undef HAVE_STDBOOL_H
+
+/* Define to 1 if you have the <stddef.h> header file. */
+#undef HAVE_STDDEF_H
+
+/* Define to 1 if you have the <stdint.h> header file. */
+#undef HAVE_STDINT_H
+
+/* Define to 1 if you have the <stdlib.h> header file. */
+#undef HAVE_STDLIB_H
+
+/* Define to 1 if you have the `strcasecmp' function. */
+#undef HAVE_STRCASECMP
+
+/* Define to 1 if you have the `strchr' function. */
+#undef HAVE_STRCHR
+
+/* Define to 1 if you have the `strdup' function. */
+#undef HAVE_STRDUP
+
+/* Define to 1 if you have the `strerror' function. */
+#undef HAVE_STRERROR
+
+/* Define to 1 if you have the `strerror_r' function. */
+#undef HAVE_STRERROR_R
+
+/* Define to 1 if you have the <strings.h> header file. */
+#undef HAVE_STRINGS_H
+
+/* Define to 1 if you have the <string.h> header file. */
+#undef HAVE_STRING_H
+
+/* Define to 1 if you have the `syscall' function. */
+#undef HAVE_SYSCALL
+
+/* Define to 1 if you have the <sys/ioctl.h> header file. */
+#undef HAVE_SYS_IOCTL_H
+
+/* Define to 1 if you have the <sys/socket.h> header file. */
+#undef HAVE_SYS_SOCKET_H
+
+/* Define to 1 if you have the <sys/stat.h> header file. */
+#undef HAVE_SYS_STAT_H
+
+/* Define to 1 if you have the <sys/types.h> header file. */
+#undef HAVE_SYS_TYPES_H
+
+/* Define to 1 if you have the <unistd.h> header file. */
+#undef HAVE_UNISTD_H
+
+/* define if unordered_map supports emplace */
+#undef HAVE_UNORDERED_MAP_EMPLACE
+
+/* Define to 1 if you have the `vfork' function. */
+#undef HAVE_VFORK
+
+/* Define to 1 if you have the <vfork.h> header file. */
+#undef HAVE_VFORK_H
+
+/* Define to 1 if `fork' works. */
+#undef HAVE_WORKING_FORK
+
+/* Define to 1 if `vfork' works. */
+#undef HAVE_WORKING_VFORK
+
+/* Define to 1 if the system has the type `_Bool'. */
+#undef HAVE__BOOL
+
+/* Define to the address where bug reports for this package should be sent. */
+#undef PACKAGE_BUGREPORT
+
+/* Define to the full name of this package. */
+#undef PACKAGE_NAME
+
+/* Define to the full name and version of this package. */
+#undef PACKAGE_STRING
+
+/* Define to the one symbol short name of this package. */
+#undef PACKAGE_TARNAME
+
+/* Define to the home page for this package. */
+#undef PACKAGE_URL
+
+/* Define to the version of this package. */
+#undef PACKAGE_VERSION
+
+/* Define to 1 if you have the ANSI C header files. */
+#undef STDC_HEADERS
+
+/* Define to 1 if strerror_r returns char *. */
+#undef STRERROR_R_CHAR_P
+
+/* Define for Solaris 2.5.1 so the uint32_t typedef from <sys/synch.h>,
+   <pthread.h>, or <semaphore.h> is not used. If the typedef were allowed, the
+   #define below would cause a syntax error. */
+#undef _UINT32_T
+
+/* Define for Solaris 2.5.1 so the uint64_t typedef from <sys/synch.h>,
+   <pthread.h>, or <semaphore.h> is not used. If the typedef were allowed, the
+   #define below would cause a syntax error. */
+#undef _UINT64_T
+
+/* Define for Solaris 2.5.1 so the uint8_t typedef from <sys/synch.h>,
+   <pthread.h>, or <semaphore.h> is not used. If the typedef were allowed, the
+   #define below would cause a syntax error. */
+#undef _UINT8_T
+
+/* Define to `__inline__' or `__inline' if that's what the C compiler
+   calls it, or to nothing if 'inline' is not supported under any name.  */
+#ifndef __cplusplus
+#undef inline
+#endif
+
+/* Define to rpl_malloc if the replacement function should be used. */
+#undef malloc
+
+/* Define to `int' if <sys/types.h> does not define. */
+#undef pid_t
+
+/* Define to the equivalent of the C99 'restrict' keyword, or to
+   nothing if this is not supported.  Do not define if restrict is
+   supported directly.  */
+#undef restrict
+/* Work around a bug in Sun C++: it does not support _Restrict or
+   __restrict__, even though the corresponding Sun C compiler ends up with
+   "#define restrict _Restrict" or "#define restrict __restrict__" in the
+   previous line.  Perhaps some future version of Sun C++ will work with
+   restrict; if so, hopefully it defines __RESTRICT like Sun C does.  */
+#if defined __SUNPRO_CC && !defined __RESTRICT
+# define _Restrict
+# define __restrict__
+#endif
+
+/* Define to `unsigned int' if <sys/types.h> does not define. */
+#undef size_t
+
+/* Define to the type of an unsigned integer type of width exactly 16 bits if
+   such a type exists and the standard includes do not define it. */
+#undef uint16_t
+
+/* Define to the type of an unsigned integer type of width exactly 32 bits if
+   such a type exists and the standard includes do not define it. */
+#undef uint32_t
+
+/* Define to the type of an unsigned integer type of width exactly 64 bits if
+   such a type exists and the standard includes do not define it. */
+#undef uint64_t
+
+/* Define to the type of an unsigned integer type of width exactly 8 bits if
+   such a type exists and the standard includes do not define it. */
+#undef uint8_t
+
+/* Define as `fork' if `vfork' does not work. */
+#undef vfork
diff --git a/src/main/c/internal/Debug.h b/src/main/c/internal/Debug.h
new file mode 100644
index 0000000..304e141
--- /dev/null
+++ b/src/main/c/internal/Debug.h
@@ -0,0 +1,29 @@
+// Copyright (c) 2013, Matt Godbolt
+// All rights reserved.
+// 
+// Redistribution and use in source and binary forms, with or without 
+// modification, are permitted provided that the following conditions are met:
+// 
+// Redistributions of source code must retain the above copyright notice, this 
+// list of conditions and the following disclaimer.
+// 
+// Redistributions in binary form must reproduce the above copyright notice, 
+// this list of conditions and the following disclaimer in the documentation 
+// and/or other materials provided with the distribution.
+// 
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
+// POSSIBILITY OF SUCH DAMAGE.
+
+#pragma once
+
+// Uncomment to actually log at DEBUG level.
+//#define LOG_DEBUG_INFO
diff --git a/src/main/c/internal/Embedded.h b/src/main/c/internal/Embedded.h
new file mode 100644
index 0000000..5741c62
--- /dev/null
+++ b/src/main/c/internal/Embedded.h
@@ -0,0 +1,35 @@
+// Copyright (c) 2013, Matt Godbolt
+// All rights reserved.
+// 
+// Redistribution and use in source and binary forms, with or without 
+// modification, are permitted provided that the following conditions are met:
+// 
+// Redistributions of source code must retain the above copyright notice, this 
+// list of conditions and the following disclaimer.
+// 
+// Redistributions in binary form must reproduce the above copyright notice, 
+// this list of conditions and the following disclaimer in the documentation 
+// and/or other materials provided with the distribution.
+// 
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
+// POSSIBILITY OF SUCH DAMAGE.
+
+#pragma once
+
+#include <string>
+
+struct EmbeddedContent {
+   const char* data;
+   size_t length;
+};
+
+const EmbeddedContent* findEmbeddedContent(const std::string& name);
diff --git a/src/main/c/internal/HeaderMap.h b/src/main/c/internal/HeaderMap.h
new file mode 100644
index 0000000..ba5ed35
--- /dev/null
+++ b/src/main/c/internal/HeaderMap.h
@@ -0,0 +1,54 @@
+// Copyright (c) 2013, Matt Godbolt
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+//
+// Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+
+#pragma once
+
+#include <cctype>
+#include <cstring>
+#include <string>
+#include <unordered_map>
+
+namespace seasocks {
+
+struct CaseInsensitiveHash {
+    size_t operator()(const std::string &string) const {
+        size_t h = 0;
+        for (auto c: string) {
+            h = h * 13 + tolower(c);
+        }
+        return h;
+    }
+};
+
+struct CaseInsensitiveComparison {
+    bool operator()(const std::string &lhs, const std::string &rhs) const {
+        return strcasecmp(lhs.c_str(), rhs.c_str()) == 0;
+    }
+};
+
+using HeaderMap = std::unordered_map<std::string, std::string,
+        CaseInsensitiveHash, CaseInsensitiveComparison>;
+
+}
diff --git a/src/main/c/internal/HybiAccept.h b/src/main/c/internal/HybiAccept.h
new file mode 100644
index 0000000..d02833b
--- /dev/null
+++ b/src/main/c/internal/HybiAccept.h
@@ -0,0 +1,34 @@
+// Copyright (c) 2013, Matt Godbolt
+// All rights reserved.
+// 
+// Redistribution and use in source and binary forms, with or without 
+// modification, are permitted provided that the following conditions are met:
+// 
+// Redistributions of source code must retain the above copyright notice, this 
+// list of conditions and the following disclaimer.
+// 
+// Redistributions in binary form must reproduce the above copyright notice, 
+// this list of conditions and the following disclaimer in the documentation 
+// and/or other materials provided with the distribution.
+// 
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
+// POSSIBILITY OF SUCH DAMAGE.
+
+#pragma once
+
+#include <string>
+
+namespace seasocks {
+
+extern std::string getAcceptKey(const std::string& challenge);
+
+}
diff --git a/src/main/c/internal/HybiPacketDecoder.h b/src/main/c/internal/HybiPacketDecoder.h
new file mode 100644
index 0000000..528bbf9
--- /dev/null
+++ b/src/main/c/internal/HybiPacketDecoder.h
@@ -0,0 +1,65 @@
+// Copyright (c) 2013, Matt Godbolt
+// All rights reserved.
+// 
+// Redistribution and use in source and binary forms, with or without 
+// modification, are permitted provided that the following conditions are met:
+// 
+// Redistributions of source code must retain the above copyright notice, this 
+// list of conditions and the following disclaimer.
+// 
+// Redistributions in binary form must reproduce the above copyright notice, 
+// this list of conditions and the following disclaimer in the documentation 
+// and/or other materials provided with the distribution.
+// 
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
+// POSSIBILITY OF SUCH DAMAGE.
+
+#pragma once
+
+#include "seasocks/Logger.h"
+
+#include <memory>
+#include <string>
+#include <vector>
+
+namespace seasocks {
+
+class HybiPacketDecoder {
+    Logger& _logger;
+    const std::vector<uint8_t>& _buffer;
+    size_t _messageStart;
+public:
+    HybiPacketDecoder(Logger& logger, const std::vector<uint8_t>& buffer);
+
+    enum {
+        OPCODE_CONT = 0x0,  // Deprecated in latest hybi spec, here anyway.
+        OPCODE_TEXT = 0x1,
+        OPCODE_BINARY = 0x2,
+        OPCODE_CLOSE = 0x8,
+        OPCODE_PING = 0x9,
+        OPCODE_PONG = 0xA,
+    };
+
+    enum MessageState {
+        NoMessage,
+        TextMessage,
+        BinaryMessage,
+        Error,
+        Ping,
+        Close
+    };
+    MessageState decodeNextMessage(std::vector<uint8_t>& messageOut);
+
+    size_t numBytesDecoded() const;
+};
+
+}
diff --git a/src/main/c/internal/LogStream.h b/src/main/c/internal/LogStream.h
new file mode 100644
index 0000000..16612ba
--- /dev/null
+++ b/src/main/c/internal/LogStream.h
@@ -0,0 +1,45 @@
+// Copyright (c) 2013, Matt Godbolt
+// All rights reserved.
+// 
+// Redistribution and use in source and binary forms, with or without 
+// modification, are permitted provided that the following conditions are met:
+// 
+// Redistributions of source code must retain the above copyright notice, this 
+// list of conditions and the following disclaimer.
+// 
+// Redistributions in binary form must reproduce the above copyright notice, 
+// this list of conditions and the following disclaimer in the documentation 
+// and/or other materials provided with the distribution.
+// 
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
+// POSSIBILITY OF SUCH DAMAGE.
+
+#pragma once
+
+#include "internal/Debug.h"
+
+// Internal stream helpers for logging.
+#include <sstream>
+
+#define LS_LOG(LOG, LEVEL, STUFF) \
+{ \
+    std::ostringstream o; \
+    o << STUFF; \
+    (LOG)->log(Logger::LEVEL, o.str().c_str()); \
+}
+
+#define LS_DEBUG(LOG, STUFF)     LS_LOG(LOG, DEBUG, STUFF)
+#define LS_ACCESS(LOG, STUFF)     LS_LOG(LOG, ACCESS, STUFF)
+#define LS_INFO(LOG, STUFF)     LS_LOG(LOG, INFO, STUFF)
+#define LS_WARNING(LOG, STUFF)     LS_LOG(LOG, WARNING, STUFF)
+#define LS_ERROR(LOG, STUFF)     LS_LOG(LOG, ERROR, STUFF)
+#define LS_SEVERE(LOG, STUFF)     LS_LOG(LOG, SEVERE, STUFF)
diff --git a/src/main/c/internal/PageRequest.h b/src/main/c/internal/PageRequest.h
new file mode 100644
index 0000000..6fea7fb
--- /dev/null
+++ b/src/main/c/internal/PageRequest.h
@@ -0,0 +1,90 @@
+// Copyright (c) 2013, Matt Godbolt
+// All rights reserved.
+// 
+// Redistribution and use in source and binary forms, with or without 
+// modification, are permitted provided that the following conditions are met:
+// 
+// Redistributions of source code must retain the above copyright notice, this 
+// list of conditions and the following disclaimer.
+// 
+// Redistributions in binary form must reproduce the above copyright notice, 
+// this list of conditions and the following disclaimer in the documentation 
+// and/or other materials provided with the distribution.
+// 
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
+// POSSIBILITY OF SUCH DAMAGE.
+
+#pragma once
+
+#include "internal/HeaderMap.h"
+#include "seasocks/Request.h"
+
+#include <unordered_map>
+#include <vector>
+
+namespace seasocks {
+
+class PageRequest : public Request {
+    std::shared_ptr<Credentials> _credentials;
+    const sockaddr_in _remoteAddress;
+    const std::string _requestUri;
+    const Verb _verb;
+    std::vector<uint8_t> _content;
+    HeaderMap _headers;
+    const size_t _contentLength;
+
+public:
+    PageRequest(
+            const sockaddr_in& remoteAddress,
+            const std::string& requestUri,
+            Verb verb,
+            HeaderMap&& headers);
+
+    virtual Verb verb() const {
+        return _verb;
+    }
+
+    virtual std::shared_ptr<Credentials> credentials() const {
+        return _credentials;
+    }
+
+    virtual const sockaddr_in& getRemoteAddress() const {
+        return _remoteAddress;
+    }
+
+    virtual const std::string& getRequestUri() const {
+        return _requestUri;
+    }
+
+    virtual size_t contentLength() const {
+        return _contentLength;
+    }
+
+    virtual const uint8_t* content() const {
+        return _contentLength > 0 ? &_content[0] : NULL;
+    }
+
+    virtual bool hasHeader(const std::string& name) const {
+        return _headers.find(name) != _headers.end();
+    }
+
+    virtual std::string getHeader(const std::string& name) const {
+        auto iter = _headers.find(name);
+        return iter == _headers.end() ? std::string() : iter->second;
+    }
+
+    bool consumeContent(std::vector<uint8_t>& buffer);
+
+    int getIntHeader(const std::string& name) const;
+};
+
+}  // namespace seasocks
diff --git a/src/main/c/internal/Version.h b/src/main/c/internal/Version.h
new file mode 100644
index 0000000..366f8ea
--- /dev/null
+++ b/src/main/c/internal/Version.h
@@ -0,0 +1,31 @@
+// Copyright (c) 2013, Matt Godbolt
+// All rights reserved.
+// 
+// Redistribution and use in source and binary forms, with or without 
+// modification, are permitted provided that the following conditions are met:
+// 
+// Redistributions of source code must retain the above copyright notice, this 
+// list of conditions and the following disclaimer.
+// 
+// Redistributions in binary form must reproduce the above copyright notice, 
+// this list of conditions and the following disclaimer in the documentation 
+// and/or other materials provided with the distribution.
+// 
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
+// POSSIBILITY OF SUCH DAMAGE.
+
+#pragma once
+
+#ifndef SEASOCKS_VERSION_STRING
+// This stops Eclipse freaking out as it doesn't know this is set on GCC command line.
+#define SEASOCKS_VERSION_STRING "SeaSocks/unversioned"
+#endif
\ No newline at end of file
diff --git a/src/main/c/md5/md5.cpp b/src/main/c/md5/md5.cpp
new file mode 100644
index 0000000..4ad3606
--- /dev/null
+++ b/src/main/c/md5/md5.cpp
@@ -0,0 +1,416 @@
+// Copyright (c) 2013, Matt Godbolt
+// All rights reserved.
+// 
+// Redistribution and use in source and binary forms, with or without 
+// modification, are permitted provided that the following conditions are met:
+// 
+// Redistributions of source code must retain the above copyright notice, this 
+// list of conditions and the following disclaimer.
+// 
+// Redistributions in binary form must reproduce the above copyright notice, 
+// this list of conditions and the following disclaimer in the documentation 
+// and/or other materials provided with the distribution.
+// 
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
+// POSSIBILITY OF SUCH DAMAGE.
+
+/*
+  Copyright (C) 1999, 2000, 2002 Aladdin Enterprises.  All rights reserved.
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+
+  L. Peter Deutsch
+  ghost@aladdin.com
+
+ */
+/* $Id: md5.c,v 1.6 2002/04/13 19:20:28 lpd Exp $ */
+/*
+  Independent implementation of MD5 (RFC 1321).
+
+  This code implements the MD5 Algorithm defined in RFC 1321, whose
+  text is available at
+    http://www.ietf.org/rfc/rfc1321.txt
+  The code is derived from the text of the RFC, including the test suite
+  (section A.5) but excluding the rest of Appendix A.  It does not include
+  any code or documentation that is identified in the RFC as being
+  copyrighted.
+
+  The original and principal author of md5.c is L. Peter Deutsch
+  <ghost@aladdin.com>.  Other authors are noted in the change history
+  that follows (in reverse chronological order):
+
+  2002-04-13 lpd Clarified derivation from RFC 1321; now handles byte order
+    either statically or dynamically; added missing #include <string.h>
+    in library.
+  2002-03-11 lpd Corrected argument list for main(), and added int return
+    type, in test program and T value program.
+  2002-02-21 lpd Added missing #include <stdio.h> in test program.
+  2000-07-03 lpd Patched to eliminate warnings about "constant is
+    unsigned in ANSI C, signed in traditional"; made test program
+    self-checking.
+  1999-11-04 lpd Edited comments slightly for automatic TOC extraction.
+  1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5).
+  1999-05-03 lpd Original version.
+ */
+
+#include "md5/md5.h"
+
+#include <string.h>
+
+#undef BYTE_ORDER    /* 1 = big-endian, -1 = little-endian, 0 = unknown */
+#ifdef ARCH_IS_BIG_ENDIAN
+#  define BYTE_ORDER (ARCH_IS_BIG_ENDIAN ? 1 : -1)
+#else
+#  define BYTE_ORDER 0
+#endif
+
+#define T_MASK ((md5_word_t)~0)
+#define T1 /* 0xd76aa478 */ (T_MASK ^ 0x28955b87)
+#define T2 /* 0xe8c7b756 */ (T_MASK ^ 0x173848a9)
+#define T3    0x242070db
+#define T4 /* 0xc1bdceee */ (T_MASK ^ 0x3e423111)
+#define T5 /* 0xf57c0faf */ (T_MASK ^ 0x0a83f050)
+#define T6    0x4787c62a
+#define T7 /* 0xa8304613 */ (T_MASK ^ 0x57cfb9ec)
+#define T8 /* 0xfd469501 */ (T_MASK ^ 0x02b96afe)
+#define T9    0x698098d8
+#define T10 /* 0x8b44f7af */ (T_MASK ^ 0x74bb0850)
+#define T11 /* 0xffff5bb1 */ (T_MASK ^ 0x0000a44e)
+#define T12 /* 0x895cd7be */ (T_MASK ^ 0x76a32841)
+#define T13    0x6b901122
+#define T14 /* 0xfd987193 */ (T_MASK ^ 0x02678e6c)
+#define T15 /* 0xa679438e */ (T_MASK ^ 0x5986bc71)
+#define T16    0x49b40821
+#define T17 /* 0xf61e2562 */ (T_MASK ^ 0x09e1da9d)
+#define T18 /* 0xc040b340 */ (T_MASK ^ 0x3fbf4cbf)
+#define T19    0x265e5a51
+#define T20 /* 0xe9b6c7aa */ (T_MASK ^ 0x16493855)
+#define T21 /* 0xd62f105d */ (T_MASK ^ 0x29d0efa2)
+#define T22    0x02441453
+#define T23 /* 0xd8a1e681 */ (T_MASK ^ 0x275e197e)
+#define T24 /* 0xe7d3fbc8 */ (T_MASK ^ 0x182c0437)
+#define T25    0x21e1cde6
+#define T26 /* 0xc33707d6 */ (T_MASK ^ 0x3cc8f829)
+#define T27 /* 0xf4d50d87 */ (T_MASK ^ 0x0b2af278)
+#define T28    0x455a14ed
+#define T29 /* 0xa9e3e905 */ (T_MASK ^ 0x561c16fa)
+#define T30 /* 0xfcefa3f8 */ (T_MASK ^ 0x03105c07)
+#define T31    0x676f02d9
+#define T32 /* 0x8d2a4c8a */ (T_MASK ^ 0x72d5b375)
+#define T33 /* 0xfffa3942 */ (T_MASK ^ 0x0005c6bd)
+#define T34 /* 0x8771f681 */ (T_MASK ^ 0x788e097e)
+#define T35    0x6d9d6122
+#define T36 /* 0xfde5380c */ (T_MASK ^ 0x021ac7f3)
+#define T37 /* 0xa4beea44 */ (T_MASK ^ 0x5b4115bb)
+#define T38    0x4bdecfa9
+#define T39 /* 0xf6bb4b60 */ (T_MASK ^ 0x0944b49f)
+#define T40 /* 0xbebfbc70 */ (T_MASK ^ 0x4140438f)
+#define T41    0x289b7ec6
+#define T42 /* 0xeaa127fa */ (T_MASK ^ 0x155ed805)
+#define T43 /* 0xd4ef3085 */ (T_MASK ^ 0x2b10cf7a)
+#define T44    0x04881d05
+#define T45 /* 0xd9d4d039 */ (T_MASK ^ 0x262b2fc6)
+#define T46 /* 0xe6db99e5 */ (T_MASK ^ 0x1924661a)
+#define T47    0x1fa27cf8
+#define T48 /* 0xc4ac5665 */ (T_MASK ^ 0x3b53a99a)
+#define T49 /* 0xf4292244 */ (T_MASK ^ 0x0bd6ddbb)
+#define T50    0x432aff97
+#define T51 /* 0xab9423a7 */ (T_MASK ^ 0x546bdc58)
+#define T52 /* 0xfc93a039 */ (T_MASK ^ 0x036c5fc6)
+#define T53    0x655b59c3
+#define T54 /* 0x8f0ccc92 */ (T_MASK ^ 0x70f3336d)
+#define T55 /* 0xffeff47d */ (T_MASK ^ 0x00100b82)
+#define T56 /* 0x85845dd1 */ (T_MASK ^ 0x7a7ba22e)
+#define T57    0x6fa87e4f
+#define T58 /* 0xfe2ce6e0 */ (T_MASK ^ 0x01d3191f)
+#define T59 /* 0xa3014314 */ (T_MASK ^ 0x5cfebceb)
+#define T60    0x4e0811a1
+#define T61 /* 0xf7537e82 */ (T_MASK ^ 0x08ac817d)
+#define T62 /* 0xbd3af235 */ (T_MASK ^ 0x42c50dca)
+#define T63    0x2ad7d2bb
+#define T64 /* 0xeb86d391 */ (T_MASK ^ 0x14792c6e)
+
+
+static void
+md5_process(md5_state_t *pms, const md5_byte_t *data /*[64]*/)
+{
+    md5_word_t
+    a = pms->abcd[0], b = pms->abcd[1],
+    c = pms->abcd[2], d = pms->abcd[3];
+    md5_word_t t;
+#if BYTE_ORDER > 0
+    /* Define storage only for big-endian CPUs. */
+    md5_word_t X[16];
+#else
+    /* Define storage for little-endian or both types of CPUs. */
+    md5_word_t xbuf[16];
+    const md5_word_t *X;
+#endif
+
+    {
+#if BYTE_ORDER == 0
+    /*
+     * Determine dynamically whether this is a big-endian or
+     * little-endian machine, since we can use a more efficient
+     * algorithm on the latter.
+     */
+    static const int w = 1;
+
+    if (*((const md5_byte_t *)&w)) /* dynamic little-endian */
+#endif
+#if BYTE_ORDER <= 0        /* little-endian */
+    {
+        /*
+         * On little-endian machines, we can process properly aligned
+         * data without copying it.
+         */
+        if (!((data - (const md5_byte_t *)0) & 3)) {
+        /* data are properly aligned */
+        X = (const md5_word_t *)data;
+        } else {
+        /* not aligned */
+        memcpy(xbuf, data, 64);
+        X = xbuf;
+        }
+    }
+#endif
+#if BYTE_ORDER == 0
+    else            /* dynamic big-endian */
+#endif
+#if BYTE_ORDER >= 0        /* big-endian */
+    {
+        /*
+         * On big-endian machines, we must arrange the bytes in the
+         * right order.
+         */
+        const md5_byte_t *xp = data;
+        int i;
+
+#  if BYTE_ORDER == 0
+        X = xbuf;        /* (dynamic only) */
+#  else
+#    define xbuf X        /* (static only) */
+#  endif
+        for (i = 0; i < 16; ++i, xp += 4)
+        xbuf[i] = xp[0] + (xp[1] << 8) + (xp[2] << 16) + (xp[3] << 24);
+    }
+#endif
+    }
+
+#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32 - (n))))
+
+    /* Round 1. */
+    /* Let [abcd k s i] denote the operation
+       a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s). */
+#define F(x, y, z) (((x) & (y)) | (~(x) & (z)))
+#define SET(a, b, c, d, k, s, Ti)\
+  t = a + F(b,c,d) + X[k] + Ti;\
+  a = ROTATE_LEFT(t, s) + b
+    /* Do the following 16 operations. */
+    SET(a, b, c, d,  0,  7,  T1);
+    SET(d, a, b, c,  1, 12,  T2);
+    SET(c, d, a, b,  2, 17,  T3);
+    SET(b, c, d, a,  3, 22,  T4);
+    SET(a, b, c, d,  4,  7,  T5);
+    SET(d, a, b, c,  5, 12,  T6);
+    SET(c, d, a, b,  6, 17,  T7);
+    SET(b, c, d, a,  7, 22,  T8);
+    SET(a, b, c, d,  8,  7,  T9);
+    SET(d, a, b, c,  9, 12, T10);
+    SET(c, d, a, b, 10, 17, T11);
+    SET(b, c, d, a, 11, 22, T12);
+    SET(a, b, c, d, 12,  7, T13);
+    SET(d, a, b, c, 13, 12, T14);
+    SET(c, d, a, b, 14, 17, T15);
+    SET(b, c, d, a, 15, 22, T16);
+#undef SET
+
+     /* Round 2. */
+     /* Let [abcd k s i] denote the operation
+          a = b + ((a + G(b,c,d) + X[k] + T[i]) <<< s). */
+#define G(x, y, z) (((x) & (z)) | ((y) & ~(z)))
+#define SET(a, b, c, d, k, s, Ti)\
+  t = a + G(b,c,d) + X[k] + Ti;\
+  a = ROTATE_LEFT(t, s) + b
+     /* Do the following 16 operations. */
+    SET(a, b, c, d,  1,  5, T17);
+    SET(d, a, b, c,  6,  9, T18);
+    SET(c, d, a, b, 11, 14, T19);
+    SET(b, c, d, a,  0, 20, T20);
+    SET(a, b, c, d,  5,  5, T21);
+    SET(d, a, b, c, 10,  9, T22);
+    SET(c, d, a, b, 15, 14, T23);
+    SET(b, c, d, a,  4, 20, T24);
+    SET(a, b, c, d,  9,  5, T25);
+    SET(d, a, b, c, 14,  9, T26);
+    SET(c, d, a, b,  3, 14, T27);
+    SET(b, c, d, a,  8, 20, T28);
+    SET(a, b, c, d, 13,  5, T29);
+    SET(d, a, b, c,  2,  9, T30);
+    SET(c, d, a, b,  7, 14, T31);
+    SET(b, c, d, a, 12, 20, T32);
+#undef SET
+
+     /* Round 3. */
+     /* Let [abcd k s t] denote the operation
+          a = b + ((a + H(b,c,d) + X[k] + T[i]) <<< s). */
+#define H(x, y, z) ((x) ^ (y) ^ (z))
+#define SET(a, b, c, d, k, s, Ti)\
+  t = a + H(b,c,d) + X[k] + Ti;\
+  a = ROTATE_LEFT(t, s) + b
+     /* Do the following 16 operations. */
+    SET(a, b, c, d,  5,  4, T33);
+    SET(d, a, b, c,  8, 11, T34);
+    SET(c, d, a, b, 11, 16, T35);
+    SET(b, c, d, a, 14, 23, T36);
+    SET(a, b, c, d,  1,  4, T37);
+    SET(d, a, b, c,  4, 11, T38);
+    SET(c, d, a, b,  7, 16, T39);
+    SET(b, c, d, a, 10, 23, T40);
+    SET(a, b, c, d, 13,  4, T41);
+    SET(d, a, b, c,  0, 11, T42);
+    SET(c, d, a, b,  3, 16, T43);
+    SET(b, c, d, a,  6, 23, T44);
+    SET(a, b, c, d,  9,  4, T45);
+    SET(d, a, b, c, 12, 11, T46);
+    SET(c, d, a, b, 15, 16, T47);
+    SET(b, c, d, a,  2, 23, T48);
+#undef SET
+
+     /* Round 4. */
+     /* Let [abcd k s t] denote the operation
+          a = b + ((a + I(b,c,d) + X[k] + T[i]) <<< s). */
+#define I(x, y, z) ((y) ^ ((x) | ~(z)))
+#define SET(a, b, c, d, k, s, Ti)\
+  t = a + I(b,c,d) + X[k] + Ti;\
+  a = ROTATE_LEFT(t, s) + b
+     /* Do the following 16 operations. */
+    SET(a, b, c, d,  0,  6, T49);
+    SET(d, a, b, c,  7, 10, T50);
+    SET(c, d, a, b, 14, 15, T51);
+    SET(b, c, d, a,  5, 21, T52);
+    SET(a, b, c, d, 12,  6, T53);
+    SET(d, a, b, c,  3, 10, T54);
+    SET(c, d, a, b, 10, 15, T55);
+    SET(b, c, d, a,  1, 21, T56);
+    SET(a, b, c, d,  8,  6, T57);
+    SET(d, a, b, c, 15, 10, T58);
+    SET(c, d, a, b,  6, 15, T59);
+    SET(b, c, d, a, 13, 21, T60);
+    SET(a, b, c, d,  4,  6, T61);
+    SET(d, a, b, c, 11, 10, T62);
+    SET(c, d, a, b,  2, 15, T63);
+    SET(b, c, d, a,  9, 21, T64);
+#undef SET
+
+     /* Then perform the following additions. (That is increment each
+        of the four registers by the value it had before this block
+        was started.) */
+    pms->abcd[0] += a;
+    pms->abcd[1] += b;
+    pms->abcd[2] += c;
+    pms->abcd[3] += d;
+}
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+void
+md5_init(md5_state_t *pms)
+{
+    pms->count[0] = pms->count[1] = 0;
+    pms->abcd[0] = 0x67452301;
+    pms->abcd[1] = /*0xefcdab89*/ T_MASK ^ 0x10325476;
+    pms->abcd[2] = /*0x98badcfe*/ T_MASK ^ 0x67452301;
+    pms->abcd[3] = 0x10325476;
+}
+
+void
+md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes)
+{
+    const md5_byte_t *p = data;
+    int left = nbytes;
+    int offset = (pms->count[0] >> 3) & 63;
+    md5_word_t nbits = (md5_word_t)(nbytes << 3);
+
+    if (nbytes <= 0)
+    return;
+
+    /* Update the message length. */
+    pms->count[1] += nbytes >> 29;
+    pms->count[0] += nbits;
+    if (pms->count[0] < nbits)
+    pms->count[1]++;
+
+    /* Process an initial partial block. */
+    if (offset) {
+    int copy = (offset + nbytes > 64 ? 64 - offset : nbytes);
+
+    memcpy(pms->buf + offset, p, copy);
+    if (offset + copy < 64)
+        return;
+    p += copy;
+    left -= copy;
+    md5_process(pms, pms->buf);
+    }
+
+    /* Process full blocks. */
+    for (; left >= 64; p += 64, left -= 64)
+    md5_process(pms, p);
+
+    /* Process a final partial block. */
+    if (left)
+    memcpy(pms->buf, p, left);
+}
+
+void
+md5_finish(md5_state_t *pms, md5_byte_t digest[16])
+{
+    static const md5_byte_t pad[64] = {
+    0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+    };
+    md5_byte_t data[8];
+    int i;
+
+    /* Save the length before padding. */
+    for (i = 0; i < 8; ++i)
+    data[i] = (md5_byte_t)(pms->count[i >> 2] >> ((i & 3) << 3));
+    /* Pad to 56 bytes mod 64. */
+    md5_append(pms, pad, ((55 - (pms->count[0] >> 3)) & 63) + 1);
+    /* Append the length. */
+    md5_append(pms, data, 8);
+    for (i = 0; i < 16; ++i)
+    digest[i] = (md5_byte_t)(pms->abcd[i >> 2] >> ((i & 3) << 3));
+}
+
+#ifdef __cplusplus
+}  /* end extern "C" */
+#endif
diff --git a/src/main/c/md5/md5.h b/src/main/c/md5/md5.h
new file mode 100644
index 0000000..3b88587
--- /dev/null
+++ b/src/main/c/md5/md5.h
@@ -0,0 +1,116 @@
+// Copyright (c) 2013, Matt Godbolt
+// All rights reserved.
+// 
+// Redistribution and use in source and binary forms, with or without 
+// modification, are permitted provided that the following conditions are met:
+// 
+// Redistributions of source code must retain the above copyright notice, this 
+// list of conditions and the following disclaimer.
+// 
+// Redistributions in binary form must reproduce the above copyright notice, 
+// this list of conditions and the following disclaimer in the documentation 
+// and/or other materials provided with the distribution.
+// 
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
+// POSSIBILITY OF SUCH DAMAGE.
+
+/*
+  Copyright (C) 1999, 2002 Aladdin Enterprises.  All rights reserved.
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+
+  L. Peter Deutsch
+  ghost@aladdin.com
+
+ */
+/* $Id: md5.h,v 1.4 2002/04/13 19:20:28 lpd Exp $ */
+/*
+  Independent implementation of MD5 (RFC 1321).
+
+  This code implements the MD5 Algorithm defined in RFC 1321, whose
+  text is available at
+    http://www.ietf.org/rfc/rfc1321.txt
+  The code is derived from the text of the RFC, including the test suite
+  (section A.5) but excluding the rest of Appendix A.  It does not include
+  any code or documentation that is identified in the RFC as being
+  copyrighted.
+
+  The original and principal author of md5.h is L. Peter Deutsch
+  <ghost@aladdin.com>.  Other authors are noted in the change history
+  that follows (in reverse chronological order):
+
+  2002-04-13 lpd Removed support for non-ANSI compilers; removed
+    references to Ghostscript; clarified derivation from RFC 1321;
+    now handles byte order either statically or dynamically.
+  1999-11-04 lpd Edited comments slightly for automatic TOC extraction.
+  1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5);
+    added conditionalization for C++ compilation from Martin
+    Purschke <purschke@bnl.gov>.
+  1999-05-03 lpd Original version.
+ */
+
+#ifndef md5_INCLUDED
+#  define md5_INCLUDED
+
+/*
+ * This package supports both compile-time and run-time determination of CPU
+ * byte order.  If ARCH_IS_BIG_ENDIAN is defined as 0, the code will be
+ * compiled to run only on little-endian CPUs; if ARCH_IS_BIG_ENDIAN is
+ * defined as non-zero, the code will be compiled to run only on big-endian
+ * CPUs; if ARCH_IS_BIG_ENDIAN is not defined, the code will be compiled to
+ * run on either big- or little-endian CPUs, but will run slightly less
+ * efficiently on either one than if ARCH_IS_BIG_ENDIAN is defined.
+ */
+
+typedef unsigned char md5_byte_t; /* 8-bit byte */
+typedef unsigned int md5_word_t; /* 32-bit word */
+
+/* Define the state of the MD5 Algorithm. */
+typedef struct md5_state_s {
+    md5_word_t count[2];    /* message length in bits, lsw first */
+    md5_word_t abcd[4];        /* digest buffer */
+    md5_byte_t buf[64];        /* accumulate block */
+} md5_state_t;
+
+#ifdef __cplusplus
+extern "C" 
+{
+#endif
+
+/* Initialize the algorithm. */
+void md5_init(md5_state_t *pms);
+
+/* Append a string to the message. */
+void md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes);
+
+/* Finish the message and return the digest. */
+void md5_finish(md5_state_t *pms, md5_byte_t digest[16]);
+
+#ifdef __cplusplus
+}  /* end extern "C" */
+#endif
+
+#endif /* md5_INCLUDED */
diff --git a/src/main/c/seasocks/Connection.h b/src/main/c/seasocks/Connection.h
new file mode 100644
index 0000000..c1aa996
--- /dev/null
+++ b/src/main/c/seasocks/Connection.h
@@ -0,0 +1,177 @@
+// Copyright (c) 2013, Matt Godbolt
+// All rights reserved.
+// 
+// Redistribution and use in source and binary forms, with or without 
+// modification, are permitted provided that the following conditions are met:
+// 
+// Redistributions of source code must retain the above copyright notice, this 
+// list of conditions and the following disclaimer.
+// 
+// Redistributions in binary form must reproduce the above copyright notice, 
+// this list of conditions and the following disclaimer in the documentation 
+// and/or other materials provided with the distribution.
+// 
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
+// POSSIBILITY OF SUCH DAMAGE.
+
+#pragma once
+
+#include "seasocks/ResponseCode.h"
+#include "seasocks/WebSocket.h"
+
+#include <netinet/in.h>
+
+#include <sys/socket.h>
+
+#include <inttypes.h>
+#include <list>
+#include <memory>
+#include <string>
+#include <vector>
+
+namespace seasocks {
+
+class Logger;
+class ServerImpl;
+class PageRequest;
+class Response;
+
+class Connection : public WebSocket {
+public:
+    Connection(
+            std::shared_ptr<Logger> logger,
+            ServerImpl& server,
+            int fd,
+            const sockaddr_in& address);
+    virtual ~Connection();
+
+    bool write(const void* data, size_t size, bool flush);
+    void handleDataReadyForRead();
+    void handleDataReadyForWrite();
+
+    int getFd() const { return _fd; }
+
+    // From WebSocket.
+    virtual void send(const char* webSocketResponse) override;
+    virtual void send(const uint8_t* webSocketResponse, size_t length) override;
+    virtual void close() override;
+
+    // From Request.
+    virtual std::shared_ptr<Credentials> credentials() const override;
+    virtual const sockaddr_in& getRemoteAddress() const override { return _address; }
+    virtual const std::string& getRequestUri() const override;
+    virtual Request::Verb verb() const override { return Request::WebSocket; }
+    virtual size_t contentLength() const override { return 0; }
+    virtual const uint8_t* content() const override { return NULL; }
+    virtual bool hasHeader(const std::string&) const override;
+    virtual std::string getHeader(const std::string&) const override;
+
+    void setLinger();
+
+    size_t inputBufferSize() const { return _inBuf.size(); }
+    size_t outputBufferSize() const { return _outBuf.size(); }
+
+    size_t bytesReceived() const { return _bytesReceived; }
+    size_t bytesSent() const { return _bytesSent; }
+
+    // For testing:
+    std::vector<uint8_t>& getInputBuffer() { return _inBuf; }
+    void handleHixieWebSocket();
+    void handleHybiWebSocket();
+    void setHandler(std::shared_ptr<WebSocket::Handler> handler) {
+        _webSocketHandler = handler;
+    }
+    void handleNewData();
+
+private:
+    void finalise();
+    bool closed() const;
+
+    void closeWhenEmpty();
+    void closeInternal();
+
+    void handleHeaders();
+    void handleWebSocketKey3();
+    void handleWebSocketTextMessage(const char* message);
+    void handleWebSocketBinaryMessage(const std::vector<uint8_t>& message);
+    void handleBufferingPostData();
+    bool handlePageRequest();
+
+    bool bufferLine(const char* line);
+    bool bufferLine(const std::string& line);
+    bool flush();
+
+    bool handleHybiHandshake(int webSocketVersion, const std::string& webSocketKey);
+
+    // Send an error document. Returns 'true' for convenience in handle*() routines.
+    bool sendError(ResponseCode errorCode, const std::string& document);
+
+    // Send individual errors. Again all return true for convenience.
+    bool sendUnsupportedError(const std::string& reason);
+    bool send404();
+    bool sendBadRequest(const std::string& reason);
+    bool sendISE(const std::string& error);
+
+    void sendHybi(int opcode, const uint8_t* webSocketResponse, size_t messageLength);
+
+    bool sendResponse(std::shared_ptr<Response> response);
+
+    bool processHeaders(uint8_t* first, uint8_t* last);
+    bool sendData(const std::string& type, const char* start, size_t size);
+
+    struct Range {
+        long start;
+        long end;
+        size_t length() const { return end - start + 1; }
+    };
+
+    bool parseRange(const std::string& rangeStr, Range& range) const;
+    bool parseRanges(const std::string& range, std::list<Range>& ranges) const;
+    bool sendStaticData();
+
+    int safeSend(const void* data, size_t size);
+
+    void bufferResponseAndCommonHeaders(ResponseCode code);
+
+    std::list<Range> processRangesForStaticData(const std::list<Range>& ranges, long fileSize);
+
+    std::shared_ptr<Logger> _logger;
+    ServerImpl &_server;
+    int _fd;
+    bool _shutdown;
+    bool _hadSendError;
+    bool _closeOnEmpty;
+    bool _registeredForWriteEvents;
+    sockaddr_in _address;
+    size_t _bytesSent;
+    size_t _bytesReceived;
+    std::vector<uint8_t> _inBuf;
+    std::vector<uint8_t> _outBuf;
+    std::shared_ptr<WebSocket::Handler> _webSocketHandler;
+    bool _shutdownByUser;
+    std::unique_ptr<PageRequest> _request;
+
+    enum State {
+        INVALID,
+        READING_HEADERS,
+        READING_WEBSOCKET_KEY3,
+        HANDLING_HIXIE_WEBSOCKET,
+        HANDLING_HYBI_WEBSOCKET,
+        BUFFERING_POST_DATA,
+    };
+    State _state;
+
+    Connection(Connection& other) = delete;
+    Connection& operator =(Connection& other) = delete;
+};
+
+}  // namespace seasocks
diff --git a/src/main/c/seasocks/Credentials.h b/src/main/c/seasocks/Credentials.h
new file mode 100644
index 0000000..06532c2
--- /dev/null
+++ b/src/main/c/seasocks/Credentials.h
@@ -0,0 +1,73 @@
+// Copyright (c) 2013, Matt Godbolt
+// All rights reserved.
+// 
+// Redistribution and use in source and binary forms, with or without 
+// modification, are permitted provided that the following conditions are met:
+// 
+// Redistributions of source code must retain the above copyright notice, this 
+// list of conditions and the following disclaimer.
+// 
+// Redistributions in binary form must reproduce the above copyright notice, 
+// this list of conditions and the following disclaimer in the documentation 
+// and/or other materials provided with the distribution.
+// 
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
+// POSSIBILITY OF SUCH DAMAGE.
+
+#pragma once
+
+#include <iostream>
+#include <map>
+#include <set>
+#include <string>
+
+namespace seasocks {
+
+struct Credentials {
+    /**
+     * Whether user was successfuly authenticated.
+     */
+    bool authenticated;
+
+    /**
+     * e.g. "mgodbolt" (or "" for unauthenticated users)
+     */
+    std::string username;
+
+    /**
+     * Groups the user is in.
+     */
+    std::set<std::string> groups;
+
+    /**
+     * Attributes for the user.
+     */
+    std::map<std::string, std::string> attributes;
+
+    Credentials(): authenticated(false) {}
+};
+
+inline std::ostream &operator<<(std::ostream &os, const Credentials& credentials) {
+    os << "{authenticated:" << credentials.authenticated << ", username:'" << credentials.username << "', groups: {";
+    for (auto it = credentials.groups.begin(); it != credentials.groups.end(); ++it) {
+        if (it != credentials.groups.begin()) os << ", ";
+        os << *it;
+    }
+    os << "}, attrs: {";
+    for (auto it = credentials.attributes.begin(); it != credentials.attributes.end(); ++it) {
+        if (it != credentials.attributes.begin()) os << ", ";
+        os << it->first << "=" << it->second;
+    }
+    return os << "}}";
+}
+
+}  // namespace seasocks
diff --git a/src/main/c/seasocks/IgnoringLogger.h b/src/main/c/seasocks/IgnoringLogger.h
new file mode 100644
index 0000000..c1d4b1e
--- /dev/null
+++ b/src/main/c/seasocks/IgnoringLogger.h
@@ -0,0 +1,38 @@
+// Copyright (c) 2013, Matt Godbolt
+// All rights reserved.
+// 
+// Redistribution and use in source and binary forms, with or without 
+// modification, are permitted provided that the following conditions are met:
+// 
+// Redistributions of source code must retain the above copyright notice, this 
+// list of conditions and the following disclaimer.
+// 
+// Redistributions in binary form must reproduce the above copyright notice, 
+// this list of conditions and the following disclaimer in the documentation 
+// and/or other materials provided with the distribution.
+// 
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
+// POSSIBILITY OF SUCH DAMAGE.
+
+#pragma once
+
+#include "seasocks/Logger.h"
+
+namespace seasocks {
+
+class IgnoringLogger : public Logger {
+public:
+    virtual void log(Level level, const char* message) {
+    }
+};
+
+}  // namespace seasocks
diff --git a/src/main/c/seasocks/Logger.h b/src/main/c/seasocks/Logger.h
new file mode 100644
index 0000000..317c54a
--- /dev/null
+++ b/src/main/c/seasocks/Logger.h
@@ -0,0 +1,68 @@
+// Copyright (c) 2013, Matt Godbolt
+// All rights reserved.
+// 
+// Redistribution and use in source and binary forms, with or without 
+// modification, are permitted provided that the following conditions are met:
+// 
+// Redistributions of source code must retain the above copyright notice, this 
+// list of conditions and the following disclaimer.
+// 
+// Redistributions in binary form must reproduce the above copyright notice, 
+// this list of conditions and the following disclaimer in the documentation 
+// and/or other materials provided with the distribution.
+// 
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
+// POSSIBILITY OF SUCH DAMAGE.
+
+#pragma once
+
+namespace seasocks {
+
+/**
+ * Class to send debug logging information to.
+ */
+class Logger {
+public:
+    virtual ~Logger() {}
+
+    enum Level {
+        DEBUG,  // NB DEBUG is usually opted-out of at compile-time.
+        ACCESS, // Used to log page requests etc
+        INFO,
+        WARNING,
+        ERROR,
+        SEVERE,
+    };
+
+    virtual void log(Level level, const char* message) = 0;
+
+    void debug(const char* message, ...);
+    void access(const char* message, ...);
+    void info(const char* message, ...);
+    void warning(const char* message, ...);
+    void error(const char* message, ...);
+    void severe(const char* message, ...);
+
+    static const char* levelToString(Level level) {
+        switch (level) {
+        case DEBUG: return "debug";
+        case ACCESS: return "access";
+        case INFO: return "info";
+        case WARNING: return "warning";
+        case ERROR: return "ERROR";
+        case SEVERE: return "SEVERE";
+        default: return "???";
+        }
+    }
+};
+
+}  // namespace seasocks
diff --git a/src/main/c/seasocks/PageHandler.h b/src/main/c/seasocks/PageHandler.h
new file mode 100644
index 0000000..d9e671d
--- /dev/null
+++ b/src/main/c/seasocks/PageHandler.h
@@ -0,0 +1,45 @@
+// Copyright (c) 2013, Matt Godbolt
+// All rights reserved.
+// 
+// Redistribution and use in source and binary forms, with or without 
+// modification, are permitted provided that the following conditions are met:
+// 
+// Redistributions of source code must retain the above copyright notice, this 
+// list of conditions and the following disclaimer.
+// 
+// Redistributions in binary form must reproduce the above copyright notice, 
+// this list of conditions and the following disclaimer in the documentation 
+// and/or other materials provided with the distribution.
+// 
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
+// POSSIBILITY OF SUCH DAMAGE.
+
+#pragma once
+
+#include "seasocks/Credentials.h"
+#include "seasocks/Response.h"
+
+#include <memory>
+#include <string>
+
+namespace seasocks {
+
+class Request;
+
+class PageHandler {
+public:
+    virtual ~PageHandler() {}
+
+    virtual std::shared_ptr<Response> handle(const Request& request) = 0;
+};
+
+}
diff --git a/src/main/c/seasocks/PrintfLogger.h b/src/main/c/seasocks/PrintfLogger.h
new file mode 100644
index 0000000..f08b1b0
--- /dev/null
+++ b/src/main/c/seasocks/PrintfLogger.h
@@ -0,0 +1,51 @@
+// Copyright (c) 2013, Matt Godbolt
+// All rights reserved.
+// 
+// Redistribution and use in source and binary forms, with or without 
+// modification, are permitted provided that the following conditions are met:
+// 
+// Redistributions of source code must retain the above copyright notice, this 
+// list of conditions and the following disclaimer.
+// 
+// Redistributions in binary form must reproduce the above copyright notice, 
+// this list of conditions and the following disclaimer in the documentation 
+// and/or other materials provided with the distribution.
+// 
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
+// POSSIBILITY OF SUCH DAMAGE.
+
+#pragma once
+
+#include "seasocks/Logger.h"
+
+#include <stdio.h>
+
+namespace seasocks {
+
+class PrintfLogger : public Logger {
+public:
+    PrintfLogger(Level minLevelToLog = Level::DEBUG) : minLevelToLog(minLevelToLog) {
+    }
+
+    ~PrintfLogger() {
+    }
+
+    virtual void log(Level level, const char* message) {
+        if (level >= minLevelToLog) {
+            printf("%s: %s\n", levelToString(level), message);
+        }
+    }
+
+    Level minLevelToLog;
+};
+
+}  // namespace seasocks
diff --git a/src/main/c/seasocks/Request.cpp b/src/main/c/seasocks/Request.cpp
new file mode 100644
index 0000000..63be5a6
--- /dev/null
+++ b/src/main/c/seasocks/Request.cpp
@@ -0,0 +1,52 @@
+// Copyright (c) 2013, Matt Godbolt
+// All rights reserved.
+// 
+// Redistribution and use in source and binary forms, with or without 
+// modification, are permitted provided that the following conditions are met:
+// 
+// Redistributions of source code must retain the above copyright notice, this 
+// list of conditions and the following disclaimer.
+// 
+// Redistributions in binary form must reproduce the above copyright notice, 
+// this list of conditions and the following disclaimer in the documentation 
+// and/or other materials provided with the distribution.
+// 
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
+// POSSIBILITY OF SUCH DAMAGE.
+
+#include "seasocks/Request.h"
+
+#include <cstring>
+
+namespace seasocks {
+
+const char* Request::name(Verb v) {
+    switch(v) {
+    case Invalid: return "Invalid";
+    case WebSocket: return "WebSocket";
+    case Get: return "Get";
+    case Put: return "Put";
+    case Post: return "Post";
+    case Delete: return "Delete";
+    default: return "???";
+    }
+}
+
+Request::Verb Request::verb(const char* verb) {
+    if (std::strcmp(verb, "GET") == 0) return Request::Get;
+    if (std::strcmp(verb, "PUT") == 0) return Request::Put;
+    if (std::strcmp(verb, "POST") == 0) return Request::Post;
+    if (std::strcmp(verb, "DELETE") == 0) return Request::Delete;
+    return Request::Invalid;
+}
+
+}  // namespace seasocks
diff --git a/src/main/c/seasocks/Request.h b/src/main/c/seasocks/Request.h
new file mode 100644
index 0000000..7c0e78a
--- /dev/null
+++ b/src/main/c/seasocks/Request.h
@@ -0,0 +1,73 @@
+// Copyright (c) 2013, Matt Godbolt
+// All rights reserved.
+// 
+// Redistribution and use in source and binary forms, with or without 
+// modification, are permitted provided that the following conditions are met:
+// 
+// Redistributions of source code must retain the above copyright notice, this 
+// list of conditions and the following disclaimer.
+// 
+// Redistributions in binary form must reproduce the above copyright notice, 
+// this list of conditions and the following disclaimer in the documentation 
+// and/or other materials provided with the distribution.
+// 
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
+// POSSIBILITY OF SUCH DAMAGE.
+
+#pragma once
+
+#include "seasocks/Credentials.h"
+
+#include <netinet/in.h>
+
+#include <cstdint>
+#include <memory>
+
+namespace seasocks {
+
+class Request {
+public:
+    virtual ~Request() {}
+
+    enum Verb {
+        Invalid,
+        WebSocket,
+        Get,
+        Put,
+        Post,
+        Delete,
+    };
+
+    virtual Verb verb() const = 0;
+
+    static const char* name(Verb v);
+    static Verb verb(const char *verb);
+
+    /**
+     * Returns the credentials associated with this request.
+     */
+    virtual std::shared_ptr<Credentials> credentials() const = 0;
+
+    virtual const sockaddr_in& getRemoteAddress() const = 0;
+
+    virtual const std::string& getRequestUri() const = 0;
+
+    virtual size_t contentLength() const = 0;
+
+    virtual const uint8_t* content() const = 0;
+
+    virtual bool hasHeader(const std::string& name) const = 0;
+
+    virtual std::string getHeader(const std::string& name) const = 0;
+};
+
+}  // namespace seasocks
diff --git a/src/main/c/seasocks/Response.h b/src/main/c/seasocks/Response.h
new file mode 100644
index 0000000..02cf03b
--- /dev/null
+++ b/src/main/c/seasocks/Response.h
@@ -0,0 +1,62 @@
+// Copyright (c) 2013, Matt Godbolt
+// All rights reserved.
+// 
+// Redistribution and use in source and binary forms, with or without 
+// modification, are permitted provided that the following conditions are met:
+// 
+// Redistributions of source code must retain the above copyright notice, this 
+// list of conditions and the following disclaimer.
+// 
+// Redistributions in binary form must reproduce the above copyright notice, 
+// this list of conditions and the following disclaimer in the documentation 
+// and/or other materials provided with the distribution.
+// 
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
+// POSSIBILITY OF SUCH DAMAGE.
+
+#pragma once
+
+#include "seasocks/ResponseCode.h"
+
+#include <map>
+#include <memory>
+#include <string>
+
+namespace seasocks {
+
+class Response {
+public:
+    virtual ~Response() {}
+    virtual ResponseCode responseCode() const = 0;
+
+    virtual const char* payload() const = 0;
+    virtual size_t payloadSize() const = 0;
+
+    virtual std::string contentType() const = 0;
+
+    virtual bool keepConnectionAlive() const = 0;
+
+    typedef std::multimap<std::string, std::string> Headers;
+    virtual Headers getAdditionalHeaders() const = 0;
+
+    static std::shared_ptr<Response> unhandled();
+
+    static std::shared_ptr<Response> notFound();
+
+    static std::shared_ptr<Response> error(ResponseCode code, const std::string& error);
+
+    static std::shared_ptr<Response> textResponse(const std::string& response);
+    static std::shared_ptr<Response> jsonResponse(const std::string& response);
+    static std::shared_ptr<Response> htmlResponse(const std::string& response);
+};
+
+}
diff --git a/src/main/c/seasocks/ResponseBuilder.cpp b/src/main/c/seasocks/ResponseBuilder.cpp
new file mode 100644
index 0000000..f9f9b29
--- /dev/null
+++ b/src/main/c/seasocks/ResponseBuilder.cpp
@@ -0,0 +1,90 @@
+// Copyright (c) 2013, Matt Godbolt
+// All rights reserved.
+// 
+// Redistribution and use in source and binary forms, with or without 
+// modification, are permitted provided that the following conditions are met:
+// 
+// Redistributions of source code must retain the above copyright notice, this 
+// list of conditions and the following disclaimer.
+// 
+// Redistributions in binary form must reproduce the above copyright notice, 
+// this list of conditions and the following disclaimer in the documentation 
+// and/or other materials provided with the distribution.
+// 
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
+// POSSIBILITY OF SUCH DAMAGE.
+
+#include "seasocks/ResponseBuilder.h"
+
+#include "internal/ConcreteResponse.h"
+
+namespace seasocks {
+
+ResponseBuilder::ResponseBuilder(ResponseCode code) :
+        _code(code),
+        _contentType("text/plain"),
+        _keepAlive(true),
+        _stream(new std::ostringstream) {
+
+}
+
+ResponseBuilder& ResponseBuilder::asHtml() {
+    return withContentType("text/html");
+}
+
+ResponseBuilder& ResponseBuilder::asText() {
+    return withContentType("text/plain");
+}
+
+ResponseBuilder& ResponseBuilder::asJson() {
+    return withContentType("application/json");
+}
+
+ResponseBuilder& ResponseBuilder::withContentType(const std::string& contentType) {
+    _contentType = contentType;
+    return *this;
+}
+
+ResponseBuilder& ResponseBuilder::keepsConnectionAlive() {
+    _keepAlive = true;
+    return *this;
+}
+
+ResponseBuilder& ResponseBuilder::closesConnection() {
+    _keepAlive = false;
+    return *this;
+}
+
+ResponseBuilder& ResponseBuilder::withLocation(const std::string& location) {
+    return withHeader("Location", location);
+}
+
+ResponseBuilder& ResponseBuilder::setsCookie(const std::string& cookie, const std::string& value) {
+    return addHeader("Set-Cookie", cookie + "=" + value);
+}
+
+ResponseBuilder& ResponseBuilder::withHeader(const std::string& name, const std::string& value) {
+    _headers.erase(name);
+    _headers.insert(std::make_pair(name, value));
+    return *this;
+}
+
+ResponseBuilder& ResponseBuilder::addHeader(const std::string& name, const std::string& value) {
+    _headers.insert(std::make_pair(name, value));
+    return *this;
+}
+
+std::shared_ptr<Response> ResponseBuilder::build() {
+    return std::shared_ptr<Response>(new ConcreteResponse(_code, _stream->str(), _contentType, _headers, _keepAlive));
+}
+
+}
diff --git a/src/main/c/seasocks/ResponseBuilder.h b/src/main/c/seasocks/ResponseBuilder.h
new file mode 100644
index 0000000..0a268e8
--- /dev/null
+++ b/src/main/c/seasocks/ResponseBuilder.h
@@ -0,0 +1,77 @@
+// Copyright (c) 2013, Matt Godbolt
+// All rights reserved.
+// 
+// Redistribution and use in source and binary forms, with or without 
+// modification, are permitted provided that the following conditions are met:
+// 
+// Redistributions of source code must retain the above copyright notice, this 
+// list of conditions and the following disclaimer.
+// 
+// Redistributions in binary form must reproduce the above copyright notice, 
+// this list of conditions and the following disclaimer in the documentation 
+// and/or other materials provided with the distribution.
+// 
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
+// POSSIBILITY OF SUCH DAMAGE.
+
+#pragma once
+
+#include "seasocks/Response.h"
+
+#include <sstream>
+#include <string>
+
+namespace seasocks {
+
+class ResponseBuilder {
+    ResponseCode _code;
+    std::string _contentType;
+    bool _keepAlive;
+    Response::Headers _headers;
+    std::shared_ptr<std::ostringstream> _stream;
+public:
+    ResponseBuilder(ResponseCode code = ResponseCode::Ok);
+
+    ResponseBuilder& asHtml();
+    ResponseBuilder& asText();
+    ResponseBuilder& asJson();
+    ResponseBuilder& withContentType(const std::string& contentType);
+
+    ResponseBuilder& keepsConnectionAlive();
+    ResponseBuilder& closesConnection();
+
+    ResponseBuilder& withLocation(const std::string& location);
+
+    ResponseBuilder& setsCookie(const std::string& cookie, const std::string& value);
+
+    ResponseBuilder& withHeader(const std::string& name, const std::string& value);
+    template<typename T>
+    ResponseBuilder& withHeader(const std::string& name, const T& t) {
+        return withHeader(name, toString(t));
+    }
+
+    ResponseBuilder& addHeader(const std::string& name, const std::string& value);
+    template<typename T>
+    ResponseBuilder& addHeader(const std::string& name, const T& t) {
+        return addHeader(name, toString(t));
+    }
+
+    template<typename T>
+    ResponseBuilder& operator << (T&& t) {
+        (*_stream) << std::forward(t);
+        return *this;
+    }
+
+    std::shared_ptr<Response> build();
+};
+
+}
diff --git a/src/main/c/seasocks/ResponseCode.cpp b/src/main/c/seasocks/ResponseCode.cpp
new file mode 100644
index 0000000..71e88f6
--- /dev/null
+++ b/src/main/c/seasocks/ResponseCode.cpp
@@ -0,0 +1,42 @@
+// Copyright (c) 2013, Matt Godbolt
+// All rights reserved.
+// 
+// Redistribution and use in source and binary forms, with or without 
+// modification, are permitted provided that the following conditions are met:
+// 
+// Redistributions of source code must retain the above copyright notice, this 
+// list of conditions and the following disclaimer.
+// 
+// Redistributions in binary form must reproduce the above copyright notice, 
+// this list of conditions and the following disclaimer in the documentation 
+// and/or other materials provided with the distribution.
+// 
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
+// POSSIBILITY OF SUCH DAMAGE.
+
+#include "seasocks/ResponseCode.h"
+
+using namespace seasocks;
+
+bool isOk(seasocks::ResponseCode code) {
+    return static_cast<int>(code) < 400;
+}
+
+const char* name(ResponseCode code) {
+    switch (code) {
+#define SEASOCKS_DEFINE_RESPONSECODE(CODE,SYMBOLICNAME,STRINGNAME) case ResponseCode::SYMBOLICNAME: return STRINGNAME;
+#include "seasocks/ResponseCodeDefs.h"
+
+#undef SEASOCKS_DEFINE_RESPONSECODE
+    }
+    return "Unknown";
+}
diff --git a/src/main/c/seasocks/ResponseCode.h b/src/main/c/seasocks/ResponseCode.h
new file mode 100644
index 0000000..eab3463
--- /dev/null
+++ b/src/main/c/seasocks/ResponseCode.h
@@ -0,0 +1,42 @@
+// Copyright (c) 2013, Matt Godbolt
+// All rights reserved.
+// 
+// Redistribution and use in source and binary forms, with or without 
+// modification, are permitted provided that the following conditions are met:
+// 
+// Redistributions of source code must retain the above copyright notice, this 
+// list of conditions and the following disclaimer.
+// 
+// Redistributions in binary form must reproduce the above copyright notice, 
+// this list of conditions and the following disclaimer in the documentation 
+// and/or other materials provided with the distribution.
+// 
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
+// POSSIBILITY OF SUCH DAMAGE.
+
+#pragma once
+
+#include <string>
+
+namespace seasocks {
+
+#define SEASOCKS_DEFINE_RESPONSECODE(CODE,SYMBOLICNAME,STRINGNAME) SYMBOLICNAME = CODE,
+enum class ResponseCode {
+#include "seasocks/ResponseCodeDefs.h"
+
+};
+#undef SEASOCKS_DEFINE_RESPONSECODE
+
+}
+
+const char* name(seasocks::ResponseCode code);
+bool isOk(seasocks::ResponseCode code);
diff --git a/src/main/c/seasocks/ResponseCodeDefs.h b/src/main/c/seasocks/ResponseCodeDefs.h
new file mode 100644
index 0000000..9b9878d
--- /dev/null
+++ b/src/main/c/seasocks/ResponseCodeDefs.h
@@ -0,0 +1,68 @@
+// Copyright (c) 2013, Matt Godbolt
+// All rights reserved.
+// 
+// Redistribution and use in source and binary forms, with or without 
+// modification, are permitted provided that the following conditions are met:
+// 
+// Redistributions of source code must retain the above copyright notice, this 
+// list of conditions and the following disclaimer.
+// 
+// Redistributions in binary form must reproduce the above copyright notice, 
+// this list of conditions and the following disclaimer in the documentation 
+// and/or other materials provided with the distribution.
+// 
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
+// POSSIBILITY OF SUCH DAMAGE.
+
+// Not a normal header file - no header guards on purpose.
+// Do not directly include! Use ResponseCode.h instead
+
+#ifdef SEASOCKS_DEFINE_RESPONSECODE  // workaround for header check
+
+SEASOCKS_DEFINE_RESPONSECODE(100, Continue, "Continue")
+SEASOCKS_DEFINE_RESPONSECODE(101, WebSocketProtocolHandshake, "WebSocket Protocol Handshake")
+SEASOCKS_DEFINE_RESPONSECODE(102, Processing, "Processing")
+SEASOCKS_DEFINE_RESPONSECODE(103, Checkpoint, "Checkpoint")
+
+SEASOCKS_DEFINE_RESPONSECODE(200, Ok, "OK")
+SEASOCKS_DEFINE_RESPONSECODE(201, Created, "Created")
+SEASOCKS_DEFINE_RESPONSECODE(202, Accepted, "Accepted")
+SEASOCKS_DEFINE_RESPONSECODE(203, NonAuthoritativeInformation, "Non Authoritative Information")
+SEASOCKS_DEFINE_RESPONSECODE(204, NoContent, "No Content")
+SEASOCKS_DEFINE_RESPONSECODE(205, ResetContent, "Reset Content")
+SEASOCKS_DEFINE_RESPONSECODE(206, PartialContent, "Partial Content")
+SEASOCKS_DEFINE_RESPONSECODE(207, MultiStatus, "Multi-Status")
+SEASOCKS_DEFINE_RESPONSECODE(208, AlreadyReported, "Already Reported")
+SEASOCKS_DEFINE_RESPONSECODE(226, IMUsed, "IM Used")
+
+SEASOCKS_DEFINE_RESPONSECODE(300, MultipleChoices, "Multiple Choices")
+SEASOCKS_DEFINE_RESPONSECODE(301, MovedPermanently, "Moved Permanently")
+SEASOCKS_DEFINE_RESPONSECODE(302, Found, "Found")
+SEASOCKS_DEFINE_RESPONSECODE(303, SeeOther, "See Other")
+SEASOCKS_DEFINE_RESPONSECODE(304, NotModified, "Not Modified")
+SEASOCKS_DEFINE_RESPONSECODE(305, UseProxy, "Use Proxy")
+SEASOCKS_DEFINE_RESPONSECODE(306, SwitchProxy, "Switch Proxy")
+SEASOCKS_DEFINE_RESPONSECODE(307, TemporaryRedirect, "Temporary Redirect")
+SEASOCKS_DEFINE_RESPONSECODE(308, ResumeIncomplete, "Resume Incomplete")
+
+SEASOCKS_DEFINE_RESPONSECODE(400, BadRequest, "Bad Request")
+SEASOCKS_DEFINE_RESPONSECODE(401, Unauthorized, "Unauthorized")
+SEASOCKS_DEFINE_RESPONSECODE(402, PaymentRequired, "Payment Required")
+SEASOCKS_DEFINE_RESPONSECODE(403, Forbidden, "Forbidden")
+SEASOCKS_DEFINE_RESPONSECODE(404, NotFound, "Not Found")
+SEASOCKS_DEFINE_RESPONSECODE(405, MethodNotAllowed, "Method Not Allowed")
+// more here...
+
+SEASOCKS_DEFINE_RESPONSECODE(500, InternalServerError, "Internal Server Error")
+SEASOCKS_DEFINE_RESPONSECODE(501, NotImplemented, "Not Implemented")
+
+#endif
diff --git a/src/main/c/seasocks/Server.h b/src/main/c/seasocks/Server.h
new file mode 100644
index 0000000..4629a43
--- /dev/null
+++ b/src/main/c/seasocks/Server.h
@@ -0,0 +1,172 @@
+// Copyright (c) 2013, Matt Godbolt
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+//
+// Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+
+#pragma once
+
+#include "seasocks/ServerImpl.h"
+#include "seasocks/WebSocket.h"
+
+#include <sys/types.h>
+
+#include <atomic>
+#include <cstdint>
+#include <list>
+#include <map>
+#include <memory>
+#include <mutex>
+#include <string>
+#include <unordered_map>
+
+namespace seasocks {
+
+class Connection;
+class Logger;
+class PageHandler;
+class Request;
+class Response;
+
+class Server : private ServerImpl {
+public:
+    Server(std::shared_ptr<Logger> logger);
+    virtual ~Server();
+
+    void addPageHandler(std::shared_ptr<PageHandler> handler);
+
+    void addWebSocketHandler(const char* endpoint, std::shared_ptr<WebSocket::Handler> handler,
+            bool allowCrossOriginRequests = false);
+
+    // If we haven't heard anything ever on a connection for this long, kill it.
+    // This is possibly caused by bad WebSocket implementation in Chrome.
+    void setLameConnectionTimeoutSeconds(int seconds);
+
+    // Sets the maximum number of TCP level keepalives that we can miss before
+    // we let the OS consider the connection dead. We configure keepalives every second,
+    // so this is also the minimum number of seconds it takes to notice a badly-behaved
+    // dead connection, e.g. a laptop going into sleep mode or a hard-crashed machine.
+    // A value of 0 disables keep alives, which is the default.
+    void setMaxKeepAliveDrops(int maxKeepAliveDrops);
+
+    // Serves static content from the given port on the current thread, until terminate is called.
+    // Roughly equivalent to startListening(port); setStaticPath(staticPath); loop();
+    // Returns whether exiting was expected.
+    bool serve(const char* staticPath, int port);
+
+    // Starts listening on a given interface (in host order) and port.
+    // Returns true if all was ok.
+    bool startListening(uint32_t ipInHostOrder, int port);
+
+    // Starts listening on a port on all interfaces.
+    // Returns true if all was ok.
+    bool startListening(int port);
+
+    // Sets the path to server static content from.
+    void setStaticPath(const char* staticPath);
+
+    // Loop (until terminate called from another thread).
+    // Returns true if terminate() was used to exit the loop, false if there
+    // was an error.
+    bool loop();
+
+    // Runs a single iteration of the main loop, blocking for a given time.
+    // Returns immediately if terminate() has been called. Must be consistently
+    // called from the same thread. Returns an enum describing why it returned.
+    enum class PollResult {
+        Continue,
+        Terminated,
+        Error,
+    };
+    PollResult poll(int millisToBlock);
+
+    // Returns a file descriptor that can be polled for changes (e.g. by
+    // placing it in an epoll set. The poll() method above only need be called
+    // when this file descriptor is readable.
+    int fd() const { return _epollFd; }
+
+    // Terminate any loop() or poll(). May be called from any thread.
+    void terminate();
+
+    class Runnable {
+    public:
+        virtual ~Runnable() {}
+        virtual void run() = 0;
+    };
+    // Execute a task on the SeaSocks thread.
+    void execute(std::shared_ptr<Runnable> runnable);
+
+private:
+    // From ServerImpl
+    virtual void remove(Connection* connection) override;
+    virtual bool subscribeToWriteEvents(Connection* connection) override;
+    virtual bool unsubscribeFromWriteEvents(Connection* connection) override;
+    virtual const std::string& getStaticPath() const override { return _staticPath; }
+    virtual std::shared_ptr<WebSocket::Handler> getWebSocketHandler(const char* endpoint) const override;
+    virtual bool isCrossOriginAllowed(const std::string &endpoint) const override;
+    virtual std::shared_ptr<Response> handle(const Request &request) override;
+    virtual std::string getStatsDocument() const override;
+    virtual void checkThread() const override;
+
+    bool makeNonBlocking(int fd) const;
+    bool configureSocket(int fd) const;
+    void handleAccept();
+    std::shared_ptr<Runnable> popNextRunnable();
+    void processEventQueue();
+
+    void shutdown();
+
+    void checkAndDispatchEpoll(int epollMillis);
+    void handlePipe();
+    enum NewState { KeepOpen, Close };
+    NewState handleConnectionEvents(Connection* connection, uint32_t events);
+
+    // Connections, mapped to initial connection time.
+    std::map<Connection*, time_t> _connections;
+    std::shared_ptr<Logger> _logger;
+    int _listenSock;
+    int _epollFd;
+    int _eventFd;
+    int _maxKeepAliveDrops;
+    int _lameConnectionTimeoutSeconds;
+    time_t _nextDeadConnectionCheck;
+
+    struct WebSocketHandlerEntry {
+        std::shared_ptr<WebSocket::Handler> handler;
+        bool allowCrossOrigin;
+    };
+    typedef std::unordered_map<std::string, WebSocketHandlerEntry> WebSocketHandlerMap;
+    WebSocketHandlerMap _webSocketHandlerMap;
+
+    std::list<std::shared_ptr<PageHandler>> _pageHandlers;
+
+    std::mutex _pendingRunnableMutex;
+    std::list<std::shared_ptr<Runnable>> _pendingRunnables;
+
+    pid_t _threadId;
+
+    std::string _staticPath;
+    std::atomic<bool> _terminate;
+    std::atomic<bool> _expectedTerminate;
+};
+
+}  // namespace seasocks
diff --git a/src/main/c/seasocks/ServerImpl.h b/src/main/c/seasocks/ServerImpl.h
new file mode 100644
index 0000000..e7dd46a
--- /dev/null
+++ b/src/main/c/seasocks/ServerImpl.h
@@ -0,0 +1,54 @@
+// Copyright (c) 2013, Matt Godbolt
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+//
+// Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+
+#pragma once
+
+#include "seasocks/WebSocket.h"
+
+#include <string>
+
+namespace seasocks {
+
+class Connection;
+class Request;
+class Response;
+
+// Internal implementation used to give access to internals to Connections.
+class ServerImpl {
+public:
+    virtual ~ServerImpl() {}
+
+    virtual void remove(Connection* connection) = 0;
+    virtual bool subscribeToWriteEvents(Connection* connection) = 0;
+    virtual bool unsubscribeFromWriteEvents(Connection* connection) = 0;
+    virtual const std::string& getStaticPath() const = 0;
+    virtual std::shared_ptr<WebSocket::Handler> getWebSocketHandler(const char* endpoint) const = 0;
+    virtual bool isCrossOriginAllowed(const std::string &endpoint) const = 0;
+    virtual std::shared_ptr<Response> handle(const Request &request) = 0;
+    virtual std::string getStatsDocument() const = 0;
+    virtual void checkThread() const = 0;
+};
+
+}
diff --git a/src/main/c/seasocks/StringUtil.h b/src/main/c/seasocks/StringUtil.h
new file mode 100644
index 0000000..3109104
--- /dev/null
+++ b/src/main/c/seasocks/StringUtil.h
@@ -0,0 +1,48 @@
+// Copyright (c) 2013, Matt Godbolt
+// All rights reserved.
+// 
+// Redistribution and use in source and binary forms, with or without 
+// modification, are permitted provided that the following conditions are met:
+// 
+// Redistributions of source code must retain the above copyright notice, this 
+// list of conditions and the following disclaimer.
+// 
+// Redistributions in binary form must reproduce the above copyright notice, 
+// this list of conditions and the following disclaimer in the documentation 
+// and/or other materials provided with the distribution.
+// 
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
+// POSSIBILITY OF SUCH DAMAGE.
+
+#pragma once
+
+#include <netinet/in.h>
+
+#include <string>
+#include <vector>
+
+namespace seasocks {
+
+char* skipWhitespace(char* str);
+char* skipNonWhitespace(char* str);
+char* shift(char*& str);
+
+std::string getLastError();
+std::string formatAddress(const sockaddr_in& address);
+
+std::vector<std::string> split(const std::string& input, char splitChar);
+
+void replace(std::string& string, const std::string& find, const std::string& replace);
+
+bool caseInsensitiveSame(const std::string &lhs, const std::string &rhs);
+
+}  // namespace seasocks
diff --git a/src/main/c/seasocks/ToString.h b/src/main/c/seasocks/ToString.h
new file mode 100644
index 0000000..40f607e
--- /dev/null
+++ b/src/main/c/seasocks/ToString.h
@@ -0,0 +1,40 @@
+// Copyright (c) 2013, Matt Godbolt
+// All rights reserved.
+// 
+// Redistribution and use in source and binary forms, with or without 
+// modification, are permitted provided that the following conditions are met:
+// 
+// Redistributions of source code must retain the above copyright notice, this 
+// list of conditions and the following disclaimer.
+// 
+// Redistributions in binary form must reproduce the above copyright notice, 
+// this list of conditions and the following disclaimer in the documentation 
+// and/or other materials provided with the distribution.
+// 
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
+// POSSIBILITY OF SUCH DAMAGE.
+
+#pragma once
+
+#include <sstream>
+#include <string>
+
+namespace seasocks {
+
+template<typename T>
+std::string toString(const T& obj) {
+    std::stringstream str;
+    str << obj;
+    return str.str();
+}
+
+}
diff --git a/src/main/c/seasocks/WebSocket.h b/src/main/c/seasocks/WebSocket.h
new file mode 100644
index 0000000..f52509a
--- /dev/null
+++ b/src/main/c/seasocks/WebSocket.h
@@ -0,0 +1,86 @@
+// Copyright (c) 2013, Matt Godbolt
+// All rights reserved.
+// 
+// Redistribution and use in source and binary forms, with or without 
+// modification, are permitted provided that the following conditions are met:
+// 
+// Redistributions of source code must retain the above copyright notice, this 
+// list of conditions and the following disclaimer.
+// 
+// Redistributions in binary form must reproduce the above copyright notice, 
+// this list of conditions and the following disclaimer in the documentation 
+// and/or other materials provided with the distribution.
+// 
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
+// POSSIBILITY OF SUCH DAMAGE.
+
+#pragma once
+
+#include "seasocks/Request.h"
+
+#include <vector>
+
+namespace seasocks {
+
+class WebSocket : public Request {
+public:
+    /**
+     * Send the given text data. Must be called on the seasocks thread.
+     * See Server::execute for how to run work on the seasocks
+     * thread externally.
+     */
+    virtual void send(const char* data) = 0;
+    /**
+     * Send the given binary data. Must be called on the seasocks thread.
+     * See Server::execute for how to run work on the seasocks
+     * thread externally.
+     */
+    virtual void send(const uint8_t* data, size_t length) = 0;
+    /**
+     * Close the socket. It's invalid to access the socket after
+     * calling close(). The Handler::onDisconnect() call may occur
+     * at a later time.
+     */
+    virtual void close() = 0;
+
+    /**
+     * Interface to dealing with WebSocket connections.
+     */
+    class Handler {
+    public:
+        virtual ~Handler() { }
+
+        /**
+         * Called on the seasocks thread during initial connection.
+         */
+        virtual void onConnect(WebSocket* connection) = 0;
+        /**
+         * Called on the seasocks thread with upon receipt of a full text WebSocket message.
+         */
+        virtual void onData(WebSocket* connection, const char* data) {}
+        /**
+         * Called on the seasocks thread with upon receipt of a full binary WebSocket message.
+         */
+        virtual void onData(WebSocket* connection, const uint8_t* data, size_t length) {}
+        /**
+         * Called on the seasocks thread when the socket has been
+         */
+        virtual void onDisconnect(WebSocket* connection) = 0;
+    };
+
+protected:
+    // To delete a WebSocket, just close it. It is owned by the Server, and
+    // the server will delete it when it's finished.
+    virtual ~WebSocket() {}
+};
+
+}  // namespace seasocks
diff --git a/src/main/c/seasocks/util/CrackedUri.h b/src/main/c/seasocks/util/CrackedUri.h
new file mode 100644
index 0000000..681ea42
--- /dev/null
+++ b/src/main/c/seasocks/util/CrackedUri.h
@@ -0,0 +1,57 @@
+// Copyright (c) 2013, Matt Godbolt
+// All rights reserved.
+// 
+// Redistribution and use in source and binary forms, with or without 
+// modification, are permitted provided that the following conditions are met:
+// 
+// Redistributions of source code must retain the above copyright notice, this 
+// list of conditions and the following disclaimer.
+// 
+// Redistributions in binary form must reproduce the above copyright notice, 
+// this list of conditions and the following disclaimer in the documentation 
+// and/or other materials provided with the distribution.
+// 
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
+// POSSIBILITY OF SUCH DAMAGE.
+
+#pragma once
+
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+namespace seasocks {
+
+class CrackedUri {
+    std::vector<std::string> _path;
+    std::unordered_multimap<std::string, std::string> _queryParams;
+
+public:
+    CrackedUri(const std::string& uri);
+
+    const std::vector<std::string>& path() const { return _path; }
+    const std::unordered_multimap<std::string, std::string> queryParams() const { return _queryParams; }
+
+    bool hasParam(const std::string& param) const;
+
+    // Returns the user-supplied default if not present. Returns the first found in the case of multiple params.
+    std::string queryParam(const std::string& param, const std::string& def = std::string()) const;
+
+    std::vector<std::string> allQueryParams(const std::string& param) const;
+
+    // Returns a new uri with the frontmost path item shifted off.  The path
+    // is always guaranteed to be non-empty. In the case of shifting the last
+    // path element, returns a path of {""}.
+    CrackedUri shift() const;
+};
+
+}
diff --git a/src/main/c/seasocks/util/CrackedUriPageHandler.h b/src/main/c/seasocks/util/CrackedUriPageHandler.h
new file mode 100644
index 0000000..cf8c362
--- /dev/null
+++ b/src/main/c/seasocks/util/CrackedUriPageHandler.h
@@ -0,0 +1,45 @@
+// Copyright (c) 2013, Matt Godbolt
+// All rights reserved.
+// 
+// Redistribution and use in source and binary forms, with or without 
+// modification, are permitted provided that the following conditions are met:
+// 
+// Redistributions of source code must retain the above copyright notice, this 
+// list of conditions and the following disclaimer.
+// 
+// Redistributions in binary form must reproduce the above copyright notice, 
+// this list of conditions and the following disclaimer in the documentation 
+// and/or other materials provided with the distribution.
+// 
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
+// POSSIBILITY OF SUCH DAMAGE.
+
+#pragma once
+
+#include "seasocks/Request.h"
+#include "seasocks/Response.h"
+#include "seasocks/util/CrackedUri.h"
+
+#include <memory>
+
+namespace seasocks {
+
+class CrackedUriPageHandler {
+public:
+    virtual ~CrackedUriPageHandler() {}
+
+    virtual std::shared_ptr<Response> handle(const CrackedUri& uri, const Request& request) = 0;
+
+    typedef std::shared_ptr<CrackedUriPageHandler> Ptr;
+};
+
+}
diff --git a/src/main/c/seasocks/util/Html.h b/src/main/c/seasocks/util/Html.h
new file mode 100644
index 0000000..6b3e887
--- /dev/null
+++ b/src/main/c/seasocks/util/Html.h
@@ -0,0 +1,206 @@
+// Copyright (c) 2013, Matt Godbolt
+// All rights reserved.
+// 
+// Redistribution and use in source and binary forms, with or without 
+// modification, are permitted provided that the following conditions are met:
+// 
+// Redistributions of source code must retain the above copyright notice, this 
+// list of conditions and the following disclaimer.
+// 
+// Redistributions in binary form must reproduce the above copyright notice, 
+// this list of conditions and the following disclaimer in the documentation 
+// and/or other materials provided with the distribution.
+// 
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
+// POSSIBILITY OF SUCH DAMAGE.
+
+#pragma once
+
+#include "seasocks/ToString.h"
+
+#include <functional>
+#include <iostream>
+#include <list>
+#include <sstream>
+#include <string>
+
+namespace seasocks {
+
+namespace html {
+
+class Element {
+    bool _isTextNode;
+    bool _needsClose;
+    std::string _nameOrText;
+    std::string _attributes;
+    std::list<Element> _children;
+
+    void append() {
+    }
+
+    template<typename T, typename... Args>
+    void append(const T& t, Args&& ...args) {
+        operator<<(t);
+        append(std::forward<Args>(args)...);
+    }
+
+    Element() : _isTextNode(true), _needsClose(false) {}
+public:
+    template<typename... Args>
+    Element(const std::string& name, bool needsClose, Args&&... args) :
+        _isTextNode(false),
+        _needsClose(needsClose),
+        _nameOrText(name) {
+        append(std::forward<Args>(args)...);
+    }
+
+    template<typename T>
+    static Element textElement(const T& text) {
+        Element e;
+        e._nameOrText = toString(text);
+        return e;
+    }
+
+    template<typename T>
+    Element& addAttribute(const char* attr, const T& value) {
+        _attributes += std::string(" ") + attr + "=\"" + toString(value) + "\"";
+        return *this;
+    }
+
+    Element& clazz(const std::string& clazz) {
+        return addAttribute("class", clazz);
+    }
+
+    Element& title(const std::string& title) {
+        return addAttribute("title", title);
+    }
+
+    Element& style(const std::string& style) {
+        return addAttribute("style", style);
+    }
+
+    Element& alt(const std::string& alt) {
+        return addAttribute("alt", alt);
+    }
+
+    Element& hidden() {
+        return addAttribute("style", "display:none;");
+    }
+
+    Element& id(const std::string& id) {
+        return addAttribute("id", id);
+    }
+
+    Element& operator <<(const char* child) {
+        _children.push_back(textElement(child));
+        return *this;
+    }
+
+    Element& operator <<(const std::string& child) {
+        _children.push_back(textElement(child));
+        return *this;
+    }
+
+    Element& operator <<(const Element& child) {
+        _children.push_back(child);
+        return *this;
+    }
+
+    template<class T>
+    typename std::enable_if<std::is_integral<T>::value || std::is_floating_point<T>::value, Element&>::type
+    operator <<(const T& child) {
+        _children.push_back(textElement(child));
+        return *this;
+    }
+
+    friend std::ostream& operator << (std::ostream& os, const Element& elem) {
+        if (elem._isTextNode) {
+            os << elem._nameOrText;
+            for (auto it = elem._children.cbegin(); it != elem._children.cend(); ++it) {
+                os << *it;
+            }
+            return os;
+        }
+        os << "<" << elem._nameOrText << elem._attributes << ">";
+        for (auto it = elem._children.cbegin(); it != elem._children.cend(); ++it) {
+            os << *it;
+        }
+        if (elem._needsClose) {
+            os << "</" << elem._nameOrText << ">";
+        }
+        return os;
+    }
+
+    template<typename C, typename F>
+    Element& addAll(const C& container, F functor) {
+        for (auto it = container.cbegin(); it != container.cend(); ++it) {
+            operator<<(functor(*it));
+        }
+        return *this;
+    }
+
+    std::string str() const {
+        std::ostringstream os;
+        os << *this;
+        return os.str();
+    }
+};
+
+#define HTMLELEM(XX) template<typename... Args> inline Element XX(Args&&... args) { return Element(#XX, true, std::forward<Args>(args)...); }
+HTMLELEM(html)
+HTMLELEM(head)
+HTMLELEM(title)
+HTMLELEM(body)
+HTMLELEM(h1)
+HTMLELEM(h2)
+HTMLELEM(h3)
+HTMLELEM(h4)
+HTMLELEM(h5)
+HTMLELEM(table)
+HTMLELEM(thead)
+HTMLELEM(tbody)
+HTMLELEM(tr)
+HTMLELEM(td)
+HTMLELEM(th)
+HTMLELEM(div)
+HTMLELEM(span)
+HTMLELEM(ul)
+HTMLELEM(ol)
+HTMLELEM(li)
+HTMLELEM(label)
+HTMLELEM(button)
+#undef HTMLELEM
+
+inline Element empty() { return Element::textElement(""); }
+
+template<typename T>
+inline Element text(const T& t) { return Element::textElement(t); }
+
+inline Element img(const std::string& src) { return Element("img", false).addAttribute("src", src); }
+
+inline Element checkbox() { return Element("input", false).addAttribute("type", "checkbox"); }
+
+inline Element externalScript(const std::string& src) { return Element("script", true).addAttribute("src", src).addAttribute("type", "text/javascript"); }
+
+inline Element inlineScript(const std::string& script) { return Element("script", true, script).addAttribute("type", "text/javascript"); }
+
+inline Element link(const std::string& href, const std::string& rel) {
+    return Element("link", false).addAttribute("href", href).addAttribute("rel", rel);
+}
+
+template<typename...Args> inline Element a(const std::string& href, Args&&... args) {
+    return Element("a", true, std::forward<Args>(args)...).addAttribute("href", href);
+}
+
+}  // namespace html
+
+}  // namespace seasocks
diff --git a/src/main/c/seasocks/util/Json.h b/src/main/c/seasocks/util/Json.h
new file mode 100644
index 0000000..c8a69dc
--- /dev/null
+++ b/src/main/c/seasocks/util/Json.h
@@ -0,0 +1,233 @@
+// Copyright (c) 2013, Matt Godbolt
+// All rights reserved.
+// 
+// Redistribution and use in source and binary forms, with or without 
+// modification, are permitted provided that the following conditions are met:
+// 
+// Redistributions of source code must retain the above copyright notice, this 
+// list of conditions and the following disclaimer.
+// 
+// Redistributions in binary form must reproduce the above copyright notice, 
+// this list of conditions and the following disclaimer in the documentation 
+// and/or other materials provided with the distribution.
+// 
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
+// POSSIBILITY OF SUCH DAMAGE.
+
+#pragma once
+
+#include <algorithm>
+#include <ctime>
+#include <iostream>
+#include <map>
+#include <sstream>
+#include <string>
+#include <type_traits>
+#include <vector>
+
+namespace seasocks {
+
+///////////////////////////////////
+
+inline void jsonToStream(std::ostream& str) {}
+
+void jsonToStream(std::ostream& str, const char* t);
+
+void jsonToStream(std::ostream& str, bool b);
+
+inline void jsonToStream(std::ostream& str, const std::string& t) {
+    jsonToStream(str, t.c_str());
+}
+
+template<typename O>
+class is_jsonable {
+    template<typename OO>
+    static auto test(int)
+    -> decltype(&OO::jsonToStream, std::true_type());
+
+    template<typename>
+    static auto test(...) -> std::false_type;
+
+public:
+    static constexpr bool value = decltype(test<O>(0))::value;
+};
+
+template<typename T>
+class is_streamable {
+    template<typename TT>
+    static auto test(int)
+    -> decltype(std::declval<std::ostream&>() << std::declval<TT>(), std::true_type());
+
+    template<typename>
+    static auto test(...) -> std::false_type;
+
+public:
+    static constexpr bool value = decltype(test<T>(0))::value;
+};
+
+template<typename T>
+typename std::enable_if<std::is_fundamental<T>::value, void>::type
+jsonToStream(std::ostream& str, const T& t) {
+    str << t;
+}
+
+template<typename T>
+typename std::enable_if<is_jsonable<T>::value, void>::type
+jsonToStream(std::ostream& str, const T& t) {
+    t.jsonToStream(str);
+}
+
+template<typename T>
+typename std::enable_if<
+    !std::is_fundamental<T>::value
+    && is_streamable<T>::value
+    && !is_jsonable<T>::value, void>::type
+jsonToStream(std::ostream& str, const T& t) {
+    str << '"' << t << '"';
+}
+
+template<typename T, typename ... Args>
+void jsonToStream(std::ostream& str, const T& t, Args&&... args) {
+    static_assert(sizeof...(Args) > 0, "Cannot stream an object with no jsonToStream or operator<< method.");
+    jsonToStream(str, t);
+    str << ",";
+    jsonToStream(str, std::forward<Args>(args)...);
+}
+
+///////////////////////////////////
+
+inline void jsonKeyPairToStream(std::ostream& str) {}
+
+template<typename T>
+void jsonKeyPairToStream(std::ostream& str, const char* key, const T& value) {
+    jsonToStream(str, key);
+    str << ":";
+    jsonToStream(str, value);
+}
+
+template<typename T>
+void jsonKeyPairToStream(std::ostream& str, const std::string& key, const T& value) {
+    jsonKeyPairToStream(str, key.c_str(), value);
+}
+
+template<typename T>
+void jsonKeyPairToStream(std::ostream& str, const T&) {
+    static_assert(!std::is_same<T, T>::value,  // To make the assertion depend on T
+            "Requires an even number of parameters. If you're trying to build a map from an existing std::map or similar, use makeMapFromContainer");
+}
+
+template<typename K, typename V, typename... Args>
+void jsonKeyPairToStream(std::ostream& str, const K& key, const V& value, Args&&... args) {
+    jsonKeyPairToStream(str, key, value);
+    str << ",";
+    jsonKeyPairToStream(str, std::forward<Args>(args)...);
+}
+
+struct JsonnedString : std::string {
+    JsonnedString() {}
+    JsonnedString(const std::string& s) : std::string(s) {}
+    JsonnedString(const std::stringstream& str) : std::string(str.str()) {}
+    void jsonToStream(std::ostream &o) const {
+        o << *this;
+    }
+};
+static_assert(is_streamable<JsonnedString>::value, "Internal stream problem");
+static_assert(is_jsonable<JsonnedString>::value, "Internal stream problem");
+
+struct EpochTimeAsLocal {
+    time_t t;
+    EpochTimeAsLocal(time_t t) : t(t) {}
+    void jsonToStream(std::ostream &o) const;
+};
+static_assert(is_jsonable<EpochTimeAsLocal>::value, "Internal stream problem");
+
+template<typename... Args>
+JsonnedString makeMap(Args&&... args) {
+    std::stringstream str;
+    str << '{';
+    jsonKeyPairToStream(str, std::forward<Args>(args)...);
+    str << '}';
+    return JsonnedString(str);
+}
+
+template<typename T>
+JsonnedString makeMapFromContainer(const T& m) {
+    std::stringstream str;
+    str << "{";
+    bool first = true;
+    for (const auto &it : m) {
+        if (!first) str << ",";
+        first = false;
+        jsonKeyPairToStream(str, it.first, it.second);
+    }
+    str << "}";
+    return JsonnedString(str);
+}
+
+template<typename ... Args>
+JsonnedString makeArray(Args&&... args) {
+    std::stringstream str;
+    str << '[';
+    jsonToStream(str, std::forward<Args>(args)...);
+    str << ']';
+    return JsonnedString(str);
+}
+
+template<typename T>
+JsonnedString makeArrayFromContainer(const T &list) {
+    std::stringstream str;
+    str << '[';
+    bool first = true;
+    for (const auto &s : list) {
+        if (!first) {
+            str << ',';
+        }
+        first = false;
+        jsonToStream(str, s);
+    };
+    str << ']';
+    return JsonnedString(str);
+}
+
+template<typename T>
+JsonnedString makeArray(const std::initializer_list<T> &list) {
+    std::stringstream str;
+    str << '[';
+    bool first = true;
+    for (const auto &s : list) {
+        if (!first) {
+            str << ',';
+        }
+        first = false;
+        jsonToStream(str, s);
+    };
+    str << ']';
+    return JsonnedString(str);
+}
+
+template<typename ... Args>
+JsonnedString makeExecString(const char* function, Args&&... args) {
+    std::stringstream str;
+    str << function << '(';
+    jsonToStream(str, std::forward<Args>(args)...);
+    str << ')';
+    return JsonnedString(str);
+}
+
+template<typename T>
+JsonnedString to_json(const T &obj) {
+    std::stringstream str;
+    jsonToStream(str, obj);
+    return str.str();
+}
+
+}
diff --git a/src/main/c/seasocks/util/PathHandler.h b/src/main/c/seasocks/util/PathHandler.h
new file mode 100644
index 0000000..275f829
--- /dev/null
+++ b/src/main/c/seasocks/util/PathHandler.h
@@ -0,0 +1,68 @@
+// Copyright (c) 2013, Matt Godbolt
+// All rights reserved.
+// 
+// Redistribution and use in source and binary forms, with or without 
+// modification, are permitted provided that the following conditions are met:
+// 
+// Redistributions of source code must retain the above copyright notice, this 
+// list of conditions and the following disclaimer.
+// 
+// Redistributions in binary form must reproduce the above copyright notice, 
+// this list of conditions and the following disclaimer in the documentation 
+// and/or other materials provided with the distribution.
+// 
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
+// POSSIBILITY OF SUCH DAMAGE.
+
+#pragma once
+
+#include "seasocks/util/CrackedUriPageHandler.h"
+
+#include <string>
+
+namespace seasocks {
+
+// Handles a subpath of a website.  Passes on a shifted path to the registered
+// subhandlers.  Useful, for example, to place a bunch of handlers beneath a
+// common subpath on the server, e.g.
+// PathHandler("debug")
+//   .add(make_shared<DebugStatsHandler>())
+//   .add(make_shared<AnotherDebugThing>())
+//   .add(...) etc
+// Each handler's CrackedUri will be shifted (i.e. won't have the "debug"
+// prefix)
+class PathHandler : public CrackedUriPageHandler {
+    std::string _path;
+    std::vector<CrackedUriPageHandler::Ptr> _handlers;
+
+public:
+    PathHandler(const std::string &path) : _path(path) {}
+    template<typename... Args>
+    PathHandler(const std::string &path, Args&&... args) : _path(path) {
+        addMany(std::forward<Args>(args)...);
+    }
+
+    CrackedUriPageHandler::Ptr add(const CrackedUriPageHandler::Ptr& handler);
+
+    void addMany() {}
+    void addMany(const CrackedUriPageHandler::Ptr& handler) { add(handler); }
+    template<typename... Rest>
+    void addMany(const CrackedUriPageHandler::Ptr& handler, Rest&&... rest) {
+        add(handler);
+        addMany(std::forward<Rest>(rest)...);
+    }
+
+    virtual std::shared_ptr<Response> handle(
+            const CrackedUri& uri, const Request& request) override;
+};
+
+}
diff --git a/src/main/c/seasocks/util/RootPageHandler.h b/src/main/c/seasocks/util/RootPageHandler.h
new file mode 100644
index 0000000..2c2ce1a
--- /dev/null
+++ b/src/main/c/seasocks/util/RootPageHandler.h
@@ -0,0 +1,49 @@
+// Copyright (c) 2013, Matt Godbolt
+// All rights reserved.
+// 
+// Redistribution and use in source and binary forms, with or without 
+// modification, are permitted provided that the following conditions are met:
+// 
+// Redistributions of source code must retain the above copyright notice, this 
+// list of conditions and the following disclaimer.
+// 
+// Redistributions in binary form must reproduce the above copyright notice, 
+// this list of conditions and the following disclaimer in the documentation 
+// and/or other materials provided with the distribution.
+// 
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
+// POSSIBILITY OF SUCH DAMAGE.
+
+#pragma once
+
+#include "seasocks/PageHandler.h"
+#include "seasocks/util/CrackedUriPageHandler.h"
+
+#include <memory>
+#include <vector>
+
+namespace seasocks {
+
+class RootPageHandler : public PageHandler {
+    std::vector<CrackedUriPageHandler::Ptr> _handlers;
+
+public:
+    RootPageHandler();
+    virtual ~RootPageHandler();
+
+    CrackedUriPageHandler::Ptr add(const CrackedUriPageHandler::Ptr& handler);
+
+    // From PageHandler.
+    virtual std::shared_ptr<Response> handle(const Request& request) override;
+};
+
+}
diff --git a/src/main/c/seasocks/util/StaticResponseHandler.h b/src/main/c/seasocks/util/StaticResponseHandler.h
new file mode 100644
index 0000000..ff6c5e7
--- /dev/null
+++ b/src/main/c/seasocks/util/StaticResponseHandler.h
@@ -0,0 +1,51 @@
+// Copyright (c) 2013, Matt Godbolt
+// All rights reserved.
+// 
+// Redistribution and use in source and binary forms, with or without 
+// modification, are permitted provided that the following conditions are met:
+// 
+// Redistributions of source code must retain the above copyright notice, this 
+// list of conditions and the following disclaimer.
+// 
+// Redistributions in binary form must reproduce the above copyright notice, 
+// this list of conditions and the following disclaimer in the documentation 
+// and/or other materials provided with the distribution.
+// 
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
+// POSSIBILITY OF SUCH DAMAGE.
+
+#include <seasocks/util/CrackedUriPageHandler.h>
+#include <seasocks/Response.h>
+
+#include <memory>
+
+namespace seasocks {
+
+// Returns a canned response for a given pathname.
+class StaticResponseHandler : public CrackedUriPageHandler {
+    std::string _path;
+    std::shared_ptr<Response> _response;
+
+public:
+    StaticResponseHandler(const std::string& path, std::shared_ptr<Response> response)
+    : _path(path), _response(response) {}
+
+    virtual std::shared_ptr<Response> handle(
+            const CrackedUri& uri,
+            const Request&) override {
+        if (uri.path().size() != 1 || uri.path()[0] != _path)
+            return Response::unhandled();
+        return _response;
+    }
+};
+
+}
diff --git a/src/main/c/sha1/Makefile b/src/main/c/sha1/Makefile
new file mode 100644
index 0000000..66aaf2b
--- /dev/null
+++ b/src/main/c/sha1/Makefile
@@ -0,0 +1,41 @@
+#
+#  Makefile
+#  
+#  Copyright (C) 1998, 2009
+#  Paul E. Jones <paulej@packetizer.com>
+#  All Rights Reserved.
+#
+#############################################################################
+#  $Id: Makefile 12 2009-06-22 19:34:25Z paulej $
+#############################################################################
+#
+#  Description:
+#	This is a makefile for UNIX to build the programs sha, shacmp, and
+#	shatest
+#
+#
+
+CC	= g++
+
+CFLAGS	= -c -O2 -Wall -D_FILE_OFFSET_BITS=64
+
+LIBS	=
+
+OBJS	= sha1.o
+
+all: sha shacmp shatest
+
+sha: sha.o $(OBJS)
+	$(CC) -o $@ sha.o $(OBJS) $(LIBS)
+
+shacmp: shacmp.o $(OBJS)
+	$(CC) -o $@ shacmp.o $(OBJS) $(LIBS)
+
+shatest: shatest.o $(OBJS)
+	$(CC) -o $@ shatest.o $(OBJS) $(LIBS)
+
+%.o: %.cpp
+	$(CC) $(CFLAGS) -o $@ $<
+
+clean:
+	$(RM) *.o sha shacmp shatest
diff --git a/src/main/c/sha1/Makefile.nt b/src/main/c/sha1/Makefile.nt
new file mode 100644
index 0000000..9bacf64
--- /dev/null
+++ b/src/main/c/sha1/Makefile.nt
@@ -0,0 +1,48 @@
+#

+#  Makefile.nt

+#  

+#  Copyright (C) 1998, 2009

+#  Paul E. Jones <paulej@packetizer.com>

+#  All Rights Reserved.

+#

+#############################################################################

+#  $Id: Makefile.nt 13 2009-06-22 20:20:32Z paulej $

+#############################################################################

+#

+#  Description:

+#	This is a makefile for Win32 to build the programs sha, shacmp, and

+#	shatest

+#

+#  Portability Issues:

+#	Designed to work with Visual C++

+#

+#

+

+.silent:

+

+!include <win32.mak>

+

+RM	= del /q

+

+LIBS	= $(conlibs) setargv.obj

+

+CFLAGS	= -D _CRT_SECURE_NO_WARNINGS /EHsc /O2 /W3

+

+OBJS	= sha1.obj

+

+all: sha.exe shacmp.exe shatest.exe

+

+sha.exe: sha.obj $(OBJS)

+	$(link) $(conflags) -out:$@ sha.obj $(OBJS) $(LIBS)

+

+shacmp.exe: shacmp.obj $(OBJS)

+	$(link) $(conflags) -out:$@ shacmp.obj $(OBJS) $(LIBS)

+

+shatest.exe: shatest.obj $(OBJS)

+	$(link) $(conflags) -out:$@ shatest.obj $(OBJS) $(LIBS)

+

+.cpp.obj:

+	$(cc) $(CFLAGS) $(cflags) $(cvars) $<

+

+clean:

+	$(RM) *.obj sha.exe shacmp.exe shatest.exe

diff --git a/src/main/c/sha1/license.txt b/src/main/c/sha1/license.txt
new file mode 100644
index 0000000..8d7f394
--- /dev/null
+++ b/src/main/c/sha1/license.txt
@@ -0,0 +1,14 @@
+Copyright (C) 1998, 2009

+Paul E. Jones <paulej@packetizer.com>

+

+Freeware Public License (FPL)

+

+This software is licensed as "freeware."  Permission to distribute

+this software in source and binary forms, including incorporation 

+into other products, is hereby granted without a fee.  THIS SOFTWARE 

+IS PROVIDED 'AS IS' AND WITHOUT ANY EXPRESSED OR IMPLIED WARRANTIES, 

+INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY 

+AND FITNESS FOR A PARTICULAR PURPOSE.  THE AUTHOR SHALL NOT BE HELD 

+LIABLE FOR ANY DAMAGES RESULTING FROM THE USE OF THIS SOFTWARE, EITHER 

+DIRECTLY OR INDIRECTLY, INCLUDING, BUT NOT LIMITED TO, LOSS OF DATA 

+OR DATA BEING RENDERED INACCURATE.

diff --git a/src/main/c/sha1/sha1.cpp b/src/main/c/sha1/sha1.cpp
new file mode 100644
index 0000000..9a42766
--- /dev/null
+++ b/src/main/c/sha1/sha1.cpp
@@ -0,0 +1,615 @@
+// Copyright (c) 2013, Matt Godbolt
+// All rights reserved.
+// 
+// Redistribution and use in source and binary forms, with or without 
+// modification, are permitted provided that the following conditions are met:
+// 
+// Redistributions of source code must retain the above copyright notice, this 
+// list of conditions and the following disclaimer.
+// 
+// Redistributions in binary form must reproduce the above copyright notice, 
+// this list of conditions and the following disclaimer in the documentation 
+// and/or other materials provided with the distribution.
+// 
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
+// POSSIBILITY OF SUCH DAMAGE.
+
+// DO NOT REFORMAT <- for tidy
+/*
+ *  sha1.cpp
+ *
+ *  Copyright (C) 1998, 2009
+ *  Paul E. Jones <paulej@packetizer.com>
+ *  All Rights Reserved.
+ *
+ *****************************************************************************
+ *  $Id: sha1.cpp 12 2009-06-22 19:34:25Z paulej $
+ *****************************************************************************
+ *
+ *  Description:
+ *      This class implements the Secure Hashing Standard as defined
+ *      in FIPS PUB 180-1 published April 17, 1995.
+ *
+ *      The Secure Hashing Standard, which uses the Secure Hashing
+ *      Algorithm (SHA), produces a 160-bit message digest for a
+ *      given data stream.  In theory, it is highly improbable that
+ *      two messages will produce the same message digest.  Therefore,
+ *      this algorithm can serve as a means of providing a "fingerprint"
+ *      for a message.
+ *
+ *  Portability Issues:
+ *      SHA-1 is defined in terms of 32-bit "words".  This code was
+ *      written with the expectation that the processor has at least
+ *      a 32-bit machine word size.  If the machine word size is larger,
+ *      the code should still function properly.  One caveat to that
+ *      is that the input functions taking characters and character arrays
+ *      assume that only 8 bits of information are stored in each character.
+ *
+ *  Caveats:
+ *      SHA-1 is designed to work with messages less than 2^64 bits long.
+ *      Although SHA-1 allows a message digest to be generated for
+ *      messages of any number of bits less than 2^64, this implementation
+ *      only works with messages with a length that is a multiple of 8
+ *      bits.
+ *
+ */
+
+
+#include "sha1.h"
+
+/*  
+ *  SHA1
+ *
+ *  Description:
+ *      This is the constructor for the sha1 class.
+ *
+ *  Parameters:
+ *      None.
+ *
+ *  Returns:
+ *      Nothing.
+ *
+ *  Comments:
+ *
+ */
+SHA1::SHA1()
+{
+    Reset();
+}
+
+/*  
+ *  ~SHA1
+ *
+ *  Description:
+ *      This is the destructor for the sha1 class
+ *
+ *  Parameters:
+ *      None.
+ *
+ *  Returns:
+ *      Nothing.
+ *
+ *  Comments:
+ *
+ */
+SHA1::~SHA1()
+{
+    // The destructor does nothing
+}
+
+/*  
+ *  Reset
+ *
+ *  Description:
+ *      This function will initialize the sha1 class member variables
+ *      in preparation for computing a new message digest.
+ *
+ *  Parameters:
+ *      None.
+ *
+ *  Returns:
+ *      Nothing.
+ *
+ *  Comments:
+ *
+ */
+void SHA1::Reset()
+{
+    Length_Low          = 0;
+    Length_High         = 0;
+    Message_Block_Index = 0;
+
+    H[0]        = 0x67452301;
+    H[1]        = 0xEFCDAB89;
+    H[2]        = 0x98BADCFE;
+    H[3]        = 0x10325476;
+    H[4]        = 0xC3D2E1F0;
+
+    Computed    = false;
+    Corrupted   = false;
+}
+
+/*  
+ *  Result
+ *
+ *  Description:
+ *      This function will return the 160-bit message digest into the
+ *      array provided.
+ *
+ *  Parameters:
+ *      message_digest_array: [out]
+ *          This is an array of five unsigned integers which will be filled
+ *          with the message digest that has been computed.
+ *
+ *  Returns:
+ *      True if successful, false if it failed.
+ *
+ *  Comments:
+ *
+ */
+bool SHA1::Result(unsigned *message_digest_array)
+{
+    int i;                                  // Counter
+
+    if (Corrupted)
+    {
+        return false;
+    }
+
+    if (!Computed)
+    {
+        PadMessage();
+        Computed = true;
+    }
+
+    for(i = 0; i < 5; i++)
+    {
+        message_digest_array[i] = H[i];
+    }
+
+    return true;
+}
+
+/*  
+ *  Input
+ *
+ *  Description:
+ *      This function accepts an array of octets as the next portion of
+ *      the message.
+ *
+ *  Parameters:
+ *      message_array: [in]
+ *          An array of characters representing the next portion of the
+ *          message.
+ *
+ *  Returns:
+ *      Nothing.
+ *
+ *  Comments:
+ *
+ */
+void SHA1::Input(   const unsigned char *message_array,
+                    unsigned            length)
+{
+    if (!length)
+    {
+        return;
+    }
+
+    if (Computed || Corrupted)
+    {
+        Corrupted = true;
+        return;
+    }
+
+    while(length-- && !Corrupted)
+    {
+        Message_Block[Message_Block_Index++] = (*message_array & 0xFF);
+
+        Length_Low += 8;
+        Length_Low &= 0xFFFFFFFF;               // Force it to 32 bits
+        if (Length_Low == 0)
+        {
+            Length_High++;
+            Length_High &= 0xFFFFFFFF;          // Force it to 32 bits
+            if (Length_High == 0)
+            {
+                Corrupted = true;               // Message is too long
+            }
+        }
+
+        if (Message_Block_Index == 64)
+        {
+            ProcessMessageBlock();
+        }
+
+        message_array++;
+    }
+}
+
+/*  
+ *  Input
+ *
+ *  Description:
+ *      This function accepts an array of octets as the next portion of
+ *      the message.
+ *
+ *  Parameters:
+ *      message_array: [in]
+ *          An array of characters representing the next portion of the
+ *          message.
+ *      length: [in]
+ *          The length of the message_array
+ *
+ *  Returns:
+ *      Nothing.
+ *
+ *  Comments:
+ *
+ */
+void SHA1::Input(   const char  *message_array,
+                    unsigned    length)
+{
+    Input((unsigned char *) message_array, length);
+}
+
+/*  
+ *  Input
+ *
+ *  Description:
+ *      This function accepts a single octets as the next message element.
+ *
+ *  Parameters:
+ *      message_element: [in]
+ *          The next octet in the message.
+ *
+ *  Returns:
+ *      Nothing.
+ *
+ *  Comments:
+ *
+ */
+void SHA1::Input(unsigned char message_element)
+{
+    Input(&message_element, 1);
+}
+
+/*  
+ *  Input
+ *
+ *  Description:
+ *      This function accepts a single octet as the next message element.
+ *
+ *  Parameters:
+ *      message_element: [in]
+ *          The next octet in the message.
+ *
+ *  Returns:
+ *      Nothing.
+ *
+ *  Comments:
+ *
+ */
+void SHA1::Input(char message_element)
+{
+    Input((unsigned char *) &message_element, 1);
+}
+
+/*  
+ *  operator<<
+ *
+ *  Description:
+ *      This operator makes it convenient to provide character strings to
+ *      the SHA1 object for processing.
+ *
+ *  Parameters:
+ *      message_array: [in]
+ *          The character array to take as input.
+ *
+ *  Returns:
+ *      A reference to the SHA1 object.
+ *
+ *  Comments:
+ *      Each character is assumed to hold 8 bits of information.
+ *
+ */
+SHA1& SHA1::operator<<(const char *message_array)
+{
+    const char *p = message_array;
+
+    while(*p)
+    {
+        Input(*p);
+        p++;
+    }
+
+    return *this;
+}
+
+/*  
+ *  operator<<
+ *
+ *  Description:
+ *      This operator makes it convenient to provide character strings to
+ *      the SHA1 object for processing.
+ *
+ *  Parameters:
+ *      message_array: [in]
+ *          The character array to take as input.
+ *
+ *  Returns:
+ *      A reference to the SHA1 object.
+ *
+ *  Comments:
+ *      Each character is assumed to hold 8 bits of information.
+ *
+ */
+SHA1& SHA1::operator<<(const unsigned char *message_array)
+{
+    const unsigned char *p = message_array;
+
+    while(*p)
+    {
+        Input(*p);
+        p++;
+    }
+
+    return *this;
+}
+
+/*  
+ *  operator<<
+ *
+ *  Description:
+ *      This function provides the next octet in the message.
+ *
+ *  Parameters:
+ *      message_element: [in]
+ *          The next octet in the message
+ *
+ *  Returns:
+ *      A reference to the SHA1 object.
+ *
+ *  Comments:
+ *      The character is assumed to hold 8 bits of information.
+ *
+ */
+SHA1& SHA1::operator<<(const char message_element)
+{
+    Input((unsigned char *) &message_element, 1);
+
+    return *this;
+}
+
+/*  
+ *  operator<<
+ *
+ *  Description:
+ *      This function provides the next octet in the message.
+ *
+ *  Parameters:
+ *      message_element: [in]
+ *          The next octet in the message
+ *
+ *  Returns:
+ *      A reference to the SHA1 object.
+ *
+ *  Comments:
+ *      The character is assumed to hold 8 bits of information.
+ *
+ */
+SHA1& SHA1::operator<<(const unsigned char message_element)
+{
+    Input(&message_element, 1);
+
+    return *this;
+}
+
+/*  
+ *  ProcessMessageBlock
+ *
+ *  Description:
+ *      This function will process the next 512 bits of the message
+ *      stored in the Message_Block array.
+ *
+ *  Parameters:
+ *      None.
+ *
+ *  Returns:
+ *      Nothing.
+ *
+ *  Comments:
+ *      Many of the variable names in this function, especially the single
+ *      character names, were used because those were the names used
+ *      in the publication.
+ *
+ */
+void SHA1::ProcessMessageBlock()
+{
+    const unsigned K[] =    {               // Constants defined for SHA-1
+                                0x5A827999,
+                                0x6ED9EBA1,
+                                0x8F1BBCDC,
+                                0xCA62C1D6
+                            };
+    int         t;                          // Loop counter
+    unsigned    temp;                       // Temporary word value
+    unsigned    W[80];                      // Word sequence
+    unsigned    A, B, C, D, E;              // Word buffers
+
+    /*
+     *  Initialize the first 16 words in the array W
+     */
+    for(t = 0; t < 16; t++)
+    {
+        W[t] = ((unsigned) Message_Block[t * 4]) << 24;
+        W[t] |= ((unsigned) Message_Block[t * 4 + 1]) << 16;
+        W[t] |= ((unsigned) Message_Block[t * 4 + 2]) << 8;
+        W[t] |= ((unsigned) Message_Block[t * 4 + 3]);
+    }
+
+    for(t = 16; t < 80; t++)
+    {
+       W[t] = CircularShift(1,W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16]);
+    }
+
+    A = H[0];
+    B = H[1];
+    C = H[2];
+    D = H[3];
+    E = H[4];
+
+    for(t = 0; t < 20; t++)
+    {
+        temp = CircularShift(5,A) + ((B & C) | ((~B) & D)) + E + W[t] + K[0];
+        temp &= 0xFFFFFFFF;
+        E = D;
+        D = C;
+        C = CircularShift(30,B);
+        B = A;
+        A = temp;
+    }
+
+    for(t = 20; t < 40; t++)
+    {
+        temp = CircularShift(5,A) + (B ^ C ^ D) + E + W[t] + K[1];
+        temp &= 0xFFFFFFFF;
+        E = D;
+        D = C;
+        C = CircularShift(30,B);
+        B = A;
+        A = temp;
+    }
+
+    for(t = 40; t < 60; t++)
+    {
+        temp = CircularShift(5,A) +
+               ((B & C) | (B & D) | (C & D)) + E + W[t] + K[2];
+        temp &= 0xFFFFFFFF;
+        E = D;
+        D = C;
+        C = CircularShift(30,B);
+        B = A;
+        A = temp;
+    }
+
+    for(t = 60; t < 80; t++)
+    {
+        temp = CircularShift(5,A) + (B ^ C ^ D) + E + W[t] + K[3];
+        temp &= 0xFFFFFFFF;
+        E = D;
+        D = C;
+        C = CircularShift(30,B);
+        B = A;
+        A = temp;
+    }
+
+    H[0] = (H[0] + A) & 0xFFFFFFFF;
+    H[1] = (H[1] + B) & 0xFFFFFFFF;
+    H[2] = (H[2] + C) & 0xFFFFFFFF;
+    H[3] = (H[3] + D) & 0xFFFFFFFF;
+    H[4] = (H[4] + E) & 0xFFFFFFFF;
+
+    Message_Block_Index = 0;
+}
+
+/*  
+ *  PadMessage
+ *
+ *  Description:
+ *      According to the standard, the message must be padded to an even
+ *      512 bits.  The first padding bit must be a '1'.  The last 64 bits
+ *      represent the length of the original message.  All bits in between
+ *      should be 0.  This function will pad the message according to those
+ *      rules by filling the message_block array accordingly.  It will also
+ *      call ProcessMessageBlock() appropriately.  When it returns, it
+ *      can be assumed that the message digest has been computed.
+ *
+ *  Parameters:
+ *      None.
+ *
+ *  Returns:
+ *      Nothing.
+ *
+ *  Comments:
+ *
+ */
+void SHA1::PadMessage()
+{
+    /*
+     *  Check to see if the current message block is too small to hold
+     *  the initial padding bits and length.  If so, we will pad the
+     *  block, process it, and then continue padding into a second block.
+     */
+    if (Message_Block_Index > 55)
+    {
+        Message_Block[Message_Block_Index++] = 0x80;
+        while(Message_Block_Index < 64)
+        {
+            Message_Block[Message_Block_Index++] = 0;
+        }
+
+        ProcessMessageBlock();
+
+        while(Message_Block_Index < 56)
+        {
+            Message_Block[Message_Block_Index++] = 0;
+        }
+    }
+    else
+    {
+        Message_Block[Message_Block_Index++] = 0x80;
+        while(Message_Block_Index < 56)
+        {
+            Message_Block[Message_Block_Index++] = 0;
+        }
+
+    }
+
+    /*
+     *  Store the message length as the last 8 octets
+     */
+    Message_Block[56] = (Length_High >> 24) & 0xFF;
+    Message_Block[57] = (Length_High >> 16) & 0xFF;
+    Message_Block[58] = (Length_High >> 8) & 0xFF;
+    Message_Block[59] = (Length_High) & 0xFF;
+    Message_Block[60] = (Length_Low >> 24) & 0xFF;
+    Message_Block[61] = (Length_Low >> 16) & 0xFF;
+    Message_Block[62] = (Length_Low >> 8) & 0xFF;
+    Message_Block[63] = (Length_Low) & 0xFF;
+
+    ProcessMessageBlock();
+}
+
+
+/*  
+ *  CircularShift
+ *
+ *  Description:
+ *      This member function will perform a circular shifting operation.
+ *
+ *  Parameters:
+ *      bits: [in]
+ *          The number of bits to shift (1-31)
+ *      word: [in]
+ *          The value to shift (assumes a 32-bit integer)
+ *
+ *  Returns:
+ *      The shifted value.
+ *
+ *  Comments:
+ *
+ */
+unsigned SHA1::CircularShift(int bits, unsigned word)
+{
+    return ((word << bits) & 0xFFFFFFFF) | ((word & 0xFFFFFFFF) >> (32-bits));
+}
diff --git a/src/main/c/sha1/sha1.h b/src/main/c/sha1/sha1.h
new file mode 100644
index 0000000..3c7d896
--- /dev/null
+++ b/src/main/c/sha1/sha1.h
@@ -0,0 +1,114 @@
+// Copyright (c) 2013, Matt Godbolt
+// All rights reserved.
+// 
+// Redistribution and use in source and binary forms, with or without 
+// modification, are permitted provided that the following conditions are met:
+// 
+// Redistributions of source code must retain the above copyright notice, this 
+// list of conditions and the following disclaimer.
+// 
+// Redistributions in binary form must reproduce the above copyright notice, 
+// this list of conditions and the following disclaimer in the documentation 
+// and/or other materials provided with the distribution.
+// 
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
+// POSSIBILITY OF SUCH DAMAGE.
+
+// DO NOT REFORMAT <- for tidy
+/*
+ *  sha1.h
+ *
+ *  Copyright (C) 1998, 2009
+ *  Paul E. Jones <paulej@packetizer.com>
+ *  All Rights Reserved.
+ *
+ *****************************************************************************
+ *  $Id: sha1.h 12 2009-06-22 19:34:25Z paulej $
+ *****************************************************************************
+ *
+ *  Description:
+ *      This class implements the Secure Hashing Standard as defined
+ *      in FIPS PUB 180-1 published April 17, 1995.
+ *
+ *      Many of the variable names in this class, especially the single
+ *      character names, were used because those were the names used
+ *      in the publication.
+ *
+ *      Please read the file sha1.cpp for more information.
+ *
+ */
+
+#ifndef _SHA1_H_
+#define _SHA1_H_
+
+class SHA1
+{
+
+    public:
+
+        SHA1();
+        virtual ~SHA1();
+
+        /*
+         *  Re-initialize the class
+         */
+        void Reset();
+
+        /*
+         *  Returns the message digest
+         */
+        bool Result(unsigned *message_digest_array);
+
+        /*
+         *  Provide input to SHA1
+         */
+        void Input( const unsigned char *message_array,
+                    unsigned            length);
+        void Input( const char  *message_array,
+                    unsigned    length);
+        void Input(unsigned char message_element);
+        void Input(char message_element);
+        SHA1& operator<<(const char *message_array);
+        SHA1& operator<<(const unsigned char *message_array);
+        SHA1& operator<<(const char message_element);
+        SHA1& operator<<(const unsigned char message_element);
+
+    private:
+
+        /*
+         *  Process the next 512 bits of the message
+         */
+        void ProcessMessageBlock();
+
+        /*
+         *  Pads the current message block to 512 bits
+         */
+        void PadMessage();
+
+        /*
+         *  Performs a circular left shift operation
+         */
+        inline unsigned CircularShift(int bits, unsigned word);
+
+        unsigned H[5];                      // Message digest buffers
+
+        unsigned Length_Low;                // Message length in bits
+        unsigned Length_High;               // Message length in bits
+
+        unsigned char Message_Block[64];    // 512-bit message blocks
+        int Message_Block_Index;            // Index into message block array
+
+        bool Computed;                      // Is the digest computed?
+        bool Corrupted;                     // Is the message digest corruped?
+
+};
+#endif
diff --git a/src/main/c/util/CrackedUri.cpp b/src/main/c/util/CrackedUri.cpp
new file mode 100644
index 0000000..1da757f
--- /dev/null
+++ b/src/main/c/util/CrackedUri.cpp
@@ -0,0 +1,129 @@
+// Copyright (c) 2013, Matt Godbolt
+// All rights reserved.
+// 
+// Redistribution and use in source and binary forms, with or without 
+// modification, are permitted provided that the following conditions are met:
+// 
+// Redistributions of source code must retain the above copyright notice, this 
+// list of conditions and the following disclaimer.
+// 
+// Redistributions in binary form must reproduce the above copyright notice, 
+// this list of conditions and the following disclaimer in the documentation 
+// and/or other materials provided with the distribution.
+// 
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
+// POSSIBILITY OF SUCH DAMAGE.
+
+#include "seasocks/StringUtil.h"
+#include "seasocks/util/CrackedUri.h"
+
+#include <algorithm>
+#include <sstream>
+#include <stdexcept>
+
+#define THROW(stuff) \
+    do {\
+        std::ostringstream err; \
+        err << stuff; \
+        throw std::runtime_error(err.str()); \
+    } while (0);
+
+namespace {
+
+char fromHex(char c) {
+    c = tolower(c);
+    return c >= '0' && c <= '9' ? c - '0' : c - 'a' + 10;
+}
+
+std::string unescape(std::string uri) {
+    seasocks::replace(uri, "+", " ");
+    size_t pos = 0;
+    while (pos < uri.size()) {
+        pos = uri.find('%', pos);
+        if (pos == uri.npos) break;
+        if (pos + 2 > uri.size()) {
+            THROW("Truncated uri: '" << uri << "'");
+        }
+        if (!isxdigit(uri[pos + 1]) || !isxdigit(uri[pos + 2])) {
+            THROW("Bad digit in uri: '" << uri << "'");
+        }
+        auto hex = (fromHex(uri[pos + 1]) << 4) | fromHex(uri[pos + 2]);
+        uri = uri.substr(0, pos) + std::string(1, hex) + uri.substr(pos + 3);
+        ++pos;
+    }
+    return uri;
+}
+
+}
+
+namespace seasocks {
+
+CrackedUri::CrackedUri(const std::string& uri) {
+    if (uri.empty() || uri[0] != '/') {
+        THROW("Malformed URI: '" << uri << "'");
+    }
+    auto endOfPath = uri.find('?');
+    std::string path;
+    std::string remainder;
+    if (endOfPath == uri.npos) {
+        path = uri.substr(1);
+    } else {
+        path = uri.substr(1, endOfPath - 1);
+        remainder = uri.substr(endOfPath + 1);
+    }
+
+    _path = split(path, '/');
+    std::transform(_path.begin(), _path.end(), _path.begin(), unescape);
+
+    auto splitRemainder = split(remainder, '&');
+    for (auto iter = splitRemainder.cbegin(); iter != splitRemainder.cend(); ++iter) {
+        if (iter->empty()) continue;
+        auto split = seasocks::split(*iter, '=');
+        std::transform(split.begin(), split.end(), split.begin(), unescape);
+        if (split.size() == 1) {
+            _queryParams.insert(std::make_pair(split[0], std::string()));
+        } else if (split.size() == 2) {
+            _queryParams.insert(std::make_pair(split[0], split[1]));
+        } else {
+            THROW("Malformed URI, two many = in query: '" << uri << "'");
+        }
+    }
+}
+
+bool CrackedUri::hasParam(const std::string& param) const {
+    return _queryParams.find(param) != _queryParams.end();
+}
+
+std::string CrackedUri::queryParam(const std::string& param, const std::string& def) const {
+    auto found = _queryParams.find(param);
+    return found == _queryParams.end() ? def : found->second;
+}
+
+std::vector<std::string> CrackedUri::allQueryParams(const std::string& param) const {
+    std::vector<std::string> params;
+    for (auto iter = _queryParams.find(param); iter != _queryParams.end() && iter->first == param; ++iter)
+        params.push_back(iter->second);
+    return params;
+}
+
+CrackedUri CrackedUri::shift() const {
+    CrackedUri shifted(*this);
+    if (_path.size() > 1) {
+        shifted._path = std::vector<std::string>(_path.begin() + 1, _path.end());
+    } else {
+        shifted._path = {""};
+    }
+
+    return shifted;
+}
+
+}
diff --git a/src/main/c/util/Json.cpp b/src/main/c/util/Json.cpp
new file mode 100644
index 0000000..48c74d7
--- /dev/null
+++ b/src/main/c/util/Json.cpp
@@ -0,0 +1,66 @@
+// Copyright (c) 2013, Matt Godbolt
+// All rights reserved.
+// 
+// Redistribution and use in source and binary forms, with or without 
+// modification, are permitted provided that the following conditions are met:
+// 
+// Redistributions of source code must retain the above copyright notice, this 
+// list of conditions and the following disclaimer.
+// 
+// Redistributions in binary form must reproduce the above copyright notice, 
+// this list of conditions and the following disclaimer in the documentation 
+// and/or other materials provided with the distribution.
+// 
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
+// POSSIBILITY OF SUCH DAMAGE.
+
+#include "seasocks/util/Json.h"
+
+#include <iomanip>
+
+namespace seasocks {
+
+void jsonToStream(std::ostream& str, const char* t) {
+    str << '"';
+    for (; *t; ++t) {
+        switch (*t) {
+        default:
+            if (*t >= 32) {
+                str << *t;
+            } else {
+                str << "\\u" << std::setw(4)
+                    << std::setfill('0') << std::hex << (int)*t;
+            }
+            break;
+        case 8: str << "\\b"; break;
+        case 9: str << "\\t"; break;
+        case 10: str << "\\n"; break;
+        case 12: str << "\\f"; break;
+        case 13: str << "\\r"; break;
+        case '"': case '\\':
+            str << '\\';
+            str << *t;
+            break;
+        }
+    }
+    str << '"';
+}
+
+void jsonToStream(std::ostream& str, bool b) {
+    str << (b ? "true" : "false");
+}
+
+void EpochTimeAsLocal::jsonToStream(std::ostream &o) const {
+    o << "new Date(" << t * 1000 << ").toLocaleString()";
+}
+
+}
diff --git a/src/main/c/util/PathHandler.cpp b/src/main/c/util/PathHandler.cpp
new file mode 100644
index 0000000..4f9b60f
--- /dev/null
+++ b/src/main/c/util/PathHandler.cpp
@@ -0,0 +1,51 @@
+// Copyright (c) 2013, Matt Godbolt
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+//
+// Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+
+#include "seasocks/util/PathHandler.h"
+
+#include "seasocks/Request.h"
+#include "seasocks/Response.h"
+#include "seasocks/util/CrackedUri.h"
+
+using namespace seasocks;
+
+CrackedUriPageHandler::Ptr PathHandler::add(const CrackedUriPageHandler::Ptr& handler) {
+    _handlers.emplace_back(handler);
+    return _handlers.back();
+}
+
+std::shared_ptr<Response> PathHandler::handle(
+        const CrackedUri& uri, const Request& request) {
+    const auto &path = uri.path();
+    if (path.empty() || path[0] != _path) return Response::unhandled();
+
+    auto shiftedUri = uri.shift();
+
+    for (const auto &it : _handlers) {
+        auto response = it->handle(shiftedUri, request);
+        if (response != Response::unhandled()) return response;
+    }
+    return Response::unhandled();
+}
diff --git a/src/main/c/util/RootPageHandler.cpp b/src/main/c/util/RootPageHandler.cpp
new file mode 100644
index 0000000..f6b4f52
--- /dev/null
+++ b/src/main/c/util/RootPageHandler.cpp
@@ -0,0 +1,52 @@
+// Copyright (c) 2013, Matt Godbolt
+// All rights reserved.
+// 
+// Redistribution and use in source and binary forms, with or without 
+// modification, are permitted provided that the following conditions are met:
+// 
+// Redistributions of source code must retain the above copyright notice, this 
+// list of conditions and the following disclaimer.
+// 
+// Redistributions in binary form must reproduce the above copyright notice, 
+// this list of conditions and the following disclaimer in the documentation 
+// and/or other materials provided with the distribution.
+// 
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
+// POSSIBILITY OF SUCH DAMAGE.
+
+#include "seasocks/util/RootPageHandler.h"
+
+#include "seasocks/Request.h"
+#include "seasocks/Response.h"
+#include "seasocks/util/CrackedUri.h"
+
+using namespace seasocks;
+
+RootPageHandler::RootPageHandler() {
+}
+
+RootPageHandler::~RootPageHandler() {
+}
+
+CrackedUriPageHandler::Ptr RootPageHandler::add(const CrackedUriPageHandler::Ptr& handler) {
+    _handlers.emplace_back(handler);
+    return _handlers.back();
+}
+
+std::shared_ptr<Response> RootPageHandler::handle(const Request& request) {
+    CrackedUri uri(request.getRequestUri());
+    for (const auto &it : _handlers) {
+        auto response = it->handle(uri, request);
+        if (response != Response::unhandled()) return response;
+    }
+    return Response::unhandled();
+}