[ Web Proxy ]
URL:
Viewing: https://raw.githubusercontent.com/Lackmann1994/esp32_https_server/master/src/HTTPConnection.cpp [Back]  [Original]

#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::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 next = std::function(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(std::bind((*itMw), &req, &res, next));
            itMw++;
          }

          // We insert the internal validation middleware at the start of the chain:
          next = std::function(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 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 * validators = node->getValidators();
  for(std::vector::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 */

Web Proxy Viewer  |  New URL  |  Original Page