ESP32_ChinaDieselHeater_Con.../lib/esp32_https_server-master/src/HTTPConnection.cpp

692 lines
23 KiB
C++

#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 */