Webserver+3d print
cyclone_tcp/web_socket/web_socket_misc.c
- Committer:
- Sergunb
- Date:
- 2017-02-04
- Revision:
- 0:8918a71cdbe9
File content as of revision 0:8918a71cdbe9:
/** * @file web_socket_misc.c * @brief Helper functions for WebSockets * * @section License * * Copyright (C) 2010-2017 Oryx Embedded SARL. All rights reserved. * * This file is part of CycloneTCP Open. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * @author Oryx Embedded SARL (www.oryx-embedded.com) * @version 1.7.6 **/ //Switch to the appropriate trace level #define TRACE_LEVEL WEB_SOCKET_TRACE_LEVEL //Dependencies #include <stdlib.h> #include "core/net.h" #include "web_socket/web_socket.h" #include "web_socket/web_socket_auth.h" #include "web_socket/web_socket_frame.h" #include "web_socket/web_socket_transport.h" #include "web_socket/web_socket_misc.h" #include "base64.h" #include "sha1.h" #include "str.h" #include "debug.h" //Check TCP/IP stack configuration #if (WEB_SOCKET_SUPPORT == ENABLED) //WebSocket GUID const char_t webSocketGuid[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; /** * @brief HTTP status codes **/ static const WebSocketStatusCodeDesc statusCodeList[] = { //Success {200, "OK"}, {201, "Created"}, {202, "Accepted"}, {204, "No Content"}, //Redirection {301, "Moved Permanently"}, {302, "Found"}, {304, "Not Modified"}, //Client error {400, "Bad Request"}, {401, "Unauthorized"}, {403, "Forbidden"}, {404, "Not Found"}, //Server error {500, "Internal Server Error"}, {501, "Not Implemented"}, {502, "Bad Gateway"}, {503, "Service Unavailable"} }; /** * @brief Update WebSocket state * @param[in] webSocket Handle to a WebSocket * @param[in] newState New state to switch to **/ void webSocketChangeState(WebSocket *webSocket, WebSocketState newState) { //Switch to the new state webSocket->state = newState; //Save current time; webSocket->timestamp = osGetSystemTime(); //Reset sub-state webSocket->txContext.state = WS_SUB_STATE_INIT; webSocket->rxContext.state = WS_SUB_STATE_INIT; } /** * @brief Parse client or server handshake * @param[in] webSocket Handle to a WebSocket * @return Error code **/ error_t webSocketParseHandshake(WebSocket *webSocket) { error_t error; size_t n; WebSocketFrameContext *rxContext; //Point to the RX context rxContext = &webSocket->rxContext; //Initialize status code error = NO_ERROR; //Wait for the handshake to complete while(1) { //Client or server operation? if(webSocket->endpoint == WS_ENDPOINT_CLIENT) { if(webSocket->state == WS_STATE_OPEN) break; } else { if(webSocket->state == WS_STATE_SERVER_HANDSHAKE) break; } //Check current sub-state if(rxContext->state == WS_SUB_STATE_INIT) { //Initialize status code webSocket->statusCode = WS_STATUS_CODE_NO_STATUS_RCVD; //Initialize FIN flag rxContext->fin = TRUE; //Flush the receive buffer rxContext->bufferPos = 0; rxContext->bufferLen = 0; //Initialize variables webSocket->handshakeContext.statusCode = 0; webSocket->handshakeContext.upgradeWebSocket = FALSE; webSocket->handshakeContext.connectionUpgrade = FALSE; webSocket->handshakeContext.connectionClose = FALSE; webSocket->handshakeContext.contentLength = 0; webSocket->handshakeContext.closingFrameSent = FALSE; webSocket->handshakeContext.closingFrameReceived = FALSE; #if (WEB_SOCKET_BASIC_AUTH_SUPPORT == ENABLED || WEB_SOCKET_DIGEST_AUTH_SUPPORT == ENABLED) webSocket->authContext.requiredAuthMode = WS_AUTH_MODE_NONE; #endif #if (WEB_SOCKET_DIGEST_AUTH_SUPPORT == ENABLED) strcpy(webSocket->authContext.nonce, ""); strcpy(webSocket->authContext.opaque, ""); webSocket->authContext.stale = FALSE; #endif //Client or server operation? if(webSocket->endpoint == WS_ENDPOINT_CLIENT) { //Clear server key strcpy(webSocket->handshakeContext.serverKey, ""); //Debug message TRACE_DEBUG("WebSocket: server handshake\r\n"); } else { //Clear client key strcpy(webSocket->handshakeContext.clientKey, ""); //Debug message TRACE_DEBUG("WebSocket: client handshake\r\n"); } //Decode the leading line rxContext->state = WS_SUB_STATE_HANDSHAKE_LEADING_LINE; } else if(rxContext->state == WS_SUB_STATE_HANDSHAKE_LEADING_LINE) { //Check whether more data is required if(rxContext->bufferLen < 2) { //Limit the number of characters to read at a time n = WEB_SOCKET_BUFFER_SIZE - 1 - rxContext->bufferLen; //Read data until a CLRF character is encountered error = webSocketReceiveData(webSocket, rxContext->buffer + rxContext->bufferLen, n, &n, SOCKET_FLAG_BREAK_CRLF); //Update the length of the buffer rxContext->bufferLen += n; } else if(rxContext->bufferLen >= (WEB_SOCKET_BUFFER_SIZE - 1)) { //Report an error error = ERROR_INVALID_REQUEST; } else if(strncmp((char_t *) rxContext->buffer + rxContext->bufferLen - 2, "\r\n", 2)) { //Limit the number of characters to read at a time n = WEB_SOCKET_BUFFER_SIZE - 1 - rxContext->bufferLen; //Read data until a CLRF character is encountered error = webSocketReceiveData(webSocket, rxContext->buffer + rxContext->bufferLen, n, &n, SOCKET_FLAG_BREAK_CRLF); //Update the length of the buffer rxContext->bufferLen += n; } else { //Properly terminate the string with a NULL character rxContext->buffer[rxContext->bufferLen] = '\0'; //Client or server operation? if(webSocket->endpoint == WS_ENDPOINT_CLIENT) { //The leading line from the server follows the Status-Line format error = webSocketParseStatusLine(webSocket, (char_t *) rxContext->buffer); } else { //The leading line from the client follows the Request-Line format error = webSocketParseRequestLine(webSocket, (char_t *) rxContext->buffer); } //Flush the receive buffer rxContext->bufferPos = 0; rxContext->bufferLen = 0; //Parse the header fields of the handshake rxContext->state = WS_SUB_STATE_HANDSHAKE_HEADER_FIELD; } } else if(rxContext->state == WS_SUB_STATE_HANDSHAKE_HEADER_FIELD) { //Check whether more data is required if(rxContext->bufferLen < 2) { //Limit the number of characters to read at a time n = WEB_SOCKET_BUFFER_SIZE - 1 - rxContext->bufferLen; //Read data until a CLRF character is encountered error = webSocketReceiveData(webSocket, rxContext->buffer + rxContext->bufferLen, n, &n, SOCKET_FLAG_BREAK_CRLF); //Update the length of the buffer rxContext->bufferLen += n; } else if(rxContext->bufferLen >= (WEB_SOCKET_BUFFER_SIZE - 1)) { //Report an error error = ERROR_INVALID_REQUEST; } else if(strncmp((char_t *) rxContext->buffer + rxContext->bufferLen - 2, "\r\n", 2)) { //Limit the number of characters to read at a time n = WEB_SOCKET_BUFFER_SIZE - 1 - rxContext->bufferLen; //Read data until a CLRF character is encountered error = webSocketReceiveData(webSocket, rxContext->buffer + rxContext->bufferLen, n, &n, SOCKET_FLAG_BREAK_CRLF); //Update the length of the buffer rxContext->bufferLen += n; } else { //Properly terminate the string with a NULL character rxContext->buffer[rxContext->bufferLen] = '\0'; //An empty line indicates the end of the header fields if(!strcmp((char_t *) rxContext->buffer, "\r\n")) { //Client or server operation? if(webSocket->endpoint == WS_ENDPOINT_CLIENT) { //Verify server's handshake error = webSocketVerifyServerHandshake(webSocket); } else { //Verify client's handshake error = webSocketVerifyClientHandshake(webSocket); } } else { //Read the next character to detect if the CRLF is immediately //followed by a LWSP character rxContext->state = WS_SUB_STATE_HANDSHAKE_LWSP; } } } else if(rxContext->state == WS_SUB_STATE_HANDSHAKE_LWSP) { char_t nextChar; //Read the next character error = webSocketReceiveData(webSocket, &nextChar, sizeof(char_t), &n, 0); //Successful read operation? if(!error && n == sizeof(char_t)) { //LWSP character found? if(nextChar == ' ' || nextChar == '\t') { //Unfolding is accomplished by regarding CRLF immediately //followed by a LWSP as equivalent to the LWSP character if(rxContext->bufferLen >= 2) { //Remove trailing CRLF sequence rxContext->bufferLen -= 2; } //The header field spans multiple line rxContext->state = WS_SUB_STATE_HANDSHAKE_HEADER_FIELD; } else { //Parse header field error = webSocketParseHeaderField(webSocket, (char_t *) rxContext->buffer); //Restore the very first character of the header field rxContext->buffer[0] = nextChar; //Adjust the length of the receive buffer rxContext->bufferLen = sizeof(char_t); //Decode the next header field rxContext->state = WS_SUB_STATE_HANDSHAKE_HEADER_FIELD; } } } else { //Invalid state error = ERROR_WRONG_STATE; } //Any error to report? if(error) break; } //Return status code return error; } /** * @brief Parse the Request-Line of the client's handshake * @param[in] webSocket Handle to a WebSocket * @param[in] line NULL-terminated string that contains the Request-Line * @return Error code **/ error_t webSocketParseRequestLine(WebSocket *webSocket, char_t *line) { error_t error; char_t *token; char_t *p; char_t *s; //Debug message TRACE_DEBUG("%s", line); //The Request-Line begins with a method token token = strtok_r(line, " \r\n", &p); //Unable to retrieve the method? if(token == NULL) return ERROR_INVALID_REQUEST; //The method of the request must be GET if(strcasecmp(token, "GET")) return ERROR_INVALID_REQUEST; //The Request-URI is following the method token token = strtok_r(NULL, " \r\n", &p); //Unable to retrieve the Request-URI? if(token == NULL) return ERROR_INVALID_REQUEST; //Check whether a query string is present s = strchr(token, '?'); //Query string found? if(s != NULL) { //Split the string *s = '\0'; //Save the Request-URI error = webSocketDecodePercentEncodedString(token, webSocket->uri, WEB_SOCKET_URI_MAX_LEN); //Any error to report? if(error) return ERROR_INVALID_REQUEST; //Check the length of the query string if(strlen(s + 1) > WEB_SOCKET_QUERY_STRING_MAX_LEN) return ERROR_INVALID_REQUEST; //Save the query string strcpy(webSocket->queryString, s + 1); } else { //Save the Request-URI error = webSocketDecodePercentEncodedString(token, webSocket->uri, WEB_SOCKET_URI_MAX_LEN); //Any error to report? if(error) return ERROR_INVALID_REQUEST; //No query string webSocket->queryString[0] = '\0'; } //The protocol version is following the Request-URI token = strtok_r(NULL, " \r\n", &p); //HTTP version 0.9? if(token == NULL) { //Save version number webSocket->handshakeContext.version = WS_HTTP_VERSION_0_9; //Persistent connections are not supported webSocket->handshakeContext.connectionClose = TRUE; } //HTTP version 1.0? else if(!strcasecmp(token, "HTTP/1.0")) { //Save version number webSocket->handshakeContext.version = WS_HTTP_VERSION_1_0; //By default connections are not persistent webSocket->handshakeContext.connectionClose = TRUE; } //HTTP version 1.1? else if(!strcasecmp(token, "HTTP/1.1")) { //Save version number webSocket->handshakeContext.version = WS_HTTP_VERSION_1_1; //HTTP 1.1 makes persistent connections the default webSocket->handshakeContext.connectionClose = FALSE; } //HTTP version not supported? else { //Report an error return ERROR_INVALID_REQUEST; } //Successful processing return NO_ERROR; } /** * @brief Parse the Status-Line of the server's handshake * @param[in] webSocket Handle to a WebSocket * @param[in] line NULL-terminated string that contains the Status-Line * @return Error code **/ error_t webSocketParseStatusLine(WebSocket *webSocket, char_t *line) { char_t *p; char_t *token; //Debug message TRACE_DEBUG("%s", line); //Retrieve the HTTP-Version field token = strtok_r(line, " ", &p); //Any parsing error? if(token == NULL) return ERROR_INVALID_SYNTAX; //Retrieve the Status-Code field token = strtok_r(NULL, " ", &p); //Any parsing error? if(token == NULL) return ERROR_INVALID_SYNTAX; //Convert the status code webSocket->handshakeContext.statusCode = strtoul(token, &p, 10); //Any parsing error? if(*p != '\0') return ERROR_INVALID_SYNTAX; //Successful processing return NO_ERROR; } /** * @brief Parse a header field * @param[in] webSocket Handle to a WebSocket * @param[in] line NULL-terminated string that contains the header field * @return Error code **/ error_t webSocketParseHeaderField(WebSocket *webSocket, char_t *line) { char_t *separator; char_t *name; char_t *value; WebSocketHandshakeContext *handshakeContext; //Point to the handshake context handshakeContext = &webSocket->handshakeContext; //Debug message TRACE_DEBUG("%s", line); //Check whether a separator is present separator = strchr(line, ':'); //Separator found? if(separator != NULL) { //Split the line *separator = '\0'; //Get field name and value name = strTrimWhitespace(line); value = strTrimWhitespace(separator + 1); //Upgrade header field found? if(!strcasecmp(name, "Upgrade")) { if(!strcasecmp(value, "websocket")) handshakeContext->upgradeWebSocket = TRUE; } //Connection header field found? else if(!strcasecmp(name, "Connection")) { //Parse Connection header field webSocketParseConnectionField(webSocket, value); } //Sec-WebSocket-Key header field found? else if(!strcasecmp(name, "Sec-WebSocket-Key")) { //Server operation? if(webSocket->endpoint == WS_ENDPOINT_SERVER) { //Save the contents of the Sec-WebSocket-Key header field strSafeCopy(handshakeContext->clientKey, value, WEB_SOCKET_CLIENT_KEY_SIZE + 1); } } //Sec-WebSocket-Accept header field found? else if(!strcasecmp(name, "Sec-WebSocket-Accept")) { //Client operation? if(webSocket->endpoint == WS_ENDPOINT_CLIENT) { //Save the contents of the Sec-WebSocket-Accept header field strSafeCopy(handshakeContext->serverKey, value, WEB_SOCKET_SERVER_KEY_SIZE + 1); } } #if (WEB_SOCKET_BASIC_AUTH_SUPPORT == ENABLED || WEB_SOCKET_DIGEST_AUTH_SUPPORT == ENABLED) //WWW-Authenticate header field found? else if(!strcasecmp(name, "WWW-Authenticate")) { //Parse WWW-Authenticate header field webSocketParseAuthenticateField(webSocket, value); } #endif //Content-Length header field found? else if(!strcasecmp(name, "Content-Length")) { handshakeContext->contentLength = strtoul(value, NULL, 10); } } //Successful processing return NO_ERROR; } /** * @brief Parse Connection header field * @param[in] webSocket Handle to a WebSocket * @param[in] value NULL-terminated string that contains the value of header field **/ void webSocketParseConnectionField(WebSocket *webSocket, char_t *value) { char_t *p; char_t *token; //Get the first value of the list token = strtok_r(value, ",", &p); //Parse the comma-separated list while(token != NULL) { //Trim whitespace characters value = strTrimWhitespace(token); //Check current value if(!strcasecmp(value, "keep-alive")) { //The connection is persistent webSocket->handshakeContext.connectionClose = FALSE; } else if(!strcasecmp(value, "close")) { //The connection will be closed after completion of the response webSocket->handshakeContext.connectionClose = TRUE; } else if(!strcasecmp(value, "upgrade")) { //Upgrade the connection webSocket->handshakeContext.connectionUpgrade = TRUE; } //Get next value token = strtok_r(NULL, ",", &p); } } /** * @brief Format client's handshake * @param[in] webSocket Handle to a WebSocket * @param[in] serverPort TCP port number used to establish the connection * @return Error code **/ error_t webSocketFormatClientHandshake(WebSocket *webSocket, uint16_t serverPort) { char_t *p; WebSocketFrameContext *txContext; //Point to the TX context txContext = &webSocket->txContext; //Point to the buffer where to format the client's handshake p = (char_t *) txContext->buffer; //The Request-Line begins with a method token, followed by the //Request-URI and the protocol version, and ending with CRLF p += sprintf(p, "GET %s HTTP/1.1\r\n", webSocket->uri); //Add Host header field if(webSocket->host[0] != '\0') { //The Host header field specifies the Internet host and port number of //the resource being requested p += sprintf(p, "Host: %s:%d\r\n", webSocket->host, serverPort); } else { //If the requested URI does not include a host name for the service being //requested, then the Host header field must be given with an empty value p += sprintf(p, "Host:\r\n"); } #if (WEB_SOCKET_BASIC_AUTH_SUPPORT == ENABLED || WEB_SOCKET_DIGEST_AUTH_SUPPORT == ENABLED) //Check whether authentication is required if(webSocket->authContext.selectedAuthMode != WS_AUTH_MODE_NONE) { //Add Authorization header field p += webSocketAddAuthorizationField(webSocket, p); } #endif //Add Origin header field if(webSocket->origin[0] != '\0') p += sprintf(p, "Origin: %s\r\n", webSocket->origin); else p += sprintf(p, "Origin: null\r\n"); //Add Upgrade header field p += sprintf(p, "Upgrade: websocket\r\n"); //Add Connection header field p += sprintf(p, "Connection: Upgrade\r\n"); //Add Sec-WebSocket-Protocol header field if(webSocket->subProtocol[0] != '\0') p += sprintf(p, "Sec-WebSocket-Protocol: %s\r\n", webSocket->subProtocol); //Add Sec-WebSocket-Key header field p += sprintf(p, "Sec-WebSocket-Key: %s\r\n", webSocket->handshakeContext.clientKey); //Add Sec-WebSocket-Version header field p += sprintf(p, "Sec-WebSocket-Version: 13\r\n"); //An empty line indicates the end of the header fields p += sprintf(p, "\r\n"); //Debug message TRACE_DEBUG("\r\n"); TRACE_DEBUG("WebSocket: client handshake\r\n"); TRACE_DEBUG("%s", txContext->buffer); //Rewind to the beginning of the buffer txContext->bufferPos = 0; //Update the number of data buffered but not yet sent txContext->bufferLen = strlen((char_t *) txContext->buffer); //Successful processing return NO_ERROR; } /** * @brief Format server's handshake * @param[in] webSocket Handle to a WebSocket * @return Error code **/ error_t webSocketFormatServerHandshake(WebSocket *webSocket) { char_t *p; WebSocketFrameContext *txContext; //Point to the TX context txContext = &webSocket->txContext; //Point to the buffer where to format the client's handshake p = (char_t *) txContext->buffer; //The first line is an HTTP Status-Line, with the status code 101 p += sprintf(p, "HTTP/1.1 101 Switching Protocols\r\n"); //Add Upgrade header field p += sprintf(p, "Upgrade: websocket\r\n"); //Add Connection header field p += sprintf(p, "Connection: Upgrade\r\n"); //Add Sec-WebSocket-Protocol header field if(webSocket->subProtocol[0] != '\0') p += sprintf(p, "Sec-WebSocket-Protocol: %s\r\n", webSocket->subProtocol); //Add Sec-WebSocket-Accept header field p += sprintf(p, "Sec-WebSocket-Accept: %s\r\n", webSocket->handshakeContext.serverKey); //An empty line indicates the end of the header fields p += sprintf(p, "\r\n"); //Debug message TRACE_DEBUG("\r\n"); TRACE_DEBUG("WebSocket: server handshake\r\n"); TRACE_DEBUG("%s", txContext->buffer); //Rewind to the beginning of the buffer txContext->bufferPos = 0; //Update the number of data buffered but not yet sent txContext->bufferLen = strlen((char_t *) txContext->buffer); //Successful processing return NO_ERROR; } /** * @brief Format HTTP error response * @param[in] webSocket Handle to a WebSocket * @param[in] statusCode HTTP status code * @param[in] message User message * @return Error code **/ error_t webSocketFormatErrorResponse(WebSocket *webSocket, uint_t statusCode, const char_t *message) { uint_t i; size_t length; char_t *p; WebSocketFrameContext *txContext; //HTML response template static const char_t template[] = "<!doctype html>\r\n" "<html>\r\n" "<head><title>Error %03d</title></head>\r\n" "<body>\r\n" "<h2>Error %03d</h2>\r\n" "<p>%s</p>\r\n" "</body>\r\n" "</html>\r\n"; //Point to the TX context txContext = &webSocket->txContext; //Point to the buffer where to format the client's handshake p = (char_t *) txContext->buffer; //The first line of a response message is the Status-Line, consisting //of the protocol version followed by a numeric status code and its //associated textual phrase p += sprintf(p, "HTTP/%u.%u %u ", MSB(webSocket->handshakeContext.version), LSB(webSocket->handshakeContext.version), statusCode); //Retrieve the Reason-Phrase that corresponds to the Status-Code for(i = 0; i < arraysize(statusCodeList); i++) { //Check the status code if(statusCodeList[i].value == statusCode) { //Append the textual phrase to the Status-Line p += sprintf(p, statusCodeList[i].message); //Break the loop and continue processing break; } } //Properly terminate the Status-Line p += sprintf(p, "\r\n"); //Content type p += sprintf(p, "Content-Type: %s\r\n", "text/html"); //Compute the length of the response length = strlen(template) + strlen(message) - 4; //Set Content-Length field p += sprintf(p, "Content-Length: %" PRIuSIZE "\r\n", length); //Terminate the header with an empty line p += sprintf(p, "\r\n"); //Format HTML response p += sprintf(p, template, statusCode, statusCode, message); //Rewind to the beginning of the buffer txContext->bufferPos = 0; //Update the number of data buffered but not yet sent txContext->bufferLen = strlen((char_t *) txContext->buffer); //Successful processing return NO_ERROR; } /** * @brief Verify client's handshake * @param[in] webSocket Handle to a WebSocket * @return Error code **/ error_t webSocketVerifyClientHandshake(WebSocket *webSocket) { error_t error; WebSocketHandshakeContext *handshakeContext; //Debug message TRACE_DEBUG("WebSocket: verifying client handshake\r\n"); //Point to the handshake context handshakeContext = &webSocket->handshakeContext; //The HTTP version must be at least 1.1 if(handshakeContext->version < WS_HTTP_VERSION_1_1) return ERROR_INVALID_REQUEST; //The request must contain an Upgrade header field whose value //must include the "websocket" keyword if(!handshakeContext->upgradeWebSocket) return ERROR_INVALID_REQUEST; //The request must contain a Connection header field whose value //must include the "Upgrade" token if(!handshakeContext->connectionUpgrade) return ERROR_INVALID_REQUEST; //The request must include a header field with the name Sec-WebSocket-Key if(handshakeContext->clientKey[0] == 0) return ERROR_INVALID_REQUEST; //Check the Sec-WebSocket-Key header field error = webSocketVerifyClientKey(webSocket); //Verification failed? if(error) return error; //Generate the server part of the handshake webSocketChangeState(webSocket, WS_STATE_SERVER_HANDSHAKE); //Successful processing return NO_ERROR; } /** * @brief Verify server's handshake * @param[in] webSocket Handle to a WebSocket * @return Error code **/ error_t webSocketVerifyServerHandshake(WebSocket *webSocket) { error_t error; WebSocketHandshakeContext *handshakeContext; //Debug message TRACE_DEBUG("WebSocket: verifying server handshake\r\n"); //Point to the handshake context handshakeContext = &webSocket->handshakeContext; //If the status code received from the server is not 101, the client //handles the response per HTTP procedures if(handshakeContext->statusCode == 401) { //Authorization required return ERROR_AUTH_REQUIRED; } else if(handshakeContext->statusCode != 101) { //Unknown status code return ERROR_INVALID_STATUS; } //If the response lacks an Upgrade header field or the Upgrade header field //contains a value that is not an ASCII case-insensitive match for the //value "websocket", the client must fail the WebSocket connection if(!handshakeContext->upgradeWebSocket) return ERROR_INVALID_SYNTAX; //If the response lacks a Connection header field or the Connection header //field doesn't contain a token that is an ASCII case-insensitive match for //the value "Upgrade", the client must fail the WebSocket connection if(!handshakeContext->connectionUpgrade) return ERROR_INVALID_SYNTAX; //If the response lacks a Sec-WebSocket-Accept header field, the client //must fail the WebSocket connection if(strlen(handshakeContext->serverKey) == 0) return ERROR_INVALID_SYNTAX; //Check the Sec-WebSocket-Accept header field error = webSocketVerifyServerKey(webSocket); //Verification failed? if(error) return error; //If the server's response is validated as provided for above, it is //said that the WebSocket connection is established and that the //WebSocket connection is in the OPEN state webSocketChangeState(webSocket, WS_STATE_OPEN); //Successful processing return NO_ERROR; } /** * @brief Generate client's key * @param[in] webSocket Handle to a WebSocket * @return Error code **/ error_t webSocketGenerateClientKey(WebSocket *webSocket) { error_t error; size_t n; uint8_t nonce[16]; WebSocketHandshakeContext *handshakeContext; //Debug message TRACE_DEBUG("WebSocket: Generating client's key...\r\n"); //Point to the handshake context handshakeContext = &webSocket->handshakeContext; //Make sure that the RNG callback function has been registered if(webSockRandCallback == NULL) { //A cryptographically strong random number generator //must be used to generate the nonce return ERROR_PRNG_NOT_READY; } //A nonce must be selected randomly for each connection error = webSockRandCallback(nonce, sizeof(nonce)); //Any error to report? if(error) return error; //Encode the client's key base64Encode(nonce, sizeof(nonce), handshakeContext->clientKey, &n); //Debug message TRACE_DEBUG(" Client key: %s\r\n", handshakeContext->clientKey); //Successful processing return NO_ERROR; } /** * @brief Generate server's key * @param[in] webSocket Handle to a WebSocket * @return Error code **/ error_t webSocketGenerateServerKey(WebSocket *webSocket) { size_t n; WebSocketHandshakeContext *handshakeContext; Sha1Context sha1Context; //Debug message TRACE_DEBUG("WebSocket: Generating server's key...\r\n"); //Point to the handshake context handshakeContext = &webSocket->handshakeContext; //Retrieve the length of the client key n = strlen(handshakeContext->clientKey); //Concatenate the Sec-WebSocket-Key with the GUID string and digest //the resulting string using SHA-1 sha1Init(&sha1Context); sha1Update(&sha1Context, handshakeContext->clientKey, n); sha1Update(&sha1Context, webSocketGuid, strlen(webSocketGuid)); sha1Final(&sha1Context, NULL); //Encode the result using Base64 base64Encode(sha1Context.digest, SHA1_DIGEST_SIZE, handshakeContext->serverKey, &n); //Debug message TRACE_DEBUG(" Server key: %s\r\n", handshakeContext->serverKey); //Successful processing return NO_ERROR; } /** * @brief Verify client's key * @param[in] webSocket Handle to a WebSocket * @return Error code **/ error_t webSocketVerifyClientKey(WebSocket *webSocket) { error_t error; size_t n; char_t *buffer; WebSocketHandshakeContext *handshakeContext; //Debug message TRACE_DEBUG("WebSocket: Verifying client's key...\r\n"); //Temporary buffer buffer = (char_t *) webSocket->txContext.buffer; //Point to the handshake context handshakeContext = &webSocket->handshakeContext; //Retrieve the length of the client's key n = strlen(handshakeContext->clientKey); //The value of the Sec-WebSocket-Key header field must be a 16-byte //value that has been Base64-encoded error = base64Decode(handshakeContext->clientKey, n, buffer, &n); //Decoding failed? if(error) return ERROR_INVALID_KEY; //Check the length of the resulting value if(n != 16) return ERROR_INVALID_KEY; //Successful verification return NO_ERROR; } /** * @brief Verify server's key * @param[in] webSocket Handle to a WebSocket * @return Error code **/ error_t webSocketVerifyServerKey(WebSocket *webSocket) { size_t n; char_t *buffer; WebSocketHandshakeContext *handshakeContext; Sha1Context sha1Context; //Debug message TRACE_DEBUG("WebSocket: Verifying server's key...\r\n"); //Temporary buffer buffer = (char_t *) webSocket->txContext.buffer; //Point to the handshake context handshakeContext = &webSocket->handshakeContext; //Retrieve the length of the client's key n = strlen(handshakeContext->clientKey); //Concatenate the Sec-WebSocket-Key with the GUID string and digest //the resulting string using SHA-1 sha1Init(&sha1Context); sha1Update(&sha1Context, handshakeContext->clientKey, n); sha1Update(&sha1Context, webSocketGuid, strlen(webSocketGuid)); sha1Final(&sha1Context, NULL); //Encode the result using Base64 base64Encode(sha1Context.digest, SHA1_DIGEST_SIZE, buffer, &n); //Debug message TRACE_DEBUG(" Client key: %s\r\n", handshakeContext->clientKey); TRACE_DEBUG(" Server key: %s\r\n", handshakeContext->serverKey); TRACE_DEBUG(" Calculated key: %s\r\n", webSocket->txContext.buffer); //Check whether the server's key is valid if(strcmp(handshakeContext->serverKey, buffer)) return ERROR_INVALID_KEY; //Successful verification return NO_ERROR; } /** * @brief Check whether a status code is valid * @param[in] statusCode Status code * @return The function returns TRUE is the specified status code is * valid. Otherwise, FALSE is returned **/ bool_t webSocketCheckStatusCode(uint16_t statusCode) { bool_t valid; //Check status code if(statusCode == WS_STATUS_CODE_NORMAL_CLOSURE || statusCode == WS_STATUS_CODE_GOING_AWAY || statusCode == WS_STATUS_CODE_PROTOCOL_ERROR || statusCode == WS_STATUS_CODE_UNSUPPORTED_DATA || statusCode == WS_STATUS_CODE_INVALID_PAYLOAD_DATA || statusCode == WS_STATUS_CODE_POLICY_VIOLATION || statusCode == WS_STATUS_CODE_MESSAGE_TOO_BIG || statusCode == WS_STATUS_CODE_MANDATORY_EXT || statusCode == WS_STATUS_CODE_INTERNAL_ERROR) { valid = TRUE; } else if(statusCode >= 3000) { valid = TRUE; } else { valid = FALSE; } //The function returns TRUE is the specified status code is valid return valid; } /** * @brief Decode a percent-encoded string * @param[in] input NULL-terminated string to be decoded * @param[out] output NULL-terminated string resulting from the decoding process * @param[in] outputSize Size of the output buffer in bytes * @return Error code **/ error_t webSocketDecodePercentEncodedString(const char_t *input, char_t *output, size_t outputSize) { size_t i; char_t buffer[3]; //Check parameters if(input == NULL || output == NULL) return ERROR_INVALID_PARAMETER; //Decode the percent-encoded string for(i = 0; *input != '\0' && i < outputSize; i++) { //Check current character if(*input == '+') { //Replace '+' characters with spaces output[i] = ' '; //Advance data pointer input++; } else if(input[0] == '%' && input[1] != '\0' && input[2] != '\0') { //Process percent-encoded characters buffer[0] = input[1]; buffer[1] = input[2]; buffer[2] = '\0'; //String to integer conversion output[i] = (uint8_t) strtoul(buffer, NULL, 16); //Advance data pointer input += 3; } else { //Copy any other characters output[i] = *input; //Advance data pointer input++; } } //Check whether the output buffer runs out of space if(i >= outputSize) return ERROR_FAILURE; //Properly terminate the resulting string output[i] = '\0'; //Successful processing return NO_ERROR; } /** * @brief Check whether a an UTF-8 stream is valid * @param[in] context UTF-8 decoding context * @param[in] data Pointer to the chunk of data to be processed * @param[in] length Data chunk length * @param[in] remaining number of remaining bytes in the UTF-8 stream * @return The function returns TRUE is the specified UTF-8 stream is * valid. Otherwise, FALSE is returned **/ bool_t webSocketCheckUtf8Stream(WebSocketUtf8Context *context, const uint8_t *data, size_t length, size_t remaining) { size_t i; bool_t valid; //Initialize flag valid = TRUE; //Interpret the byte stream as UTF-8 for(i = 0; i < length && valid; i++) { //Leading or continuation byte? if(context->utf8CharIndex == 0) { //7-bit code point? if((data[i] & 0x80) == 0x00) { //The code point consist of a single byte context->utf8CharSize = 1; //Decode the first byte of the sequence context->utf8CodePoint = data[i] & 0x7F; } //11-bit code point? else if((data[i] & 0xE0) == 0xC0) { //The code point consist of a 2 bytes context->utf8CharSize = 2; //Decode the first byte of the sequence context->utf8CodePoint = (data[i] & 0x1F) << 6; } //16-bit code point? else if((data[i] & 0xF0) == 0xE0) { //The code point consist of a 3 bytes context->utf8CharSize = 3; //Decode the first byte of the sequence context->utf8CodePoint = (data[i] & 0x0F) << 12; } //21-bit code point? else if((data[i] & 0xF8) == 0xF0) { //The code point consist of a 3 bytes context->utf8CharSize = 4; //Decode the first byte of the sequence context->utf8CodePoint = (data[i] & 0x07) << 18; } else { //The UTF-8 stream is not valid valid = FALSE; } //This test only applies to frames that are not fragmented if(length <= remaining) { //Make sure the UTF-8 stream is properly terminated if((i + context->utf8CharSize) > remaining) { //The UTF-8 stream is not valid valid = FALSE; } } //Decode the next byte of the sequence context->utf8CharIndex = context->utf8CharSize - 1; } else { //Continuation bytes all have 10 in the high-order position if((data[i] & 0xC0) == 0x80) { //Decode the multi-byte sequence context->utf8CharIndex--; //All continuation bytes contain exactly 6 bits from the code point context->utf8CodePoint |= (data[i] & 0x3F) << (context->utf8CharIndex * 6); //The correct encoding of a code point use only the minimum number //of bytes required to hold the significant bits of the code point if(context->utf8CharSize == 2) { //Overlong encoding is not supported if((context->utf8CodePoint & ~0x7F) == 0) valid = FALSE; } if(context->utf8CharSize == 3 && context->utf8CharIndex < 2) { //Overlong encoding is not supported if((context->utf8CodePoint & ~0x7FF) == 0) valid = FALSE; } if(context->utf8CharSize == 4 && context->utf8CharIndex < 3) { //Overlong encoding is not supported if((context->utf8CodePoint & ~0xFFFF) == 0) valid = FALSE; } //According to the UTF-8 definition (RFC 3629) the high and low //surrogate halves used by UTF-16 (U+D800 through U+DFFF) are not //legal Unicode values, and their UTF-8 encoding should be treated //as an invalid byte sequence if(context->utf8CodePoint >= 0xD800 && context->utf8CodePoint < 0xE000) valid = FALSE; //Code points greater than U+10FFFF are not valid if(context->utf8CodePoint >= 0x110000) valid = FALSE; } else { //The start byte is not followed by enough continuation bytes valid = FALSE; } } } //The function returns TRUE is the specified UTF-8 stream is valid return valid; } #endif