first commit

This commit is contained in:
2023-04-22 21:55:43 +03:00
commit 0b89d84626
67 changed files with 7552 additions and 0 deletions

View File

@@ -0,0 +1,17 @@
#include "ConnectionContext.hpp"
namespace httpsserver {
ConnectionContext::ConnectionContext() {
}
ConnectionContext::~ConnectionContext() {
}
void ConnectionContext::setWebsocketHandler(WebsocketHandler *wsHandler) {
_wsHandler = wsHandler;
}
} /* namespace httpsserver */

View File

@@ -0,0 +1,41 @@
#ifndef SRC_CONNECTIONCONTEXT_HPP_
#define SRC_CONNECTIONCONTEXT_HPP_
#include <Arduino.h>
#include <IPAddress.h>
// Required for SSL
#include "openssl/ssl.h"
#undef read
namespace httpsserver {
class WebsocketHandler;
/**
* \brief Internal class to handle the state of a connection
*/
class ConnectionContext {
public:
ConnectionContext();
virtual ~ConnectionContext();
virtual void signalRequestError() = 0;
virtual void signalClientClose() = 0;
virtual size_t getCacheSize() = 0;
virtual size_t readBuffer(byte* buffer, size_t length) = 0;
virtual size_t pendingBufferSize() = 0;
virtual size_t writeBuffer(byte* buffer, size_t length) = 0;
virtual bool isSecure() = 0;
virtual void setWebsocketHandler(WebsocketHandler *wsHandler);
virtual IPAddress getClientIP() = 0;
WebsocketHandler * _wsHandler;
};
} /* namespace httpsserver */
#endif /* SRC_CONNECTIONCONTEXT_HPP_ */

View File

@@ -0,0 +1,68 @@
#ifndef SRC_HTTPBODYPARSER_HPP_
#define SRC_HTTPBODYPARSER_HPP_
#include <Arduino.h>
#include <string>
#include "HTTPRequest.hpp"
namespace httpsserver {
/**
* Superclass for various body parser implementations that can be used to
* interpret http-specific bodies (like x-www-form-urlencoded or multipart/form-data)
*
* To allow for arbitrary body length, the interface of the body parser provides access
* to one underlying "field" at a time. A field may be a value of the urlencoded string
* or a part of a multipart message.
*
* Using next() proceeds to the next field.
*/
class HTTPBodyParser {
public:
const size_t unknownLength = 0x7ffffffe;
HTTPBodyParser(HTTPRequest * req): _request(req) {};
virtual ~HTTPBodyParser() {}
/**
* Proceeds to the next field of the body
*
* If a field has not been read completely, the remaining content is discarded.
*
* Returns true iff proceeding to the next field succeeded (ie there was a next field)
*/
virtual bool nextField() = 0;
/** Returns the name of the current field */
virtual std::string getFieldName() = 0;
/** Returns the filename of the current field or an empty string */
virtual std::string getFieldFilename() = 0;
/**
* Returns the mime type of the current field.
*
* Note: This value is set by the client. It can be altered maliciously. Do NOT rely on it
* for anything that affects the security of your device or other clients connected to it!
*
* Not every BodyParser might provide this value, usually it's set to something like text/plain then
*/
virtual std::string getFieldMimeType() = 0;
/**
* Reads a maximum of bufferSize bytes into buffer and returns the actual amount of bytes that have been read
*/
virtual size_t read(byte* buffer, size_t bufferSize) = 0;
/** Returns true when all field data has been read */
virtual bool endOfField() = 0;
protected:
/** The underlying request */
HTTPRequest * _request;
};
} // namespace httpserver
#endif

691
src/http/HTTPConnection.cpp Normal file
View File

@@ -0,0 +1,691 @@
#include "HTTPConnection.hpp"
namespace httpsserver {
HTTPConnection::HTTPConnection(ResourceResolver * resResolver):
_resResolver(resResolver) {
_socket = -1;
_addrLen = 0;
_bufferProcessed = 0;
_bufferUnusedIdx = 0;
_connectionState = STATE_UNDEFINED;
_clientState = CSTATE_UNDEFINED;
_httpHeaders = NULL;
_defaultHeaders = NULL;
_isKeepAlive = false;
_lastTransmissionTS = millis();
_shutdownTS = 0;
_wsHandler = nullptr;
}
HTTPConnection::~HTTPConnection() {
// Close the socket
closeConnection();
}
/**
* Initializes the connection from a server socket.
*
* The call WILL BLOCK if accept(serverSocketID) blocks. So use select() to check for that in advance.
*/
int HTTPConnection::initialize(int serverSocketID, HTTPHeaders *defaultHeaders) {
if (_connectionState == STATE_UNDEFINED) {
_defaultHeaders = defaultHeaders;
_addrLen = sizeof(_sockAddr);
_socket = accept(serverSocketID, (struct sockaddr * )&_sockAddr, &_addrLen);
// Build up SSL Connection context if the socket has been created successfully
if (_socket >= 0) {
HTTPS_LOGI("New connection. Socket FID=%d", _socket);
_connectionState = STATE_INITIAL;
_httpHeaders = new HTTPHeaders();
refreshTimeout();
return _socket;
}
HTTPS_LOGE("Could not accept() new connection");
_addrLen = 0;
_connectionState = STATE_ERROR;
_clientState = CSTATE_ACTIVE;
// This will only be called if the connection could not be established and cleanup
// variables etc.
closeConnection();
}
// Error: The connection has already been established or could not be established
return -1;
}
/**
* Returns the client's IPv4
*/
IPAddress HTTPConnection::getClientIP() {
if (_addrLen > 0 && _sockAddr.sa_family == AF_INET) {
struct sockaddr_in *sockAddrIn = (struct sockaddr_in *)(&_sockAddr);
return IPAddress(sockAddrIn->sin_addr.s_addr);
}
return IPAddress(0, 0, 0, 0);
}
/**
* True if the connection is timed out.
*
* (Should be checkd in the loop and transition should go to CONNECTION_CLOSE if exceeded)
*/
bool HTTPConnection::isTimeoutExceeded() {
return _lastTransmissionTS + HTTPS_CONNECTION_TIMEOUT < millis();
}
/**
* Resets the timeout to allow again the full HTTPS_CONNECTION_TIMEOUT milliseconds
*/
void HTTPConnection::refreshTimeout() {
_lastTransmissionTS = millis();
}
/**
* Returns true, if the connection has been closed.
*/
bool HTTPConnection::isClosed() {
return (_connectionState == STATE_ERROR || _connectionState == STATE_CLOSED);
}
/**
* Returns true, if the connection has been closed due to error
*/
bool HTTPConnection::isError() {
return (_connectionState == STATE_ERROR);
}
bool HTTPConnection::isSecure() {
return false;
}
void HTTPConnection::closeConnection() {
// TODO: Call an event handler here, maybe?
if (_connectionState != STATE_ERROR && _connectionState != STATE_CLOSED) {
// First call to closeConnection - set the timestamp to calculate the timeout later on
if (_connectionState != STATE_CLOSING) {
_shutdownTS = millis();
}
// Set the connection state to closing. We stay in closing as long as SSL has not been shutdown
// correctly
_connectionState = STATE_CLOSING;
}
// Tear down the socket
if (_socket >= 0) {
HTTPS_LOGI("Connection closed. Socket FID=%d", _socket);
close(_socket);
_socket = -1;
_addrLen = 0;
}
if (_connectionState != STATE_ERROR) {
_connectionState = STATE_CLOSED;
}
if (_httpHeaders != NULL) {
HTTPS_LOGD("Free headers");
delete _httpHeaders;
_httpHeaders = NULL;
}
if (_wsHandler != nullptr) {
HTTPS_LOGD("Free WS Handler");
delete _wsHandler;
_wsHandler = NULL;
}
}
/**
* This method will try to fill up the buffer with data from
*/
int HTTPConnection::updateBuffer() {
if (!isClosed()) {
// If there is buffer data that has been marked as processed.
// Some example is shown here:
//
// Previous configuration:
// GET / HTTP/1.1\\Host: test\\Foo: bar\\\\[some uninitialized memory]
// ^ processed ^ unusedIdx
//
// New configuration after shifting:
// Host: test\\Foo: bar\\\\[some uninitialized memory]
// ^ processed ^ unusedIdx
if (_bufferProcessed > 0) {
for(int i = 0; i < HTTPS_CONNECTION_DATA_CHUNK_SIZE; i++) {
int copyFrom = i + _bufferProcessed;
if (copyFrom < _bufferUnusedIdx) {
_receiveBuffer[i] = _receiveBuffer[copyFrom];
} else {
break;
}
}
_bufferUnusedIdx -= _bufferProcessed;
_bufferProcessed = 0;
}
if (_bufferUnusedIdx < HTTPS_CONNECTION_DATA_CHUNK_SIZE) {
if (canReadData()) {
HTTPS_LOGD("Data on Socket FID=%d", _socket);
int readReturnCode;
// The return code of SSL_read means:
// > 0 : Length of the data that has been read
// < 0 : Error
// = 0 : Connection closed
readReturnCode = readBytesToBuffer(
// Only after the part of the buffer that has not been processed yet
(byte*)(_receiveBuffer + sizeof(char) * _bufferUnusedIdx),
// Only append up to the end of the buffer
HTTPS_CONNECTION_DATA_CHUNK_SIZE - _bufferUnusedIdx
);
if (readReturnCode > 0) {
_bufferUnusedIdx += readReturnCode;
refreshTimeout();
return readReturnCode;
} else if (readReturnCode == 0) {
// The connection has been closed by the client
_clientState = CSTATE_CLOSED;
HTTPS_LOGI("Client closed connection, FID=%d", _socket);
// TODO: If we are in state websocket, we might need to do something here
return 0;
} else {
// An error occured
_connectionState = STATE_ERROR;
HTTPS_LOGE("An receive error occured, FID=%d", _socket);
closeConnection();
return -1;
}
} // data pending
} // buffer can read more
}
return 0;
}
bool HTTPConnection::canReadData() {
fd_set sockfds;
FD_ZERO( &sockfds );
FD_SET(_socket, &sockfds);
// We define an immediate timeout (return immediately, if there's no data)
timeval timeout;
timeout.tv_sec = 0;
timeout.tv_usec = 0;
// Check for input
// As by 2017-12-14, it seems that FD_SETSIZE is defined as 0x40, but socket IDs now
// start at 0x1000, so we need to use _socket+1 here
select(_socket + 1, &sockfds, NULL, NULL, &timeout);
return FD_ISSET(_socket, &sockfds);
}
size_t HTTPConnection::readBuffer(byte* buffer, size_t length) {
updateBuffer();
size_t bufferSize = _bufferUnusedIdx - _bufferProcessed;
if (length > bufferSize) {
length = bufferSize;
}
// Write until length is reached (either by param of by empty buffer
for(int i = 0; i < length; i++) {
buffer[i] = _receiveBuffer[_bufferProcessed++];
}
return length;
}
size_t HTTPConnection::pendingBufferSize() {
updateBuffer();
return _bufferUnusedIdx - _bufferProcessed + pendingByteCount();
}
size_t HTTPConnection::pendingByteCount() {
return 0; // FIXME: Add the value of the equivalent function of SSL_pending() here
}
size_t HTTPConnection::writeBuffer(byte* buffer, size_t length) {
return send(_socket, buffer, length, 0);
}
size_t HTTPConnection::readBytesToBuffer(byte* buffer, size_t length) {
return recv(_socket, buffer, length, MSG_WAITALL | MSG_DONTWAIT);
}
void HTTPConnection::raiseError(uint16_t code, std::string reason) {
_connectionState = STATE_ERROR;
std::string sCode = intToString(code);
char headers[] = "\r\nConnection: close\r\nContent-Type: text/plain;charset=utf8\r\n\r\n";
writeBuffer((byte*)"HTTP/1.1 ", 9);
writeBuffer((byte*)sCode.c_str(), sCode.length());
writeBuffer((byte*)" ", 1);
writeBuffer((byte*)(reason.c_str()), reason.length());
writeBuffer((byte*)headers, strlen(headers));
writeBuffer((byte*)sCode.c_str(), sCode.length());
writeBuffer((byte*)" ", 1);
writeBuffer((byte*)(reason.c_str()), reason.length());
closeConnection();
}
void HTTPConnection::readLine(int lengthLimit) {
while(_bufferProcessed < _bufferUnusedIdx) {
char newChar = _receiveBuffer[_bufferProcessed];
if ( newChar == '\r') {
// Look ahead for \n (if not possible, wait for next round
if (_bufferProcessed+1 < _bufferUnusedIdx) {
if (_receiveBuffer[_bufferProcessed+1] == '\n') {
_bufferProcessed += 2;
_parserLine.parsingFinished = true;
return;
} else {
// Line has not been terminated by \r\n
HTTPS_LOGW("Line without \\r\\n (got only \\r). FID=%d", _socket);
raiseError(400, "Bad Request");
return;
}
}
} else {
_parserLine.text += newChar;
_bufferProcessed += 1;
}
// Check that the max request string size is not exceeded
if (_parserLine.text.length() > lengthLimit) {
HTTPS_LOGW("Header length exceeded. FID=%d", _socket);
raiseError(431, "Request Header Fields Too Large");
return;
}
}
}
/**
* Called by the request to signal that the client has closed the connection
*/
void HTTPConnection::signalClientClose() {
_clientState = CSTATE_CLOSED;
}
/**
* Called by the request to signal that an error has occured
*/
void HTTPConnection::signalRequestError() {
// TODO: Check that no response has been transmitted yet
raiseError(400, "Bad Request");
}
/**
* Returns the cache size that should be cached (in the response) to enable keep-alive requests.
*
* 0 = no keep alive.
*/
size_t HTTPConnection::getCacheSize() {
return (_isKeepAlive ? HTTPS_KEEPALIVE_CACHESIZE : 0);
}
void HTTPConnection::loop() {
// First, update the buffer
// newByteCount will contain the number of new bytes that have to be processed
updateBuffer();
if (_clientState == CSTATE_CLOSED) {
HTTPS_LOGI("Client closed (FID=%d, cstate=%d)", _socket, _clientState);
}
if (_clientState == CSTATE_CLOSED && _bufferProcessed == _bufferUnusedIdx && _connectionState < STATE_HEADERS_FINISHED) {
closeConnection();
}
if (!isClosed() && isTimeoutExceeded()) {
HTTPS_LOGI("Connection timeout. FID=%d", _socket);
closeConnection();
}
if (!isError()) {
// State machine (Reading request, reading headers, ...)
switch(_connectionState) {
case STATE_INITIAL: // Read request line
readLine(HTTPS_REQUEST_MAX_REQUEST_LENGTH);
if (_parserLine.parsingFinished && !isClosed()) {
// Find the method
size_t spaceAfterMethodIdx = _parserLine.text.find(' ');
if (spaceAfterMethodIdx == std::string::npos) {
HTTPS_LOGW("Missing space after method");
raiseError(400, "Bad Request");
break;
}
_httpMethod = _parserLine.text.substr(0, spaceAfterMethodIdx);
// Find the resource string:
size_t spaceAfterResourceIdx = _parserLine.text.find(' ', spaceAfterMethodIdx + 1);
if (spaceAfterResourceIdx == std::string::npos) {
HTTPS_LOGW("Missing space after resource");
raiseError(400, "Bad Request");
break;
}
_httpResource = _parserLine.text.substr(spaceAfterMethodIdx + 1, spaceAfterResourceIdx - _httpMethod.length() - 1);
_parserLine.parsingFinished = false;
_parserLine.text = "";
HTTPS_LOGI("Request: %s %s (FID=%d)", _httpMethod.c_str(), _httpResource.c_str(), _socket);
_connectionState = STATE_REQUEST_FINISHED;
}
break;
case STATE_REQUEST_FINISHED: // Read headers
while (_bufferProcessed < _bufferUnusedIdx && !isClosed()) {
readLine(HTTPS_REQUEST_MAX_HEADER_LENGTH);
if (_parserLine.parsingFinished && _connectionState != STATE_ERROR) {
if (_parserLine.text.empty()) {
HTTPS_LOGD("Headers finished, FID=%d", _socket);
_connectionState = STATE_HEADERS_FINISHED;
// Break, so that the rest of the body does not get flushed through
_parserLine.parsingFinished = false;
_parserLine.text = "";
break;
} else {
int idxColon = _parserLine.text.find(':');
if ( (idxColon != std::string::npos) && (_parserLine.text[idxColon+1]==' ') ) {
_httpHeaders->set(new HTTPHeader(
_parserLine.text.substr(0, idxColon),
_parserLine.text.substr(idxColon+2)
));
HTTPS_LOGD("Header: %s = %s (FID=%d)", _parserLine.text.substr(0, idxColon).c_str(), _parserLine.text.substr(idxColon+2).c_str(), _socket);
} else {
HTTPS_LOGW("Malformed request header: %s", _parserLine.text.c_str());
raiseError(400, "Bad Request");
break;
}
}
_parserLine.parsingFinished = false;
_parserLine.text = "";
}
}
break;
case STATE_HEADERS_FINISHED: // Handle body
{
HTTPS_LOGD("Resolving resource...");
ResolvedResource resolvedResource;
// Check which kind of node we need (Websocket or regular)
bool websocketRequested = checkWebsocket();
_resResolver->resolveNode(_httpMethod, _httpResource, resolvedResource, websocketRequested ? WEBSOCKET : HANDLER_CALLBACK);
// Is there any match (may be the defaultNode, if it is configured)
if (resolvedResource.didMatch()) {
// Check for client's request to keep-alive if we have a handler function.
if (resolvedResource.getMatchingNode()->_nodeType == HANDLER_CALLBACK) {
// Did the client set connection:keep-alive?
HTTPHeader * connectionHeader = _httpHeaders->get("Connection");
std::string connectionHeaderValue = "";
if (connectionHeader != NULL) {
connectionHeaderValue += connectionHeader->_value;
std::transform(
connectionHeaderValue.begin(),
connectionHeaderValue.end(),
connectionHeaderValue.begin(),
[](unsigned char c){ return ::tolower(c); }
);
}
if (std::string("keep-alive").compare(connectionHeaderValue)==0) {
HTTPS_LOGD("Keep-Alive activated. FID=%d", _socket);
_isKeepAlive = true;
} else {
HTTPS_LOGD("Keep-Alive disabled. FID=%d", _socket);
_isKeepAlive = false;
}
} else {
_isKeepAlive = false;
}
// Create request context
HTTPRequest req = HTTPRequest(
this,
_httpHeaders,
resolvedResource.getMatchingNode(),
_httpMethod,
resolvedResource.getParams(),
_httpResource
);
HTTPResponse res = HTTPResponse(this);
// Add default headers to the response
auto allDefaultHeaders = _defaultHeaders->getAll();
for(std::vector<HTTPHeader*>::iterator header = allDefaultHeaders->begin(); header != allDefaultHeaders->end(); ++header) {
res.setHeader((*header)->_name, (*header)->_value);
}
// Find the request handler callback
HTTPSCallbackFunction * resourceCallback;
if (websocketRequested) {
// For the websocket, we use the handshake callback defined below
resourceCallback = &handleWebsocketHandshake;
} else {
// For resource nodes, we use the callback defined by the node itself
resourceCallback = ((ResourceNode*)resolvedResource.getMatchingNode())->_callback;
}
// Get the current middleware chain
auto vecMw = _resResolver->getMiddleware();
// Anchor of the chain is the actual resource. The call to the handler is bound here
std::function<void()> next = std::function<void()>(std::bind(resourceCallback, &req, &res));
// Go back in the middleware chain and glue everything together
auto itMw = vecMw.rbegin();
while(itMw != vecMw.rend()) {
next = std::function<void()>(std::bind((*itMw), &req, &res, next));
itMw++;
}
// We insert the internal validation middleware at the start of the chain:
next = std::function<void()>(std::bind(&validationMiddleware, &req, &res, next));
// Call the whole chain
next();
// The callback-function should have read all of the request body.
// However, if it does not, we need to clear the request body now,
// because otherwise it would be parsed in the next request.
if (!req.requestComplete()) {
HTTPS_LOGW("Callback function did not parse full request body");
req.discardRequestBody();
}
// Finally, after the handshake is done, we create the WebsocketHandler and change the internal state.
if(websocketRequested) {
_wsHandler = ((WebsocketNode*)resolvedResource.getMatchingNode())->newHandler();
_wsHandler->initialize(this); // make websocket with this connection
_connectionState = STATE_WEBSOCKET;
} else {
// Handling the request is done
HTTPS_LOGD("Handler function done, request complete");
// Now we need to check if we can use keep-alive to reuse the SSL connection
// However, if the client did not set content-size or defined connection: close,
// we have no chance to do so.
// Also, the programmer may have explicitly set Connection: close for the response.
std::string hConnection = res.getHeader("Connection");
if (hConnection == "close") {
_isKeepAlive = false;
}
if (!_isKeepAlive) {
// No KeepAlive -> We are done. Transition to next state.
if (!isClosed()) {
res.finalize();
_connectionState = STATE_BODY_FINISHED;
}
} else {
if (res.isResponseBuffered()) {
// If the response could be buffered:
res.setHeader("Connection", "keep-alive");
res.finalize();
if (_clientState != CSTATE_CLOSED) {
// Refresh the timeout for the new request
refreshTimeout();
// Reset headers for the new connection
_httpHeaders->clearAll();
// Go back to initial state
_connectionState = STATE_INITIAL;
}
}
// The response could not be buffered or the client has closed:
if (!isClosed() && _connectionState!=STATE_INITIAL) {
_connectionState = STATE_BODY_FINISHED;
}
}
}
} else {
// No match (no default route configured, nothing does match)
HTTPS_LOGW("Could not find a matching resource");
raiseError(404, "Not Found");
}
}
break;
case STATE_BODY_FINISHED: // Request is complete
closeConnection();
break;
case STATE_CLOSING: // As long as we are in closing state, we call closeConnection() again and wait for it to finish or timeout
closeConnection();
break;
case STATE_WEBSOCKET: // Do handling of the websocket
refreshTimeout(); // don't timeout websocket connection
if(pendingBufferSize() > 0) {
HTTPS_LOGD("Calling WS handler, FID=%d", _socket);
_wsHandler->loop();
}
// If the client closed the connection unexpectedly
if (_clientState == CSTATE_CLOSED) {
HTTPS_LOGI("WS lost client, calling onClose, FID=%d", _socket);
_wsHandler->onClose();
}
// If the handler has terminated the connection, clean up and close the socket too
if (_wsHandler->closed() || _clientState == CSTATE_CLOSED) {
HTTPS_LOGI("WS closed, freeing Handler, FID=%d", _socket);
delete _wsHandler;
_wsHandler = nullptr;
_connectionState = STATE_CLOSING;
}
break;
default:;
}
}
}
bool HTTPConnection::checkWebsocket() {
if(_httpMethod == "GET" &&
!_httpHeaders->getValue("Host").empty() &&
_httpHeaders->getValue("Upgrade") == "websocket" &&
_httpHeaders->getValue("Connection").find("Upgrade") != std::string::npos &&
!_httpHeaders->getValue("Sec-WebSocket-Key").empty() &&
_httpHeaders->getValue("Sec-WebSocket-Version") == "13") {
HTTPS_LOGI("Upgrading to WS, FID=%d", _socket);
return true;
} else
return false;
}
/**
* Middleware function that handles the validation of parameters
*/
void validationMiddleware(HTTPRequest * req, HTTPResponse * res, std::function<void()> next) {
bool valid = true;
// Get the matched node
HTTPNode * node = req->getResolvedNode();
// Get the parameters
ResourceParameters * params = req->getParams();
// Iterate over the validators and run them
std::vector<HTTPValidator*> * validators = node->getValidators();
for(std::vector<HTTPValidator*>::iterator validator = validators->begin(); valid && validator != validators->end(); ++validator) {
std::string param;
if (params->getPathParameter((*validator)->_idx, param)) {
valid = ((*validator)->_validatorFunction)(param);
} else {
valid = false;
}
}
if (valid) {
next();
} else {
res->setStatusCode(400);
res->setStatusText("Bad Request");
res->print("400 Bad Request");
}
}
/**
* Handler function for the websocket handshake. Will be used by HTTPConnection if a websocket is detected
*/
void handleWebsocketHandshake(HTTPRequest * req, HTTPResponse * res) {
res->setStatusCode(101);
res->setStatusText("Switching Protocols");
res->setHeader("Upgrade", "websocket");
res->setHeader("Connection", "Upgrade");
res->setHeader("Sec-WebSocket-Accept", websocketKeyResponseHash(req->getHeader("Sec-WebSocket-Key")));
res->print("");
}
/**
* Function used to compute the value of the Sec-WebSocket-Accept during Websocket handshake
*/
std::string websocketKeyResponseHash(std::string const &key) {
std::string newKey = key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
uint8_t shaData[HTTPS_SHA1_LENGTH];
esp_sha(SHA1, (uint8_t*)newKey.data(), newKey.length(), shaData);
// Get output size required for base64 representation
size_t b64BufferSize = 0;
mbedtls_base64_encode(nullptr, 0, &b64BufferSize, (const unsigned char*)shaData, HTTPS_SHA1_LENGTH);
// Do the real encoding
unsigned char bufferOut[b64BufferSize];
size_t bytesEncoded = 0;
int res = mbedtls_base64_encode(
bufferOut,
b64BufferSize,
&bytesEncoded,
(const unsigned char*)shaData,
HTTPS_SHA1_LENGTH
);
// Check result and return the encoded string
if (res != 0) {
return std::string();
}
return std::string((char*)bufferOut, bytesEncoded);
} // WebsocketKeyResponseHash
} /* namespace httpsserver */

173
src/http/HTTPConnection.hpp Normal file
View File

@@ -0,0 +1,173 @@
#ifndef SRC_HTTPCONNECTION_HPP_
#define SRC_HTTPCONNECTION_HPP_
#include <Arduino.h>
#include <IPAddress.h>
#include <string>
#include <mbedtls/base64.h>
#include <esp32/sha.h>
#include <functional>
// Required for sockets
#include "lwip/netdb.h"
#undef read
#include "lwip/sockets.h"
#include "HTTPSServerConstants.hpp"
#include "ConnectionContext.hpp"
#include "HTTPHeaders.hpp"
#include "HTTPHeader.hpp"
#include "ResourceResolver.hpp"
#include "ResolvedResource.hpp"
#include "ResourceNode.hpp"
#include "HTTPRequest.hpp"
#include "HTTPResponse.hpp"
#include "WebsocketHandler.hpp"
#include "WebsocketNode.hpp"
namespace httpsserver {
/**
* \brief Represents a single open connection for the plain HTTPServer, without TLS
*/
class HTTPConnection : private ConnectionContext {
public:
HTTPConnection(ResourceResolver * resResolver);
virtual ~HTTPConnection();
virtual int initialize(int serverSocketID, HTTPHeaders *defaultHeaders);
virtual void closeConnection();
virtual bool isSecure();
virtual IPAddress getClientIP();
void loop();
bool isClosed();
bool isError();
protected:
friend class HTTPRequest;
friend class HTTPResponse;
friend class WebsocketInputStreambuf;
virtual size_t writeBuffer(byte* buffer, size_t length);
virtual size_t readBytesToBuffer(byte* buffer, size_t length);
virtual bool canReadData();
virtual size_t pendingByteCount();
// Timestamp of the last transmission action
unsigned long _lastTransmissionTS;
// Timestamp of when the shutdown was started
unsigned long _shutdownTS;
// Internal state machine of the connection:
//
// O --- > STATE_UNDEFINED -- initialize() --> STATE_INITIAL -- get / http/1.1 --> STATE_REQUEST_FINISHED --.
// | | | |
// | | | | Host: ...\r\n
// STATE_ERROR <- on error-----------------------<---------------------------------------< | Foo: bar\r\n
// ^ | | | \r\n
// | shutdown .--> STATE_CLOSED | | | \r\n
// | fails | | | |
// | | close() | | |
// STATE_CLOSING <---- STATE_WEBSOCKET <-. | | |
// ^ | | | |
// `---------- close() ---------- STATE_BODY_FINISHED <-- Body received or GET -- STATE_HEADERS_FINISHED <-´
//
enum {
// The order is important, to be able to use state <= STATE_HEADERS_FINISHED etc.
// The connection has not been established yet
STATE_UNDEFINED,
// The connection has just been created
STATE_INITIAL,
// The request line has been parsed
STATE_REQUEST_FINISHED,
// The headers have been parsed
STATE_HEADERS_FINISHED,
// The body has been parsed/the complete request has been processed (GET has body of length 0)
STATE_BODY_FINISHED,
// The connection is in websocket mode
STATE_WEBSOCKET,
// The connection is about to close (and waiting for the client to send close notify)
STATE_CLOSING,
// The connection has been closed
STATE_CLOSED,
// An error has occured
STATE_ERROR
} _connectionState;
enum {
CSTATE_UNDEFINED,
CSTATE_ACTIVE,
CSTATE_CLOSED
} _clientState;
private:
void raiseError(uint16_t code, std::string reason);
void readLine(int lengthLimit);
bool isTimeoutExceeded();
void refreshTimeout();
int updateBuffer();
size_t pendingBufferSize();
void signalClientClose();
void signalRequestError();
size_t readBuffer(byte* buffer, size_t length);
size_t getCacheSize();
bool checkWebsocket();
// The receive buffer
char _receiveBuffer[HTTPS_CONNECTION_DATA_CHUNK_SIZE];
// First index on _receive_buffer that has not been processed yet (anything before may be discarded)
int _bufferProcessed;
// The index on the receive_buffer that is the first one which is empty at the end.
int _bufferUnusedIdx;
// Socket address, length etc for the connection
struct sockaddr _sockAddr;
socklen_t _addrLen;
int _socket;
// Resource resolver used to resolve resources
ResourceResolver * _resResolver;
// The parser line. The struct is used to read the next line up to the \r\n in readLine()
struct {
std::string text = "";
bool parsingFinished = false;
} _parserLine;
// HTTP properties: Method, Request, Headers
std::string _httpMethod;
std::string _httpResource;
HTTPHeaders * _httpHeaders;
// Default headers that are applied to every response
HTTPHeaders * _defaultHeaders;
// Should we use keep alive
bool _isKeepAlive;
//Websocket connection
WebsocketHandler * _wsHandler;
};
void handleWebsocketHandshake(HTTPRequest * req, HTTPResponse * res);
std::string websocketKeyResponseHash(std::string const &key);
void validationMiddleware(HTTPRequest * req, HTTPResponse * res, std::function<void()> next);
} /* namespace httpsserver */
#endif /* SRC_HTTPCONNECTION_HPP_ */

43
src/http/HTTPHeader.cpp Normal file
View File

@@ -0,0 +1,43 @@
#include "HTTPHeader.hpp"
#include <locale>
#include <ostream>
#include <sstream>
namespace httpsserver {
HTTPHeader::HTTPHeader(const std::string &name, const std::string &value):
_name(normalizeHeaderName(name)),
_value(value) {
}
HTTPHeader::~HTTPHeader() {
}
std::string HTTPHeader::print() {
return _name + ": " + _value;
}
std::string normalizeHeaderName(std::string const &name) {
std::locale loc;
std::stringbuf buf;
std::ostream oBuf(&buf);
bool upper = true;
std::string::size_type len = name.length();
for (std::string::size_type i = 0; i < len; ++i) {
if (upper) {
oBuf << std::toupper(name[i], loc);
upper = false;
} else {
oBuf << std::tolower(name[i], loc);
if (!std::isalnum(name[i], loc)) {
upper=true;
}
}
}
return buf.str();
}
} /* namespace httpsserver */

32
src/http/HTTPHeader.hpp Normal file
View File

@@ -0,0 +1,32 @@
#ifndef SRC_HTTPHEADER_HPP_
#define SRC_HTTPHEADER_HPP_
#include <Arduino.h>
#include <string>
namespace httpsserver {
/**
* \brief Represents a single name/value pair of an HTTP header
*/
class HTTPHeader {
public:
HTTPHeader(const std::string &name, const std::string &value);
virtual ~HTTPHeader();
const std::string _name;
const std::string _value;
std::string print();
};
/**
* \brief Normalizes case in header names
*
* It converts the first letter and every letter after a non-alnum character
* to uppercase. For example, "content-length" becomes "Content-Length" and
* "HOST" becomes "Host".
*/
std::string normalizeHeaderName(std::string const &name);
} /* namespace httpsserver */
#endif /* SRC_HTTPHEADER_HPP_ */

61
src/http/HTTPHeaders.cpp Normal file
View File

@@ -0,0 +1,61 @@
#include "HTTPHeaders.hpp"
namespace httpsserver {
HTTPHeaders::HTTPHeaders() {
_headers = new std::vector<HTTPHeader *>();
}
HTTPHeaders::~HTTPHeaders() {
clearAll();
delete _headers;
}
HTTPHeader * HTTPHeaders::get(std::string const &name) {
std::string normalizedName = normalizeHeaderName(name);
for(std::vector<HTTPHeader*>::iterator header = _headers->begin(); header != _headers->end(); ++header) {
if ((*header)->_name.compare(normalizedName)==0) {
return (*header);
}
}
return NULL;
}
std::string HTTPHeaders::getValue(std::string const &name) {
std::string normalizedName = normalizeHeaderName(name);
for(std::vector<HTTPHeader*>::iterator header = _headers->begin(); header != _headers->end(); ++header) {
if ((*header)->_name.compare(normalizedName)==0) {
return ((*header)->_value);
}
}
return "";
}
void HTTPHeaders::set(HTTPHeader * header) {
for(int i = 0; i < _headers->size(); i++) {
if ((*_headers)[i]->_name.compare(header->_name)==0) {
delete (*_headers)[i];
(*_headers)[i] = header;
return;
}
}
_headers->push_back(header);
}
std::vector<HTTPHeader *> * HTTPHeaders::getAll() {
return _headers;
}
/**
* Deletes all headers
*/
void HTTPHeaders::clearAll() {
for(std::vector<HTTPHeader*>::iterator header = _headers->begin(); header != _headers->end(); ++header) {
delete (*header);
}
_headers->clear();
}
} /* namespace httpsserver */

37
src/http/HTTPHeaders.hpp Normal file
View File

@@ -0,0 +1,37 @@
#ifndef SRC_HTTPHEADERS_HPP_
#define SRC_HTTPHEADERS_HPP_
#include <string>
// Arduino declares it's own min max, incompatible with the stl...
#undef min
#undef max
#include <vector>
#include "HTTPSServerConstants.hpp"
#include "HTTPHeader.hpp"
namespace httpsserver {
/**
* \brief Groups and manages a set of HTTPHeader instances
*/
class HTTPHeaders {
public:
HTTPHeaders();
virtual ~HTTPHeaders();
HTTPHeader * get(std::string const &name);
std::string getValue(std::string const &name);
void set(HTTPHeader * header);
std::vector<HTTPHeader *> * getAll();
void clearAll();
private:
std::vector<HTTPHeader*> * _headers;
};
} /* namespace httpsserver */
#endif /* SRC_HTTPHEADERS_HPP_ */

View File

@@ -0,0 +1,26 @@
#ifndef SRC_HTTPMIDDLEWAREFUNCTION_HPP_
#define SRC_HTTPMIDDLEWAREFUNCTION_HPP_
#include <functional>
#include "HTTPRequest.hpp"
#include "HTTPResponse.hpp"
#include "HTTPSCallbackFunction.hpp"
namespace httpsserver {
class HTTPRequest;
/**
* \brief A middleware function that can be registered at the server.
*
* It will be called before an incoming request is passed to any HTTPSCallbackFunction and may perform
* operations like redirects or authentication.
*
* It receives the request and response object as well as a function pointer ("next") to pass on processing.
* This allows chaining those functions. If next() is not called, the HTTPSCallbackFunction that
* would match the request url will not be invoked. This might become handy if you want to intercept request
* handling in case of missing authentication. Don't forget to call next in case you want to access your
* resources, though.
*/
typedef void (HTTPSMiddlewareFunction)(HTTPRequest * req, HTTPResponse * res, std::function<void()> next);
}
#endif /* SRC_HTTPMIDDLEWAREFUNCTION_HPP_ */

View File

@@ -0,0 +1,288 @@
#include "HTTPMultipartBodyParser.hpp"
#include <sstream>
const size_t MAXLINESIZE = 256;
namespace httpsserver {
HTTPMultipartBodyParser::HTTPMultipartBodyParser(HTTPRequest * req):
HTTPBodyParser(req),
peekBuffer(NULL),
peekBufferSize(0),
boundary(""),
lastBoundary(""),
fieldName(""),
fieldMimeType(""),
fieldFilename("")
{
auto contentType = _request->getHeader("Content-Type");
#ifdef DEBUG_MULTIPART_PARSER
Serial.print("Content type: ");
Serial.println(contentType.c_str());
#endif
auto boundaryIndex = contentType.find("boundary=");
if(boundaryIndex == std::string::npos) {
HTTPS_LOGE("Multipart: missing boundary=");
discardBody();
return;
}
boundary = contentType.substr(boundaryIndex + 9); // "boundary="
auto commaIndex = boundary.find(';');
boundary = "--" + boundary.substr(0, commaIndex);
if(boundary.size() > 72) {
HTTPS_LOGE("Multipart: boundary string too long");
discardBody();
}
lastBoundary = boundary + "--";
}
HTTPMultipartBodyParser::~HTTPMultipartBodyParser() {
if (peekBuffer) {
free(peekBuffer);
peekBuffer = NULL;
}
}
void HTTPMultipartBodyParser::discardBody() {
if (peekBuffer) {
free(peekBuffer);
}
peekBuffer = NULL;
peekBufferSize = 0;
_request->discardRequestBody();
}
bool HTTPMultipartBodyParser::endOfBody() {
return peekBufferSize == 0 && _request->requestComplete();
}
void HTTPMultipartBodyParser::fillBuffer(size_t maxLen) {
// Fill the buffer with up to maxLen bytes (total length, including
// what was already in the buffer), but stop reading ahead once
// we have a CR in the buffer (because the upper layers will
// stop consuming there anyway, to forestall overrunning
// a boundary)
char *bufPtr;
if (peekBuffer == NULL) {
// Nothing in the buffer. Allocate one of the wanted size
peekBuffer = (char *)malloc(maxLen);
if (peekBuffer == NULL) {
HTTPS_LOGE("Multipart: out of memory");
discardBody();
return;
}
bufPtr = peekBuffer;
peekBufferSize = 0;
} else if (peekBufferSize < maxLen) {
// Something in the buffer, but not enough
char *newPeekBuffer = (char *)realloc(peekBuffer, maxLen);
if (newPeekBuffer == NULL) {
HTTPS_LOGE("Multipart: out of memory");
discardBody();
return;
}
peekBuffer = newPeekBuffer;
bufPtr = peekBuffer + peekBufferSize;
} else {
// We already have enough data in the buffer.
return;
}
while(bufPtr < peekBuffer+maxLen) {
size_t didRead = _request->readChars(bufPtr, peekBuffer+maxLen-bufPtr);
if (didRead == 0) {
break;
}
bufPtr += didRead;
// We stop buffering once we have a CR in the buffer
if (memchr(peekBuffer, '\r', bufPtr-peekBuffer) != NULL) {
break;
}
}
peekBufferSize = bufPtr - peekBuffer;
if (peekBufferSize == 0) {
HTTPS_LOGE("Multipart incomplete");
}
}
void HTTPMultipartBodyParser::consumedBuffer(size_t consumed) {
if (consumed == 0) {
return;
}
if (consumed == peekBufferSize) {
free(peekBuffer);
peekBuffer = NULL;
peekBufferSize = 0;
} else {
memmove(peekBuffer, peekBuffer+consumed, peekBufferSize-consumed);
peekBufferSize -= consumed;
}
}
bool HTTPMultipartBodyParser::skipCRLF() {
if (peekBufferSize < 2) {
fillBuffer(2);
}
if (peekBufferSize < 2) {
return false;
}
if (peekBuffer[0] != '\r') {
return false;
}
if (peekBuffer[1] != '\n') {
HTTPS_LOGE("Multipart incorrect line terminator");
discardBody();
return false;
}
consumedBuffer(2);
return true;
}
std::string HTTPMultipartBodyParser::readLine() {
fillBuffer(MAXLINESIZE);
if (peekBufferSize == 0) {
return "";
}
char *crPtr = (char *)memchr(peekBuffer, '\r', peekBufferSize);
if (crPtr == NULL) {
HTTPS_LOGE("Multipart line too long");
discardBody();
return "";
}
size_t lineLength = crPtr-peekBuffer;
std::string rv(peekBuffer, lineLength);
consumedBuffer(lineLength);
skipCRLF();
return rv;
}
// Returns true if the buffer contains a boundary (or possibly lastBoundary)
bool HTTPMultipartBodyParser::peekBoundary() {
if (peekBuffer == NULL || peekBufferSize < boundary.size()) {
return false;
}
char *ptr = peekBuffer;
if (*ptr == '\r') {
ptr++;
}
if (*ptr == '\n') {
ptr++;
}
return memcmp(ptr, boundary.c_str(), boundary.size()) == 0;
}
bool HTTPMultipartBodyParser::nextField() {
fillBuffer(MAXLINESIZE);
while(!peekBoundary()) {
std::string dummy = readLine();
if (endOfBody()) {
HTTPS_LOGE("Multipart missing last boundary");
return false;
}
fillBuffer(MAXLINESIZE);
}
skipCRLF();
std::string line = readLine();
if (line == lastBoundary) {
discardBody();
return false;
}
if (line != boundary) {
HTTPS_LOGE("Multipart incorrect boundary");
return false;
}
// Read header lines up to and including blank line
fieldName = "";
fieldMimeType = "text/plain";
fieldFilename = "";
while (true) {
line = readLine();
if (line == "") {
break;
}
if (line.substr(0, 14) == "Content-Type: ") {
fieldMimeType = line.substr(14);
}
if (line.substr(0, 31) == "Content-Disposition: form-data;") {
// Parse name=value; or name="value"; fields.
std::string field;
line = line.substr(31);
while(true) {
size_t pos = line.find_first_not_of(' ');
if (pos != std::string::npos) {
line = line.substr(pos);
}
if (line == "") break;
pos = line.find(';');
if (pos == std::string::npos) {
field = line;
line = "";
} else {
field = line.substr(0, pos);
line = line.substr(pos+1);
}
pos = field.find('=');
if (pos == std::string::npos) {
HTTPS_LOGE("Multipart ill-formed form-data header");
return false;
}
std::string headerName = field.substr(0, pos);
std::string headerValue = field.substr(pos+1);
if (headerValue.substr(0,1) == "\"") {
headerValue = headerValue.substr(1, headerValue.size()-2);
}
if (headerName == "name") {
fieldName = headerValue;
}
if (headerName == "filename") {
fieldFilename = headerValue;
}
}
}
}
if (fieldName == "") {
HTTPS_LOGE("Multipart missing name");
return false;
}
return true;
}
std::string HTTPMultipartBodyParser::getFieldName() {
return fieldName;
}
std::string HTTPMultipartBodyParser::getFieldFilename() {
return fieldFilename;
}
std::string HTTPMultipartBodyParser::getFieldMimeType() {
return fieldMimeType;
}
bool HTTPMultipartBodyParser::endOfField() {
return peekBoundary();
}
size_t HTTPMultipartBodyParser::read(byte* buffer, size_t bufferSize) {
if (peekBoundary()) {
return 0;
}
size_t readSize = std::min(bufferSize, MAXLINESIZE);
fillBuffer(readSize);
if (peekBoundary()) {
return 0;
}
// We read at most up to a CR (so we don't miss a boundary that has been partially buffered)
// but we always read at least one byte so if the first byte in the buffer is a CR we do read it.
if (peekBufferSize > 1) {
char *crPtr = (char *)memchr(peekBuffer+1, '\r', peekBufferSize-1);
if (crPtr != NULL && crPtr - peekBuffer < bufferSize) {
bufferSize = crPtr - peekBuffer;
}
}
size_t copySize = std::min(bufferSize, peekBufferSize);
memcpy(buffer, peekBuffer, copySize);
consumedBuffer(copySize);
return copySize;
}
} /* namespace httpsserver */

View File

@@ -0,0 +1,39 @@
#ifndef SRC_HTTPMULTIPARTBODYPARSER_HPP_
#define SRC_HTTPMULTIPARTBODYPARSER_HPP_
#include <Arduino.h>
#include "HTTPBodyParser.hpp"
namespace httpsserver {
class HTTPMultipartBodyParser : public HTTPBodyParser {
public:
HTTPMultipartBodyParser(HTTPRequest * req);
~HTTPMultipartBodyParser();
virtual bool nextField();
virtual std::string getFieldName();
virtual std::string getFieldFilename();
virtual std::string getFieldMimeType();
virtual bool endOfField();
virtual size_t read(byte* buffer, size_t bufferSize);
private:
std::string readLine();
void fillBuffer(size_t maxLen);
void consumedBuffer(size_t consumed);
bool skipCRLF();
bool peekBoundary();
void discardBody();
bool endOfBody();
char *peekBuffer;
size_t peekBufferSize;
std::string boundary;
std::string lastBoundary;
std::string fieldName;
std::string fieldMimeType;
std::string fieldFilename;
};
} // namespace httpserver
#endif

54
src/http/HTTPNode.cpp Normal file
View File

@@ -0,0 +1,54 @@
#include "HTTPNode.hpp"
#include "HTTPSServerConstants.hpp"
namespace httpsserver {
HTTPNode::HTTPNode(std::string const &path, const HTTPNodeType nodeType, std::string const &tag):
_path(path),
_tag(tag),
_nodeType(nodeType) {
// Count the parameters and store the indices
size_t idx = 0;
while((idx = path.find("/*", idx)) != std::string::npos) {
// Skip the "/*"
idx+=2;
// Assure that we have either /* at the URL's end or /*/ somewhere in between
if (idx == path.size() || path[idx] == '/') {
_pathParamIdx.push_back(idx - 1);
}
};
}
HTTPNode::~HTTPNode() {
// Delete validator references
for(std::vector<HTTPValidator*>::iterator validator = _validators.begin(); validator != _validators.end(); ++validator) {
delete *validator;
}
}
bool HTTPNode::hasPathParameter() {
return !_pathParamIdx.empty();
}
ssize_t HTTPNode::getParamIdx(size_t idx) {
if (idx<_pathParamIdx.size()) {
return _pathParamIdx[idx];
} else {
return -1;
}
}
size_t HTTPNode::getPathParamCount() {
return _pathParamIdx.size();
}
void HTTPNode::addPathParamValidator(size_t paramIdx, const HTTPValidationFunction * validator) {
_validators.push_back(new HTTPValidator(paramIdx, validator));
}
std::vector<HTTPValidator*> * HTTPNode::getValidators() {
return &_validators;
}
}

70
src/http/HTTPNode.hpp Normal file
View File

@@ -0,0 +1,70 @@
#ifndef SRC_HTTPNODE_HPP_
#define SRC_HTTPNODE_HPP_
#include <Arduino.h>
#include <string>
#undef min
#undef max
#include <vector>
#include "HTTPValidator.hpp"
namespace httpsserver {
enum HTTPNodeType {
/** Node with a handler callback function (class ResourceNode) */
HANDLER_CALLBACK,
/** Node with a websocket handler (class WebsocketNode) */
WEBSOCKET
};
/**
* \brief Base class for a URL/route-handler in the server.
*
* Use ResourceNode for requests that access dynamic or static resources or HttpNode for routes that
* create Websockets.
*/
class HTTPNode {
public:
HTTPNode(const std::string &path, const HTTPNodeType nodeType, const std::string &tag = "");
virtual ~HTTPNode();
/**
* The path under which this node will be available. Should start with a slash. Example:
* "/myResource"
*/
const std::string _path;
/**
* Stores a tag that can be used in middleware and handler functions to identify this
* specific node, tag the node with a required permission, ...
*/
const std::string _tag;
/** Stores the type of the node (as we have not runtime type information by default) */
const HTTPNodeType _nodeType;
bool hasPathParameter();
size_t getPathParamCount();
ssize_t getParamIdx(size_t);
std::vector<HTTPValidator*> * getValidators();
virtual std::string getMethod() = 0;
/**
* Adds a validation function that checks if the actual value of a parameter matches the expectation
* @param paramIdx defines the ID of the parameter that should be checked (starts by 0)
* @param validator the function (string -> bool) that checks if the parameter matches the expecatation
*
* @see ValidatorFunctions.hpp if you need some predefined templates for functions
*/
void addPathParamValidator(size_t paramIdx, const HTTPValidationFunction * validator);
private:
std::vector<size_t> _pathParamIdx;
std::vector<HTTPValidator*> _validators;
};
} // namespace httpserver
#endif

185
src/http/HTTPRequest.cpp Normal file
View File

@@ -0,0 +1,185 @@
#include "HTTPRequest.hpp"
namespace httpsserver {
HTTPRequest::HTTPRequest(
ConnectionContext * con,
HTTPHeaders * headers,
HTTPNode * resolvedNode,
std::string method,
ResourceParameters * params,
std::string requestString):
_con(con),
_headers(headers),
_resolvedNode(resolvedNode),
_method(method),
_params(params),
_requestString(requestString) {
HTTPHeader * contentLength = headers->get("Content-Length");
if (contentLength == NULL) {
_remainingContent = 0;
_contentLengthSet = false;
} else {
_remainingContent = parseInt(contentLength->_value);
_contentLengthSet = true;
}
}
HTTPRequest::~HTTPRequest() {
_headers->clearAll();
}
ResourceParameters * HTTPRequest::getParams() {
return _params;
}
HTTPHeaders * HTTPRequest::getHTTPHeaders() {
return _headers;
}
std::string HTTPRequest::getHeader(std::string const &name) {
HTTPHeader * h = _headers->get(name);
if (h != NULL) {
return h->_value;
} else {
return std::string();
}
}
void HTTPRequest::setHeader(std::string const &name, std::string const &value) {
_headers->set(new HTTPHeader(name, value));
}
HTTPNode * HTTPRequest::getResolvedNode() {
return _resolvedNode;
}
IPAddress HTTPRequest::getClientIP() {
return _con->getClientIP();
}
size_t HTTPRequest::readBytes(byte * buffer, size_t length) {
// Limit reading to content length
if (_contentLengthSet && length > _remainingContent) {
length = _remainingContent;
}
size_t bytesRead = 0;
if (length > 0) {
bytesRead = _con->readBuffer(buffer, length);
}
if (_contentLengthSet) {
_remainingContent -= bytesRead;
}
return bytesRead;
}
size_t HTTPRequest::readChars(char * buffer, size_t length) {
return readBytes((byte*)buffer, length);
}
size_t HTTPRequest::getContentLength() {
return _remainingContent;
}
std::string HTTPRequest::getRequestString() {
return _requestString;
}
std::string HTTPRequest::getMethod() {
return _method;
}
std::string HTTPRequest::getTag() {
return _resolvedNode->_tag;
}
bool HTTPRequest::requestComplete() {
if (_contentLengthSet) {
// If we have a content size, rely on it.
return (_remainingContent == 0);
} else {
// If there is no more input...
return (_con->pendingBufferSize() == 0);
}
}
/**
* This function will drop whatever is remaining of the request body
*/
void HTTPRequest::discardRequestBody() {
byte buf[16];
while(!requestComplete()) {
readBytes(buf, 16);
}
}
std::string HTTPRequest::getBasicAuthUser() {
std::string token = decodeBasicAuthToken();
size_t splitpoint = token.find(":");
if (splitpoint != std::string::npos && splitpoint > 0) {
return token.substr(0, splitpoint);
} else {
return std::string();
}
}
std::string HTTPRequest::getBasicAuthPassword() {
std::string token = decodeBasicAuthToken();
size_t splitpoint = token.find(":");
if (splitpoint != std::string::npos && splitpoint > 0) {
return token.substr(splitpoint+1);
} else {
return std::string();
}
}
std::string HTTPRequest::decodeBasicAuthToken() {
std::string basicAuthString = getHeader("Authorization");
// Get the length of the token
size_t sourceLength = basicAuthString.length();
// Only handle basic auth tokens
if (basicAuthString.substr(0, 6) != "Basic ") {
return std::string();
}
// If the token is too long, skip
if (sourceLength > 100) {
return std::string();
} else {
// Try to decode. As we are using mbedtls anyway, we can use that function
unsigned char * bufOut = new unsigned char[basicAuthString.length()];
size_t outputLength = 0;
int res = mbedtls_base64_decode(
bufOut,
sourceLength,
&outputLength,
((const unsigned char *)basicAuthString.substr(6).c_str()), // Strip "Basic "
sourceLength - 6 // Strip "Basic "
);
// Failure of decoding
if (res != 0) {
delete[] bufOut;
return std::string();
}
std::string tokenRes = std::string((char*)bufOut, outputLength);
delete[] bufOut;
return tokenRes;
}
}
bool HTTPRequest::isSecure() {
return _con->isSecure();
}
void HTTPRequest::setWebsocketHandler(WebsocketHandler *wsHandler) {
_con->setWebsocketHandler(wsHandler);
}
} /* namespace httpsserver */

68
src/http/HTTPRequest.hpp Normal file
View File

@@ -0,0 +1,68 @@
#ifndef SRC_HTTPREQUEST_HPP_
#define SRC_HTTPREQUEST_HPP_
#include <Arduino.h>
#include <IPAddress.h>
#include <string>
#include <mbedtls/base64.h>
#include "ConnectionContext.hpp"
#include "HTTPNode.hpp"
#include "HTTPHeader.hpp"
#include "HTTPHeaders.hpp"
#include "ResourceParameters.hpp"
#include "util.hpp"
namespace httpsserver {
/**
* \brief Represents the request stream for an HTTP request
*/
class HTTPRequest {
public:
HTTPRequest(ConnectionContext * con, HTTPHeaders * headers, HTTPNode * resolvedNode, std::string method, ResourceParameters * params, std::string requestString);
virtual ~HTTPRequest();
std::string getHeader(std::string const &name);
void setHeader(std::string const &name, std::string const &value);
HTTPNode * getResolvedNode();
std::string getRequestString();
std::string getMethod();
std::string getTag();
IPAddress getClientIP();
size_t readChars(char * buffer, size_t length);
size_t readBytes(byte * buffer, size_t length);
size_t getContentLength();
bool requestComplete();
void discardRequestBody();
ResourceParameters * getParams();
HTTPHeaders *getHTTPHeaders();
std::string getBasicAuthUser();
std::string getBasicAuthPassword();
bool isSecure();
void setWebsocketHandler(WebsocketHandler *wsHandler);
private:
std::string decodeBasicAuthToken();
ConnectionContext * _con;
HTTPHeaders * _headers;
HTTPNode * _resolvedNode;
std::string _method;
ResourceParameters * _params;
std::string _requestString;
bool _contentLengthSet;
size_t _remainingContent;
};
} /* namespace httpsserver */
#endif /* SRC_HTTPREQUEST_HPP_ */

188
src/http/HTTPResponse.cpp Normal file
View File

@@ -0,0 +1,188 @@
#include "HTTPResponse.hpp"
#include <Arduino.h>
#include "lwip/sockets.h"
namespace httpsserver {
HTTPResponse::HTTPResponse(ConnectionContext * con):
_con(con) {
// Default status code is 200 OK
_statusCode = 200;
_statusText = "OK";
_headerWritten = false;
_isError = false;
_responseCacheSize = con->getCacheSize();
_responseCachePointer = 0;
if (_responseCacheSize > 0) {
HTTPS_LOGD("Creating buffered response, size: %d", _responseCacheSize);
_responseCache = new byte[_responseCacheSize];
} else {
HTTPS_LOGD("Creating non-buffered response");
_responseCache = NULL;
}
}
HTTPResponse::~HTTPResponse() {
if (_responseCache != NULL) {
delete[] _responseCache;
}
_headers.clearAll();
}
void HTTPResponse::setStatusCode(uint16_t statusCode) {
_statusCode = statusCode;
}
void HTTPResponse::setStatusText(std::string const &statusText) {
_statusText = statusText;
}
uint16_t HTTPResponse::getStatusCode() {
return _statusCode;
}
std::string HTTPResponse::getStatusText() {
return _statusText;
}
void HTTPResponse::setHeader(std::string const &name, std::string const &value) {
_headers.set(new HTTPHeader(name, value));
}
std::string HTTPResponse::getHeader(std::string const &name) {
HTTPHeader * h = _headers.get(name);
if (h != NULL) {
return h->_value;
} else {
return std::string();
}
}
bool HTTPResponse::isHeaderWritten() {
return _headerWritten;
}
bool HTTPResponse::isResponseBuffered() {
return _responseCache != NULL;
}
void HTTPResponse::finalize() {
if (isResponseBuffered()) {
drainBuffer();
}
}
/**
* Writes a string to the response. May be called several times.
*/
void HTTPResponse::printStd(const std::string &str) {
write((uint8_t*)str.c_str(), str.length());
}
/**
* Writes bytes to the response. May be called several times.
*/
size_t HTTPResponse::write(const uint8_t *buffer, size_t size) {
if(!isResponseBuffered()) {
printHeader();
}
return writeBytesInternal(buffer, size);
}
/**
* Writes a single byte to the response.
*/
size_t HTTPResponse::write(uint8_t b) {
if(!isResponseBuffered()) {
printHeader();
}
byte ba[] = {b};
return writeBytesInternal(ba, 1);
}
/**
* If not already done, writes the header.
*/
void HTTPResponse::printHeader() {
if (!_headerWritten) {
HTTPS_LOGD("Printing headers");
// Status line, like: "HTTP/1.1 200 OK\r\n"
std::string statusLine = "HTTP/1.1 " + intToString(_statusCode) + " " + _statusText + "\r\n";
printInternal(statusLine, true);
// Each header, like: "Host: myEsp32\r\n"
std::vector<HTTPHeader *> * headers = _headers.getAll();
for(std::vector<HTTPHeader*>::iterator header = headers->begin(); header != headers->end(); ++header) {
printInternal((*header)->print()+"\r\n", true);
}
printInternal("\r\n", true);
_headerWritten=true;
}
}
/**
* This method can be called to cancel the ongoing transmission and send the error page (if possible)
*/
void HTTPResponse::error() {
_con->signalRequestError();
}
void HTTPResponse::printInternal(const std::string &str, bool skipBuffer) {
writeBytesInternal((uint8_t*)str.c_str(), str.length(), skipBuffer);
}
size_t HTTPResponse::writeBytesInternal(const void * data, int length, bool skipBuffer) {
if (!_isError) {
if (isResponseBuffered() && !skipBuffer) {
// We are buffering ...
if(length <= _responseCacheSize - _responseCachePointer) {
// ... and there is space left in the buffer -> Write to buffer
size_t end = _responseCachePointer + length;
size_t i = 0;
while(_responseCachePointer < end) {
_responseCache[_responseCachePointer++] = ((byte*)data)[i++];
}
// Returning skips the SSL_write below
return length;
} else {
// .., and the buffer is too small. This is the point where we switch from
// caching to streaming
if (!_headerWritten) {
setHeader("Connection", "close");
}
drainBuffer(true);
}
}
return _con->writeBuffer((byte*)data, length);
} else {
return 0;
}
}
void HTTPResponse::drainBuffer(bool onOverflow) {
if (!_headerWritten) {
if (_responseCache != NULL && !onOverflow) {
_headers.set(new HTTPHeader("Content-Length", intToString(_responseCachePointer)));
}
printHeader();
}
if (_responseCache != NULL) {
HTTPS_LOGD("Draining response buffer");
// Check for 0 as it may be an overflow reaction without any data that has been written earlier
if(_responseCachePointer > 0) {
// FIXME: Return value?
_con->writeBuffer((byte*)_responseCache, _responseCachePointer);
}
delete[] _responseCache;
_responseCache = NULL;
}
}
} /* namespace httpsserver */

71
src/http/HTTPResponse.hpp Normal file
View File

@@ -0,0 +1,71 @@
#ifndef SRC_HTTPRESPONSE_HPP_
#define SRC_HTTPRESPONSE_HPP_
#include <Arduino.h>
#include <string>
// Arduino declares it's own min max, incompatible with the stl...
#undef min
#undef max
#undef write
#include <vector>
#include <openssl/ssl.h>
#include "util.hpp"
#include "ConnectionContext.hpp"
#include "HTTPHeaders.hpp"
#include "HTTPHeader.hpp"
namespace httpsserver {
/**
* \brief Represents the response stream of an HTTP request
*/
class HTTPResponse : public Print {
public:
HTTPResponse(ConnectionContext * con);
virtual ~HTTPResponse();
void setStatusCode(uint16_t statusCode);
void setStatusText(std::string const &statusText);
uint16_t getStatusCode();
std::string getStatusText();
void setHeader(std::string const &name, std::string const &value);
std::string getHeader(std::string const &name);
bool isHeaderWritten();
void printStd(std::string const &str);
// From Print:
size_t write(const uint8_t *buffer, size_t size);
size_t write(uint8_t);
void error();
bool isResponseBuffered();
void finalize();
ConnectionContext * _con;
private:
void printHeader();
void printInternal(const std::string &str, bool skipBuffer = false);
size_t writeBytesInternal(const void * data, int length, bool skipBuffer = false);
void drainBuffer(bool onOverflow = false);
uint16_t _statusCode;
std::string _statusText;
HTTPHeaders _headers;
bool _headerWritten;
bool _isError;
// Response cache
byte * _responseCache;
size_t _responseCacheSize;
size_t _responseCachePointer;
};
} /* namespace httpsserver */
#endif /* SRC_HTTPRESPONSE_HPP_ */

View File

@@ -0,0 +1,14 @@
#ifndef SRC_HTTPSCALLBACKFUNCTION_HPP_
#define SRC_HTTPSCALLBACKFUNCTION_HPP_
#include "HTTPRequest.hpp"
#include "HTTPResponse.hpp"
namespace httpsserver {
/**
* \brief A callback function that will be called by the server to handle a request
*/
typedef void (HTTPSCallbackFunction)(HTTPRequest * req, HTTPResponse * res);
}
#endif /* SRC_HTTPSCALLBACKFUNCTION_HPP_ */

View File

@@ -0,0 +1,123 @@
#include "HTTPSConnection.hpp"
namespace httpsserver {
HTTPSConnection::HTTPSConnection(ResourceResolver * resResolver):
HTTPConnection(resResolver) {
_ssl = NULL;
}
HTTPSConnection::~HTTPSConnection() {
// Close the socket
closeConnection();
}
bool HTTPSConnection::isSecure() {
return true;
}
/**
* Initializes the connection from a server socket.
*
* The call WILL BLOCK if accept(serverSocketID) blocks. So use select() to check for that in advance.
*/
int HTTPSConnection::initialize(int serverSocketID, SSL_CTX * sslCtx, HTTPHeaders *defaultHeaders) {
if (_connectionState == STATE_UNDEFINED) {
// Let the base class connect the plain tcp socket
int resSocket = HTTPConnection::initialize(serverSocketID, defaultHeaders);
// Build up SSL Connection context if the socket has been created successfully
if (resSocket >= 0) {
_ssl = SSL_new(sslCtx);
if (_ssl) {
// Bind SSL to the socket
int success = SSL_set_fd(_ssl, resSocket);
if (success) {
// Perform the handshake
success = SSL_accept(_ssl);
if (success) {
return resSocket;
} else {
HTTPS_LOGE("SSL_accept failed. Aborting handshake. FID=%d", resSocket);
}
} else {
HTTPS_LOGE("SSL_set_fd failed. Aborting handshake. FID=%d", resSocket);
}
} else {
HTTPS_LOGE("SSL_new failed. Aborting handshake. FID=%d", resSocket);
}
} else {
HTTPS_LOGE("Could not accept() new connection. FID=%d", resSocket);
}
_connectionState = STATE_ERROR;
_clientState = CSTATE_ACTIVE;
// This will only be called if the connection could not be established and cleanup
// variables like _ssl etc.
closeConnection();
}
// Error: The connection has already been established or could not be established
return -1;
}
void HTTPSConnection::closeConnection() {
// FIXME: Copy from HTTPConnection, could be done better probably
if (_connectionState != STATE_ERROR && _connectionState != STATE_CLOSED) {
// First call to closeConnection - set the timestamp to calculate the timeout later on
if (_connectionState != STATE_CLOSING) {
_shutdownTS = millis();
}
// Set the connection state to closing. We stay in closing as long as SSL has not been shutdown
// correctly
_connectionState = STATE_CLOSING;
}
// Try to tear down SSL while we are in the _shutdownTS timeout period or if an error occurred
if (_ssl) {
if(_connectionState == STATE_ERROR || SSL_shutdown(_ssl) == 0) {
// SSL_shutdown will return 1 as soon as the client answered with close notify
// This means we are safe to close the socket
SSL_free(_ssl);
_ssl = NULL;
} else if (_shutdownTS + HTTPS_SHUTDOWN_TIMEOUT < millis()) {
// The timeout has been hit, we force SSL shutdown now by freeing the context
SSL_free(_ssl);
_ssl = NULL;
HTTPS_LOGW("SSL_shutdown did not receive close notification from the client");
_connectionState = STATE_ERROR;
}
}
// If SSL has been brought down, close the socket
if (!_ssl) {
HTTPConnection::closeConnection();
}
}
size_t HTTPSConnection::writeBuffer(byte* buffer, size_t length) {
return SSL_write(_ssl, buffer, length);
}
size_t HTTPSConnection::readBytesToBuffer(byte* buffer, size_t length) {
return SSL_read(_ssl, buffer, length);
}
size_t HTTPSConnection::pendingByteCount() {
return SSL_pending(_ssl);
}
bool HTTPSConnection::canReadData() {
return HTTPConnection::canReadData() || (SSL_pending(_ssl) > 0);
}
} /* namespace httpsserver */

View File

@@ -0,0 +1,58 @@
#ifndef SRC_HTTPSCONNECTION_HPP_
#define SRC_HTTPSCONNECTION_HPP_
#include <Arduino.h>
#include <string>
// Required for SSL
#include "openssl/ssl.h"
#undef read
// Required for sockets
#include "lwip/netdb.h"
#undef read
#include "lwip/sockets.h"
#include "HTTPSServerConstants.hpp"
#include "HTTPConnection.hpp"
#include "HTTPHeaders.hpp"
#include "HTTPHeader.hpp"
#include "ResourceResolver.hpp"
#include "ResolvedResource.hpp"
#include "ResourceNode.hpp"
#include "HTTPRequest.hpp"
#include "HTTPResponse.hpp"
namespace httpsserver {
/**
* \brief Connection class for an open TLS-enabled connection to an HTTPSServer
*/
class HTTPSConnection : public HTTPConnection {
public:
HTTPSConnection(ResourceResolver * resResolver);
virtual ~HTTPSConnection();
virtual int initialize(int serverSocketID, SSL_CTX * sslCtx, HTTPHeaders *defaultHeaders);
virtual void closeConnection();
virtual bool isSecure();
protected:
friend class HTTPRequest;
friend class HTTPResponse;
virtual size_t readBytesToBuffer(byte* buffer, size_t length);
virtual size_t pendingByteCount();
virtual bool canReadData();
virtual size_t writeBuffer(byte* buffer, size_t length);
private:
// SSL context for this connection
SSL * _ssl;
};
} /* namespace httpsserver */
#endif /* SRC_HTTPSCONNECTION_HPP_ */

102
src/http/HTTPSServer.cpp Normal file
View File

@@ -0,0 +1,102 @@
#include "HTTPSServer.hpp"
namespace httpsserver {
HTTPSServer::HTTPSServer(SSLCert * cert, const uint16_t port, const uint8_t maxConnections, const in_addr_t bindAddress):
HTTPServer(port, maxConnections, bindAddress),
_cert(cert) {
// Configure runtime data
_sslctx = NULL;
}
HTTPSServer::~HTTPSServer() {
}
/**
* This method starts the server and begins to listen on the port
*/
uint8_t HTTPSServer::setupSocket() {
if (!isRunning()) {
if (!setupSSLCTX()) {
Serial.println("setupSSLCTX failed");
return 0;
}
if (!setupCert()) {
Serial.println("setupCert failed");
SSL_CTX_free(_sslctx);
_sslctx = NULL;
return 0;
}
if (HTTPServer::setupSocket()) {
return 1;
} else {
Serial.println("setupSockets failed");
SSL_CTX_free(_sslctx);
_sslctx = NULL;
return 0;
}
} else {
return 1;
}
}
void HTTPSServer::teardownSocket() {
HTTPServer::teardownSocket();
// Tear down the SSL context
SSL_CTX_free(_sslctx);
_sslctx = NULL;
}
int HTTPSServer::createConnection(int idx) {
HTTPSConnection * newConnection = new HTTPSConnection(this);
_connections[idx] = newConnection;
return newConnection->initialize(_socket, _sslctx, &_defaultHeaders);
}
/**
* This method configures the ssl context that is used for the server
*/
uint8_t HTTPSServer::setupSSLCTX() {
_sslctx = SSL_CTX_new(TLSv1_2_server_method());
if (_sslctx) {
// Set SSL Timeout to 5 minutes
SSL_CTX_set_timeout(_sslctx, 300);
return 1;
} else {
_sslctx = NULL;
return 0;
}
}
/**
* This method configures the certificate and private key for the given
* ssl context
*/
uint8_t HTTPSServer::setupCert() {
// Configure the certificate first
uint8_t ret = SSL_CTX_use_certificate_ASN1(
_sslctx,
_cert->getCertLength(),
_cert->getCertData()
);
// Then set the private key accordingly
if (ret) {
ret = SSL_CTX_use_RSAPrivateKey_ASN1(
_sslctx,
_cert->getPKData(),
_cert->getPKLength()
);
}
return ret;
}
} /* namespace httpsserver */

56
src/http/HTTPSServer.hpp Normal file
View File

@@ -0,0 +1,56 @@
#ifndef SRC_HTTPSSERVER_HPP_
#define SRC_HTTPSSERVER_HPP_
// Standard library
#include <string>
// Arduino stuff
#include <Arduino.h>
// Required for SSL
#include "openssl/ssl.h"
#undef read
// Internal includes
#include "HTTPServer.hpp"
#include "HTTPSServerConstants.hpp"
#include "HTTPHeaders.hpp"
#include "HTTPHeader.hpp"
#include "ResourceNode.hpp"
#include "ResourceResolver.hpp"
#include "ResolvedResource.hpp"
#include "HTTPSConnection.hpp"
#include "SSLCert.hpp"
namespace httpsserver {
/**
* \brief Main implementation of the HTTP Server with TLS support. Use HTTPServer for plain HTTP
*/
class HTTPSServer : public HTTPServer {
public:
HTTPSServer(SSLCert * cert, const uint16_t portHTTPS = 443, const uint8_t maxConnections = 4, const in_addr_t bindAddress = 0);
virtual ~HTTPSServer();
private:
// Static configuration. Port, keys, etc. ====================
// Certificate that should be used (includes private key)
SSLCert * _cert;
//// Runtime data ============================================
SSL_CTX * _sslctx;
// Status of the server: Are we running, or not?
// Setup functions
virtual uint8_t setupSocket();
virtual void teardownSocket();
uint8_t setupSSLCTX();
uint8_t setupCert();
// Helper functions
virtual int createConnection(int idx);
};
} /* namespace httpsserver */
#endif /* SRC_HTTPSSERVER_HPP_ */

View File

@@ -0,0 +1,89 @@
#ifndef SRC_HTTPSSERVERCONSTANTS_HPP_
#define SRC_HTTPSSERVERCONSTANTS_HPP_
#include "Arduino.h"
// 1: Error
// 2: Error + Warn
// 3: Error + Warn + Info
// 4: Error + Warn + Info + Debug
#ifndef HTTPS_LOGLEVEL
#define HTTPS_LOGLEVEL 3
#endif
#ifdef HTTPS_LOGTIMESTAMP
#define HTTPS_LOGTAG(LVL) Serial.printf("[HTTPS:" LVL ":%10lu] ", millis())
#else
#define HTTPS_LOGTAG(LVL) Serial.print("[HTTPS:" LVL "] ")
#endif
#if HTTPS_LOGLEVEL > 0
#define HTTPS_LOGE(...) HTTPS_LOGTAG("E");Serial.printf(__VA_ARGS__);Serial.println()
#else
#define HTTPS_LOGE(...) do {} while (0)
#endif
#if HTTPS_LOGLEVEL > 1
#define HTTPS_LOGW(...) HTTPS_LOGTAG("W");Serial.printf(__VA_ARGS__);Serial.println()
#else
#define HTTPS_LOGW(...) do {} while (0)
#endif
#if HTTPS_LOGLEVEL > 2
#define HTTPS_LOGI(...) HTTPS_LOGTAG("I");Serial.printf(__VA_ARGS__);Serial.println()
#else
#define HTTPS_LOGI(...) do {} while (0)
#endif
#if HTTPS_LOGLEVEL > 3
#define HTTPS_LOGD(...) HTTPS_LOGTAG("D");Serial.printf(__VA_ARGS__);Serial.println()
#else
#define HTTPS_LOGD(...) do {} while (0)
#endif
// The following lines define limits of the protocol. Exceeding these limits will lead to a 500 error
// Maximum of header lines that are parsed
#ifndef HTTPS_REQUEST_MAX_HEADERS
#define HTTPS_REQUEST_MAX_HEADERS 20
#endif
// Maximum length of the request line (GET /... HTTP/1.1)
#ifndef HTTPS_REQUEST_MAX_REQUEST_LENGTH
#define HTTPS_REQUEST_MAX_REQUEST_LENGTH 128
#endif
// Maximum length of a header line (including name and value)
#ifndef HTTPS_REQUEST_MAX_HEADER_LENGTH
#define HTTPS_REQUEST_MAX_HEADER_LENGTH 384
#endif
// Chunk size used for reading data from the ssl-enabled socket
#ifndef HTTPS_CONNECTION_DATA_CHUNK_SIZE
#define HTTPS_CONNECTION_DATA_CHUNK_SIZE 512
#endif
// Size (in bytes) of the Connection:keep-alive Cache (we need to be able to
// store-and-forward the response to calculate the content-size)
#ifndef HTTPS_KEEPALIVE_CACHESIZE
#define HTTPS_KEEPALIVE_CACHESIZE 1400
#endif
// Timeout for an HTTPS connection without any transmission
#ifndef HTTPS_CONNECTION_TIMEOUT
#define HTTPS_CONNECTION_TIMEOUT 20000
#endif
// Timeout used to wait for shutdown of SSL connection (ms)
// (time for the client to return notify close flag) - without it, truncation attacks might be possible
#ifndef HTTPS_SHUTDOWN_TIMEOUT
#define HTTPS_SHUTDOWN_TIMEOUT 5000
#endif
// Length of a SHA1 hash
#ifndef HTTPS_SHA1_LENGTH
#define HTTPS_SHA1_LENGTH 20
#endif
#endif /* SRC_HTTPSSERVERCONSTANTS_HPP_ */

211
src/http/HTTPServer.cpp Normal file
View File

@@ -0,0 +1,211 @@
#include "HTTPServer.hpp"
namespace httpsserver {
HTTPServer::HTTPServer(const uint16_t port, const uint8_t maxConnections, const in_addr_t bindAddress):
_port(port),
_maxConnections(maxConnections),
_bindAddress(bindAddress) {
// Create space for the connections
_connections = new HTTPConnection*[maxConnections];
for(uint8_t i = 0; i < maxConnections; i++) _connections[i] = NULL;
// Configure runtime data
_socket = -1;
_running = false;
}
HTTPServer::~HTTPServer() {
// Stop the server.
// This will also remove all existing connections
if(_running) {
stop();
}
// Delete connection pointers
delete[] _connections;
}
/**
* This method starts the server and begins to listen on the port
*/
uint8_t HTTPServer::start() {
if (!_running) {
if (setupSocket()) {
_running = true;
return 1;
}
return 0;
} else {
return 1;
}
}
bool HTTPServer::isRunning() {
return _running;
}
/**
* This method stops the server
*/
void HTTPServer::stop() {
if (_running) {
// Set the flag that the server is stopped
_running = false;
// Clean up the connections
bool hasOpenConnections = true;
while(hasOpenConnections) {
hasOpenConnections = false;
for(int i = 0; i < _maxConnections; i++) {
if (_connections[i] != NULL) {
_connections[i]->closeConnection();
// Check if closing succeeded. If not, we need to call the close function multiple times
// and wait for the client
if (_connections[i]->isClosed()) {
delete _connections[i];
_connections[i] = NULL;
} else {
hasOpenConnections = true;
}
}
}
delay(1);
}
teardownSocket();
}
}
/**
* Adds a default header that is included in every response.
*
* This could be used for example to add a Server: header or for CORS options
*/
void HTTPServer::setDefaultHeader(std::string name, std::string value) {
_defaultHeaders.set(new HTTPHeader(name, value));
}
/**
* The loop method can either be called by periodical interrupt or in the main loop and handles processing
* of data
*/
void HTTPServer::loop() {
// Only handle requests if the server is still running
if(!_running) return;
// Step 1: Process existing connections
// Process open connections and store the index of a free connection
// (we might use that later on)
int freeConnectionIdx = -1;
for (int i = 0; i < _maxConnections; i++) {
// Fetch a free index in the pointer array
if (_connections[i] == NULL) {
freeConnectionIdx = i;
} else {
// if there is a connection (_connections[i]!=NULL), check if its open or closed:
if (_connections[i]->isClosed()) {
// if it's closed, clean up:
delete _connections[i];
_connections[i] = NULL;
freeConnectionIdx = i;
} else {
// if not, process it:
_connections[i]->loop();
}
}
}
// Step 2: Check for new connections
// This makes only sense if there is space to store the connection
if (freeConnectionIdx > -1) {
// We create a file descriptor set to be able to use the select function
fd_set sockfds;
// Out socket is the only socket in this set
FD_ZERO(&sockfds);
FD_SET(_socket, &sockfds);
// We define a "immediate" timeout
timeval timeout;
timeout.tv_sec = 0;
timeout.tv_usec = 0; // Return immediately, if possible
// Wait for input
// As by 2017-12-14, it seems that FD_SETSIZE is defined as 0x40, but socket IDs now
// start at 0x1000, so we need to use _socket+1 here
select(_socket + 1, &sockfds, NULL, NULL, &timeout);
// There is input
if (FD_ISSET(_socket, &sockfds)) {
int socketIdentifier = createConnection(freeConnectionIdx);
// If initializing did not work, discard the new socket immediately
if (socketIdentifier < 0) {
delete _connections[freeConnectionIdx];
_connections[freeConnectionIdx] = NULL;
}
}
}
}
int HTTPServer::createConnection(int idx) {
HTTPConnection * newConnection = new HTTPConnection(this);
_connections[idx] = newConnection;
return newConnection->initialize(_socket, &_defaultHeaders);
}
/**
* This method prepares the tcp server socket
*/
uint8_t HTTPServer::setupSocket() {
// (AF_INET = IPv4, SOCK_STREAM = TCP)
_socket = socket(AF_INET, SOCK_STREAM, 0);
if (_socket>=0) {
_sock_addr.sin_family = AF_INET;
// Listen on all interfaces
_sock_addr.sin_addr.s_addr = _bindAddress;
// Set the server port
_sock_addr.sin_port = htons(_port);
// Now bind the TCP socket we did create above to the socket address we specified
// (The TCP-socket now listens on 0.0.0.0:port)
int err = bind(_socket, (struct sockaddr* )&_sock_addr, sizeof(_sock_addr));
if(!err) {
err = listen(_socket, _maxConnections);
if (!err) {
return 1;
} else {
close(_socket);
_socket = -1;
return 0;
}
} else {
close(_socket);
_socket = -1;
return 0;
}
} else {
_socket = -1;
return 0;
}
}
void HTTPServer::teardownSocket() {
// Close the actual server sockets
close(_socket);
_socket = -1;
}
} /* namespace httpsserver */

76
src/http/HTTPServer.hpp Normal file
View File

@@ -0,0 +1,76 @@
#ifndef SRC_HTTPSERVER_HPP_
#define SRC_HTTPSERVER_HPP_
// Standard library
#include <string>
// Arduino stuff
#include <Arduino.h>
// Required for sockets
#include "lwip/netdb.h"
#undef read
#include "lwip/sockets.h"
#include "lwip/inet.h"
// Internal includes
#include "HTTPSServerConstants.hpp"
#include "HTTPHeaders.hpp"
#include "HTTPHeader.hpp"
#include "ResourceNode.hpp"
#include "ResourceResolver.hpp"
#include "ResolvedResource.hpp"
#include "HTTPConnection.hpp"
namespace httpsserver {
/**
* \brief Main implementation for the plain HTTP server. Use HTTPSServer for TLS support
*/
class HTTPServer : public ResourceResolver {
public:
HTTPServer(const uint16_t portHTTPS = 80, const uint8_t maxConnections = 8, const in_addr_t bindAddress = 0);
virtual ~HTTPServer();
uint8_t start();
void stop();
bool isRunning();
void loop();
void setDefaultHeader(std::string name, std::string value);
protected:
// Static configuration. Port, keys, etc. ====================
// Certificate that should be used (includes private key)
const uint16_t _port;
// Max parallel connections that the server will accept
const uint8_t _maxConnections;
// Address to bind to (0 = all interfaces)
const in_addr_t _bindAddress;
//// Runtime data ============================================
// The array of connections that are currently active
HTTPConnection ** _connections;
// Status of the server: Are we running, or not?
boolean _running;
// The server socket
int _socket;
// The server socket address, that our service is bound to
sockaddr_in _sock_addr;
// Headers that are included in every response
HTTPHeaders _defaultHeaders;
// Setup functions
virtual uint8_t setupSocket();
virtual void teardownSocket();
// Helper functions
virtual int createConnection(int idx);
};
}
#endif /* SRC_HTTPSERVER_HPP_ */

View File

@@ -0,0 +1,128 @@
#include "HTTPURLEncodedBodyParser.hpp"
#define CHUNKSIZE 512
#define MINCHUNKSIZE 64
namespace httpsserver {
HTTPURLEncodedBodyParser::HTTPURLEncodedBodyParser(HTTPRequest * req):
HTTPBodyParser(req),
bodyBuffer(NULL),
bodyPtr(NULL),
bodyLength(0),
fieldBuffer(""),
fieldPtr(NULL),
fieldRemainingLength(0)
{
bodyLength = _request->getContentLength();
if (bodyLength) {
// We know the body length. We try to read that much and give an error if it fails.
bodyBuffer = (char *)malloc(bodyLength+1);
if (bodyBuffer == NULL) {
HTTPS_LOGE("HTTPURLEncodedBodyParser: out of memory");
return;
}
bodyPtr = bodyBuffer;
size_t toRead = bodyLength;
while(toRead > 0) {
size_t didRead = _request->readChars(bodyPtr, toRead);
if (didRead == 0) {
HTTPS_LOGE("HTTPURLEncodedBodyParser: short read");
bodyLength = bodyPtr - bodyBuffer;
break;
}
bodyPtr += didRead;
toRead -= didRead;
}
} else {
// We don't know the length. Read as much as possible.
bodyBuffer = (char *)malloc(CHUNKSIZE+1);
if (bodyBuffer == NULL) {
HTTPS_LOGE("HTTPURLEncodedBodyParser: out of memory");
return;
}
bodyPtr = bodyBuffer;
size_t bufferUsed = 0;
size_t bufferAvailable = CHUNKSIZE;
while(!_request->requestComplete()) {
if (bufferAvailable < MINCHUNKSIZE) {
char *pBuf = (char *)realloc(bodyBuffer, bufferUsed + CHUNKSIZE+1);
if (pBuf == NULL) {
HTTPS_LOGE("HTTPURLEncodedBodyParser: out of memory");
free(bodyBuffer);
bodyBuffer = NULL;
return;
}
bodyBuffer = pBuf;
bufferAvailable = CHUNKSIZE;
}
size_t didRead = _request->readChars(bodyBuffer+bufferUsed, bufferAvailable);
bufferUsed += didRead;
bufferAvailable -= didRead;
}
bodyLength = bufferUsed;
}
bodyPtr = bodyBuffer;
bodyBuffer[bodyLength] = '\0';
}
HTTPURLEncodedBodyParser::~HTTPURLEncodedBodyParser() {
if (bodyBuffer) {
free(bodyBuffer);
}
bodyBuffer = NULL;
}
bool HTTPURLEncodedBodyParser::nextField() {
fieldBuffer = "";
fieldPtr = NULL;
fieldRemainingLength = 0;
char *equalPtr = index(bodyPtr, '=');
if (equalPtr == NULL) {
return false;
}
fieldName = std::string(bodyPtr, equalPtr-bodyPtr);
char *valuePtr = equalPtr + 1;
char *endPtr = index(valuePtr, '&');
if (endPtr == NULL) {
endPtr = equalPtr + strlen(equalPtr);
bodyPtr = endPtr;
} else {
bodyPtr = endPtr+1;
}
fieldBuffer = std::string(valuePtr, endPtr - valuePtr);
fieldBuffer = urlDecode(fieldBuffer);
fieldRemainingLength = fieldBuffer.size();
fieldPtr = fieldBuffer.c_str();
return true;
}
std::string HTTPURLEncodedBodyParser::getFieldName() {
return fieldName;
}
std::string HTTPURLEncodedBodyParser::getFieldFilename() {
return "";
}
std::string HTTPURLEncodedBodyParser::getFieldMimeType() {
return std::string("text/plain");
}
bool HTTPURLEncodedBodyParser::endOfField() {
return fieldRemainingLength <= 0;
}
size_t HTTPURLEncodedBodyParser::read(byte* buffer, size_t bufferSize) {
if (bufferSize > fieldRemainingLength) {
bufferSize = fieldRemainingLength;
}
memcpy(buffer, fieldPtr, bufferSize);
fieldRemainingLength -= bufferSize;
fieldPtr += bufferSize;
return bufferSize;
}
} /* namespace httpsserver */

View File

@@ -0,0 +1,32 @@
#ifndef SRC_HTTPURLENCODEDBODYPARSER_HPP_
#define SRC_HTTPURLENCODEDBODYPARSER_HPP_
#include <Arduino.h>
#include "HTTPBodyParser.hpp"
namespace httpsserver {
class HTTPURLEncodedBodyParser : public HTTPBodyParser {
public:
// From HTTPBodyParser
HTTPURLEncodedBodyParser(HTTPRequest * req);
~HTTPURLEncodedBodyParser();
virtual bool nextField();
virtual std::string getFieldName();
virtual std::string getFieldFilename();
virtual std::string getFieldMimeType();
virtual bool endOfField();
virtual size_t read(byte* buffer, size_t bufferSize);
protected:
char *bodyBuffer;
char *bodyPtr;
size_t bodyLength;
std::string fieldName;
std::string fieldBuffer;
const char *fieldPtr;
size_t fieldRemainingLength;
};
} // namespace httpserver
#endif

View File

@@ -0,0 +1,15 @@
#include "HTTPValidator.hpp"
namespace httpsserver {
HTTPValidator::HTTPValidator(const uint8_t idx, const HTTPValidationFunction * validatorFunction):
_idx(idx),
_validatorFunction(validatorFunction) {
}
HTTPValidator::~HTTPValidator() {
}
} /* namespace httpsserver */

View File

@@ -0,0 +1,23 @@
#ifndef SRC_HTTPVALIDATOR_HPP_
#define SRC_HTTPVALIDATOR_HPP_
#include <string>
namespace httpsserver {
typedef bool (HTTPValidationFunction)(std::string);
/**
* \brief Internal representation of a validator function
*/
class HTTPValidator {
public:
HTTPValidator(const uint8_t idx, const HTTPValidationFunction * validatorFunction);
virtual ~HTTPValidator();
const uint8_t _idx;
const HTTPValidationFunction * _validatorFunction;
};
} /* namespace httpsserver */
#endif /* SRC_HTTPVALIDATOR_HPP_ */

View File

@@ -0,0 +1,40 @@
#include "ResolvedResource.hpp"
namespace httpsserver {
ResolvedResource::ResolvedResource() {
_matchingNode = NULL;
_params = NULL;
}
ResolvedResource::~ResolvedResource() {
// Delete only params, nodes are reused/server-internal
if (_params != NULL) {
delete _params;
}
}
bool ResolvedResource::didMatch() {
return _matchingNode != NULL;
}
HTTPNode * ResolvedResource::getMatchingNode() {
return _matchingNode;
}
void ResolvedResource::setMatchingNode(HTTPNode * node) {
_matchingNode = node;
}
ResourceParameters * ResolvedResource::getParams() {
return _params;
}
void ResolvedResource::setParams(ResourceParameters * params) {
if (_params != NULL && _params!=params) {
delete _params;
}
_params = params;
}
} /* namespace httpsserver */

View File

@@ -0,0 +1,30 @@
#ifndef SRC_RESOLVEDRESOURCE_HPP_
#define SRC_RESOLVEDRESOURCE_HPP_
#include "ResourceNode.hpp"
#include "ResourceParameters.hpp"
namespace httpsserver {
/**
* \brief This class represents a resolved resource, meaning the result of mapping a string URL to an HTTPNode
*/
class ResolvedResource {
public:
ResolvedResource();
~ResolvedResource();
void setMatchingNode(HTTPNode * node);
HTTPNode * getMatchingNode();
bool didMatch();
ResourceParameters * getParams();
void setParams(ResourceParameters * params);
private:
HTTPNode * _matchingNode;
ResourceParameters * _params;
};
} /* namespace httpsserver */
#endif /* SRC_RESOLVEDRESOURCE_HPP_ */

16
src/http/ResourceNode.cpp Normal file
View File

@@ -0,0 +1,16 @@
#include "ResourceNode.hpp"
namespace httpsserver {
ResourceNode::ResourceNode(const std::string &path, const std::string &method, const HTTPSCallbackFunction * callback, const std::string &tag):
HTTPNode(path, HANDLER_CALLBACK, tag),
_method(method),
_callback(callback) {
}
ResourceNode::~ResourceNode() {
}
} /* namespace httpsserver */

28
src/http/ResourceNode.hpp Normal file
View File

@@ -0,0 +1,28 @@
#ifndef SRC_RESOURCENODE_HPP_
#define SRC_RESOURCENODE_HPP_
#include <string>
#include "HTTPNode.hpp"
#include "HTTPSCallbackFunction.hpp"
namespace httpsserver {
/**
* \brief This HTTPNode represents a route that maps to a regular HTTP request for a resource (static or dynamic)
*
* It therefore contrasts to the WebsocketNode, which handles requests for Websockets.
*/
class ResourceNode : public HTTPNode {
public:
ResourceNode(const std::string &path, const std::string &method, const HTTPSCallbackFunction * callback, const std::string &tag = "");
virtual ~ResourceNode();
const std::string _method;
const HTTPSCallbackFunction * _callback;
std::string getMethod() { return _method; }
};
} /* namespace httpsserver */
#endif /* SRC_RESOURCENODE_HPP_ */

View File

@@ -0,0 +1,167 @@
#include "ResourceParameters.hpp"
#include "HTTPSServerConstants.hpp"
namespace httpsserver {
ResourceParameters::ResourceParameters() {
}
ResourceParameters::~ResourceParameters() {
}
/**
* @brief Checks whether a specific HTTPS query parameter is set.
*
* Query parameters are key-value pairs that are appended to the URI after a question mark.
*
* If the key exists (either as a value-less parameter or with a value), the function returns true.
*
* @param name The parameter to check
* @return true iff the parameter exists
*/
bool ResourceParameters::isQueryParameterSet(std::string const &name) {
for(auto queryParam = _queryParams.begin(); queryParam != _queryParams.end(); ++queryParam) {
if ((*queryParam).first.compare(name)==0) {
return true;
}
}
return false;
}
/**
* @brief Returns an HTTP query parameter.
*
* Query parameters are key-value pairs that are appended to the URI after a question mark.
*
* The name parameter specifies the name of the query parameter to retrieve. If it is set,
* the value is written to the value parameter and true is returned. If the parameter does
* not exist, value is left unchanged and false is returned. If the parameter is used
* without a value, an empty string is written to value and true is returned.
*
* @param name The name of the parameter to retrieve. If the parameter exists multiple times,
* the first occurence is used for the value. Use beginQueryParameters() to retrieve all values.
* @param value The target to write the value to, if the parameter exists.
* @return true iff the parameter exists and the corresponding value has been written.
*/
bool ResourceParameters::getQueryParameter(std::string const &name, std::string &value) {
for(auto queryParam = _queryParams.begin(); queryParam != _queryParams.end(); ++queryParam) {
if ((*queryParam).first.compare(name)==0) {
value=(*queryParam).second;
return true;
}
}
return false;
}
/**
* @brief Returns the number of query parameters.
*
* Query parameters are key-value pairs that are appended to the URI after a question mark.
*
* @param unique If true, return the number of unique keys (using the same key multiple times
* is counted only once). False by default, as checking for uniqueness is not efficient.
* @return Number of query parameters
*/
size_t ResourceParameters::getQueryParameterCount(bool unique) {
if (!unique) {
return _queryParams.size();
}
size_t count = 0;
for(auto a = _queryParams.begin(); a != _queryParams.end(); ++a) {
bool exists = false;
for(auto b = _queryParams.begin(); !exists && b != a; ++b) {
exists = (*a).first.compare((*b).first)==0;
}
count += exists ? 0 : 1;
}
return count;
}
/**
* @brief Provides iterator access to the query parameters
*
* Query parameters are key-value pairs that are appended to the URI after a question mark.
*
* If you want just a specific parameter, have a look at getQueryParameter()
*
* The iterator will yield pairs of std::string, of which the first value specifies the
* query parameter key and the second value corresponds to the query parameters value.
* If the entry is value-less, the second value will be the empty string.
*
* If the same key is used multiple times in the query, the iterator will yield it multiple
* times, once for each occurence with the specific value.
*
* @return Iterator over std::pairs of std::strings that represent (key, value) pairs
*/
std::vector<std::pair<std::string,std::string>>::iterator ResourceParameters::beginQueryParameters() {
return _queryParams.begin();
}
/**
* @brief Counterpart to beginQueryParameters() for iterating over query parameters
*/
std::vector<std::pair<std::string,std::string>>::iterator ResourceParameters::endQueryParameters() {
return _queryParams.end();
}
void ResourceParameters::setQueryParameter(std::string const &name, std::string const &value) {
std::pair<std::string, std::string> param;
param.first = name;
param.second = value;
_queryParams.push_back(param);
}
/**
* @brief Checks for the existence of a path parameter and returns it as string.
*
* Path parameters are defined by an asterisk as placeholder when specifying the path of
* the ResourceNode and addressed by an index starting at 0 for the first parameter.
*
* For values of idx that have no matching placeholder, value is left unchanged and the
* method will return false.
*
* @param idx Defines the index of the parameter to return, starting with 0.
* @param value The value is written into this parameter.
* @return true iff the value could be written.
*/
bool ResourceParameters::getPathParameter(size_t const idx, std::string &value) {
if (idx < _pathParams.size()) {
value = _pathParams.at(idx);
return true;
}
return false;
}
/**
* @brief Directly returns a path parameter
*
* Path parameters are defined by an asterisk as placeholder when specifying the path of
* the ResourceNode and addressed by an index starting at 0 for the first parameter.
*
* This method will return the parameter specified by the index. The caller is responsible
* to assure that the index exists. Otherwise, an empty string will be returned.
*
* @param idx Defines the index of the parameter to return, starting with 0.
* @return the value of the placeholder
*/
std::string ResourceParameters::getPathParameter(size_t const idx) {
if (idx < _pathParams.size()) {
return _pathParams.at(idx);
}
return "";
}
void ResourceParameters::resetPathParameters() {
_pathParams.clear();
}
void ResourceParameters::setPathParameter(size_t idx, std::string const &val) {
if(idx>=_pathParams.size()) {
_pathParams.resize(idx + 1);
}
_pathParams.at(idx) = val;
}
} /* namespace httpsserver */

View File

@@ -0,0 +1,59 @@
#ifndef SRC_RESOURCEPARAMETERS_HPP_
#define SRC_RESOURCEPARAMETERS_HPP_
#include <Arduino.h>
#include <string>
// Arduino declares it's own min max, incompatible with the stl...
#undef min
#undef max
#include <vector>
#include <utility>
#include "util.hpp"
namespace httpsserver {
class ResourceResolver;
/**
* @brief The ResourceParameters provide access to the parameters passed in the URI.
*
* There are two types of parameters: Path parameters and query parameters.
*
* Path parameters are the values that fill the asterisk placeholders in the route
* definition of a ResourceNode.
*
* Query parameters are the key-value pairs after a question mark which can be added
* to each request, either by specifying them manually or as result of submitting an
* HTML form with a GET as method property.
*/
class ResourceParameters {
public:
ResourceParameters();
virtual ~ResourceParameters();
bool isQueryParameterSet(std::string const &name);
bool getQueryParameter(std::string const &name, std::string &value);
std::vector<std::pair<std::string,std::string>>::iterator beginQueryParameters();
std::vector<std::pair<std::string,std::string>>::iterator endQueryParameters();
size_t getQueryParameterCount(bool unique=false);
bool getPathParameter(size_t const idx, std::string &value);
std::string getPathParameter(size_t const idx);
protected:
friend class ResourceResolver;
void setQueryParameter(std::string const &name, std::string const &value);
void resetPathParameters();
void setPathParameter(size_t idx, std::string const &val);
private:
/** Parameters in the path of the URL, the actual values for asterisk placeholders */
std::vector<std::string> _pathParams;
/** HTTP Query parameters, as key-value pairs */
std::vector<std::pair<std::string, std::string>> _queryParams;
};
} /* namespace httpsserver */
#endif /* SRC_RESOURCEPARAMETERS_HPP_ */

View File

@@ -0,0 +1,179 @@
#include "ResourceResolver.hpp"
namespace httpsserver {
ResourceResolver::ResourceResolver() {
_nodes = new std::vector<HTTPNode *>();
_defaultNode = NULL;
}
ResourceResolver::~ResourceResolver() {
delete _nodes;
}
/**
* This method will register the HTTPSNode so it is reachable and its callback gets called for a request
*/
void ResourceResolver::registerNode(HTTPNode *node) {
_nodes->push_back(node);
}
/**
* This method can be used to deactivate a HTTPSNode that has been registered previously
*/
void ResourceResolver::unregisterNode(HTTPNode *node) {
}
void ResourceResolver::resolveNode(const std::string &method, const std::string &url, ResolvedResource &resolvedResource, HTTPNodeType nodeType) {
// Reset the resource
resolvedResource.setMatchingNode(NULL);
resolvedResource.setParams(NULL);
// Memory management of this object will be performed by the ResolvedResource instance
ResourceParameters * params = new ResourceParameters();
// Split URL in resource name and request params. Request params start after an optional '?'
size_t reqparamIdx = url.find('?');
// Store this index to stop path parsing there
size_t pathEnd = reqparamIdx != std::string::npos ? reqparamIdx : url.size();
// If no '?' is contained in url, 0:npos will return the string as it is
std::string resourceName = url.substr(0, reqparamIdx);
// Set request params in params object if a '?' exists
if (reqparamIdx != std::string::npos) {
do {
// Drop the '?' or '&'
reqparamIdx += 1;
// Parameters are separated by '&'
size_t nextparamIdx = url.find('&', reqparamIdx);
// Get the "name=value" string
std::string param = nextparamIdx == std::string::npos ?
url.substr(reqparamIdx) :
url.substr(reqparamIdx, nextparamIdx - reqparamIdx);
if (param.length() > 0) {
// Find the position where the string has to be split
size_t nvSplitIdx = param.find('=');
// Use empty string if only name is set. /foo?bar&baz=1 will return "" for bar
std::string name = urlDecode(param.substr(0, nvSplitIdx));
std::string value = "";
if (nvSplitIdx != std::string::npos) {
value = urlDecode(param.substr(nvSplitIdx+1));
}
// Now we finally have name and value.
params->setQueryParameter(name, value);
}
// Update reqparamIdx
reqparamIdx = nextparamIdx;
} while(reqparamIdx != std::string::npos);
}
// Check whether a resource matches
for(std::vector<HTTPNode*>::iterator itNode = _nodes->begin(); itNode != _nodes->end(); ++itNode) {
params->resetPathParameters();
HTTPNode *node = *itNode;
if (node->_nodeType==nodeType) {
if (
// For handler functions, check the method declared with the node
(node->_nodeType==HANDLER_CALLBACK && ((ResourceNode*)node)->_method == method) ||
// For websockets, the specification says that GET is the only choice
(node->_nodeType==WEBSOCKET && method=="GET")
) {
HTTPS_LOGD("Testing route %s", node->_path.c_str());
bool match = true;
size_t paramCount = node->getPathParamCount();
// indices in input and pattern
size_t inputIdx = 0, pathIdx = 0;
HTTPS_LOGD("(INIT) inputIdx: %d, pathIdx: %d, pathEnd: %d, path: %s, url: %s",
inputIdx, pathIdx, pathEnd, node->_path.c_str(), url.c_str());
for (size_t paramIdx = 0; match && paramIdx < paramCount; paramIdx += 1) {
HTTPS_LOGD("(LOOP) inputIdx: %d, pathIdx: %d, pathEnd: %d, path: %s, url: %s",
inputIdx, pathIdx, pathEnd, node->_path.c_str(), url.c_str());
// Test static path before the parameter
size_t paramPos = node->getParamIdx(paramIdx);
size_t staticLength = paramPos - pathIdx;
match &= url.substr(inputIdx, staticLength) == node->_path.substr(pathIdx, staticLength);
inputIdx += staticLength;
pathIdx += staticLength;
// Extract parameter value
if (match) {
size_t paramEnd = url.find('/', inputIdx);
if (paramEnd == std::string::npos && inputIdx <= pathEnd) {
// Consume the remaining input (might be "" for the last param)
paramEnd = pathEnd;
}
if (paramEnd != std::string::npos) {
size_t paramLength = paramEnd - inputIdx;
params->setPathParameter(paramIdx, urlDecode(url.substr(inputIdx, paramLength)));
pathIdx += 1;
inputIdx += paramLength;
} else {
match = false;
HTTPS_LOGD("(LOOP) No match on param part");
}
} else {
HTTPS_LOGD("(LOOP) No match on static part");
}
}
HTTPS_LOGD("(STTC) inputIdx: %d, pathIdx: %d, pathEnd: %d, path: %s, url: %s",
inputIdx, pathIdx, pathEnd, node->_path.c_str(), url.c_str());
// Test static path after the parameter (up to pathEnd)
if (match) {
match = url.substr(inputIdx, pathEnd - inputIdx)==node->_path.substr(pathIdx);
}
HTTPS_LOGD("(END ) inputIdx: %d, pathIdx: %d, pathEnd: %d, path: %s, url: %s",
inputIdx, pathIdx, pathEnd, node->_path.c_str(), url.c_str());
if (match) {
resolvedResource.setMatchingNode(node);
HTTPS_LOGD("It's a match!");
break;
}
} // method check
} // node type check
} // resource node for loop
// If the resource did not match, configure the default resource
if (!resolvedResource.didMatch() && _defaultNode != NULL) {
params->resetPathParameters();
resolvedResource.setMatchingNode(_defaultNode);
}
// If resolving did work, set the params, otherwise delete them
if (resolvedResource.didMatch()) {
// The resolvedResource now takes care of memory management for the params
resolvedResource.setParams(params);
} else {
delete params;
}
}
void ResourceResolver::addMiddleware(const HTTPSMiddlewareFunction * mwFunction) {
_middleware.push_back(mwFunction);
}
void ResourceResolver::removeMiddleware(const HTTPSMiddlewareFunction * mwFunction) {
_middleware.erase(std::remove(_middleware.begin(), _middleware.end(), mwFunction), _middleware.end());
}
const std::vector<HTTPSMiddlewareFunction*> ResourceResolver::getMiddleware() {
return _middleware;
}
void ResourceResolver::setDefaultNode(HTTPNode * defaultNode) {
_defaultNode = defaultNode;
}
}

View File

@@ -0,0 +1,51 @@
#ifndef SRC_RESOURCERESOLVER_HPP_
#define SRC_RESOURCERESOLVER_HPP_
#include <string>
// Arduino declares it's own min max, incompatible with the stl...
#undef min
#undef max
#include <vector>
#include <algorithm>
#include "HTTPNode.hpp"
#include "WebsocketNode.hpp"
#include "ResourceNode.hpp"
#include "ResolvedResource.hpp"
#include "HTTPMiddlewareFunction.hpp"
namespace httpsserver {
/**
* \brief This class is used internally to resolve a string URL to the corresponding HTTPNode
*/
class ResourceResolver {
public:
ResourceResolver();
~ResourceResolver();
void registerNode(HTTPNode *node);
void unregisterNode(HTTPNode *node);
void setDefaultNode(HTTPNode *node);
void resolveNode(const std::string &method, const std::string &url, ResolvedResource &resolvedResource, HTTPNodeType nodeType);
/** Add a middleware function to the end of the middleware function chain. See HTTPSMiddlewareFunction.hpp for details. */
void addMiddleware(const HTTPSMiddlewareFunction * mwFunction);
/** Remove a specific function from the middleware function chain. */
void removeMiddleware(const HTTPSMiddlewareFunction * mwFunction);
/** Get the current middleware chain with a resource function at the end */
const std::vector<HTTPSMiddlewareFunction*> getMiddleware();
private:
// This vector holds all nodes (with callbacks) that are registered
std::vector<HTTPNode*> * _nodes;
HTTPNode * _defaultNode;
// Middleware functions, if any are registered. Will be called in order of the vector.
std::vector<const HTTPSMiddlewareFunction*> _middleware;
};
} /* namespace httpsserver */
#endif /* SRC_RESOURCERESOLVER_HPP_ */

310
src/http/SSLCert.cpp Normal file
View File

@@ -0,0 +1,310 @@
#include "SSLCert.hpp"
namespace httpsserver {
SSLCert::SSLCert(unsigned char * certData, uint16_t certLength, unsigned char * pkData, uint16_t pkLength):
_certLength(certLength),
_certData(certData),
_pkLength(pkLength),
_pkData(pkData) {
}
SSLCert::~SSLCert() {
// TODO Auto-generated destructor stub
}
uint16_t SSLCert::getCertLength() {
return _certLength;
}
uint16_t SSLCert::getPKLength() {
return _pkLength;
}
unsigned char * SSLCert::getCertData() {
return _certData;
}
unsigned char * SSLCert::getPKData() {
return _pkData;
}
void SSLCert::setPK(unsigned char * pkData, uint16_t length) {
_pkData = pkData;
_pkLength = length;
}
void SSLCert::setCert(unsigned char * certData, uint16_t length) {
_certData = certData;
_certLength = length;
}
void SSLCert::clear() {
for(uint16_t i = 0; i < _certLength; i++) _certData[i]=0;
delete _certData;
_certLength = 0;
for(uint16_t i = 0; i < _pkLength; i++) _pkData[i] = 0;
delete _pkData;
_pkLength = 0;
}
#ifndef HTTPS_DISABLE_SELFSIGNING
/**
* Function to create the key for a self-signed certificate.
*
* Writes private key as DER in certCtx
*
* Based on programs/pkey/gen_key.c
*/
static int gen_key(SSLCert &certCtx, SSLKeySize keySize) {
// Initialize the entropy source
mbedtls_entropy_context entropy;
mbedtls_entropy_init( &entropy );
// Initialize the RNG
mbedtls_ctr_drbg_context ctr_drbg;
mbedtls_ctr_drbg_init( &ctr_drbg );
int rngRes = mbedtls_ctr_drbg_seed(
&ctr_drbg, mbedtls_entropy_func, &entropy,
NULL, 0
);
if (rngRes != 0) {
mbedtls_entropy_free( &entropy );
return HTTPS_SERVER_ERROR_KEYGEN_RNG;
}
// Initialize the private key
mbedtls_pk_context key;
mbedtls_pk_init( &key );
int resPkSetup = mbedtls_pk_setup( &key, mbedtls_pk_info_from_type( MBEDTLS_PK_RSA ) );
if ( resPkSetup != 0) {
mbedtls_ctr_drbg_free( &ctr_drbg );
mbedtls_entropy_free( &entropy );
return HTTPS_SERVER_ERROR_KEYGEN_SETUP_PK;
}
// Actual key generation
int resPkGen = mbedtls_rsa_gen_key(
mbedtls_pk_rsa( key ),
mbedtls_ctr_drbg_random,
&ctr_drbg,
keySize,
65537
);
if ( resPkGen != 0) {
mbedtls_pk_free( &key );
mbedtls_ctr_drbg_free( &ctr_drbg );
mbedtls_entropy_free( &entropy );
return HTTPS_SERVER_ERROR_KEYGEN_GEN_PK;
}
// Free the entropy source and the RNG as they are no longer needed
mbedtls_ctr_drbg_free( &ctr_drbg );
mbedtls_entropy_free( &entropy );
// Allocate the space on the heap, as stack size is quite limited
unsigned char * output_buf = new unsigned char[4096];
if (output_buf == NULL) {
mbedtls_pk_free( &key );
return HTTPS_SERVER_ERROR_KEY_OUT_OF_MEM;
}
memset(output_buf, 0, 4096);
// Write the key to the temporary buffer and determine its length
int resPkWrite = mbedtls_pk_write_key_der( &key, output_buf, 4096 );
if (resPkWrite < 0) {
delete[] output_buf;
mbedtls_pk_free( &key );
return HTTPS_SERVER_ERROR_KEY_WRITE_PK;
}
size_t pkLength = resPkWrite;
unsigned char *pkOffset = output_buf + sizeof(unsigned char) * 4096 - pkLength;
// Copy the key into a new, fitting space on the heap
unsigned char * output_pk = new unsigned char[pkLength];
if (output_pk == NULL) {
delete[] output_buf;
mbedtls_pk_free( &key );
return HTTPS_SERVER_ERROR_KEY_WRITE_PK;
}
memcpy(output_pk, pkOffset, pkLength);
// Clean up the temporary buffer and clear the key context
delete[] output_buf;
mbedtls_pk_free( &key );
// Set the private key in the context
certCtx.setPK(output_pk, pkLength);
return 0;
}
/**
* Function to generate the X509 certificate data for a private key
*
* Writes certificate in certCtx
*
* Based on programs/x509/cert_write.c
*/
static int cert_write(SSLCert &certCtx, std::string dn, std::string validityFrom, std::string validityTo) {
int funcRes = 0;
int stepRes = 0;
mbedtls_entropy_context entropy;
mbedtls_ctr_drbg_context ctr_drbg;
mbedtls_pk_context key;
mbedtls_x509write_cert crt;
mbedtls_mpi serial;
unsigned char * primary_buffer;
unsigned char *certOffset;
unsigned char * output_buffer;
size_t certLength;
// Make a C-friendly version of the distinguished name
char dn_cstr[dn.length()+1];
strcpy(dn_cstr, dn.c_str());
// Initialize the entropy source
mbedtls_entropy_init( &entropy );
// Initialize the RNG
mbedtls_ctr_drbg_init( &ctr_drbg );
stepRes = mbedtls_ctr_drbg_seed( &ctr_drbg, mbedtls_entropy_func, &entropy, NULL, 0 );
if (stepRes != 0) {
funcRes = HTTPS_SERVER_ERROR_CERTGEN_RNG;
goto error_after_entropy;
}
mbedtls_pk_init( &key );
stepRes = mbedtls_pk_parse_key( &key, certCtx.getPKData(), certCtx.getPKLength(), NULL, 0 );
if (stepRes != 0) {
funcRes = HTTPS_SERVER_ERROR_CERTGEN_READKEY;
goto error_after_key;
}
// Start configuring the certificate
mbedtls_x509write_crt_init( &crt );
// Set version and hash algorithm
mbedtls_x509write_crt_set_version( &crt, MBEDTLS_X509_CRT_VERSION_3 );
mbedtls_x509write_crt_set_md_alg( &crt, MBEDTLS_MD_SHA256 );
// Set the keys (same key as we self-sign)
mbedtls_x509write_crt_set_subject_key( &crt, &key );
mbedtls_x509write_crt_set_issuer_key( &crt, &key );
// Set issuer and subject (same, as we self-sign)
stepRes = mbedtls_x509write_crt_set_subject_name( &crt, dn_cstr );
if (stepRes != 0) {
funcRes = HTTPS_SERVER_ERROR_CERTGEN_NAME;
goto error_after_cert;
}
stepRes = mbedtls_x509write_crt_set_issuer_name( &crt, dn_cstr );
if (stepRes != 0) {
funcRes = HTTPS_SERVER_ERROR_CERTGEN_NAME;
goto error_after_cert;
}
// Set the validity of the certificate. At the moment, it's fixed from 2019 to end of 2029.
stepRes = mbedtls_x509write_crt_set_validity( &crt, validityFrom.c_str(), validityTo.c_str());
if (stepRes != 0) {
funcRes = HTTPS_SERVER_ERROR_CERTGEN_VALIDITY;
goto error_after_cert;
}
// Make this a CA certificate
stepRes = mbedtls_x509write_crt_set_basic_constraints( &crt, 1, 0 );
if (stepRes != 0) {
funcRes = HTTPS_SERVER_ERROR_CERTGEN_VALIDITY;
goto error_after_cert;
}
// Initialize the serial number
mbedtls_mpi_init( &serial );
stepRes = mbedtls_mpi_read_string( &serial, 10, "1" );
if (stepRes != 0) {
funcRes = HTTPS_SERVER_ERROR_CERTGEN_SERIAL;
goto error_after_cert_serial;
}
stepRes = mbedtls_x509write_crt_set_serial( &crt, &serial );
if (stepRes != 0) {
funcRes = HTTPS_SERVER_ERROR_CERTGEN_SERIAL;
goto error_after_cert_serial;
}
// Create buffer to write the certificate
primary_buffer = new unsigned char[4096];
if (primary_buffer == NULL) {
funcRes = HTTPS_SERVER_ERROR_CERTGEN_OUT_OF_MEM;
goto error_after_cert_serial;
}
// Write the actual certificate
stepRes = mbedtls_x509write_crt_der(&crt, primary_buffer, 4096, mbedtls_ctr_drbg_random, &ctr_drbg );
if (stepRes < 0) {
funcRes = HTTPS_SERVER_ERROR_CERTGEN_WRITE;
goto error_after_primary_buffer;
}
// Create a matching buffer
certLength = stepRes;
certOffset = primary_buffer + sizeof(unsigned char) * 4096 - certLength;
// Copy the cert into a new, fitting space on the heap
output_buffer = new unsigned char[certLength];
if (output_buffer == NULL) {
funcRes = HTTPS_SERVER_ERROR_CERTGEN_OUT_OF_MEM;
goto error_after_primary_buffer;
}
memcpy(output_buffer, certOffset, certLength);
// Configure the cert in the context
certCtx.setCert(output_buffer, certLength);
// Run through the cleanup process
error_after_primary_buffer:
delete[] primary_buffer;
error_after_cert_serial:
mbedtls_mpi_free( &serial );
error_after_cert:
mbedtls_x509write_crt_free( &crt );
error_after_key:
mbedtls_pk_free(&key);
error_after_entropy:
mbedtls_ctr_drbg_free( &ctr_drbg );
mbedtls_entropy_free( &entropy );
return funcRes;
}
int createSelfSignedCert(SSLCert &certCtx, SSLKeySize keySize, std::string dn, std::string validFrom, std::string validUntil) {
// Add the private key
int keyRes = gen_key(certCtx, keySize);
if (keyRes != 0) {
// Key-generation failed, return the failure code
return keyRes;
}
// Add the self-signed certificate
int certRes = cert_write(certCtx, dn, validFrom, validUntil);
if (certRes != 0) {
// Cert writing failed, reset the pk and return failure code
certCtx.setPK(NULL, 0);
return certRes;
}
// If all went well, return 0
return 0;
}
#endif // !HTTPS_DISABLE_SELFSIGNING
} /* namespace httpsserver */

182
src/http/SSLCert.hpp Normal file
View File

@@ -0,0 +1,182 @@
#ifndef SRC_SSLCERT_HPP_
#define SRC_SSLCERT_HPP_
#include <Arduino.h>
#ifndef HTTPS_DISABLE_SELFSIGNING
#include <string>
#include <mbedtls/rsa.h>
#include <mbedtls/entropy.h>
#include <mbedtls/ctr_drbg.h>
#include <mbedtls/pk.h>
#include <mbedtls/x509.h>
#include <mbedtls/x509_crt.h>
#include <mbedtls/x509_csr.h>
#define HTTPS_SERVER_ERROR_KEYGEN 0x0F
#define HTTPS_SERVER_ERROR_KEYGEN_RNG 0x02
#define HTTPS_SERVER_ERROR_KEYGEN_SETUP_PK 0x03
#define HTTPS_SERVER_ERROR_KEYGEN_GEN_PK 0x04
#define HTTPS_SERVER_ERROR_KEY_WRITE_PK 0x05
#define HTTPS_SERVER_ERROR_KEY_OUT_OF_MEM 0x06
#define HTTPS_SERVER_ERROR_CERTGEN 0x1F
#define HTTPS_SERVER_ERROR_CERTGEN_RNG 0x12
#define HTTPS_SERVER_ERROR_CERTGEN_READKEY 0x13
#define HTTPS_SERVER_ERROR_CERTGEN_WRITE 0x15
#define HTTPS_SERVER_ERROR_CERTGEN_OUT_OF_MEM 0x16
#define HTTPS_SERVER_ERROR_CERTGEN_NAME 0x17
#define HTTPS_SERVER_ERROR_CERTGEN_SERIAL 0x18
#define HTTPS_SERVER_ERROR_CERTGEN_VALIDITY 0x19
#endif // !HTTPS_DISABLE_SELFSIGNING
namespace httpsserver {
/**
* \brief Certificate and private key that can be passed to the HTTPSServer.
*
* **Converting PEM to DER Files**
*
* Certificate:
* ```bash
* openssl x509 -inform PEM -outform DER -in myCert.crt -out cert.der
* ```
*
* Private Key:
* ```bash
* openssl rsa -inform PEM -outform DER -in myCert.key -out key.der
* ```
*
* **Converting DER File to C Header**
*
* ```bash
* echo "#ifndef KEY_H_" > ./key.h
* echo "#define KEY_H_" >> ./key.h
* xxd -i key.der >> ./key.h
* echo "#endif" >> ./key.h
* ```
*/
class SSLCert {
public:
/**
* \brief Creates a new SSLCert.
*
* The certificate and key data may be NULL (default values) if the certificate is meant
* to be passed to createSelfSignedCert().
*
* Otherwise, the data must reside in a memory location that is not deleted until the server
* using the certificate is stopped.
*
* \param[in] certData The certificate data to use (DER format)
* \param[in] certLength The length of the certificate data
* \param[in] pkData The private key data to use (DER format)
* \param[in] pkLength The length of the private key
*/
SSLCert(
unsigned char * certData = NULL,
uint16_t certLength = 0,
unsigned char * pkData = NULL,
uint16_t pkLength = 0
);
virtual ~SSLCert();
/**
* \brief Returns the length of the certificate in byte
*/
uint16_t getCertLength();
/**
* \brief Returns the length of the private key in byte
*/
uint16_t getPKLength();
/**
* \brief Returns the certificate data
*/
unsigned char * getCertData();
/**
* \brief Returns the private key data
*/
unsigned char * getPKData();
/**
* \brief Sets the private key in DER format
*
* The data has to reside in a place in memory that is not deleted as long as the
* server is running.
*
* See SSLCert() for some information on how to generate DER data.
*
* \param[in] _pkData The data of the private key
* \param[in] length The length of the private key
*/
void setPK(unsigned char * _pkData, uint16_t length);
/**
* \brief Sets the certificate data in DER format
*
* The data has to reside in a place in memory that is not deleted as long as the
* server is running.
*
* See SSLCert for some information on how to generate DER data.
*
* \param[in] _certData The data of the certificate
* \param[in] length The length of the certificate
*/
void setCert(unsigned char * _certData, uint16_t length);
/**
* \brief Clears the key buffers and deletes them.
*/
void clear();
private:
uint16_t _certLength;
unsigned char * _certData;
uint16_t _pkLength;
unsigned char * _pkData;
};
#ifndef HTTPS_DISABLE_SELFSIGNING
/**
* \brief Defines the key size for key generation
*
* Not available if the `HTTPS_DISABLE_SELFSIGNING` compiler flag is set
*/
enum SSLKeySize {
/** \brief RSA key with 1024 bit */
KEYSIZE_1024 = 1024,
/** \brief RSA key with 2048 bit */
KEYSIZE_2048 = 2048,
/** \brief RSA key with 4096 bit */
KEYSIZE_4096 = 4096
};
/**
* \brief Creates a self-signed certificate on the ESP32
*
* This function creates a new self-signed certificate for the given hostname on the heap.
* Make sure to clear() it before you delete it.
*
* The distinguished name (dn) parameter has to follow the x509 specifications. An example
* would be:
* CN=myesp.local,O=acme,C=US
*
* The strings validFrom and validUntil have to be formatted like this:
* "20190101000000", "20300101000000"
*
* This will take some time, so you should probably write the certificate data to non-volatile
* storage when you are done.
*
* Setting the `HTTPS_DISABLE_SELFSIGNING` compiler flag will remove this function from the library
*/
int createSelfSignedCert(SSLCert &certCtx, SSLKeySize keySize, std::string dn, std::string validFrom = "20190101000000", std::string validUntil = "20300101000000");
#endif // !HTTPS_DISABLE_SELFSIGNING
} /* namespace httpsserver */
#endif /* SRC_SSLCERT_HPP_ */

View File

@@ -0,0 +1,14 @@
#include "ValidatorFunctions.hpp"
namespace httpsserver {
bool validateNotEmpty(std::string s) {
return s!="";
}
bool validateUnsignedInteger(std::string s) {
for(size_t x = 0; x < s.size(); x++) {
if (s[x]<'0' || s[x]>'9') return false;
}
return true;
}
}

View File

@@ -0,0 +1,37 @@
#ifndef SRC_VALIDATORFUNCTIONS_HPP_
#define SRC_VALIDATORFUNCTIONS_HPP_
#include <Arduino.h>
#include <string>
#undef max
#undef min
#include <functional>
#include <memory>
#include "HTTPValidator.hpp"
#include "util.hpp"
/**
* This file contains some validator functions that can be used to validate URL parameters.
*
* They covor common cases like checking for integer, non-empty, ..., so the user of this library
* does not need to write them on his own.
*/
namespace httpsserver {
/**
* \brief **Built-in validator function**: Checks that a string is not empty.
*/
bool validateNotEmpty(std::string s);
/**
* \brief **Built-in validator function**: Checks that a value is a positive int
*
* Checks that the value is a positive integer (combine it with newValidateUnsignedIntegerMax if
* you have constraints regarding the size of that number)
*/
bool validateUnsignedInteger(std::string s);
}
#endif

View File

@@ -0,0 +1,254 @@
#include "WebsocketHandler.hpp"
namespace httpsserver {
/**
* @brief Dump the content of the WebSocket frame for debugging.
* @param [in] frame The frame to dump.
*/
static void dumpFrame(WebsocketFrame frame) {
std::string opcode = std::string("Unknown");
switch(frame.opCode) {
case WebsocketHandler::OPCODE_BINARY: opcode = std::string("BINARY"); break;
case WebsocketHandler::OPCODE_CONTINUE: opcode = std::string("CONTINUE"); break;
case WebsocketHandler::OPCODE_CLOSE: opcode = std::string("CLOSE"); break;
case WebsocketHandler::OPCODE_PING: opcode = std::string("PING"); break;
case WebsocketHandler::OPCODE_PONG: opcode = std::string("PONG"); break;
case WebsocketHandler::OPCODE_TEXT: opcode = std::string("TEXT"); break;
}
ESP_LOGI(
TAG,
"Fin: %d, OpCode: %d (%s), Mask: %d, Len: %d",
(int)frame.fin,
(int)frame.opCode,
opcode.c_str(),
(int)frame.mask,
(int)frame.len
);
}
WebsocketHandler::WebsocketHandler() {
_con = nullptr;
_receivedClose = false;
_sentClose = false;
}
WebsocketHandler::~WebsocketHandler() {
} // ~WebSocketHandler()
/**
* @brief The default onClose handler.
* If no over-riding handler is provided for the "close" event, this method is called.
*/
void WebsocketHandler::onClose() {
HTTPS_LOGD("WebsocketHandler close()");
}
/**
* @brief The default onData handler.
* If no over-riding handler is provided for the "message" event, this method is called.
* A particularly useful pattern for using onMessage is:
* ```
* std::stringstream buffer;
* buffer << pWebSocketInputRecordStreambuf;
* ```
* This will read the whole message into the string stream.
*/
void WebsocketHandler::onMessage(WebsocketInputStreambuf* pWebsocketInputStreambuf) { //, Websocket *pWebSocket) {
HTTPS_LOGD("WebsocketHandler onMessage()");
}
/**
* @brief The default onError handler.
* If no over-riding handler is provided for the "error" event, this method is called.
*/
void WebsocketHandler::onError(std::string error) {
HTTPS_LOGD("WebsocketHandler onError()");
}
void WebsocketHandler::initialize(ConnectionContext * con) {
_con = con;
}
void WebsocketHandler::loop() {
if(read() < 0) {
close();
}
}
int WebsocketHandler::read() {
WebsocketFrame frame;
int length = _con->readBuffer((uint8_t*)&frame, sizeof(frame));
HTTPS_LOGD("Websocket: Read %d bytes", length);
if(length == 0)
return 0;
else if (length != sizeof(frame)) {
HTTPS_LOGE("Websocket read error");
//_con->closeConnection();
return -1;
}
dumpFrame(frame);
// The following section parses the WebSocket frame.
uint32_t payloadLen = 0;
uint8_t mask[4];
if (frame.len < 126) {
payloadLen = frame.len;
} else if (frame.len == 126) {
uint16_t tempLen;
_con->readBuffer((uint8_t*)&tempLen, sizeof(tempLen));
payloadLen = ntohs(tempLen);
} else if (frame.len == 127) {
uint64_t tempLen;
_con->readBuffer((uint8_t*)&tempLen, sizeof(tempLen));
payloadLen = ntohl((uint32_t)tempLen);
}
if (frame.mask == 1) {
_con->readBuffer(mask, sizeof(mask));
}
if (payloadLen == 0) {
HTTPS_LOGW("WS payload not present");
} else {
HTTPS_LOGI("WS payload: length=%d", payloadLen);
}
switch(frame.opCode) {
case OPCODE_TEXT:
case OPCODE_BINARY: {
HTTPS_LOGD("Creating Streambuf");
WebsocketInputStreambuf streambuf(_con, payloadLen, frame.mask==1?mask:nullptr);
HTTPS_LOGD("Calling onMessage");
onMessage(&streambuf);
HTTPS_LOGD("Discarding Streambuf");
streambuf.discard();
break;
}
case OPCODE_CLOSE: { // If the WebSocket operation code is close then we are closing the connection.
_receivedClose = true;
onClose();
//close(); // Close the websocket.
return -1;
break;
}
case OPCODE_CONTINUE: {
break;
}
case OPCODE_PING: {
break;
}
case OPCODE_PONG: {
break;
}
default: {
HTTPS_LOGW("WebSocketReader: Unknown opcode: %d", frame.opCode);
break;
}
} // Switch opCode
return 0;
} // Websocket::read
/**
* @brief Close the Web socket
* @param [in] status The code passed in the close request.
* @param [in] message A clarification message on the close request.
*/
void WebsocketHandler::close(uint16_t status, std::string message) {
HTTPS_LOGD("Websocket close()");
_sentClose = true; // Flag that we have sent a close request.
WebsocketFrame frame; // Build the web socket frame indicating a close request.
frame.fin = 1;
frame.rsv1 = 0;
frame.rsv2 = 0;
frame.rsv3 = 0;
frame.opCode = OPCODE_CLOSE;
frame.mask = 0;
frame.len = message.length() + 2;
int rc = _con->writeBuffer((uint8_t *)&frame, sizeof(frame));
if (rc > 0) {
rc = _con->writeBuffer((byte *) &status, 2);
}
if (rc > 0) {
_con->writeBuffer((byte *) message.data(), message.length());
}
} // Websocket::close
/**
* @brief Send data down the web socket
* See the WebSocket spec (RFC6455) section "6.1 Sending Data".
* We build a WebSocket frame, send the frame followed by the data.
* @param [in] data The data to send down the WebSocket.
* @param [in] sendType The type of payload. Either SEND_TYPE_TEXT or SEND_TYPE_BINARY.
*/
void WebsocketHandler::send(std::string data, uint8_t sendType) {
HTTPS_LOGD(">> Websocket.send(): length=%d", data.length());
WebsocketFrame frame;
frame.fin = 1;
frame.rsv1 = 0;
frame.rsv2 = 0;
frame.rsv3 = 0;
frame.opCode = sendType==SEND_TYPE_TEXT?OPCODE_TEXT:OPCODE_BINARY;
frame.mask = 0;
if (data.length() < 126) {
frame.len = data.length();
_con->writeBuffer((uint8_t *)&frame, sizeof(frame));
} else {
frame.len = 126;
_con->writeBuffer((uint8_t *)&frame, sizeof(frame));
uint16_t net_len = htons((uint16_t)data.length());
_con->writeBuffer((uint8_t *)&net_len, sizeof(uint16_t)); // Convert to network byte order from host byte order
}
_con->writeBuffer((uint8_t*)data.data(), data.length());
HTTPS_LOGD("<< Websocket.send()");
} // Websocket::send
/**
* @brief Send data down the web socket
* See the WebSocket spec (RFC6455) section "6.1 Sending Data".
* We build a WebSocket frame, send the frame followed by the data.
* @param [in] data The data to send down the WebSocket.
* @param [in] sendType The type of payload. Either SEND_TYPE_TEXT or SEND_TYPE_BINARY.
*/
void WebsocketHandler::send(uint8_t* data, uint16_t length, uint8_t sendType) {
HTTPS_LOGD(">> Websocket.send(): length=%d", length);
WebsocketFrame frame;
frame.fin = 1;
frame.rsv1 = 0;
frame.rsv2 = 0;
frame.rsv3 = 0;
frame.opCode = sendType==SEND_TYPE_TEXT?OPCODE_TEXT:OPCODE_BINARY;
frame.mask = 0;
if (length < 126) {
frame.len = length;
_con->writeBuffer((uint8_t *)&frame, sizeof(frame));
} else {
frame.len = 126;
_con->writeBuffer((uint8_t *)&frame, sizeof(frame));
uint16_t net_len = htons(length);
_con->writeBuffer((uint8_t *)&net_len, sizeof(uint16_t)); // Convert to network byte order from host byte order
}
_con->writeBuffer(data, length);
HTTPS_LOGD("<< Websocket.send()");
} // Websocket::send
/**
* Returns true if the connection has been closed, either by client or server
*/
bool WebsocketHandler::closed() {
return _receivedClose || _sentClose;
}
}

View File

@@ -0,0 +1,87 @@
#ifndef SRC_WEBSOCKETHANDLER_HPP_
#define SRC_WEBSOCKETHANDLER_HPP_
#include <Arduino.h>
#include <lwip/def.h>
#include <string>
#undef min
#undef max
#include <sstream>
#include "HTTPSServerConstants.hpp"
#include "ConnectionContext.hpp"
#include "WebsocketInputStreambuf.hpp"
namespace httpsserver {
// Structure definition for the WebSocket frame.
struct WebsocketFrame
{
// Byte 0
uint8_t opCode : 4; // [7:4]
uint8_t rsv3 : 1; // [3]
uint8_t rsv2 : 1; // [2]
uint8_t rsv1 : 1; // [1]
uint8_t fin : 1; // [0]
// Byte 1
uint8_t len : 7; // [7:1]
uint8_t mask : 1; // [0]
};
class WebsocketHandler
{
public:
// WebSocket op codes as found in a WebSocket frame.
static const int OPCODE_CONTINUE = 0x00;
static const int OPCODE_TEXT = 0x01;
static const int OPCODE_BINARY = 0x02;
static const int OPCODE_CLOSE = 0x08;
static const int OPCODE_PING = 0x09;
static const int OPCODE_PONG = 0x0a;
static const uint16_t CLOSE_NORMAL_CLOSURE = 1000;
static const uint16_t CLOSE_GOING_AWAY = 1001;
static const uint16_t CLOSE_PROTOCOL_ERROR = 1002;
static const uint16_t CLOSE_CANNOT_ACCEPT = 1003;
static const uint16_t CLOSE_NO_STATUS_CODE = 1005;
static const uint16_t CLOSE_CLOSED_ABNORMALLY = 1006;
static const uint16_t CLOSE_NOT_CONSISTENT = 1007;
static const uint16_t CLOSE_VIOLATED_POLICY = 1008;
static const uint16_t CLOSE_TOO_BIG = 1009;
static const uint16_t CLOSE_NO_EXTENSION = 1010;
static const uint16_t CLOSE_UNEXPECTED_CONDITION = 1011;
static const uint16_t CLOSE_SERVICE_RESTART = 1012;
static const uint16_t CLOSE_TRY_AGAIN_LATER = 1013;
static const uint16_t CLOSE_TLS_HANDSHAKE_FAILURE = 1015;
static const uint8_t SEND_TYPE_BINARY = 0x01;
static const uint8_t SEND_TYPE_TEXT = 0x02;
WebsocketHandler();
virtual ~WebsocketHandler();
virtual void onClose();
virtual void onMessage(WebsocketInputStreambuf *pWebsocketInputStreambuf);
virtual void onError(std::string error);
void close(uint16_t status = CLOSE_NORMAL_CLOSURE, std::string message = "");
void send(std::string data, uint8_t sendType = SEND_TYPE_BINARY);
void send(uint8_t *data, uint16_t length, uint8_t sendType = SEND_TYPE_BINARY);
bool closed();
void loop();
void initialize(ConnectionContext * con);
private:
int read();
ConnectionContext * _con;
bool _receivedClose; // True when we have received a close request.
bool _sentClose; // True when we have sent a close request.
};
}
#endif

View File

@@ -0,0 +1,110 @@
#include "WebsocketInputStreambuf.hpp"
namespace httpsserver {
/**
* @brief Create a Web Socket input record streambuf
* @param [in] socket The socket we will be reading from.
* @param [in] dataLength The size of a record.
* @param [in] bufferSize The size of the buffer we wish to allocate to hold data.
*/
WebsocketInputStreambuf::WebsocketInputStreambuf(
ConnectionContext *con,
size_t dataLength,
uint8_t *pMask,
size_t bufferSize
) {
_con = con; // The socket we will be reading from
_dataLength = dataLength; // The size of the record we wish to read.
_pMask = pMask;
_bufferSize = bufferSize; // The size of the buffer used to hold data
_sizeRead = 0; // The size of data read from the socket
_buffer = new char[bufferSize]; // Create the buffer used to hold the data read from the socket.
setg(_buffer, _buffer, _buffer); // Set the initial get buffer pointers to no data.
}
WebsocketInputStreambuf::~WebsocketInputStreambuf() {
//FIXME: Call order incorrect? discard() uses _buffer
delete[] _buffer;
discard();
}
/**
* @brief Discard data for the record that has not yet been read.
*
* We are working on a logical fixed length record in a socket stream. This means that we know in advance
* how big the record should be. If we have read some data from the stream and no longer wish to consume
* any further, we have to discard the remaining bytes in the stream before we can get to process the
* next record. This function discards the remainder of the data.
*
* For example, if our record size is 1000 bytes and we have read 700 bytes and determine that we no
* longer need to continue, we can't just stop. There are still 300 bytes in the socket stream that
* need to be consumed/discarded before we can move on to the next record.
*/
void WebsocketInputStreambuf::discard() {
uint8_t byte;
HTTPS_LOGD(">> WebsocketContext.discard(): %d bytes", _dataLength - _sizeRead);
while(_sizeRead < _dataLength) {
_con->readBuffer(&byte, 1);
_sizeRead++;
}
HTTPS_LOGD("<< WebsocketContext.discard()");
} // WebsocketInputStreambuf::discard
/**
* @brief Get the size of the expected record.
* @return The size of the expected record.
*/
size_t WebsocketInputStreambuf::getRecordSize() {
return _dataLength;
} // WebsocketInputStreambuf::getRecordSize
/**
* @brief Handle the request to read data from the stream but we need more data from the source.
*
*/
WebsocketInputStreambuf::int_type WebsocketInputStreambuf::underflow() {
HTTPS_LOGD(">> WebSocketInputStreambuf.underflow()");
// If we have already read as many bytes as our record definition says we should read
// then don't attempt to ready any further.
if (_sizeRead >= getRecordSize()) {
HTTPS_LOGD("<< WebSocketInputStreambuf.underflow(): Already read maximum");
return EOF;
}
// We wish to refill the buffer. We want to read data from the socket. We want to read either
// the size of the buffer to fill it or the maximum number of bytes remaining to be read.
// We will choose which ever is smaller as the number of bytes to read into the buffer.
int remainingBytes = getRecordSize()-_sizeRead;
size_t sizeToRead;
if (remainingBytes < _bufferSize) {
sizeToRead = remainingBytes;
} else {
sizeToRead = _bufferSize;
}
HTTPS_LOGD("WebSocketInputRecordStreambuf - getting next buffer of data; size request: %d", sizeToRead);
int bytesRead = _con->readBuffer((uint8_t*)_buffer, sizeToRead);
if (bytesRead == 0) {
HTTPS_LOGD("<< WebSocketInputRecordStreambuf.underflow(): Read 0 bytes");
return EOF;
}
// If the WebSocket frame shows that we have a mask bit set then we have to unmask the data.
if (_pMask != nullptr) {
for (int i=0; i<bytesRead; i++) {
_buffer[i] = _buffer[i] ^ _pMask[(_sizeRead+i)%4];
}
}
_sizeRead += bytesRead; // Increase the count of number of bytes actually read from the source.
setg(_buffer, _buffer, _buffer + bytesRead); // Change the buffer pointers to reflect the new data read.
HTTPS_LOGD("<< WebSocketInputRecordStreambuf.underflow(): got %d bytes", bytesRead);
return traits_type::to_int_type(*gptr());
} // underflow
}

View File

@@ -0,0 +1,47 @@
#ifndef SRC_WEBSOCKETINPUTSTREAMBUF_HPP_
#define SRC_WEBSOCKETINPUTSTREAMBUF_HPP_
#include <Arduino.h>
#include <lwip/def.h>
#include <string>
#undef min
#undef max
#include <iostream>
#include <streambuf>
#include <sstream>
#include <iostream>
#include "HTTPSServerConstants.hpp"
#include "ConnectionContext.hpp"
namespace httpsserver {
class WebsocketInputStreambuf : public std::streambuf {
public:
WebsocketInputStreambuf(
ConnectionContext *con,
size_t dataLength,
uint8_t *_ = nullptr,
size_t bufferSize = 2048
);
virtual ~WebsocketInputStreambuf();
int_type underflow();
void discard();
size_t getRecordSize();
private:
char *_buffer;
ConnectionContext *_con;
size_t _dataLength;
size_t _bufferSize;
size_t _sizeRead;
uint8_t *_pMask;
};
} // namespace
#endif

View File

@@ -0,0 +1,20 @@
#include "WebsocketNode.hpp"
namespace httpsserver {
WebsocketNode::WebsocketNode(const std::string &path, const WebsocketHandlerCreator * creatorFunction, const std::string &tag):
HTTPNode(path, WEBSOCKET, tag),
_creatorFunction(creatorFunction) {
}
WebsocketNode::~WebsocketNode() {
}
WebsocketHandler* WebsocketNode::newHandler() {
WebsocketHandler * handler = _creatorFunction();
return handler;
}
} /* namespace httpsserver */

View File

@@ -0,0 +1,25 @@
#ifndef SRC_WEBSOCKETNODE_HPP_
#define SRC_WEBSOCKETNODE_HPP_
#include <string>
#include "HTTPNode.hpp"
#include "WebsocketHandler.hpp"
namespace httpsserver {
typedef WebsocketHandler* (WebsocketHandlerCreator)();
class WebsocketNode : public HTTPNode {
public:
WebsocketNode(const std::string &path, const WebsocketHandlerCreator creatorFunction, const std::string &tag = "");
virtual ~WebsocketNode();
WebsocketHandler* newHandler();
std::string getMethod() { return std::string("GET"); }
private:
const WebsocketHandlerCreator * _creatorFunction;
};
} /* namespace httpsserver */
#endif /* SRC_WEBSOCKET_HPP_ */

98
src/http/util.cpp Normal file
View File

@@ -0,0 +1,98 @@
#include "util.hpp"
namespace httpsserver {
uint32_t parseUInt(std::string const &s, uint32_t max) {
uint32_t i = 0; // value
// Check sign
size_t x = 0;
if (s[0]=='+') {
x = 1;
}
// We device max by 10, so we can check if we would exceed it by the next *10 multiplication
max/=10;
// Convert by base 10
for(; x < s.size(); x++) {
char c = s[x];
if (i < max) {
if (c >= '0' && c<='9') {
i = i*10 + (c-'0');
} else {
break;
}
} else {
return max;
}
}
return i;
}
int32_t parseInt(std::string const &s) {
uint32_t max = 0x7fffffff;
if (s[0]=='-') {
return -1 * parseUInt(s.substr(1,max));
}
return parseUInt(s,max);
}
std::string intToString(int i) {
if (i==0) {
return "0";
}
// We need this much digits
int digits = ceil(log10(i+1));
char c[digits+1];
c[digits] = '\0';
for(int x = digits-1; x >= 0; x--) {
char v = (i%10);
c[x] = '0' + v;
i = (i-v)/10;
}
return std::string(c);
}
}
std::string urlDecode(std::string input) {
std::size_t idxReplaced = 0;
// First replace + by space
std::size_t idxFound = input.find('+');
while (idxFound != std::string::npos) {
input.replace(idxFound, 1, 1, ' ');
idxFound = input.find('+', idxFound + 1);
}
// Now replace percent-escapes
idxFound = input.find('%');
while (idxFound != std::string::npos) {
if (idxFound <= input.length() + 3) {
char hex[2] = { input[idxFound+1], input[idxFound+2] };
byte val = 0;
for(int n = 0; n < sizeof(hex); n++) {
val <<= 4;
if ('0' <= hex[n] && hex[n] <= '9') {
val += hex[n]-'0';
}
else if ('A' <= hex[n] && hex[n] <= 'F') {
val += hex[n]-'A'+10;
}
else if ('a' <= hex[n] && hex[n] <= 'f') {
val += hex[n]-'a'+10;
}
else {
goto skipChar;
}
}
input.replace(idxFound, 3, 1, (char)val);
}
skipChar:
idxReplaced = idxFound + 1;
idxFound = input.find('%', idxReplaced);
}
return input;
}

35
src/http/util.hpp Normal file
View File

@@ -0,0 +1,35 @@
#ifndef SRC_UTIL_HPP_
#define SRC_UTIL_HPP_
#include <Arduino.h>
#include <cmath>
#include <string>
namespace httpsserver {
/**
* \brief **Utility function**: Parse an unsigned integer from a string
*
* The second parameter can be used to define the maximum value that is acceptable
*/
uint32_t parseUInt(std::string const &s, uint32_t max = 0xffffffff);
/**
* \brief **Utility function**: Parse a signed integer from a string
*/
int32_t parseInt(std::string const &s);
/**
* \brief **Utility function**: Transform an int to a std::string
*/
std::string intToString(int i);
}
/**
* \brief **Utility function**: Removes URL encoding from the string (e.g. %20 -> space)
*/
std::string urlDecode(std::string input);
#endif /* SRC_UTIL_HPP_ */