From bb9bd9b42287c2a5c37ab0054d10430705faaf5f Mon Sep 17 00:00:00 2001 From: Sandeep Mistry Date: Fri, 17 Jun 2016 09:31:49 -0400 Subject: [PATCH 001/125] Rename to ArduinoHttpClient, and other metadata updates --- README.md | 6 ++++-- library.properties | 6 +++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index d55dfa4..5536b55 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ -# HttpClient +# ArduinoHttpClient -HttpClient is a library to make it easier to interact with web servers from Arduino. +ArduinoHttpClient is a library to make it easier to interact with web servers from Arduino. + +Derived from [Adrian McEwen's HttpClient library](https://github.com/amcewen/HttpClient) ## Dependencies diff --git a/library.properties b/library.properties index 577c5d9..35c75cc 100644 --- a/library.properties +++ b/library.properties @@ -1,9 +1,9 @@ -name=HttpClient +name=ArduinoHttpClient version=2.2.0 author=Adrian McEwen -maintainer=Adrian McEwen +maintainer=Arduino sentence=Library to easily make HTTP GET, POST and PUT requests to a web server. paragraph=Works with any class derived from Client - so switching between Ethernet, WiFi and GSMClient requires minimal code changes. category=Communication -url=http://github.com/amcewen/HttpClient +url=https://github.com/arduino-libraries/ArduinoHttpClient architectures=* From e057c073d9759f0dd14feba55962e43553f76932 Mon Sep 17 00:00:00 2001 From: Sandeep Mistry Date: Fri, 17 Jun 2016 09:32:05 -0400 Subject: [PATCH 002/125] Add ArduinoHttpClient.h --- ArduinoHttpClient.h | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 ArduinoHttpClient.h diff --git a/ArduinoHttpClient.h b/ArduinoHttpClient.h new file mode 100644 index 0000000..f43f59a --- /dev/null +++ b/ArduinoHttpClient.h @@ -0,0 +1,10 @@ +// Class to simplify HTTP fetching on Arduino +// (c) Copyright Arduino. 2016 +// Released under Apache License, version 2.0 + +#ifndef ArduinoHttpClient_h +#define ArduinoHttpClient_h + +#include "HttpClient.h" + +#endif From b72b7056a966a60149ce3ba697225d46a380dd2d Mon Sep 17 00:00:00 2001 From: Sandeep Mistry Date: Fri, 17 Jun 2016 09:41:12 -0400 Subject: [PATCH 003/125] Update example to use WiFi101 --- .../SimpleHttpExample/SimpleHttpExample.ino | 35 ++++++++++++------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/examples/SimpleHttpExample/SimpleHttpExample.ino b/examples/SimpleHttpExample/SimpleHttpExample.ino index f12f987..351ff2b 100644 --- a/examples/SimpleHttpExample/SimpleHttpExample.ino +++ b/examples/SimpleHttpExample/SimpleHttpExample.ino @@ -6,20 +6,20 @@ // outputs the content to the serial port #include -#include -#include -#include +#include +#include // This example downloads the URL "http://arduino.cc/" +char ssid[] = "yourNetwork"; // your network SSID (name) +char pass[] = "secretPassword"; // your network password + // Name of the server we want to connect to const char kHostname[] = "arduino.cc"; // Path to download (this is the bit after the hostname in the URL // that you want to download const char kPath[] = "/"; -byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; - // Number of milliseconds to wait without receiving any data before we give up const int kNetworkTimeout = 30*1000; // Number of milliseconds to wait if no data is available before trying again @@ -27,21 +27,30 @@ const int kNetworkDelay = 1000; void setup() { - // initialize serial communications at 9600 bps: - Serial.begin(9600); + //Initialize serial and wait for port to open: + Serial.begin(9600); + while (!Serial) { + ; // wait for serial port to connect. Needed for native USB port only + } - while (Ethernet.begin(mac) != 1) - { - Serial.println("Error getting IP address via DHCP, trying again..."); - delay(15000); - } + // attempt to connect to Wifi network: + Serial.print("Attempting to connect to WPA SSID: "); + Serial.println(ssid); + while (WiFi.begin(ssid, pass) != WL_CONNECTED) { + // unsuccessful, retry in 4 seconds + Serial.print("failed ... "); + delay(4000); + Serial.print("retrying ... "); + } + + Serial.println("connected"); } void loop() { int err =0; - EthernetClient c; + WiFiClient c; HttpClient http(c); err = http.get(kHostname, kPath); From ad9bd941367f0ae0cb53e6c069161644681f4a74 Mon Sep 17 00:00:00 2001 From: Sandeep Mistry Date: Fri, 17 Jun 2016 10:02:32 -0400 Subject: [PATCH 004/125] Wrap proxy member var declarations in #ifdef --- HttpClient.cpp | 2 +- HttpClient.h | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/HttpClient.cpp b/HttpClient.cpp index 5a11a45..6efc4ea 100644 --- a/HttpClient.cpp +++ b/HttpClient.cpp @@ -29,7 +29,7 @@ HttpClient::HttpClient(Client& aClient, const char* aProxy, uint16_t aProxyPort) } #else HttpClient::HttpClient(Client& aClient) - : iClient(&aClient), iProxyPort(0) + : iClient(&aClient) { resetState(); } diff --git a/HttpClient.h b/HttpClient.h index b4c3974..ff00083 100644 --- a/HttpClient.h +++ b/HttpClient.h @@ -440,9 +440,11 @@ class HttpClient : public Client int iBodyLengthConsumed; // How far through a Content-Length header prefix we are const char* iContentLengthPtr; +#ifdef PROXY_ENABLED // Address of the proxy to use, if we're using one IPAddress iProxyAddress; uint16_t iProxyPort; +#endif uint32_t iHttpResponseTimeout; }; From e3a6c20cd99517bfbd0e1781998f2285425e20a1 Mon Sep 17 00:00:00 2001 From: Sandeep Mistry Date: Fri, 17 Jun 2016 10:13:56 -0400 Subject: [PATCH 005/125] Remove proxy support (for now) --- HttpClient.cpp | 85 +++++--------------------------------------------- HttpClient.h | 9 ------ 2 files changed, 7 insertions(+), 87 deletions(-) diff --git a/HttpClient.cpp b/HttpClient.cpp index 6efc4ea..3e7c852 100644 --- a/HttpClient.cpp +++ b/HttpClient.cpp @@ -4,36 +4,16 @@ #include "HttpClient.h" #include "b64.h" -#ifdef PROXY_ENABLED // currently disabled as introduces dependency on Dns.h in Ethernet -#include -#endif // Initialize constants const char* HttpClient::kUserAgent = "Arduino/2.2.0"; const char* HttpClient::kContentLengthPrefix = HTTP_HEADER_CONTENT_LENGTH ": "; -#ifdef PROXY_ENABLED // currently disabled as introduces dependency on Dns.h in Ethernet -HttpClient::HttpClient(Client& aClient, const char* aProxy, uint16_t aProxyPort) - : iClient(&aClient), iProxyPort(aProxyPort) -{ - resetState(); - if (aProxy) - { - // Resolve the IP address for the proxy - DNSClient dns; - dns.begin(Ethernet.dnsServerIP()); - // Not ideal that we discard any errors here, but not a lot we can do in the ctor - // and we'll get a connect error later anyway - (void)dns.getHostByName(aProxy, iProxyAddress); - } -} -#else HttpClient::HttpClient(Client& aClient) : iClient(&aClient) { resetState(); } -#endif void HttpClient::resetState() { @@ -64,27 +44,12 @@ int HttpClient::startRequest(const char* aServerName, uint16_t aServerPort, cons return HTTP_ERROR_API; } -#ifdef PROXY_ENABLED - if (iProxyPort) - { - if (!iClient->connect(iProxyAddress, iProxyPort) > 0) - { -#ifdef LOGGING - Serial.println("Proxy connection failed"); -#endif - return HTTP_ERROR_CONNECTION_FAILED; - } - } - else -#endif + if (!iClient->connect(aServerName, aServerPort) > 0) { - if (!iClient->connect(aServerName, aServerPort) > 0) - { #ifdef LOGGING - Serial.println("Connection failed"); + Serial.println("Connection failed"); #endif - return HTTP_ERROR_CONNECTION_FAILED; - } + return HTTP_ERROR_CONNECTION_FAILED; } // Now we're connected, send the first part of the request @@ -107,27 +72,12 @@ int HttpClient::startRequest(const IPAddress& aServerAddress, const char* aServe return HTTP_ERROR_API; } -#ifdef PROXY_ENABLED - if (iProxyPort) + if (!iClient->connect(aServerAddress, aServerPort) > 0) { - if (!iClient->connect(iProxyAddress, iProxyPort) > 0) - { #ifdef LOGGING - Serial.println("Proxy connection failed"); + Serial.println("Connection failed"); #endif - return HTTP_ERROR_CONNECTION_FAILED; - } - } - else -#endif - { - if (!iClient->connect(aServerAddress, aServerPort) > 0) - { -#ifdef LOGGING - Serial.println("Connection failed"); -#endif - return HTTP_ERROR_CONNECTION_FAILED; - } + return HTTP_ERROR_CONNECTION_FAILED; } // Now we're connected, send the first part of the request @@ -150,28 +100,7 @@ int HttpClient::sendInitialHeaders(const char* aServerName, IPAddress aServerIP, // Send the HTTP command, i.e. "GET /somepath/ HTTP/1.0" iClient->print(aHttpMethod); iClient->print(" "); -#ifdef PROXY_ENABLED - if (iProxyPort) - { - // We're going through a proxy, send a full URL - iClient->print("http://"); - if (aServerName) - { - // We've got a server name, so use it - iClient->print(aServerName); - } - else - { - // We'll have to use the IP address - iClient->print(aServerIP); - } - if (aPort != kHttpPort) - { - iClient->print(":"); - iClient->print(aPort); - } - } -#endif + iClient->print(aURLPath); iClient->println(" HTTP/1.1"); // The host header, if required diff --git a/HttpClient.h b/HttpClient.h index ff00083..d4d980c 100644 --- a/HttpClient.h +++ b/HttpClient.h @@ -44,11 +44,7 @@ class HttpClient : public Client // FIXME Write longer API request, using port and user-agent, example // FIXME Update tempToPachube example to calculate Content-Length correctly -#ifdef PROXY_ENABLED // currently disabled as introduces dependency on Dns.h in Ethernet - HttpClient(Client& aClient, const char* aProxy =NULL, uint16_t aProxyPort =0); -#else HttpClient(Client& aClient); -#endif /** Start a more complex request. Use this when you need to send additional headers in the request, @@ -440,11 +436,6 @@ class HttpClient : public Client int iBodyLengthConsumed; // How far through a Content-Length header prefix we are const char* iContentLengthPtr; -#ifdef PROXY_ENABLED - // Address of the proxy to use, if we're using one - IPAddress iProxyAddress; - uint16_t iProxyPort; -#endif uint32_t iHttpResponseTimeout; }; From 0030d412876add1418e4d3efa2995bd50b2a9bc0 Mon Sep 17 00:00:00 2001 From: Sandeep Mistry Date: Fri, 17 Jun 2016 10:56:27 -0400 Subject: [PATCH 006/125] Make server name/address and port constructor arguments --- HttpClient.cpp | 84 +++---- HttpClient.h | 227 ++---------------- .../SimpleHttpExample/SimpleHttpExample.ino | 4 +- 3 files changed, 53 insertions(+), 262 deletions(-) diff --git a/HttpClient.cpp b/HttpClient.cpp index 3e7c852..a96ef80 100644 --- a/HttpClient.cpp +++ b/HttpClient.cpp @@ -9,8 +9,14 @@ const char* HttpClient::kUserAgent = "Arduino/2.2.0"; const char* HttpClient::kContentLengthPrefix = HTTP_HEADER_CONTENT_LENGTH ": "; -HttpClient::HttpClient(Client& aClient) - : iClient(&aClient) +HttpClient::HttpClient(Client& aClient, const char* aServerName, uint16_t aServerPort) + : iClient(&aClient), iServerName(aServerName), iServerAddress(), iServerPort(aServerPort) +{ + resetState(); +} + +HttpClient::HttpClient(Client& aClient, const IPAddress& aServerAddress, uint16_t aServerPort) + : iClient(&aClient), iServerName(NULL), iServerAddress(aServerAddress), iServerPort(aServerPort) { resetState(); } @@ -36,7 +42,7 @@ void HttpClient::beginRequest() iState = eRequestStarted; } -int HttpClient::startRequest(const char* aServerName, uint16_t aServerPort, const char* aURLPath, const char* aHttpMethod, const char* aUserAgent) +int HttpClient::startRequest(const char* aURLPath, const char* aHttpMethod) { tHttpState initialState = iState; if ((eIdle != iState) && (eRequestStarted != iState)) @@ -44,44 +50,26 @@ int HttpClient::startRequest(const char* aServerName, uint16_t aServerPort, cons return HTTP_ERROR_API; } - if (!iClient->connect(aServerName, aServerPort) > 0) - { -#ifdef LOGGING - Serial.println("Connection failed"); -#endif - return HTTP_ERROR_CONNECTION_FAILED; - } - - // Now we're connected, send the first part of the request - int ret = sendInitialHeaders(aServerName, IPAddress(0,0,0,0), aServerPort, aURLPath, aHttpMethod, aUserAgent); - if ((initialState == eIdle) && (HTTP_SUCCESS == ret)) - { - // This was a simple version of the API, so terminate the headers now - finishHeaders(); - } - // else we'll call it in endRequest or in the first call to print, etc. - - return ret; -} - -int HttpClient::startRequest(const IPAddress& aServerAddress, const char* aServerName, uint16_t aServerPort, const char* aURLPath, const char* aHttpMethod, const char* aUserAgent) -{ - tHttpState initialState = iState; - if ((eIdle != iState) && (eRequestStarted != iState)) - { - return HTTP_ERROR_API; - } - - if (!iClient->connect(aServerAddress, aServerPort) > 0) - { -#ifdef LOGGING - Serial.println("Connection failed"); -#endif - return HTTP_ERROR_CONNECTION_FAILED; + if (iServerName) { + if (!iClient->connect(iServerName, iServerPort) > 0) + { + #ifdef LOGGING + Serial.println("Connection failed"); + #endif + return HTTP_ERROR_CONNECTION_FAILED; + } + } else { + if (!iClient->connect(iServerAddress, iServerPort) > 0) + { + #ifdef LOGGING + Serial.println("Connection failed"); + #endif + return HTTP_ERROR_CONNECTION_FAILED; + } } // Now we're connected, send the first part of the request - int ret = sendInitialHeaders(aServerName, aServerAddress, aServerPort, aURLPath, aHttpMethod, aUserAgent); + int ret = sendInitialHeaders(aURLPath, aHttpMethod); if ((initialState == eIdle) && (HTTP_SUCCESS == ret)) { // This was a simple version of the API, so terminate the headers now @@ -92,7 +80,7 @@ int HttpClient::startRequest(const IPAddress& aServerAddress, const char* aServe return ret; } -int HttpClient::sendInitialHeaders(const char* aServerName, IPAddress aServerIP, uint16_t aPort, const char* aURLPath, const char* aHttpMethod, const char* aUserAgent) +int HttpClient::sendInitialHeaders(const char* aURLPath, const char* aHttpMethod) { #ifdef LOGGING Serial.println("Connected"); @@ -104,26 +92,20 @@ int HttpClient::sendInitialHeaders(const char* aServerName, IPAddress aServerIP, iClient->print(aURLPath); iClient->println(" HTTP/1.1"); // The host header, if required - if (aServerName) + if (iServerName) { iClient->print("Host: "); - iClient->print(aServerName); - if (aPort != kHttpPort) + iClient->print(iServerName); + if (iServerPort != kHttpPort) { iClient->print(":"); - iClient->print(aPort); + iClient->print(iServerPort); } iClient->println(); } // And user-agent string - if (aUserAgent) - { - sendHeader(HTTP_HEADER_USER_AGENT, aUserAgent); - } - else - { - sendHeader(HTTP_HEADER_USER_AGENT, kUserAgent); - } + sendHeader(HTTP_HEADER_USER_AGENT, kUserAgent); + // We don't support persistent connections, so tell the server to // close this connection after we're done sendHeader(HTTP_HEADER_CONNECTION, "close"); diff --git a/HttpClient.h b/HttpClient.h index d4d980c..677bb55 100644 --- a/HttpClient.h +++ b/HttpClient.h @@ -44,7 +44,8 @@ class HttpClient : public Client // FIXME Write longer API request, using port and user-agent, example // FIXME Update tempToPachube example to calculate Content-Length correctly - HttpClient(Client& aClient); + HttpClient(Client& aClient, const char* aServerName, uint16_t aServerPort = kHttpPort); + HttpClient(Client& aClient, const IPAddress& aServerAddress, uint16_t aServerPort = kHttpPort); /** Start a more complex request. Use this when you need to send additional headers in the request, @@ -59,219 +60,33 @@ class HttpClient : public Client void endRequest(); /** Connect to the server and start to send a GET request. - @param aServerName Name of the server being connected to. If NULL, the - "Host" header line won't be sent - @param aServerPort Port to connect to on the server @param aURLPath Url to request - @param aUserAgent User-Agent string to send. If NULL the default - user-agent kUserAgent will be sent @return 0 if successful, else error */ - int get(const char* aServerName, uint16_t aServerPort, const char* aURLPath, - const char* aUserAgent =NULL) - { return startRequest(aServerName, aServerPort, aURLPath, HTTP_METHOD_GET, aUserAgent); } - - /** Connect to the server and start to send a GET request. - @param aServerName Name of the server being connected to. If NULL, the - "Host" header line won't be sent - @param aURLPath Url to request - @param aUserAgent User-Agent string to send. If NULL the default - user-agent kUserAgent will be sent - @return 0 if successful, else error - */ - int get(const char* aServerName, const char* aURLPath, const char* aUserAgent =NULL) - { return startRequest(aServerName, kHttpPort, aURLPath, HTTP_METHOD_GET, aUserAgent); } - - /** Connect to the server and start to send a GET request. This version connects - doesn't perform a DNS lookup and just connects to the given IP address. - @param aServerAddress IP address of the server to connect to - @param aServerName Name of the server being connected to. If NULL, the - "Host" header line won't be sent - @param aServerPort Port to connect to on the server - @param aURLPath Url to request - @param aUserAgent User-Agent string to send. If NULL the default - user-agent kUserAgent will be sent - @return 0 if successful, else error - */ - int get(const IPAddress& aServerAddress, - const char* aServerName, - uint16_t aServerPort, - const char* aURLPath, - const char* aUserAgent =NULL) - { return startRequest(aServerAddress, aServerName, aServerPort, aURLPath, HTTP_METHOD_GET, aUserAgent); } - - /** Connect to the server and start to send a GET request. This version connects - doesn't perform a DNS lookup and just connects to the given IP address. - @param aServerAddress IP address of the server to connect to - @param aServerName Name of the server being connected to. If NULL, the - "Host" header line won't be sent - @param aURLPath Url to request - @param aUserAgent User-Agent string to send. If NULL the default - user-agent kUserAgent will be sent - @return 0 if successful, else error - */ - int get(const IPAddress& aServerAddress, - const char* aServerName, - const char* aURLPath, - const char* aUserAgent =NULL) - { return startRequest(aServerAddress, aServerName, kHttpPort, aURLPath, HTTP_METHOD_GET, aUserAgent); } + int get(const char* aURLPath) + { return startRequest(aURLPath, HTTP_METHOD_GET); } /** Connect to the server and start to send a POST request. - @param aServerName Name of the server being connected to. If NULL, the - "Host" header line won't be sent - @param aServerPort Port to connect to on the server @param aURLPath Url to request - @param aUserAgent User-Agent string to send. If NULL the default - user-agent kUserAgent will be sent @return 0 if successful, else error */ - int post(const char* aServerName, - uint16_t aServerPort, - const char* aURLPath, - const char* aUserAgent =NULL) - { return startRequest(aServerName, aServerPort, aURLPath, HTTP_METHOD_POST, aUserAgent); } - - /** Connect to the server and start to send a POST request. - @param aServerName Name of the server being connected to. If NULL, the - "Host" header line won't be sent - @param aURLPath Url to request - @param aUserAgent User-Agent string to send. If NULL the default - user-agent kUserAgent will be sent - @return 0 if successful, else error - */ - int post(const char* aServerName, - const char* aURLPath, - const char* aUserAgent =NULL) - { return startRequest(aServerName, kHttpPort, aURLPath, HTTP_METHOD_POST, aUserAgent); } - - /** Connect to the server and start to send a POST request. This version connects - doesn't perform a DNS lookup and just connects to the given IP address. - @param aServerAddress IP address of the server to connect to - @param aServerName Name of the server being connected to. If NULL, the - "Host" header line won't be sent - @param aServerPort Port to connect to on the server - @param aURLPath Url to request - @param aUserAgent User-Agent string to send. If NULL the default - user-agent kUserAgent will be sent - @return 0 if successful, else error - */ - int post(const IPAddress& aServerAddress, - const char* aServerName, - uint16_t aServerPort, - const char* aURLPath, - const char* aUserAgent =NULL) - { return startRequest(aServerAddress, aServerName, aServerPort, aURLPath, HTTP_METHOD_POST, aUserAgent); } - - /** Connect to the server and start to send a POST request. This version connects - doesn't perform a DNS lookup and just connects to the given IP address. - @param aServerAddress IP address of the server to connect to - @param aServerName Name of the server being connected to. If NULL, the - "Host" header line won't be sent - @param aURLPath Url to request - @param aUserAgent User-Agent string to send. If NULL the default - user-agent kUserAgent will be sent - @return 0 if successful, else error - */ - int post(const IPAddress& aServerAddress, - const char* aServerName, - const char* aURLPath, - const char* aUserAgent =NULL) - { return startRequest(aServerAddress, aServerName, kHttpPort, aURLPath, HTTP_METHOD_POST, aUserAgent); } + int post(const char* aURLPath) + { return startRequest(aURLPath, HTTP_METHOD_POST); } /** Connect to the server and start to send a PUT request. - @param aServerName Name of the server being connected to. If NULL, the - "Host" header line won't be sent - @param aServerPort Port to connect to on the server - @param aURLPath Url to request - @param aUserAgent User-Agent string to send. If NULL the default - user-agent kUserAgent will be sent - @return 0 if successful, else error - */ - int put(const char* aServerName, - uint16_t aServerPort, - const char* aURLPath, - const char* aUserAgent =NULL) - { return startRequest(aServerName, aServerPort, aURLPath, HTTP_METHOD_PUT, aUserAgent); } - - /** Connect to the server and start to send a PUT request. - @param aServerName Name of the server being connected to. If NULL, the - "Host" header line won't be sent - @param aURLPath Url to request - @param aUserAgent User-Agent string to send. If NULL the default - user-agent kUserAgent will be sent - @return 0 if successful, else error - */ - int put(const char* aServerName, - const char* aURLPath, - const char* aUserAgent =NULL) - { return startRequest(aServerName, kHttpPort, aURLPath, HTTP_METHOD_PUT, aUserAgent); } - - /** Connect to the server and start to send a PUT request. This version connects - doesn't perform a DNS lookup and just connects to the given IP address. - @param aServerAddress IP address of the server to connect to - @param aServerName Name of the server being connected to. If NULL, the - "Host" header line won't be sent - @param aServerPort Port to connect to on the server @param aURLPath Url to request - @param aUserAgent User-Agent string to send. If NULL the default - user-agent kUserAgent will be sent @return 0 if successful, else error */ - int put(const IPAddress& aServerAddress, - const char* aServerName, - uint16_t aServerPort, - const char* aURLPath, - const char* aUserAgent =NULL) - { return startRequest(aServerAddress, aServerName, aServerPort, aURLPath, HTTP_METHOD_PUT, aUserAgent); } - - /** Connect to the server and start to send a PUT request. This version connects - doesn't perform a DNS lookup and just connects to the given IP address. - @param aServerAddress IP address of the server to connect to - @param aServerName Name of the server being connected to. If NULL, the - "Host" header line won't be sent - @param aURLPath Url to request - @param aUserAgent User-Agent string to send. If NULL the default - user-agent kUserAgent will be sent - @return 0 if successful, else error - */ - int put(const IPAddress& aServerAddress, - const char* aServerName, - const char* aURLPath, - const char* aUserAgent =NULL) - { return startRequest(aServerAddress, aServerName, kHttpPort, aURLPath, HTTP_METHOD_PUT, aUserAgent); } + int put(const char* aURLPath) + { return startRequest(aURLPath, HTTP_METHOD_PUT); } /** Connect to the server and start to send the request. - @param aServerName Name of the server being connected to. - @param aServerPort Port to connect to on the server @param aURLPath Url to request @param aHttpMethod Type of HTTP request to make, e.g. "GET", "POST", etc. - @param aUserAgent User-Agent string to send. If NULL the default - user-agent kUserAgent will be sent - @return 0 if successful, else error - */ - int startRequest(const char* aServerName, - uint16_t aServerPort, - const char* aURLPath, - const char* aHttpMethod, - const char* aUserAgent); - - /** Connect to the server and start to send the request. - @param aServerAddress IP address of the server to connect to. - @param aServerName Name of the server being connected to. If NULL, the - "Host" header line won't be sent - @param aServerPort Port to connect to on the server - @param aURLPath Url to request - @param aHttpMethod Type of HTTP request to make, e.g. "GET", "POST", etc. - @param aUserAgent User-Agent string to send. If NULL the default - user-agent kUserAgent will be sent @return 0 if successful, else error */ - int startRequest(const IPAddress& aServerAddress, - const char* aServerName, - uint16_t aServerPort, - const char* aURLPath, - const char* aHttpMethod, - const char* aUserAgent); + int startRequest(const char* aURLPath, + const char* aHttpMethod); /** Send an additional header line. This can only be called in between the calls to startRequest and finishRequest. @@ -383,23 +198,12 @@ class HttpClient : public Client void resetState(); /** Send the first part of the request and the initial headers. - @param aServerName Name of the server being connected to. If NULL, the - "Host" header line won't be sent - @param aServerIP IP address of the server (only used if we're going through a - proxy and aServerName is NULL - @param aServerPort Port of the server being connected to. @param aURLPath Url to request @param aHttpMethod Type of HTTP request to make, e.g. "GET", "POST", etc. - @param aUserAgent User-Agent string to send. If NULL the default - user-agent kUserAgent will be sent @return 0 if successful, else error */ - int sendInitialHeaders(const char* aServerName, - IPAddress aServerIP, - uint16_t aPort, - const char* aURLPath, - const char* aHttpMethod, - const char* aUserAgent); + int sendInitialHeaders(const char* aURLPath, + const char* aHttpMethod); /* Let the server know that we've reached the end of the headers */ @@ -424,8 +228,13 @@ class HttpClient : public Client eLineStartingCRFound, eReadingBody } tHttpState; - // Ethernet client we're using + // Client we're using Client* iClient; + // Server we are connecting to + const char* iServerName; + IPAddress iServerAddress; + // Port of server we are connecting to + uint16_t iServerPort; // Current state of the finite-state-machine tHttpState iState; // Stores the status code for the response, once known diff --git a/examples/SimpleHttpExample/SimpleHttpExample.ino b/examples/SimpleHttpExample/SimpleHttpExample.ino index 351ff2b..dd8cb10 100644 --- a/examples/SimpleHttpExample/SimpleHttpExample.ino +++ b/examples/SimpleHttpExample/SimpleHttpExample.ino @@ -51,9 +51,9 @@ void loop() int err =0; WiFiClient c; - HttpClient http(c); + HttpClient http(c, kHostname); - err = http.get(kHostname, kPath); + err = http.get(kPath); if (err == 0) { Serial.println("startedRequest ok"); From 3beefd981a3b3def8ebf5948699a36971bfe054f Mon Sep 17 00:00:00 2001 From: Sandeep Mistry Date: Fri, 17 Jun 2016 11:01:38 -0400 Subject: [PATCH 007/125] Add support for String parameter types for host and URL path --- HttpClient.cpp | 5 +++++ HttpClient.h | 10 ++++++++++ 2 files changed, 15 insertions(+) diff --git a/HttpClient.cpp b/HttpClient.cpp index a96ef80..0cbf083 100644 --- a/HttpClient.cpp +++ b/HttpClient.cpp @@ -15,6 +15,11 @@ HttpClient::HttpClient(Client& aClient, const char* aServerName, uint16_t aServe resetState(); } +HttpClient::HttpClient(Client& aClient, const String& aServerName, uint16_t aServerPort) + : HttpClient(aClient, aServerName.c_str(), aServerPort) +{ +} + HttpClient::HttpClient(Client& aClient, const IPAddress& aServerAddress, uint16_t aServerPort) : iClient(&aClient), iServerName(NULL), iServerAddress(aServerAddress), iServerPort(aServerPort) { diff --git a/HttpClient.h b/HttpClient.h index 677bb55..5d48008 100644 --- a/HttpClient.h +++ b/HttpClient.h @@ -45,6 +45,7 @@ class HttpClient : public Client // FIXME Update tempToPachube example to calculate Content-Length correctly HttpClient(Client& aClient, const char* aServerName, uint16_t aServerPort = kHttpPort); + HttpClient(Client& aClient, const String& aServerName, uint16_t aServerPort = kHttpPort); HttpClient(Client& aClient, const IPAddress& aServerAddress, uint16_t aServerPort = kHttpPort); /** Start a more complex request. @@ -66,6 +67,9 @@ class HttpClient : public Client int get(const char* aURLPath) { return startRequest(aURLPath, HTTP_METHOD_GET); } + int get(const String& aURLPath) + { return get(aURLPath.c_str()); } + /** Connect to the server and start to send a POST request. @param aURLPath Url to request @return 0 if successful, else error @@ -73,6 +77,9 @@ class HttpClient : public Client int post(const char* aURLPath) { return startRequest(aURLPath, HTTP_METHOD_POST); } + int post(const String& aURLPath) + { return post(aURLPath.c_str()); } + /** Connect to the server and start to send a PUT request. @param aURLPath Url to request @return 0 if successful, else error @@ -80,6 +87,9 @@ class HttpClient : public Client int put(const char* aURLPath) { return startRequest(aURLPath, HTTP_METHOD_PUT); } + int put(const String& aURLPath) + { return put(aURLPath.c_str()); } + /** Connect to the server and start to send the request. @param aURLPath Url to request @param aHttpMethod Type of HTTP request to make, e.g. "GET", "POST", etc. From 777b561cc539983d7b84903237e53c8527e47fbe Mon Sep 17 00:00:00 2001 From: Sandeep Mistry Date: Fri, 17 Jun 2016 11:09:14 -0400 Subject: [PATCH 008/125] Make white space consistent --- HttpClient.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/HttpClient.cpp b/HttpClient.cpp index 0cbf083..65099e1 100644 --- a/HttpClient.cpp +++ b/HttpClient.cpp @@ -375,11 +375,11 @@ int HttpClient::read() if (ret >= 0) { if (endOfHeadersReached() && iContentLength > 0) - { + { // We're outputting the body now and we've seen a Content-Length header // So keep track of how many bytes are left iBodyLengthConsumed++; - } + } } return ret; #endif @@ -393,9 +393,9 @@ int HttpClient::read(uint8_t *buf, size_t size) // We're outputting the body now and we've seen a Content-Length header // So keep track of how many bytes are left if (ret >= 0) - { + { iBodyLengthConsumed += ret; - } + } } return ret; } From 625154754a009bcab987154689ce6b277266de9b Mon Sep 17 00:00:00 2001 From: Sandeep Mistry Date: Fri, 17 Jun 2016 11:09:37 -0400 Subject: [PATCH 009/125] Remove unused code --- HttpClient.cpp | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/HttpClient.cpp b/HttpClient.cpp index 65099e1..600820f 100644 --- a/HttpClient.cpp +++ b/HttpClient.cpp @@ -359,18 +359,6 @@ bool HttpClient::endOfBodyReached() int HttpClient::read() { -#if 0 // Fails on WiFi because multi-byte read seems to be broken - uint8_t b[1]; - int ret = read(b, 1); - if (ret == 1) - { - return b[0]; - } - else - { - return -1; - } -#else int ret = iClient->read(); if (ret >= 0) { @@ -382,7 +370,6 @@ int HttpClient::read() } } return ret; -#endif } int HttpClient::read(uint8_t *buf, size_t size) From 9ab55ef6e2da26d72c9f41a63e8abe4bdbce1199 Mon Sep 17 00:00:00 2001 From: Sandeep Mistry Date: Fri, 17 Jun 2016 12:48:49 -0400 Subject: [PATCH 010/125] New API's to read header name and value as String's --- HttpClient.cpp | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++ HttpClient.h | 17 ++++++++++++++ 2 files changed, 79 insertions(+) diff --git a/HttpClient.cpp b/HttpClient.cpp index 600820f..d969ce4 100644 --- a/HttpClient.cpp +++ b/HttpClient.cpp @@ -372,6 +372,68 @@ int HttpClient::read() return ret; } +bool HttpClient::headerAvailable() +{ + // clear the currently store header line + iHeaderLine = ""; + + while (!endOfHeadersReached()) + { + // read a byte from the header + int c = readHeader(); + + if (c == '\r' || c == '\n') + { + if (iHeaderLine.length()) + { + // end of the line, all done + break; + } + else + { + // ignore any CR or LF characters + continue; + } + } + + // append byte to header line + iHeaderLine += (char)c; + } + + return (iHeaderLine.length() > 0); +} + +String HttpClient::readHeaderName() +{ + int colonIndex = iHeaderLine.indexOf(':'); + + if (colonIndex == -1) + { + return ""; + } + + return iHeaderLine.substring(0, colonIndex); +} + +String HttpClient::readHeaderValue() +{ + int colonIndex = iHeaderLine.indexOf(':'); + int startIndex = colonIndex + 1; + + if (colonIndex == -1) + { + return ""; + } + + // trim any leading whitespace + while (startIndex < (int)iHeaderLine.length() && isSpace(iHeaderLine[startIndex])) + { + startIndex++; + } + + return iHeaderLine.substring(startIndex); +} + int HttpClient::read(uint8_t *buf, size_t size) { int ret =iClient->read(buf, size); diff --git a/HttpClient.h b/HttpClient.h index 5d48008..e8312f8 100644 --- a/HttpClient.h +++ b/HttpClient.h @@ -142,6 +142,22 @@ class HttpClient : public Client */ int responseStatusCode(); + /** Check if a header is available to be read. + Use readHeaderName() to read header name, and readHeaderValue() to + read the header value + */ + bool headerAvailable(); + + /** Read the name of the current response header. + Returns empty string if a header is not available. + */ + String readHeaderName(); + + /** Read the vallue of the current response header. + Returns empty string if a header is not available. + */ + String readHeaderValue(); + /** Read the next character of the response headers. This functions in the same way as read() but to be used when reading through the headers. Check whether or not the end of the headers has @@ -256,6 +272,7 @@ class HttpClient : public Client // How far through a Content-Length header prefix we are const char* iContentLengthPtr; uint32_t iHttpResponseTimeout; + String iHeaderLine; }; #endif From 33804d4534dbd4b04ba589849525f8aaa027a2e7 Mon Sep 17 00:00:00 2001 From: Sandeep Mistry Date: Fri, 17 Jun 2016 12:55:17 -0400 Subject: [PATCH 011/125] Add support for String parameters to sendHeader and sendHeader --- HttpClient.h | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/HttpClient.h b/HttpClient.h index e8312f8..ce6a761 100644 --- a/HttpClient.h +++ b/HttpClient.h @@ -105,6 +105,9 @@ class HttpClient : public Client */ void sendHeader(const char* aHeader); + void sendHeader(const String& aHeader) + { sendHeader(aHeader.c_str()); } + /** Send an additional header line. This is an alternate form of sendHeader() which takes the header name and content as separate strings. The call will add the ": " to separate the header, so for example, to @@ -114,6 +117,9 @@ class HttpClient : public Client */ void sendHeader(const char* aHeaderName, const char* aHeaderValue); + void sendHeader(const String& aHeaderName, const String& aHeaderValue) + { sendHeader(aHeaderName.c_str(), aHeaderValue.c_str()); } + /** Send an additional header line. This is an alternate form of sendHeader() which takes the header name and content separately but where the value is provided as an integer. @@ -124,6 +130,9 @@ class HttpClient : public Client */ void sendHeader(const char* aHeaderName, const int aHeaderValue); + void sendHeader(const String& aHeaderName, const int aHeaderValue) + { sendHeader(aHeaderName.c_str(), aHeaderValue); } + /** Send a basic authentication header. This will encode the given username and password, and send them in suitable header line for doing Basic Authentication. @@ -132,6 +141,9 @@ class HttpClient : public Client */ void sendBasicAuth(const char* aUser, const char* aPassword); + void sendBasicAuth(const String& aUser, const String& aPassword) + { sendBasicAuth(aUser.c_str(), aPassword.c_str()); } + /** Finish sending the HTTP request. This basically just sends the blank line to signify the end of the request */ From bfedff87c1641a57b9fc47d3346c0de14e39bb53 Mon Sep 17 00:00:00 2001 From: Sandeep Mistry Date: Fri, 17 Jun 2016 14:18:11 -0400 Subject: [PATCH 012/125] Add connection keep alive support --- HttpClient.cpp | 77 +++++++++++++++++++++++++++++++++++++------------- HttpClient.h | 7 +++++ 2 files changed, 64 insertions(+), 20 deletions(-) diff --git a/HttpClient.cpp b/HttpClient.cpp index d969ce4..055c121 100644 --- a/HttpClient.cpp +++ b/HttpClient.cpp @@ -10,7 +10,8 @@ const char* HttpClient::kUserAgent = "Arduino/2.2.0"; const char* HttpClient::kContentLengthPrefix = HTTP_HEADER_CONTENT_LENGTH ": "; HttpClient::HttpClient(Client& aClient, const char* aServerName, uint16_t aServerPort) - : iClient(&aClient), iServerName(aServerName), iServerAddress(), iServerPort(aServerPort) + : iClient(&aClient), iServerName(aServerName), iServerAddress(), iServerPort(aServerPort), + iConnectionClose(true) { resetState(); } @@ -21,7 +22,8 @@ HttpClient::HttpClient(Client& aClient, const String& aServerName, uint16_t aSer } HttpClient::HttpClient(Client& aClient, const IPAddress& aServerAddress, uint16_t aServerPort) - : iClient(&aClient), iServerName(NULL), iServerAddress(aServerAddress), iServerPort(aServerPort) + : iClient(&aClient), iServerName(NULL), iServerAddress(aServerAddress), iServerPort(aServerPort), + iConnectionClose(true) { resetState(); } @@ -42,6 +44,11 @@ void HttpClient::stop() resetState(); } +void HttpClient::connectionKeepAlive() +{ + iConnectionClose = false; +} + void HttpClient::beginRequest() { iState = eRequestStarted; @@ -49,28 +56,44 @@ void HttpClient::beginRequest() int HttpClient::startRequest(const char* aURLPath, const char* aHttpMethod) { + if (!iConnectionClose) + { + flushClientRx(); + + resetState(); + } + tHttpState initialState = iState; if ((eIdle != iState) && (eRequestStarted != iState)) { return HTTP_ERROR_API; } - if (iServerName) { - if (!iClient->connect(iServerName, iServerPort) > 0) - { - #ifdef LOGGING - Serial.println("Connection failed"); - #endif - return HTTP_ERROR_CONNECTION_FAILED; + if (iConnectionClose || !iClient->connected()) + { + if (iServerName) { + if (!iClient->connect(iServerName, iServerPort) > 0) + { +#ifdef LOGGING + Serial.println("Connection failed"); +#endif + return HTTP_ERROR_CONNECTION_FAILED; + } + } else { + if (!iClient->connect(iServerAddress, iServerPort) > 0) + { +#ifdef LOGGING + Serial.println("Connection failed"); +#endif + return HTTP_ERROR_CONNECTION_FAILED; + } } - } else { - if (!iClient->connect(iServerAddress, iServerPort) > 0) - { - #ifdef LOGGING - Serial.println("Connection failed"); - #endif - return HTTP_ERROR_CONNECTION_FAILED; - } + } + else + { +#ifdef LOGGING + Serial.println("Connection already open"); +#endif } // Now we're connected, send the first part of the request @@ -111,9 +134,12 @@ int HttpClient::sendInitialHeaders(const char* aURLPath, const char* aHttpMethod // And user-agent string sendHeader(HTTP_HEADER_USER_AGENT, kUserAgent); - // We don't support persistent connections, so tell the server to - // close this connection after we're done - sendHeader(HTTP_HEADER_CONNECTION, "close"); + if (iConnectionClose) + { + // Tell the server to + // close this connection after we're done + sendHeader(HTTP_HEADER_CONNECTION, "close"); + } // Everything has gone well iState = eRequestStarted; @@ -194,6 +220,17 @@ void HttpClient::finishHeaders() iState = eRequestSent; } +void HttpClient::flushClientRx() +{ + if (iClient->connected()) + { + while (iClient->available()) + { + iClient->read(); + } + } +} + void HttpClient::endRequest() { if (iState < eRequestSent) diff --git a/HttpClient.h b/HttpClient.h index ce6a761..79ad889 100644 --- a/HttpClient.h +++ b/HttpClient.h @@ -48,6 +48,8 @@ class HttpClient : public Client HttpClient(Client& aClient, const String& aServerName, uint16_t aServerPort = kHttpPort); HttpClient(Client& aClient, const IPAddress& aServerAddress, uint16_t aServerPort = kHttpPort); + void connectionKeepAlive(); + /** Start a more complex request. Use this when you need to send additional headers in the request, but you will also need to call endRequest() when you are finished. @@ -247,6 +249,10 @@ class HttpClient : public Client */ void finishHeaders(); + /** Reading any pending data from the client (used in connection keep alive mode) + */ + void flushClientRx(); + // Number of milliseconds that we wait each time there isn't any data // available to be read (during status code and header processing) static const int kHttpWaitForDataDelay = 1000; @@ -284,6 +290,7 @@ class HttpClient : public Client // How far through a Content-Length header prefix we are const char* iContentLengthPtr; uint32_t iHttpResponseTimeout; + bool iConnectionClose; String iHeaderLine; }; From 4b6f4dfa2912d77bd58e8f386fdbd8bda31766ea Mon Sep 17 00:00:00 2001 From: Sandeep Mistry Date: Fri, 17 Jun 2016 14:37:51 -0400 Subject: [PATCH 013/125] Add check for bodyLen in read loop --- examples/SimpleHttpExample/SimpleHttpExample.ino | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/SimpleHttpExample/SimpleHttpExample.ino b/examples/SimpleHttpExample/SimpleHttpExample.ino index dd8cb10..d409b42 100644 --- a/examples/SimpleHttpExample/SimpleHttpExample.ino +++ b/examples/SimpleHttpExample/SimpleHttpExample.ino @@ -6,8 +6,8 @@ // outputs the content to the serial port #include -#include #include +#include // This example downloads the URL "http://arduino.cc/" @@ -82,6 +82,7 @@ void loop() char c; // Whilst we haven't timed out & haven't reached the end of the body while ( (http.connected() || http.available()) && + (bodyLen > 0 || bodyLen != HttpClient::kNoContentLengthHeader) && ((millis() - timeoutStart) < kNetworkTimeout) ) { if (http.available()) From 255118660bcc84253fae507b6ea8b749bb79509b Mon Sep 17 00:00:00 2001 From: Sandeep Mistry Date: Fri, 17 Jun 2016 14:55:03 -0400 Subject: [PATCH 014/125] Make skipResponseHeaders() optional, if contentLength() is called first --- HttpClient.cpp | 11 +++ HttpClient.h | 7 +- .../SimpleHttpExample/SimpleHttpExample.ino | 75 +++++++++---------- 3 files changed, 54 insertions(+), 39 deletions(-) diff --git a/HttpClient.cpp b/HttpClient.cpp index 055c121..dc1bb5a 100644 --- a/HttpClient.cpp +++ b/HttpClient.cpp @@ -384,6 +384,17 @@ int HttpClient::skipResponseHeaders() } } +int HttpClient::contentLength() +{ + // skip the response headers, if they haven't been read already + if (!endOfHeadersReached()) + { + skipResponseHeaders(); + } + + return iContentLength; +} + bool HttpClient::endOfBodyReached() { if (endOfHeadersReached() && (contentLength() != kNoContentLengthHeader)) diff --git a/HttpClient.h b/HttpClient.h index 79ad889..0066bbc 100644 --- a/HttpClient.h +++ b/HttpClient.h @@ -159,6 +159,7 @@ class HttpClient : public Client /** Check if a header is available to be read. Use readHeaderName() to read header name, and readHeaderValue() to read the header value + MUST be called after responseStatusCode() and before contentLength() */ bool headerAvailable(); @@ -177,6 +178,7 @@ class HttpClient : public Client through the headers. Check whether or not the end of the headers has been reached by calling endOfHeadersReached(), although after that point this will still return data as read() would, but slightly less efficiently + MUST be called after responseStatusCode() and before contentLength() @return The next character of the response headers */ int readHeader(); @@ -186,6 +188,7 @@ class HttpClient : public Client returned in the response. You can also use it after you've found all of the headers you're interested in, and just want to get on with processing the body. + MUST be called after responseStatusCode() @return HTTP_SUCCESS if successful, else an error code */ int skipResponseHeaders(); @@ -204,10 +207,12 @@ class HttpClient : public Client virtual bool completed() { return endOfBodyReached(); }; /** Return the length of the body. + Also skips response headers if they have not been read already + MUST be called after responseStatusCode() @return Length of the body, in bytes, or kNoContentLengthHeader if no Content-Length header was returned by the server */ - int contentLength() { return iContentLength; }; + int contentLength(); // Inherited from Print // Note: 1st call to these indicates the user is sending the body, so if need diff --git a/examples/SimpleHttpExample/SimpleHttpExample.ino b/examples/SimpleHttpExample/SimpleHttpExample.ino index d409b42..79ac237 100644 --- a/examples/SimpleHttpExample/SimpleHttpExample.ino +++ b/examples/SimpleHttpExample/SimpleHttpExample.ino @@ -68,45 +68,44 @@ void loop() // similar "success" code (200-299) before carrying on, // but we'll print out whatever response we get - err = http.skipResponseHeaders(); - if (err >= 0) - { - int bodyLen = http.contentLength(); - Serial.print("Content length is: "); - Serial.println(bodyLen); - Serial.println(); - Serial.println("Body returned follows:"); - - // Now we've got to the body, so we can print it out - unsigned long timeoutStart = millis(); - char c; - // Whilst we haven't timed out & haven't reached the end of the body - while ( (http.connected() || http.available()) && - (bodyLen > 0 || bodyLen != HttpClient::kNoContentLengthHeader) && - ((millis() - timeoutStart) < kNetworkTimeout) ) - { - if (http.available()) - { - c = http.read(); - // Print out this character - Serial.print(c); - - bodyLen--; - // We read something, reset the timeout counter - timeoutStart = millis(); - } - else - { - // We haven't got any data, so let's pause to allow some to - // arrive - delay(kNetworkDelay); - } - } - } - else + // If you are interesting in the response headers, you + // can read them here: + //while(http.headerAvailable()) + //{ + // String headerName = http.readHeaderName(); + // String headerValue = http.readHeaderValue(); + //} + + int bodyLen = http.contentLength(); + Serial.print("Content length is: "); + Serial.println(bodyLen); + Serial.println(); + Serial.println("Body returned follows:"); + + // Now we've got to the body, so we can print it out + unsigned long timeoutStart = millis(); + char c; + // Whilst we haven't timed out & haven't reached the end of the body + while ( (http.connected() || http.available()) && + (bodyLen > 0 || bodyLen != HttpClient::kNoContentLengthHeader) && + ((millis() - timeoutStart) < kNetworkTimeout) ) { - Serial.print("Failed to skip response headers: "); - Serial.println(err); + if (http.available()) + { + c = http.read(); + // Print out this character + Serial.print(c); + + bodyLen--; + // We read something, reset the timeout counter + timeoutStart = millis(); + } + else + { + // We haven't got any data, so let's pause to allow some to + // arrive + delay(kNetworkDelay); + } } } else From 7ee216f4aaaa633aa26a1dcf3658dd51a042010e Mon Sep 17 00:00:00 2001 From: Sandeep Mistry Date: Fri, 17 Jun 2016 14:58:17 -0400 Subject: [PATCH 015/125] Use http.endOfBodyReached() in example instead of tracking body len --- examples/SimpleHttpExample/SimpleHttpExample.ino | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/SimpleHttpExample/SimpleHttpExample.ino b/examples/SimpleHttpExample/SimpleHttpExample.ino index 79ac237..550015a 100644 --- a/examples/SimpleHttpExample/SimpleHttpExample.ino +++ b/examples/SimpleHttpExample/SimpleHttpExample.ino @@ -87,7 +87,7 @@ void loop() char c; // Whilst we haven't timed out & haven't reached the end of the body while ( (http.connected() || http.available()) && - (bodyLen > 0 || bodyLen != HttpClient::kNoContentLengthHeader) && + (!http.endOfBodyReached()) && ((millis() - timeoutStart) < kNetworkTimeout) ) { if (http.available()) @@ -96,7 +96,6 @@ void loop() // Print out this character Serial.print(c); - bodyLen--; // We read something, reset the timeout counter timeoutStart = millis(); } From 343a417d1b866f21b9a72a7f49177e6287442c85 Mon Sep 17 00:00:00 2001 From: Sandeep Mistry Date: Fri, 17 Jun 2016 14:59:37 -0400 Subject: [PATCH 016/125] Update header comment --- ArduinoHttpClient.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ArduinoHttpClient.h b/ArduinoHttpClient.h index f43f59a..2064028 100644 --- a/ArduinoHttpClient.h +++ b/ArduinoHttpClient.h @@ -1,4 +1,4 @@ -// Class to simplify HTTP fetching on Arduino +// Library to simplify HTTP fetching on Arduino // (c) Copyright Arduino. 2016 // Released under Apache License, version 2.0 From c5484daee912f8ae8fda9cf3b45e379f9230f501 Mon Sep 17 00:00:00 2001 From: Sandeep Mistry Date: Fri, 17 Jun 2016 15:08:11 -0400 Subject: [PATCH 017/125] Update keywords.txt for new API's --- keywords.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/keywords.txt b/keywords.txt index cdefda4..66489d6 100644 --- a/keywords.txt +++ b/keywords.txt @@ -6,6 +6,7 @@ # Datatypes (KEYWORD1) ####################################### +ArduinoHttpClient KEYWORD1 HttpClient KEYWORD1 ####################################### @@ -27,6 +28,9 @@ endOfHeadersReached KEYWORD2 endOfBodyReached KEYWORD2 completed KEYWORD2 contentLength KEYWORD2 +headerAvailable KEYWORD2 +readHeaderName KEYWORD2 +readHeaderValue KEYWORD2 ####################################### # Constants (LITERAL1) From 91ea1fd8fa867e541785943458d77e395e1a3d7c Mon Sep 17 00:00:00 2001 From: Sandeep Mistry Date: Fri, 17 Jun 2016 15:10:32 -0400 Subject: [PATCH 018/125] Remove installation instructions, update dependencies --- README.md | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 5536b55..655b618 100644 --- a/README.md +++ b/README.md @@ -6,20 +6,17 @@ Derived from [Adrian McEwen's HttpClient library](https://github.com/amcewen/Htt ## Dependencies -- Requires the new Ethernet library API (with DHCP and DNS) which is in Arduino 1.0 and later - -## Installation - -1. Download the latest version of the library from https://github.com/amcewen/HttpClient/releases and save the file somewhere -1. In the Arduino IDE, go to the Sketch -> Import Library -> Add Library... menu option -1. Find the zip file that you saved in the first step, and choose that -1. Check that it has been successfully added by opening the Sketch -> Import Library menu. You should now see HttpClient listed among the available libraries. +- Requires a networking hardware and a library that provides transport specific `Client` instance, such as: + - [WiFi101](https://github.com/arduino-libraries/WiFi101) + - [Ethernet](https://github.com/arduino-libraries/Ethernet) + - [WiFi](https://github.com/arduino-libraries/WiFi) + - [GSM](https://github.com/arduino-libraries/GSM) ## Usage In normal usage, handles the outgoing request and Host header. The returned status code is parsed for you, as is the Content-Length header (if present). -Because it expects an object of type Client, you can use it with any of the networking classes that derive from that. Which means it will work with EthernetClient, WiFiClient and GSMClient. +Because it expects an object of type Client, you can use it with any of the networking classes that derive from that. Which means it will work with WiFiClient, EthernetClient and GSMClient. See the examples for more detail on how the library is used. From f12a797fb3184e6672404710d16ef86c6ddfc341 Mon Sep 17 00:00:00 2001 From: Sandeep Mistry Date: Fri, 17 Jun 2016 15:20:16 -0400 Subject: [PATCH 019/125] Add connectionKeepAlive to keywords.txt and add comment for it --- HttpClient.h | 2 ++ keywords.txt | 1 + 2 files changed, 3 insertions(+) diff --git a/HttpClient.h b/HttpClient.h index 0066bbc..e03b3ed 100644 --- a/HttpClient.h +++ b/HttpClient.h @@ -48,6 +48,8 @@ class HttpClient : public Client HttpClient(Client& aClient, const String& aServerName, uint16_t aServerPort = kHttpPort); HttpClient(Client& aClient, const IPAddress& aServerAddress, uint16_t aServerPort = kHttpPort); + /** Enables connection keep-alive mode + */ void connectionKeepAlive(); /** Start a more complex request. diff --git a/keywords.txt b/keywords.txt index 66489d6..16c8a2d 100644 --- a/keywords.txt +++ b/keywords.txt @@ -28,6 +28,7 @@ endOfHeadersReached KEYWORD2 endOfBodyReached KEYWORD2 completed KEYWORD2 contentLength KEYWORD2 +connectionKeepAlive KEYWORD2 headerAvailable KEYWORD2 readHeaderName KEYWORD2 readHeaderValue KEYWORD2 From 5df9084d8e484372b8c3a7d238d0be45620c3d81 Mon Sep 17 00:00:00 2001 From: Sandeep Mistry Date: Fri, 17 Jun 2016 15:34:49 -0400 Subject: [PATCH 020/125] Correct some warnings --- HttpClient.cpp | 5 ++++- b64.cpp | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/HttpClient.cpp b/HttpClient.cpp index dc1bb5a..3dcf252 100644 --- a/HttpClient.cpp +++ b/HttpClient.cpp @@ -252,7 +252,7 @@ int HttpClient::responseStatusCode() // Where HTTP-Version is of the form: // HTTP-Version = "HTTP" "/" 1*DIGIT "." 1*DIGIT - char c = '\0'; + int c = '\0'; do { // Make sure the status code is reset, and likewise the state. This @@ -311,6 +311,9 @@ int HttpClient::responseStatusCode() case eStatusCodeRead: // We're just waiting for the end of the line now break; + + default: + break; }; // We read something, reset the timeout counter timeoutStart = millis(); diff --git a/b64.cpp b/b64.cpp index b926cad..683d60a 100644 --- a/b64.cpp +++ b/b64.cpp @@ -66,5 +66,7 @@ int b64_encode(const unsigned char* aInput, int aInputLen, unsigned char* aOutpu b64_encode(&aInput[i*3], aInputLen % 3, &aOutput[i*4], aOutputLen - (i*4)); } } + + return ((aInputLen+2)/3)*4; } From 70dc5ee44ea9a99d07ae45bbaed85705126bb76b Mon Sep 17 00:00:00 2001 From: Sandeep Mistry Date: Fri, 17 Jun 2016 16:37:32 -0400 Subject: [PATCH 021/125] Remove unimplemented finishRequest from header --- HttpClient.h | 5 ----- 1 file changed, 5 deletions(-) diff --git a/HttpClient.h b/HttpClient.h index e03b3ed..ab88408 100644 --- a/HttpClient.h +++ b/HttpClient.h @@ -148,11 +148,6 @@ class HttpClient : public Client void sendBasicAuth(const String& aUser, const String& aPassword) { sendBasicAuth(aUser.c_str(), aPassword.c_str()); } - /** Finish sending the HTTP request. This basically just sends the blank - line to signify the end of the request - */ - void finishRequest(); - /** Get the HTTP status code contained in the response. For example, 200 for successful request, 404 for file not found, etc. */ From 0bcba913fd441ec56cd1611c9db1b6550f476436 Mon Sep 17 00:00:00 2001 From: Sandeep Mistry Date: Fri, 17 Jun 2016 16:38:09 -0400 Subject: [PATCH 022/125] Move storing of initialState in startRequest to the start --- HttpClient.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/HttpClient.cpp b/HttpClient.cpp index 3dcf252..db0879d 100644 --- a/HttpClient.cpp +++ b/HttpClient.cpp @@ -56,6 +56,8 @@ void HttpClient::beginRequest() int HttpClient::startRequest(const char* aURLPath, const char* aHttpMethod) { + tHttpState initialState = iState; + if (!iConnectionClose) { flushClientRx(); @@ -63,7 +65,6 @@ int HttpClient::startRequest(const char* aURLPath, const char* aHttpMethod) resetState(); } - tHttpState initialState = iState; if ((eIdle != iState) && (eRequestStarted != iState)) { return HTTP_ERROR_API; From 8012c8dace3e629cf160f19197fa1acbb138fb90 Mon Sep 17 00:00:00 2001 From: Sandeep Mistry Date: Fri, 17 Jun 2016 16:49:52 -0400 Subject: [PATCH 023/125] Add noDefaultRequestHeaders API to turn off sending default headers --- HttpClient.cpp | 32 ++++++++++++++++++++------------ HttpClient.h | 5 +++++ keywords.txt | 1 + 3 files changed, 26 insertions(+), 12 deletions(-) diff --git a/HttpClient.cpp b/HttpClient.cpp index db0879d..9176108 100644 --- a/HttpClient.cpp +++ b/HttpClient.cpp @@ -11,7 +11,7 @@ const char* HttpClient::kContentLengthPrefix = HTTP_HEADER_CONTENT_LENGTH ": "; HttpClient::HttpClient(Client& aClient, const char* aServerName, uint16_t aServerPort) : iClient(&aClient), iServerName(aServerName), iServerAddress(), iServerPort(aServerPort), - iConnectionClose(true) + iConnectionClose(true), iSendDefaultRequestHeaders(true) { resetState(); } @@ -23,7 +23,7 @@ HttpClient::HttpClient(Client& aClient, const String& aServerName, uint16_t aSer HttpClient::HttpClient(Client& aClient, const IPAddress& aServerAddress, uint16_t aServerPort) : iClient(&aClient), iServerName(NULL), iServerAddress(aServerAddress), iServerPort(aServerPort), - iConnectionClose(true) + iConnectionClose(true), iSendDefaultRequestHeaders(true) { resetState(); } @@ -49,6 +49,11 @@ void HttpClient::connectionKeepAlive() iConnectionClose = false; } +void HttpClient::noDefaultRequestHeaders() +{ + iSendDefaultRequestHeaders = false; +} + void HttpClient::beginRequest() { iState = eRequestStarted; @@ -120,20 +125,23 @@ int HttpClient::sendInitialHeaders(const char* aURLPath, const char* aHttpMethod iClient->print(aURLPath); iClient->println(" HTTP/1.1"); - // The host header, if required - if (iServerName) + if (iSendDefaultRequestHeaders) { - iClient->print("Host: "); - iClient->print(iServerName); - if (iServerPort != kHttpPort) + // The host header, if required + if (iServerName) { - iClient->print(":"); - iClient->print(iServerPort); + iClient->print("Host: "); + iClient->print(iServerName); + if (iServerPort != kHttpPort) + { + iClient->print(":"); + iClient->print(iServerPort); + } + iClient->println(); } - iClient->println(); + // And user-agent string + sendHeader(HTTP_HEADER_USER_AGENT, kUserAgent); } - // And user-agent string - sendHeader(HTTP_HEADER_USER_AGENT, kUserAgent); if (iConnectionClose) { diff --git a/HttpClient.h b/HttpClient.h index ab88408..2394c46 100644 --- a/HttpClient.h +++ b/HttpClient.h @@ -52,6 +52,10 @@ class HttpClient : public Client */ void connectionKeepAlive(); + /** Disables sending the default request headers (Host and User Agent) + */ + void noDefaultRequestHeaders(); + /** Start a more complex request. Use this when you need to send additional headers in the request, but you will also need to call endRequest() when you are finished. @@ -293,6 +297,7 @@ class HttpClient : public Client const char* iContentLengthPtr; uint32_t iHttpResponseTimeout; bool iConnectionClose; + bool iSendDefaultRequestHeaders; String iHeaderLine; }; diff --git a/keywords.txt b/keywords.txt index 16c8a2d..dc1140e 100644 --- a/keywords.txt +++ b/keywords.txt @@ -29,6 +29,7 @@ endOfBodyReached KEYWORD2 completed KEYWORD2 contentLength KEYWORD2 connectionKeepAlive KEYWORD2 +noDefaultRequestHeaders KEYWORD2 headerAvailable KEYWORD2 readHeaderName KEYWORD2 readHeaderValue KEYWORD2 From 9f0078440fe2c2137bef3c8ad181901e345404c4 Mon Sep 17 00:00:00 2001 From: Sandeep Mistry Date: Fri, 17 Jun 2016 16:50:43 -0400 Subject: [PATCH 024/125] Move new API definitions down a bit in header --- HttpClient.h | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/HttpClient.h b/HttpClient.h index 2394c46..4b51ca3 100644 --- a/HttpClient.h +++ b/HttpClient.h @@ -48,14 +48,6 @@ class HttpClient : public Client HttpClient(Client& aClient, const String& aServerName, uint16_t aServerPort = kHttpPort); HttpClient(Client& aClient, const IPAddress& aServerAddress, uint16_t aServerPort = kHttpPort); - /** Enables connection keep-alive mode - */ - void connectionKeepAlive(); - - /** Disables sending the default request headers (Host and User Agent) - */ - void noDefaultRequestHeaders(); - /** Start a more complex request. Use this when you need to send additional headers in the request, but you will also need to call endRequest() when you are finished. @@ -215,6 +207,14 @@ class HttpClient : public Client */ int contentLength(); + /** Enables connection keep-alive mode + */ + void connectionKeepAlive(); + + /** Disables sending the default request headers (Host and User Agent) + */ + void noDefaultRequestHeaders(); + // Inherited from Print // Note: 1st call to these indicates the user is sending the body, so if need // Note: be we should finish the header first From 94a8723420b6e984835232dd46a6b9a3880b0aa3 Mon Sep 17 00:00:00 2001 From: Sandeep Mistry Date: Fri, 17 Jun 2016 17:29:47 -0400 Subject: [PATCH 025/125] Make WiFiClient and HttpClient global var in example --- examples/SimpleHttpExample/SimpleHttpExample.ino | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/SimpleHttpExample/SimpleHttpExample.ino b/examples/SimpleHttpExample/SimpleHttpExample.ino index 550015a..a6aa6b6 100644 --- a/examples/SimpleHttpExample/SimpleHttpExample.ino +++ b/examples/SimpleHttpExample/SimpleHttpExample.ino @@ -25,6 +25,9 @@ const int kNetworkTimeout = 30*1000; // Number of milliseconds to wait if no data is available before trying again const int kNetworkDelay = 1000; +WiFiClient c; +HttpClient http(c, kHostname); + void setup() { //Initialize serial and wait for port to open: @@ -50,9 +53,6 @@ void loop() { int err =0; - WiFiClient c; - HttpClient http(c, kHostname); - err = http.get(kPath); if (err == 0) { From fc3e6c6fe740eaa48e79aa6d08a45fbdfcb0f07d Mon Sep 17 00:00:00 2001 From: Sandeep Mistry Date: Tue, 21 Jun 2016 14:10:43 -0400 Subject: [PATCH 026/125] Initial port of examples from RestClient --- examples/DweetGet/DweetGet.ino | 121 ++++++++++++++++++ examples/DweetGet/config.h | 2 + examples/DweetPost/DweetPost.ino | 93 ++++++++++++++ examples/DweetPost/config.h | 2 + examples/HueBlink/HueBlink.ino | 110 ++++++++++++++++ examples/HueBlink/config.h | 2 + examples/SimpleDelete/SimpleDelete.ino | 80 ++++++++++++ examples/SimpleDelete/config.h | 2 + examples/SimpleGet/SimpleGet.ino | 74 +++++++++++ examples/SimpleGet/config.h | 2 + examples/SimplePost/SimplePost.ino | 80 ++++++++++++ examples/SimplePost/config.h | 2 + examples/SimplePut/SimplePut.ino | 80 ++++++++++++ examples/SimplePut/config.h | 2 + examples/node_test_server/getPostPutDelete.js | 42 ++++++ examples/node_test_server/package.json | 16 +++ 16 files changed, 710 insertions(+) create mode 100644 examples/DweetGet/DweetGet.ino create mode 100644 examples/DweetGet/config.h create mode 100644 examples/DweetPost/DweetPost.ino create mode 100644 examples/DweetPost/config.h create mode 100644 examples/HueBlink/HueBlink.ino create mode 100644 examples/HueBlink/config.h create mode 100644 examples/SimpleDelete/SimpleDelete.ino create mode 100644 examples/SimpleDelete/config.h create mode 100644 examples/SimpleGet/SimpleGet.ino create mode 100644 examples/SimpleGet/config.h create mode 100644 examples/SimplePost/SimplePost.ino create mode 100644 examples/SimplePost/config.h create mode 100644 examples/SimplePut/SimplePut.ino create mode 100644 examples/SimplePut/config.h create mode 100644 examples/node_test_server/getPostPutDelete.js create mode 100644 examples/node_test_server/package.json diff --git a/examples/DweetGet/DweetGet.ino b/examples/DweetGet/DweetGet.ino new file mode 100644 index 0000000..11d3daa --- /dev/null +++ b/examples/DweetGet/DweetGet.ino @@ -0,0 +1,121 @@ +/* + Dweet.io GET client for ArduinoHttpClient library + Connects to dweet.io once every ten seconds, + sends a GET request and a request body. Uses SSL + + Shows how to use Strings to assemble path and parse content + from response. dweet.io expects: + https://dweet.io/get/latest/dweet/for/thingName + + For more on dweet.io, see https://dweet.io/play/ + + note: WiFi SSID and password are stored in config.h file. + If it is not present, add a new tab, call it "config.h" + and add the following variables: + char ssid[] = "ssid"; // your network SSID (name) + char pass[] = "password"; // your network password + + created 15 Feb 2016 + updated 16 Feb 2016 + by Tom Igoe + + this example is in the public domain +*/ +#include +#include +#include "config.h" + +const char serverAddress[] = "dweet.io"; // server address +int port = 80; +String dweetName = "scandalous-cheese-hoarder"; // use your own thing name here + +WiFiClient wifi; +HttpClient client = HttpClient(wifi, serverAddress, port); +int status = WL_IDLE_STATUS; +int statusCode = 0; +int contentLength = 0; +String response; + +void setup() { + Serial.begin(9600); + while (!Serial); + while ( status != WL_CONNECTED) { + Serial.print("Attempting to connect to Network named: "); + Serial.println(ssid); // print the network name (SSID); + + // Connect to WPA/WPA2 network: + status = WiFi.begin(ssid, pass); + } + + // print the SSID of the network you're attached to: + Serial.print("SSID: "); + Serial.println(WiFi.SSID()); + + // print your WiFi shield's IP address: + IPAddress ip = WiFi.localIP(); + Serial.print("IP Address: "); + Serial.println(ip); +} + +void loop() { + // assemble the path for the GET message: + String path = "/get/latest/dweet/for/" + dweetName; + + // send the GET request + Serial.println("making GET request"); + client.beginRequest(); + client.get(path); + client.endRequest(); + + // read the status code of the response + statusCode = client.responseStatusCode(); + Serial.print("Status code: "); + Serial.println(statusCode); + + // read the content length of the response + contentLength = client.contentLength(); + Serial.print("Content Length: "); + Serial.println(contentLength); + + // read the response body + response = ""; + response.reserve(contentLength); + while (client.available()) { + response += (char)client.read(); + } + + Serial.print("Response: "); + Serial.println(response); + + /* + Typical response is: + {"this":"succeeded", + "by":"getting", + "the":"dweets", + "with":[{"thing":"my-thing-name", + "created":"2016-02-16T05:10:36.589Z", + "content":{"sensorValue":456}}]} + + You want "content": numberValue + */ + // now parse the response looking for "content": + int labelStart = response.indexOf("content\":"); + // find the first { after "content": + int contentStart = response.indexOf("{", labelStart); + // find the following } and get what's between the braces: + int contentEnd = response.indexOf("}", labelStart); + String content = response.substring(contentStart + 1, contentEnd); + Serial.println(content); + + // now get the value after the colon, and convert to an int: + int valueStart = content.indexOf(":"); + String valueString = content.substring(valueStart + 1); + int number = valueString.toInt(); + Serial.print("Value string: "); + Serial.println(valueString); + Serial.print("Actual value: "); + Serial.println(number); + + Serial.println("Wait ten seconds\n"); + delay(10000); +} diff --git a/examples/DweetGet/config.h b/examples/DweetGet/config.h new file mode 100644 index 0000000..c263766 --- /dev/null +++ b/examples/DweetGet/config.h @@ -0,0 +1,2 @@ +char ssid[] = "ssid"; // your network SSID (name) +char pass[] = "password"; // your network password diff --git a/examples/DweetPost/DweetPost.ino b/examples/DweetPost/DweetPost.ino new file mode 100644 index 0000000..04b5f3e --- /dev/null +++ b/examples/DweetPost/DweetPost.ino @@ -0,0 +1,93 @@ +/* + Dweet.io POST client for ArduinoHttpClient library + Connects to dweet.io once every ten seconds, + sends a POST request and a request body. + + Shows how to use Strings to assemble path and body + + note: WiFi SSID and password are stored in config.h file. + If it is not present, add a new tab, call it "config.h" + and add the following variables: + char ssid[] = "ssid"; // your network SSID (name) + char pass[] = "password"; // your network password + + created 15 Feb 2016 + by Tom Igoe + + this example is in the public domain +*/ +#include +#include +#include "config.h" + +const char serverAddress[] = "dweet.io"; // server address +int port = 80; + +WiFiClient wifi; +HttpClient client = HttpClient(wifi, serverAddress, port); +int status = WL_IDLE_STATUS; +int statusCode = 0; +int contentLength = 0; +String response; + +void setup() { + Serial.begin(9600); + while(!Serial); + while ( status != WL_CONNECTED) { + Serial.print("Attempting to connect to Network named: "); + Serial.println(ssid); // print the network name (SSID); + + // Connect to WPA/WPA2 network: + status = WiFi.begin(ssid, pass); + } + + // print the SSID of the network you're attached to: + Serial.print("SSID: "); + Serial.println(WiFi.SSID()); + + // print your WiFi shield's IP address: + IPAddress ip = WiFi.localIP(); + Serial.print("IP Address: "); + Serial.println(ip); +} + +void loop() { + // assemble the path for the POST message: + String dweetName = "scandalous-cheese-hoarder"; + String path = "/dweet/for/" + dweetName; + + // assemble the body of the POST message: + int sensorValue = analogRead(A0); + String postData = "{\"sensorValue\":\""; + postData += sensorValue; + postData += "\"}"; + + Serial.println("making POST request"); + + // send the POST request + client.beginRequest(); + client.post(path); + client.sendHeader("Content-Type", "application/json"); + client.sendHeader("Content-Length", postData.length()); + client.endRequest(); + client.write((const byte*)postData.c_str(), postData.length()); + + // read the status code and content length of the response + statusCode = client.responseStatusCode(); + contentLength = client.contentLength(); + + // read the response body + response = ""; + response.reserve(contentLength); + while (client.available()) { + response += (char)client.read(); + } + + Serial.print("Status code: "); + Serial.println(statusCode); + Serial.print("Response: "); + Serial.println(response); + + Serial.println("Wait ten seconds\n"); + delay(10000); +} diff --git a/examples/DweetPost/config.h b/examples/DweetPost/config.h new file mode 100644 index 0000000..c263766 --- /dev/null +++ b/examples/DweetPost/config.h @@ -0,0 +1,2 @@ +char ssid[] = "ssid"; // your network SSID (name) +char pass[] = "password"; // your network password diff --git a/examples/HueBlink/HueBlink.ino b/examples/HueBlink/HueBlink.ino new file mode 100644 index 0000000..3d6a028 --- /dev/null +++ b/examples/HueBlink/HueBlink.ino @@ -0,0 +1,110 @@ +/* HueBlink example for ArduinoHttpClient library + + Uses ArduinoHttpClient library to control Philips Hue + For more on Hue developer API see http://developer.meethue.com + + To control a light, the Hue expects a HTTP PUT request to: + + http://hue.hub.address/api/hueUserName/lights/lightNumber/state + + The body of the PUT request looks like this: + {"on": true} or {"on":false} + + This example shows how to concatenate Strings to assemble the + PUT request and the body of the request. + + note: WiFi SSID and password are stored in config.h file. + If it is not present, add a new tab, call it "config.h" + and add the following variables: + char ssid[] = "ssid"; // your network SSID (name) + char pass[] = "password"; // your network password + + modified 15 Feb 2016 + by Tom Igoe (tigoe) to match new API +*/ + +#include +#include +#include +#include "config.h" + +int status = WL_IDLE_STATUS; // the Wifi radio's status +char hueHubIP[] = "192.168.0.3"; // IP address of the HUE bridge +String hueUserName = "huebridgeusername"; // hue bridge username + +// make a wifi instance and a HttpClient instance: +WiFiClient wifi; +HttpClient httpClient = HttpClient(wifi, hueHubIP); + + +void setup() { + //Initialize serial and wait for port to open: + Serial.begin(9600); + while (!Serial); // wait for serial port to connect. + + // attempt to connect to Wifi network: + while ( status != WL_CONNECTED) { + Serial.print("Attempting to connect to WPA SSID: "); + Serial.println(ssid); + // Connect to WPA/WPA2 network: + status = WiFi.begin(ssid, pass); + } + + // you're connected now, so print out the data: + Serial.print("You're connected to the network IP = "); + IPAddress ip = WiFi.localIP(); + Serial.println(ip); +} + +void loop() { + sendRequest(3, "on", "true"); // turn light on + delay(2000); // wait 2 seconds + sendRequest(3, "on", "false"); // turn light off + delay(2000); // wait 2 seconds +} + +void sendRequest(int light, String cmd, String value) { + // make a String for the HTTP request path: + String request = "/api/" + hueUserName; + request += "/lights/"; + request += light; + request += "/state/"; + + // make a string for the JSON command: + String hueCmd = "{\"" + cmd; + hueCmd += "\":"; + hueCmd += value; + hueCmd += "}"; + // see what you assembled to send: + Serial.print("PUT request to server: "); + Serial.println(request); + Serial.print("JSON command to server: "); + + // make the PUT request to the hub: + httpClient.beginRequest(); + httpClient.put(request); + httpClient.sendHeader("Content-Type", "application/json"); + httpClient.sendHeader("Content-Length", hueCmd.length()); + httpClient.endRequest(); + httpClient.write((const byte*)hueCmd.c_str(), hueCmd.length()); + + // read the status code and content length of the response + int statusCode = httpClient.responseStatusCode(); + int contentLength = httpClient.contentLength(); + + // read the response body + String response = ""; + response.reserve(contentLength); + while (httpClient.available()) { + response += (char)httpClient.read(); + } + + Serial.println(hueCmd); + Serial.print("Status code from server: "); + Serial.println(statusCode); + Serial.print("Server response: "); + Serial.println(response); + Serial.println(); +} + + diff --git a/examples/HueBlink/config.h b/examples/HueBlink/config.h new file mode 100644 index 0000000..c263766 --- /dev/null +++ b/examples/HueBlink/config.h @@ -0,0 +1,2 @@ +char ssid[] = "ssid"; // your network SSID (name) +char pass[] = "password"; // your network password diff --git a/examples/SimpleDelete/SimpleDelete.ino b/examples/SimpleDelete/SimpleDelete.ino new file mode 100644 index 0000000..591f4de --- /dev/null +++ b/examples/SimpleDelete/SimpleDelete.ino @@ -0,0 +1,80 @@ +/* + Simple DELETE client for ArduinoHttpClient library + Connects to server once every five seconds, sends a DELETE request + and a request body + + note: WiFi SSID and password are stored in config.h file. + If it is not present, add a new tab, call it "config.h" + and add the following variables: + char ssid[] = "ssid"; // your network SSID (name) + char pass[] = "password"; // your network password + + created 14 Feb 2016 + by Tom Igoe + + this example is in the public domain + */ +#include +#include +#include "config.h" + +char serverAddress[] = "192.168.0.3"; // server address +int port = 8080; + +WiFiClient wifi; +HttpClient client = HttpClient(wifi, serverAddress, port); +int status = WL_IDLE_STATUS; +String response; +int statusCode = 0; +int contentLength = 0; + +void setup() { + Serial.begin(9600); + while ( status != WL_CONNECTED) { + Serial.print("Attempting to connect to Network named: "); + Serial.println(ssid); // print the network name (SSID); + + // Connect to WPA/WPA2 network: + status = WiFi.begin(ssid, pass); + } + + // print the SSID of the network you're attached to: + Serial.print("SSID: "); + Serial.println(WiFi.SSID()); + + // print your WiFi shield's IP address: + IPAddress ip = WiFi.localIP(); + Serial.print("IP Address: "); + Serial.println(ip); +} + +void loop() { + Serial.println("making DELETE request"); + String delData = "name=light&age=46"; + + client.beginRequest(); + client.startRequest("/", HTTP_METHOD_DELETE); + client.sendHeader("Content-Type", "application/x-www-form-urlencoded"); + client.sendHeader("Content-Length", delData.length()); + client.endRequest(); + client.write((const byte*)delData.c_str(), delData.length()); + + // read the status code and content length of the response + statusCode = client.responseStatusCode(); + contentLength = client.contentLength(); + + // read the response body + response = ""; + response.reserve(contentLength); + while (client.available()) { + response += (char)client.read(); + } + + Serial.print("Status code: "); + Serial.println(statusCode); + Serial.print("Response: "); + Serial.println(response); + + Serial.println("Wait five seconds"); + delay(5000); +} diff --git a/examples/SimpleDelete/config.h b/examples/SimpleDelete/config.h new file mode 100644 index 0000000..c263766 --- /dev/null +++ b/examples/SimpleDelete/config.h @@ -0,0 +1,2 @@ +char ssid[] = "ssid"; // your network SSID (name) +char pass[] = "password"; // your network password diff --git a/examples/SimpleGet/SimpleGet.ino b/examples/SimpleGet/SimpleGet.ino new file mode 100644 index 0000000..86cdb9d --- /dev/null +++ b/examples/SimpleGet/SimpleGet.ino @@ -0,0 +1,74 @@ +/* + Simple GET client for ArduinoHttpClient library + Connects to server once every five seconds, sends a GET request + + note: WiFi SSID and password are stored in config.h file. + If it is not present, add a new tab, call it "config.h" + and add the following variables: + char ssid[] = "ssid"; // your network SSID (name) + char pass[] = "password"; // your network password + + created 14 Feb 2016 + by Tom Igoe + + this example is in the public domain + */ +#include +#include +#include "config.h" + +char serverAddress[] = "192.168.0.3"; // server address +int port = 8080; + +WiFiClient wifi; +HttpClient client = HttpClient(wifi, serverAddress, port); +int status = WL_IDLE_STATUS; +String response; +int statusCode = 0; +int contentLength = 0; + +void setup() { + Serial.begin(9600); + while ( status != WL_CONNECTED) { + Serial.print("Attempting to connect to Network named: "); + Serial.println(ssid); // print the network name (SSID); + + // Connect to WPA/WPA2 network: + status = WiFi.begin(ssid, pass); + } + + // print the SSID of the network you're attached to: + Serial.print("SSID: "); + Serial.println(WiFi.SSID()); + + // print your WiFi shield's IP address: + IPAddress ip = WiFi.localIP(); + Serial.print("IP Address: "); + Serial.println(ip); +} + +void loop() { + Serial.println("making GET request"); + + // read the status code and content length of the response + client.beginRequest(); + client.get("/"); + client.endRequest(); + + statusCode = client.responseStatusCode(); + contentLength = client.contentLength(); + + // read the response body + response = ""; + response.reserve(contentLength); + while (client.available()) { + response += (char)client.read(); + } + + Serial.print("Status code: "); + Serial.println(statusCode); + Serial.print("Response: "); + Serial.println(response); + Serial.println("Wait five seconds"); + delay(5000); +} diff --git a/examples/SimpleGet/config.h b/examples/SimpleGet/config.h new file mode 100644 index 0000000..c263766 --- /dev/null +++ b/examples/SimpleGet/config.h @@ -0,0 +1,2 @@ +char ssid[] = "ssid"; // your network SSID (name) +char pass[] = "password"; // your network password diff --git a/examples/SimplePost/SimplePost.ino b/examples/SimplePost/SimplePost.ino new file mode 100644 index 0000000..46982ca --- /dev/null +++ b/examples/SimplePost/SimplePost.ino @@ -0,0 +1,80 @@ +/* + Simple POST client for ArduinoHttpClient library + Connects to server once every five seconds, sends a POST request + and a request body + + note: WiFi SSID and password are stored in config.h file. + If it is not present, add a new tab, call it "config.h" + and add the following variables: + char ssid[] = "ssid"; // your network SSID (name) + char pass[] = "password"; // your network password + + created 14 Feb 2016 + by Tom Igoe + + this example is in the public domain + */ +#include +#include +#include "config.h" + +char serverAddress[] = "192.168.0.3"; // server address +int port = 8080; + +WiFiClient wifi; +HttpClient client = HttpClient(wifi, serverAddress, port); +int status = WL_IDLE_STATUS; +String response; +int statusCode = 0; +int contentLength = 0; + +void setup() { + Serial.begin(9600); + while ( status != WL_CONNECTED) { + Serial.print("Attempting to connect to Network named: "); + Serial.println(ssid); // print the network name (SSID); + + // Connect to WPA/WPA2 network: + status = WiFi.begin(ssid, pass); + } + + // print the SSID of the network you're attached to: + Serial.print("SSID: "); + Serial.println(WiFi.SSID()); + + // print your WiFi shield's IP address: + IPAddress ip = WiFi.localIP(); + Serial.print("IP Address: "); + Serial.println(ip); +} + +void loop() { + Serial.println("making POST request"); + String postData = "name=Alice&age=12"; + + client.beginRequest(); + client.post("/"); + client.sendHeader("Content-Type", "application/x-www-form-urlencoded"); + client.sendHeader("Content-Length", postData.length()); + client.endRequest(); + client.write((const byte*)postData.c_str(), postData.length()); + + // read the status code and content length of the response + statusCode = client.responseStatusCode(); + contentLength = client.contentLength(); + + // read the response body + response = ""; + response.reserve(contentLength); + while (client.available()) { + response += (char)client.read(); + } + + Serial.print("Status code: "); + Serial.println(statusCode); + Serial.print("Response: "); + Serial.println(response); + + Serial.println("Wait five seconds"); + delay(5000); +} diff --git a/examples/SimplePost/config.h b/examples/SimplePost/config.h new file mode 100644 index 0000000..c263766 --- /dev/null +++ b/examples/SimplePost/config.h @@ -0,0 +1,2 @@ +char ssid[] = "ssid"; // your network SSID (name) +char pass[] = "password"; // your network password diff --git a/examples/SimplePut/SimplePut.ino b/examples/SimplePut/SimplePut.ino new file mode 100644 index 0000000..611f55c --- /dev/null +++ b/examples/SimplePut/SimplePut.ino @@ -0,0 +1,80 @@ +/* + Simple PUT client for ArduinoHttpClient library + Connects to server once every five seconds, sends a PUT request + and a request body + + note: WiFi SSID and password are stored in config.h file. + If it is not present, add a new tab, call it "config.h" + and add the following variables: + char ssid[] = "ssid"; // your network SSID (name) + char pass[] = "password"; // your network password + + created 14 Feb 2016 + by Tom Igoe + + this example is in the public domain + */ +#include +#include +#include "config.h" + +char serverAddress[] = "192.168.0.3"; // server address +int port = 8080; + +WiFiClient wifi; +HttpClient client = HttpClient(wifi, serverAddress, port); +int status = WL_IDLE_STATUS; +String response; +int statusCode = 0; +int contentLength = 0; + +void setup() { + Serial.begin(9600); + while ( status != WL_CONNECTED) { + Serial.print("Attempting to connect to Network named: "); + Serial.println(ssid); // print the network name (SSID); + + // Connect to WPA/WPA2 network: + status = WiFi.begin(ssid, pass); + } + + // print the SSID of the network you're attached to: + Serial.print("SSID: "); + Serial.println(WiFi.SSID()); + + // print your WiFi shield's IP address: + IPAddress ip = WiFi.localIP(); + Serial.print("IP Address: "); + Serial.println(ip); +} + +void loop() { + Serial.println("making PUT request"); + String putData = "name=light&age=46"; + + client.beginRequest(); + client.put("/"); + client.sendHeader("Content-Type", "application/x-www-form-urlencoded"); + client.sendHeader("Content-Length", putData.length()); + client.endRequest(); + client.write((const byte*)putData.c_str(), putData.length()); + + // read the status code and content length of the response + statusCode = client.responseStatusCode(); + contentLength = client.contentLength(); + + // read the response body + response = ""; + response.reserve(contentLength); + while (client.available()) { + response += (char)client.read(); + } + + Serial.print("Status code: "); + Serial.println(statusCode); + Serial.print("Response: "); + Serial.println(response); + + Serial.println("Wait five seconds"); + delay(5000); +} diff --git a/examples/SimplePut/config.h b/examples/SimplePut/config.h new file mode 100644 index 0000000..c263766 --- /dev/null +++ b/examples/SimplePut/config.h @@ -0,0 +1,2 @@ +char ssid[] = "ssid"; // your network SSID (name) +char pass[] = "password"; // your network password diff --git a/examples/node_test_server/getPostPutDelete.js b/examples/node_test_server/getPostPutDelete.js new file mode 100644 index 0000000..ec58f82 --- /dev/null +++ b/examples/node_test_server/getPostPutDelete.js @@ -0,0 +1,42 @@ +/* + Express.js GET/POST example + Shows how handle GET, POST, PUT, DELETE + in Express.js 4.0 + + created 14 Feb 2016 + by Tom Igoe +*/ + +var express = require('express'); // include express.js +var app = express(); // a local instance of it +var bodyParser = require('body-parser'); // include body-parser + +// you need a body parser: +app.use(bodyParser.urlencoded({extended: false})); // for application/x-www-form-urlencoded + +// this runs after the server successfully starts: +function serverStart() { + var port = server.address().port; + console.log('Server listening on port '+ port); +} + +// this is the POST handler: +app.all('/*', function (request, response) { + console.log('Got a ' + request.method + ' request'); + // the parameters of a GET request are passed in + // request.body. Pass that to formatResponse() + // for formatting: + console.log(request.headers); + if (request.method == 'GET') { + console.log(request.query); + } else { + console.log(request.body); + } + + // send the response: + response.send('OK'); + response.end(); +}); + +// start the server: +var server = app.listen(8080, serverStart); diff --git a/examples/node_test_server/package.json b/examples/node_test_server/package.json new file mode 100644 index 0000000..d6fb7cc --- /dev/null +++ b/examples/node_test_server/package.json @@ -0,0 +1,16 @@ +{ + "name": "node_test_server", + "version": "0.0.1", + "author": { + "name":"Tom Igoe" + }, + "dependencies": { + "express": ">=4.0.0", + "body-parser" : ">=1.11.0", + "multer" : "*" + }, + "engines": { + "node": "0.10.x", + "npm": "1.3.x" + } +} From 248841e827b61218edac9e9b43d38bf8aa229dfe Mon Sep 17 00:00:00 2001 From: Sandeep Mistry Date: Tue, 21 Jun 2016 14:11:39 -0400 Subject: [PATCH 027/125] Add .gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..89d225a --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.development +examples/node_test_server/node_modules/ From c3023b25ee89d736862ed0525d98834f227c398f Mon Sep 17 00:00:00 2001 From: Sandeep Mistry Date: Wed, 22 Jun 2016 12:10:47 -0400 Subject: [PATCH 028/125] Add optional content type, content length and body parameters to post, put, and startRequest Also, flush client RX data in start request, if state is ready body. --- HttpClient.cpp | 103 +++++++++++++++++++++++++---- HttpClient.h | 52 ++++++++++----- examples/DweetGet/DweetGet.ino | 2 - examples/DweetPost/DweetPost.ino | 9 +-- examples/SimpleGet/SimpleGet.ino | 5 +- examples/SimplePost/SimplePost.ino | 8 +-- examples/SimplePut/SimplePut.ino | 8 +-- 7 files changed, 134 insertions(+), 53 deletions(-) diff --git a/HttpClient.cpp b/HttpClient.cpp index 9176108..72c471f 100644 --- a/HttpClient.cpp +++ b/HttpClient.cpp @@ -59,17 +59,18 @@ void HttpClient::beginRequest() iState = eRequestStarted; } -int HttpClient::startRequest(const char* aURLPath, const char* aHttpMethod) +int HttpClient::startRequest(const char* aURLPath, const char* aHttpMethod, + const char* aContentType, int aContentLength, const byte aBody[]) { - tHttpState initialState = iState; - - if (!iConnectionClose) + if (iState == eReadingBody) { flushClientRx(); resetState(); } + tHttpState initialState = iState; + if ((eIdle != iState) && (eRequestStarted != iState)) { return HTTP_ERROR_API; @@ -104,12 +105,31 @@ int HttpClient::startRequest(const char* aURLPath, const char* aHttpMethod) // Now we're connected, send the first part of the request int ret = sendInitialHeaders(aURLPath, aHttpMethod); - if ((initialState == eIdle) && (HTTP_SUCCESS == ret)) + + if (HTTP_SUCCESS == ret) { - // This was a simple version of the API, so terminate the headers now - finishHeaders(); + if (aContentType) + { + sendHeader(HTTP_HEADER_CONTENT_TYPE, aContentType); + } + + if (aContentLength > 0) + { + sendHeader(HTTP_HEADER_CONTENT_LENGTH, aContentLength); + } + + if ((initialState == eIdle)) + { + // This was a simple version of the API, so terminate the headers now + finishHeaders(); + + if (aBody && aContentLength > 0) + { + write(aBody, aContentLength); + } + } + // else we'll call it in endRequest or in the first call to print, etc. } - // else we'll call it in endRequest or in the first call to print, etc. return ret; } @@ -231,12 +251,9 @@ void HttpClient::finishHeaders() void HttpClient::flushClientRx() { - if (iClient->connected()) + while (iClient->available()) { - while (iClient->available()) - { - iClient->read(); - } + iClient->read(); } } @@ -250,6 +267,66 @@ void HttpClient::endRequest() // else the end of headers has already been sent, so nothing to do here } +int HttpClient::get(const char* aURLPath) +{ + return startRequest(aURLPath, HTTP_METHOD_GET); +} + +int HttpClient::get(const String& aURLPath) +{ + return get(aURLPath.c_str()); +} + +int HttpClient::post(const char* aURLPath) +{ + return startRequest(aURLPath, HTTP_METHOD_POST); +} + +int HttpClient::post(const String& aURLPath) +{ + return post(aURLPath.c_str()); +} + +int HttpClient::post(const char* aURLPath, const char* aContentType, const char* aBody) +{ + return post(aURLPath, aContentType, strlen(aBody), (const byte*)aBody); +} + +int HttpClient::post(const String& aURLPath, const String& aContentType, const String& aBody) +{ + return post(aURLPath.c_str(), aContentType.c_str(), aBody.length(), (const byte*)aBody.c_str()); +} + +int HttpClient::post(const char* aURLPath, const char* aContentType, int aContentLength, const byte aBody[]) +{ + return startRequest(aURLPath, HTTP_METHOD_POST, aContentType, aContentLength, aBody); +} + +int HttpClient::put(const char* aURLPath) +{ + return startRequest(aURLPath, HTTP_METHOD_PUT); +} + +int HttpClient::put(const String& aURLPath) +{ + return put(aURLPath.c_str()); +} + +int HttpClient::put(const char* aURLPath, const char* aContentType, const char* aBody) +{ + return put(aURLPath, aContentType, strlen(aBody), (const byte*)aBody); +} + +int HttpClient::put(const String& aURLPath, const String& aContentType, const String& aBody) +{ + return put(aURLPath.c_str(), aContentType.c_str(), aBody.length(), (const byte*)aBody.c_str()); +} + +int HttpClient::put(const char* aURLPath, const char* aContentType, int aContentLength, const byte aBody[]) +{ + return startRequest(aURLPath, HTTP_METHOD_PUT, aContentType, aContentLength, aBody); +} + int HttpClient::responseStatusCode() { if (iState < eRequestSent) diff --git a/HttpClient.h b/HttpClient.h index 4b51ca3..06af847 100644 --- a/HttpClient.h +++ b/HttpClient.h @@ -31,6 +31,7 @@ static const int HTTP_ERROR_INVALID_RESPONSE =-4; #define HTTP_METHOD_PUT "PUT" #define HTTP_METHOD_DELETE "DELETE" #define HTTP_HEADER_CONTENT_LENGTH "Content-Length" +#define HTTP_HEADER_CONTENT_TYPE "Content-Type" #define HTTP_HEADER_CONNECTION "Connection" #define HTTP_HEADER_USER_AGENT "User-Agent" @@ -64,39 +65,58 @@ class HttpClient : public Client @param aURLPath Url to request @return 0 if successful, else error */ - int get(const char* aURLPath) - { return startRequest(aURLPath, HTTP_METHOD_GET); } - - int get(const String& aURLPath) - { return get(aURLPath.c_str()); } + int get(const char* aURLPath); + int get(const String& aURLPath); /** Connect to the server and start to send a POST request. @param aURLPath Url to request @return 0 if successful, else error */ - int post(const char* aURLPath) - { return startRequest(aURLPath, HTTP_METHOD_POST); } + int post(const char* aURLPath); + int post(const String& aURLPath); - int post(const String& aURLPath) - { return post(aURLPath.c_str()); } + /** Connect to the server and start to send a POST request + with body and content type + @param aURLPath Url to request + @param aContentType Content type of request body + @param aBody Body of the request + @return 0 if successful, else error + */ + int post(const char* aURLPath, const char* aContentType, const char* aBody); + int post(const String& aURLPath, const String& aContentType, const String& aBody); + int post(const char* aURLPath, const char* aContentType, int aContentLength, const byte aBody[]); /** Connect to the server and start to send a PUT request. @param aURLPath Url to request @return 0 if successful, else error */ - int put(const char* aURLPath) - { return startRequest(aURLPath, HTTP_METHOD_PUT); } + int put(const char* aURLPath); + int put(const String& aURLPath); - int put(const String& aURLPath) - { return put(aURLPath.c_str()); } + /** Connect to the server and start to send a PUT request + with body and content type + @param aURLPath Url to request + @param aContentType Content type of request body + @param aBody Body of the request + @return 0 if successful, else error + */ + int put(const char* aURLPath, const char* aContentType, const char* aBody); + int put(const String& aURLPath, const String& aContentType, const String& aBody); + int put(const char* aURLPath, const char* aContentType, int aContentLength, const byte aBody[]); /** Connect to the server and start to send the request. - @param aURLPath Url to request - @param aHttpMethod Type of HTTP request to make, e.g. "GET", "POST", etc. + @param aURLPath Url to request + @param aHttpMethod Type of HTTP request to make, e.g. "GET", "POST", etc. + @param aContentType Content type of request body (optional) + @param aContentLength Length of request body (optional) + @param aBody Body of request (optional) @return 0 if successful, else error */ int startRequest(const char* aURLPath, - const char* aHttpMethod); + const char* aHttpMethod, + const char* aContentType = NULL, + int aContentLength = -1, + const byte aBody[] = NULL); /** Send an additional header line. This can only be called in between the calls to startRequest and finishRequest. diff --git a/examples/DweetGet/DweetGet.ino b/examples/DweetGet/DweetGet.ino index 11d3daa..7ab952f 100644 --- a/examples/DweetGet/DweetGet.ino +++ b/examples/DweetGet/DweetGet.ino @@ -63,9 +63,7 @@ void loop() { // send the GET request Serial.println("making GET request"); - client.beginRequest(); client.get(path); - client.endRequest(); // read the status code of the response statusCode = client.responseStatusCode(); diff --git a/examples/DweetPost/DweetPost.ino b/examples/DweetPost/DweetPost.ino index 04b5f3e..d2f0582 100644 --- a/examples/DweetPost/DweetPost.ino +++ b/examples/DweetPost/DweetPost.ino @@ -56,6 +56,8 @@ void loop() { String dweetName = "scandalous-cheese-hoarder"; String path = "/dweet/for/" + dweetName; + String contentType = "application/json"; + // assemble the body of the POST message: int sensorValue = analogRead(A0); String postData = "{\"sensorValue\":\""; @@ -65,12 +67,7 @@ void loop() { Serial.println("making POST request"); // send the POST request - client.beginRequest(); - client.post(path); - client.sendHeader("Content-Type", "application/json"); - client.sendHeader("Content-Length", postData.length()); - client.endRequest(); - client.write((const byte*)postData.c_str(), postData.length()); + client.post(path, contentType, postData); // read the status code and content length of the response statusCode = client.responseStatusCode(); diff --git a/examples/SimpleGet/SimpleGet.ino b/examples/SimpleGet/SimpleGet.ino index 86cdb9d..78c3062 100644 --- a/examples/SimpleGet/SimpleGet.ino +++ b/examples/SimpleGet/SimpleGet.ino @@ -49,12 +49,9 @@ void setup() { void loop() { Serial.println("making GET request"); - - // read the status code and content length of the response - client.beginRequest(); client.get("/"); - client.endRequest(); + // read the status code and content length of the response statusCode = client.responseStatusCode(); contentLength = client.contentLength(); diff --git a/examples/SimplePost/SimplePost.ino b/examples/SimplePost/SimplePost.ino index 46982ca..341205d 100644 --- a/examples/SimplePost/SimplePost.ino +++ b/examples/SimplePost/SimplePost.ino @@ -50,14 +50,10 @@ void setup() { void loop() { Serial.println("making POST request"); + String contentType = "application/x-www-form-urlencoded"; String postData = "name=Alice&age=12"; - client.beginRequest(); - client.post("/"); - client.sendHeader("Content-Type", "application/x-www-form-urlencoded"); - client.sendHeader("Content-Length", postData.length()); - client.endRequest(); - client.write((const byte*)postData.c_str(), postData.length()); + client.post("/", contentType, postData); // read the status code and content length of the response statusCode = client.responseStatusCode(); diff --git a/examples/SimplePut/SimplePut.ino b/examples/SimplePut/SimplePut.ino index 611f55c..dfca20c 100644 --- a/examples/SimplePut/SimplePut.ino +++ b/examples/SimplePut/SimplePut.ino @@ -50,14 +50,10 @@ void setup() { void loop() { Serial.println("making PUT request"); + String contentType = "application/x-www-form-urlencoded"; String putData = "name=light&age=46"; - client.beginRequest(); - client.put("/"); - client.sendHeader("Content-Type", "application/x-www-form-urlencoded"); - client.sendHeader("Content-Length", putData.length()); - client.endRequest(); - client.write((const byte*)putData.c_str(), putData.length()); + client.put("/", contentType, putData); // read the status code and content length of the response statusCode = client.responseStatusCode(); From bdc528173375f65f8c575a8ea394d435bf1f4696 Mon Sep 17 00:00:00 2001 From: Sandeep Mistry Date: Wed, 22 Jun 2016 12:16:12 -0400 Subject: [PATCH 029/125] Add new del API for HTTP DELETE method --- HttpClient.cpp | 25 +++++++++++++++++++++++++ HttpClient.h | 18 ++++++++++++++++++ examples/SimpleDelete/SimpleDelete.ino | 8 ++------ 3 files changed, 45 insertions(+), 6 deletions(-) diff --git a/HttpClient.cpp b/HttpClient.cpp index 72c471f..8545aaa 100644 --- a/HttpClient.cpp +++ b/HttpClient.cpp @@ -327,6 +327,31 @@ int HttpClient::put(const char* aURLPath, const char* aContentType, int aContent return startRequest(aURLPath, HTTP_METHOD_PUT, aContentType, aContentLength, aBody); } +int HttpClient::del(const char* aURLPath) +{ + return startRequest(aURLPath, HTTP_METHOD_DELETE); +} + +int HttpClient::del(const String& aURLPath) +{ + return del(aURLPath.c_str()); +} + +int HttpClient::del(const char* aURLPath, const char* aContentType, const char* aBody) +{ + return del(aURLPath, aContentType, strlen(aBody), (const byte*)aBody); +} + +int HttpClient::del(const String& aURLPath, const String& aContentType, const String& aBody) +{ + return del(aURLPath.c_str(), aContentType.c_str(), aBody.length(), (const byte*)aBody.c_str()); +} + +int HttpClient::del(const char* aURLPath, const char* aContentType, int aContentLength, const byte aBody[]) +{ + return startRequest(aURLPath, HTTP_METHOD_DELETE, aContentType, aContentLength, aBody); +} + int HttpClient::responseStatusCode() { if (iState < eRequestSent) diff --git a/HttpClient.h b/HttpClient.h index 06af847..639d7a5 100644 --- a/HttpClient.h +++ b/HttpClient.h @@ -104,6 +104,24 @@ class HttpClient : public Client int put(const String& aURLPath, const String& aContentType, const String& aBody); int put(const char* aURLPath, const char* aContentType, int aContentLength, const byte aBody[]); + /** Connect to the server and start to send a DELETE request. + @param aURLPath Url to request + @return 0 if successful, else error + */ + int del(const char* aURLPath); + int del(const String& aURLPath); + + /** Connect to the server and start to send a DELETE request + with body and content type + @param aURLPath Url to request + @param aContentType Content type of request body + @param aBody Body of the request + @return 0 if successful, else error + */ + int del(const char* aURLPath, const char* aContentType, const char* aBody); + int del(const String& aURLPath, const String& aContentType, const String& aBody); + int del(const char* aURLPath, const char* aContentType, int aContentLength, const byte aBody[]); + /** Connect to the server and start to send the request. @param aURLPath Url to request @param aHttpMethod Type of HTTP request to make, e.g. "GET", "POST", etc. diff --git a/examples/SimpleDelete/SimpleDelete.ino b/examples/SimpleDelete/SimpleDelete.ino index 591f4de..787a6fe 100644 --- a/examples/SimpleDelete/SimpleDelete.ino +++ b/examples/SimpleDelete/SimpleDelete.ino @@ -50,14 +50,10 @@ void setup() { void loop() { Serial.println("making DELETE request"); + String contentType = "application/x-www-form-urlencoded"; String delData = "name=light&age=46"; - client.beginRequest(); - client.startRequest("/", HTTP_METHOD_DELETE); - client.sendHeader("Content-Type", "application/x-www-form-urlencoded"); - client.sendHeader("Content-Length", delData.length()); - client.endRequest(); - client.write((const byte*)delData.c_str(), delData.length()); + client.del("/", contentType, delData); // read the status code and content length of the response statusCode = client.responseStatusCode(); From f56eecbc6fe5c6cbd8202ef6dd089d12de3d9c77 Mon Sep 17 00:00:00 2001 From: Sandeep Mistry Date: Wed, 22 Jun 2016 12:47:56 -0400 Subject: [PATCH 030/125] Add new responseBody API to simplify reading response body as a String --- HttpClient.cpp | 18 ++++++++++++++++++ HttpClient.h | 7 +++++++ examples/DweetGet/DweetGet.ino | 17 ++--------------- examples/DweetPost/DweetPost.ino | 12 ++---------- examples/HueBlink/HueBlink.ino | 20 +++++--------------- examples/SimpleDelete/SimpleDelete.ino | 12 ++---------- examples/SimpleGet/SimpleGet.ino | 12 ++---------- examples/SimplePost/SimplePost.ino | 12 ++---------- examples/SimplePut/SimplePut.ino | 12 ++---------- keywords.txt | 1 + 10 files changed, 43 insertions(+), 80 deletions(-) diff --git a/HttpClient.cpp b/HttpClient.cpp index 8545aaa..e131272 100644 --- a/HttpClient.cpp +++ b/HttpClient.cpp @@ -509,6 +509,24 @@ int HttpClient::contentLength() return iContentLength; } +String HttpClient::responseBody() +{ + int bodyLength = contentLength(); + String response; + + if (bodyLength > 0) + { + response.reserve(bodyLength); + } + + while (available()) + { + response += (char)read(); + } + + return response; +} + bool HttpClient::endOfBodyReached() { if (endOfHeadersReached() && (contentLength() != kNoContentLengthHeader)) diff --git a/HttpClient.h b/HttpClient.h index 639d7a5..d5633cd 100644 --- a/HttpClient.h +++ b/HttpClient.h @@ -245,6 +245,13 @@ class HttpClient : public Client */ int contentLength(); + /** Return the response body as a String + Also skips response headers if they have not been read already + MUST be called after responseStatusCode() + @return response body of request as a String + */ + String responseBody(); + /** Enables connection keep-alive mode */ void connectionKeepAlive(); diff --git a/examples/DweetGet/DweetGet.ino b/examples/DweetGet/DweetGet.ino index 7ab952f..96f698d 100644 --- a/examples/DweetGet/DweetGet.ino +++ b/examples/DweetGet/DweetGet.ino @@ -33,7 +33,6 @@ WiFiClient wifi; HttpClient client = HttpClient(wifi, serverAddress, port); int status = WL_IDLE_STATUS; int statusCode = 0; -int contentLength = 0; String response; void setup() { @@ -65,23 +64,11 @@ void loop() { Serial.println("making GET request"); client.get(path); - // read the status code of the response + // read the status code and body of the response statusCode = client.responseStatusCode(); + response = client.responseBody(); Serial.print("Status code: "); Serial.println(statusCode); - - // read the content length of the response - contentLength = client.contentLength(); - Serial.print("Content Length: "); - Serial.println(contentLength); - - // read the response body - response = ""; - response.reserve(contentLength); - while (client.available()) { - response += (char)client.read(); - } - Serial.print("Response: "); Serial.println(response); diff --git a/examples/DweetPost/DweetPost.ino b/examples/DweetPost/DweetPost.ino index d2f0582..201e76c 100644 --- a/examples/DweetPost/DweetPost.ino +++ b/examples/DweetPost/DweetPost.ino @@ -27,7 +27,6 @@ WiFiClient wifi; HttpClient client = HttpClient(wifi, serverAddress, port); int status = WL_IDLE_STATUS; int statusCode = 0; -int contentLength = 0; String response; void setup() { @@ -69,16 +68,9 @@ void loop() { // send the POST request client.post(path, contentType, postData); - // read the status code and content length of the response + // read the status code and body of the response statusCode = client.responseStatusCode(); - contentLength = client.contentLength(); - - // read the response body - response = ""; - response.reserve(contentLength); - while (client.available()) { - response += (char)client.read(); - } + response = client.responseBody(); Serial.print("Status code: "); Serial.println(statusCode); diff --git a/examples/HueBlink/HueBlink.ino b/examples/HueBlink/HueBlink.ino index 3d6a028..a88bec9 100644 --- a/examples/HueBlink/HueBlink.ino +++ b/examples/HueBlink/HueBlink.ino @@ -70,6 +70,8 @@ void sendRequest(int light, String cmd, String value) { request += light; request += "/state/"; + String contentType = "application/json"; + // make a string for the JSON command: String hueCmd = "{\"" + cmd; hueCmd += "\":"; @@ -81,23 +83,11 @@ void sendRequest(int light, String cmd, String value) { Serial.print("JSON command to server: "); // make the PUT request to the hub: - httpClient.beginRequest(); - httpClient.put(request); - httpClient.sendHeader("Content-Type", "application/json"); - httpClient.sendHeader("Content-Length", hueCmd.length()); - httpClient.endRequest(); - httpClient.write((const byte*)hueCmd.c_str(), hueCmd.length()); + httpClient.put(request, contentType, hueCmd); - // read the status code and content length of the response + // read the status code and body of the response int statusCode = httpClient.responseStatusCode(); - int contentLength = httpClient.contentLength(); - - // read the response body - String response = ""; - response.reserve(contentLength); - while (httpClient.available()) { - response += (char)httpClient.read(); - } + String response = httpClient.responseBody(); Serial.println(hueCmd); Serial.print("Status code from server: "); diff --git a/examples/SimpleDelete/SimpleDelete.ino b/examples/SimpleDelete/SimpleDelete.ino index 787a6fe..9304be0 100644 --- a/examples/SimpleDelete/SimpleDelete.ino +++ b/examples/SimpleDelete/SimpleDelete.ino @@ -26,7 +26,6 @@ HttpClient client = HttpClient(wifi, serverAddress, port); int status = WL_IDLE_STATUS; String response; int statusCode = 0; -int contentLength = 0; void setup() { Serial.begin(9600); @@ -55,16 +54,9 @@ void loop() { client.del("/", contentType, delData); - // read the status code and content length of the response + // read the status code and body of the response statusCode = client.responseStatusCode(); - contentLength = client.contentLength(); - - // read the response body - response = ""; - response.reserve(contentLength); - while (client.available()) { - response += (char)client.read(); - } + response = client.responseBody(); Serial.print("Status code: "); Serial.println(statusCode); diff --git a/examples/SimpleGet/SimpleGet.ino b/examples/SimpleGet/SimpleGet.ino index 78c3062..dc68eda 100644 --- a/examples/SimpleGet/SimpleGet.ino +++ b/examples/SimpleGet/SimpleGet.ino @@ -25,7 +25,6 @@ HttpClient client = HttpClient(wifi, serverAddress, port); int status = WL_IDLE_STATUS; String response; int statusCode = 0; -int contentLength = 0; void setup() { Serial.begin(9600); @@ -51,16 +50,9 @@ void loop() { Serial.println("making GET request"); client.get("/"); - // read the status code and content length of the response + // read the status code and body of the response statusCode = client.responseStatusCode(); - contentLength = client.contentLength(); - - // read the response body - response = ""; - response.reserve(contentLength); - while (client.available()) { - response += (char)client.read(); - } + response = client.responseBody(); Serial.print("Status code: "); Serial.println(statusCode); diff --git a/examples/SimplePost/SimplePost.ino b/examples/SimplePost/SimplePost.ino index 341205d..42dfea8 100644 --- a/examples/SimplePost/SimplePost.ino +++ b/examples/SimplePost/SimplePost.ino @@ -26,7 +26,6 @@ HttpClient client = HttpClient(wifi, serverAddress, port); int status = WL_IDLE_STATUS; String response; int statusCode = 0; -int contentLength = 0; void setup() { Serial.begin(9600); @@ -55,16 +54,9 @@ void loop() { client.post("/", contentType, postData); - // read the status code and content length of the response + // read the status code and body of the response statusCode = client.responseStatusCode(); - contentLength = client.contentLength(); - - // read the response body - response = ""; - response.reserve(contentLength); - while (client.available()) { - response += (char)client.read(); - } + response = client.responseBody(); Serial.print("Status code: "); Serial.println(statusCode); diff --git a/examples/SimplePut/SimplePut.ino b/examples/SimplePut/SimplePut.ino index dfca20c..30da9c0 100644 --- a/examples/SimplePut/SimplePut.ino +++ b/examples/SimplePut/SimplePut.ino @@ -26,7 +26,6 @@ HttpClient client = HttpClient(wifi, serverAddress, port); int status = WL_IDLE_STATUS; String response; int statusCode = 0; -int contentLength = 0; void setup() { Serial.begin(9600); @@ -55,16 +54,9 @@ void loop() { client.put("/", contentType, putData); - // read the status code and content length of the response + // read the status code and body of the response statusCode = client.responseStatusCode(); - contentLength = client.contentLength(); - - // read the response body - response = ""; - response.reserve(contentLength); - while (client.available()) { - response += (char)client.read(); - } + response = client.responseBody(); Serial.print("Status code: "); Serial.println(statusCode); diff --git a/keywords.txt b/keywords.txt index dc1140e..00bf130 100644 --- a/keywords.txt +++ b/keywords.txt @@ -33,6 +33,7 @@ noDefaultRequestHeaders KEYWORD2 headerAvailable KEYWORD2 readHeaderName KEYWORD2 readHeaderValue KEYWORD2 +responseBody KEYWORD2 ####################################### # Constants (LITERAL1) From 8f42a68a507e55b79bb72b3a823cf693709fee1a Mon Sep 17 00:00:00 2001 From: Sandeep Mistry Date: Wed, 22 Jun 2016 12:59:38 -0400 Subject: [PATCH 031/125] Always finish headers if a request body is provided to startRequest --- HttpClient.cpp | 12 +++++++----- HttpClient.h | 7 ++++--- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/HttpClient.cpp b/HttpClient.cpp index e131272..4159b9f 100644 --- a/HttpClient.cpp +++ b/HttpClient.cpp @@ -118,17 +118,19 @@ int HttpClient::startRequest(const char* aURLPath, const char* aHttpMethod, sendHeader(HTTP_HEADER_CONTENT_LENGTH, aContentLength); } - if ((initialState == eIdle)) + bool hasBody = (aBody && aContentLength > 0); + + if (initialState == eIdle || hasBody) { // This was a simple version of the API, so terminate the headers now finishHeaders(); + } + // else we'll call it in endRequest or in the first call to print, etc. - if (aBody && aContentLength > 0) - { + if (hasBody) + { write(aBody, aContentLength); - } } - // else we'll call it in endRequest or in the first call to print, etc. } return ret; diff --git a/HttpClient.h b/HttpClient.h index d5633cd..141b2df 100644 --- a/HttpClient.h +++ b/HttpClient.h @@ -75,7 +75,7 @@ class HttpClient : public Client int post(const char* aURLPath); int post(const String& aURLPath); - /** Connect to the server and start to send a POST request + /** Connect to the server and send a POST request with body and content type @param aURLPath Url to request @param aContentType Content type of request body @@ -93,7 +93,7 @@ class HttpClient : public Client int put(const char* aURLPath); int put(const String& aURLPath); - /** Connect to the server and start to send a PUT request + /** Connect to the server and send a PUT request with body and content type @param aURLPath Url to request @param aContentType Content type of request body @@ -111,7 +111,7 @@ class HttpClient : public Client int del(const char* aURLPath); int del(const String& aURLPath); - /** Connect to the server and start to send a DELETE request + /** Connect to the server and send a DELETE request with body and content type @param aURLPath Url to request @param aContentType Content type of request body @@ -123,6 +123,7 @@ class HttpClient : public Client int del(const char* aURLPath, const char* aContentType, int aContentLength, const byte aBody[]); /** Connect to the server and start to send the request. + If a body is provided, the entire request (including headers and body) will be sent @param aURLPath Url to request @param aHttpMethod Type of HTTP request to make, e.g. "GET", "POST", etc. @param aContentType Content type of request body (optional) From 829cb2d1f7129a69cc18e13a2dc75ea51f841bb2 Mon Sep 17 00:00:00 2001 From: Sandeep Mistry Date: Tue, 28 Jun 2016 16:49:15 -0400 Subject: [PATCH 032/125] Make whitespace consistent in getPostPutDelete.js --- examples/node_test_server/getPostPutDelete.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/examples/node_test_server/getPostPutDelete.js b/examples/node_test_server/getPostPutDelete.js index ec58f82..4e51f14 100644 --- a/examples/node_test_server/getPostPutDelete.js +++ b/examples/node_test_server/getPostPutDelete.js @@ -8,7 +8,7 @@ */ var express = require('express'); // include express.js -var app = express(); // a local instance of it +var app = express(); // a local instance of it var bodyParser = require('body-parser'); // include body-parser // you need a body parser: @@ -16,8 +16,8 @@ app.use(bodyParser.urlencoded({extended: false})); // for application/x-www-form // this runs after the server successfully starts: function serverStart() { - var port = server.address().port; - console.log('Server listening on port '+ port); + var port = server.address().port; + console.log('Server listening on port '+ port); } // this is the POST handler: @@ -26,12 +26,12 @@ app.all('/*', function (request, response) { // the parameters of a GET request are passed in // request.body. Pass that to formatResponse() // for formatting: - console.log(request.headers); - if (request.method == 'GET') { - console.log(request.query); - } else { - console.log(request.body); - } + console.log(request.headers); + if (request.method == 'GET') { + console.log(request.query); + } else { + console.log(request.body); + } // send the response: response.send('OK'); From 932837039523c29943f452476e89360f753f63b0 Mon Sep 17 00:00:00 2001 From: Sandeep Mistry Date: Tue, 28 Jun 2016 16:49:52 -0400 Subject: [PATCH 033/125] Add WebSocket server to getPostPutDelete.js using ws --- examples/node_test_server/getPostPutDelete.js | 15 +++++++++++++++ examples/node_test_server/package.json | 9 +++++---- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/examples/node_test_server/getPostPutDelete.js b/examples/node_test_server/getPostPutDelete.js index 4e51f14..e055b65 100644 --- a/examples/node_test_server/getPostPutDelete.js +++ b/examples/node_test_server/getPostPutDelete.js @@ -10,6 +10,7 @@ var express = require('express'); // include express.js var app = express(); // a local instance of it var bodyParser = require('body-parser'); // include body-parser +var WebSocketServer = require('ws').Server // include Web Socket server // you need a body parser: app.use(bodyParser.urlencoded({extended: false})); // for application/x-www-form-urlencoded @@ -40,3 +41,17 @@ app.all('/*', function (request, response) { // start the server: var server = app.listen(8080, serverStart); + +// create a WebSocket server and attach it to the server +var wss = new WebSocketServer({server: server}); + +wss.on('connection', function connection(ws) { + // new connection, add message listener + ws.on('message', function incoming(message) { + // received a message + console.log('received: %s', message); + + // echo it back + ws.send(message); + }); +}); diff --git a/examples/node_test_server/package.json b/examples/node_test_server/package.json index d6fb7cc..25fa25b 100644 --- a/examples/node_test_server/package.json +++ b/examples/node_test_server/package.json @@ -2,12 +2,13 @@ "name": "node_test_server", "version": "0.0.1", "author": { - "name":"Tom Igoe" - }, + "name": "Tom Igoe" + }, "dependencies": { + "body-parser": ">=1.11.0", "express": ">=4.0.0", - "body-parser" : ">=1.11.0", - "multer" : "*" + "multer": "*", + "ws": "^1.1.1" }, "engines": { "node": "0.10.x", From 32895db9d67bffebacdb644b5a5e66b6330230b6 Mon Sep 17 00:00:00 2001 From: Sandeep Mistry Date: Tue, 28 Jun 2016 16:50:33 -0400 Subject: [PATCH 034/125] Treat 101 status codes as a complete --- HttpClient.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/HttpClient.cpp b/HttpClient.cpp index 4159b9f..287e8c7 100644 --- a/HttpClient.cpp +++ b/HttpClient.cpp @@ -439,7 +439,7 @@ int HttpClient::responseStatusCode() delay(kHttpWaitForDataDelay); } } - if ( (c == '\n') && (iStatusCode < 200) ) + if ( (c == '\n') && (iStatusCode < 200 && iStatusCode != 101) ) { // We've reached the end of an informational status line c = '\0'; // Clear c so we'll go back into the data reading loop @@ -447,7 +447,7 @@ int HttpClient::responseStatusCode() } // If we've read a status code successfully but it's informational (1xx) // loop back to the start - while ( (iState == eStatusCodeRead) && (iStatusCode < 200) ); + while ( (iState == eStatusCodeRead) && (iStatusCode < 200 && iStatusCode != 101) ); if ( (c == '\n') && (iState == eStatusCodeRead) ) { From 0cb4f90f2a1aa760080e82a1b3ead5ead180782e Mon Sep 17 00:00:00 2001 From: Sandeep Mistry Date: Tue, 28 Jun 2016 16:53:05 -0400 Subject: [PATCH 035/125] Add initial WebSocketClient --- ArduinoHttpClient.h | 1 + WebSocketClient.cpp | 295 ++++++++++++++++++++++++++++++++++++++++++++ WebSocketClient.h | 90 ++++++++++++++ 3 files changed, 386 insertions(+) create mode 100644 WebSocketClient.cpp create mode 100644 WebSocketClient.h diff --git a/ArduinoHttpClient.h b/ArduinoHttpClient.h index 2064028..578733f 100644 --- a/ArduinoHttpClient.h +++ b/ArduinoHttpClient.h @@ -6,5 +6,6 @@ #define ArduinoHttpClient_h #include "HttpClient.h" +#include "WebSocketClient.h" #endif diff --git a/WebSocketClient.cpp b/WebSocketClient.cpp new file mode 100644 index 0000000..0f0401c --- /dev/null +++ b/WebSocketClient.cpp @@ -0,0 +1,295 @@ +// (c) Copyright Arduino. 2016 +// Released under Apache License, version 2.0 + +#include "b64.h" + +#include "WebSocketClient.h" + +WebSocketClient::WebSocketClient(Client& aClient, const char* aServerName, uint16_t aServerPort) + : HttpClient(aClient, aServerName, aServerPort) +{ +} + +WebSocketClient::WebSocketClient(Client& aClient, const String& aServerName, uint16_t aServerPort) + : HttpClient(aClient, aServerName, aServerPort) +{ +} + +WebSocketClient::WebSocketClient(Client& aClient, const IPAddress& aServerAddress, uint16_t aServerPort) + : HttpClient(aClient, aServerAddress, aServerPort) +{ +} + +int WebSocketClient::begin(const char* aPath) +{ + // start the GET request + beginRequest(); + connectionKeepAlive(); + int status = get(aPath); + + if (status == 0) + { + uint8_t randomKey[13]; + char base64RandomKey[21]; + + // create a random key for the connection upgrade + for (int i = 0; i < (int)sizeof(randomKey); i++) + { + randomKey[i] = random(0x01, 0xff); + } + memset(base64RandomKey, 0x00, sizeof(base64RandomKey)); + b64_encode(randomKey, sizeof(randomKey), (unsigned char*)base64RandomKey, sizeof(base64RandomKey)); + + // start the connection upgrade sequence + sendHeader("Upgrade", "websocket"); + sendHeader("Connection", "Upgrade"); + sendHeader("Sec-WebSocket-Key", base64RandomKey); + sendHeader("Sec-WebSocket-Version", "13"); + endRequest(); + + status = responseStatusCode(); + + if (status > 0) + { + skipResponseHeaders(); + } + } + + // status code of 101 means success + return (status == 101) ? 0 : status; +} + +int WebSocketClient::begin(const String& aPath) +{ + return begin(aPath.c_str()); +} + +int WebSocketClient::beginMessage(int aType) +{ + iTxMessageType = (aType & 0xf); + iTxSize = 0; + + return 0; +} + +int WebSocketClient::endMessage() +{ + // send FIN + the message type (opcode) + HttpClient::write(0x80 | iTxMessageType); + + // the message is masked (0x80) + // send the length + if (iTxSize < 126) + { + HttpClient::write(0x80 | (uint8_t)iTxSize); + } + else if (iTxSize < 0xffff) + { + HttpClient::write(0x80 | 126); + HttpClient::write((iTxSize >> 8) & 0xff); + HttpClient::write((iTxSize >> 0) & 0xff); + } + else + { + HttpClient::write(0x80 | 127); + HttpClient::write((iTxSize >> 56) & 0xff); + HttpClient::write((iTxSize >> 48) & 0xff); + HttpClient::write((iTxSize >> 40) & 0xff); + HttpClient::write((iTxSize >> 32) & 0xff); + HttpClient::write((iTxSize >> 24) & 0xff); + HttpClient::write((iTxSize >> 16) & 0xff); + HttpClient::write((iTxSize >> 8) & 0xff); + HttpClient::write((iTxSize >> 0) & 0xff); + } + + uint8_t maskKey[4]; + + // create a random mask for the data and send + for (int i = 0; i < (int)sizeof(maskKey); i++) + { + maskKey[i] = random(0xff); + } + HttpClient::write(maskKey, sizeof(maskKey)); + + // mask the data and send + for (int i = 0; i < (int)iTxSize; i++) { + iTxBuffer[i] ^= maskKey[i % sizeof(maskKey)]; + } + + int txSize = iTxSize; + + iTxSize = 0; + + return HttpClient::write(iTxBuffer, txSize); +} + +size_t WebSocketClient::write(uint8_t aByte) +{ + return write(&aByte, sizeof(aByte)); +} + +size_t WebSocketClient::write(const uint8_t *aBuffer, size_t aSize) +{ + if (iState < eReadingBody) + { + // have not upgraded the connection yet + return HttpClient::write(aBuffer, aSize); + } + + // check if the write size, fits in the buffer + if ((iTxSize + aSize) > sizeof(iTxBuffer)) + { + aSize = sizeof(iTxSize) - iTxSize; + } + + // copy data into the buffer + memcpy(iTxBuffer + iTxSize, aBuffer, aSize); + + iTxSize += aSize; + + return aSize; +} + +int WebSocketClient::parseMessage() +{ + // make sure 2 bytes (opcode + length) + // are available + if (HttpClient::available() < 2) + { + return 0; + } + + // read open code and length + uint8_t opcode = HttpClient::read(); + int length = HttpClient::read(); + + if ((opcode & 0x0f) == 0) + { + // continuation, use previous opcode and update flags + iRxOpCode |= opcode; + } + else + { + iRxOpCode = opcode; + } + + iRxMasked = (length & 0x80); + length &= 0x7f; + + // read the RX size + if (length < 126) + { + iRxSize = length; + } + else if (length == 126) + { + iRxSize = (HttpClient::read() << 8) | HttpClient::read(); + } + else + { + iRxSize = ((uint64_t)HttpClient::read() << 56) | + ((uint64_t)HttpClient::read() << 48) | + ((uint64_t)HttpClient::read() << 40) | + ((uint64_t)HttpClient::read() << 32) | + ((uint64_t)HttpClient::read() << 24) | + ((uint64_t)HttpClient::read() << 16) | + ((uint64_t)HttpClient::read() << 8) | + (uint64_t)HttpClient::read(); + } + + // read in the mask, if present + if (iRxMasked) + { + for (int i = 0; i < (int)sizeof(iRxMaskKey); i++) + { + iRxMaskKey[i] = HttpClient::read(); + } + } + + iRxMaskIndex = 0; + + return iRxSize; +} + +int WebSocketClient::messageType() +{ + return (iRxOpCode & 0x0f); +} + +bool WebSocketClient::isFinal() +{ + return ((iRxOpCode & 0x80) != 0); +} + +String WebSocketClient::readString() +{ + int avail = available(); + String s; + + if (avail > 0) + { + s.reserve(avail); + + for (int i = 0; i < avail; i++) + { + s += (char)read(); + } + } + + return s; +} + +int WebSocketClient::available() +{ + if (iState < eReadingBody) + { + return HttpClient::available(); + } + + return iRxSize; +} + +int WebSocketClient::read() +{ + byte b; + + if (read(&b, sizeof(b))) + { + return b; + } + + return -1; +} + +int WebSocketClient::read(uint8_t *aBuffer, size_t aSize) +{ + int readCount = HttpClient::read(aBuffer, aSize); + + if (readCount > 0) + { + iRxSize -= readCount; + + // unmask the RX data if needed + if (iRxMasked) + { + for (int i = 0; i < (int)aSize; i++, iRxMaskIndex++) { + aBuffer[i] ^= iRxMaskKey[iRxMaskIndex % sizeof(iRxMaskKey)]; + } + } + } + + return readCount; +} + +int WebSocketClient::peek() +{ + int p = HttpClient::peek(); + + if (p != -1 && iRxMasked) + { + // unmask the RX data if needed + p = (uint8_t)p ^ iRxMaskKey[iRxMaskIndex % sizeof(iRxMaskKey)]; + } + + return p; +} diff --git a/WebSocketClient.h b/WebSocketClient.h new file mode 100644 index 0000000..dc85966 --- /dev/null +++ b/WebSocketClient.h @@ -0,0 +1,90 @@ +// (c) Copyright Arduino. 2016 +// Released under Apache License, version 2.0 + +#ifndef WebSocketClient_h +#define WebSocketClient_h + +#include + +#include "HttpClient.h" + +static const int TYPE_CONTINUATION = 0x0; +static const int TYPE_TEXT = 0x1; +static const int TYPE_BINARY = 0x2; +static const int TYPE_CONNECTION_CLOSE = 0x8; +static const int TYPE_PING = 0x9; +static const int TYPE_PONG = 0xa; + +class WebSocketClient : public HttpClient +{ +public: + WebSocketClient(Client& aClient, const char* aServerName, uint16_t aServerPort = HttpClient::kHttpPort); + WebSocketClient(Client& aClient, const String& aServerName, uint16_t aServerPort = HttpClient::kHttpPort); + WebSocketClient(Client& aClient, const IPAddress& aServerAddress, uint16_t aServerPort = HttpClient::kHttpPort); + + /** Start the Web Socket connection to the specified path + @param aURLPath Path to use in request (optional, "/" is used by default) + @return 0 if successful, else error + */ + int begin(const char* aPath = "/"); + int begin(const String& aPath); + + /** Begin to send a message of type (TYPE_TEXT or TYPE_BINARY) + Use the write or Stream API's to set message content, followed by endMessage + to complete the message. + @param aURLPath Path to use in request + @return 0 if successful, else error + */ + int beginMessage(int aType); + + /** Completes sending of a message started by beginMessage + @return 0 if successful, else error + */ + int endMessage(); + + /** Try to parse an incoming messages + @return 0 if no message available, else size of parsed message + */ + int parseMessage(); + + /** Returns type of current parsed message + @return type of current parsedMessage (TYPE_TEXT or TYPE_BINARY) + */ + int messageType(); + + /** Returns if the current message is the final chunk of a split + message + @return true for final message, false otherwise + */ + bool isFinal(); + + /** Read the current messages as a string + @return current message as a string + */ + String readString(); + + // Inherited from Print + virtual size_t write(uint8_t aByte); + virtual size_t write(const uint8_t *aBuffer, size_t aSize); + // Inherited from Stream + virtual int available(); + /** Read the next byte from the server. + @return Byte read or -1 if there are no bytes available. + */ + virtual int read(); + virtual int read(uint8_t *buf, size_t size); + virtual int peek(); + +private: + uint8_t iTxMessageType; + uint8_t iTxBuffer[128]; + uint64_t iTxSize; + + uint8_t iRxOpCode; + uint64_t iRxSize; + bool iRxMasked; + int iRxMaskIndex; + uint8_t iRxMaskKey[4]; +}; + +#endif From 6a924a90aa4ae1b9eaed9d69aed1117c455c6afa Mon Sep 17 00:00:00 2001 From: Sandeep Mistry Date: Tue, 28 Jun 2016 16:53:17 -0400 Subject: [PATCH 036/125] Update keywords.txt --- keywords.txt | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/keywords.txt b/keywords.txt index 00bf130..3a02cbd 100644 --- a/keywords.txt +++ b/keywords.txt @@ -8,6 +8,7 @@ ArduinoHttpClient KEYWORD1 HttpClient KEYWORD1 +WebSocketClient KEYWORD1 ####################################### # Methods and Functions (KEYWORD2) @@ -35,12 +36,25 @@ readHeaderName KEYWORD2 readHeaderValue KEYWORD2 responseBody KEYWORD2 +beginMessage KEYWORD2 +endMessage KEYWORD2 +parseMessage KEYWORD2 +messageType KEYWORD2 +isFinal KEYWORD2 +readString KEYWORD2 + ####################################### # Constants (LITERAL1) ####################################### -HTTP_SUCCESS LITERAL1 -HTTP_ERROR_CONNECTION_FAILED LITERAL1 -HTTP_ERROR_API LITERAL1 -HTTP_ERROR_TIMED_OUT LITERAL1 -HTTP_ERROR_INVALID_RESPONSE LITERAL1 +HTTP_SUCCESS LITERAL1 +HTTP_ERROR_CONNECTION_FAILED LITERAL1 +HTTP_ERROR_API LITERAL1 +HTTP_ERROR_TIMED_OUT LITERAL1 +HTTP_ERROR_INVALID_RESPONSE LITERAL1 +TYPE_CONTINUATION LITERAL1 +TYPE_TEXT LITERAL1 +TYPE_BINARY LITERAL1 +TYPE_CONNECTION_CLOSE LITERAL1 +TYPE_PING LITERAL1 +TYPE_PONG LITERAL1 From 1c56734360e6d464aa0e21910f9ead0ad954ded9 Mon Sep 17 00:00:00 2001 From: Sandeep Mistry Date: Tue, 28 Jun 2016 16:54:40 -0400 Subject: [PATCH 037/125] Add new SimpleWebSocket example --- examples/SimpleWebSocket/SimpleWebSocket.ino | 80 +++++++++++++++++++ .../{DweetGet => SimpleWebSocket}/config.h | 1 + 2 files changed, 81 insertions(+) create mode 100644 examples/SimpleWebSocket/SimpleWebSocket.ino rename examples/{DweetGet => SimpleWebSocket}/config.h (99%) diff --git a/examples/SimpleWebSocket/SimpleWebSocket.ino b/examples/SimpleWebSocket/SimpleWebSocket.ino new file mode 100644 index 0000000..ccd97b7 --- /dev/null +++ b/examples/SimpleWebSocket/SimpleWebSocket.ino @@ -0,0 +1,80 @@ +/* + Simple WebSocket client for ArduinoHttpClient library + Connects to the WebSocket server, and sends a hello + message every 5 seconds + + note: WiFi SSID and password are stored in config.h file. + If it is not present, add a new tab, call it "config.h" + and add the following variables: + char ssid[] = "ssid"; // your network SSID (name) + char pass[] = "password"; // your network password + + created 28 Jun 2016 + by Sandeep Mistry + + this example is in the public domain +*/ +#include +#include +#include "config.h" + +char serverAddress[] = "echo.websocket.org"; // server address +int port = 80; + +WiFiClient wifi; +WebSocketClient client = WebSocketClient(wifi, serverAddress, port); +int status = WL_IDLE_STATUS; +int count = 0; + +void setup() { + Serial.begin(9600); + while ( status != WL_CONNECTED) { + Serial.print("Attempting to connect to Network named: "); + Serial.println(ssid); // print the network name (SSID); + + // Connect to WPA/WPA2 network: + status = WiFi.begin(ssid, pass); + } + + // print the SSID of the network you're attached to: + Serial.print("SSID: "); + Serial.println(WiFi.SSID()); + + // print your WiFi shield's IP address: + IPAddress ip = WiFi.localIP(); + Serial.print("IP Address: "); + Serial.println(ip); +} + +void loop() { + Serial.println("starting WebSocket client"); + client.begin(); + + while (client.connected()) { + Serial.print("Sending hello "); + Serial.println(count); + + // send a hello # + client.beginMessage(TYPE_TEXT); + client.print("hello "); + client.print(count); + client.endMessage(); + + // increment count for next message + count++; + + // check if a message is available to be received + int messageSize = client.parseMessage(); + + if (messageSize > 0) { + Serial.println("Received a message:"); + Serial.println(client.readString()); + } + + // wait 5 seconds + delay(5000); + } + + Serial.println("disconnected"); +} + diff --git a/examples/DweetGet/config.h b/examples/SimpleWebSocket/config.h similarity index 99% rename from examples/DweetGet/config.h rename to examples/SimpleWebSocket/config.h index c263766..7765359 100644 --- a/examples/DweetGet/config.h +++ b/examples/SimpleWebSocket/config.h @@ -1,2 +1,3 @@ char ssid[] = "ssid"; // your network SSID (name) char pass[] = "password"; // your network password + From 00cd68f52fdf2a50e1c3c7b703963e04385d93a2 Mon Sep 17 00:00:00 2001 From: Sandeep Mistry Date: Wed, 29 Jun 2016 13:04:27 -0400 Subject: [PATCH 038/125] Add ping, pong, and connection close support --- WebSocketClient.cpp | 59 ++++++++++++++++++++++++++++++++++++++++++--- WebSocketClient.h | 8 ++++++ keywords.txt | 1 + 3 files changed, 65 insertions(+), 3 deletions(-) diff --git a/WebSocketClient.cpp b/WebSocketClient.cpp index 0f0401c..6a17d0e 100644 --- a/WebSocketClient.cpp +++ b/WebSocketClient.cpp @@ -6,17 +6,20 @@ #include "WebSocketClient.h" WebSocketClient::WebSocketClient(Client& aClient, const char* aServerName, uint16_t aServerPort) - : HttpClient(aClient, aServerName, aServerPort) + : HttpClient(aClient, aServerName, aServerPort), + iRxSize(0) { } WebSocketClient::WebSocketClient(Client& aClient, const String& aServerName, uint16_t aServerPort) - : HttpClient(aClient, aServerName, aServerPort) + : HttpClient(aClient, aServerName, aServerPort), + iRxSize(0) { } WebSocketClient::WebSocketClient(Client& aClient, const IPAddress& aServerAddress, uint16_t aServerPort) - : HttpClient(aClient, aServerAddress, aServerPort) + : HttpClient(aClient, aServerAddress, aServerPort), + iRxSize(0) { } @@ -55,6 +58,8 @@ int WebSocketClient::begin(const char* aPath) } } + iRxSize = 0; + // status code of 101 means success return (status == 101) ? 0 : status; } @@ -152,6 +157,8 @@ size_t WebSocketClient::write(const uint8_t *aBuffer, size_t aSize) int WebSocketClient::parseMessage() { + flushRx(); + // make sure 2 bytes (opcode + length) // are available if (HttpClient::available() < 2) @@ -208,6 +215,29 @@ int WebSocketClient::parseMessage() iRxMaskIndex = 0; + if (TYPE_CONNECTION_CLOSE == messageType()) + { + flushRx(); + stop(); + iRxSize = 0; + } + else if (TYPE_PING == messageType()) + { + beginMessage(TYPE_PONG); + while(available()) + { + write(read()); + } + endMessage(); + + iRxSize = 0; + } + else if (TYPE_PONG == messageType()) + { + flushRx(); + iRxSize = 0; + } + return iRxSize; } @@ -239,6 +269,21 @@ String WebSocketClient::readString() return s; } +int WebSocketClient::ping() +{ + uint8_t pingData[16]; + + // create random data for the ping + for (int i = 0; i < (int)sizeof(pingData); i++) + { + pingData[i] = random(0xff); + } + + beginMessage(TYPE_PING); + write(pingData, sizeof(pingData)); + return endMessage(); +} + int WebSocketClient::available() { if (iState < eReadingBody) @@ -293,3 +338,11 @@ int WebSocketClient::peek() return p; } + +void WebSocketClient::flushRx() +{ + while(available()) + { + read(); + } +} diff --git a/WebSocketClient.h b/WebSocketClient.h index dc85966..4ab328f 100644 --- a/WebSocketClient.h +++ b/WebSocketClient.h @@ -63,6 +63,11 @@ class WebSocketClient : public HttpClient */ String readString(); + /** Send a ping + @return 0 if successful, else error + */ + int ping(); + // Inherited from Print virtual size_t write(uint8_t aByte); virtual size_t write(const uint8_t *aBuffer, size_t aSize); @@ -75,6 +80,9 @@ class WebSocketClient : public HttpClient virtual int read(uint8_t *buf, size_t size); virtual int peek(); +private: + void flushRx(); + private: uint8_t iTxMessageType; uint8_t iTxBuffer[128]; diff --git a/keywords.txt b/keywords.txt index 3a02cbd..f62ba70 100644 --- a/keywords.txt +++ b/keywords.txt @@ -42,6 +42,7 @@ parseMessage KEYWORD2 messageType KEYWORD2 isFinal KEYWORD2 readString KEYWORD2 +ping KEYWORD2 ####################################### # Constants (LITERAL1) From f99517c3e5f38a49811f317379f9b04453fd9f9e Mon Sep 17 00:00:00 2001 From: Sandeep Mistry Date: Wed, 29 Jun 2016 14:15:22 -0400 Subject: [PATCH 039/125] Correct endMessage return value, add check for TX state --- WebSocketClient.cpp | 27 +++++++++++++++++++++++++-- WebSocketClient.h | 1 + 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/WebSocketClient.cpp b/WebSocketClient.cpp index 6a17d0e..1237b04 100644 --- a/WebSocketClient.cpp +++ b/WebSocketClient.cpp @@ -7,18 +7,21 @@ WebSocketClient::WebSocketClient(Client& aClient, const char* aServerName, uint16_t aServerPort) : HttpClient(aClient, aServerName, aServerPort), + iTxStarted(false), iRxSize(0) { } WebSocketClient::WebSocketClient(Client& aClient, const String& aServerName, uint16_t aServerPort) : HttpClient(aClient, aServerName, aServerPort), + iTxStarted(false), iRxSize(0) { } WebSocketClient::WebSocketClient(Client& aClient, const IPAddress& aServerAddress, uint16_t aServerPort) : HttpClient(aClient, aServerAddress, aServerPort), + iTxStarted(false), iRxSize(0) { } @@ -71,6 +74,13 @@ int WebSocketClient::begin(const String& aPath) int WebSocketClient::beginMessage(int aType) { + if (iTxStarted) + { + // fail TX already started + return 1; + } + + iTxStarted = true; iTxMessageType = (aType & 0xf); iTxSize = 0; @@ -79,6 +89,12 @@ int WebSocketClient::beginMessage(int aType) int WebSocketClient::endMessage() { + if (!iTxStarted) + { + // fail TX not started + return 1; + } + // send FIN + the message type (opcode) HttpClient::write(0x80 | iTxMessageType); @@ -121,11 +137,12 @@ int WebSocketClient::endMessage() iTxBuffer[i] ^= maskKey[i % sizeof(maskKey)]; } - int txSize = iTxSize; + size_t txSize = iTxSize; + iTxStarted = false; iTxSize = 0; - return HttpClient::write(iTxBuffer, txSize); + return (HttpClient::write(iTxBuffer, txSize) == txSize) ? 0 : 1; } size_t WebSocketClient::write(uint8_t aByte) @@ -141,6 +158,12 @@ size_t WebSocketClient::write(const uint8_t *aBuffer, size_t aSize) return HttpClient::write(aBuffer, aSize); } + if (!iTxStarted) + { + // fail TX not started + return 0; + } + // check if the write size, fits in the buffer if ((iTxSize + aSize) > sizeof(iTxBuffer)) { diff --git a/WebSocketClient.h b/WebSocketClient.h index 4ab328f..4b009e6 100644 --- a/WebSocketClient.h +++ b/WebSocketClient.h @@ -84,6 +84,7 @@ class WebSocketClient : public HttpClient void flushRx(); private: + bool iTxStarted; uint8_t iTxMessageType; uint8_t iTxBuffer[128]; uint64_t iTxSize; From f9f75fb6ea0c573c5ef3d420c1c49ecbfa8c6e53 Mon Sep 17 00:00:00 2001 From: Sandeep Mistry Date: Mon, 4 Jul 2016 16:15:45 -0400 Subject: [PATCH 040/125] Update library.properties --- library.properties | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/library.properties b/library.properties index 35c75cc..8f1173d 100644 --- a/library.properties +++ b/library.properties @@ -1,9 +1,9 @@ name=ArduinoHttpClient -version=2.2.0 -author=Adrian McEwen +version=0.1.0 +author=Arduino maintainer=Arduino -sentence=Library to easily make HTTP GET, POST and PUT requests to a web server. -paragraph=Works with any class derived from Client - so switching between Ethernet, WiFi and GSMClient requires minimal code changes. +sentence=[EXPERIMENTAL] Easily interact with web servers from Arduino, using HTTP and WebSocket's. +paragraph=This library can be used for HTTP (GET, POST, PUT, DELETE) requests to a web server. It also supports exchanging messages with WebSocket servers. Based on Adrian McEwen's HttpClient library. category=Communication url=https://github.com/arduino-libraries/ArduinoHttpClient architectures=* From e36394733e9014862b15834192a47a937b6545be Mon Sep 17 00:00:00 2001 From: Sandeep Mistry Date: Tue, 5 Jul 2016 09:21:08 -0400 Subject: [PATCH 041/125] Make bracket style consistent --- HttpClient.cpp | 7 +++++-- WebSocketClient.cpp | 3 ++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/HttpClient.cpp b/HttpClient.cpp index 287e8c7..498d26c 100644 --- a/HttpClient.cpp +++ b/HttpClient.cpp @@ -78,7 +78,8 @@ int HttpClient::startRequest(const char* aURLPath, const char* aHttpMethod, if (iConnectionClose || !iClient->connected()) { - if (iServerName) { + if (iServerName) + { if (!iClient->connect(iServerName, iServerPort) > 0) { #ifdef LOGGING @@ -86,7 +87,9 @@ int HttpClient::startRequest(const char* aURLPath, const char* aHttpMethod, #endif return HTTP_ERROR_CONNECTION_FAILED; } - } else { + } + else + { if (!iClient->connect(iServerAddress, iServerPort) > 0) { #ifdef LOGGING diff --git a/WebSocketClient.cpp b/WebSocketClient.cpp index 1237b04..f78d946 100644 --- a/WebSocketClient.cpp +++ b/WebSocketClient.cpp @@ -340,7 +340,8 @@ int WebSocketClient::read(uint8_t *aBuffer, size_t aSize) // unmask the RX data if needed if (iRxMasked) { - for (int i = 0; i < (int)aSize; i++, iRxMaskIndex++) { + for (int i = 0; i < (int)aSize; i++, iRxMaskIndex++) + { aBuffer[i] ^= iRxMaskKey[iRxMaskIndex % sizeof(iRxMaskKey)]; } } From 364364744e3a8165f40793f46d21bead559ccdc5 Mon Sep 17 00:00:00 2001 From: Sandeep Mistry Date: Tue, 5 Jul 2016 09:32:13 -0400 Subject: [PATCH 042/125] Create src folder --- ArduinoHttpClient.h => src/ArduinoHttpClient.h | 0 HttpClient.cpp => src/HttpClient.cpp | 0 HttpClient.h => src/HttpClient.h | 0 WebSocketClient.cpp => src/WebSocketClient.cpp | 0 WebSocketClient.h => src/WebSocketClient.h | 0 b64.cpp => src/b64.cpp | 0 b64.h => src/b64.h | 0 7 files changed, 0 insertions(+), 0 deletions(-) rename ArduinoHttpClient.h => src/ArduinoHttpClient.h (100%) rename HttpClient.cpp => src/HttpClient.cpp (100%) rename HttpClient.h => src/HttpClient.h (100%) rename WebSocketClient.cpp => src/WebSocketClient.cpp (100%) rename WebSocketClient.h => src/WebSocketClient.h (100%) rename b64.cpp => src/b64.cpp (100%) rename b64.h => src/b64.h (100%) diff --git a/ArduinoHttpClient.h b/src/ArduinoHttpClient.h similarity index 100% rename from ArduinoHttpClient.h rename to src/ArduinoHttpClient.h diff --git a/HttpClient.cpp b/src/HttpClient.cpp similarity index 100% rename from HttpClient.cpp rename to src/HttpClient.cpp diff --git a/HttpClient.h b/src/HttpClient.h similarity index 100% rename from HttpClient.h rename to src/HttpClient.h diff --git a/WebSocketClient.cpp b/src/WebSocketClient.cpp similarity index 100% rename from WebSocketClient.cpp rename to src/WebSocketClient.cpp diff --git a/WebSocketClient.h b/src/WebSocketClient.h similarity index 100% rename from WebSocketClient.h rename to src/WebSocketClient.h diff --git a/b64.cpp b/src/b64.cpp similarity index 100% rename from b64.cpp rename to src/b64.cpp diff --git a/b64.h b/src/b64.h similarity index 100% rename from b64.h rename to src/b64.h From 67b4e3cd4762b8730419aa5f63bebf8aba05444c Mon Sep 17 00:00:00 2001 From: Sandeep Mistry Date: Tue, 5 Jul 2016 09:46:46 -0400 Subject: [PATCH 043/125] Add includes key in library.properties --- library.properties | 1 + 1 file changed, 1 insertion(+) diff --git a/library.properties b/library.properties index 8f1173d..c05a6ca 100644 --- a/library.properties +++ b/library.properties @@ -7,3 +7,4 @@ paragraph=This library can be used for HTTP (GET, POST, PUT, DELETE) requests to category=Communication url=https://github.com/arduino-libraries/ArduinoHttpClient architectures=* +includes=ArduinoHttpClient.h From dbf13c8aa7764d7406970212804791deb57ad43a Mon Sep 17 00:00:00 2001 From: Sandeep Mistry Date: Tue, 5 Jul 2016 10:40:06 -0400 Subject: [PATCH 044/125] Add change log --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..14a7d48 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,3 @@ +## ArduinoHttpClient 0.1.0 - 2016.07.05 + +* Initial release From fe46191445476bfc650878a019a7d34a650844de Mon Sep 17 00:00:00 2001 From: Sandeep Mistry Date: Tue, 12 Jul 2016 17:23:48 -0400 Subject: [PATCH 045/125] Make HttpClient::responseBody more robust Return invalidated String if memory allocation fails or content length does not match body data length. Also, use timed reads to support responses without a content length. --- src/HttpClient.cpp | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/src/HttpClient.cpp b/src/HttpClient.cpp index 498d26c..d640bd1 100644 --- a/src/HttpClient.cpp +++ b/src/HttpClient.cpp @@ -32,7 +32,7 @@ void HttpClient::resetState() { iState = eIdle; iStatusCode = 0; - iContentLength = 0; + iContentLength = kNoContentLengthHeader; iBodyLengthConsumed = 0; iContentLengthPtr = kContentLengthPrefix; iHttpResponseTimeout = kHttpResponseTimeout; @@ -521,12 +521,35 @@ String HttpClient::responseBody() if (bodyLength > 0) { - response.reserve(bodyLength); + // try to reserve bodyLength bytes + if (response.reserve(bodyLength) == 0) { + // String reserve failed + return String((const char*)NULL); + } } - while (available()) + // keep on timedRead'ing, until: + // - we have a content length: body length equals consumed or no bytes + // available + // - no content length: no bytes are available + while (iBodyLengthConsumed != bodyLength) { - response += (char)read(); + int c = timedRead(); + + if (c == -1) { + // read timed out, done + break; + } + + if (!response.concat((char)c)) { + // adding char failed + return String((const char*)NULL); + } + } + + if (bodyLength > 0 && (unsigned int)bodyLength != response.length()) { + // failure, we did not read in reponse content length bytes + return String((const char*)NULL); } return response; From 172049e0c3c8d29799c5faec5c473025109c68de Mon Sep 17 00:00:00 2001 From: Sandeep Mistry Date: Fri, 12 Aug 2016 17:00:56 -0400 Subject: [PATCH 046/125] Also set iBodyLengthConsumed to zero when parsing --- src/HttpClient.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/HttpClient.cpp b/src/HttpClient.cpp index d640bd1..e66d488 100644 --- a/src/HttpClient.cpp +++ b/src/HttpClient.cpp @@ -686,6 +686,7 @@ int HttpClient::readHeader() // Just in case we get multiple Content-Length headers, this // will ensure we just get the value of the last one iContentLength = 0; + iBodyLengthConsumed = 0; } } else if ((iContentLengthPtr == kContentLengthPrefix) && (c == '\r')) From 208005fec244dd0f5dcdb8f4f9c2c48b124b2dfd Mon Sep 17 00:00:00 2001 From: Daniel Mack Date: Mon, 22 Aug 2016 11:28:22 +0200 Subject: [PATCH 047/125] Add library.json for PlatformIO integration See http://docs.platformio.org/en/stable/librarymanager/creating.html --- library.json | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 library.json diff --git a/library.json b/library.json new file mode 100644 index 0000000..bc5e6f2 --- /dev/null +++ b/library.json @@ -0,0 +1,12 @@ +{ + "name": "ArduinoHttpClient", + "keywords": "http, web, client, ethernet, wifi, GSM", + "description": "ArduinoHttpClient is a library to make it easier to interact with web servers from Arduino", + "repository": + { + "type": "git", + "url": "https://github.com/arduino-libraries/ArduinoHttpClient.git" + }, + "frameworks": "arduino", + "platforms": "*" +} From b6eaf7ce3745dbd421f8b1d6dbeae284ddda4053 Mon Sep 17 00:00:00 2001 From: Sandeep Mistry Date: Fri, 14 Oct 2016 15:28:19 -0400 Subject: [PATCH 048/125] Make description match library.properties --- library.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library.json b/library.json index bc5e6f2..6bbda24 100644 --- a/library.json +++ b/library.json @@ -1,7 +1,7 @@ { "name": "ArduinoHttpClient", "keywords": "http, web, client, ethernet, wifi, GSM", - "description": "ArduinoHttpClient is a library to make it easier to interact with web servers from Arduino", + "description": "Easily interact with web servers from Arduino, using HTTP and WebSocket's.", "repository": { "type": "git", From 7170f7fc5f48937661145d0c69780b6dee56fd14 Mon Sep 17 00:00:00 2001 From: agdl Date: Fri, 16 Dec 2016 17:11:40 +0100 Subject: [PATCH 049/125] library release --- CHANGELOG.md | 4 ++++ library.properties | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 14a7d48..c4a0c01 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ ## ArduinoHttpClient 0.1.0 - 2016.07.05 * Initial release +## ArduinoHttpClient 0.1.1 - 2016.12.16 + +* More robust response parser + diff --git a/library.properties b/library.properties index c05a6ca..cc6904c 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=ArduinoHttpClient -version=0.1.0 +version=0.1.1 author=Arduino maintainer=Arduino sentence=[EXPERIMENTAL] Easily interact with web servers from Arduino, using HTTP and WebSocket's. From 68aebb113a8a27fd85cd0c5116e85b15ba85be8e Mon Sep 17 00:00:00 2001 From: agdl Date: Fri, 16 Dec 2016 17:13:16 +0100 Subject: [PATCH 050/125] Fixed changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c4a0c01..19c448c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ## ArduinoHttpClient 0.1.0 - 2016.07.05 * Initial release + ## ArduinoHttpClient 0.1.1 - 2016.12.16 * More robust response parser From d261fa35f57cd620907bb050b1cbe7b8aff98fbb Mon Sep 17 00:00:00 2001 From: Sandeep Mistry Date: Tue, 3 Jan 2017 16:20:03 -0500 Subject: [PATCH 051/125] Add support for PATCH operations --- keywords.txt | 1 + src/HttpClient.cpp | 25 +++++++++++++++++++++++++ src/HttpClient.h | 19 +++++++++++++++++++ 3 files changed, 45 insertions(+) diff --git a/keywords.txt b/keywords.txt index f62ba70..7c2061c 100644 --- a/keywords.txt +++ b/keywords.txt @@ -17,6 +17,7 @@ WebSocketClient KEYWORD1 get KEYWORD2 post KEYWORD2 put KEYWORD2 +patch KEYWORD2 startRequest KEYWORD2 beginRequest KEYWORD2 sendHeader KEYWORD2 diff --git a/src/HttpClient.cpp b/src/HttpClient.cpp index e66d488..f3accc3 100644 --- a/src/HttpClient.cpp +++ b/src/HttpClient.cpp @@ -332,6 +332,31 @@ int HttpClient::put(const char* aURLPath, const char* aContentType, int aContent return startRequest(aURLPath, HTTP_METHOD_PUT, aContentType, aContentLength, aBody); } +int HttpClient::patch(const char* aURLPath) +{ + return startRequest(aURLPath, HTTP_METHOD_PATCH); +} + +int HttpClient::patch(const String& aURLPath) +{ + return patch(aURLPath.c_str()); +} + +int HttpClient::patch(const char* aURLPath, const char* aContentType, const char* aBody) +{ + return patch(aURLPath, aContentType, strlen(aBody), (const byte*)aBody); +} + +int HttpClient::patch(const String& aURLPath, const String& aContentType, const String& aBody) +{ + return patch(aURLPath.c_str(), aContentType.c_str(), aBody.length(), (const byte*)aBody.c_str()); +} + +int HttpClient::patch(const char* aURLPath, const char* aContentType, int aContentLength, const byte aBody[]) +{ + return startRequest(aURLPath, HTTP_METHOD_PATCH, aContentType, aContentLength, aBody); +} + int HttpClient::del(const char* aURLPath) { return startRequest(aURLPath, HTTP_METHOD_DELETE); diff --git a/src/HttpClient.h b/src/HttpClient.h index 141b2df..eaab360 100644 --- a/src/HttpClient.h +++ b/src/HttpClient.h @@ -29,6 +29,7 @@ static const int HTTP_ERROR_INVALID_RESPONSE =-4; #define HTTP_METHOD_GET "GET" #define HTTP_METHOD_POST "POST" #define HTTP_METHOD_PUT "PUT" +#define HTTP_METHOD_PATCH "PATCH" #define HTTP_METHOD_DELETE "DELETE" #define HTTP_HEADER_CONTENT_LENGTH "Content-Length" #define HTTP_HEADER_CONTENT_TYPE "Content-Type" @@ -104,6 +105,24 @@ class HttpClient : public Client int put(const String& aURLPath, const String& aContentType, const String& aBody); int put(const char* aURLPath, const char* aContentType, int aContentLength, const byte aBody[]); + /** Connect to the server and start to send a PATCH request. + @param aURLPath Url to request + @return 0 if successful, else error + */ + int patch(const char* aURLPath); + int patch(const String& aURLPath); + + /** Connect to the server and send a PATCH request + with body and content type + @param aURLPath Url to request + @param aContentType Content type of request body + @param aBody Body of the request + @return 0 if successful, else error + */ + int patch(const char* aURLPath, const char* aContentType, const char* aBody); + int patch(const String& aURLPath, const String& aContentType, const String& aBody); + int patch(const char* aURLPath, const char* aContentType, int aContentLength, const byte aBody[]); + /** Connect to the server and start to send a DELETE request. @param aURLPath Url to request @return 0 if successful, else error From bdb8c02702b4ffe7534e60e0095c217affe7058e Mon Sep 17 00:00:00 2001 From: Sandeep Mistry Date: Tue, 3 Jan 2017 16:33:14 -0500 Subject: [PATCH 052/125] Add new basic auth example --- examples/BasicAuthGet/BasicAuthGet.ino | 69 ++++++++++++++++++++++++++ examples/BasicAuthGet/config.h | 3 ++ 2 files changed, 72 insertions(+) create mode 100644 examples/BasicAuthGet/BasicAuthGet.ino create mode 100644 examples/BasicAuthGet/config.h diff --git a/examples/BasicAuthGet/BasicAuthGet.ino b/examples/BasicAuthGet/BasicAuthGet.ino new file mode 100644 index 0000000..b8e0f51 --- /dev/null +++ b/examples/BasicAuthGet/BasicAuthGet.ino @@ -0,0 +1,69 @@ +/* + GET client with HTTP basic authentication for ArduinoHttpClient library + Connects to server once every five seconds, sends a GET request + + note: WiFi SSID and password are stored in config.h file. + If it is not present, add a new tab, call it "config.h" + and add the following variables: + char ssid[] = "ssid"; // your network SSID (name) + char pass[] = "password"; // your network password + + created 14 Feb 2016 + by Tom Igoe + modified 3 Jan 2017 to add HTTP basic authentication + by Sandeep Mistry + + this example is in the public domain + */ +#include +#include +#include "config.h" + +char serverAddress[] = "192.168.0.3"; // server address +int port = 8080; + +WiFiClient wifi; +HttpClient client = HttpClient(wifi, serverAddress, port); +int status = WL_IDLE_STATUS; +String response; +int statusCode = 0; + +void setup() { + Serial.begin(9600); + while ( status != WL_CONNECTED) { + Serial.print("Attempting to connect to Network named: "); + Serial.println(ssid); // print the network name (SSID); + + // Connect to WPA/WPA2 network: + status = WiFi.begin(ssid, pass); + } + + // print the SSID of the network you're attached to: + Serial.print("SSID: "); + Serial.println(WiFi.SSID()); + + // print your WiFi shield's IP address: + IPAddress ip = WiFi.localIP(); + Serial.print("IP Address: "); + Serial.println(ip); +} + +void loop() { + Serial.println("making GET request with HTTP basic authentication"); + client.beginRequest(); + client.get("/secure"); + client.sendBasicAuth("username", "password"); // send the username and password for authentication + client.endRequest(); + + // read the status code and body of the response + statusCode = client.responseStatusCode(); + response = client.responseBody(); + + Serial.print("Status code: "); + Serial.println(statusCode); + Serial.print("Response: "); + Serial.println(response); + Serial.println("Wait five seconds"); + delay(5000); +} + diff --git a/examples/BasicAuthGet/config.h b/examples/BasicAuthGet/config.h new file mode 100644 index 0000000..7765359 --- /dev/null +++ b/examples/BasicAuthGet/config.h @@ -0,0 +1,3 @@ +char ssid[] = "ssid"; // your network SSID (name) +char pass[] = "password"; // your network password + From 4e21c8497e41d1376d8524f4d0ccb775e564dd46 Mon Sep 17 00:00:00 2001 From: agdl Date: Thu, 12 Jan 2017 15:41:54 +0100 Subject: [PATCH 053/125] Make Node.js and npm version requirements less strict --- examples/node_test_server/package.json | 4 ---- 1 file changed, 4 deletions(-) diff --git a/examples/node_test_server/package.json b/examples/node_test_server/package.json index 25fa25b..09f2d8b 100644 --- a/examples/node_test_server/package.json +++ b/examples/node_test_server/package.json @@ -9,9 +9,5 @@ "express": ">=4.0.0", "multer": "*", "ws": "^1.1.1" - }, - "engines": { - "node": "0.10.x", - "npm": "1.3.x" } } From 767ecf2fe94834d5823d74cc3cdb314c9507c8a6 Mon Sep 17 00:00:00 2001 From: Arturo Guadalupi Date: Thu, 12 Jan 2017 16:10:02 +0100 Subject: [PATCH 054/125] Added Custom Header Example (#20) Added the example posted in #12 to close the PR. Tested and it works --- examples/CustomHeader/CustomHeader.ino | 91 ++++++++++++++++++++++++++ examples/CustomHeader/config.h | 3 + 2 files changed, 94 insertions(+) create mode 100644 examples/CustomHeader/CustomHeader.ino create mode 100644 examples/CustomHeader/config.h diff --git a/examples/CustomHeader/CustomHeader.ino b/examples/CustomHeader/CustomHeader.ino new file mode 100644 index 0000000..898c97b --- /dev/null +++ b/examples/CustomHeader/CustomHeader.ino @@ -0,0 +1,91 @@ +/* + Custom request header example for the ArduinoHttpClient + library. This example sends a GET and a POST request with a custom header every 5 seconds. + + note: WiFi SSID and password are stored in config.h file. + If it is not present, add a new tab, call it "config.h" + and add the following variables: + char ssid[] = "ssid"; // your network SSID (name) + char pass[] = "password"; // your network password + + based on SimpleGet example by Tom Igoe + header modifications by Todd Treece + + this example is in the public domain + */ + +#include +#include +#include "config.h" + +char serverAddress[] = "192.168.0.3"; // server address +int port = 8080; + +WiFiClient wifi; +HttpClient client = HttpClient(wifi, serverAddress, port); +int status = WL_IDLE_STATUS; +String response; +int statusCode = 0; + +void setup() { + Serial.begin(9600); + while ( status != WL_CONNECTED) { + Serial.print("Attempting to connect to Network named: "); + Serial.println(ssid); // print the network name (SSID); + + // Connect to WPA/WPA2 network: + status = WiFi.begin(ssid, pass); + } + + // print the SSID of the network you're attached to: + Serial.print("SSID: "); + Serial.println(WiFi.SSID()); + + // print your WiFi shield's IP address: + IPAddress ip = WiFi.localIP(); + Serial.print("IP Address: "); + Serial.println(ip); +} + +void loop() { + + Serial.println("making GET request"); + client.beginRequest(); + client.get("/"); + client.sendHeader("X-CUSTOM-HEADER", "custom_value"); + client.endRequest(); + + // read the status code and body of the response + statusCode = client.responseStatusCode(); + response = client.responseBody(); + + Serial.print("GET Status code: "); + Serial.println(statusCode); + Serial.print("GET Response: "); + Serial.println(response); + + Serial.println("Wait five seconds"); + delay(5000); + + Serial.println("making POST request"); + String postData = "name=Alice&age=12"; + client.beginRequest(); + client.post("/"); + client.sendHeader(HTTP_HEADER_CONTENT_TYPE, "application/x-www-form-urlencoded"); + client.sendHeader(HTTP_HEADER_CONTENT_LENGTH, postData.length()); + client.sendHeader("X-CUSTOM-HEADER", "custom_value"); + client.endRequest(); + client.write((const byte*)postData.c_str(), postData.length()); + + // read the status code and body of the response + statusCode = client.responseStatusCode(); + response = client.responseBody(); + + Serial.print("POST Status code: "); + Serial.println(statusCode); + Serial.print("POST Response: "); + Serial.println(response); + + Serial.println("Wait five seconds"); + delay(5000); +} diff --git a/examples/CustomHeader/config.h b/examples/CustomHeader/config.h new file mode 100644 index 0000000..7765359 --- /dev/null +++ b/examples/CustomHeader/config.h @@ -0,0 +1,3 @@ +char ssid[] = "ssid"; // your network SSID (name) +char pass[] = "password"; // your network password + From 5bda5b6f07fba5273dfe4035e33dc635a4c5f887 Mon Sep 17 00:00:00 2001 From: agdl Date: Thu, 12 Jan 2017 16:14:59 +0100 Subject: [PATCH 055/125] release commit --- CHANGELOG.md | 10 ++++++++-- library.properties | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 19c448c..ccb98b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,14 @@ -## ArduinoHttpClient 0.1.0 - 2016.07.05 +## ArduinoHttpClient 0.2.0 - 2017.01.12 -* Initial release +* Added PATCH method +* Added basic auth example +* Added custom header example ## ArduinoHttpClient 0.1.1 - 2016.12.16 * More robust response parser +## ArduinoHttpClient 0.1.0 - 2016.07.05 + +* Initial release + diff --git a/library.properties b/library.properties index cc6904c..c906b45 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=ArduinoHttpClient -version=0.1.1 +version=0.2.0 author=Arduino maintainer=Arduino sentence=[EXPERIMENTAL] Easily interact with web servers from Arduino, using HTTP and WebSocket's. From 522cf5d11aa43cf4844cce722bd611bf64868151 Mon Sep 17 00:00:00 2001 From: Sandeep Mistry Date: Thu, 23 Mar 2017 14:41:41 -0400 Subject: [PATCH 056/125] Add support for chunked response bodies --- keywords.txt | 1 + src/HttpClient.cpp | 104 +++++++++++++++++++++++++++++++++++++++++++-- src/HttpClient.h | 22 ++++++++-- 3 files changed, 120 insertions(+), 7 deletions(-) diff --git a/keywords.txt b/keywords.txt index 7c2061c..1d8bb80 100644 --- a/keywords.txt +++ b/keywords.txt @@ -30,6 +30,7 @@ endOfHeadersReached KEYWORD2 endOfBodyReached KEYWORD2 completed KEYWORD2 contentLength KEYWORD2 +isChunked KEYWORD2 connectionKeepAlive KEYWORD2 noDefaultRequestHeaders KEYWORD2 headerAvailable KEYWORD2 diff --git a/src/HttpClient.cpp b/src/HttpClient.cpp index f3accc3..2c6d941 100644 --- a/src/HttpClient.cpp +++ b/src/HttpClient.cpp @@ -8,6 +8,7 @@ // Initialize constants const char* HttpClient::kUserAgent = "Arduino/2.2.0"; const char* HttpClient::kContentLengthPrefix = HTTP_HEADER_CONTENT_LENGTH ": "; +const char* HttpClient::kTransferEncodingChunked = HTTP_HEADER_TRANSFER_ENCODING ": " HTTP_HEADER_VALUE_CHUNKED; HttpClient::HttpClient(Client& aClient, const char* aServerName, uint16_t aServerPort) : iClient(&aClient), iServerName(aServerName), iServerAddress(), iServerPort(aServerPort), @@ -35,6 +36,9 @@ void HttpClient::resetState() iContentLength = kNoContentLengthHeader; iBodyLengthConsumed = 0; iContentLengthPtr = kContentLengthPrefix; + iTransferEncodingChunkedPtr = kTransferEncodingChunked; + iIsChunked = false; + iChunkLength = 0; iHttpResponseTimeout = kHttpResponseTimeout; } @@ -62,7 +66,7 @@ void HttpClient::beginRequest() int HttpClient::startRequest(const char* aURLPath, const char* aHttpMethod, const char* aContentType, int aContentLength, const byte aBody[]) { - if (iState == eReadingBody) + if (iState == eReadingBody || iState == eReadingChunkLength || iState == eReadingBodyChunk) { flushClientRx(); @@ -528,6 +532,11 @@ int HttpClient::skipResponseHeaders() } } +bool HttpClient::endOfHeadersReached() +{ + return (iState == eReadingBody || iState == eReadingChunkLength || iState == eReadingBodyChunk); +}; + int HttpClient::contentLength() { // skip the response headers, if they haven't been read already @@ -590,8 +599,65 @@ bool HttpClient::endOfBodyReached() return false; } +int HttpClient::available() +{ + if (iState == eReadingChunkLength) + { + while (iClient->available()) + { + char c = iClient->read(); + + if (c == '\n') + { + iState = eReadingBodyChunk; + break; + } + else if (c == '\r') + { + // no-op + } + else if (isHexadecimalDigit(c)) + { + char digit[2] = {c, '\0'}; + + iChunkLength = (iChunkLength * 16) + strtol(digit, NULL, 16); + } + } + } + + if (iState == eReadingBodyChunk && iChunkLength == 0) + { + iState = eReadingChunkLength; + } + + if (iState == eReadingChunkLength) + { + return 0; + } + + int clientAvailable = iClient->available(); + + if (iState == eReadingBodyChunk) + { + return min(clientAvailable, iChunkLength); + } + else + { + return clientAvailable; + } +} + + int HttpClient::read() { + if (iState == eReadingBodyChunk) + { + if (!available()) + { + return -1; + } + } + int ret = iClient->read(); if (ret >= 0) { @@ -601,6 +667,16 @@ int HttpClient::read() // So keep track of how many bytes are left iBodyLengthConsumed++; } + + if (iState == eReadingBodyChunk) + { + iChunkLength--; + + if (iChunkLength == 0) + { + iState = eReadingChunkLength; + } + } } return ret; } @@ -714,7 +790,18 @@ int HttpClient::readHeader() iBodyLengthConsumed = 0; } } - else if ((iContentLengthPtr == kContentLengthPrefix) && (c == '\r')) + else if (*iTransferEncodingChunkedPtr == c) + { + // This character matches, just move along + iTransferEncodingChunkedPtr++; + if (*iTransferEncodingChunkedPtr == '\0') + { + // We've reached the end of the Transfer Encoding: chunked header + iIsChunked = true; + iState = eSkipToEndOfHeader; + } + } + else if (((iContentLengthPtr == kContentLengthPrefix) && (iTransferEncodingChunkedPtr == kTransferEncodingChunked)) && (c == '\r')) { // We've found a '\r' at the start of a line, so this is probably // the end of the headers @@ -722,7 +809,7 @@ int HttpClient::readHeader() } else { - // This isn't the Content-Length header, skip to the end of the line + // This isn't the Content-Length or Transfer Encoding chunked header, skip to the end of the line iState = eSkipToEndOfHeader; } break; @@ -742,7 +829,15 @@ int HttpClient::readHeader() case eLineStartingCRFound: if (c == '\n') { - iState = eReadingBody; + if (iIsChunked) + { + iState = eReadingChunkLength; + iChunkLength = 0; + } + else + { + iState = eReadingBody; + } } break; default: @@ -755,6 +850,7 @@ int HttpClient::readHeader() // We've got to the end of this line, start processing again iState = eStatusCodeRead; iContentLengthPtr = kContentLengthPrefix; + iTransferEncodingChunkedPtr = kTransferEncodingChunked; } // And return the character read to whoever wants it return c; diff --git a/src/HttpClient.h b/src/HttpClient.h index eaab360..7433f67 100644 --- a/src/HttpClient.h +++ b/src/HttpClient.h @@ -34,7 +34,9 @@ static const int HTTP_ERROR_INVALID_RESPONSE =-4; #define HTTP_HEADER_CONTENT_LENGTH "Content-Length" #define HTTP_HEADER_CONTENT_TYPE "Content-Type" #define HTTP_HEADER_CONNECTION "Connection" +#define HTTP_HEADER_TRANSFER_ENCODING "Transfer-Encoding" #define HTTP_HEADER_USER_AGENT "User-Agent" +#define HTTP_HEADER_VALUE_CHUNKED "chunked" class HttpClient : public Client { @@ -247,7 +249,7 @@ class HttpClient : public Client /** Test whether all of the response headers have been consumed. @return true if we are now processing the response body, else false */ - bool endOfHeadersReached() { return (iState == eReadingBody); }; + bool endOfHeadersReached(); /** Test whether the end of the body has been reached. Only works if the Content-Length header was returned by the server @@ -265,6 +267,11 @@ class HttpClient : public Client */ int contentLength(); + /** Returns if the response body is chunked + @return true if response body is chunked, false otherwise + */ + int isChunked() { return iIsChunked; } + /** Return the response body as a String Also skips response headers if they have not been read already MUST be called after responseStatusCode() @@ -286,7 +293,7 @@ class HttpClient : public Client virtual size_t write(uint8_t aByte) { if (iState < eRequestSent) { finishHeaders(); }; return iClient-> write(aByte); }; virtual size_t write(const uint8_t *aBuffer, size_t aSize) { if (iState < eRequestSent) { finishHeaders(); }; return iClient->write(aBuffer, aSize); }; // Inherited from Stream - virtual int available() { return iClient->available(); }; + virtual int available(); /** Read the next byte from the server. @return Byte read or -1 if there are no bytes available. */ @@ -332,6 +339,7 @@ class HttpClient : public Client // processing) static const int kHttpResponseTimeout = 30*1000; static const char* kContentLengthPrefix; + static const char* kTransferEncodingChunked; typedef enum { eIdle, eRequestStarted, @@ -341,7 +349,9 @@ class HttpClient : public Client eReadingContentLength, eSkipToEndOfHeader, eLineStartingCRFound, - eReadingBody + eReadingBody, + eReadingChunkLength, + eReadingBodyChunk } tHttpState; // Client we're using Client* iClient; @@ -360,6 +370,12 @@ class HttpClient : public Client int iBodyLengthConsumed; // How far through a Content-Length header prefix we are const char* iContentLengthPtr; + // How far through a Transfer-Encoding chunked header we are + const char* iTransferEncodingChunkedPtr; + // Stores if the response body is chunked + bool iIsChunked; + // Stores the value of the current chunk length, if present + int iChunkLength; uint32_t iHttpResponseTimeout; bool iConnectionClose; bool iSendDefaultRequestHeaders; From 2a9c01c21070dd035ed8b9d4374dee9cfe2bcb38 Mon Sep 17 00:00:00 2001 From: Sandeep Mistry Date: Sat, 25 Mar 2017 09:40:52 -0400 Subject: [PATCH 057/125] Change read() to always check available() if response is chunked --- src/HttpClient.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/HttpClient.cpp b/src/HttpClient.cpp index 2c6d941..8a5b5c9 100644 --- a/src/HttpClient.cpp +++ b/src/HttpClient.cpp @@ -650,12 +650,9 @@ int HttpClient::available() int HttpClient::read() { - if (iState == eReadingBodyChunk) + if (iIsChunked && !available()) { - if (!available()) - { - return -1; - } + return -1; } int ret = iClient->read(); From 2394bbb7cd3b1c11c8363429d52c33c39bb09a81 Mon Sep 17 00:00:00 2001 From: Sandeep Mistry Date: Tue, 28 Mar 2017 14:09:09 -0400 Subject: [PATCH 058/125] Add initial PostWithHeaders example --- examples/PostWithHeaders/PostWithHeaders.ino | 75 ++++++++++++++++++++ examples/PostWithHeaders/config.h | 2 + 2 files changed, 77 insertions(+) create mode 100644 examples/PostWithHeaders/PostWithHeaders.ino create mode 100644 examples/PostWithHeaders/config.h diff --git a/examples/PostWithHeaders/PostWithHeaders.ino b/examples/PostWithHeaders/PostWithHeaders.ino new file mode 100644 index 0000000..b39afae --- /dev/null +++ b/examples/PostWithHeaders/PostWithHeaders.ino @@ -0,0 +1,75 @@ +/* + POST with headers client for ArduinoHttpClient library + Connects to server once every five seconds, sends a POST request + with custome headers and a request body + + note: WiFi SSID and password are stored in config.h file. + If it is not present, add a new tab, call it "config.h" + and add the following variables: + char ssid[] = "ssid"; // your network SSID (name) + char pass[] = "password"; // your network password + + created 14 Feb 2016 + by Tom Igoe + modified 18 Mar 2017 + by Sandeep Mistry + + this example is in the public domain + */ +#include +#include +#include "config.h" + +char serverAddress[] = "192.168.0.3"; // server address +int port = 8080; + +WiFiClient wifi; +HttpClient client = HttpClient(wifi, serverAddress, port); +int status = WL_IDLE_STATUS; +String response; +int statusCode = 0; + +void setup() { + Serial.begin(9600); + while ( status != WL_CONNECTED) { + Serial.print("Attempting to connect to Network named: "); + Serial.println(ssid); // print the network name (SSID); + + // Connect to WPA/WPA2 network: + status = WiFi.begin(ssid, pass); + } + + // print the SSID of the network you're attached to: + Serial.print("SSID: "); + Serial.println(WiFi.SSID()); + + // print your WiFi shield's IP address: + IPAddress ip = WiFi.localIP(); + Serial.print("IP Address: "); + Serial.println(ip); +} + +void loop() { + Serial.println("making POST request"); + String postData = "name=Alice&age=12"; + + client.beginRequest(); + client.post("/"); + client.sendHeader("Content-Type", "application/x-www-form-urlencoded"); + client.sendHeader("Content-Length", postData.length()); + client.sendHeader("X-Custom-Header", "custom-header-value"); + client.endRequest(); + client.print(postData); + + // read the status code and body of the response + statusCode = client.responseStatusCode(); + response = client.responseBody(); + + Serial.print("Status code: "); + Serial.println(statusCode); + Serial.print("Response: "); + Serial.println(response); + + Serial.println("Wait five seconds"); + delay(5000); +} diff --git a/examples/PostWithHeaders/config.h b/examples/PostWithHeaders/config.h new file mode 100644 index 0000000..c263766 --- /dev/null +++ b/examples/PostWithHeaders/config.h @@ -0,0 +1,2 @@ +char ssid[] = "ssid"; // your network SSID (name) +char pass[] = "password"; // your network password From 448a1520c892f7667ab11f9aa6141fd9d5376533 Mon Sep 17 00:00:00 2001 From: Sandeep Mistry Date: Tue, 28 Mar 2017 14:12:20 -0400 Subject: [PATCH 059/125] Add new beginBody API --- examples/PostWithHeaders/PostWithHeaders.ino | 3 ++- keywords.txt | 1 + src/HttpClient.cpp | 5 +++++ src/HttpClient.h | 7 +++++++ 4 files changed, 15 insertions(+), 1 deletion(-) diff --git a/examples/PostWithHeaders/PostWithHeaders.ino b/examples/PostWithHeaders/PostWithHeaders.ino index b39afae..06139db 100644 --- a/examples/PostWithHeaders/PostWithHeaders.ino +++ b/examples/PostWithHeaders/PostWithHeaders.ino @@ -58,8 +58,9 @@ void loop() { client.sendHeader("Content-Type", "application/x-www-form-urlencoded"); client.sendHeader("Content-Length", postData.length()); client.sendHeader("X-Custom-Header", "custom-header-value"); - client.endRequest(); + client.beginBody(); client.print(postData); + client.endRequest(); // read the status code and body of the response statusCode = client.responseStatusCode(); diff --git a/keywords.txt b/keywords.txt index 7c2061c..1f4a10e 100644 --- a/keywords.txt +++ b/keywords.txt @@ -20,6 +20,7 @@ put KEYWORD2 patch KEYWORD2 startRequest KEYWORD2 beginRequest KEYWORD2 +beginBody KEYWORD2 sendHeader KEYWORD2 sendBasicAuth KEYWORD2 endRequest KEYWORD2 diff --git a/src/HttpClient.cpp b/src/HttpClient.cpp index f3accc3..b46f628 100644 --- a/src/HttpClient.cpp +++ b/src/HttpClient.cpp @@ -263,6 +263,11 @@ void HttpClient::flushClientRx() } void HttpClient::endRequest() +{ + beginBody(); +} + +void HttpClient::beginBody() { if (iState < eRequestSent) { diff --git a/src/HttpClient.h b/src/HttpClient.h index eaab360..e627b21 100644 --- a/src/HttpClient.h +++ b/src/HttpClient.h @@ -62,6 +62,13 @@ class HttpClient : public Client */ void endRequest(); + /** Start the body of a more complex request. + Use this when you need to send the body after additional headers + in the request, but can optionally call endRequest() when + you are finished. + */ + void beginBody(); + /** Connect to the server and start to send a GET request. @param aURLPath Url to request @return 0 if successful, else error From fdedff59b8a795c5205d9d90fa4e5cee28f3cc78 Mon Sep 17 00:00:00 2001 From: Sandeep Mistry Date: Thu, 13 Apr 2017 09:33:01 -0400 Subject: [PATCH 060/125] Rename isChunked to isResponseChunked --- keywords.txt | 2 +- src/HttpClient.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/keywords.txt b/keywords.txt index 1d8bb80..8d6a7ca 100644 --- a/keywords.txt +++ b/keywords.txt @@ -30,7 +30,7 @@ endOfHeadersReached KEYWORD2 endOfBodyReached KEYWORD2 completed KEYWORD2 contentLength KEYWORD2 -isChunked KEYWORD2 +isResponseChunked KEYWORD2 connectionKeepAlive KEYWORD2 noDefaultRequestHeaders KEYWORD2 headerAvailable KEYWORD2 diff --git a/src/HttpClient.h b/src/HttpClient.h index 7433f67..e120ef7 100644 --- a/src/HttpClient.h +++ b/src/HttpClient.h @@ -270,7 +270,7 @@ class HttpClient : public Client /** Returns if the response body is chunked @return true if response body is chunked, false otherwise */ - int isChunked() { return iIsChunked; } + int isResponseChunked() { return iIsChunked; } /** Return the response body as a String Also skips response headers if they have not been read already From 0dd9f4209586734e3980737bc3548699555211aa Mon Sep 17 00:00:00 2001 From: Sandeep Mistry Date: Thu, 13 Apr 2017 09:33:29 -0400 Subject: [PATCH 061/125] Add new /chunked endpoint to Node.js test server --- examples/node_test_server/getPostPutDelete.js | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/examples/node_test_server/getPostPutDelete.js b/examples/node_test_server/getPostPutDelete.js index e055b65..f3dd4d8 100644 --- a/examples/node_test_server/getPostPutDelete.js +++ b/examples/node_test_server/getPostPutDelete.js @@ -21,6 +21,51 @@ function serverStart() { console.log('Server listening on port '+ port); } +app.get('/chunked', function(request, response) { + response.write('\n'); + response.write(' `:;;;,` .:;;:. \n'); + response.write(' .;;;;;;;;;;;` :;;;;;;;;;;: TM \n'); + response.write(' `;;;;;;;;;;;;;;;` :;;;;;;;;;;;;;;; \n'); + response.write(' :;;;;;;;;;;;;;;;;;; `;;;;;;;;;;;;;;;;;; \n'); + response.write(' ;;;;;;;;;;;;;;;;;;;;; .;;;;;;;;;;;;;;;;;;;; \n'); + response.write(' ;;;;;;;;:` `;;;;;;;;; ,;;;;;;;;.` .;;;;;;;; \n'); + response.write(' .;;;;;;, :;;;;;;; .;;;;;;; ;;;;;;; \n'); + response.write(' ;;;;;; ;;;;;;; ;;;;;;, ;;;;;;. \n'); + response.write(' ,;;;;; ;;;;;;.;;;;;;` ;;;;;; \n'); + response.write(' ;;;;;. ;;;;;;;;;;;` ``` ;;;;;`\n'); + response.write(' ;;;;; ;;;;;;;;;, ;;; .;;;;;\n'); + response.write('`;;;;: `;;;;;;;; ;;; ;;;;;\n'); + response.write(',;;;;` `,,,,,,,, ;;;;;;; .,,;;;,,, ;;;;;\n'); + response.write(':;;;;` .;;;;;;;; ;;;;;, :;;;;;;;; ;;;;;\n'); + response.write(':;;;;` .;;;;;;;; `;;;;;; :;;;;;;;; ;;;;;\n'); + response.write('.;;;;. ;;;;;;;. ;;; ;;;;;\n'); + response.write(' ;;;;; ;;;;;;;;; ;;; ;;;;;\n'); + response.write(' ;;;;; .;;;;;;;;;; ;;; ;;;;;,\n'); + response.write(' ;;;;;; `;;;;;;;;;;;; ;;;;; \n'); + response.write(' `;;;;;, .;;;;;; ;;;;;;; ;;;;;; \n'); + response.write(' ;;;;;;: :;;;;;;. ;;;;;;; ;;;;;; \n'); + response.write(' ;;;;;;;` .;;;;;;;, ;;;;;;;; ;;;;;;;: \n'); + response.write(' ;;;;;;;;;:,:;;;;;;;;;: ;;;;;;;;;;:,;;;;;;;;;; \n'); + response.write(' `;;;;;;;;;;;;;;;;;;;. ;;;;;;;;;;;;;;;;;;;; \n'); + response.write(' ;;;;;;;;;;;;;;;;; :;;;;;;;;;;;;;;;;: \n'); + response.write(' ,;;;;;;;;;;;;;, ;;;;;;;;;;;;;; \n'); + response.write(' .;;;;;;;;;` ,;;;;;;;;: \n'); + response.write(' \n'); + response.write(' \n'); + response.write(' \n'); + response.write(' \n'); + response.write(' ;;; ;;;;;` ;;;;: .;; ;; ,;;;;;, ;;. `;, ;;;; \n'); + response.write(' ;;; ;;:;;; ;;;;;; .;; ;; ,;;;;;: ;;; `;, ;;;:;; \n'); + response.write(' ,;:; ;; ;; ;; ;; .;; ;; ,;, ;;;,`;, ;; ;; \n'); + response.write(' ;; ;: ;; ;; ;; ;; .;; ;; ,;, ;;;;`;, ;; ;;. \n'); + response.write(' ;: ;; ;;;;;: ;; ;; .;; ;; ,;, ;;`;;;, ;; ;;` \n'); + response.write(' ,;;;;; ;;`;; ;; ;; .;; ;; ,;, ;; ;;;, ;; ;; \n'); + response.write(' ;; ,;, ;; .;; ;;;;;: ;;;;;: ,;;;;;: ;; ;;, ;;;;;; \n'); + response.write(' ;; ;; ;; ;;` ;;;;. `;;;: ,;;;;;, ;; ;;, ;;;; \n'); + response.write('\n'); + response.end(); +}); + // this is the POST handler: app.all('/*', function (request, response) { console.log('Got a ' + request.method + ' request'); From 4e476a651de830a8f0f5d3a162772ceefe1f32ee Mon Sep 17 00:00:00 2001 From: Sandeep Mistry Date: Thu, 20 Apr 2017 10:08:26 -0400 Subject: [PATCH 062/125] Version 0.3.0 --- CHANGELOG.md | 6 ++++++ library.properties | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ccb98b6..16ee89f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## ArduinoHttpClient 0.3.0 - 2017.04.20 + +* Added support for PATCH operations +* Added support for chunked response bodies +* Added new beginBody API + ## ArduinoHttpClient 0.2.0 - 2017.01.12 * Added PATCH method diff --git a/library.properties b/library.properties index c906b45..affbb18 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=ArduinoHttpClient -version=0.2.0 +version=0.3.0 author=Arduino maintainer=Arduino sentence=[EXPERIMENTAL] Easily interact with web servers from Arduino, using HTTP and WebSocket's. From cfe046dcb39967ca90db0cd84505cc0f00e97e80 Mon Sep 17 00:00:00 2001 From: Sandeep Mistry Date: Tue, 2 May 2017 13:18:16 -0400 Subject: [PATCH 063/125] Increase WebSocket sec key length to 24 characters --- src/WebSocketClient.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/WebSocketClient.cpp b/src/WebSocketClient.cpp index f78d946..ab41b0a 100644 --- a/src/WebSocketClient.cpp +++ b/src/WebSocketClient.cpp @@ -35,8 +35,8 @@ int WebSocketClient::begin(const char* aPath) if (status == 0) { - uint8_t randomKey[13]; - char base64RandomKey[21]; + uint8_t randomKey[16]; + char base64RandomKey[25]; // create a random key for the connection upgrade for (int i = 0; i < (int)sizeof(randomKey); i++) From 889d7b35d9de69557adc70fed57980a80cbbe173 Mon Sep 17 00:00:00 2001 From: marcus johansson Date: Thu, 14 Sep 2017 16:27:32 +0200 Subject: [PATCH 064/125] St update --- examples/BasicAuthGet/BasicAuthGet.ino | 12 ++++++------ examples/BasicAuthGet/arduino_secrets.h | 3 +++ examples/BasicAuthGet/config.h | 3 --- examples/CustomHeader/CustomHeader.ino | 7 ++++++- examples/CustomHeader/arduino_secrets.h | 3 +++ examples/CustomHeader/config.h | 3 --- examples/DweetGet/DweetGet.ino | 13 +++++++------ examples/DweetGet/arduino_secrets.h | 3 +++ examples/DweetPost/DweetPost.ino | 7 ++++++- examples/DweetPost/arduino_secrets.h | 3 +++ examples/DweetPost/config.h | 2 -- examples/HueBlink/HueBlink.ino | 14 ++++++++------ examples/HueBlink/arduino_secrets.h | 3 +++ examples/HueBlink/config.h | 2 -- examples/PostWithHeaders/PostWithHeaders.ino | 15 +++++++++------ examples/PostWithHeaders/arduino_secrets.h | 3 +++ examples/PostWithHeaders/config.h | 2 -- examples/SimpleDelete/SimpleDelete.ino | 15 +++++++++------ examples/SimpleDelete/arduino_secrets.h | 3 +++ examples/SimpleDelete/config.h | 2 -- examples/SimpleGet/SimpleGet.ino | 15 +++++++++------ examples/SimpleGet/arduino_secrets.h | 3 +++ examples/SimpleGet/config.h | 2 -- examples/SimpleHttpExample/SimpleHttpExample.ino | 10 ++++++++-- examples/SimpleHttpExample/arduino_secrets.h | 3 +++ examples/SimplePost/SimplePost.ino | 14 ++++++++------ examples/SimplePost/arduino_secrets.h | 3 +++ examples/SimplePost/config.h | 2 -- examples/SimplePut/SimplePut.ino | 14 ++++++++------ examples/SimplePut/arduino_secrets.h | 3 +++ examples/SimplePut/config.h | 2 -- examples/SimpleWebSocket/SimpleWebSocket.ino | 12 ++++++------ examples/SimpleWebSocket/arduino_secrets.h | 3 +++ examples/SimpleWebSocket/config.h | 3 --- 34 files changed, 126 insertions(+), 81 deletions(-) create mode 100644 examples/BasicAuthGet/arduino_secrets.h delete mode 100644 examples/BasicAuthGet/config.h create mode 100644 examples/CustomHeader/arduino_secrets.h delete mode 100644 examples/CustomHeader/config.h create mode 100644 examples/DweetGet/arduino_secrets.h create mode 100644 examples/DweetPost/arduino_secrets.h delete mode 100644 examples/DweetPost/config.h create mode 100644 examples/HueBlink/arduino_secrets.h delete mode 100644 examples/HueBlink/config.h create mode 100644 examples/PostWithHeaders/arduino_secrets.h delete mode 100644 examples/PostWithHeaders/config.h create mode 100644 examples/SimpleDelete/arduino_secrets.h delete mode 100644 examples/SimpleDelete/config.h create mode 100644 examples/SimpleGet/arduino_secrets.h delete mode 100644 examples/SimpleGet/config.h create mode 100644 examples/SimpleHttpExample/arduino_secrets.h create mode 100644 examples/SimplePost/arduino_secrets.h delete mode 100644 examples/SimplePost/config.h create mode 100644 examples/SimplePut/arduino_secrets.h delete mode 100644 examples/SimplePut/config.h create mode 100644 examples/SimpleWebSocket/arduino_secrets.h delete mode 100644 examples/SimpleWebSocket/config.h diff --git a/examples/BasicAuthGet/BasicAuthGet.ino b/examples/BasicAuthGet/BasicAuthGet.ino index b8e0f51..e793d47 100644 --- a/examples/BasicAuthGet/BasicAuthGet.ino +++ b/examples/BasicAuthGet/BasicAuthGet.ino @@ -2,11 +2,7 @@ GET client with HTTP basic authentication for ArduinoHttpClient library Connects to server once every five seconds, sends a GET request - note: WiFi SSID and password are stored in config.h file. - If it is not present, add a new tab, call it "config.h" - and add the following variables: - char ssid[] = "ssid"; // your network SSID (name) - char pass[] = "password"; // your network password + created 14 Feb 2016 by Tom Igoe @@ -17,7 +13,11 @@ */ #include #include -#include "config.h" +#include "arduino_secrets.h" +///////please enter your sensitive data in the Secret tab/arduino_secrets.h +/////// Wifi Settings /////// +char ssid[] = SECRET_SSID; +char pass[] = SECRET_PASS; char serverAddress[] = "192.168.0.3"; // server address int port = 8080; diff --git a/examples/BasicAuthGet/arduino_secrets.h b/examples/BasicAuthGet/arduino_secrets.h new file mode 100644 index 0000000..a8ff904 --- /dev/null +++ b/examples/BasicAuthGet/arduino_secrets.h @@ -0,0 +1,3 @@ +#define SECRET_SSID "" +#define SECRET_PASS "" + diff --git a/examples/BasicAuthGet/config.h b/examples/BasicAuthGet/config.h deleted file mode 100644 index 7765359..0000000 --- a/examples/BasicAuthGet/config.h +++ /dev/null @@ -1,3 +0,0 @@ -char ssid[] = "ssid"; // your network SSID (name) -char pass[] = "password"; // your network password - diff --git a/examples/CustomHeader/CustomHeader.ino b/examples/CustomHeader/CustomHeader.ino index 898c97b..fa44aca 100644 --- a/examples/CustomHeader/CustomHeader.ino +++ b/examples/CustomHeader/CustomHeader.ino @@ -16,7 +16,12 @@ #include #include -#include "config.h" + +#include "arduino_secrets.h" +///////please enter your sensitive data in the Secret tab/arduino_secrets.h +/////// Wifi Settings /////// +char ssid[] = SECRET_SSID; +char pass[] = SECRET_PASS; char serverAddress[] = "192.168.0.3"; // server address int port = 8080; diff --git a/examples/CustomHeader/arduino_secrets.h b/examples/CustomHeader/arduino_secrets.h new file mode 100644 index 0000000..a8ff904 --- /dev/null +++ b/examples/CustomHeader/arduino_secrets.h @@ -0,0 +1,3 @@ +#define SECRET_SSID "" +#define SECRET_PASS "" + diff --git a/examples/CustomHeader/config.h b/examples/CustomHeader/config.h deleted file mode 100644 index 7765359..0000000 --- a/examples/CustomHeader/config.h +++ /dev/null @@ -1,3 +0,0 @@ -char ssid[] = "ssid"; // your network SSID (name) -char pass[] = "password"; // your network password - diff --git a/examples/DweetGet/DweetGet.ino b/examples/DweetGet/DweetGet.ino index 96f698d..51abd3f 100644 --- a/examples/DweetGet/DweetGet.ino +++ b/examples/DweetGet/DweetGet.ino @@ -9,11 +9,7 @@ For more on dweet.io, see https://dweet.io/play/ - note: WiFi SSID and password are stored in config.h file. - If it is not present, add a new tab, call it "config.h" - and add the following variables: - char ssid[] = "ssid"; // your network SSID (name) - char pass[] = "password"; // your network password + created 15 Feb 2016 updated 16 Feb 2016 @@ -23,7 +19,12 @@ */ #include #include -#include "config.h" + +#include "arduino_secrets.h" +///////please enter your sensitive data in the Secret tab/arduino_secrets.h +/////// Wifi Settings /////// +char ssid[] = SECRET_SSID; +char pass[] = SECRET_PASS; const char serverAddress[] = "dweet.io"; // server address int port = 80; diff --git a/examples/DweetGet/arduino_secrets.h b/examples/DweetGet/arduino_secrets.h new file mode 100644 index 0000000..a8ff904 --- /dev/null +++ b/examples/DweetGet/arduino_secrets.h @@ -0,0 +1,3 @@ +#define SECRET_SSID "" +#define SECRET_PASS "" + diff --git a/examples/DweetPost/DweetPost.ino b/examples/DweetPost/DweetPost.ino index 201e76c..41c6f2f 100644 --- a/examples/DweetPost/DweetPost.ino +++ b/examples/DweetPost/DweetPost.ino @@ -18,7 +18,12 @@ */ #include #include -#include "config.h" + +#include "arduino_secrets.h" +///////please enter your sensitive data in the Secret tab/arduino_secrets.h +/////// Wifi Settings /////// +char ssid[] = SECRET_SSID; +char pass[] = SECRET_PASS; const char serverAddress[] = "dweet.io"; // server address int port = 80; diff --git a/examples/DweetPost/arduino_secrets.h b/examples/DweetPost/arduino_secrets.h new file mode 100644 index 0000000..a8ff904 --- /dev/null +++ b/examples/DweetPost/arduino_secrets.h @@ -0,0 +1,3 @@ +#define SECRET_SSID "" +#define SECRET_PASS "" + diff --git a/examples/DweetPost/config.h b/examples/DweetPost/config.h deleted file mode 100644 index c263766..0000000 --- a/examples/DweetPost/config.h +++ /dev/null @@ -1,2 +0,0 @@ -char ssid[] = "ssid"; // your network SSID (name) -char pass[] = "password"; // your network password diff --git a/examples/HueBlink/HueBlink.ino b/examples/HueBlink/HueBlink.ino index a88bec9..c18ae47 100644 --- a/examples/HueBlink/HueBlink.ino +++ b/examples/HueBlink/HueBlink.ino @@ -13,11 +13,7 @@ This example shows how to concatenate Strings to assemble the PUT request and the body of the request. - note: WiFi SSID and password are stored in config.h file. - If it is not present, add a new tab, call it "config.h" - and add the following variables: - char ssid[] = "ssid"; // your network SSID (name) - char pass[] = "password"; // your network password + modified 15 Feb 2016 by Tom Igoe (tigoe) to match new API @@ -26,9 +22,15 @@ #include #include #include -#include "config.h" +#include "arduino_secrets.h" + +///////please enter your sensitive data in the Secret tab/arduino_secrets.h +/////// Wifi Settings /////// +char ssid[] = SECRET_SSID; +char pass[] = SECRET_PASS; int status = WL_IDLE_STATUS; // the Wifi radio's status + char hueHubIP[] = "192.168.0.3"; // IP address of the HUE bridge String hueUserName = "huebridgeusername"; // hue bridge username diff --git a/examples/HueBlink/arduino_secrets.h b/examples/HueBlink/arduino_secrets.h new file mode 100644 index 0000000..a8ff904 --- /dev/null +++ b/examples/HueBlink/arduino_secrets.h @@ -0,0 +1,3 @@ +#define SECRET_SSID "" +#define SECRET_PASS "" + diff --git a/examples/HueBlink/config.h b/examples/HueBlink/config.h deleted file mode 100644 index c263766..0000000 --- a/examples/HueBlink/config.h +++ /dev/null @@ -1,2 +0,0 @@ -char ssid[] = "ssid"; // your network SSID (name) -char pass[] = "password"; // your network password diff --git a/examples/PostWithHeaders/PostWithHeaders.ino b/examples/PostWithHeaders/PostWithHeaders.ino index 06139db..835a26f 100644 --- a/examples/PostWithHeaders/PostWithHeaders.ino +++ b/examples/PostWithHeaders/PostWithHeaders.ino @@ -3,11 +3,7 @@ Connects to server once every five seconds, sends a POST request with custome headers and a request body - note: WiFi SSID and password are stored in config.h file. - If it is not present, add a new tab, call it "config.h" - and add the following variables: - char ssid[] = "ssid"; // your network SSID (name) - char pass[] = "password"; // your network password + created 14 Feb 2016 by Tom Igoe @@ -18,7 +14,14 @@ */ #include #include -#include "config.h" + +#include "arduino_secrets.h" + +///////please enter your sensitive data in the Secret tab/arduino_secrets.h +/////// Wifi Settings /////// +char ssid[] = SECRET_SSID; +char pass[] = SECRET_PASS; + char serverAddress[] = "192.168.0.3"; // server address int port = 8080; diff --git a/examples/PostWithHeaders/arduino_secrets.h b/examples/PostWithHeaders/arduino_secrets.h new file mode 100644 index 0000000..a8ff904 --- /dev/null +++ b/examples/PostWithHeaders/arduino_secrets.h @@ -0,0 +1,3 @@ +#define SECRET_SSID "" +#define SECRET_PASS "" + diff --git a/examples/PostWithHeaders/config.h b/examples/PostWithHeaders/config.h deleted file mode 100644 index c263766..0000000 --- a/examples/PostWithHeaders/config.h +++ /dev/null @@ -1,2 +0,0 @@ -char ssid[] = "ssid"; // your network SSID (name) -char pass[] = "password"; // your network password diff --git a/examples/SimpleDelete/SimpleDelete.ino b/examples/SimpleDelete/SimpleDelete.ino index 9304be0..374a145 100644 --- a/examples/SimpleDelete/SimpleDelete.ino +++ b/examples/SimpleDelete/SimpleDelete.ino @@ -3,11 +3,7 @@ Connects to server once every five seconds, sends a DELETE request and a request body - note: WiFi SSID and password are stored in config.h file. - If it is not present, add a new tab, call it "config.h" - and add the following variables: - char ssid[] = "ssid"; // your network SSID (name) - char pass[] = "password"; // your network password + created 14 Feb 2016 by Tom Igoe @@ -16,7 +12,14 @@ */ #include #include -#include "config.h" + +#include "arduino_secrets.h" + +///////please enter your sensitive data in the Secret tab/arduino_secrets.h +/////// Wifi Settings /////// +char ssid[] = SECRET_SSID; +char pass[] = SECRET_PASS; + char serverAddress[] = "192.168.0.3"; // server address int port = 8080; diff --git a/examples/SimpleDelete/arduino_secrets.h b/examples/SimpleDelete/arduino_secrets.h new file mode 100644 index 0000000..a8ff904 --- /dev/null +++ b/examples/SimpleDelete/arduino_secrets.h @@ -0,0 +1,3 @@ +#define SECRET_SSID "" +#define SECRET_PASS "" + diff --git a/examples/SimpleDelete/config.h b/examples/SimpleDelete/config.h deleted file mode 100644 index c263766..0000000 --- a/examples/SimpleDelete/config.h +++ /dev/null @@ -1,2 +0,0 @@ -char ssid[] = "ssid"; // your network SSID (name) -char pass[] = "password"; // your network password diff --git a/examples/SimpleGet/SimpleGet.ino b/examples/SimpleGet/SimpleGet.ino index dc68eda..75db9a7 100644 --- a/examples/SimpleGet/SimpleGet.ino +++ b/examples/SimpleGet/SimpleGet.ino @@ -2,11 +2,7 @@ Simple GET client for ArduinoHttpClient library Connects to server once every five seconds, sends a GET request - note: WiFi SSID and password are stored in config.h file. - If it is not present, add a new tab, call it "config.h" - and add the following variables: - char ssid[] = "ssid"; // your network SSID (name) - char pass[] = "password"; // your network password + created 14 Feb 2016 by Tom Igoe @@ -15,7 +11,14 @@ */ #include #include -#include "config.h" + +#include "arduino_secrets.h" + +///////please enter your sensitive data in the Secret tab/arduino_secrets.h +/////// Wifi Settings /////// +char ssid[] = SECRET_SSID; +char pass[] = SECRET_PASS; + char serverAddress[] = "192.168.0.3"; // server address int port = 8080; diff --git a/examples/SimpleGet/arduino_secrets.h b/examples/SimpleGet/arduino_secrets.h new file mode 100644 index 0000000..a8ff904 --- /dev/null +++ b/examples/SimpleGet/arduino_secrets.h @@ -0,0 +1,3 @@ +#define SECRET_SSID "" +#define SECRET_PASS "" + diff --git a/examples/SimpleGet/config.h b/examples/SimpleGet/config.h deleted file mode 100644 index c263766..0000000 --- a/examples/SimpleGet/config.h +++ /dev/null @@ -1,2 +0,0 @@ -char ssid[] = "ssid"; // your network SSID (name) -char pass[] = "password"; // your network password diff --git a/examples/SimpleHttpExample/SimpleHttpExample.ino b/examples/SimpleHttpExample/SimpleHttpExample.ino index a6aa6b6..f64b9ba 100644 --- a/examples/SimpleHttpExample/SimpleHttpExample.ino +++ b/examples/SimpleHttpExample/SimpleHttpExample.ino @@ -11,8 +11,14 @@ // This example downloads the URL "http://arduino.cc/" -char ssid[] = "yourNetwork"; // your network SSID (name) -char pass[] = "secretPassword"; // your network password +#include "arduino_secrets.h" + +///////please enter your sensitive data in the Secret tab/arduino_secrets.h +/////// Wifi Settings /////// +char ssid[] = SECRET_SSID; +char pass[] = SECRET_PASS; + + // Name of the server we want to connect to const char kHostname[] = "arduino.cc"; diff --git a/examples/SimpleHttpExample/arduino_secrets.h b/examples/SimpleHttpExample/arduino_secrets.h new file mode 100644 index 0000000..a8ff904 --- /dev/null +++ b/examples/SimpleHttpExample/arduino_secrets.h @@ -0,0 +1,3 @@ +#define SECRET_SSID "" +#define SECRET_PASS "" + diff --git a/examples/SimplePost/SimplePost.ino b/examples/SimplePost/SimplePost.ino index 42dfea8..6cc3517 100644 --- a/examples/SimplePost/SimplePost.ino +++ b/examples/SimplePost/SimplePost.ino @@ -3,11 +3,7 @@ Connects to server once every five seconds, sends a POST request and a request body - note: WiFi SSID and password are stored in config.h file. - If it is not present, add a new tab, call it "config.h" - and add the following variables: - char ssid[] = "ssid"; // your network SSID (name) - char pass[] = "password"; // your network password + created 14 Feb 2016 by Tom Igoe @@ -16,7 +12,13 @@ */ #include #include -#include "config.h" +#include "arduino_secrets.h" + +///////please enter your sensitive data in the Secret tab/arduino_secrets.h +/////// Wifi Settings /////// +char ssid[] = SECRET_SSID; +char pass[] = SECRET_PASS; + char serverAddress[] = "192.168.0.3"; // server address int port = 8080; diff --git a/examples/SimplePost/arduino_secrets.h b/examples/SimplePost/arduino_secrets.h new file mode 100644 index 0000000..a8ff904 --- /dev/null +++ b/examples/SimplePost/arduino_secrets.h @@ -0,0 +1,3 @@ +#define SECRET_SSID "" +#define SECRET_PASS "" + diff --git a/examples/SimplePost/config.h b/examples/SimplePost/config.h deleted file mode 100644 index c263766..0000000 --- a/examples/SimplePost/config.h +++ /dev/null @@ -1,2 +0,0 @@ -char ssid[] = "ssid"; // your network SSID (name) -char pass[] = "password"; // your network password diff --git a/examples/SimplePut/SimplePut.ino b/examples/SimplePut/SimplePut.ino index 30da9c0..1b06105 100644 --- a/examples/SimplePut/SimplePut.ino +++ b/examples/SimplePut/SimplePut.ino @@ -3,11 +3,7 @@ Connects to server once every five seconds, sends a PUT request and a request body - note: WiFi SSID and password are stored in config.h file. - If it is not present, add a new tab, call it "config.h" - and add the following variables: - char ssid[] = "ssid"; // your network SSID (name) - char pass[] = "password"; // your network password + created 14 Feb 2016 by Tom Igoe @@ -16,7 +12,13 @@ */ #include #include -#include "config.h" +#include "arduino_secrets.h" + +///////please enter your sensitive data in the Secret tab/arduino_secrets.h +/////// Wifi Settings /////// +char ssid[] = SECRET_SSID; +char pass[] = SECRET_PASS; + char serverAddress[] = "192.168.0.3"; // server address int port = 8080; diff --git a/examples/SimplePut/arduino_secrets.h b/examples/SimplePut/arduino_secrets.h new file mode 100644 index 0000000..a8ff904 --- /dev/null +++ b/examples/SimplePut/arduino_secrets.h @@ -0,0 +1,3 @@ +#define SECRET_SSID "" +#define SECRET_PASS "" + diff --git a/examples/SimplePut/config.h b/examples/SimplePut/config.h deleted file mode 100644 index c263766..0000000 --- a/examples/SimplePut/config.h +++ /dev/null @@ -1,2 +0,0 @@ -char ssid[] = "ssid"; // your network SSID (name) -char pass[] = "password"; // your network password diff --git a/examples/SimpleWebSocket/SimpleWebSocket.ino b/examples/SimpleWebSocket/SimpleWebSocket.ino index ccd97b7..baac12f 100644 --- a/examples/SimpleWebSocket/SimpleWebSocket.ino +++ b/examples/SimpleWebSocket/SimpleWebSocket.ino @@ -3,11 +3,6 @@ Connects to the WebSocket server, and sends a hello message every 5 seconds - note: WiFi SSID and password are stored in config.h file. - If it is not present, add a new tab, call it "config.h" - and add the following variables: - char ssid[] = "ssid"; // your network SSID (name) - char pass[] = "password"; // your network password created 28 Jun 2016 by Sandeep Mistry @@ -16,7 +11,12 @@ */ #include #include -#include "config.h" +#include "arduino_secrets.h" + +///////please enter your sensitive data in the Secret tab/arduino_secrets.h +/////// Wifi Settings /////// +char ssid[] = SECRET_SSID; +char pass[] = SECRET_PASS; char serverAddress[] = "echo.websocket.org"; // server address int port = 80; diff --git a/examples/SimpleWebSocket/arduino_secrets.h b/examples/SimpleWebSocket/arduino_secrets.h new file mode 100644 index 0000000..a8ff904 --- /dev/null +++ b/examples/SimpleWebSocket/arduino_secrets.h @@ -0,0 +1,3 @@ +#define SECRET_SSID "" +#define SECRET_PASS "" + diff --git a/examples/SimpleWebSocket/config.h b/examples/SimpleWebSocket/config.h deleted file mode 100644 index 7765359..0000000 --- a/examples/SimpleWebSocket/config.h +++ /dev/null @@ -1,3 +0,0 @@ -char ssid[] = "ssid"; // your network SSID (name) -char pass[] = "password"; // your network password - From acde3dce38fbd654a54220481ad92f9123bd6d79 Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Mon, 25 Sep 2017 10:19:59 +0200 Subject: [PATCH 065/125] Release 0.3.1 --- CHANGELOG.md | 5 +++++ library.properties | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 16ee89f..5645508 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## ArduinoHttpClient 0.3.1 - 2017.09.25 + +* Changed examples to support Arduino Create secret tabs +* Increase WebSocket secrect-key length to 24 characters + ## ArduinoHttpClient 0.3.0 - 2017.04.20 * Added support for PATCH operations diff --git a/library.properties b/library.properties index affbb18..2ac5bb5 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=ArduinoHttpClient -version=0.3.0 +version=0.3.1 author=Arduino maintainer=Arduino sentence=[EXPERIMENTAL] Easily interact with web servers from Arduino, using HTTP and WebSocket's. From 1f81c4e8aa8737719ffeeb4279a65041b93ab896 Mon Sep 17 00:00:00 2001 From: Johan Euphrosine Date: Sat, 16 Jun 2018 03:04:02 +0900 Subject: [PATCH 066/125] HttpClient: fix sendHeader docstring --- src/HttpClient.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/HttpClient.h b/src/HttpClient.h index c954872..6dde94f 100644 --- a/src/HttpClient.h +++ b/src/HttpClient.h @@ -166,7 +166,7 @@ class HttpClient : public Client const byte aBody[] = NULL); /** Send an additional header line. This can only be called in between the - calls to startRequest and finishRequest. + calls to beginRequest and endRequest. @param aHeader Header line to send, in its entirety (but without the trailing CRLF. E.g. "Authorization: Basic YQDDCAIGES" */ From 780546745d0af31b0006e7732d89741bf7996fd0 Mon Sep 17 00:00:00 2001 From: Max Payne Date: Sat, 29 Dec 2018 21:06:23 +0200 Subject: [PATCH 067/125] Fix incorrect return value, resulting in compilation error Error in question: ArduinoHttpClient/src/HttpClient.h: In member function 'virtual void HttpClient::flush()': ArduinoHttpClient/src/HttpClient.h:310:50: error: return-statement with a value, in function returning 'void' [-fpermissive] --- src/HttpClient.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/HttpClient.h b/src/HttpClient.h index 6dde94f..38fd799 100644 --- a/src/HttpClient.h +++ b/src/HttpClient.h @@ -168,7 +168,7 @@ class HttpClient : public Client /** Send an additional header line. This can only be called in between the calls to beginRequest and endRequest. @param aHeader Header line to send, in its entirety (but without the - trailing CRLF. E.g. "Authorization: Basic YQDDCAIGES" + trailing CRLF. E.g. "Authorization: Basic YQDDCAIGES" */ void sendHeader(const char* aHeader); @@ -307,7 +307,7 @@ class HttpClient : public Client virtual int read(); virtual int read(uint8_t *buf, size_t size); virtual int peek() { return iClient->peek(); }; - virtual void flush() { return iClient->flush(); }; + virtual void flush() { iClient->flush(); }; // Inherited from Client virtual int connect(IPAddress ip, uint16_t port) { return iClient->connect(ip, port); }; From a5796508f52ed2deb92e1a90e451f25a1c91473a Mon Sep 17 00:00:00 2001 From: tigoe Date: Mon, 21 Jan 2019 18:02:47 -0500 Subject: [PATCH 068/125] gitignore --- .DS_Store | Bin 0 -> 6148 bytes examples/.DS_Store | Bin 0 -> 6148 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 .DS_Store create mode 100644 examples/.DS_Store diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..eac32cb9ea57e5754d19c22b8293ae0b2ed98380 GIT binary patch literal 6148 zcmeHK%}&BV5Z(pUZHSSBi5z?J#sU0+a4=~Q58g~_^q>aXw2>yICA5f&n7)R-kx$_3 zIJ3JD2zv2g44Fx0zuEcO&3@bLb{S*5HS%hVl^A0J6ftK&^NnB}bwzU8gUIC=0~Ru$ zCGI#GtYnkpKQe%OCt%MWnZx>QVgKAPb|LoR4#FTwtJP1Q%NI5_w}cQyu`TZGUOcu_ zCmkg%XLuuff#NlM2>Ueq&=uL z3_}>5Qh72tXdYM0qh@_tF(-{i73{jiUL!4y7$63I#{ixWCMcq%F;ggy4ruWA z5#t3!6tM9vfoN*9G-e7R0>V`(pep71iNRGl_)VQ>Y0MO=a>nJ%Fpi#?>lX@_vxDE1 z>5N+nsU-%8fn^5rs$0VQ|Lo`Y|8f!ahyh|?tr*~iw%u;QlJwoWvN*hJP0%hV3g%@B l=Otjss~B?eDlUUc0l$d`prtWW2p$mn5s)-cLk#>W17CpzQ*!_S literal 0 HcmV?d00001 diff --git a/examples/.DS_Store b/examples/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..47345c560437516917b37facbacfe2c2a76eb7b0 GIT binary patch literal 6148 zcmeHKO;5r=5Php00FB_yWB-ExpjL<+NjPvsff@~9jRp1CpYEI8!E95w8lyAG&MWh| z`=Gll0GI8S*FYaYn?tbGr8*$um(h?DwOkOLud%=lcG%(xca3N>7z4(@Z(~5--E(Ak zK#6C>?{ALnvRtg^MX{Di9}RkBlmu}HV9+h!|B^dEfel8;S$9eO76-L4zz2ONrtp?T&-IRKR`si-B@ri}q(z!>;uK=y}(LoijW0{X3k%3lG9Etr)hL3$Y|A)u#|MevMWDFPse~JOu8cYTmm!xNFV{vlUCLH%1A`({x^eI$&9Qy}3 dimy1l(3i*qF;%Pr(n7I60-gp_#=ws<@CBF%e7yhw literal 0 HcmV?d00001 From b6424e430db57fa0c67f82333e80d58b1521a97b Mon Sep 17 00:00:00 2001 From: tigoe Date: Tue, 22 Jan 2019 07:36:31 -0500 Subject: [PATCH 069/125] gitignore --- .gitignore | 3 +++ examples/.DS_Store | Bin 6148 -> 6148 bytes 2 files changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 89d225a..653acab 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ .development examples/node_test_server/node_modules/ +*.DS_Store +*/.DS_Store +examples/.DS_Store diff --git a/examples/.DS_Store b/examples/.DS_Store index 47345c560437516917b37facbacfe2c2a76eb7b0..d15a23844a8c7aa8fdf0604a8f6973a4fc5e34f1 100644 GIT binary patch delta 14 VcmZoMXffDez{1G5*^uS3FaRLC1RVeX delta 14 VcmZoMXffDez{1F|*^uS3FaRL61RMYW From 5e1f1f21bf197370e850d504d2541682b20a34b8 Mon Sep 17 00:00:00 2001 From: tigoe Date: Tue, 22 Jan 2019 08:02:55 -0500 Subject: [PATCH 070/125] Updated examples and readme.md --- README.md | 1 + examples/BasicAuthGet/BasicAuthGet.ino | 11 ++++------ examples/CustomHeader/CustomHeader.ino | 21 ++++++++------------ examples/DweetGet/DweetGet.ino | 10 +++------- examples/DweetPost/DweetPost.ino | 14 +++---------- examples/HueBlink/HueBlink.ino | 6 +----- examples/PostWithHeaders/PostWithHeaders.ino | 10 ++++------ examples/SimpleDelete/SimpleDelete.ino | 9 +++------ examples/SimpleGet/SimpleGet.ino | 10 +++------- examples/SimplePost/SimplePost.ino | 10 +++------- examples/SimplePut/SimplePut.ino | 10 +++------- examples/SimpleWebSocket/SimpleWebSocket.ino | 6 +++--- 12 files changed, 39 insertions(+), 79 deletions(-) diff --git a/README.md b/README.md index 655b618..9824ea0 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ Derived from [Adrian McEwen's HttpClient library](https://github.com/amcewen/Htt ## Dependencies - Requires a networking hardware and a library that provides transport specific `Client` instance, such as: + - [WiFiNINA](https://github.com/arduino-libraries/WiFiNINA) - [WiFi101](https://github.com/arduino-libraries/WiFi101) - [Ethernet](https://github.com/arduino-libraries/Ethernet) - [WiFi](https://github.com/arduino-libraries/WiFi) diff --git a/examples/BasicAuthGet/BasicAuthGet.ino b/examples/BasicAuthGet/BasicAuthGet.ino index e793d47..ca801b7 100644 --- a/examples/BasicAuthGet/BasicAuthGet.ino +++ b/examples/BasicAuthGet/BasicAuthGet.ino @@ -2,12 +2,12 @@ GET client with HTTP basic authentication for ArduinoHttpClient library Connects to server once every five seconds, sends a GET request - - created 14 Feb 2016 by Tom Igoe modified 3 Jan 2017 to add HTTP basic authentication by Sandeep Mistry + modified 22 Jan 2019 + by Tom Igoe this example is in the public domain */ @@ -25,8 +25,6 @@ int port = 8080; WiFiClient wifi; HttpClient client = HttpClient(wifi, serverAddress, port); int status = WL_IDLE_STATUS; -String response; -int statusCode = 0; void setup() { Serial.begin(9600); @@ -56,8 +54,8 @@ void loop() { client.endRequest(); // read the status code and body of the response - statusCode = client.responseStatusCode(); - response = client.responseBody(); + int statusCode = client.responseStatusCode(); + String response = client.responseBody(); Serial.print("Status code: "); Serial.println(statusCode); @@ -66,4 +64,3 @@ void loop() { Serial.println("Wait five seconds"); delay(5000); } - diff --git a/examples/CustomHeader/CustomHeader.ino b/examples/CustomHeader/CustomHeader.ino index fa44aca..e2131eb 100644 --- a/examples/CustomHeader/CustomHeader.ino +++ b/examples/CustomHeader/CustomHeader.ino @@ -2,18 +2,14 @@ Custom request header example for the ArduinoHttpClient library. This example sends a GET and a POST request with a custom header every 5 seconds. - note: WiFi SSID and password are stored in config.h file. - If it is not present, add a new tab, call it "config.h" - and add the following variables: - char ssid[] = "ssid"; // your network SSID (name) - char pass[] = "password"; // your network password - based on SimpleGet example by Tom Igoe header modifications by Todd Treece + modified 22 Jan 2019 + by Tom Igoe this example is in the public domain - */ - +*/ + #include #include @@ -29,8 +25,6 @@ int port = 8080; WiFiClient wifi; HttpClient client = HttpClient(wifi, serverAddress, port); int status = WL_IDLE_STATUS; -String response; -int statusCode = 0; void setup() { Serial.begin(9600); @@ -53,7 +47,6 @@ void setup() { } void loop() { - Serial.println("making GET request"); client.beginRequest(); client.get("/"); @@ -61,8 +54,8 @@ void loop() { client.endRequest(); // read the status code and body of the response - statusCode = client.responseStatusCode(); - response = client.responseBody(); + int statusCode = client.responseStatusCode(); + String response = client.responseBody(); Serial.print("GET Status code: "); Serial.println(statusCode); @@ -81,6 +74,8 @@ void loop() { client.sendHeader("X-CUSTOM-HEADER", "custom_value"); client.endRequest(); client.write((const byte*)postData.c_str(), postData.length()); + // note: the above line can also be achieved with the simpler line below: + //client.print(postData); // read the status code and body of the response statusCode = client.responseStatusCode(); diff --git a/examples/DweetGet/DweetGet.ino b/examples/DweetGet/DweetGet.ino index 51abd3f..d191285 100644 --- a/examples/DweetGet/DweetGet.ino +++ b/examples/DweetGet/DweetGet.ino @@ -9,10 +9,8 @@ For more on dweet.io, see https://dweet.io/play/ - - created 15 Feb 2016 - updated 16 Feb 2016 + updated 22 Jan 2019 by Tom Igoe this example is in the public domain @@ -33,8 +31,6 @@ String dweetName = "scandalous-cheese-hoarder"; // use your own thing name here WiFiClient wifi; HttpClient client = HttpClient(wifi, serverAddress, port); int status = WL_IDLE_STATUS; -int statusCode = 0; -String response; void setup() { Serial.begin(9600); @@ -66,8 +62,8 @@ void loop() { client.get(path); // read the status code and body of the response - statusCode = client.responseStatusCode(); - response = client.responseBody(); + int statusCode = client.responseStatusCode(); + String response = client.responseBody(); Serial.print("Status code: "); Serial.println(statusCode); Serial.print("Response: "); diff --git a/examples/DweetPost/DweetPost.ino b/examples/DweetPost/DweetPost.ino index 41c6f2f..1e2d792 100644 --- a/examples/DweetPost/DweetPost.ino +++ b/examples/DweetPost/DweetPost.ino @@ -5,13 +5,8 @@ Shows how to use Strings to assemble path and body - note: WiFi SSID and password are stored in config.h file. - If it is not present, add a new tab, call it "config.h" - and add the following variables: - char ssid[] = "ssid"; // your network SSID (name) - char pass[] = "password"; // your network password - created 15 Feb 2016 + modified 22 Jan 2019 by Tom Igoe this example is in the public domain @@ -31,8 +26,6 @@ int port = 80; WiFiClient wifi; HttpClient client = HttpClient(wifi, serverAddress, port); int status = WL_IDLE_STATUS; -int statusCode = 0; -String response; void setup() { Serial.begin(9600); @@ -59,7 +52,6 @@ void loop() { // assemble the path for the POST message: String dweetName = "scandalous-cheese-hoarder"; String path = "/dweet/for/" + dweetName; - String contentType = "application/json"; // assemble the body of the POST message: @@ -74,8 +66,8 @@ void loop() { client.post(path, contentType, postData); // read the status code and body of the response - statusCode = client.responseStatusCode(); - response = client.responseBody(); + int statusCode = client.responseStatusCode(); + String response = client.responseBody(); Serial.print("Status code: "); Serial.println(statusCode); diff --git a/examples/HueBlink/HueBlink.ino b/examples/HueBlink/HueBlink.ino index c18ae47..eab74fe 100644 --- a/examples/HueBlink/HueBlink.ino +++ b/examples/HueBlink/HueBlink.ino @@ -13,8 +13,6 @@ This example shows how to concatenate Strings to assemble the PUT request and the body of the request. - - modified 15 Feb 2016 by Tom Igoe (tigoe) to match new API */ @@ -97,6 +95,4 @@ void sendRequest(int light, String cmd, String value) { Serial.print("Server response: "); Serial.println(response); Serial.println(); -} - - +} \ No newline at end of file diff --git a/examples/PostWithHeaders/PostWithHeaders.ino b/examples/PostWithHeaders/PostWithHeaders.ino index 835a26f..d061651 100644 --- a/examples/PostWithHeaders/PostWithHeaders.ino +++ b/examples/PostWithHeaders/PostWithHeaders.ino @@ -3,12 +3,12 @@ Connects to server once every five seconds, sends a POST request with custome headers and a request body - - created 14 Feb 2016 by Tom Igoe modified 18 Mar 2017 by Sandeep Mistry + modified 22 Jan 2019 + by Tom Igoe this example is in the public domain */ @@ -29,8 +29,6 @@ int port = 8080; WiFiClient wifi; HttpClient client = HttpClient(wifi, serverAddress, port); int status = WL_IDLE_STATUS; -String response; -int statusCode = 0; void setup() { Serial.begin(9600); @@ -66,8 +64,8 @@ void loop() { client.endRequest(); // read the status code and body of the response - statusCode = client.responseStatusCode(); - response = client.responseBody(); + int statusCode = client.responseStatusCode(); + String response = client.responseBody(); Serial.print("Status code: "); Serial.println(statusCode); diff --git a/examples/SimpleDelete/SimpleDelete.ino b/examples/SimpleDelete/SimpleDelete.ino index 374a145..84a802e 100644 --- a/examples/SimpleDelete/SimpleDelete.ino +++ b/examples/SimpleDelete/SimpleDelete.ino @@ -3,9 +3,8 @@ Connects to server once every five seconds, sends a DELETE request and a request body - - created 14 Feb 2016 + modified 22 Jan 2019 by Tom Igoe this example is in the public domain @@ -27,8 +26,6 @@ int port = 8080; WiFiClient wifi; HttpClient client = HttpClient(wifi, serverAddress, port); int status = WL_IDLE_STATUS; -String response; -int statusCode = 0; void setup() { Serial.begin(9600); @@ -58,8 +55,8 @@ void loop() { client.del("/", contentType, delData); // read the status code and body of the response - statusCode = client.responseStatusCode(); - response = client.responseBody(); + int statusCode = client.responseStatusCode(); + String response = client.responseBody(); Serial.print("Status code: "); Serial.println(statusCode); diff --git a/examples/SimpleGet/SimpleGet.ino b/examples/SimpleGet/SimpleGet.ino index 75db9a7..845cfc6 100644 --- a/examples/SimpleGet/SimpleGet.ino +++ b/examples/SimpleGet/SimpleGet.ino @@ -2,9 +2,8 @@ Simple GET client for ArduinoHttpClient library Connects to server once every five seconds, sends a GET request - - created 14 Feb 2016 + modified 22 Jan 2019 by Tom Igoe this example is in the public domain @@ -19,15 +18,12 @@ char ssid[] = SECRET_SSID; char pass[] = SECRET_PASS; - char serverAddress[] = "192.168.0.3"; // server address int port = 8080; WiFiClient wifi; HttpClient client = HttpClient(wifi, serverAddress, port); int status = WL_IDLE_STATUS; -String response; -int statusCode = 0; void setup() { Serial.begin(9600); @@ -54,8 +50,8 @@ void loop() { client.get("/"); // read the status code and body of the response - statusCode = client.responseStatusCode(); - response = client.responseBody(); + int statusCode = client.responseStatusCode(); + String response = client.responseBody(); Serial.print("Status code: "); Serial.println(statusCode); diff --git a/examples/SimplePost/SimplePost.ino b/examples/SimplePost/SimplePost.ino index 6cc3517..19fae75 100644 --- a/examples/SimplePost/SimplePost.ino +++ b/examples/SimplePost/SimplePost.ino @@ -3,9 +3,8 @@ Connects to server once every five seconds, sends a POST request and a request body - - created 14 Feb 2016 + modified 22 Jan 2019 by Tom Igoe this example is in the public domain @@ -19,15 +18,12 @@ char ssid[] = SECRET_SSID; char pass[] = SECRET_PASS; - char serverAddress[] = "192.168.0.3"; // server address int port = 8080; WiFiClient wifi; HttpClient client = HttpClient(wifi, serverAddress, port); int status = WL_IDLE_STATUS; -String response; -int statusCode = 0; void setup() { Serial.begin(9600); @@ -57,8 +53,8 @@ void loop() { client.post("/", contentType, postData); // read the status code and body of the response - statusCode = client.responseStatusCode(); - response = client.responseBody(); + int statusCode = client.responseStatusCode(); + String response = client.responseBody(); Serial.print("Status code: "); Serial.println(statusCode); diff --git a/examples/SimplePut/SimplePut.ino b/examples/SimplePut/SimplePut.ino index 1b06105..82264ca 100644 --- a/examples/SimplePut/SimplePut.ino +++ b/examples/SimplePut/SimplePut.ino @@ -3,9 +3,8 @@ Connects to server once every five seconds, sends a PUT request and a request body - - created 14 Feb 2016 + modified 22 Jan 2019 by Tom Igoe this example is in the public domain @@ -19,15 +18,12 @@ char ssid[] = SECRET_SSID; char pass[] = SECRET_PASS; - char serverAddress[] = "192.168.0.3"; // server address int port = 8080; WiFiClient wifi; HttpClient client = HttpClient(wifi, serverAddress, port); int status = WL_IDLE_STATUS; -String response; -int statusCode = 0; void setup() { Serial.begin(9600); @@ -57,8 +53,8 @@ void loop() { client.put("/", contentType, putData); // read the status code and body of the response - statusCode = client.responseStatusCode(); - response = client.responseBody(); + int statusCode = client.responseStatusCode(); + String response = client.responseBody(); Serial.print("Status code: "); Serial.println(statusCode); diff --git a/examples/SimpleWebSocket/SimpleWebSocket.ino b/examples/SimpleWebSocket/SimpleWebSocket.ino index baac12f..92f3f55 100644 --- a/examples/SimpleWebSocket/SimpleWebSocket.ino +++ b/examples/SimpleWebSocket/SimpleWebSocket.ino @@ -3,9 +3,10 @@ Connects to the WebSocket server, and sends a hello message every 5 seconds - created 28 Jun 2016 by Sandeep Mistry + modified 22 Jan 2019 + by Tom Igoe this example is in the public domain */ @@ -76,5 +77,4 @@ void loop() { } Serial.println("disconnected"); -} - +} \ No newline at end of file From 74d6f3614efa264506c8d58c7307fbf91f34bed6 Mon Sep 17 00:00:00 2001 From: Sandeep Mistry Date: Tue, 22 Jan 2019 10:59:20 -0500 Subject: [PATCH 071/125] Remove .DS_Store files --- .DS_Store | Bin 6148 -> 0 bytes examples/.DS_Store | Bin 6148 -> 0 bytes 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 .DS_Store delete mode 100644 examples/.DS_Store diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index eac32cb9ea57e5754d19c22b8293ae0b2ed98380..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHK%}&BV5Z(pUZHSSBi5z?J#sU0+a4=~Q58g~_^q>aXw2>yICA5f&n7)R-kx$_3 zIJ3JD2zv2g44Fx0zuEcO&3@bLb{S*5HS%hVl^A0J6ftK&^NnB}bwzU8gUIC=0~Ru$ zCGI#GtYnkpKQe%OCt%MWnZx>QVgKAPb|LoR4#FTwtJP1Q%NI5_w}cQyu`TZGUOcu_ zCmkg%XLuuff#NlM2>Ueq&=uL z3_}>5Qh72tXdYM0qh@_tF(-{i73{jiUL!4y7$63I#{ixWCMcq%F;ggy4ruWA z5#t3!6tM9vfoN*9G-e7R0>V`(pep71iNRGl_)VQ>Y0MO=a>nJ%Fpi#?>lX@_vxDE1 z>5N+nsU-%8fn^5rs$0VQ|Lo`Y|8f!ahyh|?tr*~iw%u;QlJwoWvN*hJP0%hV3g%@B l=Otjss~B?eDlUUc0l$d`prtWW2p$mn5s)-cLk#>W17CpzQ*!_S diff --git a/examples/.DS_Store b/examples/.DS_Store deleted file mode 100644 index d15a23844a8c7aa8fdf0604a8f6973a4fc5e34f1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHKO;5r=5Php05RKr?WB-ExpjL<+NjPvsff@~9jRp1CpYEI8LANPfjnSE8=WTbs zwy*3i3&3UD#TC#4&}I{Cb*T1;*rhe(NJq|z&gYop8e44eh}(v@4HyH)z;9zf*6tZH z+@r)3;`N(jvnc1QSy8OGxDPu$(CgdjhU>rN4p3l?A#&zjP`}1bZS?WZK^EXPK^+@U zBIAfzm@787nRLsT3@c9e8Ozw%<3Qo-O_@6P^O#bzKHfy{oaG~Qq3JPUCfTy0>7v}Z zhF2fcWsMgW%{jl^BP+yCCy1RI?{!f#4_R=-d;@%tA(t!$5K%8Tl3h2B@7Cfc zobrq#1wZhT{hF(gVe%gtkl!|?eHzfTF<=ZB178ft{*bT Date: Tue, 22 Jan 2019 11:33:45 -0500 Subject: [PATCH 072/125] Add MKRGSM and MKRNB links --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 9824ea0..1b95559 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,8 @@ Derived from [Adrian McEwen's HttpClient library](https://github.com/amcewen/Htt - [WiFiNINA](https://github.com/arduino-libraries/WiFiNINA) - [WiFi101](https://github.com/arduino-libraries/WiFi101) - [Ethernet](https://github.com/arduino-libraries/Ethernet) + - [MKRGSM](https://github.com/arduino-libraries/MKRGSM) + - [MKRNB](https://github.com/arduino-libraries/MKRNB) - [WiFi](https://github.com/arduino-libraries/WiFi) - [GSM](https://github.com/arduino-libraries/GSM) From 3df4990ed3adb1252e8d2cb5ef119bceabdd9e49 Mon Sep 17 00:00:00 2001 From: Sandeep Mistry Date: Tue, 22 Jan 2019 12:04:13 -0500 Subject: [PATCH 073/125] Spacing --- examples/DweetPost/DweetPost.ino | 2 +- examples/PostWithHeaders/PostWithHeaders.ino | 2 +- examples/SimpleDelete/SimpleDelete.ino | 2 +- examples/SimpleGet/SimpleGet.ino | 2 +- examples/SimplePost/SimplePost.ino | 2 +- examples/SimplePut/SimplePut.ino | 2 +- examples/SimpleWebSocket/SimpleWebSocket.ino | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/examples/DweetPost/DweetPost.ino b/examples/DweetPost/DweetPost.ino index 1e2d792..f1da2b9 100644 --- a/examples/DweetPost/DweetPost.ino +++ b/examples/DweetPost/DweetPost.ino @@ -67,7 +67,7 @@ void loop() { // read the status code and body of the response int statusCode = client.responseStatusCode(); - String response = client.responseBody(); + String response = client.responseBody(); Serial.print("Status code: "); Serial.println(statusCode); diff --git a/examples/PostWithHeaders/PostWithHeaders.ino b/examples/PostWithHeaders/PostWithHeaders.ino index d061651..c2b1f0a 100644 --- a/examples/PostWithHeaders/PostWithHeaders.ino +++ b/examples/PostWithHeaders/PostWithHeaders.ino @@ -65,7 +65,7 @@ void loop() { // read the status code and body of the response int statusCode = client.responseStatusCode(); - String response = client.responseBody(); + String response = client.responseBody(); Serial.print("Status code: "); Serial.println(statusCode); diff --git a/examples/SimpleDelete/SimpleDelete.ino b/examples/SimpleDelete/SimpleDelete.ino index 84a802e..120f8d4 100644 --- a/examples/SimpleDelete/SimpleDelete.ino +++ b/examples/SimpleDelete/SimpleDelete.ino @@ -56,7 +56,7 @@ void loop() { // read the status code and body of the response int statusCode = client.responseStatusCode(); - String response = client.responseBody(); + String response = client.responseBody(); Serial.print("Status code: "); Serial.println(statusCode); diff --git a/examples/SimpleGet/SimpleGet.ino b/examples/SimpleGet/SimpleGet.ino index 845cfc6..90d1f68 100644 --- a/examples/SimpleGet/SimpleGet.ino +++ b/examples/SimpleGet/SimpleGet.ino @@ -51,7 +51,7 @@ void loop() { // read the status code and body of the response int statusCode = client.responseStatusCode(); - String response = client.responseBody(); + String response = client.responseBody(); Serial.print("Status code: "); Serial.println(statusCode); diff --git a/examples/SimplePost/SimplePost.ino b/examples/SimplePost/SimplePost.ino index 19fae75..a4704e5 100644 --- a/examples/SimplePost/SimplePost.ino +++ b/examples/SimplePost/SimplePost.ino @@ -54,7 +54,7 @@ void loop() { // read the status code and body of the response int statusCode = client.responseStatusCode(); - String response = client.responseBody(); + String response = client.responseBody(); Serial.print("Status code: "); Serial.println(statusCode); diff --git a/examples/SimplePut/SimplePut.ino b/examples/SimplePut/SimplePut.ino index 82264ca..99af49f 100644 --- a/examples/SimplePut/SimplePut.ino +++ b/examples/SimplePut/SimplePut.ino @@ -54,7 +54,7 @@ void loop() { // read the status code and body of the response int statusCode = client.responseStatusCode(); - String response = client.responseBody(); + String response = client.responseBody(); Serial.print("Status code: "); Serial.println(statusCode); diff --git a/examples/SimpleWebSocket/SimpleWebSocket.ino b/examples/SimpleWebSocket/SimpleWebSocket.ino index 92f3f55..b20d74b 100644 --- a/examples/SimpleWebSocket/SimpleWebSocket.ino +++ b/examples/SimpleWebSocket/SimpleWebSocket.ino @@ -77,4 +77,4 @@ void loop() { } Serial.println("disconnected"); -} \ No newline at end of file +} From 99826c691696f9e2ed947fc94c479a1d4a443090 Mon Sep 17 00:00:00 2001 From: Riccardo Rizzo Date: Mon, 4 Feb 2019 15:32:25 +0100 Subject: [PATCH 074/125] Version 0.3.2 --- CHANGELOG.md | 4 ++++ library.properties | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5645508..f828d31 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## ArduinoHttpClient 0.3.2 - 2019.02.04 + +* Changed Flush return value resulting in compilation error. Thanks @forGGe + ## ArduinoHttpClient 0.3.1 - 2017.09.25 * Changed examples to support Arduino Create secret tabs diff --git a/library.properties b/library.properties index 2ac5bb5..adb5aec 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=ArduinoHttpClient -version=0.3.1 +version=0.3.2 author=Arduino maintainer=Arduino sentence=[EXPERIMENTAL] Easily interact with web servers from Arduino, using HTTP and WebSocket's. From d73940758a8fcb6e05d99fc7fcfa1978bec5ee2e Mon Sep 17 00:00:00 2001 From: Sandeep Mistry Date: Mon, 8 Apr 2019 15:05:59 -0400 Subject: [PATCH 075/125] Add URL Encoder class --- keywords.txt | 3 +++ src/ArduinoHttpClient.h | 1 + src/URLEncoder.cpp | 53 +++++++++++++++++++++++++++++++++++++++++ src/URLEncoder.h | 25 +++++++++++++++++++ 4 files changed, 82 insertions(+) create mode 100644 src/URLEncoder.cpp create mode 100644 src/URLEncoder.h diff --git a/keywords.txt b/keywords.txt index 27cd516..1b4bd2c 100644 --- a/keywords.txt +++ b/keywords.txt @@ -9,6 +9,7 @@ ArduinoHttpClient KEYWORD1 HttpClient KEYWORD1 WebSocketClient KEYWORD1 +URLEncoder KEYWORD1 ####################################### # Methods and Functions (KEYWORD2) @@ -47,6 +48,8 @@ isFinal KEYWORD2 readString KEYWORD2 ping KEYWORD2 +encode KEYWORD2 + ####################################### # Constants (LITERAL1) ####################################### diff --git a/src/ArduinoHttpClient.h b/src/ArduinoHttpClient.h index 578733f..abb8494 100644 --- a/src/ArduinoHttpClient.h +++ b/src/ArduinoHttpClient.h @@ -7,5 +7,6 @@ #include "HttpClient.h" #include "WebSocketClient.h" +#include "URLEncoder.h" #endif diff --git a/src/URLEncoder.cpp b/src/URLEncoder.cpp new file mode 100644 index 0000000..7baf5a9 --- /dev/null +++ b/src/URLEncoder.cpp @@ -0,0 +1,53 @@ +// Library to simplify HTTP fetching on Arduino +// (c) Copyright Arduino. 2019 +// Released under Apache License, version 2.0 + +#include "URLEncoder.h" + +URLEncoderClass::URLEncoderClass() +{ +} + +URLEncoderClass::~URLEncoderClass() +{ +} + +String URLEncoderClass::encode(const char* str) +{ + return encode(str, strlen(str)); +} + +String URLEncoderClass::encode(const String& str) +{ + return encode(str.c_str(), str.length()); +} + +String URLEncoderClass::encode(const char* str, int length) +{ + String encoded; + + encoded.reserve(length); + + for (int i = 0; i < length; i++) { + char c = str[i]; + + const char HEX_DIGIT_MAPPER[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; + + if (isAlphaNumeric(c) || (c == '-') || (c == '.') || (c == '_') || (c == '~')) { + encoded += c; + } else { + char s[4]; + + s[0] = '%'; + s[1] = HEX_DIGIT_MAPPER[(c >> 4) & 0xf]; + s[2] = HEX_DIGIT_MAPPER[(c & 0x0f)]; + s[3] = 0; + + encoded += s; + } + } + + return encoded; +} + +URLEncoderClass URLEncoder; diff --git a/src/URLEncoder.h b/src/URLEncoder.h new file mode 100644 index 0000000..e1859ff --- /dev/null +++ b/src/URLEncoder.h @@ -0,0 +1,25 @@ +// Library to simplify HTTP fetching on Arduino +// (c) Copyright Arduino. 2019 +// Released under Apache License, version 2.0 + +#ifndef URL_ENCODER_H +#define URL_ENCODER_H + +#include + +class URLEncoderClass +{ +public: + URLEncoderClass(); + virtual ~URLEncoderClass(); + + String encode(const char* str); + String encode(const String& str); + +private: + String encode(const char* str, int length); +}; + +extern URLEncoderClass URLEncoder; + +#endif From 5f567031d56b8a12e81ad88ea7fdc3b59d91c184 Mon Sep 17 00:00:00 2001 From: Sandeep Mistry Date: Tue, 9 Apr 2019 10:00:39 -0400 Subject: [PATCH 076/125] Make encode method's static --- src/URLEncoder.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/URLEncoder.h b/src/URLEncoder.h index e1859ff..bce5f17 100644 --- a/src/URLEncoder.h +++ b/src/URLEncoder.h @@ -13,11 +13,11 @@ class URLEncoderClass URLEncoderClass(); virtual ~URLEncoderClass(); - String encode(const char* str); - String encode(const String& str); + static String encode(const char* str); + static String encode(const String& str); private: - String encode(const char* str, int length); + static String encode(const char* str, int length); }; extern URLEncoderClass URLEncoder; From 7f36561e0bced5458516066ae09636e119cae0ed Mon Sep 17 00:00:00 2001 From: Sandeep Mistry Date: Tue, 9 Apr 2019 12:48:29 -0400 Subject: [PATCH 077/125] Version 0.4.0 --- CHANGELOG.md | 4 ++++ library.properties | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f828d31..ac78d18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## ArduinoHttpClient 0.4.0 - 2019.04.09 + +* Added URLEncoder helper + ## ArduinoHttpClient 0.3.2 - 2019.02.04 * Changed Flush return value resulting in compilation error. Thanks @forGGe diff --git a/library.properties b/library.properties index adb5aec..632910e 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=ArduinoHttpClient -version=0.3.2 +version=0.4.0 author=Arduino maintainer=Arduino sentence=[EXPERIMENTAL] Easily interact with web servers from Arduino, using HTTP and WebSocket's. From 208dfec4cf79794547a897bdb1c79a55f8e269f7 Mon Sep 17 00:00:00 2001 From: Park0 Date: Sun, 28 Jun 2020 11:52:27 +0200 Subject: [PATCH 078/125] ContentLength can go over 2 byte int Converted int to long to allow for more then 65535 bytes in length --- src/HttpClient.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/HttpClient.h b/src/HttpClient.h index 38fd799..b9f68f8 100644 --- a/src/HttpClient.h +++ b/src/HttpClient.h @@ -272,7 +272,7 @@ class HttpClient : public Client @return Length of the body, in bytes, or kNoContentLengthHeader if no Content-Length header was returned by the server */ - int contentLength(); + long contentLength(); /** Returns if the response body is chunked @return true if response body is chunked, false otherwise @@ -372,7 +372,7 @@ class HttpClient : public Client // Stores the status code for the response, once known int iStatusCode; // Stores the value of the Content-Length header, if present - int iContentLength; + long iContentLength; // How many bytes of the response body have been read by the user int iBodyLengthConsumed; // How far through a Content-Length header prefix we are From 0fac9f0033de4e1f45a9e2d2a28d326c94354163 Mon Sep 17 00:00:00 2001 From: Park0 Date: Sun, 28 Jun 2020 11:53:31 +0200 Subject: [PATCH 079/125] ContentLength can go over 2 byte int Converted int to long to allow for more then 65535 bytes in length --- src/HttpClient.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/HttpClient.cpp b/src/HttpClient.cpp index 7517eea..33ac681 100644 --- a/src/HttpClient.cpp +++ b/src/HttpClient.cpp @@ -542,7 +542,7 @@ bool HttpClient::endOfHeadersReached() return (iState == eReadingBody || iState == eReadingChunkLength || iState == eReadingBodyChunk); }; -int HttpClient::contentLength() +long HttpClient::contentLength() { // skip the response headers, if they haven't been read already if (!endOfHeadersReached()) From 9a5afdfc741a35ec18a71295c508c1142391ec09 Mon Sep 17 00:00:00 2001 From: Vaibhav Patel Date: Fri, 26 Feb 2021 05:01:05 -0500 Subject: [PATCH 080/125] Fix -Wlogical-not-parentheses warning (#79) * Fix -Wlogical-not-parentheses warning The warning occurs because `operator!()` has higher precedence than `operator>()`. Alternatively, we can use: ``` iClient->connect(iServerAddress, iServerPort) <= 0 ``` --- src/HttpClient.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/HttpClient.cpp b/src/HttpClient.cpp index 7517eea..4f3410f 100644 --- a/src/HttpClient.cpp +++ b/src/HttpClient.cpp @@ -84,7 +84,7 @@ int HttpClient::startRequest(const char* aURLPath, const char* aHttpMethod, { if (iServerName) { - if (!iClient->connect(iServerName, iServerPort) > 0) + if (!(iClient->connect(iServerName, iServerPort) > 0)) { #ifdef LOGGING Serial.println("Connection failed"); @@ -94,7 +94,7 @@ int HttpClient::startRequest(const char* aURLPath, const char* aHttpMethod, } else { - if (!iClient->connect(iServerAddress, iServerPort) > 0) + if (!(iClient->connect(iServerAddress, iServerPort) > 0)) { #ifdef LOGGING Serial.println("Connection failed"); From 05b7dfe53d0c58706c38f2ead07f7a21d293e9ff Mon Sep 17 00:00:00 2001 From: per1234 Date: Sun, 11 Apr 2021 21:40:50 -0700 Subject: [PATCH 081/125] Configure Dependabot to check for outdated actions used in workflows Dependabot will periodically check the versions of all actions used in the repository's workflows. If any are found to be outdated, it will submit a pull request to update them. NOTE: Dependabot's PRs will sometimes try to pin to the patch version of the action (e.g., updating `uses: foo/bar@v1` to `uses: foo/bar@v2.3.4`). When the action author has provided a major version ref, use that instead (e.g., `uses: foo/bar@v2`). Dependabot will automatically close its PR once the workflow has been updated. More information: https://docs.github.com/en/github/administering-a-repository/keeping-your-actions-up-to-date-with-dependabot --- .github/dependabot.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..03600dd --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,10 @@ +# See: https://docs.github.com/en/github/administering-a-repository/configuration-options-for-dependency-updates#about-the-dependabotyml-file +version: 2 + +updates: + # Configure check for outdated GitHub Actions actions in workflows. + # See: https://docs.github.com/en/github/administering-a-repository/keeping-your-actions-up-to-date-with-dependabot + - package-ecosystem: github-actions + directory: / # Check the repository's workflows under /.github/workflows/ + schedule: + interval: daily From 42d8a204f0157066fe249e1535179bafa25665c2 Mon Sep 17 00:00:00 2001 From: per1234 Date: Sun, 11 Apr 2021 21:41:26 -0700 Subject: [PATCH 082/125] Add CI workflow to check for commonly misspelled words On every push, pull request, and periodically, use the codespell-project/actions-codespell action to check for commonly misspelled words. In the event of a false positive, the problematic word should be added, in all lowercase, to the ignore-words-list field of ./.codespellrc. Regardless of the case of the word in the false positive, it must be in all lowercase in the ignore list. The ignore list is comma-separated with no spaces. --- .codespellrc | 7 +++++++ .github/workflows/spell-check.yml | 22 ++++++++++++++++++++++ README.md | 2 ++ 3 files changed, 31 insertions(+) create mode 100644 .codespellrc create mode 100644 .github/workflows/spell-check.yml diff --git a/.codespellrc b/.codespellrc new file mode 100644 index 0000000..101edae --- /dev/null +++ b/.codespellrc @@ -0,0 +1,7 @@ +# See: https://github.com/codespell-project/codespell#using-a-config-file +[codespell] +# In the event of a false positive, add the problematic word, in all lowercase, to a comma-separated list here: +ignore-words-list = , +check-filenames = +check-hidden = +skip = ./.git diff --git a/.github/workflows/spell-check.yml b/.github/workflows/spell-check.yml new file mode 100644 index 0000000..01bee87 --- /dev/null +++ b/.github/workflows/spell-check.yml @@ -0,0 +1,22 @@ +name: Spell Check + +# See: https://docs.github.com/en/free-pro-team@latest/actions/reference/events-that-trigger-workflows +on: + push: + pull_request: + schedule: + # Run every Tuesday at 8 AM UTC to catch new misspelling detections resulting from dictionary updates. + - cron: "0 8 * * TUE" + workflow_dispatch: + repository_dispatch: + +jobs: + spellcheck: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Spell check + uses: codespell-project/actions-codespell@master diff --git a/README.md b/README.md index 1b95559..b976c27 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # ArduinoHttpClient +[![Spell Check status](https://github.com/arduino-libraries/ArduinoHttpClient/actions/workflows/spell-check.yml/badge.svg)](https://github.com/arduino-libraries/ArduinoHttpClient/actions/workflows/spell-check.yml) + ArduinoHttpClient is a library to make it easier to interact with web servers from Arduino. Derived from [Adrian McEwen's HttpClient library](https://github.com/amcewen/HttpClient) From e9897fc56834ed998ba6bb64e40aaa534bceef26 Mon Sep 17 00:00:00 2001 From: per1234 Date: Sun, 11 Apr 2021 21:53:59 -0700 Subject: [PATCH 083/125] Correct typos in comments and documentation --- CHANGELOG.md | 2 +- examples/BasicAuthGet/BasicAuthGet.ino | 2 +- examples/CustomHeader/CustomHeader.ino | 2 +- examples/DweetGet/DweetGet.ino | 2 +- examples/DweetPost/DweetPost.ino | 2 +- examples/HueBlink/HueBlink.ino | 12 ++++++------ examples/PostWithHeaders/PostWithHeaders.ino | 4 ++-- examples/SimpleDelete/SimpleDelete.ino | 2 +- examples/SimpleGet/SimpleGet.ino | 2 +- examples/SimpleHttpExample/SimpleHttpExample.ino | 8 +++----- examples/SimplePost/SimplePost.ino | 2 +- examples/SimplePut/SimplePut.ino | 2 +- examples/SimpleWebSocket/SimpleWebSocket.ino | 2 +- keywords.txt | 2 +- library.json | 2 +- library.properties | 2 +- src/HttpClient.cpp | 4 ++-- src/HttpClient.h | 4 ++-- 18 files changed, 28 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ac78d18..fc0e879 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ ## ArduinoHttpClient 0.3.1 - 2017.09.25 * Changed examples to support Arduino Create secret tabs -* Increase WebSocket secrect-key length to 24 characters +* Increase WebSocket secret-key length to 24 characters ## ArduinoHttpClient 0.3.0 - 2017.04.20 diff --git a/examples/BasicAuthGet/BasicAuthGet.ino b/examples/BasicAuthGet/BasicAuthGet.ino index ca801b7..861acc1 100644 --- a/examples/BasicAuthGet/BasicAuthGet.ino +++ b/examples/BasicAuthGet/BasicAuthGet.ino @@ -15,7 +15,7 @@ #include #include "arduino_secrets.h" ///////please enter your sensitive data in the Secret tab/arduino_secrets.h -/////// Wifi Settings /////// +/////// WiFi Settings /////// char ssid[] = SECRET_SSID; char pass[] = SECRET_PASS; diff --git a/examples/CustomHeader/CustomHeader.ino b/examples/CustomHeader/CustomHeader.ino index e2131eb..4b8abbb 100644 --- a/examples/CustomHeader/CustomHeader.ino +++ b/examples/CustomHeader/CustomHeader.ino @@ -15,7 +15,7 @@ #include "arduino_secrets.h" ///////please enter your sensitive data in the Secret tab/arduino_secrets.h -/////// Wifi Settings /////// +/////// WiFi Settings /////// char ssid[] = SECRET_SSID; char pass[] = SECRET_PASS; diff --git a/examples/DweetGet/DweetGet.ino b/examples/DweetGet/DweetGet.ino index d191285..1c26c35 100644 --- a/examples/DweetGet/DweetGet.ino +++ b/examples/DweetGet/DweetGet.ino @@ -20,7 +20,7 @@ #include "arduino_secrets.h" ///////please enter your sensitive data in the Secret tab/arduino_secrets.h -/////// Wifi Settings /////// +/////// WiFi Settings /////// char ssid[] = SECRET_SSID; char pass[] = SECRET_PASS; diff --git a/examples/DweetPost/DweetPost.ino b/examples/DweetPost/DweetPost.ino index f1da2b9..17e76f5 100644 --- a/examples/DweetPost/DweetPost.ino +++ b/examples/DweetPost/DweetPost.ino @@ -16,7 +16,7 @@ #include "arduino_secrets.h" ///////please enter your sensitive data in the Secret tab/arduino_secrets.h -/////// Wifi Settings /////// +/////// WiFi Settings /////// char ssid[] = SECRET_SSID; char pass[] = SECRET_PASS; diff --git a/examples/HueBlink/HueBlink.ino b/examples/HueBlink/HueBlink.ino index eab74fe..0583fe9 100644 --- a/examples/HueBlink/HueBlink.ino +++ b/examples/HueBlink/HueBlink.ino @@ -10,7 +10,7 @@ The body of the PUT request looks like this: {"on": true} or {"on":false} - This example shows how to concatenate Strings to assemble the + This example shows how to concatenate Strings to assemble the PUT request and the body of the request. modified 15 Feb 2016 @@ -23,16 +23,16 @@ #include "arduino_secrets.h" ///////please enter your sensitive data in the Secret tab/arduino_secrets.h -/////// Wifi Settings /////// +/////// WiFi Settings /////// char ssid[] = SECRET_SSID; char pass[] = SECRET_PASS; -int status = WL_IDLE_STATUS; // the Wifi radio's status +int status = WL_IDLE_STATUS; // the WiFi radio's status char hueHubIP[] = "192.168.0.3"; // IP address of the HUE bridge String hueUserName = "huebridgeusername"; // hue bridge username -// make a wifi instance and a HttpClient instance: +// make a WiFiClient instance and a HttpClient instance: WiFiClient wifi; HttpClient httpClient = HttpClient(wifi, hueHubIP); @@ -42,7 +42,7 @@ void setup() { Serial.begin(9600); while (!Serial); // wait for serial port to connect. - // attempt to connect to Wifi network: + // attempt to connect to WiFi network: while ( status != WL_CONNECTED) { Serial.print("Attempting to connect to WPA SSID: "); Serial.println(ssid); @@ -95,4 +95,4 @@ void sendRequest(int light, String cmd, String value) { Serial.print("Server response: "); Serial.println(response); Serial.println(); -} \ No newline at end of file +} diff --git a/examples/PostWithHeaders/PostWithHeaders.ino b/examples/PostWithHeaders/PostWithHeaders.ino index c2b1f0a..f97439f 100644 --- a/examples/PostWithHeaders/PostWithHeaders.ino +++ b/examples/PostWithHeaders/PostWithHeaders.ino @@ -1,7 +1,7 @@ /* POST with headers client for ArduinoHttpClient library Connects to server once every five seconds, sends a POST request - with custome headers and a request body + with custom headers and a request body created 14 Feb 2016 by Tom Igoe @@ -18,7 +18,7 @@ #include "arduino_secrets.h" ///////please enter your sensitive data in the Secret tab/arduino_secrets.h -/////// Wifi Settings /////// +/////// WiFi Settings /////// char ssid[] = SECRET_SSID; char pass[] = SECRET_PASS; diff --git a/examples/SimpleDelete/SimpleDelete.ino b/examples/SimpleDelete/SimpleDelete.ino index 120f8d4..336cb7a 100644 --- a/examples/SimpleDelete/SimpleDelete.ino +++ b/examples/SimpleDelete/SimpleDelete.ino @@ -15,7 +15,7 @@ #include "arduino_secrets.h" ///////please enter your sensitive data in the Secret tab/arduino_secrets.h -/////// Wifi Settings /////// +/////// WiFi Settings /////// char ssid[] = SECRET_SSID; char pass[] = SECRET_PASS; diff --git a/examples/SimpleGet/SimpleGet.ino b/examples/SimpleGet/SimpleGet.ino index 90d1f68..759b13f 100644 --- a/examples/SimpleGet/SimpleGet.ino +++ b/examples/SimpleGet/SimpleGet.ino @@ -14,7 +14,7 @@ #include "arduino_secrets.h" ///////please enter your sensitive data in the Secret tab/arduino_secrets.h -/////// Wifi Settings /////// +/////// WiFi Settings /////// char ssid[] = SECRET_SSID; char pass[] = SECRET_PASS; diff --git a/examples/SimpleHttpExample/SimpleHttpExample.ino b/examples/SimpleHttpExample/SimpleHttpExample.ino index f64b9ba..449d6af 100644 --- a/examples/SimpleHttpExample/SimpleHttpExample.ino +++ b/examples/SimpleHttpExample/SimpleHttpExample.ino @@ -2,7 +2,7 @@ // Released under Apache License, version 2.0 // // Simple example to show how to use the HttpClient library -// Get's the web page given at http:// and +// Gets the web page given at http:// and // outputs the content to the serial port #include @@ -14,7 +14,7 @@ #include "arduino_secrets.h" ///////please enter your sensitive data in the Secret tab/arduino_secrets.h -/////// Wifi Settings /////// +/////// WiFi Settings /////// char ssid[] = SECRET_SSID; char pass[] = SECRET_PASS; @@ -42,7 +42,7 @@ void setup() ; // wait for serial port to connect. Needed for native USB port only } - // attempt to connect to Wifi network: + // attempt to connect to WiFi network: Serial.print("Attempting to connect to WPA SSID: "); Serial.println(ssid); while (WiFi.begin(ssid, pass) != WL_CONNECTED) { @@ -129,5 +129,3 @@ void loop() // And just stop, now that we've tried a download while(1); } - - diff --git a/examples/SimplePost/SimplePost.ino b/examples/SimplePost/SimplePost.ino index a4704e5..8717438 100644 --- a/examples/SimplePost/SimplePost.ino +++ b/examples/SimplePost/SimplePost.ino @@ -14,7 +14,7 @@ #include "arduino_secrets.h" ///////please enter your sensitive data in the Secret tab/arduino_secrets.h -/////// Wifi Settings /////// +/////// WiFi Settings /////// char ssid[] = SECRET_SSID; char pass[] = SECRET_PASS; diff --git a/examples/SimplePut/SimplePut.ino b/examples/SimplePut/SimplePut.ino index 99af49f..06dcd15 100644 --- a/examples/SimplePut/SimplePut.ino +++ b/examples/SimplePut/SimplePut.ino @@ -14,7 +14,7 @@ #include "arduino_secrets.h" ///////please enter your sensitive data in the Secret tab/arduino_secrets.h -/////// Wifi Settings /////// +/////// WiFi Settings /////// char ssid[] = SECRET_SSID; char pass[] = SECRET_PASS; diff --git a/examples/SimpleWebSocket/SimpleWebSocket.ino b/examples/SimpleWebSocket/SimpleWebSocket.ino index b20d74b..32f74e1 100644 --- a/examples/SimpleWebSocket/SimpleWebSocket.ino +++ b/examples/SimpleWebSocket/SimpleWebSocket.ino @@ -15,7 +15,7 @@ #include "arduino_secrets.h" ///////please enter your sensitive data in the Secret tab/arduino_secrets.h -/////// Wifi Settings /////// +/////// WiFi Settings /////// char ssid[] = SECRET_SSID; char pass[] = SECRET_PASS; diff --git a/keywords.txt b/keywords.txt index 1b4bd2c..209c917 100644 --- a/keywords.txt +++ b/keywords.txt @@ -1,5 +1,5 @@ ####################################### -# Syntax Coloring Map For HttpClient +# Syntax Coloring Map For ArduinoHttpClient ####################################### ####################################### diff --git a/library.json b/library.json index 6bbda24..5376d0a 100644 --- a/library.json +++ b/library.json @@ -1,7 +1,7 @@ { "name": "ArduinoHttpClient", "keywords": "http, web, client, ethernet, wifi, GSM", - "description": "Easily interact with web servers from Arduino, using HTTP and WebSocket's.", + "description": "Easily interact with web servers from Arduino, using HTTP and WebSockets.", "repository": { "type": "git", diff --git a/library.properties b/library.properties index 632910e..2682599 100644 --- a/library.properties +++ b/library.properties @@ -2,7 +2,7 @@ name=ArduinoHttpClient version=0.4.0 author=Arduino maintainer=Arduino -sentence=[EXPERIMENTAL] Easily interact with web servers from Arduino, using HTTP and WebSocket's. +sentence=[EXPERIMENTAL] Easily interact with web servers from Arduino, using HTTP and WebSockets. paragraph=This library can be used for HTTP (GET, POST, PUT, DELETE) requests to a web server. It also supports exchanging messages with WebSocket servers. Based on Adrian McEwen's HttpClient library. category=Communication url=https://github.com/arduino-libraries/ArduinoHttpClient diff --git a/src/HttpClient.cpp b/src/HttpClient.cpp index 4f3410f..1c73464 100644 --- a/src/HttpClient.cpp +++ b/src/HttpClient.cpp @@ -587,7 +587,7 @@ String HttpClient::responseBody() } if (bodyLength > 0 && (unsigned int)bodyLength != response.length()) { - // failure, we did not read in reponse content length bytes + // failure, we did not read in response content length bytes return String((const char*)NULL); } @@ -685,7 +685,7 @@ int HttpClient::read() bool HttpClient::headerAvailable() { - // clear the currently store header line + // clear the currently stored header line iHeaderLine = ""; while (!endOfHeadersReached()) diff --git a/src/HttpClient.h b/src/HttpClient.h index 38fd799..6a7aa1d 100644 --- a/src/HttpClient.h +++ b/src/HttpClient.h @@ -228,7 +228,7 @@ class HttpClient : public Client */ String readHeaderName(); - /** Read the vallue of the current response header. + /** Read the value of the current response header. Returns empty string if a header is not available. */ String readHeaderValue(); @@ -341,7 +341,7 @@ class HttpClient : public Client // Number of milliseconds that we wait each time there isn't any data // available to be read (during status code and header processing) static const int kHttpWaitForDataDelay = 1000; - // Number of milliseconds that we'll wait in total without receiveing any + // Number of milliseconds that we'll wait in total without receiving any // data before returning HTTP_ERROR_TIMED_OUT (during status code and header // processing) static const int kHttpResponseTimeout = 30*1000; From 0d235030a6e8da348dde4f0b2abc3f22aaad9566 Mon Sep 17 00:00:00 2001 From: per1234 Date: Sun, 11 Apr 2021 21:54:28 -0700 Subject: [PATCH 084/125] Add CI workflow to do Arduino project-specific linting On every push, pull request, and periodically, run Arduino Lint to check for common problems not related to the project code. --- .github/workflows/check-arduino.yml | 28 ++++++++++++++++++++++++++++ README.md | 1 + 2 files changed, 29 insertions(+) create mode 100644 .github/workflows/check-arduino.yml diff --git a/.github/workflows/check-arduino.yml b/.github/workflows/check-arduino.yml new file mode 100644 index 0000000..0d969f6 --- /dev/null +++ b/.github/workflows/check-arduino.yml @@ -0,0 +1,28 @@ +name: Check Arduino + +# See: https://docs.github.com/en/free-pro-team@latest/actions/reference/events-that-trigger-workflows +on: + push: + pull_request: + schedule: + # Run every Tuesday at 8 AM UTC to catch breakage caused by new rules added to Arduino Lint. + - cron: "0 8 * * TUE" + workflow_dispatch: + repository_dispatch: + +jobs: + lint: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Arduino Lint + uses: arduino/arduino-lint-action@v1 + with: + compliance: specification + library-manager: update + # Always use this setting for official repositories. Remove for 3rd party projects. + official: true + project-type: library diff --git a/README.md b/README.md index b976c27..4d99298 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ # ArduinoHttpClient +[![Check Arduino status](https://github.com/arduino-libraries/ArduinoHttpClient/actions/workflows/check-arduino.yml/badge.svg)](https://github.com/arduino-libraries/ArduinoHttpClient/actions/workflows/check-arduino.yml) [![Spell Check status](https://github.com/arduino-libraries/ArduinoHttpClient/actions/workflows/spell-check.yml/badge.svg)](https://github.com/arduino-libraries/ArduinoHttpClient/actions/workflows/spell-check.yml) ArduinoHttpClient is a library to make it easier to interact with web servers from Arduino. From d07192a7f72005a2339fe499a01d2e05c8b80aec Mon Sep 17 00:00:00 2001 From: per1234 Date: Sun, 11 Apr 2021 21:56:53 -0700 Subject: [PATCH 085/125] Add "smoke test" examples compilation CI workflow On every push or pull request that affects library source or example files, and periodically, compile all example sketches for the specified boards. --- .github/workflows/compile-examples.yml | 50 ++++++++++++++++++++++++++ README.md | 1 + 2 files changed, 51 insertions(+) create mode 100644 .github/workflows/compile-examples.yml diff --git a/.github/workflows/compile-examples.yml b/.github/workflows/compile-examples.yml new file mode 100644 index 0000000..b296861 --- /dev/null +++ b/.github/workflows/compile-examples.yml @@ -0,0 +1,50 @@ +name: Compile Examples + +# See: https://docs.github.com/en/free-pro-team@latest/actions/reference/events-that-trigger-workflows +on: + push: + paths: + - ".github/workflows/compile-examples.yml" + - "examples/**" + - "src/**" + pull_request: + paths: + - ".github/workflows/compile-examples.yml" + - "examples/**" + - "src/**" + schedule: + # Run every Tuesday at 8 AM UTC to catch breakage caused by changes to external resources (libraries, platforms). + - cron: "0 8 * * TUE" + workflow_dispatch: + repository_dispatch: + +jobs: + build: + name: ${{ matrix.board.fqbn }} + runs-on: ubuntu-latest + + strategy: + fail-fast: false + + matrix: + board: + - fqbn: arduino:samd:mkr1000 + platforms: | + - name: arduino:samd + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Compile examples + uses: arduino/compile-sketches@v1 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + fqbn: ${{ matrix.board.fqbn }} + platforms: ${{ matrix.board.platforms }} + libraries: | + # Install the library from the local path. + - source-path: ./ + - name: WiFi101 + sketch-paths: | + - examples diff --git a/README.md b/README.md index 4d99298..e9c163b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # ArduinoHttpClient [![Check Arduino status](https://github.com/arduino-libraries/ArduinoHttpClient/actions/workflows/check-arduino.yml/badge.svg)](https://github.com/arduino-libraries/ArduinoHttpClient/actions/workflows/check-arduino.yml) +[![Compile Examples status](https://github.com/arduino-libraries/ArduinoHttpClient/actions/workflows/compile-examples.yml/badge.svg)](https://github.com/arduino-libraries/ArduinoHttpClient/actions/workflows/compile-examples.yml) [![Spell Check status](https://github.com/arduino-libraries/ArduinoHttpClient/actions/workflows/spell-check.yml/badge.svg)](https://github.com/arduino-libraries/ArduinoHttpClient/actions/workflows/spell-check.yml) ArduinoHttpClient is a library to make it easier to interact with web servers from Arduino. From b9db0381749abf8c2bb3bc2656a36785d104ff3d Mon Sep 17 00:00:00 2001 From: per1234 Date: Sun, 11 Apr 2021 21:57:20 -0700 Subject: [PATCH 086/125] Report changes in memory usage that would result from merging a PR On creation or commit to a pull request, a report of the resulting change in memory usage of the examples will be commented to the PR thread. --- .github/workflows/compile-examples.yml | 12 ++++++++++++ .github/workflows/report-size-deltas.yml | 24 ++++++++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 .github/workflows/report-size-deltas.yml diff --git a/.github/workflows/compile-examples.yml b/.github/workflows/compile-examples.yml index b296861..d363d82 100644 --- a/.github/workflows/compile-examples.yml +++ b/.github/workflows/compile-examples.yml @@ -23,6 +23,9 @@ jobs: name: ${{ matrix.board.fqbn }} runs-on: ubuntu-latest + env: + SKETCHES_REPORTS_PATH: sketches-reports + strategy: fail-fast: false @@ -48,3 +51,12 @@ jobs: - name: WiFi101 sketch-paths: | - examples + enable-deltas-report: true + sketches-report-path: ${{ env.SKETCHES_REPORTS_PATH }} + + - name: Save sketches report as workflow artifact + uses: actions/upload-artifact@v2 + with: + if-no-files-found: error + path: ${{ env.SKETCHES_REPORTS_PATH }} + name: ${{ env.SKETCHES_REPORTS_PATH }} diff --git a/.github/workflows/report-size-deltas.yml b/.github/workflows/report-size-deltas.yml new file mode 100644 index 0000000..652be5d --- /dev/null +++ b/.github/workflows/report-size-deltas.yml @@ -0,0 +1,24 @@ +name: Report Size Deltas + +# See: https://docs.github.com/en/free-pro-team@latest/actions/reference/events-that-trigger-workflows +on: + push: + paths: + - ".github/workflows/report-size-deltas.yml" + schedule: + # Run at the minimum interval allowed by GitHub Actions. + # Note: GitHub Actions periodically has outages which result in workflow failures. + # In this event, the workflows will start passing again once the service recovers. + - cron: "*/5 * * * *" + workflow_dispatch: + repository_dispatch: + +jobs: + report: + runs-on: ubuntu-latest + steps: + - name: Comment size deltas reports to PRs + uses: arduino/report-size-deltas@v1 + with: + # The name of the workflow artifact created by the sketch compilation workflow + sketches-reports-source: sketches-reports From c014e0cdb8ff7c8550cb5a53bec14d48f84d5356 Mon Sep 17 00:00:00 2001 From: per1234 Date: Mon, 10 Jan 2022 01:00:56 -0800 Subject: [PATCH 087/125] Add GitHub Actions workflow to synchronize with shared repository labels (#120) On every push that changes relevant files, and periodically, configure the repository's issue and pull request labels according to the universal, shared, and local label configuration files. --- .github/dependabot.yml | 2 + .github/workflows/sync-labels.yml | 138 ++++++++++++++++++++++++++++++ 2 files changed, 140 insertions(+) create mode 100644 .github/workflows/sync-labels.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 03600dd..fa738ec 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -8,3 +8,5 @@ updates: directory: / # Check the repository's workflows under /.github/workflows/ schedule: interval: daily + labels: + - "topic: infrastructure" diff --git a/.github/workflows/sync-labels.yml b/.github/workflows/sync-labels.yml new file mode 100644 index 0000000..3ee6feb --- /dev/null +++ b/.github/workflows/sync-labels.yml @@ -0,0 +1,138 @@ +# Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/sync-labels.md +name: Sync Labels + +# See: https://docs.github.com/en/actions/reference/events-that-trigger-workflows +on: + push: + paths: + - ".github/workflows/sync-labels.ya?ml" + - ".github/label-configuration-files/*.ya?ml" + pull_request: + paths: + - ".github/workflows/sync-labels.ya?ml" + - ".github/label-configuration-files/*.ya?ml" + schedule: + # Run daily at 8 AM UTC to sync with changes to shared label configurations. + - cron: "0 8 * * *" + workflow_dispatch: + repository_dispatch: + +env: + CONFIGURATIONS_FOLDER: .github/label-configuration-files + CONFIGURATIONS_ARTIFACT: label-configuration-files + +jobs: + check: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Download JSON schema for labels configuration file + id: download-schema + uses: carlosperate/download-file-action@v1 + with: + file-url: https://raw.githubusercontent.com/arduino/tooling-project-assets/main/workflow-templates/assets/sync-labels/arduino-tooling-gh-label-configuration-schema.json + location: ${{ runner.temp }}/label-configuration-schema + + - name: Install JSON schema validator + run: | + sudo npm install \ + --global \ + ajv-cli \ + ajv-formats + + - name: Validate local labels configuration + run: | + # See: https://github.com/ajv-validator/ajv-cli#readme + ajv validate \ + --all-errors \ + -c ajv-formats \ + -s "${{ steps.download-schema.outputs.file-path }}" \ + -d "${{ env.CONFIGURATIONS_FOLDER }}/*.{yml,yaml}" + + download: + needs: check + runs-on: ubuntu-latest + + strategy: + matrix: + filename: + # Filenames of the shared configurations to apply to the repository in addition to the local configuration. + # https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/assets/sync-labels + - universal.yml + + steps: + - name: Download + uses: carlosperate/download-file-action@v1 + with: + file-url: https://raw.githubusercontent.com/arduino/tooling-project-assets/main/workflow-templates/assets/sync-labels/${{ matrix.filename }} + + - name: Pass configuration files to next job via workflow artifact + uses: actions/upload-artifact@v2 + with: + path: | + *.yaml + *.yml + if-no-files-found: error + name: ${{ env.CONFIGURATIONS_ARTIFACT }} + + sync: + needs: download + runs-on: ubuntu-latest + + steps: + - name: Set environment variables + run: | + # See: https://docs.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-an-environment-variable + echo "MERGED_CONFIGURATION_PATH=${{ runner.temp }}/labels.yml" >> "$GITHUB_ENV" + + - name: Determine whether to dry run + id: dry-run + if: > + github.event_name == 'pull_request' || + ( + ( + github.event_name == 'push' || + github.event_name == 'workflow_dispatch' + ) && + github.ref != format('refs/heads/{0}', github.event.repository.default_branch) + ) + run: | + # Use of this flag in the github-label-sync command will cause it to only check the validity of the + # configuration. + echo "::set-output name=flag::--dry-run" + + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Download configuration files artifact + uses: actions/download-artifact@v2 + with: + name: ${{ env.CONFIGURATIONS_ARTIFACT }} + path: ${{ env.CONFIGURATIONS_FOLDER }} + + - name: Remove unneeded artifact + uses: geekyeggo/delete-artifact@v1 + with: + name: ${{ env.CONFIGURATIONS_ARTIFACT }} + + - name: Merge label configuration files + run: | + # Merge all configuration files + shopt -s extglob + cat "${{ env.CONFIGURATIONS_FOLDER }}"/*.@(yml|yaml) > "${{ env.MERGED_CONFIGURATION_PATH }}" + + - name: Install github-label-sync + run: sudo npm install --global github-label-sync + + - name: Sync labels + env: + GITHUB_ACCESS_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + # See: https://github.com/Financial-Times/github-label-sync + github-label-sync \ + --labels "${{ env.MERGED_CONFIGURATION_PATH }}" \ + ${{ steps.dry-run.outputs.flag }} \ + ${{ github.repository }} From 09c53095e3e25549554fe20de6fc5147cff5bf5b Mon Sep 17 00:00:00 2001 From: Pablo Clemente Date: Tue, 25 Jan 2022 12:00:21 +0100 Subject: [PATCH 088/125] Added configurabel WebSocket tx buffer size --- src/WebSocketClient.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/WebSocketClient.h b/src/WebSocketClient.h index 4b009e6..96eb6d2 100644 --- a/src/WebSocketClient.h +++ b/src/WebSocketClient.h @@ -8,6 +8,10 @@ #include "HttpClient.h" +#ifndef WS_TX_BUFFER_SIZE + #define WS_TX_BUFFER_SIZE 128 +#endif + static const int TYPE_CONTINUATION = 0x0; static const int TYPE_TEXT = 0x1; static const int TYPE_BINARY = 0x2; @@ -86,7 +90,7 @@ class WebSocketClient : public HttpClient private: bool iTxStarted; uint8_t iTxMessageType; - uint8_t iTxBuffer[128]; + uint8_t iTxBuffer[WS_TX_BUFFER_SIZE]; uint64_t iTxSize; uint8_t iRxOpCode; From 527967d010b0b2e290ca051e6ce9e6c821f00c58 Mon Sep 17 00:00:00 2001 From: carbotaniuman <41451839+carbotaniuman@users.noreply.github.com> Date: Tue, 1 Mar 2022 08:07:10 -0600 Subject: [PATCH 089/125] Reduce timeout from 1000ms to 100ms Using this with the Arduino `WiFiClient` makes simple requests really slow. Reduce the timeout in order to make it faster. --- src/HttpClient.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/HttpClient.h b/src/HttpClient.h index 6a7aa1d..1aeb1d5 100644 --- a/src/HttpClient.h +++ b/src/HttpClient.h @@ -340,7 +340,7 @@ class HttpClient : public Client // Number of milliseconds that we wait each time there isn't any data // available to be read (during status code and header processing) - static const int kHttpWaitForDataDelay = 1000; + static const int kHttpWaitForDataDelay = 100; // Number of milliseconds that we'll wait in total without receiving any // data before returning HTTP_ERROR_TIMED_OUT (during status code and header // processing) From 2b74b7a619bff7ed9226a2c131d297afd842f0a4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 2 Mar 2022 17:11:28 +0000 Subject: [PATCH 090/125] Bump actions/checkout from 2 to 3 Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 3. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v2...v3) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/check-arduino.yml | 2 +- .github/workflows/compile-examples.yml | 2 +- .github/workflows/spell-check.yml | 2 +- .github/workflows/sync-labels.yml | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/check-arduino.yml b/.github/workflows/check-arduino.yml index 0d969f6..3e0d26c 100644 --- a/.github/workflows/check-arduino.yml +++ b/.github/workflows/check-arduino.yml @@ -16,7 +16,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Arduino Lint uses: arduino/arduino-lint-action@v1 diff --git a/.github/workflows/compile-examples.yml b/.github/workflows/compile-examples.yml index d363d82..c5ab21f 100644 --- a/.github/workflows/compile-examples.yml +++ b/.github/workflows/compile-examples.yml @@ -37,7 +37,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Compile examples uses: arduino/compile-sketches@v1 diff --git a/.github/workflows/spell-check.yml b/.github/workflows/spell-check.yml index 01bee87..3f6b03f 100644 --- a/.github/workflows/spell-check.yml +++ b/.github/workflows/spell-check.yml @@ -16,7 +16,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Spell check uses: codespell-project/actions-codespell@master diff --git a/.github/workflows/sync-labels.yml b/.github/workflows/sync-labels.yml index 3ee6feb..4ea5755 100644 --- a/.github/workflows/sync-labels.yml +++ b/.github/workflows/sync-labels.yml @@ -27,7 +27,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Download JSON schema for labels configuration file id: download-schema @@ -105,7 +105,7 @@ jobs: echo "::set-output name=flag::--dry-run" - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Download configuration files artifact uses: actions/download-artifact@v2 From 533711d23a4f5650808408a82464e6b89d855f21 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 8 Apr 2022 17:08:54 +0000 Subject: [PATCH 091/125] Bump actions/upload-artifact from 2 to 3 Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 2 to 3. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v2...v3) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/compile-examples.yml | 2 +- .github/workflows/sync-labels.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/compile-examples.yml b/.github/workflows/compile-examples.yml index c5ab21f..17cbc1f 100644 --- a/.github/workflows/compile-examples.yml +++ b/.github/workflows/compile-examples.yml @@ -55,7 +55,7 @@ jobs: sketches-report-path: ${{ env.SKETCHES_REPORTS_PATH }} - name: Save sketches report as workflow artifact - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: if-no-files-found: error path: ${{ env.SKETCHES_REPORTS_PATH }} diff --git a/.github/workflows/sync-labels.yml b/.github/workflows/sync-labels.yml index 4ea5755..1d969d5 100644 --- a/.github/workflows/sync-labels.yml +++ b/.github/workflows/sync-labels.yml @@ -70,7 +70,7 @@ jobs: file-url: https://raw.githubusercontent.com/arduino/tooling-project-assets/main/workflow-templates/assets/sync-labels/${{ matrix.filename }} - name: Pass configuration files to next job via workflow artifact - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: path: | *.yaml From 033387f9ff6c415f2b4fb55211d8e419a7c7c101 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 8 Apr 2022 17:08:57 +0000 Subject: [PATCH 092/125] Bump actions/download-artifact from 2 to 3 Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 2 to 3. - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v2...v3) --- updated-dependencies: - dependency-name: actions/download-artifact dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/sync-labels.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sync-labels.yml b/.github/workflows/sync-labels.yml index 4ea5755..e84e803 100644 --- a/.github/workflows/sync-labels.yml +++ b/.github/workflows/sync-labels.yml @@ -108,7 +108,7 @@ jobs: uses: actions/checkout@v3 - name: Download configuration files artifact - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v3 with: name: ${{ env.CONFIGURATIONS_ARTIFACT }} path: ${{ env.CONFIGURATIONS_FOLDER }} From b7fe7f78d4510119e7735a85a9e4115bc5baf8b7 Mon Sep 17 00:00:00 2001 From: per1234 Date: Sun, 24 Apr 2022 21:52:33 -0700 Subject: [PATCH 093/125] Remove superfluous and broken manifest file (#133) `library.json` is PlatformIO's native library manifest file but Arduino's `library.properties` is also supported by PlatformIO: https://docs.platformio.org/en/latest/librarymanager/creating.html#creating-library The `library.json` file was missing the required `version` field: https://docs.platformio.org/en/latest/librarymanager/config.html#version Although it could be added, experience with other libraries indicates that it is not really realistic to expect two manifest files to be properly maintained. Since the Arduino development software and the Arduino Library Manager requires `library.properties`, if we are to have only one the choice of keeping `library.properties` and removing `library.json` is clear. --- library.json | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 library.json diff --git a/library.json b/library.json deleted file mode 100644 index 5376d0a..0000000 --- a/library.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "name": "ArduinoHttpClient", - "keywords": "http, web, client, ethernet, wifi, GSM", - "description": "Easily interact with web servers from Arduino, using HTTP and WebSockets.", - "repository": - { - "type": "git", - "url": "https://github.com/arduino-libraries/ArduinoHttpClient.git" - }, - "frameworks": "arduino", - "platforms": "*" -} From 1f50562ffc1a37be45766380bebb143db1094ab6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 12 Oct 2022 17:10:08 +0000 Subject: [PATCH 094/125] Bump geekyeggo/delete-artifact from 1 to 2 Bumps [geekyeggo/delete-artifact](https://github.com/geekyeggo/delete-artifact) from 1 to 2. - [Release notes](https://github.com/geekyeggo/delete-artifact/releases) - [Commits](https://github.com/geekyeggo/delete-artifact/compare/v1...v2) --- updated-dependencies: - dependency-name: geekyeggo/delete-artifact dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/sync-labels.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sync-labels.yml b/.github/workflows/sync-labels.yml index 986bda6..10abaea 100644 --- a/.github/workflows/sync-labels.yml +++ b/.github/workflows/sync-labels.yml @@ -114,7 +114,7 @@ jobs: path: ${{ env.CONFIGURATIONS_FOLDER }} - name: Remove unneeded artifact - uses: geekyeggo/delete-artifact@v1 + uses: geekyeggo/delete-artifact@v2 with: name: ${{ env.CONFIGURATIONS_ARTIFACT }} From 8fc4883140e95be88781274d9e9c227e2030d9e9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Oct 2022 17:52:24 +0000 Subject: [PATCH 095/125] Bump carlosperate/download-file-action from 1 to 2 Bumps [carlosperate/download-file-action](https://github.com/carlosperate/download-file-action) from 1 to 2. - [Release notes](https://github.com/carlosperate/download-file-action/releases) - [Commits](https://github.com/carlosperate/download-file-action/compare/v1...v2) --- updated-dependencies: - dependency-name: carlosperate/download-file-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/sync-labels.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/sync-labels.yml b/.github/workflows/sync-labels.yml index 10abaea..94938f3 100644 --- a/.github/workflows/sync-labels.yml +++ b/.github/workflows/sync-labels.yml @@ -31,7 +31,7 @@ jobs: - name: Download JSON schema for labels configuration file id: download-schema - uses: carlosperate/download-file-action@v1 + uses: carlosperate/download-file-action@v2 with: file-url: https://raw.githubusercontent.com/arduino/tooling-project-assets/main/workflow-templates/assets/sync-labels/arduino-tooling-gh-label-configuration-schema.json location: ${{ runner.temp }}/label-configuration-schema @@ -65,7 +65,7 @@ jobs: steps: - name: Download - uses: carlosperate/download-file-action@v1 + uses: carlosperate/download-file-action@v2 with: file-url: https://raw.githubusercontent.com/arduino/tooling-project-assets/main/workflow-templates/assets/sync-labels/${{ matrix.filename }} From 8566fb4cc0098ef2c91f080f5eb877eefa0e23b6 Mon Sep 17 00:00:00 2001 From: Tim Vandecasteele Date: Sun, 9 Apr 2023 18:34:47 +0200 Subject: [PATCH 096/125] Make sure to use HttpClient when upgrading the connection for websockets Using WebSocketClient::begin I got into problems where `status = responseStatusCode();` would be trying to read the HTTP header, but because both HttpClient and WebSocketClient have a read function, the read from WebSocketClient was used, which returns a bunch of gibberish. This caused the WebSocket to think that the connection was not successfully upgraded. --- src/HttpClient.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/HttpClient.cpp b/src/HttpClient.cpp index 1c73464..000c763 100644 --- a/src/HttpClient.cpp +++ b/src/HttpClient.cpp @@ -421,7 +421,7 @@ int HttpClient::responseStatusCode() { if (available()) { - c = read(); + c = HttpClient::read(); if (c != -1) { switch(iState) @@ -762,7 +762,7 @@ int HttpClient::read(uint8_t *buf, size_t size) int HttpClient::readHeader() { - char c = read(); + char c = HttpClient::read(); if (endOfHeadersReached()) { From 0a1a8ab51a1ef60d5cfbea05cbc5a9020521d6d5 Mon Sep 17 00:00:00 2001 From: Alexander Entinger Date: Mon, 17 Jul 2023 09:53:42 +0200 Subject: [PATCH 097/125] Release v0.5.0. --- library.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library.properties b/library.properties index 2682599..3b7307e 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=ArduinoHttpClient -version=0.4.0 +version=0.5.0 author=Arduino maintainer=Arduino sentence=[EXPERIMENTAL] Easily interact with web servers from Arduino, using HTTP and WebSockets. From b93e3d233523f97f8a4da91016132b7ec94c52be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leonardo=20Jos=C3=A9?= Date: Fri, 4 Aug 2023 22:27:58 -0300 Subject: [PATCH 098/125] Add ability to set response wait time --- src/HttpClient.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/HttpClient.h b/src/HttpClient.h index 6a7aa1d..2ca2e31 100644 --- a/src/HttpClient.h +++ b/src/HttpClient.h @@ -384,6 +384,7 @@ class HttpClient : public Client // Stores the value of the current chunk length, if present int iChunkLength; uint32_t iHttpResponseTimeout; + uint32_t iHttpWaitForDataDelay; bool iConnectionClose; bool iSendDefaultRequestHeaders; String iHeaderLine; From 9b3987ca7ff6ba44a459e7401a12307711466a81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leonardo=20Jos=C3=A9?= Date: Fri, 4 Aug 2023 22:33:13 -0300 Subject: [PATCH 099/125] Add getter and setter functions --- src/HttpClient.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/HttpClient.h b/src/HttpClient.h index 2ca2e31..cf24d96 100644 --- a/src/HttpClient.h +++ b/src/HttpClient.h @@ -317,6 +317,8 @@ class HttpClient : public Client virtual operator bool() { return bool(iClient); }; virtual uint32_t httpResponseTimeout() { return iHttpResponseTimeout; }; virtual void setHttpResponseTimeout(uint32_t timeout) { iHttpResponseTimeout = timeout; }; + virtual uint32_t httpWaitForDataDelay() { return iHttpWaitForDataDelay; }; + virtual void setHttpWaitForDataDelay(uint32_t delay) { iHttpWaitForDataDelay = delay; }; protected: /** Reset internal state data back to the "just initialised" state */ From c490dde1f16875b988a45d21f9bb9678011061eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leonardo=20Jos=C3=A9?= Date: Fri, 4 Aug 2023 22:35:09 -0300 Subject: [PATCH 100/125] Replace delay constant by variable --- src/HttpClient.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/HttpClient.cpp b/src/HttpClient.cpp index 1c73464..5b07fd7 100644 --- a/src/HttpClient.cpp +++ b/src/HttpClient.cpp @@ -40,6 +40,7 @@ void HttpClient::resetState() iIsChunked = false; iChunkLength = 0; iHttpResponseTimeout = kHttpResponseTimeout; + iHttpWaitForDataDelay = kHttpWaitForDataDelay; } void HttpClient::stop() @@ -473,7 +474,7 @@ int HttpClient::responseStatusCode() { // We haven't got any data, so let's pause to allow some to // arrive - delay(kHttpWaitForDataDelay); + delay(iHttpWaitForDataDelay); } } if ( (c == '\n') && (iStatusCode < 200 && iStatusCode != 101) ) @@ -522,7 +523,7 @@ int HttpClient::skipResponseHeaders() { // We haven't got any data, so let's pause to allow some to // arrive - delay(kHttpWaitForDataDelay); + delay(iHttpWaitForDataDelay); } } if (endOfHeadersReached()) From 42e8f1023176e6c13b619f4651b6c01030120f39 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Sep 2023 17:45:42 +0000 Subject: [PATCH 101/125] Bump actions/checkout from 3 to 4 Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/check-arduino.yml | 2 +- .github/workflows/compile-examples.yml | 2 +- .github/workflows/spell-check.yml | 2 +- .github/workflows/sync-labels.yml | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/check-arduino.yml b/.github/workflows/check-arduino.yml index 3e0d26c..adb330f 100644 --- a/.github/workflows/check-arduino.yml +++ b/.github/workflows/check-arduino.yml @@ -16,7 +16,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Arduino Lint uses: arduino/arduino-lint-action@v1 diff --git a/.github/workflows/compile-examples.yml b/.github/workflows/compile-examples.yml index 17cbc1f..18ea893 100644 --- a/.github/workflows/compile-examples.yml +++ b/.github/workflows/compile-examples.yml @@ -37,7 +37,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Compile examples uses: arduino/compile-sketches@v1 diff --git a/.github/workflows/spell-check.yml b/.github/workflows/spell-check.yml index 3f6b03f..ef7d894 100644 --- a/.github/workflows/spell-check.yml +++ b/.github/workflows/spell-check.yml @@ -16,7 +16,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Spell check uses: codespell-project/actions-codespell@master diff --git a/.github/workflows/sync-labels.yml b/.github/workflows/sync-labels.yml index 94938f3..9cde1ac 100644 --- a/.github/workflows/sync-labels.yml +++ b/.github/workflows/sync-labels.yml @@ -27,7 +27,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Download JSON schema for labels configuration file id: download-schema @@ -105,7 +105,7 @@ jobs: echo "::set-output name=flag::--dry-run" - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Download configuration files artifact uses: actions/download-artifact@v3 From 0a3e61acaff5eb127359e6f0cf40c8ff18203b56 Mon Sep 17 00:00:00 2001 From: Kodav <46531343+kodav@users.noreply.github.com> Date: Thu, 2 Nov 2023 16:51:50 +0700 Subject: [PATCH 102/125] Update HttpClient.cpp Add 443 port for HTTPS requests --- src/HttpClient.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/HttpClient.cpp b/src/HttpClient.cpp index 1c73464..d9fed33 100644 --- a/src/HttpClient.cpp +++ b/src/HttpClient.cpp @@ -161,7 +161,7 @@ int HttpClient::sendInitialHeaders(const char* aURLPath, const char* aHttpMethod { iClient->print("Host: "); iClient->print(iServerName); - if (iServerPort != kHttpPort) + if (iServerPort != kHttpPort && iServerPort != kHttpsPort) { iClient->print(":"); iClient->print(iServerPort); From ab3d0000f736a961cbecbd983412dbf782e84969 Mon Sep 17 00:00:00 2001 From: Kodav <46531343+kodav@users.noreply.github.com> Date: Thu, 2 Nov 2023 16:55:21 +0700 Subject: [PATCH 103/125] Update HttpClient.h --- src/HttpClient.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/HttpClient.h b/src/HttpClient.h index 6a7aa1d..1b3eb3e 100644 --- a/src/HttpClient.h +++ b/src/HttpClient.h @@ -43,6 +43,7 @@ class HttpClient : public Client public: static const int kNoContentLengthHeader =-1; static const int kHttpPort =80; + static const int kHttpsPort =443; static const char* kUserAgent; // FIXME Write longer API request, using port and user-agent, example From a721a5b6b31d6eb5bed8ffb170048f13dbb88da9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 16 Feb 2024 08:24:56 +0100 Subject: [PATCH 104/125] Bump actions/upload-artifact from 3 to 4 (#164) Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 3 to 4. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/compile-examples.yml | 2 +- .github/workflows/sync-labels.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/compile-examples.yml b/.github/workflows/compile-examples.yml index 18ea893..6dbcd38 100644 --- a/.github/workflows/compile-examples.yml +++ b/.github/workflows/compile-examples.yml @@ -55,7 +55,7 @@ jobs: sketches-report-path: ${{ env.SKETCHES_REPORTS_PATH }} - name: Save sketches report as workflow artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: if-no-files-found: error path: ${{ env.SKETCHES_REPORTS_PATH }} diff --git a/.github/workflows/sync-labels.yml b/.github/workflows/sync-labels.yml index 9cde1ac..7680b37 100644 --- a/.github/workflows/sync-labels.yml +++ b/.github/workflows/sync-labels.yml @@ -70,7 +70,7 @@ jobs: file-url: https://raw.githubusercontent.com/arduino/tooling-project-assets/main/workflow-templates/assets/sync-labels/${{ matrix.filename }} - name: Pass configuration files to next job via workflow artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: path: | *.yaml From 13d12a45facb356447499431d6dc23d81386ea15 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 16 Feb 2024 08:25:10 +0100 Subject: [PATCH 105/125] Bump actions/download-artifact from 3 to 4 (#165) Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 3 to 4. - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/download-artifact dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/sync-labels.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sync-labels.yml b/.github/workflows/sync-labels.yml index 7680b37..2e1d6e0 100644 --- a/.github/workflows/sync-labels.yml +++ b/.github/workflows/sync-labels.yml @@ -108,7 +108,7 @@ jobs: uses: actions/checkout@v4 - name: Download configuration files artifact - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: ${{ env.CONFIGURATIONS_ARTIFACT }} path: ${{ env.CONFIGURATIONS_FOLDER }} From a6a4127294c52e00a85db7ec751f4a376525f3f4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 16 Feb 2024 08:25:26 +0100 Subject: [PATCH 106/125] Bump geekyeggo/delete-artifact from 2 to 4 (#167) Bumps [geekyeggo/delete-artifact](https://github.com/geekyeggo/delete-artifact) from 2 to 4. - [Release notes](https://github.com/geekyeggo/delete-artifact/releases) - [Changelog](https://github.com/GeekyEggo/delete-artifact/blob/main/CHANGELOG.md) - [Commits](https://github.com/geekyeggo/delete-artifact/compare/v2...v4) --- updated-dependencies: - dependency-name: geekyeggo/delete-artifact dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/sync-labels.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sync-labels.yml b/.github/workflows/sync-labels.yml index 2e1d6e0..47ac50a 100644 --- a/.github/workflows/sync-labels.yml +++ b/.github/workflows/sync-labels.yml @@ -114,7 +114,7 @@ jobs: path: ${{ env.CONFIGURATIONS_FOLDER }} - name: Remove unneeded artifact - uses: geekyeggo/delete-artifact@v2 + uses: geekyeggo/delete-artifact@v4 with: name: ${{ env.CONFIGURATIONS_ARTIFACT }} From 9625575fcc63dd284842a55d6b54cba23284d6ae Mon Sep 17 00:00:00 2001 From: Alexander Entinger Date: Fri, 16 Feb 2024 08:31:45 +0100 Subject: [PATCH 107/125] Fix regression re report-size-deltas after updating actions/upload-artifact. (#171) For more information see https://github.com/arduino/report-size-deltas/blob/main/docs/FAQ.md#size-deltas-report-workflow-triggered-by-schedule-event . --- .github/workflows/compile-examples.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/compile-examples.yml b/.github/workflows/compile-examples.yml index 6dbcd38..0dd5ac7 100644 --- a/.github/workflows/compile-examples.yml +++ b/.github/workflows/compile-examples.yml @@ -34,6 +34,7 @@ jobs: - fqbn: arduino:samd:mkr1000 platforms: | - name: arduino:samd + artifact-name-suffix: arduino-samd-mkr1000 steps: - name: Checkout repository @@ -59,4 +60,4 @@ jobs: with: if-no-files-found: error path: ${{ env.SKETCHES_REPORTS_PATH }} - name: ${{ env.SKETCHES_REPORTS_PATH }} + name: sketches-report-${{ matrix.board.artifact-name-suffix }} From 7a6b39a1af31c175c7e999ac366f52b6a3efcba4 Mon Sep 17 00:00:00 2001 From: Alexander Entinger Date: Wed, 21 Feb 2024 06:58:53 +0100 Subject: [PATCH 108/125] Fix regression: report size delta size on PR. (#172) The necessary steps have in fact been documented here: https://github.com/arduino/report-size-deltas/blob/main/docs/FAQ.md#workflow-triggered-by-pull_request-event but I have overlooked them when I fixed the upload issue. With this PR the size deltas are - once again - reported within the PR. --- .github/workflows/compile-examples.yml | 27 ++++++++++++++++++++---- .github/workflows/report-size-deltas.yml | 24 --------------------- .gitignore | 1 + 3 files changed, 24 insertions(+), 28 deletions(-) delete mode 100644 .github/workflows/report-size-deltas.yml diff --git a/.github/workflows/compile-examples.yml b/.github/workflows/compile-examples.yml index 0dd5ac7..d7a183e 100644 --- a/.github/workflows/compile-examples.yml +++ b/.github/workflows/compile-examples.yml @@ -18,14 +18,15 @@ on: workflow_dispatch: repository_dispatch: +env: + # It's convenient to set variables for values used multiple times in the workflow. + SKETCHES_REPORTS_PATH: sketches-reports + jobs: - build: + compile: name: ${{ matrix.board.fqbn }} runs-on: ubuntu-latest - env: - SKETCHES_REPORTS_PATH: sketches-reports - strategy: fail-fast: false @@ -61,3 +62,21 @@ jobs: if-no-files-found: error path: ${{ env.SKETCHES_REPORTS_PATH }} name: sketches-report-${{ matrix.board.artifact-name-suffix }} + + # When using a matrix to compile for multiple boards, it's necessary to use a separate job for the deltas report + report: + needs: compile # Wait for the compile job to finish to get the data for the report + if: github.event_name == 'pull_request' # Only run the job when the workflow is triggered by a pull request + runs-on: ubuntu-latest + + steps: + # This step is needed to get the size data produced by the compile jobs + - name: Download sketches reports artifacts + uses: actions/download-artifact@v4 + with: + # All workflow artifacts will be downloaded to this location. + path: ${{ env.SKETCHES_REPORTS_PATH }} + + - uses: arduino/report-size-deltas@v1 + with: + sketches-reports-source: ${{ env.SKETCHES_REPORTS_PATH }} diff --git a/.github/workflows/report-size-deltas.yml b/.github/workflows/report-size-deltas.yml deleted file mode 100644 index 652be5d..0000000 --- a/.github/workflows/report-size-deltas.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: Report Size Deltas - -# See: https://docs.github.com/en/free-pro-team@latest/actions/reference/events-that-trigger-workflows -on: - push: - paths: - - ".github/workflows/report-size-deltas.yml" - schedule: - # Run at the minimum interval allowed by GitHub Actions. - # Note: GitHub Actions periodically has outages which result in workflow failures. - # In this event, the workflows will start passing again once the service recovers. - - cron: "*/5 * * * *" - workflow_dispatch: - repository_dispatch: - -jobs: - report: - runs-on: ubuntu-latest - steps: - - name: Comment size deltas reports to PRs - uses: arduino/report-size-deltas@v1 - with: - # The name of the workflow artifact created by the sketch compilation workflow - sketches-reports-source: sketches-reports diff --git a/.gitignore b/.gitignore index 653acab..24a0007 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ examples/node_test_server/node_modules/ *.DS_Store */.DS_Store examples/.DS_Store +.idea/ From cf741b0dbf59ca71f49759e04e774bb670fde6b5 Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Wed, 21 Feb 2024 14:41:20 +0100 Subject: [PATCH 109/125] Imported Url parser from src/http/ngx_http_parse.c from NGINX --- src/utility/LICENSE | 23 + src/utility/http_parser.c | 2470 +++++++++++++++++++++++++++++++++++++ src/utility/http_parser.h | 432 +++++++ 3 files changed, 2925 insertions(+) create mode 100644 src/utility/LICENSE create mode 100644 src/utility/http_parser.c create mode 100644 src/utility/http_parser.h diff --git a/src/utility/LICENSE b/src/utility/LICENSE new file mode 100644 index 0000000..5baf7c0 --- /dev/null +++ b/src/utility/LICENSE @@ -0,0 +1,23 @@ +http_parser.c is based on src/http/ngx_http_parse.c from NGINX copyright +Igor Sysoev. + +Additional changes are licensed under the same terms as NGINX and +copyright Joyent, Inc. and other Node contributors. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. diff --git a/src/utility/http_parser.c b/src/utility/http_parser.c new file mode 100644 index 0000000..b6bd43e --- /dev/null +++ b/src/utility/http_parser.c @@ -0,0 +1,2470 @@ +/* Based on src/http/ngx_http_parse.c from NGINX copyright Igor Sysoev + * + * Additional changes are licensed under the same terms as NGINX and + * copyright Joyent, Inc. and other Node contributors. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ +#include "http_parser.h" +#include +#include +#include +#include +#include +#include + +#ifndef ULLONG_MAX +# define ULLONG_MAX ((uint64_t) -1) /* 2^64-1 */ +#endif + +#ifndef MIN +# define MIN(a,b) ((a) < (b) ? (a) : (b)) +#endif + +#ifndef ARRAY_SIZE +# define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) +#endif + +#ifndef BIT_AT +# define BIT_AT(a, i) \ + (!!((unsigned int) (a)[(unsigned int) (i) >> 3] & \ + (1 << ((unsigned int) (i) & 7)))) +#endif + +#ifndef ELEM_AT +# define ELEM_AT(a, i, v) ((unsigned int) (i) < ARRAY_SIZE(a) ? (a)[(i)] : (v)) +#endif + +#define SET_ERRNO(e) \ +do { \ + parser->http_errno = (e); \ +} while(0) + +#define CURRENT_STATE() p_state +#define UPDATE_STATE(V) p_state = (enum state) (V); +#define RETURN(V) \ +do { \ + parser->state = CURRENT_STATE(); \ + return (V); \ +} while (0); +#define REEXECUTE() \ + goto reexecute; \ + + +#ifdef __GNUC__ +# define LIKELY(X) __builtin_expect(!!(X), 1) +# define UNLIKELY(X) __builtin_expect(!!(X), 0) +#else +# define LIKELY(X) (X) +# define UNLIKELY(X) (X) +#endif + + +/* Run the notify callback FOR, returning ER if it fails */ +#define CALLBACK_NOTIFY_(FOR, ER) \ +do { \ + assert(HTTP_PARSER_ERRNO(parser) == HPE_OK); \ + \ + if (LIKELY(settings->on_##FOR)) { \ + parser->state = CURRENT_STATE(); \ + if (UNLIKELY(0 != settings->on_##FOR(parser))) { \ + SET_ERRNO(HPE_CB_##FOR); \ + } \ + UPDATE_STATE(parser->state); \ + \ + /* We either errored above or got paused; get out */ \ + if (UNLIKELY(HTTP_PARSER_ERRNO(parser) != HPE_OK)) { \ + return (ER); \ + } \ + } \ +} while (0) + +/* Run the notify callback FOR and consume the current byte */ +#define CALLBACK_NOTIFY(FOR) CALLBACK_NOTIFY_(FOR, p - data + 1) + +/* Run the notify callback FOR and don't consume the current byte */ +#define CALLBACK_NOTIFY_NOADVANCE(FOR) CALLBACK_NOTIFY_(FOR, p - data) + +/* Run data callback FOR with LEN bytes, returning ER if it fails */ +#define CALLBACK_DATA_(FOR, LEN, ER) \ +do { \ + assert(HTTP_PARSER_ERRNO(parser) == HPE_OK); \ + \ + if (FOR##_mark) { \ + if (LIKELY(settings->on_##FOR)) { \ + parser->state = CURRENT_STATE(); \ + if (UNLIKELY(0 != \ + settings->on_##FOR(parser, FOR##_mark, (LEN)))) { \ + SET_ERRNO(HPE_CB_##FOR); \ + } \ + UPDATE_STATE(parser->state); \ + \ + /* We either errored above or got paused; get out */ \ + if (UNLIKELY(HTTP_PARSER_ERRNO(parser) != HPE_OK)) { \ + return (ER); \ + } \ + } \ + FOR##_mark = NULL; \ + } \ +} while (0) + +/* Run the data callback FOR and consume the current byte */ +#define CALLBACK_DATA(FOR) \ + CALLBACK_DATA_(FOR, p - FOR##_mark, p - data + 1) + +/* Run the data callback FOR and don't consume the current byte */ +#define CALLBACK_DATA_NOADVANCE(FOR) \ + CALLBACK_DATA_(FOR, p - FOR##_mark, p - data) + +/* Set the mark FOR; non-destructive if mark is already set */ +#define MARK(FOR) \ +do { \ + if (!FOR##_mark) { \ + FOR##_mark = p; \ + } \ +} while (0) + +/* Don't allow the total size of the HTTP headers (including the status + * line) to exceed HTTP_MAX_HEADER_SIZE. This check is here to protect + * embedders against denial-of-service attacks where the attacker feeds + * us a never-ending header that the embedder keeps buffering. + * + * This check is arguably the responsibility of embedders but we're doing + * it on the embedder's behalf because most won't bother and this way we + * make the web a little safer. HTTP_MAX_HEADER_SIZE is still far bigger + * than any reasonable request or response so this should never affect + * day-to-day operation. + */ +#define COUNT_HEADER_SIZE(V) \ +do { \ + parser->nread += (V); \ + if (UNLIKELY(parser->nread > (HTTP_MAX_HEADER_SIZE))) { \ + SET_ERRNO(HPE_HEADER_OVERFLOW); \ + goto error; \ + } \ +} while (0) + + +#define PROXY_CONNECTION "proxy-connection" +#define CONNECTION "connection" +#define CONTENT_LENGTH "content-length" +#define TRANSFER_ENCODING "transfer-encoding" +#define UPGRADE "upgrade" +#define CHUNKED "chunked" +#define KEEP_ALIVE "keep-alive" +#define CLOSE "close" + + +static const char *method_strings[] = + { +#define XX(num, name, string) #string, + HTTP_METHOD_MAP(XX) +#undef XX + }; + + +/* Tokens as defined by rfc 2616. Also lowercases them. + * token = 1* + * separators = "(" | ")" | "<" | ">" | "@" + * | "," | ";" | ":" | "\" | <"> + * | "/" | "[" | "]" | "?" | "=" + * | "{" | "}" | SP | HT + */ +static const char tokens[256] = { +/* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */ + 0, 0, 0, 0, 0, 0, 0, 0, +/* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */ + 0, 0, 0, 0, 0, 0, 0, 0, +/* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */ + 0, 0, 0, 0, 0, 0, 0, 0, +/* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */ + 0, 0, 0, 0, 0, 0, 0, 0, +/* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */ + 0, '!', 0, '#', '$', '%', '&', '\'', +/* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */ + 0, 0, '*', '+', 0, '-', '.', 0, +/* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */ + '0', '1', '2', '3', '4', '5', '6', '7', +/* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */ + '8', '9', 0, 0, 0, 0, 0, 0, +/* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */ + 0, 'a', 'b', 'c', 'd', 'e', 'f', 'g', +/* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */ + 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', +/* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */ + 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', +/* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */ + 'x', 'y', 'z', 0, 0, 0, '^', '_', +/* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */ + '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', +/* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */ + 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', +/* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */ + 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', +/* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */ + 'x', 'y', 'z', 0, '|', 0, '~', 0 }; + + +static const int8_t unhex[256] = + {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 + ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 + ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 + , 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-1,-1,-1,-1,-1,-1 + ,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1 + ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 + ,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1 + ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 + }; + + +#if HTTP_PARSER_STRICT +# define T(v) 0 +#else +# define T(v) v +#endif + + +static const uint8_t normal_url_char[32] = { +/* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */ + 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0, +/* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */ + 0 | T(2) | 0 | 0 | T(16) | 0 | 0 | 0, +/* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */ + 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0, +/* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */ + 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0, +/* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */ + 0 | 2 | 4 | 0 | 16 | 32 | 64 | 128, +/* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 0, +/* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 0, }; + +#undef T + +enum state + { s_dead = 1 /* important that this is > 0 */ + + , s_start_req_or_res + , s_res_or_resp_H + , s_start_res + , s_res_H + , s_res_HT + , s_res_HTT + , s_res_HTTP + , s_res_first_http_major + , s_res_http_major + , s_res_first_http_minor + , s_res_http_minor + , s_res_first_status_code + , s_res_status_code + , s_res_status_start + , s_res_status + , s_res_line_almost_done + + , s_start_req + + , s_req_method + , s_req_spaces_before_url + , s_req_schema + , s_req_schema_slash + , s_req_schema_slash_slash + , s_req_server_start + , s_req_server + , s_req_server_with_at + , s_req_path + , s_req_query_string_start + , s_req_query_string + , s_req_fragment_start + , s_req_fragment + , s_req_http_start + , s_req_http_H + , s_req_http_HT + , s_req_http_HTT + , s_req_http_HTTP + , s_req_first_http_major + , s_req_http_major + , s_req_first_http_minor + , s_req_http_minor + , s_req_line_almost_done + + , s_header_field_start + , s_header_field + , s_header_value_discard_ws + , s_header_value_discard_ws_almost_done + , s_header_value_discard_lws + , s_header_value_start + , s_header_value + , s_header_value_lws + + , s_header_almost_done + + , s_chunk_size_start + , s_chunk_size + , s_chunk_parameters + , s_chunk_size_almost_done + + , s_headers_almost_done + , s_headers_done + + /* Important: 's_headers_done' must be the last 'header' state. All + * states beyond this must be 'body' states. It is used for overflow + * checking. See the PARSING_HEADER() macro. + */ + + , s_chunk_data + , s_chunk_data_almost_done + , s_chunk_data_done + + , s_body_identity + , s_body_identity_eof + + , s_message_done + }; + + +#define PARSING_HEADER(state) (state <= s_headers_done) + + +enum header_states + { h_general = 0 + , h_C + , h_CO + , h_CON + + , h_matching_connection + , h_matching_proxy_connection + , h_matching_content_length + , h_matching_transfer_encoding + , h_matching_upgrade + + , h_connection + , h_content_length + , h_transfer_encoding + , h_upgrade + + , h_matching_transfer_encoding_chunked + , h_matching_connection_token_start + , h_matching_connection_keep_alive + , h_matching_connection_close + , h_matching_connection_upgrade + , h_matching_connection_token + + , h_transfer_encoding_chunked + , h_connection_keep_alive + , h_connection_close + , h_connection_upgrade + }; + +enum http_host_state + { + s_http_host_dead = 1 + , s_http_userinfo_start + , s_http_userinfo + , s_http_host_start + , s_http_host_v6_start + , s_http_host + , s_http_host_v6 + , s_http_host_v6_end + , s_http_host_v6_zone_start + , s_http_host_v6_zone + , s_http_host_port_start + , s_http_host_port +}; + +/* Macros for character classes; depends on strict-mode */ +#define CR '\r' +#define LF '\n' +#define LOWER(c) (unsigned char)(c | 0x20) +#define IS_ALPHA(c) (LOWER(c) >= 'a' && LOWER(c) <= 'z') +#define IS_NUM(c) ((c) >= '0' && (c) <= '9') +#define IS_ALPHANUM(c) (IS_ALPHA(c) || IS_NUM(c)) +#define IS_HEX(c) (IS_NUM(c) || (LOWER(c) >= 'a' && LOWER(c) <= 'f')) +#define IS_MARK(c) ((c) == '-' || (c) == '_' || (c) == '.' || \ + (c) == '!' || (c) == '~' || (c) == '*' || (c) == '\'' || (c) == '(' || \ + (c) == ')') +#define IS_USERINFO_CHAR(c) (IS_ALPHANUM(c) || IS_MARK(c) || (c) == '%' || \ + (c) == ';' || (c) == ':' || (c) == '&' || (c) == '=' || (c) == '+' || \ + (c) == '$' || (c) == ',') + +#define STRICT_TOKEN(c) (tokens[(unsigned char)c]) + +#if HTTP_PARSER_STRICT +#define TOKEN(c) (tokens[(unsigned char)c]) +#define IS_URL_CHAR(c) (BIT_AT(normal_url_char, (unsigned char)c)) +#define IS_HOST_CHAR(c) (IS_ALPHANUM(c) || (c) == '.' || (c) == '-') +#else +#define TOKEN(c) ((c == ' ') ? ' ' : tokens[(unsigned char)c]) +#define IS_URL_CHAR(c) \ + (BIT_AT(normal_url_char, (unsigned char)c) || ((c) & 0x80)) +#define IS_HOST_CHAR(c) \ + (IS_ALPHANUM(c) || (c) == '.' || (c) == '-' || (c) == '_') +#endif + +/** + * Verify that a char is a valid visible (printable) US-ASCII + * character or %x80-FF + **/ +#define IS_HEADER_CHAR(ch) \ + (ch == CR || ch == LF || ch == 9 || ((unsigned char)ch > 31 && ch != 127)) + +#define start_state (parser->type == HTTP_REQUEST ? s_start_req : s_start_res) + + +#if HTTP_PARSER_STRICT +# define STRICT_CHECK(cond) \ +do { \ + if (cond) { \ + SET_ERRNO(HPE_STRICT); \ + goto error; \ + } \ +} while (0) +# define NEW_MESSAGE() (http_should_keep_alive(parser) ? start_state : s_dead) +#else +# define STRICT_CHECK(cond) +# define NEW_MESSAGE() start_state +#endif + + +/* Map errno values to strings for human-readable output */ +#define HTTP_STRERROR_GEN(n, s) { "HPE_" #n, s }, +static struct { + const char *name; + const char *description; +} http_strerror_tab[] = { + HTTP_ERRNO_MAP(HTTP_STRERROR_GEN) +}; +#undef HTTP_STRERROR_GEN + +int http_message_needs_eof(const http_parser *parser); + +/* Our URL parser. + * + * This is designed to be shared by http_parser_execute() for URL validation, + * hence it has a state transition + byte-for-byte interface. In addition, it + * is meant to be embedded in http_parser_parse_url(), which does the dirty + * work of turning state transitions URL components for its API. + * + * This function should only be invoked with non-space characters. It is + * assumed that the caller cares about (and can detect) the transition between + * URL and non-URL states by looking for these. + */ +static enum state +parse_url_char(enum state s, const char ch) +{ + if (ch == ' ' || ch == '\r' || ch == '\n') { + return s_dead; + } + +#if HTTP_PARSER_STRICT + if (ch == '\t' || ch == '\f') { + return s_dead; + } +#endif + + switch (s) { + case s_req_spaces_before_url: + /* Proxied requests are followed by scheme of an absolute URI (alpha). + * All methods except CONNECT are followed by '/' or '*'. + */ + + if (ch == '/' || ch == '*') { + return s_req_path; + } + + if (IS_ALPHA(ch)) { + return s_req_schema; + } + + break; + + case s_req_schema: + if (IS_ALPHA(ch)) { + return s; + } + + if (ch == ':') { + return s_req_schema_slash; + } + + break; + + case s_req_schema_slash: + if (ch == '/') { + return s_req_schema_slash_slash; + } + + break; + + case s_req_schema_slash_slash: + if (ch == '/') { + return s_req_server_start; + } + + break; + + case s_req_server_with_at: + if (ch == '@') { + return s_dead; + } + + /* FALLTHROUGH */ + case s_req_server_start: + case s_req_server: + if (ch == '/') { + return s_req_path; + } + + if (ch == '?') { + return s_req_query_string_start; + } + + if (ch == '@') { + return s_req_server_with_at; + } + + if (IS_USERINFO_CHAR(ch) || ch == '[' || ch == ']') { + return s_req_server; + } + + break; + + case s_req_path: + if (IS_URL_CHAR(ch)) { + return s; + } + + switch (ch) { + case '?': + return s_req_query_string_start; + + case '#': + return s_req_fragment_start; + } + + break; + + case s_req_query_string_start: + case s_req_query_string: + if (IS_URL_CHAR(ch)) { + return s_req_query_string; + } + + switch (ch) { + case '?': + /* allow extra '?' in query string */ + return s_req_query_string; + + case '#': + return s_req_fragment_start; + } + + break; + + case s_req_fragment_start: + if (IS_URL_CHAR(ch)) { + return s_req_fragment; + } + + switch (ch) { + case '?': + return s_req_fragment; + + case '#': + return s; + } + + break; + + case s_req_fragment: + if (IS_URL_CHAR(ch)) { + return s; + } + + switch (ch) { + case '?': + case '#': + return s; + } + + break; + + default: + break; + } + + /* We should never fall out of the switch above unless there's an error */ + return s_dead; +} + +uint32_t http_parser_execute (http_parser *parser, + const http_parser_settings *settings, + const char *data, + uint32_t len) +{ + char c, ch; + int8_t unhex_val; + const char *p = data; + const char *header_field_mark = 0; + const char *header_value_mark = 0; + const char *url_mark = 0; + const char *body_mark = 0; + const char *status_mark = 0; + enum state p_state = (enum state) parser->state; + const unsigned int lenient = parser->lenient_http_headers; + + /* We're in an error state. Don't bother doing anything. */ + if (HTTP_PARSER_ERRNO(parser) != HPE_OK) { + return 0; + } + + if (len == 0) { + switch (CURRENT_STATE()) { + case s_body_identity_eof: + /* Use of CALLBACK_NOTIFY() here would erroneously return 1 byte read if + * we got paused. + */ + CALLBACK_NOTIFY_NOADVANCE(message_complete); + return 0; + + case s_dead: + case s_start_req_or_res: + case s_start_res: + case s_start_req: + return 0; + + default: + SET_ERRNO(HPE_INVALID_EOF_STATE); + return 1; + } + } + + + if (CURRENT_STATE() == s_header_field) + header_field_mark = data; + if (CURRENT_STATE() == s_header_value) + header_value_mark = data; + switch (CURRENT_STATE()) { + case s_req_path: + case s_req_schema: + case s_req_schema_slash: + case s_req_schema_slash_slash: + case s_req_server_start: + case s_req_server: + case s_req_server_with_at: + case s_req_query_string_start: + case s_req_query_string: + case s_req_fragment_start: + case s_req_fragment: + url_mark = data; + break; + case s_res_status: + status_mark = data; + break; + default: + break; + } + + for (p=data; p != data + len; p++) { + ch = *p; + + if (PARSING_HEADER(CURRENT_STATE())) + COUNT_HEADER_SIZE(1); + +reexecute: + switch (CURRENT_STATE()) { + + case s_dead: + /* this state is used after a 'Connection: close' message + * the parser will error out if it reads another message + */ + if (LIKELY(ch == CR || ch == LF)) + break; + + SET_ERRNO(HPE_CLOSED_CONNECTION); + goto error; + + case s_start_req_or_res: + { + if (ch == CR || ch == LF) + break; + parser->flags = 0; + parser->content_length = ULLONG_MAX; + + if (ch == 'H') { + UPDATE_STATE(s_res_or_resp_H); + + CALLBACK_NOTIFY(message_begin); + } else { + parser->type = HTTP_REQUEST; + UPDATE_STATE(s_start_req); + REEXECUTE(); + } + + break; + } + + case s_res_or_resp_H: + if (ch == 'T') { + parser->type = HTTP_RESPONSE; + UPDATE_STATE(s_res_HT); + } else { + if (UNLIKELY(ch != 'E')) { + SET_ERRNO(HPE_INVALID_CONSTANT); + goto error; + } + + parser->type = HTTP_REQUEST; + parser->method = HTTP_HEAD; + parser->index = 2; + UPDATE_STATE(s_req_method); + } + break; + + case s_start_res: + { + parser->flags = 0; + parser->content_length = ULLONG_MAX; + + switch (ch) { + case 'H': + UPDATE_STATE(s_res_H); + break; + + case CR: + case LF: + break; + + default: + SET_ERRNO(HPE_INVALID_CONSTANT); + goto error; + } + + CALLBACK_NOTIFY(message_begin); + break; + } + + case s_res_H: + STRICT_CHECK(ch != 'T'); + UPDATE_STATE(s_res_HT); + break; + + case s_res_HT: + STRICT_CHECK(ch != 'T'); + UPDATE_STATE(s_res_HTT); + break; + + case s_res_HTT: + STRICT_CHECK(ch != 'P'); + UPDATE_STATE(s_res_HTTP); + break; + + case s_res_HTTP: + STRICT_CHECK(ch != '/'); + UPDATE_STATE(s_res_first_http_major); + break; + + case s_res_first_http_major: + if (UNLIKELY(ch < '0' || ch > '9')) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_major = ch - '0'; + UPDATE_STATE(s_res_http_major); + break; + + /* major HTTP version or dot */ + case s_res_http_major: + { + if (ch == '.') { + UPDATE_STATE(s_res_first_http_minor); + break; + } + + if (!IS_NUM(ch)) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_major *= 10; + parser->http_major += ch - '0'; + + if (UNLIKELY(parser->http_major > 999)) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + break; + } + + /* first digit of minor HTTP version */ + case s_res_first_http_minor: + if (UNLIKELY(!IS_NUM(ch))) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_minor = ch - '0'; + UPDATE_STATE(s_res_http_minor); + break; + + /* minor HTTP version or end of request line */ + case s_res_http_minor: + { + if (ch == ' ') { + UPDATE_STATE(s_res_first_status_code); + break; + } + + if (UNLIKELY(!IS_NUM(ch))) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_minor *= 10; + parser->http_minor += ch - '0'; + + if (UNLIKELY(parser->http_minor > 999)) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + break; + } + + case s_res_first_status_code: + { + if (!IS_NUM(ch)) { + if (ch == ' ') { + break; + } + + SET_ERRNO(HPE_INVALID_STATUS); + goto error; + } + parser->status_code = ch - '0'; + UPDATE_STATE(s_res_status_code); + break; + } + + case s_res_status_code: + { + if (!IS_NUM(ch)) { + switch (ch) { + case ' ': + UPDATE_STATE(s_res_status_start); + break; + case CR: + UPDATE_STATE(s_res_line_almost_done); + break; + case LF: + UPDATE_STATE(s_header_field_start); + break; + default: + SET_ERRNO(HPE_INVALID_STATUS); + goto error; + } + break; + } + + parser->status_code *= 10; + parser->status_code += ch - '0'; + + if (UNLIKELY(parser->status_code > 999)) { + SET_ERRNO(HPE_INVALID_STATUS); + goto error; + } + + break; + } + + case s_res_status_start: + { + if (ch == CR) { + UPDATE_STATE(s_res_line_almost_done); + break; + } + + if (ch == LF) { + UPDATE_STATE(s_header_field_start); + break; + } + + MARK(status); + UPDATE_STATE(s_res_status); + parser->index = 0; + break; + } + + case s_res_status: + if (ch == CR) { + UPDATE_STATE(s_res_line_almost_done); + CALLBACK_DATA(status); + break; + } + + if (ch == LF) { + UPDATE_STATE(s_header_field_start); + CALLBACK_DATA(status); + break; + } + + break; + + case s_res_line_almost_done: + STRICT_CHECK(ch != LF); + UPDATE_STATE(s_header_field_start); + break; + + case s_start_req: + { + if (ch == CR || ch == LF) + break; + parser->flags = 0; + parser->content_length = ULLONG_MAX; + + if (UNLIKELY(!IS_ALPHA(ch))) { + SET_ERRNO(HPE_INVALID_METHOD); + goto error; + } + + parser->method = (enum http_method) 0; + parser->index = 1; + switch (ch) { + case 'A': parser->method = HTTP_ACL; break; + case 'B': parser->method = HTTP_BIND; break; + case 'C': parser->method = HTTP_CONNECT; /* or COPY, CHECKOUT */ break; + case 'D': parser->method = HTTP_DELETE; break; + case 'G': parser->method = HTTP_GET; break; + case 'H': parser->method = HTTP_HEAD; break; + case 'L': parser->method = HTTP_LOCK; /* or LINK */ break; + case 'M': parser->method = HTTP_MKCOL; /* or MOVE, MKACTIVITY, MERGE, M-SEARCH, MKCALENDAR */ break; + case 'N': parser->method = HTTP_NOTIFY; break; + case 'O': parser->method = HTTP_OPTIONS; break; + case 'P': parser->method = HTTP_POST; + /* or PROPFIND|PROPPATCH|PUT|PATCH|PURGE */ + break; + case 'R': parser->method = HTTP_REPORT; /* or REBIND */ break; + case 'S': parser->method = HTTP_SUBSCRIBE; /* or SEARCH */ break; + case 'T': parser->method = HTTP_TRACE; break; + case 'U': parser->method = HTTP_UNLOCK; /* or UNSUBSCRIBE, UNBIND, UNLINK */ break; + default: + SET_ERRNO(HPE_INVALID_METHOD); + goto error; + } + UPDATE_STATE(s_req_method); + + CALLBACK_NOTIFY(message_begin); + + break; + } + + case s_req_method: + { + const char *matcher; + if (UNLIKELY(ch == '\0')) { + SET_ERRNO(HPE_INVALID_METHOD); + goto error; + } + + matcher = method_strings[parser->method]; + if (ch == ' ' && matcher[parser->index] == '\0') { + UPDATE_STATE(s_req_spaces_before_url); + } else if (ch == matcher[parser->index]) { + ; /* nada */ + } else if (IS_ALPHA(ch)) { + + switch (parser->method << 16 | parser->index << 8 | ch) { +#define XX(meth, pos, ch, new_meth) \ + case (HTTP_##meth << 16 | pos << 8 | ch): \ + parser->method = HTTP_##new_meth; break; + + XX(POST, 1, 'U', PUT) + XX(POST, 1, 'A', PATCH) + XX(CONNECT, 1, 'H', CHECKOUT) + XX(CONNECT, 2, 'P', COPY) + XX(MKCOL, 1, 'O', MOVE) + XX(MKCOL, 1, 'E', MERGE) + XX(MKCOL, 2, 'A', MKACTIVITY) + XX(MKCOL, 3, 'A', MKCALENDAR) + XX(SUBSCRIBE, 1, 'E', SEARCH) + XX(REPORT, 2, 'B', REBIND) + XX(POST, 1, 'R', PROPFIND) + XX(PROPFIND, 4, 'P', PROPPATCH) + XX(PUT, 2, 'R', PURGE) + XX(LOCK, 1, 'I', LINK) + XX(UNLOCK, 2, 'S', UNSUBSCRIBE) + XX(UNLOCK, 2, 'B', UNBIND) + XX(UNLOCK, 3, 'I', UNLINK) +#undef XX + + default: + SET_ERRNO(HPE_INVALID_METHOD); + goto error; + } + } else if (ch == '-' && + parser->index == 1 && + parser->method == HTTP_MKCOL) { + parser->method = HTTP_MSEARCH; + } else { + SET_ERRNO(HPE_INVALID_METHOD); + goto error; + } + + ++parser->index; + break; + } + + case s_req_spaces_before_url: + { + if (ch == ' ') break; + + MARK(url); + if (parser->method == HTTP_CONNECT) { + UPDATE_STATE(s_req_server_start); + } + + UPDATE_STATE(parse_url_char(CURRENT_STATE(), ch)); + if (UNLIKELY(CURRENT_STATE() == s_dead)) { + SET_ERRNO(HPE_INVALID_URL); + goto error; + } + + break; + } + + case s_req_schema: + case s_req_schema_slash: + case s_req_schema_slash_slash: + case s_req_server_start: + { + switch (ch) { + /* No whitespace allowed here */ + case ' ': + case CR: + case LF: + SET_ERRNO(HPE_INVALID_URL); + goto error; + default: + UPDATE_STATE(parse_url_char(CURRENT_STATE(), ch)); + if (UNLIKELY(CURRENT_STATE() == s_dead)) { + SET_ERRNO(HPE_INVALID_URL); + goto error; + } + } + + break; + } + + case s_req_server: + case s_req_server_with_at: + case s_req_path: + case s_req_query_string_start: + case s_req_query_string: + case s_req_fragment_start: + case s_req_fragment: + { + switch (ch) { + case ' ': + UPDATE_STATE(s_req_http_start); + CALLBACK_DATA(url); + break; + case CR: + case LF: + parser->http_major = 0; + parser->http_minor = 9; + UPDATE_STATE((ch == CR) ? + s_req_line_almost_done : + s_header_field_start); + CALLBACK_DATA(url); + break; + default: + UPDATE_STATE(parse_url_char(CURRENT_STATE(), ch)); + if (UNLIKELY(CURRENT_STATE() == s_dead)) { + SET_ERRNO(HPE_INVALID_URL); + goto error; + } + } + break; + } + + case s_req_http_start: + switch (ch) { + case 'H': + UPDATE_STATE(s_req_http_H); + break; + case ' ': + break; + default: + SET_ERRNO(HPE_INVALID_CONSTANT); + goto error; + } + break; + + case s_req_http_H: + STRICT_CHECK(ch != 'T'); + UPDATE_STATE(s_req_http_HT); + break; + + case s_req_http_HT: + STRICT_CHECK(ch != 'T'); + UPDATE_STATE(s_req_http_HTT); + break; + + case s_req_http_HTT: + STRICT_CHECK(ch != 'P'); + UPDATE_STATE(s_req_http_HTTP); + break; + + case s_req_http_HTTP: + STRICT_CHECK(ch != '/'); + UPDATE_STATE(s_req_first_http_major); + break; + + /* first digit of major HTTP version */ + case s_req_first_http_major: + if (UNLIKELY(ch < '1' || ch > '9')) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_major = ch - '0'; + UPDATE_STATE(s_req_http_major); + break; + + /* major HTTP version or dot */ + case s_req_http_major: + { + if (ch == '.') { + UPDATE_STATE(s_req_first_http_minor); + break; + } + + if (UNLIKELY(!IS_NUM(ch))) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_major *= 10; + parser->http_major += ch - '0'; + + if (UNLIKELY(parser->http_major > 999)) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + break; + } + + /* first digit of minor HTTP version */ + case s_req_first_http_minor: + if (UNLIKELY(!IS_NUM(ch))) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_minor = ch - '0'; + UPDATE_STATE(s_req_http_minor); + break; + + /* minor HTTP version or end of request line */ + case s_req_http_minor: + { + if (ch == CR) { + UPDATE_STATE(s_req_line_almost_done); + break; + } + + if (ch == LF) { + UPDATE_STATE(s_header_field_start); + break; + } + + /* XXX allow spaces after digit? */ + + if (UNLIKELY(!IS_NUM(ch))) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_minor *= 10; + parser->http_minor += ch - '0'; + + if (UNLIKELY(parser->http_minor > 999)) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + break; + } + + /* end of request line */ + case s_req_line_almost_done: + { + if (UNLIKELY(ch != LF)) { + SET_ERRNO(HPE_LF_EXPECTED); + goto error; + } + + UPDATE_STATE(s_header_field_start); + break; + } + + case s_header_field_start: + { + if (ch == CR) { + UPDATE_STATE(s_headers_almost_done); + break; + } + + if (ch == LF) { + /* they might be just sending \n instead of \r\n so this would be + * the second \n to denote the end of headers*/ + UPDATE_STATE(s_headers_almost_done); + REEXECUTE(); + } + + c = TOKEN(ch); + + if (UNLIKELY(!c)) { + SET_ERRNO(HPE_INVALID_HEADER_TOKEN); + goto error; + } + + MARK(header_field); + + parser->index = 0; + UPDATE_STATE(s_header_field); + + switch (c) { + case 'c': + parser->header_state = h_C; + break; + + case 'p': + parser->header_state = h_matching_proxy_connection; + break; + + case 't': + parser->header_state = h_matching_transfer_encoding; + break; + + case 'u': + parser->header_state = h_matching_upgrade; + break; + + default: + parser->header_state = h_general; + break; + } + break; + } + + case s_header_field: + { + const char* start = p; + for (; p != data + len; p++) { + ch = *p; + c = TOKEN(ch); + + if (!c) + break; + + switch (parser->header_state) { + case h_general: + break; + + case h_C: + parser->index++; + parser->header_state = (c == 'o' ? h_CO : h_general); + break; + + case h_CO: + parser->index++; + parser->header_state = (c == 'n' ? h_CON : h_general); + break; + + case h_CON: + parser->index++; + switch (c) { + case 'n': + parser->header_state = h_matching_connection; + break; + case 't': + parser->header_state = h_matching_content_length; + break; + default: + parser->header_state = h_general; + break; + } + break; + + /* connection */ + + case h_matching_connection: + parser->index++; + if (parser->index > sizeof(CONNECTION)-1 + || c != CONNECTION[parser->index]) { + parser->header_state = h_general; + } else if (parser->index == sizeof(CONNECTION)-2) { + parser->header_state = h_connection; + } + break; + + /* proxy-connection */ + + case h_matching_proxy_connection: + parser->index++; + if (parser->index > sizeof(PROXY_CONNECTION)-1 + || c != PROXY_CONNECTION[parser->index]) { + parser->header_state = h_general; + } else if (parser->index == sizeof(PROXY_CONNECTION)-2) { + parser->header_state = h_connection; + } + break; + + /* content-length */ + + case h_matching_content_length: + parser->index++; + if (parser->index > sizeof(CONTENT_LENGTH)-1 + || c != CONTENT_LENGTH[parser->index]) { + parser->header_state = h_general; + } else if (parser->index == sizeof(CONTENT_LENGTH)-2) { + parser->header_state = h_content_length; + } + break; + + /* transfer-encoding */ + + case h_matching_transfer_encoding: + parser->index++; + if (parser->index > sizeof(TRANSFER_ENCODING)-1 + || c != TRANSFER_ENCODING[parser->index]) { + parser->header_state = h_general; + } else if (parser->index == sizeof(TRANSFER_ENCODING)-2) { + parser->header_state = h_transfer_encoding; + } + break; + + /* upgrade */ + + case h_matching_upgrade: + parser->index++; + if (parser->index > sizeof(UPGRADE)-1 + || c != UPGRADE[parser->index]) { + parser->header_state = h_general; + } else if (parser->index == sizeof(UPGRADE)-2) { + parser->header_state = h_upgrade; + } + break; + + case h_connection: + case h_content_length: + case h_transfer_encoding: + case h_upgrade: + if (ch != ' ') parser->header_state = h_general; + break; + + default: + assert(0 && "Unknown header_state"); + break; + } + } + + COUNT_HEADER_SIZE(p - start); + + if (p == data + len) { + --p; + break; + } + + if (ch == ':') { + UPDATE_STATE(s_header_value_discard_ws); + CALLBACK_DATA(header_field); + break; + } + + SET_ERRNO(HPE_INVALID_HEADER_TOKEN); + goto error; + } + + case s_header_value_discard_ws: + if (ch == ' ' || ch == '\t') break; + + if (ch == CR) { + UPDATE_STATE(s_header_value_discard_ws_almost_done); + break; + } + + if (ch == LF) { + UPDATE_STATE(s_header_value_discard_lws); + break; + } + + /* FALLTHROUGH */ + + case s_header_value_start: + { + MARK(header_value); + + UPDATE_STATE(s_header_value); + parser->index = 0; + + c = LOWER(ch); + + switch (parser->header_state) { + case h_upgrade: + parser->flags |= F_UPGRADE; + parser->header_state = h_general; + break; + + case h_transfer_encoding: + /* looking for 'Transfer-Encoding: chunked' */ + if ('c' == c) { + parser->header_state = h_matching_transfer_encoding_chunked; + } else { + parser->header_state = h_general; + } + break; + + case h_content_length: + if (UNLIKELY(!IS_NUM(ch))) { + SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); + goto error; + } + + if (parser->flags & F_CONTENTLENGTH) { + SET_ERRNO(HPE_UNEXPECTED_CONTENT_LENGTH); + goto error; + } + + parser->flags |= F_CONTENTLENGTH; + parser->content_length = ch - '0'; + break; + + case h_connection: + /* looking for 'Connection: keep-alive' */ + if (c == 'k') { + parser->header_state = h_matching_connection_keep_alive; + /* looking for 'Connection: close' */ + } else if (c == 'c') { + parser->header_state = h_matching_connection_close; + } else if (c == 'u') { + parser->header_state = h_matching_connection_upgrade; + } else { + parser->header_state = h_matching_connection_token; + } + break; + + /* Multi-value `Connection` header */ + case h_matching_connection_token_start: + break; + + default: + parser->header_state = h_general; + break; + } + break; + } + + case s_header_value: + { + const char* start = p; + enum header_states h_state = (enum header_states) parser->header_state; + for (; p != data + len; p++) { + ch = *p; + if (ch == CR) { + UPDATE_STATE(s_header_almost_done); + parser->header_state = h_state; + CALLBACK_DATA(header_value); + break; + } + + if (ch == LF) { + UPDATE_STATE(s_header_almost_done); + COUNT_HEADER_SIZE(p - start); + parser->header_state = h_state; + CALLBACK_DATA_NOADVANCE(header_value); + REEXECUTE(); + } + + if (!lenient && !IS_HEADER_CHAR(ch)) { + SET_ERRNO(HPE_INVALID_HEADER_TOKEN); + goto error; + } + + c = LOWER(ch); + + switch (h_state) { + case h_general: + { + const char* p_cr; + const char* p_lf; + uint32_t limit = data + len - p; + + limit = MIN(limit, HTTP_MAX_HEADER_SIZE); + + p_cr = (const char*) memchr(p, CR, limit); + p_lf = (const char*) memchr(p, LF, limit); + if (p_cr != NULL) { + if (p_lf != NULL && p_cr >= p_lf) + p = p_lf; + else + p = p_cr; + } else if (UNLIKELY(p_lf != NULL)) { + p = p_lf; + } else { + p = data + len; + } + --p; + + break; + } + + case h_connection: + case h_transfer_encoding: + assert(0 && "Shouldn't get here."); + break; + + case h_content_length: + { + uint64_t t; + + if (ch == ' ') break; + + if (UNLIKELY(!IS_NUM(ch))) { + SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); + parser->header_state = h_state; + goto error; + } + + t = parser->content_length; + t *= 10; + t += ch - '0'; + + /* Overflow? Test against a conservative limit for simplicity. */ + if (UNLIKELY((ULLONG_MAX - 10) / 10 < parser->content_length)) { + SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); + parser->header_state = h_state; + goto error; + } + + parser->content_length = t; + break; + } + + /* Transfer-Encoding: chunked */ + case h_matching_transfer_encoding_chunked: + parser->index++; + if (parser->index > sizeof(CHUNKED)-1 + || c != CHUNKED[parser->index]) { + h_state = h_general; + } else if (parser->index == sizeof(CHUNKED)-2) { + h_state = h_transfer_encoding_chunked; + } + break; + + case h_matching_connection_token_start: + /* looking for 'Connection: keep-alive' */ + if (c == 'k') { + h_state = h_matching_connection_keep_alive; + /* looking for 'Connection: close' */ + } else if (c == 'c') { + h_state = h_matching_connection_close; + } else if (c == 'u') { + h_state = h_matching_connection_upgrade; + } else if (STRICT_TOKEN(c)) { + h_state = h_matching_connection_token; + } else if (c == ' ' || c == '\t') { + /* Skip lws */ + } else { + h_state = h_general; + } + break; + + /* looking for 'Connection: keep-alive' */ + case h_matching_connection_keep_alive: + parser->index++; + if (parser->index > sizeof(KEEP_ALIVE)-1 + || c != KEEP_ALIVE[parser->index]) { + h_state = h_matching_connection_token; + } else if (parser->index == sizeof(KEEP_ALIVE)-2) { + h_state = h_connection_keep_alive; + } + break; + + /* looking for 'Connection: close' */ + case h_matching_connection_close: + parser->index++; + if (parser->index > sizeof(CLOSE)-1 || c != CLOSE[parser->index]) { + h_state = h_matching_connection_token; + } else if (parser->index == sizeof(CLOSE)-2) { + h_state = h_connection_close; + } + break; + + /* looking for 'Connection: upgrade' */ + case h_matching_connection_upgrade: + parser->index++; + if (parser->index > sizeof(UPGRADE) - 1 || + c != UPGRADE[parser->index]) { + h_state = h_matching_connection_token; + } else if (parser->index == sizeof(UPGRADE)-2) { + h_state = h_connection_upgrade; + } + break; + + case h_matching_connection_token: + if (ch == ',') { + h_state = h_matching_connection_token_start; + parser->index = 0; + } + break; + + case h_transfer_encoding_chunked: + if (ch != ' ') h_state = h_general; + break; + + case h_connection_keep_alive: + case h_connection_close: + case h_connection_upgrade: + if (ch == ',') { + if (h_state == h_connection_keep_alive) { + parser->flags |= F_CONNECTION_KEEP_ALIVE; + } else if (h_state == h_connection_close) { + parser->flags |= F_CONNECTION_CLOSE; + } else if (h_state == h_connection_upgrade) { + parser->flags |= F_CONNECTION_UPGRADE; + } + h_state = h_matching_connection_token_start; + parser->index = 0; + } else if (ch != ' ') { + h_state = h_matching_connection_token; + } + break; + + default: + UPDATE_STATE(s_header_value); + h_state = h_general; + break; + } + } + parser->header_state = h_state; + + COUNT_HEADER_SIZE(p - start); + + if (p == data + len) + --p; + break; + } + + case s_header_almost_done: + { + if (UNLIKELY(ch != LF)) { + SET_ERRNO(HPE_LF_EXPECTED); + goto error; + } + + UPDATE_STATE(s_header_value_lws); + break; + } + + case s_header_value_lws: + { + if (ch == ' ' || ch == '\t') { + UPDATE_STATE(s_header_value_start); + REEXECUTE(); + } + + /* finished the header */ + switch (parser->header_state) { + case h_connection_keep_alive: + parser->flags |= F_CONNECTION_KEEP_ALIVE; + break; + case h_connection_close: + parser->flags |= F_CONNECTION_CLOSE; + break; + case h_transfer_encoding_chunked: + parser->flags |= F_CHUNKED; + break; + case h_connection_upgrade: + parser->flags |= F_CONNECTION_UPGRADE; + break; + default: + break; + } + + UPDATE_STATE(s_header_field_start); + REEXECUTE(); + } + + case s_header_value_discard_ws_almost_done: + { + STRICT_CHECK(ch != LF); + UPDATE_STATE(s_header_value_discard_lws); + break; + } + + case s_header_value_discard_lws: + { + if (ch == ' ' || ch == '\t') { + UPDATE_STATE(s_header_value_discard_ws); + break; + } else { + switch (parser->header_state) { + case h_connection_keep_alive: + parser->flags |= F_CONNECTION_KEEP_ALIVE; + break; + case h_connection_close: + parser->flags |= F_CONNECTION_CLOSE; + break; + case h_connection_upgrade: + parser->flags |= F_CONNECTION_UPGRADE; + break; + case h_transfer_encoding_chunked: + parser->flags |= F_CHUNKED; + break; + default: + break; + } + + /* header value was empty */ + MARK(header_value); + UPDATE_STATE(s_header_field_start); + CALLBACK_DATA_NOADVANCE(header_value); + REEXECUTE(); + } + } + + case s_headers_almost_done: + { + STRICT_CHECK(ch != LF); + + if (parser->flags & F_TRAILING) { + /* End of a chunked request */ + UPDATE_STATE(s_message_done); + CALLBACK_NOTIFY_NOADVANCE(chunk_complete); + REEXECUTE(); + } + + /* Cannot use chunked encoding and a content-length header together + per the HTTP specification. */ + if ((parser->flags & F_CHUNKED) && + (parser->flags & F_CONTENTLENGTH)) { + SET_ERRNO(HPE_UNEXPECTED_CONTENT_LENGTH); + goto error; + } + + UPDATE_STATE(s_headers_done); + + /* Set this here so that on_headers_complete() callbacks can see it */ + parser->upgrade = + ((parser->flags & (F_UPGRADE | F_CONNECTION_UPGRADE)) == + (F_UPGRADE | F_CONNECTION_UPGRADE) || + parser->method == HTTP_CONNECT); + + /* Here we call the headers_complete callback. This is somewhat + * different than other callbacks because if the user returns 1, we + * will interpret that as saying that this message has no body. This + * is needed for the annoying case of recieving a response to a HEAD + * request. + * + * We'd like to use CALLBACK_NOTIFY_NOADVANCE() here but we cannot, so + * we have to simulate it by handling a change in errno below. + */ + if (settings->on_headers_complete) { + switch (settings->on_headers_complete(parser)) { + case 0: + break; + + case 2: + parser->upgrade = 1; + + case 1: + parser->flags |= F_SKIPBODY; + break; + + default: + SET_ERRNO(HPE_CB_headers_complete); + RETURN(p - data); /* Error */ + } + } + + if (HTTP_PARSER_ERRNO(parser) != HPE_OK) { + RETURN(p - data); + } + + REEXECUTE(); + } + + case s_headers_done: + { + int hasBody; + STRICT_CHECK(ch != LF); + + parser->nread = 0; + + hasBody = parser->flags & F_CHUNKED || + (parser->content_length > 0 && parser->content_length != ULLONG_MAX); + if (parser->upgrade && (parser->method == HTTP_CONNECT || + (parser->flags & F_SKIPBODY) || !hasBody)) { + /* Exit, the rest of the message is in a different protocol. */ + UPDATE_STATE(NEW_MESSAGE()); + CALLBACK_NOTIFY(message_complete); + RETURN((p - data) + 1); + } + + if (parser->flags & F_SKIPBODY) { + UPDATE_STATE(NEW_MESSAGE()); + CALLBACK_NOTIFY(message_complete); + } else if (parser->flags & F_CHUNKED) { + /* chunked encoding - ignore Content-Length header */ + UPDATE_STATE(s_chunk_size_start); + } else { + if (parser->content_length == 0) { + /* Content-Length header given but zero: Content-Length: 0\r\n */ + UPDATE_STATE(NEW_MESSAGE()); + CALLBACK_NOTIFY(message_complete); + } else if (parser->content_length != ULLONG_MAX) { + /* Content-Length header given and non-zero */ + UPDATE_STATE(s_body_identity); + } else { + if (!http_message_needs_eof(parser)) { + /* Assume content-length 0 - read the next */ + UPDATE_STATE(NEW_MESSAGE()); + CALLBACK_NOTIFY(message_complete); + } else { + /* Read body until EOF */ + UPDATE_STATE(s_body_identity_eof); + } + } + } + + break; + } + + case s_body_identity: + { + uint64_t to_read = MIN(parser->content_length, + (uint64_t) ((data + len) - p)); + + assert(parser->content_length != 0 + && parser->content_length != ULLONG_MAX); + + /* The difference between advancing content_length and p is because + * the latter will automaticaly advance on the next loop iteration. + * Further, if content_length ends up at 0, we want to see the last + * byte again for our message complete callback. + */ + MARK(body); + parser->content_length -= to_read; + p += to_read - 1; + + if (parser->content_length == 0) { + UPDATE_STATE(s_message_done); + + /* Mimic CALLBACK_DATA_NOADVANCE() but with one extra byte. + * + * The alternative to doing this is to wait for the next byte to + * trigger the data callback, just as in every other case. The + * problem with this is that this makes it difficult for the test + * harness to distinguish between complete-on-EOF and + * complete-on-length. It's not clear that this distinction is + * important for applications, but let's keep it for now. + */ + CALLBACK_DATA_(body, p - body_mark + 1, p - data); + REEXECUTE(); + } + + break; + } + + /* read until EOF */ + case s_body_identity_eof: + MARK(body); + p = data + len - 1; + + break; + + case s_message_done: + UPDATE_STATE(NEW_MESSAGE()); + CALLBACK_NOTIFY(message_complete); + if (parser->upgrade) { + /* Exit, the rest of the message is in a different protocol. */ + RETURN((p - data) + 1); + } + break; + + case s_chunk_size_start: + { + assert(parser->nread == 1); + assert(parser->flags & F_CHUNKED); + + unhex_val = unhex[(unsigned char)ch]; + if (UNLIKELY(unhex_val == -1)) { + SET_ERRNO(HPE_INVALID_CHUNK_SIZE); + goto error; + } + + parser->content_length = unhex_val; + UPDATE_STATE(s_chunk_size); + break; + } + + case s_chunk_size: + { + uint64_t t; + + assert(parser->flags & F_CHUNKED); + + if (ch == CR) { + UPDATE_STATE(s_chunk_size_almost_done); + break; + } + + unhex_val = unhex[(unsigned char)ch]; + + if (unhex_val == -1) { + if (ch == ';' || ch == ' ') { + UPDATE_STATE(s_chunk_parameters); + break; + } + + SET_ERRNO(HPE_INVALID_CHUNK_SIZE); + goto error; + } + + t = parser->content_length; + t *= 16; + t += unhex_val; + + /* Overflow? Test against a conservative limit for simplicity. */ + if (UNLIKELY((ULLONG_MAX - 16) / 16 < parser->content_length)) { + SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); + goto error; + } + + parser->content_length = t; + break; + } + + case s_chunk_parameters: + { + assert(parser->flags & F_CHUNKED); + /* just ignore this shit. TODO check for overflow */ + if (ch == CR) { + UPDATE_STATE(s_chunk_size_almost_done); + break; + } + break; + } + + case s_chunk_size_almost_done: + { + assert(parser->flags & F_CHUNKED); + STRICT_CHECK(ch != LF); + + parser->nread = 0; + + if (parser->content_length == 0) { + parser->flags |= F_TRAILING; + UPDATE_STATE(s_header_field_start); + } else { + UPDATE_STATE(s_chunk_data); + } + CALLBACK_NOTIFY(chunk_header); + break; + } + + case s_chunk_data: + { + uint64_t to_read = MIN(parser->content_length, + (uint64_t) ((data + len) - p)); + + assert(parser->flags & F_CHUNKED); + assert(parser->content_length != 0 + && parser->content_length != ULLONG_MAX); + + /* See the explanation in s_body_identity for why the content + * length and data pointers are managed this way. + */ + MARK(body); + parser->content_length -= to_read; + p += to_read - 1; + + if (parser->content_length == 0) { + UPDATE_STATE(s_chunk_data_almost_done); + } + + break; + } + + case s_chunk_data_almost_done: + assert(parser->flags & F_CHUNKED); + assert(parser->content_length == 0); + STRICT_CHECK(ch != CR); + UPDATE_STATE(s_chunk_data_done); + CALLBACK_DATA(body); + break; + + case s_chunk_data_done: + assert(parser->flags & F_CHUNKED); + STRICT_CHECK(ch != LF); + parser->nread = 0; + UPDATE_STATE(s_chunk_size_start); + CALLBACK_NOTIFY(chunk_complete); + break; + + default: + assert(0 && "unhandled state"); + SET_ERRNO(HPE_INVALID_INTERNAL_STATE); + goto error; + } + } + + /* Run callbacks for any marks that we have leftover after we ran our of + * bytes. There should be at most one of these set, so it's OK to invoke + * them in series (unset marks will not result in callbacks). + * + * We use the NOADVANCE() variety of callbacks here because 'p' has already + * overflowed 'data' and this allows us to correct for the off-by-one that + * we'd otherwise have (since CALLBACK_DATA() is meant to be run with a 'p' + * value that's in-bounds). + */ + + assert(((header_field_mark ? 1 : 0) + + (header_value_mark ? 1 : 0) + + (url_mark ? 1 : 0) + + (body_mark ? 1 : 0) + + (status_mark ? 1 : 0)) <= 1); + + CALLBACK_DATA_NOADVANCE(header_field); + CALLBACK_DATA_NOADVANCE(header_value); + CALLBACK_DATA_NOADVANCE(url); + CALLBACK_DATA_NOADVANCE(body); + CALLBACK_DATA_NOADVANCE(status); + + RETURN(len); + +error: + if (HTTP_PARSER_ERRNO(parser) == HPE_OK) { + SET_ERRNO(HPE_UNKNOWN); + } + + RETURN(p - data); +} + + +/* Does the parser need to see an EOF to find the end of the message? */ +int +http_message_needs_eof (const http_parser *parser) +{ + if (parser->type == HTTP_REQUEST) { + return 0; + } + + /* See RFC 2616 section 4.4 */ + if (parser->status_code / 100 == 1 || /* 1xx e.g. Continue */ + parser->status_code == 204 || /* No Content */ + parser->status_code == 304 || /* Not Modified */ + parser->flags & F_SKIPBODY) { /* response to a HEAD request */ + return 0; + } + + if ((parser->flags & F_CHUNKED) || parser->content_length != ULLONG_MAX) { + return 0; + } + + return 1; +} + + +int +http_should_keep_alive (const http_parser *parser) +{ + if (parser->http_major > 0 && parser->http_minor > 0) { + /* HTTP/1.1 */ + if (parser->flags & F_CONNECTION_CLOSE) { + return 0; + } + } else { + /* HTTP/1.0 or earlier */ + if (!(parser->flags & F_CONNECTION_KEEP_ALIVE)) { + return 0; + } + } + + return !http_message_needs_eof(parser); +} + + +const char * +http_method_str (enum http_method m) +{ + return ELEM_AT(method_strings, m, ""); +} + + +void +http_parser_init (http_parser *parser, enum http_parser_type t) +{ + void *data = parser->data; /* preserve application data */ + memset(parser, 0, sizeof(*parser)); + parser->data = data; + parser->type = t; + parser->state = (t == HTTP_REQUEST ? s_start_req : (t == HTTP_RESPONSE ? s_start_res : s_start_req_or_res)); + parser->http_errno = HPE_OK; +} + +void +http_parser_settings_init(http_parser_settings *settings) +{ + memset(settings, 0, sizeof(*settings)); +} + +const char * +http_errno_name(enum http_errno err) { + assert(((uint32_t) err) < ARRAY_SIZE(http_strerror_tab)); + return http_strerror_tab[err].name; +} + +const char * +http_errno_description(enum http_errno err) { + assert(((uint32_t) err) < ARRAY_SIZE(http_strerror_tab)); + return http_strerror_tab[err].description; +} + +static enum http_host_state +http_parse_host_char(enum http_host_state s, const char ch) { + switch(s) { + case s_http_userinfo: + case s_http_userinfo_start: + if (ch == '@') { + return s_http_host_start; + } + + if (IS_USERINFO_CHAR(ch)) { + return s_http_userinfo; + } + break; + + case s_http_host_start: + if (ch == '[') { + return s_http_host_v6_start; + } + + if (IS_HOST_CHAR(ch)) { + return s_http_host; + } + + break; + + case s_http_host: + if (IS_HOST_CHAR(ch)) { + return s_http_host; + } + + /* FALLTHROUGH */ + case s_http_host_v6_end: + if (ch == ':') { + return s_http_host_port_start; + } + + break; + + case s_http_host_v6: + if (ch == ']') { + return s_http_host_v6_end; + } + + /* FALLTHROUGH */ + case s_http_host_v6_start: + if (IS_HEX(ch) || ch == ':' || ch == '.') { + return s_http_host_v6; + } + + if (s == s_http_host_v6 && ch == '%') { + return s_http_host_v6_zone_start; + } + break; + + case s_http_host_v6_zone: + if (ch == ']') { + return s_http_host_v6_end; + } + + /* FALLTHROUGH */ + case s_http_host_v6_zone_start: + /* RFC 6874 Zone ID consists of 1*( unreserved / pct-encoded) */ + if (IS_ALPHANUM(ch) || ch == '%' || ch == '.' || ch == '-' || ch == '_' || + ch == '~') { + return s_http_host_v6_zone; + } + break; + + case s_http_host_port: + case s_http_host_port_start: + if (IS_NUM(ch)) { + return s_http_host_port; + } + + break; + + default: + break; + } + return s_http_host_dead; +} + +static int +http_parse_host(const char * buf, struct http_parser_url *u, int found_at) { + enum http_host_state s; + + const char *p; + uint32_t buflen = u->field_data[UF_HOST].off + u->field_data[UF_HOST].len; + + assert(u->field_set & (1 << UF_HOST)); + + u->field_data[UF_HOST].len = 0; + + s = found_at ? s_http_userinfo_start : s_http_host_start; + + for (p = buf + u->field_data[UF_HOST].off; p < buf + buflen; p++) { + enum http_host_state new_s = http_parse_host_char(s, *p); + + if (new_s == s_http_host_dead) { + return 1; + } + + switch(new_s) { + case s_http_host: + if (s != s_http_host) { + u->field_data[UF_HOST].off = p - buf; + } + u->field_data[UF_HOST].len++; + break; + + case s_http_host_v6: + if (s != s_http_host_v6) { + u->field_data[UF_HOST].off = p - buf; + } + u->field_data[UF_HOST].len++; + break; + + case s_http_host_v6_zone_start: + case s_http_host_v6_zone: + u->field_data[UF_HOST].len++; + break; + + case s_http_host_port: + if (s != s_http_host_port) { + u->field_data[UF_PORT].off = p - buf; + u->field_data[UF_PORT].len = 0; + u->field_set |= (1 << UF_PORT); + } + u->field_data[UF_PORT].len++; + break; + + case s_http_userinfo: + if (s != s_http_userinfo) { + u->field_data[UF_USERINFO].off = p - buf ; + u->field_data[UF_USERINFO].len = 0; + u->field_set |= (1 << UF_USERINFO); + } + u->field_data[UF_USERINFO].len++; + break; + + default: + break; + } + s = new_s; + } + + /* Make sure we don't end somewhere unexpected */ + switch (s) { + case s_http_host_start: + case s_http_host_v6_start: + case s_http_host_v6: + case s_http_host_v6_zone_start: + case s_http_host_v6_zone: + case s_http_host_port_start: + case s_http_userinfo: + case s_http_userinfo_start: + return 1; + default: + break; + } + + return 0; +} + +void +http_parser_url_init(struct http_parser_url *u) { + memset(u, 0, sizeof(*u)); +} + +int +http_parser_parse_url(const char *buf, uint32_t buflen, int is_connect, + struct http_parser_url *u) +{ + enum state s; + const char *p; + enum http_parser_url_fields uf, old_uf; + int found_at = 0; + + u->port = u->field_set = 0; + s = is_connect ? s_req_server_start : s_req_spaces_before_url; + old_uf = UF_MAX; + + for (p = buf; p < buf + buflen; p++) { + s = parse_url_char(s, *p); + + /* Figure out the next field that we're operating on */ + switch (s) { + case s_dead: + return 1; + + /* Skip delimeters */ + case s_req_schema_slash: + case s_req_schema_slash_slash: + case s_req_server_start: + case s_req_query_string_start: + case s_req_fragment_start: + continue; + + case s_req_schema: + uf = UF_SCHEMA; + break; + + case s_req_server_with_at: + found_at = 1; + + /* FALLTROUGH */ + case s_req_server: + uf = UF_HOST; + break; + + case s_req_path: + uf = UF_PATH; + break; + + case s_req_query_string: + uf = UF_QUERY; + break; + + case s_req_fragment: + uf = UF_FRAGMENT; + break; + + default: + assert(!"Unexpected state"); + return 1; + } + + /* Nothing's changed; soldier on */ + if (uf == old_uf) { + u->field_data[uf].len++; + continue; + } + + u->field_data[uf].off = p - buf; + u->field_data[uf].len = 1; + + u->field_set |= (1 << uf); + old_uf = uf; + } + + /* host must be present if there is a schema */ + /* parsing http:///toto will fail */ + if ((u->field_set & (1 << UF_SCHEMA)) && + (u->field_set & (1 << UF_HOST)) == 0) { + return 1; + } + + if (u->field_set & (1 << UF_HOST)) { + if (http_parse_host(buf, u, found_at) != 0) { + return 1; + } + } + + /* CONNECT requests can only contain "hostname:port" */ + if (is_connect && u->field_set != ((1 << UF_HOST)|(1 << UF_PORT))) { + return 1; + } + + if (u->field_set & (1 << UF_PORT)) { + /* Don't bother with endp; we've already validated the string */ + unsigned long v = strtoul(buf + u->field_data[UF_PORT].off, NULL, 10); + + /* Ports have a max value of 2^16 */ + if (v > 0xffff) { + return 1; + } + + u->port = (uint16_t) v; + } + + return 0; +} + +void +http_parser_pause(http_parser *parser, int paused) { + /* Users should only be pausing/unpausing a parser that is not in an error + * state. In non-debug builds, there's not much that we can do about this + * other than ignore it. + */ + if (HTTP_PARSER_ERRNO(parser) == HPE_OK || + HTTP_PARSER_ERRNO(parser) == HPE_PAUSED) { + SET_ERRNO((paused) ? HPE_PAUSED : HPE_OK); + } else { + assert(0 && "Attempting to pause parser in error state"); + } +} + +int +http_body_is_final(const struct http_parser *parser) { + return parser->state == s_message_done; +} + +unsigned long +http_parser_version(void) { + return HTTP_PARSER_VERSION_MAJOR * 0x10000 | + HTTP_PARSER_VERSION_MINOR * 0x00100 | + HTTP_PARSER_VERSION_PATCH * 0x00001; +} diff --git a/src/utility/http_parser.h b/src/utility/http_parser.h new file mode 100644 index 0000000..472bf54 --- /dev/null +++ b/src/utility/http_parser.h @@ -0,0 +1,432 @@ +/* Copyright Joyent, Inc. and other Node contributors. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ +#ifndef http_parser_h +#define http_parser_h + +#ifdef __cplusplus +extern "C" { +#endif + +/* Also update SONAME in the Makefile whenever you change these. */ +#define HTTP_PARSER_VERSION_MAJOR 2 +#define HTTP_PARSER_VERSION_MINOR 7 +#define HTTP_PARSER_VERSION_PATCH 1 + +#if defined(_WIN32) && !defined(__MINGW32__) && \ + (!defined(_MSC_VER) || _MSC_VER<1600) && !defined(__WINE__) +#include +#include +typedef __int8 int8_t; +typedef unsigned __int8 uint8_t; +typedef __int16 int16_t; +typedef unsigned __int16 uint16_t; +typedef __int32 int32_t; +typedef unsigned __int32 uint32_t; +typedef __int64 int64_t; +typedef unsigned __int64 uint64_t; +#else +#include +#endif + +/* Compile with -DHTTP_PARSER_STRICT=0 to make less checks, but run + * faster + */ +#ifndef HTTP_PARSER_STRICT +# define HTTP_PARSER_STRICT 1 +#endif + +/* Maximium header size allowed. If the macro is not defined + * before including this header then the default is used. To + * change the maximum header size, define the macro in the build + * environment (e.g. -DHTTP_MAX_HEADER_SIZE=). To remove + * the effective limit on the size of the header, define the macro + * to a very large number (e.g. -DHTTP_MAX_HEADER_SIZE=0x7fffffff) + */ +#ifndef HTTP_MAX_HEADER_SIZE +# define HTTP_MAX_HEADER_SIZE (80*1024) +#endif + +typedef struct http_parser http_parser; +typedef struct http_parser_settings http_parser_settings; + + +/* Callbacks should return non-zero to indicate an error. The parser will + * then halt execution. + * + * The one exception is on_headers_complete. In a HTTP_RESPONSE parser + * returning '1' from on_headers_complete will tell the parser that it + * should not expect a body. This is used when receiving a response to a + * HEAD request which may contain 'Content-Length' or 'Transfer-Encoding: + * chunked' headers that indicate the presence of a body. + * + * Returning `2` from on_headers_complete will tell parser that it should not + * expect neither a body nor any futher responses on this connection. This is + * useful for handling responses to a CONNECT request which may not contain + * `Upgrade` or `Connection: upgrade` headers. + * + * http_data_cb does not return data chunks. It will be called arbitrarily + * many times for each string. E.G. you might get 10 callbacks for "on_url" + * each providing just a few characters more data. + */ +typedef int (*http_data_cb) (http_parser*, const char *at, uint32_t length); +typedef int (*http_cb) (http_parser*); + + +/* Status Codes */ +#define HTTP_STATUS_MAP(XX) \ + XX(100, CONTINUE, Continue) \ + XX(101, SWITCHING_PROTOCOLS, Switching Protocols) \ + XX(102, PROCESSING, Processing) \ + XX(200, OK, OK) \ + XX(201, CREATED, Created) \ + XX(202, ACCEPTED, Accepted) \ + XX(203, NON_AUTHORITATIVE_INFORMATION, Non-Authoritative Information) \ + XX(204, NO_CONTENT, No Content) \ + XX(205, RESET_CONTENT, Reset Content) \ + XX(206, PARTIAL_CONTENT, Partial Content) \ + XX(207, MULTI_STATUS, Multi-Status) \ + XX(208, ALREADY_REPORTED, Already Reported) \ + XX(226, IM_USED, IM Used) \ + XX(300, MULTIPLE_CHOICES, Multiple Choices) \ + XX(301, MOVED_PERMANENTLY, Moved Permanently) \ + XX(302, FOUND, Found) \ + XX(303, SEE_OTHER, See Other) \ + XX(304, NOT_MODIFIED, Not Modified) \ + XX(305, USE_PROXY, Use Proxy) \ + XX(307, TEMPORARY_REDIRECT, Temporary Redirect) \ + XX(308, PERMANENT_REDIRECT, Permanent Redirect) \ + XX(400, BAD_REQUEST, Bad Request) \ + XX(401, UNAUTHORIZED, Unauthorized) \ + XX(402, PAYMENT_REQUIRED, Payment Required) \ + XX(403, FORBIDDEN, Forbidden) \ + XX(404, NOT_FOUND, Not Found) \ + XX(405, METHOD_NOT_ALLOWED, Method Not Allowed) \ + XX(406, NOT_ACCEPTABLE, Not Acceptable) \ + XX(407, PROXY_AUTHENTICATION_REQUIRED, Proxy Authentication Required) \ + XX(408, REQUEST_TIMEOUT, Request Timeout) \ + XX(409, CONFLICT, Conflict) \ + XX(410, GONE, Gone) \ + XX(411, LENGTH_REQUIRED, Length Required) \ + XX(412, PRECONDITION_FAILED, Precondition Failed) \ + XX(413, PAYLOAD_TOO_LARGE, Payload Too Large) \ + XX(414, URI_TOO_LONG, URI Too Long) \ + XX(415, UNSUPPORTED_MEDIA_TYPE, Unsupported Media Type) \ + XX(416, RANGE_NOT_SATISFIABLE, Range Not Satisfiable) \ + XX(417, EXPECTATION_FAILED, Expectation Failed) \ + XX(421, MISDIRECTED_REQUEST, Misdirected Request) \ + XX(422, UNPROCESSABLE_ENTITY, Unprocessable Entity) \ + XX(423, LOCKED, Locked) \ + XX(424, FAILED_DEPENDENCY, Failed Dependency) \ + XX(426, UPGRADE_REQUIRED, Upgrade Required) \ + XX(428, PRECONDITION_REQUIRED, Precondition Required) \ + XX(429, TOO_MANY_REQUESTS, Too Many Requests) \ + XX(431, REQUEST_HEADER_FIELDS_TOO_LARGE, Request Header Fields Too Large) \ + XX(451, UNAVAILABLE_FOR_LEGAL_REASONS, Unavailable For Legal Reasons) \ + XX(500, INTERNAL_SERVER_ERROR, Internal Server Error) \ + XX(501, NOT_IMPLEMENTED, Not Implemented) \ + XX(502, BAD_GATEWAY, Bad Gateway) \ + XX(503, SERVICE_UNAVAILABLE, Service Unavailable) \ + XX(504, GATEWAY_TIMEOUT, Gateway Timeout) \ + XX(505, HTTP_VERSION_NOT_SUPPORTED, HTTP Version Not Supported) \ + XX(506, VARIANT_ALSO_NEGOTIATES, Variant Also Negotiates) \ + XX(507, INSUFFICIENT_STORAGE, Insufficient Storage) \ + XX(508, LOOP_DETECTED, Loop Detected) \ + XX(510, NOT_EXTENDED, Not Extended) \ + XX(511, NETWORK_AUTHENTICATION_REQUIRED, Network Authentication Required) \ + +enum http_status + { +#define XX(num, name, string) HTTP_STATUS_##name = num, + HTTP_STATUS_MAP(XX) +#undef XX + }; + + +/* Request Methods */ +#define HTTP_METHOD_MAP(XX) \ + XX(0, DELETE, DELETE) \ + XX(1, GET, GET) \ + XX(2, HEAD, HEAD) \ + XX(3, POST, POST) \ + XX(4, PUT, PUT) \ + /* pathological */ \ + XX(5, CONNECT, CONNECT) \ + XX(6, OPTIONS, OPTIONS) \ + XX(7, TRACE, TRACE) \ + /* WebDAV */ \ + XX(8, COPY, COPY) \ + XX(9, LOCK, LOCK) \ + XX(10, MKCOL, MKCOL) \ + XX(11, MOVE, MOVE) \ + XX(12, PROPFIND, PROPFIND) \ + XX(13, PROPPATCH, PROPPATCH) \ + XX(14, SEARCH, SEARCH) \ + XX(15, UNLOCK, UNLOCK) \ + XX(16, BIND, BIND) \ + XX(17, REBIND, REBIND) \ + XX(18, UNBIND, UNBIND) \ + XX(19, ACL, ACL) \ + /* subversion */ \ + XX(20, REPORT, REPORT) \ + XX(21, MKACTIVITY, MKACTIVITY) \ + XX(22, CHECKOUT, CHECKOUT) \ + XX(23, MERGE, MERGE) \ + /* upnp */ \ + XX(24, MSEARCH, M-SEARCH) \ + XX(25, NOTIFY, NOTIFY) \ + XX(26, SUBSCRIBE, SUBSCRIBE) \ + XX(27, UNSUBSCRIBE, UNSUBSCRIBE) \ + /* RFC-5789 */ \ + XX(28, PATCH, PATCH) \ + XX(29, PURGE, PURGE) \ + /* CalDAV */ \ + XX(30, MKCALENDAR, MKCALENDAR) \ + /* RFC-2068, section 19.6.1.2 */ \ + XX(31, LINK, LINK) \ + XX(32, UNLINK, UNLINK) \ + +enum http_method + { +#define XX(num, name, string) HTTP_##name = num, + HTTP_METHOD_MAP(XX) +#undef XX + }; + + +enum http_parser_type { HTTP_REQUEST, HTTP_RESPONSE, HTTP_BOTH }; + + +/* Flag values for http_parser.flags field */ +enum flags + { F_CHUNKED = 1 << 0 + , F_CONNECTION_KEEP_ALIVE = 1 << 1 + , F_CONNECTION_CLOSE = 1 << 2 + , F_CONNECTION_UPGRADE = 1 << 3 + , F_TRAILING = 1 << 4 + , F_UPGRADE = 1 << 5 + , F_SKIPBODY = 1 << 6 + , F_CONTENTLENGTH = 1 << 7 + }; + + +/* Map for errno-related constants + * + * The provided argument should be a macro that takes 2 arguments. + */ +#define HTTP_ERRNO_MAP(XX) \ + /* No error */ \ + XX(OK, "success") \ + \ + /* Callback-related errors */ \ + XX(CB_message_begin, "the on_message_begin callback failed") \ + XX(CB_url, "the on_url callback failed") \ + XX(CB_header_field, "the on_header_field callback failed") \ + XX(CB_header_value, "the on_header_value callback failed") \ + XX(CB_headers_complete, "the on_headers_complete callback failed") \ + XX(CB_body, "the on_body callback failed") \ + XX(CB_message_complete, "the on_message_complete callback failed") \ + XX(CB_status, "the on_status callback failed") \ + XX(CB_chunk_header, "the on_chunk_header callback failed") \ + XX(CB_chunk_complete, "the on_chunk_complete callback failed") \ + \ + /* Parsing-related errors */ \ + XX(INVALID_EOF_STATE, "stream ended at an unexpected time") \ + XX(HEADER_OVERFLOW, \ + "too many header bytes seen; overflow detected") \ + XX(CLOSED_CONNECTION, \ + "data received after completed connection: close message") \ + XX(INVALID_VERSION, "invalid HTTP version") \ + XX(INVALID_STATUS, "invalid HTTP status code") \ + XX(INVALID_METHOD, "invalid HTTP method") \ + XX(INVALID_URL, "invalid URL") \ + XX(INVALID_HOST, "invalid host") \ + XX(INVALID_PORT, "invalid port") \ + XX(INVALID_PATH, "invalid path") \ + XX(INVALID_QUERY_STRING, "invalid query string") \ + XX(INVALID_FRAGMENT, "invalid fragment") \ + XX(LF_EXPECTED, "LF character expected") \ + XX(INVALID_HEADER_TOKEN, "invalid character in header") \ + XX(INVALID_CONTENT_LENGTH, \ + "invalid character in content-length header") \ + XX(UNEXPECTED_CONTENT_LENGTH, \ + "unexpected content-length header") \ + XX(INVALID_CHUNK_SIZE, \ + "invalid character in chunk size header") \ + XX(INVALID_CONSTANT, "invalid constant string") \ + XX(INVALID_INTERNAL_STATE, "encountered unexpected internal state")\ + XX(STRICT, "strict mode assertion failed") \ + XX(PAUSED, "parser is paused") \ + XX(UNKNOWN, "an unknown error occurred") + + +/* Define HPE_* values for each errno value above */ +#define HTTP_ERRNO_GEN(n, s) HPE_##n, +enum http_errno { + HTTP_ERRNO_MAP(HTTP_ERRNO_GEN) +}; +#undef HTTP_ERRNO_GEN + + +/* Get an http_errno value from an http_parser */ +#define HTTP_PARSER_ERRNO(p) ((enum http_errno) (p)->http_errno) + + +struct http_parser { + /** PRIVATE **/ + unsigned int type : 2; /* enum http_parser_type */ + unsigned int flags : 8; /* F_* values from 'flags' enum; semi-public */ + unsigned int state : 7; /* enum state from http_parser.c */ + unsigned int header_state : 7; /* enum header_state from http_parser.c */ + unsigned int index : 7; /* index into current matcher */ + unsigned int lenient_http_headers : 1; + + uint32_t nread; /* # bytes read in various scenarios */ + uint64_t content_length; /* # bytes in body (0 if no Content-Length header) */ + + /** READ-ONLY **/ + unsigned short http_major; + unsigned short http_minor; + unsigned int status_code : 16; /* responses only */ + unsigned int method : 8; /* requests only */ + unsigned int http_errno : 7; + + /* 1 = Upgrade header was present and the parser has exited because of that. + * 0 = No upgrade header present. + * Should be checked when http_parser_execute() returns in addition to + * error checking. + */ + unsigned int upgrade : 1; + + /** PUBLIC **/ + void *data; /* A pointer to get hook to the "connection" or "socket" object */ +}; + + +struct http_parser_settings { + http_cb on_message_begin; + http_data_cb on_url; + http_data_cb on_status; + http_data_cb on_header_field; + http_data_cb on_header_value; + http_cb on_headers_complete; + http_data_cb on_body; + http_cb on_message_complete; + /* When on_chunk_header is called, the current chunk length is stored + * in parser->content_length. + */ + http_cb on_chunk_header; + http_cb on_chunk_complete; +}; + + +enum http_parser_url_fields + { UF_SCHEMA = 0 + , UF_HOST = 1 + , UF_PORT = 2 + , UF_PATH = 3 + , UF_QUERY = 4 + , UF_FRAGMENT = 5 + , UF_USERINFO = 6 + , UF_MAX = 7 + }; + + +/* Result structure for http_parser_parse_url(). + * + * Callers should index into field_data[] with UF_* values iff field_set + * has the relevant (1 << UF_*) bit set. As a courtesy to clients (and + * because we probably have padding left over), we convert any port to + * a uint16_t. + */ +struct http_parser_url { + uint16_t field_set; /* Bitmask of (1 << UF_*) values */ + uint16_t port; /* Converted UF_PORT string */ + + struct { + uint16_t off; /* Offset into buffer in which field starts */ + uint16_t len; /* Length of run in buffer */ + } field_data[UF_MAX]; +}; + + +/* Returns the library version. Bits 16-23 contain the major version number, + * bits 8-15 the minor version number and bits 0-7 the patch level. + * Usage example: + * + * unsigned long version = http_parser_version(); + * unsigned major = (version >> 16) & 255; + * unsigned minor = (version >> 8) & 255; + * unsigned patch = version & 255; + * printf("http_parser v%u.%u.%u\n", major, minor, patch); + */ +unsigned long http_parser_version(void); + +void http_parser_init(http_parser *parser, enum http_parser_type type); + + +/* Initialize http_parser_settings members to 0 + */ +void http_parser_settings_init(http_parser_settings *settings); + + +/* Executes the parser. Returns number of parsed bytes. Sets + * `parser->http_errno` on error. */ +uint32_t http_parser_execute(http_parser *parser, + const http_parser_settings *settings, + const char *data, + uint32_t len); + + +/* If http_should_keep_alive() in the on_headers_complete or + * on_message_complete callback returns 0, then this should be + * the last message on the connection. + * If you are the server, respond with the "Connection: close" header. + * If you are the client, close the connection. + */ +int http_should_keep_alive(const http_parser *parser); + +/* Returns a string version of the HTTP method. */ +const char *http_method_str(enum http_method m); + +/* Return a string name of the given error */ +const char *http_errno_name(enum http_errno err); + +/* Return a string description of the given error */ +const char *http_errno_description(enum http_errno err); + +/* Initialize all http_parser_url members to 0 */ +void http_parser_url_init(struct http_parser_url *u); + +/* Parse a URL; return nonzero on failure */ +int http_parser_parse_url(const char *buf, uint32_t buflen, + int is_connect, + struct http_parser_url *u); + +/* Pause or un-pause the parser; a nonzero value pauses */ +void http_parser_pause(http_parser *parser, int paused); + +/* Checks if this is the final chunk of the body. */ +int http_body_is_final(const http_parser *parser); + +#ifdef __cplusplus +} +#endif +#endif \ No newline at end of file From 6159db90b9a4959f574a2adb4e32885ae99f6d21 Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Wed, 21 Feb 2024 15:30:34 +0100 Subject: [PATCH 110/125] Removed functionalities not related to URL parsing from the imported library --- src/utility/http_parser.c | 1888 ------------------------------------- src/utility/http_parser.h | 336 ------- 2 files changed, 2224 deletions(-) diff --git a/src/utility/http_parser.c b/src/utility/http_parser.c index b6bd43e..f1be647 100644 --- a/src/utility/http_parser.c +++ b/src/utility/http_parser.c @@ -29,210 +29,17 @@ #include #include -#ifndef ULLONG_MAX -# define ULLONG_MAX ((uint64_t) -1) /* 2^64-1 */ -#endif - -#ifndef MIN -# define MIN(a,b) ((a) < (b) ? (a) : (b)) -#endif - -#ifndef ARRAY_SIZE -# define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) -#endif - #ifndef BIT_AT # define BIT_AT(a, i) \ (!!((unsigned int) (a)[(unsigned int) (i) >> 3] & \ (1 << ((unsigned int) (i) & 7)))) #endif -#ifndef ELEM_AT -# define ELEM_AT(a, i, v) ((unsigned int) (i) < ARRAY_SIZE(a) ? (a)[(i)] : (v)) -#endif - #define SET_ERRNO(e) \ do { \ parser->http_errno = (e); \ } while(0) -#define CURRENT_STATE() p_state -#define UPDATE_STATE(V) p_state = (enum state) (V); -#define RETURN(V) \ -do { \ - parser->state = CURRENT_STATE(); \ - return (V); \ -} while (0); -#define REEXECUTE() \ - goto reexecute; \ - - -#ifdef __GNUC__ -# define LIKELY(X) __builtin_expect(!!(X), 1) -# define UNLIKELY(X) __builtin_expect(!!(X), 0) -#else -# define LIKELY(X) (X) -# define UNLIKELY(X) (X) -#endif - - -/* Run the notify callback FOR, returning ER if it fails */ -#define CALLBACK_NOTIFY_(FOR, ER) \ -do { \ - assert(HTTP_PARSER_ERRNO(parser) == HPE_OK); \ - \ - if (LIKELY(settings->on_##FOR)) { \ - parser->state = CURRENT_STATE(); \ - if (UNLIKELY(0 != settings->on_##FOR(parser))) { \ - SET_ERRNO(HPE_CB_##FOR); \ - } \ - UPDATE_STATE(parser->state); \ - \ - /* We either errored above or got paused; get out */ \ - if (UNLIKELY(HTTP_PARSER_ERRNO(parser) != HPE_OK)) { \ - return (ER); \ - } \ - } \ -} while (0) - -/* Run the notify callback FOR and consume the current byte */ -#define CALLBACK_NOTIFY(FOR) CALLBACK_NOTIFY_(FOR, p - data + 1) - -/* Run the notify callback FOR and don't consume the current byte */ -#define CALLBACK_NOTIFY_NOADVANCE(FOR) CALLBACK_NOTIFY_(FOR, p - data) - -/* Run data callback FOR with LEN bytes, returning ER if it fails */ -#define CALLBACK_DATA_(FOR, LEN, ER) \ -do { \ - assert(HTTP_PARSER_ERRNO(parser) == HPE_OK); \ - \ - if (FOR##_mark) { \ - if (LIKELY(settings->on_##FOR)) { \ - parser->state = CURRENT_STATE(); \ - if (UNLIKELY(0 != \ - settings->on_##FOR(parser, FOR##_mark, (LEN)))) { \ - SET_ERRNO(HPE_CB_##FOR); \ - } \ - UPDATE_STATE(parser->state); \ - \ - /* We either errored above or got paused; get out */ \ - if (UNLIKELY(HTTP_PARSER_ERRNO(parser) != HPE_OK)) { \ - return (ER); \ - } \ - } \ - FOR##_mark = NULL; \ - } \ -} while (0) - -/* Run the data callback FOR and consume the current byte */ -#define CALLBACK_DATA(FOR) \ - CALLBACK_DATA_(FOR, p - FOR##_mark, p - data + 1) - -/* Run the data callback FOR and don't consume the current byte */ -#define CALLBACK_DATA_NOADVANCE(FOR) \ - CALLBACK_DATA_(FOR, p - FOR##_mark, p - data) - -/* Set the mark FOR; non-destructive if mark is already set */ -#define MARK(FOR) \ -do { \ - if (!FOR##_mark) { \ - FOR##_mark = p; \ - } \ -} while (0) - -/* Don't allow the total size of the HTTP headers (including the status - * line) to exceed HTTP_MAX_HEADER_SIZE. This check is here to protect - * embedders against denial-of-service attacks where the attacker feeds - * us a never-ending header that the embedder keeps buffering. - * - * This check is arguably the responsibility of embedders but we're doing - * it on the embedder's behalf because most won't bother and this way we - * make the web a little safer. HTTP_MAX_HEADER_SIZE is still far bigger - * than any reasonable request or response so this should never affect - * day-to-day operation. - */ -#define COUNT_HEADER_SIZE(V) \ -do { \ - parser->nread += (V); \ - if (UNLIKELY(parser->nread > (HTTP_MAX_HEADER_SIZE))) { \ - SET_ERRNO(HPE_HEADER_OVERFLOW); \ - goto error; \ - } \ -} while (0) - - -#define PROXY_CONNECTION "proxy-connection" -#define CONNECTION "connection" -#define CONTENT_LENGTH "content-length" -#define TRANSFER_ENCODING "transfer-encoding" -#define UPGRADE "upgrade" -#define CHUNKED "chunked" -#define KEEP_ALIVE "keep-alive" -#define CLOSE "close" - - -static const char *method_strings[] = - { -#define XX(num, name, string) #string, - HTTP_METHOD_MAP(XX) -#undef XX - }; - - -/* Tokens as defined by rfc 2616. Also lowercases them. - * token = 1* - * separators = "(" | ")" | "<" | ">" | "@" - * | "," | ";" | ":" | "\" | <"> - * | "/" | "[" | "]" | "?" | "=" - * | "{" | "}" | SP | HT - */ -static const char tokens[256] = { -/* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */ - 0, 0, 0, 0, 0, 0, 0, 0, -/* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */ - 0, 0, 0, 0, 0, 0, 0, 0, -/* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */ - 0, 0, 0, 0, 0, 0, 0, 0, -/* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */ - 0, 0, 0, 0, 0, 0, 0, 0, -/* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */ - 0, '!', 0, '#', '$', '%', '&', '\'', -/* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */ - 0, 0, '*', '+', 0, '-', '.', 0, -/* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */ - '0', '1', '2', '3', '4', '5', '6', '7', -/* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */ - '8', '9', 0, 0, 0, 0, 0, 0, -/* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */ - 0, 'a', 'b', 'c', 'd', 'e', 'f', 'g', -/* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */ - 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', -/* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */ - 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', -/* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */ - 'x', 'y', 'z', 0, 0, 0, '^', '_', -/* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */ - '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', -/* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */ - 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', -/* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */ - 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', -/* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */ - 'x', 'y', 'z', 0, '|', 0, '~', 0 }; - - -static const int8_t unhex[256] = - {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 - ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 - ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 - , 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-1,-1,-1,-1,-1,-1 - ,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1 - ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 - ,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1 - ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 - }; - - #if HTTP_PARSER_STRICT # define T(v) 0 #else @@ -279,26 +86,8 @@ static const uint8_t normal_url_char[32] = { enum state { s_dead = 1 /* important that this is > 0 */ - , s_start_req_or_res - , s_res_or_resp_H - , s_start_res - , s_res_H - , s_res_HT - , s_res_HTT - , s_res_HTTP - , s_res_first_http_major - , s_res_http_major - , s_res_first_http_minor - , s_res_http_minor - , s_res_first_status_code - , s_res_status_code - , s_res_status_start - , s_res_status - , s_res_line_almost_done - , s_start_req - , s_req_method , s_req_spaces_before_url , s_req_schema , s_req_schema_slash @@ -311,83 +100,7 @@ enum state , s_req_query_string , s_req_fragment_start , s_req_fragment - , s_req_http_start - , s_req_http_H - , s_req_http_HT - , s_req_http_HTT - , s_req_http_HTTP - , s_req_first_http_major - , s_req_http_major - , s_req_first_http_minor - , s_req_http_minor - , s_req_line_almost_done - - , s_header_field_start - , s_header_field - , s_header_value_discard_ws - , s_header_value_discard_ws_almost_done - , s_header_value_discard_lws - , s_header_value_start - , s_header_value - , s_header_value_lws - - , s_header_almost_done - - , s_chunk_size_start - , s_chunk_size - , s_chunk_parameters - , s_chunk_size_almost_done - - , s_headers_almost_done , s_headers_done - - /* Important: 's_headers_done' must be the last 'header' state. All - * states beyond this must be 'body' states. It is used for overflow - * checking. See the PARSING_HEADER() macro. - */ - - , s_chunk_data - , s_chunk_data_almost_done - , s_chunk_data_done - - , s_body_identity - , s_body_identity_eof - - , s_message_done - }; - - -#define PARSING_HEADER(state) (state <= s_headers_done) - - -enum header_states - { h_general = 0 - , h_C - , h_CO - , h_CON - - , h_matching_connection - , h_matching_proxy_connection - , h_matching_content_length - , h_matching_transfer_encoding - , h_matching_upgrade - - , h_connection - , h_content_length - , h_transfer_encoding - , h_upgrade - - , h_matching_transfer_encoding_chunked - , h_matching_connection_token_start - , h_matching_connection_keep_alive - , h_matching_connection_close - , h_matching_connection_upgrade - , h_matching_connection_token - - , h_transfer_encoding_chunked - , h_connection_keep_alive - , h_connection_close - , h_connection_upgrade }; enum http_host_state @@ -407,8 +120,6 @@ enum http_host_state }; /* Macros for character classes; depends on strict-mode */ -#define CR '\r' -#define LF '\n' #define LOWER(c) (unsigned char)(c | 0x20) #define IS_ALPHA(c) (LOWER(c) >= 'a' && LOWER(c) <= 'z') #define IS_NUM(c) ((c) >= '0' && (c) <= '9') @@ -421,57 +132,16 @@ enum http_host_state (c) == ';' || (c) == ':' || (c) == '&' || (c) == '=' || (c) == '+' || \ (c) == '$' || (c) == ',') -#define STRICT_TOKEN(c) (tokens[(unsigned char)c]) - #if HTTP_PARSER_STRICT -#define TOKEN(c) (tokens[(unsigned char)c]) #define IS_URL_CHAR(c) (BIT_AT(normal_url_char, (unsigned char)c)) #define IS_HOST_CHAR(c) (IS_ALPHANUM(c) || (c) == '.' || (c) == '-') #else -#define TOKEN(c) ((c == ' ') ? ' ' : tokens[(unsigned char)c]) #define IS_URL_CHAR(c) \ (BIT_AT(normal_url_char, (unsigned char)c) || ((c) & 0x80)) #define IS_HOST_CHAR(c) \ (IS_ALPHANUM(c) || (c) == '.' || (c) == '-' || (c) == '_') #endif -/** - * Verify that a char is a valid visible (printable) US-ASCII - * character or %x80-FF - **/ -#define IS_HEADER_CHAR(ch) \ - (ch == CR || ch == LF || ch == 9 || ((unsigned char)ch > 31 && ch != 127)) - -#define start_state (parser->type == HTTP_REQUEST ? s_start_req : s_start_res) - - -#if HTTP_PARSER_STRICT -# define STRICT_CHECK(cond) \ -do { \ - if (cond) { \ - SET_ERRNO(HPE_STRICT); \ - goto error; \ - } \ -} while (0) -# define NEW_MESSAGE() (http_should_keep_alive(parser) ? start_state : s_dead) -#else -# define STRICT_CHECK(cond) -# define NEW_MESSAGE() start_state -#endif - - -/* Map errno values to strings for human-readable output */ -#define HTTP_STRERROR_GEN(n, s) { "HPE_" #n, s }, -static struct { - const char *name; - const char *description; -} http_strerror_tab[] = { - HTTP_ERRNO_MAP(HTTP_STRERROR_GEN) -}; -#undef HTTP_STRERROR_GEN - -int http_message_needs_eof(const http_parser *parser); - /* Our URL parser. * * This is designed to be shared by http_parser_execute() for URL validation, @@ -631,1545 +301,6 @@ parse_url_char(enum state s, const char ch) return s_dead; } -uint32_t http_parser_execute (http_parser *parser, - const http_parser_settings *settings, - const char *data, - uint32_t len) -{ - char c, ch; - int8_t unhex_val; - const char *p = data; - const char *header_field_mark = 0; - const char *header_value_mark = 0; - const char *url_mark = 0; - const char *body_mark = 0; - const char *status_mark = 0; - enum state p_state = (enum state) parser->state; - const unsigned int lenient = parser->lenient_http_headers; - - /* We're in an error state. Don't bother doing anything. */ - if (HTTP_PARSER_ERRNO(parser) != HPE_OK) { - return 0; - } - - if (len == 0) { - switch (CURRENT_STATE()) { - case s_body_identity_eof: - /* Use of CALLBACK_NOTIFY() here would erroneously return 1 byte read if - * we got paused. - */ - CALLBACK_NOTIFY_NOADVANCE(message_complete); - return 0; - - case s_dead: - case s_start_req_or_res: - case s_start_res: - case s_start_req: - return 0; - - default: - SET_ERRNO(HPE_INVALID_EOF_STATE); - return 1; - } - } - - - if (CURRENT_STATE() == s_header_field) - header_field_mark = data; - if (CURRENT_STATE() == s_header_value) - header_value_mark = data; - switch (CURRENT_STATE()) { - case s_req_path: - case s_req_schema: - case s_req_schema_slash: - case s_req_schema_slash_slash: - case s_req_server_start: - case s_req_server: - case s_req_server_with_at: - case s_req_query_string_start: - case s_req_query_string: - case s_req_fragment_start: - case s_req_fragment: - url_mark = data; - break; - case s_res_status: - status_mark = data; - break; - default: - break; - } - - for (p=data; p != data + len; p++) { - ch = *p; - - if (PARSING_HEADER(CURRENT_STATE())) - COUNT_HEADER_SIZE(1); - -reexecute: - switch (CURRENT_STATE()) { - - case s_dead: - /* this state is used after a 'Connection: close' message - * the parser will error out if it reads another message - */ - if (LIKELY(ch == CR || ch == LF)) - break; - - SET_ERRNO(HPE_CLOSED_CONNECTION); - goto error; - - case s_start_req_or_res: - { - if (ch == CR || ch == LF) - break; - parser->flags = 0; - parser->content_length = ULLONG_MAX; - - if (ch == 'H') { - UPDATE_STATE(s_res_or_resp_H); - - CALLBACK_NOTIFY(message_begin); - } else { - parser->type = HTTP_REQUEST; - UPDATE_STATE(s_start_req); - REEXECUTE(); - } - - break; - } - - case s_res_or_resp_H: - if (ch == 'T') { - parser->type = HTTP_RESPONSE; - UPDATE_STATE(s_res_HT); - } else { - if (UNLIKELY(ch != 'E')) { - SET_ERRNO(HPE_INVALID_CONSTANT); - goto error; - } - - parser->type = HTTP_REQUEST; - parser->method = HTTP_HEAD; - parser->index = 2; - UPDATE_STATE(s_req_method); - } - break; - - case s_start_res: - { - parser->flags = 0; - parser->content_length = ULLONG_MAX; - - switch (ch) { - case 'H': - UPDATE_STATE(s_res_H); - break; - - case CR: - case LF: - break; - - default: - SET_ERRNO(HPE_INVALID_CONSTANT); - goto error; - } - - CALLBACK_NOTIFY(message_begin); - break; - } - - case s_res_H: - STRICT_CHECK(ch != 'T'); - UPDATE_STATE(s_res_HT); - break; - - case s_res_HT: - STRICT_CHECK(ch != 'T'); - UPDATE_STATE(s_res_HTT); - break; - - case s_res_HTT: - STRICT_CHECK(ch != 'P'); - UPDATE_STATE(s_res_HTTP); - break; - - case s_res_HTTP: - STRICT_CHECK(ch != '/'); - UPDATE_STATE(s_res_first_http_major); - break; - - case s_res_first_http_major: - if (UNLIKELY(ch < '0' || ch > '9')) { - SET_ERRNO(HPE_INVALID_VERSION); - goto error; - } - - parser->http_major = ch - '0'; - UPDATE_STATE(s_res_http_major); - break; - - /* major HTTP version or dot */ - case s_res_http_major: - { - if (ch == '.') { - UPDATE_STATE(s_res_first_http_minor); - break; - } - - if (!IS_NUM(ch)) { - SET_ERRNO(HPE_INVALID_VERSION); - goto error; - } - - parser->http_major *= 10; - parser->http_major += ch - '0'; - - if (UNLIKELY(parser->http_major > 999)) { - SET_ERRNO(HPE_INVALID_VERSION); - goto error; - } - - break; - } - - /* first digit of minor HTTP version */ - case s_res_first_http_minor: - if (UNLIKELY(!IS_NUM(ch))) { - SET_ERRNO(HPE_INVALID_VERSION); - goto error; - } - - parser->http_minor = ch - '0'; - UPDATE_STATE(s_res_http_minor); - break; - - /* minor HTTP version or end of request line */ - case s_res_http_minor: - { - if (ch == ' ') { - UPDATE_STATE(s_res_first_status_code); - break; - } - - if (UNLIKELY(!IS_NUM(ch))) { - SET_ERRNO(HPE_INVALID_VERSION); - goto error; - } - - parser->http_minor *= 10; - parser->http_minor += ch - '0'; - - if (UNLIKELY(parser->http_minor > 999)) { - SET_ERRNO(HPE_INVALID_VERSION); - goto error; - } - - break; - } - - case s_res_first_status_code: - { - if (!IS_NUM(ch)) { - if (ch == ' ') { - break; - } - - SET_ERRNO(HPE_INVALID_STATUS); - goto error; - } - parser->status_code = ch - '0'; - UPDATE_STATE(s_res_status_code); - break; - } - - case s_res_status_code: - { - if (!IS_NUM(ch)) { - switch (ch) { - case ' ': - UPDATE_STATE(s_res_status_start); - break; - case CR: - UPDATE_STATE(s_res_line_almost_done); - break; - case LF: - UPDATE_STATE(s_header_field_start); - break; - default: - SET_ERRNO(HPE_INVALID_STATUS); - goto error; - } - break; - } - - parser->status_code *= 10; - parser->status_code += ch - '0'; - - if (UNLIKELY(parser->status_code > 999)) { - SET_ERRNO(HPE_INVALID_STATUS); - goto error; - } - - break; - } - - case s_res_status_start: - { - if (ch == CR) { - UPDATE_STATE(s_res_line_almost_done); - break; - } - - if (ch == LF) { - UPDATE_STATE(s_header_field_start); - break; - } - - MARK(status); - UPDATE_STATE(s_res_status); - parser->index = 0; - break; - } - - case s_res_status: - if (ch == CR) { - UPDATE_STATE(s_res_line_almost_done); - CALLBACK_DATA(status); - break; - } - - if (ch == LF) { - UPDATE_STATE(s_header_field_start); - CALLBACK_DATA(status); - break; - } - - break; - - case s_res_line_almost_done: - STRICT_CHECK(ch != LF); - UPDATE_STATE(s_header_field_start); - break; - - case s_start_req: - { - if (ch == CR || ch == LF) - break; - parser->flags = 0; - parser->content_length = ULLONG_MAX; - - if (UNLIKELY(!IS_ALPHA(ch))) { - SET_ERRNO(HPE_INVALID_METHOD); - goto error; - } - - parser->method = (enum http_method) 0; - parser->index = 1; - switch (ch) { - case 'A': parser->method = HTTP_ACL; break; - case 'B': parser->method = HTTP_BIND; break; - case 'C': parser->method = HTTP_CONNECT; /* or COPY, CHECKOUT */ break; - case 'D': parser->method = HTTP_DELETE; break; - case 'G': parser->method = HTTP_GET; break; - case 'H': parser->method = HTTP_HEAD; break; - case 'L': parser->method = HTTP_LOCK; /* or LINK */ break; - case 'M': parser->method = HTTP_MKCOL; /* or MOVE, MKACTIVITY, MERGE, M-SEARCH, MKCALENDAR */ break; - case 'N': parser->method = HTTP_NOTIFY; break; - case 'O': parser->method = HTTP_OPTIONS; break; - case 'P': parser->method = HTTP_POST; - /* or PROPFIND|PROPPATCH|PUT|PATCH|PURGE */ - break; - case 'R': parser->method = HTTP_REPORT; /* or REBIND */ break; - case 'S': parser->method = HTTP_SUBSCRIBE; /* or SEARCH */ break; - case 'T': parser->method = HTTP_TRACE; break; - case 'U': parser->method = HTTP_UNLOCK; /* or UNSUBSCRIBE, UNBIND, UNLINK */ break; - default: - SET_ERRNO(HPE_INVALID_METHOD); - goto error; - } - UPDATE_STATE(s_req_method); - - CALLBACK_NOTIFY(message_begin); - - break; - } - - case s_req_method: - { - const char *matcher; - if (UNLIKELY(ch == '\0')) { - SET_ERRNO(HPE_INVALID_METHOD); - goto error; - } - - matcher = method_strings[parser->method]; - if (ch == ' ' && matcher[parser->index] == '\0') { - UPDATE_STATE(s_req_spaces_before_url); - } else if (ch == matcher[parser->index]) { - ; /* nada */ - } else if (IS_ALPHA(ch)) { - - switch (parser->method << 16 | parser->index << 8 | ch) { -#define XX(meth, pos, ch, new_meth) \ - case (HTTP_##meth << 16 | pos << 8 | ch): \ - parser->method = HTTP_##new_meth; break; - - XX(POST, 1, 'U', PUT) - XX(POST, 1, 'A', PATCH) - XX(CONNECT, 1, 'H', CHECKOUT) - XX(CONNECT, 2, 'P', COPY) - XX(MKCOL, 1, 'O', MOVE) - XX(MKCOL, 1, 'E', MERGE) - XX(MKCOL, 2, 'A', MKACTIVITY) - XX(MKCOL, 3, 'A', MKCALENDAR) - XX(SUBSCRIBE, 1, 'E', SEARCH) - XX(REPORT, 2, 'B', REBIND) - XX(POST, 1, 'R', PROPFIND) - XX(PROPFIND, 4, 'P', PROPPATCH) - XX(PUT, 2, 'R', PURGE) - XX(LOCK, 1, 'I', LINK) - XX(UNLOCK, 2, 'S', UNSUBSCRIBE) - XX(UNLOCK, 2, 'B', UNBIND) - XX(UNLOCK, 3, 'I', UNLINK) -#undef XX - - default: - SET_ERRNO(HPE_INVALID_METHOD); - goto error; - } - } else if (ch == '-' && - parser->index == 1 && - parser->method == HTTP_MKCOL) { - parser->method = HTTP_MSEARCH; - } else { - SET_ERRNO(HPE_INVALID_METHOD); - goto error; - } - - ++parser->index; - break; - } - - case s_req_spaces_before_url: - { - if (ch == ' ') break; - - MARK(url); - if (parser->method == HTTP_CONNECT) { - UPDATE_STATE(s_req_server_start); - } - - UPDATE_STATE(parse_url_char(CURRENT_STATE(), ch)); - if (UNLIKELY(CURRENT_STATE() == s_dead)) { - SET_ERRNO(HPE_INVALID_URL); - goto error; - } - - break; - } - - case s_req_schema: - case s_req_schema_slash: - case s_req_schema_slash_slash: - case s_req_server_start: - { - switch (ch) { - /* No whitespace allowed here */ - case ' ': - case CR: - case LF: - SET_ERRNO(HPE_INVALID_URL); - goto error; - default: - UPDATE_STATE(parse_url_char(CURRENT_STATE(), ch)); - if (UNLIKELY(CURRENT_STATE() == s_dead)) { - SET_ERRNO(HPE_INVALID_URL); - goto error; - } - } - - break; - } - - case s_req_server: - case s_req_server_with_at: - case s_req_path: - case s_req_query_string_start: - case s_req_query_string: - case s_req_fragment_start: - case s_req_fragment: - { - switch (ch) { - case ' ': - UPDATE_STATE(s_req_http_start); - CALLBACK_DATA(url); - break; - case CR: - case LF: - parser->http_major = 0; - parser->http_minor = 9; - UPDATE_STATE((ch == CR) ? - s_req_line_almost_done : - s_header_field_start); - CALLBACK_DATA(url); - break; - default: - UPDATE_STATE(parse_url_char(CURRENT_STATE(), ch)); - if (UNLIKELY(CURRENT_STATE() == s_dead)) { - SET_ERRNO(HPE_INVALID_URL); - goto error; - } - } - break; - } - - case s_req_http_start: - switch (ch) { - case 'H': - UPDATE_STATE(s_req_http_H); - break; - case ' ': - break; - default: - SET_ERRNO(HPE_INVALID_CONSTANT); - goto error; - } - break; - - case s_req_http_H: - STRICT_CHECK(ch != 'T'); - UPDATE_STATE(s_req_http_HT); - break; - - case s_req_http_HT: - STRICT_CHECK(ch != 'T'); - UPDATE_STATE(s_req_http_HTT); - break; - - case s_req_http_HTT: - STRICT_CHECK(ch != 'P'); - UPDATE_STATE(s_req_http_HTTP); - break; - - case s_req_http_HTTP: - STRICT_CHECK(ch != '/'); - UPDATE_STATE(s_req_first_http_major); - break; - - /* first digit of major HTTP version */ - case s_req_first_http_major: - if (UNLIKELY(ch < '1' || ch > '9')) { - SET_ERRNO(HPE_INVALID_VERSION); - goto error; - } - - parser->http_major = ch - '0'; - UPDATE_STATE(s_req_http_major); - break; - - /* major HTTP version or dot */ - case s_req_http_major: - { - if (ch == '.') { - UPDATE_STATE(s_req_first_http_minor); - break; - } - - if (UNLIKELY(!IS_NUM(ch))) { - SET_ERRNO(HPE_INVALID_VERSION); - goto error; - } - - parser->http_major *= 10; - parser->http_major += ch - '0'; - - if (UNLIKELY(parser->http_major > 999)) { - SET_ERRNO(HPE_INVALID_VERSION); - goto error; - } - - break; - } - - /* first digit of minor HTTP version */ - case s_req_first_http_minor: - if (UNLIKELY(!IS_NUM(ch))) { - SET_ERRNO(HPE_INVALID_VERSION); - goto error; - } - - parser->http_minor = ch - '0'; - UPDATE_STATE(s_req_http_minor); - break; - - /* minor HTTP version or end of request line */ - case s_req_http_minor: - { - if (ch == CR) { - UPDATE_STATE(s_req_line_almost_done); - break; - } - - if (ch == LF) { - UPDATE_STATE(s_header_field_start); - break; - } - - /* XXX allow spaces after digit? */ - - if (UNLIKELY(!IS_NUM(ch))) { - SET_ERRNO(HPE_INVALID_VERSION); - goto error; - } - - parser->http_minor *= 10; - parser->http_minor += ch - '0'; - - if (UNLIKELY(parser->http_minor > 999)) { - SET_ERRNO(HPE_INVALID_VERSION); - goto error; - } - - break; - } - - /* end of request line */ - case s_req_line_almost_done: - { - if (UNLIKELY(ch != LF)) { - SET_ERRNO(HPE_LF_EXPECTED); - goto error; - } - - UPDATE_STATE(s_header_field_start); - break; - } - - case s_header_field_start: - { - if (ch == CR) { - UPDATE_STATE(s_headers_almost_done); - break; - } - - if (ch == LF) { - /* they might be just sending \n instead of \r\n so this would be - * the second \n to denote the end of headers*/ - UPDATE_STATE(s_headers_almost_done); - REEXECUTE(); - } - - c = TOKEN(ch); - - if (UNLIKELY(!c)) { - SET_ERRNO(HPE_INVALID_HEADER_TOKEN); - goto error; - } - - MARK(header_field); - - parser->index = 0; - UPDATE_STATE(s_header_field); - - switch (c) { - case 'c': - parser->header_state = h_C; - break; - - case 'p': - parser->header_state = h_matching_proxy_connection; - break; - - case 't': - parser->header_state = h_matching_transfer_encoding; - break; - - case 'u': - parser->header_state = h_matching_upgrade; - break; - - default: - parser->header_state = h_general; - break; - } - break; - } - - case s_header_field: - { - const char* start = p; - for (; p != data + len; p++) { - ch = *p; - c = TOKEN(ch); - - if (!c) - break; - - switch (parser->header_state) { - case h_general: - break; - - case h_C: - parser->index++; - parser->header_state = (c == 'o' ? h_CO : h_general); - break; - - case h_CO: - parser->index++; - parser->header_state = (c == 'n' ? h_CON : h_general); - break; - - case h_CON: - parser->index++; - switch (c) { - case 'n': - parser->header_state = h_matching_connection; - break; - case 't': - parser->header_state = h_matching_content_length; - break; - default: - parser->header_state = h_general; - break; - } - break; - - /* connection */ - - case h_matching_connection: - parser->index++; - if (parser->index > sizeof(CONNECTION)-1 - || c != CONNECTION[parser->index]) { - parser->header_state = h_general; - } else if (parser->index == sizeof(CONNECTION)-2) { - parser->header_state = h_connection; - } - break; - - /* proxy-connection */ - - case h_matching_proxy_connection: - parser->index++; - if (parser->index > sizeof(PROXY_CONNECTION)-1 - || c != PROXY_CONNECTION[parser->index]) { - parser->header_state = h_general; - } else if (parser->index == sizeof(PROXY_CONNECTION)-2) { - parser->header_state = h_connection; - } - break; - - /* content-length */ - - case h_matching_content_length: - parser->index++; - if (parser->index > sizeof(CONTENT_LENGTH)-1 - || c != CONTENT_LENGTH[parser->index]) { - parser->header_state = h_general; - } else if (parser->index == sizeof(CONTENT_LENGTH)-2) { - parser->header_state = h_content_length; - } - break; - - /* transfer-encoding */ - - case h_matching_transfer_encoding: - parser->index++; - if (parser->index > sizeof(TRANSFER_ENCODING)-1 - || c != TRANSFER_ENCODING[parser->index]) { - parser->header_state = h_general; - } else if (parser->index == sizeof(TRANSFER_ENCODING)-2) { - parser->header_state = h_transfer_encoding; - } - break; - - /* upgrade */ - - case h_matching_upgrade: - parser->index++; - if (parser->index > sizeof(UPGRADE)-1 - || c != UPGRADE[parser->index]) { - parser->header_state = h_general; - } else if (parser->index == sizeof(UPGRADE)-2) { - parser->header_state = h_upgrade; - } - break; - - case h_connection: - case h_content_length: - case h_transfer_encoding: - case h_upgrade: - if (ch != ' ') parser->header_state = h_general; - break; - - default: - assert(0 && "Unknown header_state"); - break; - } - } - - COUNT_HEADER_SIZE(p - start); - - if (p == data + len) { - --p; - break; - } - - if (ch == ':') { - UPDATE_STATE(s_header_value_discard_ws); - CALLBACK_DATA(header_field); - break; - } - - SET_ERRNO(HPE_INVALID_HEADER_TOKEN); - goto error; - } - - case s_header_value_discard_ws: - if (ch == ' ' || ch == '\t') break; - - if (ch == CR) { - UPDATE_STATE(s_header_value_discard_ws_almost_done); - break; - } - - if (ch == LF) { - UPDATE_STATE(s_header_value_discard_lws); - break; - } - - /* FALLTHROUGH */ - - case s_header_value_start: - { - MARK(header_value); - - UPDATE_STATE(s_header_value); - parser->index = 0; - - c = LOWER(ch); - - switch (parser->header_state) { - case h_upgrade: - parser->flags |= F_UPGRADE; - parser->header_state = h_general; - break; - - case h_transfer_encoding: - /* looking for 'Transfer-Encoding: chunked' */ - if ('c' == c) { - parser->header_state = h_matching_transfer_encoding_chunked; - } else { - parser->header_state = h_general; - } - break; - - case h_content_length: - if (UNLIKELY(!IS_NUM(ch))) { - SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); - goto error; - } - - if (parser->flags & F_CONTENTLENGTH) { - SET_ERRNO(HPE_UNEXPECTED_CONTENT_LENGTH); - goto error; - } - - parser->flags |= F_CONTENTLENGTH; - parser->content_length = ch - '0'; - break; - - case h_connection: - /* looking for 'Connection: keep-alive' */ - if (c == 'k') { - parser->header_state = h_matching_connection_keep_alive; - /* looking for 'Connection: close' */ - } else if (c == 'c') { - parser->header_state = h_matching_connection_close; - } else if (c == 'u') { - parser->header_state = h_matching_connection_upgrade; - } else { - parser->header_state = h_matching_connection_token; - } - break; - - /* Multi-value `Connection` header */ - case h_matching_connection_token_start: - break; - - default: - parser->header_state = h_general; - break; - } - break; - } - - case s_header_value: - { - const char* start = p; - enum header_states h_state = (enum header_states) parser->header_state; - for (; p != data + len; p++) { - ch = *p; - if (ch == CR) { - UPDATE_STATE(s_header_almost_done); - parser->header_state = h_state; - CALLBACK_DATA(header_value); - break; - } - - if (ch == LF) { - UPDATE_STATE(s_header_almost_done); - COUNT_HEADER_SIZE(p - start); - parser->header_state = h_state; - CALLBACK_DATA_NOADVANCE(header_value); - REEXECUTE(); - } - - if (!lenient && !IS_HEADER_CHAR(ch)) { - SET_ERRNO(HPE_INVALID_HEADER_TOKEN); - goto error; - } - - c = LOWER(ch); - - switch (h_state) { - case h_general: - { - const char* p_cr; - const char* p_lf; - uint32_t limit = data + len - p; - - limit = MIN(limit, HTTP_MAX_HEADER_SIZE); - - p_cr = (const char*) memchr(p, CR, limit); - p_lf = (const char*) memchr(p, LF, limit); - if (p_cr != NULL) { - if (p_lf != NULL && p_cr >= p_lf) - p = p_lf; - else - p = p_cr; - } else if (UNLIKELY(p_lf != NULL)) { - p = p_lf; - } else { - p = data + len; - } - --p; - - break; - } - - case h_connection: - case h_transfer_encoding: - assert(0 && "Shouldn't get here."); - break; - - case h_content_length: - { - uint64_t t; - - if (ch == ' ') break; - - if (UNLIKELY(!IS_NUM(ch))) { - SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); - parser->header_state = h_state; - goto error; - } - - t = parser->content_length; - t *= 10; - t += ch - '0'; - - /* Overflow? Test against a conservative limit for simplicity. */ - if (UNLIKELY((ULLONG_MAX - 10) / 10 < parser->content_length)) { - SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); - parser->header_state = h_state; - goto error; - } - - parser->content_length = t; - break; - } - - /* Transfer-Encoding: chunked */ - case h_matching_transfer_encoding_chunked: - parser->index++; - if (parser->index > sizeof(CHUNKED)-1 - || c != CHUNKED[parser->index]) { - h_state = h_general; - } else if (parser->index == sizeof(CHUNKED)-2) { - h_state = h_transfer_encoding_chunked; - } - break; - - case h_matching_connection_token_start: - /* looking for 'Connection: keep-alive' */ - if (c == 'k') { - h_state = h_matching_connection_keep_alive; - /* looking for 'Connection: close' */ - } else if (c == 'c') { - h_state = h_matching_connection_close; - } else if (c == 'u') { - h_state = h_matching_connection_upgrade; - } else if (STRICT_TOKEN(c)) { - h_state = h_matching_connection_token; - } else if (c == ' ' || c == '\t') { - /* Skip lws */ - } else { - h_state = h_general; - } - break; - - /* looking for 'Connection: keep-alive' */ - case h_matching_connection_keep_alive: - parser->index++; - if (parser->index > sizeof(KEEP_ALIVE)-1 - || c != KEEP_ALIVE[parser->index]) { - h_state = h_matching_connection_token; - } else if (parser->index == sizeof(KEEP_ALIVE)-2) { - h_state = h_connection_keep_alive; - } - break; - - /* looking for 'Connection: close' */ - case h_matching_connection_close: - parser->index++; - if (parser->index > sizeof(CLOSE)-1 || c != CLOSE[parser->index]) { - h_state = h_matching_connection_token; - } else if (parser->index == sizeof(CLOSE)-2) { - h_state = h_connection_close; - } - break; - - /* looking for 'Connection: upgrade' */ - case h_matching_connection_upgrade: - parser->index++; - if (parser->index > sizeof(UPGRADE) - 1 || - c != UPGRADE[parser->index]) { - h_state = h_matching_connection_token; - } else if (parser->index == sizeof(UPGRADE)-2) { - h_state = h_connection_upgrade; - } - break; - - case h_matching_connection_token: - if (ch == ',') { - h_state = h_matching_connection_token_start; - parser->index = 0; - } - break; - - case h_transfer_encoding_chunked: - if (ch != ' ') h_state = h_general; - break; - - case h_connection_keep_alive: - case h_connection_close: - case h_connection_upgrade: - if (ch == ',') { - if (h_state == h_connection_keep_alive) { - parser->flags |= F_CONNECTION_KEEP_ALIVE; - } else if (h_state == h_connection_close) { - parser->flags |= F_CONNECTION_CLOSE; - } else if (h_state == h_connection_upgrade) { - parser->flags |= F_CONNECTION_UPGRADE; - } - h_state = h_matching_connection_token_start; - parser->index = 0; - } else if (ch != ' ') { - h_state = h_matching_connection_token; - } - break; - - default: - UPDATE_STATE(s_header_value); - h_state = h_general; - break; - } - } - parser->header_state = h_state; - - COUNT_HEADER_SIZE(p - start); - - if (p == data + len) - --p; - break; - } - - case s_header_almost_done: - { - if (UNLIKELY(ch != LF)) { - SET_ERRNO(HPE_LF_EXPECTED); - goto error; - } - - UPDATE_STATE(s_header_value_lws); - break; - } - - case s_header_value_lws: - { - if (ch == ' ' || ch == '\t') { - UPDATE_STATE(s_header_value_start); - REEXECUTE(); - } - - /* finished the header */ - switch (parser->header_state) { - case h_connection_keep_alive: - parser->flags |= F_CONNECTION_KEEP_ALIVE; - break; - case h_connection_close: - parser->flags |= F_CONNECTION_CLOSE; - break; - case h_transfer_encoding_chunked: - parser->flags |= F_CHUNKED; - break; - case h_connection_upgrade: - parser->flags |= F_CONNECTION_UPGRADE; - break; - default: - break; - } - - UPDATE_STATE(s_header_field_start); - REEXECUTE(); - } - - case s_header_value_discard_ws_almost_done: - { - STRICT_CHECK(ch != LF); - UPDATE_STATE(s_header_value_discard_lws); - break; - } - - case s_header_value_discard_lws: - { - if (ch == ' ' || ch == '\t') { - UPDATE_STATE(s_header_value_discard_ws); - break; - } else { - switch (parser->header_state) { - case h_connection_keep_alive: - parser->flags |= F_CONNECTION_KEEP_ALIVE; - break; - case h_connection_close: - parser->flags |= F_CONNECTION_CLOSE; - break; - case h_connection_upgrade: - parser->flags |= F_CONNECTION_UPGRADE; - break; - case h_transfer_encoding_chunked: - parser->flags |= F_CHUNKED; - break; - default: - break; - } - - /* header value was empty */ - MARK(header_value); - UPDATE_STATE(s_header_field_start); - CALLBACK_DATA_NOADVANCE(header_value); - REEXECUTE(); - } - } - - case s_headers_almost_done: - { - STRICT_CHECK(ch != LF); - - if (parser->flags & F_TRAILING) { - /* End of a chunked request */ - UPDATE_STATE(s_message_done); - CALLBACK_NOTIFY_NOADVANCE(chunk_complete); - REEXECUTE(); - } - - /* Cannot use chunked encoding and a content-length header together - per the HTTP specification. */ - if ((parser->flags & F_CHUNKED) && - (parser->flags & F_CONTENTLENGTH)) { - SET_ERRNO(HPE_UNEXPECTED_CONTENT_LENGTH); - goto error; - } - - UPDATE_STATE(s_headers_done); - - /* Set this here so that on_headers_complete() callbacks can see it */ - parser->upgrade = - ((parser->flags & (F_UPGRADE | F_CONNECTION_UPGRADE)) == - (F_UPGRADE | F_CONNECTION_UPGRADE) || - parser->method == HTTP_CONNECT); - - /* Here we call the headers_complete callback. This is somewhat - * different than other callbacks because if the user returns 1, we - * will interpret that as saying that this message has no body. This - * is needed for the annoying case of recieving a response to a HEAD - * request. - * - * We'd like to use CALLBACK_NOTIFY_NOADVANCE() here but we cannot, so - * we have to simulate it by handling a change in errno below. - */ - if (settings->on_headers_complete) { - switch (settings->on_headers_complete(parser)) { - case 0: - break; - - case 2: - parser->upgrade = 1; - - case 1: - parser->flags |= F_SKIPBODY; - break; - - default: - SET_ERRNO(HPE_CB_headers_complete); - RETURN(p - data); /* Error */ - } - } - - if (HTTP_PARSER_ERRNO(parser) != HPE_OK) { - RETURN(p - data); - } - - REEXECUTE(); - } - - case s_headers_done: - { - int hasBody; - STRICT_CHECK(ch != LF); - - parser->nread = 0; - - hasBody = parser->flags & F_CHUNKED || - (parser->content_length > 0 && parser->content_length != ULLONG_MAX); - if (parser->upgrade && (parser->method == HTTP_CONNECT || - (parser->flags & F_SKIPBODY) || !hasBody)) { - /* Exit, the rest of the message is in a different protocol. */ - UPDATE_STATE(NEW_MESSAGE()); - CALLBACK_NOTIFY(message_complete); - RETURN((p - data) + 1); - } - - if (parser->flags & F_SKIPBODY) { - UPDATE_STATE(NEW_MESSAGE()); - CALLBACK_NOTIFY(message_complete); - } else if (parser->flags & F_CHUNKED) { - /* chunked encoding - ignore Content-Length header */ - UPDATE_STATE(s_chunk_size_start); - } else { - if (parser->content_length == 0) { - /* Content-Length header given but zero: Content-Length: 0\r\n */ - UPDATE_STATE(NEW_MESSAGE()); - CALLBACK_NOTIFY(message_complete); - } else if (parser->content_length != ULLONG_MAX) { - /* Content-Length header given and non-zero */ - UPDATE_STATE(s_body_identity); - } else { - if (!http_message_needs_eof(parser)) { - /* Assume content-length 0 - read the next */ - UPDATE_STATE(NEW_MESSAGE()); - CALLBACK_NOTIFY(message_complete); - } else { - /* Read body until EOF */ - UPDATE_STATE(s_body_identity_eof); - } - } - } - - break; - } - - case s_body_identity: - { - uint64_t to_read = MIN(parser->content_length, - (uint64_t) ((data + len) - p)); - - assert(parser->content_length != 0 - && parser->content_length != ULLONG_MAX); - - /* The difference between advancing content_length and p is because - * the latter will automaticaly advance on the next loop iteration. - * Further, if content_length ends up at 0, we want to see the last - * byte again for our message complete callback. - */ - MARK(body); - parser->content_length -= to_read; - p += to_read - 1; - - if (parser->content_length == 0) { - UPDATE_STATE(s_message_done); - - /* Mimic CALLBACK_DATA_NOADVANCE() but with one extra byte. - * - * The alternative to doing this is to wait for the next byte to - * trigger the data callback, just as in every other case. The - * problem with this is that this makes it difficult for the test - * harness to distinguish between complete-on-EOF and - * complete-on-length. It's not clear that this distinction is - * important for applications, but let's keep it for now. - */ - CALLBACK_DATA_(body, p - body_mark + 1, p - data); - REEXECUTE(); - } - - break; - } - - /* read until EOF */ - case s_body_identity_eof: - MARK(body); - p = data + len - 1; - - break; - - case s_message_done: - UPDATE_STATE(NEW_MESSAGE()); - CALLBACK_NOTIFY(message_complete); - if (parser->upgrade) { - /* Exit, the rest of the message is in a different protocol. */ - RETURN((p - data) + 1); - } - break; - - case s_chunk_size_start: - { - assert(parser->nread == 1); - assert(parser->flags & F_CHUNKED); - - unhex_val = unhex[(unsigned char)ch]; - if (UNLIKELY(unhex_val == -1)) { - SET_ERRNO(HPE_INVALID_CHUNK_SIZE); - goto error; - } - - parser->content_length = unhex_val; - UPDATE_STATE(s_chunk_size); - break; - } - - case s_chunk_size: - { - uint64_t t; - - assert(parser->flags & F_CHUNKED); - - if (ch == CR) { - UPDATE_STATE(s_chunk_size_almost_done); - break; - } - - unhex_val = unhex[(unsigned char)ch]; - - if (unhex_val == -1) { - if (ch == ';' || ch == ' ') { - UPDATE_STATE(s_chunk_parameters); - break; - } - - SET_ERRNO(HPE_INVALID_CHUNK_SIZE); - goto error; - } - - t = parser->content_length; - t *= 16; - t += unhex_val; - - /* Overflow? Test against a conservative limit for simplicity. */ - if (UNLIKELY((ULLONG_MAX - 16) / 16 < parser->content_length)) { - SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); - goto error; - } - - parser->content_length = t; - break; - } - - case s_chunk_parameters: - { - assert(parser->flags & F_CHUNKED); - /* just ignore this shit. TODO check for overflow */ - if (ch == CR) { - UPDATE_STATE(s_chunk_size_almost_done); - break; - } - break; - } - - case s_chunk_size_almost_done: - { - assert(parser->flags & F_CHUNKED); - STRICT_CHECK(ch != LF); - - parser->nread = 0; - - if (parser->content_length == 0) { - parser->flags |= F_TRAILING; - UPDATE_STATE(s_header_field_start); - } else { - UPDATE_STATE(s_chunk_data); - } - CALLBACK_NOTIFY(chunk_header); - break; - } - - case s_chunk_data: - { - uint64_t to_read = MIN(parser->content_length, - (uint64_t) ((data + len) - p)); - - assert(parser->flags & F_CHUNKED); - assert(parser->content_length != 0 - && parser->content_length != ULLONG_MAX); - - /* See the explanation in s_body_identity for why the content - * length and data pointers are managed this way. - */ - MARK(body); - parser->content_length -= to_read; - p += to_read - 1; - - if (parser->content_length == 0) { - UPDATE_STATE(s_chunk_data_almost_done); - } - - break; - } - - case s_chunk_data_almost_done: - assert(parser->flags & F_CHUNKED); - assert(parser->content_length == 0); - STRICT_CHECK(ch != CR); - UPDATE_STATE(s_chunk_data_done); - CALLBACK_DATA(body); - break; - - case s_chunk_data_done: - assert(parser->flags & F_CHUNKED); - STRICT_CHECK(ch != LF); - parser->nread = 0; - UPDATE_STATE(s_chunk_size_start); - CALLBACK_NOTIFY(chunk_complete); - break; - - default: - assert(0 && "unhandled state"); - SET_ERRNO(HPE_INVALID_INTERNAL_STATE); - goto error; - } - } - - /* Run callbacks for any marks that we have leftover after we ran our of - * bytes. There should be at most one of these set, so it's OK to invoke - * them in series (unset marks will not result in callbacks). - * - * We use the NOADVANCE() variety of callbacks here because 'p' has already - * overflowed 'data' and this allows us to correct for the off-by-one that - * we'd otherwise have (since CALLBACK_DATA() is meant to be run with a 'p' - * value that's in-bounds). - */ - - assert(((header_field_mark ? 1 : 0) + - (header_value_mark ? 1 : 0) + - (url_mark ? 1 : 0) + - (body_mark ? 1 : 0) + - (status_mark ? 1 : 0)) <= 1); - - CALLBACK_DATA_NOADVANCE(header_field); - CALLBACK_DATA_NOADVANCE(header_value); - CALLBACK_DATA_NOADVANCE(url); - CALLBACK_DATA_NOADVANCE(body); - CALLBACK_DATA_NOADVANCE(status); - - RETURN(len); - -error: - if (HTTP_PARSER_ERRNO(parser) == HPE_OK) { - SET_ERRNO(HPE_UNKNOWN); - } - - RETURN(p - data); -} - - -/* Does the parser need to see an EOF to find the end of the message? */ -int -http_message_needs_eof (const http_parser *parser) -{ - if (parser->type == HTTP_REQUEST) { - return 0; - } - - /* See RFC 2616 section 4.4 */ - if (parser->status_code / 100 == 1 || /* 1xx e.g. Continue */ - parser->status_code == 204 || /* No Content */ - parser->status_code == 304 || /* Not Modified */ - parser->flags & F_SKIPBODY) { /* response to a HEAD request */ - return 0; - } - - if ((parser->flags & F_CHUNKED) || parser->content_length != ULLONG_MAX) { - return 0; - } - - return 1; -} - - -int -http_should_keep_alive (const http_parser *parser) -{ - if (parser->http_major > 0 && parser->http_minor > 0) { - /* HTTP/1.1 */ - if (parser->flags & F_CONNECTION_CLOSE) { - return 0; - } - } else { - /* HTTP/1.0 or earlier */ - if (!(parser->flags & F_CONNECTION_KEEP_ALIVE)) { - return 0; - } - } - - return !http_message_needs_eof(parser); -} - - -const char * -http_method_str (enum http_method m) -{ - return ELEM_AT(method_strings, m, ""); -} - - -void -http_parser_init (http_parser *parser, enum http_parser_type t) -{ - void *data = parser->data; /* preserve application data */ - memset(parser, 0, sizeof(*parser)); - parser->data = data; - parser->type = t; - parser->state = (t == HTTP_REQUEST ? s_start_req : (t == HTTP_RESPONSE ? s_start_res : s_start_req_or_res)); - parser->http_errno = HPE_OK; -} - -void -http_parser_settings_init(http_parser_settings *settings) -{ - memset(settings, 0, sizeof(*settings)); -} - -const char * -http_errno_name(enum http_errno err) { - assert(((uint32_t) err) < ARRAY_SIZE(http_strerror_tab)); - return http_strerror_tab[err].name; -} - -const char * -http_errno_description(enum http_errno err) { - assert(((uint32_t) err) < ARRAY_SIZE(http_strerror_tab)); - return http_strerror_tab[err].description; -} - static enum http_host_state http_parse_host_char(enum http_host_state s, const char ch) { switch(s) { @@ -2443,25 +574,6 @@ http_parser_parse_url(const char *buf, uint32_t buflen, int is_connect, return 0; } -void -http_parser_pause(http_parser *parser, int paused) { - /* Users should only be pausing/unpausing a parser that is not in an error - * state. In non-debug builds, there's not much that we can do about this - * other than ignore it. - */ - if (HTTP_PARSER_ERRNO(parser) == HPE_OK || - HTTP_PARSER_ERRNO(parser) == HPE_PAUSED) { - SET_ERRNO((paused) ? HPE_PAUSED : HPE_OK); - } else { - assert(0 && "Attempting to pause parser in error state"); - } -} - -int -http_body_is_final(const struct http_parser *parser) { - return parser->state == s_message_done; -} - unsigned long http_parser_version(void) { return HTTP_PARSER_VERSION_MAJOR * 0x10000 | diff --git a/src/utility/http_parser.h b/src/utility/http_parser.h index 472bf54..85a5238 100644 --- a/src/utility/http_parser.h +++ b/src/utility/http_parser.h @@ -30,21 +30,7 @@ extern "C" { #define HTTP_PARSER_VERSION_MINOR 7 #define HTTP_PARSER_VERSION_PATCH 1 -#if defined(_WIN32) && !defined(__MINGW32__) && \ - (!defined(_MSC_VER) || _MSC_VER<1600) && !defined(__WINE__) -#include -#include -typedef __int8 int8_t; -typedef unsigned __int8 uint8_t; -typedef __int16 int16_t; -typedef unsigned __int16 uint16_t; -typedef __int32 int32_t; -typedef unsigned __int32 uint32_t; -typedef __int64 int64_t; -typedef unsigned __int64 uint64_t; -#else #include -#endif /* Compile with -DHTTP_PARSER_STRICT=0 to make less checks, but run * faster @@ -53,289 +39,6 @@ typedef unsigned __int64 uint64_t; # define HTTP_PARSER_STRICT 1 #endif -/* Maximium header size allowed. If the macro is not defined - * before including this header then the default is used. To - * change the maximum header size, define the macro in the build - * environment (e.g. -DHTTP_MAX_HEADER_SIZE=). To remove - * the effective limit on the size of the header, define the macro - * to a very large number (e.g. -DHTTP_MAX_HEADER_SIZE=0x7fffffff) - */ -#ifndef HTTP_MAX_HEADER_SIZE -# define HTTP_MAX_HEADER_SIZE (80*1024) -#endif - -typedef struct http_parser http_parser; -typedef struct http_parser_settings http_parser_settings; - - -/* Callbacks should return non-zero to indicate an error. The parser will - * then halt execution. - * - * The one exception is on_headers_complete. In a HTTP_RESPONSE parser - * returning '1' from on_headers_complete will tell the parser that it - * should not expect a body. This is used when receiving a response to a - * HEAD request which may contain 'Content-Length' or 'Transfer-Encoding: - * chunked' headers that indicate the presence of a body. - * - * Returning `2` from on_headers_complete will tell parser that it should not - * expect neither a body nor any futher responses on this connection. This is - * useful for handling responses to a CONNECT request which may not contain - * `Upgrade` or `Connection: upgrade` headers. - * - * http_data_cb does not return data chunks. It will be called arbitrarily - * many times for each string. E.G. you might get 10 callbacks for "on_url" - * each providing just a few characters more data. - */ -typedef int (*http_data_cb) (http_parser*, const char *at, uint32_t length); -typedef int (*http_cb) (http_parser*); - - -/* Status Codes */ -#define HTTP_STATUS_MAP(XX) \ - XX(100, CONTINUE, Continue) \ - XX(101, SWITCHING_PROTOCOLS, Switching Protocols) \ - XX(102, PROCESSING, Processing) \ - XX(200, OK, OK) \ - XX(201, CREATED, Created) \ - XX(202, ACCEPTED, Accepted) \ - XX(203, NON_AUTHORITATIVE_INFORMATION, Non-Authoritative Information) \ - XX(204, NO_CONTENT, No Content) \ - XX(205, RESET_CONTENT, Reset Content) \ - XX(206, PARTIAL_CONTENT, Partial Content) \ - XX(207, MULTI_STATUS, Multi-Status) \ - XX(208, ALREADY_REPORTED, Already Reported) \ - XX(226, IM_USED, IM Used) \ - XX(300, MULTIPLE_CHOICES, Multiple Choices) \ - XX(301, MOVED_PERMANENTLY, Moved Permanently) \ - XX(302, FOUND, Found) \ - XX(303, SEE_OTHER, See Other) \ - XX(304, NOT_MODIFIED, Not Modified) \ - XX(305, USE_PROXY, Use Proxy) \ - XX(307, TEMPORARY_REDIRECT, Temporary Redirect) \ - XX(308, PERMANENT_REDIRECT, Permanent Redirect) \ - XX(400, BAD_REQUEST, Bad Request) \ - XX(401, UNAUTHORIZED, Unauthorized) \ - XX(402, PAYMENT_REQUIRED, Payment Required) \ - XX(403, FORBIDDEN, Forbidden) \ - XX(404, NOT_FOUND, Not Found) \ - XX(405, METHOD_NOT_ALLOWED, Method Not Allowed) \ - XX(406, NOT_ACCEPTABLE, Not Acceptable) \ - XX(407, PROXY_AUTHENTICATION_REQUIRED, Proxy Authentication Required) \ - XX(408, REQUEST_TIMEOUT, Request Timeout) \ - XX(409, CONFLICT, Conflict) \ - XX(410, GONE, Gone) \ - XX(411, LENGTH_REQUIRED, Length Required) \ - XX(412, PRECONDITION_FAILED, Precondition Failed) \ - XX(413, PAYLOAD_TOO_LARGE, Payload Too Large) \ - XX(414, URI_TOO_LONG, URI Too Long) \ - XX(415, UNSUPPORTED_MEDIA_TYPE, Unsupported Media Type) \ - XX(416, RANGE_NOT_SATISFIABLE, Range Not Satisfiable) \ - XX(417, EXPECTATION_FAILED, Expectation Failed) \ - XX(421, MISDIRECTED_REQUEST, Misdirected Request) \ - XX(422, UNPROCESSABLE_ENTITY, Unprocessable Entity) \ - XX(423, LOCKED, Locked) \ - XX(424, FAILED_DEPENDENCY, Failed Dependency) \ - XX(426, UPGRADE_REQUIRED, Upgrade Required) \ - XX(428, PRECONDITION_REQUIRED, Precondition Required) \ - XX(429, TOO_MANY_REQUESTS, Too Many Requests) \ - XX(431, REQUEST_HEADER_FIELDS_TOO_LARGE, Request Header Fields Too Large) \ - XX(451, UNAVAILABLE_FOR_LEGAL_REASONS, Unavailable For Legal Reasons) \ - XX(500, INTERNAL_SERVER_ERROR, Internal Server Error) \ - XX(501, NOT_IMPLEMENTED, Not Implemented) \ - XX(502, BAD_GATEWAY, Bad Gateway) \ - XX(503, SERVICE_UNAVAILABLE, Service Unavailable) \ - XX(504, GATEWAY_TIMEOUT, Gateway Timeout) \ - XX(505, HTTP_VERSION_NOT_SUPPORTED, HTTP Version Not Supported) \ - XX(506, VARIANT_ALSO_NEGOTIATES, Variant Also Negotiates) \ - XX(507, INSUFFICIENT_STORAGE, Insufficient Storage) \ - XX(508, LOOP_DETECTED, Loop Detected) \ - XX(510, NOT_EXTENDED, Not Extended) \ - XX(511, NETWORK_AUTHENTICATION_REQUIRED, Network Authentication Required) \ - -enum http_status - { -#define XX(num, name, string) HTTP_STATUS_##name = num, - HTTP_STATUS_MAP(XX) -#undef XX - }; - - -/* Request Methods */ -#define HTTP_METHOD_MAP(XX) \ - XX(0, DELETE, DELETE) \ - XX(1, GET, GET) \ - XX(2, HEAD, HEAD) \ - XX(3, POST, POST) \ - XX(4, PUT, PUT) \ - /* pathological */ \ - XX(5, CONNECT, CONNECT) \ - XX(6, OPTIONS, OPTIONS) \ - XX(7, TRACE, TRACE) \ - /* WebDAV */ \ - XX(8, COPY, COPY) \ - XX(9, LOCK, LOCK) \ - XX(10, MKCOL, MKCOL) \ - XX(11, MOVE, MOVE) \ - XX(12, PROPFIND, PROPFIND) \ - XX(13, PROPPATCH, PROPPATCH) \ - XX(14, SEARCH, SEARCH) \ - XX(15, UNLOCK, UNLOCK) \ - XX(16, BIND, BIND) \ - XX(17, REBIND, REBIND) \ - XX(18, UNBIND, UNBIND) \ - XX(19, ACL, ACL) \ - /* subversion */ \ - XX(20, REPORT, REPORT) \ - XX(21, MKACTIVITY, MKACTIVITY) \ - XX(22, CHECKOUT, CHECKOUT) \ - XX(23, MERGE, MERGE) \ - /* upnp */ \ - XX(24, MSEARCH, M-SEARCH) \ - XX(25, NOTIFY, NOTIFY) \ - XX(26, SUBSCRIBE, SUBSCRIBE) \ - XX(27, UNSUBSCRIBE, UNSUBSCRIBE) \ - /* RFC-5789 */ \ - XX(28, PATCH, PATCH) \ - XX(29, PURGE, PURGE) \ - /* CalDAV */ \ - XX(30, MKCALENDAR, MKCALENDAR) \ - /* RFC-2068, section 19.6.1.2 */ \ - XX(31, LINK, LINK) \ - XX(32, UNLINK, UNLINK) \ - -enum http_method - { -#define XX(num, name, string) HTTP_##name = num, - HTTP_METHOD_MAP(XX) -#undef XX - }; - - -enum http_parser_type { HTTP_REQUEST, HTTP_RESPONSE, HTTP_BOTH }; - - -/* Flag values for http_parser.flags field */ -enum flags - { F_CHUNKED = 1 << 0 - , F_CONNECTION_KEEP_ALIVE = 1 << 1 - , F_CONNECTION_CLOSE = 1 << 2 - , F_CONNECTION_UPGRADE = 1 << 3 - , F_TRAILING = 1 << 4 - , F_UPGRADE = 1 << 5 - , F_SKIPBODY = 1 << 6 - , F_CONTENTLENGTH = 1 << 7 - }; - - -/* Map for errno-related constants - * - * The provided argument should be a macro that takes 2 arguments. - */ -#define HTTP_ERRNO_MAP(XX) \ - /* No error */ \ - XX(OK, "success") \ - \ - /* Callback-related errors */ \ - XX(CB_message_begin, "the on_message_begin callback failed") \ - XX(CB_url, "the on_url callback failed") \ - XX(CB_header_field, "the on_header_field callback failed") \ - XX(CB_header_value, "the on_header_value callback failed") \ - XX(CB_headers_complete, "the on_headers_complete callback failed") \ - XX(CB_body, "the on_body callback failed") \ - XX(CB_message_complete, "the on_message_complete callback failed") \ - XX(CB_status, "the on_status callback failed") \ - XX(CB_chunk_header, "the on_chunk_header callback failed") \ - XX(CB_chunk_complete, "the on_chunk_complete callback failed") \ - \ - /* Parsing-related errors */ \ - XX(INVALID_EOF_STATE, "stream ended at an unexpected time") \ - XX(HEADER_OVERFLOW, \ - "too many header bytes seen; overflow detected") \ - XX(CLOSED_CONNECTION, \ - "data received after completed connection: close message") \ - XX(INVALID_VERSION, "invalid HTTP version") \ - XX(INVALID_STATUS, "invalid HTTP status code") \ - XX(INVALID_METHOD, "invalid HTTP method") \ - XX(INVALID_URL, "invalid URL") \ - XX(INVALID_HOST, "invalid host") \ - XX(INVALID_PORT, "invalid port") \ - XX(INVALID_PATH, "invalid path") \ - XX(INVALID_QUERY_STRING, "invalid query string") \ - XX(INVALID_FRAGMENT, "invalid fragment") \ - XX(LF_EXPECTED, "LF character expected") \ - XX(INVALID_HEADER_TOKEN, "invalid character in header") \ - XX(INVALID_CONTENT_LENGTH, \ - "invalid character in content-length header") \ - XX(UNEXPECTED_CONTENT_LENGTH, \ - "unexpected content-length header") \ - XX(INVALID_CHUNK_SIZE, \ - "invalid character in chunk size header") \ - XX(INVALID_CONSTANT, "invalid constant string") \ - XX(INVALID_INTERNAL_STATE, "encountered unexpected internal state")\ - XX(STRICT, "strict mode assertion failed") \ - XX(PAUSED, "parser is paused") \ - XX(UNKNOWN, "an unknown error occurred") - - -/* Define HPE_* values for each errno value above */ -#define HTTP_ERRNO_GEN(n, s) HPE_##n, -enum http_errno { - HTTP_ERRNO_MAP(HTTP_ERRNO_GEN) -}; -#undef HTTP_ERRNO_GEN - - -/* Get an http_errno value from an http_parser */ -#define HTTP_PARSER_ERRNO(p) ((enum http_errno) (p)->http_errno) - - -struct http_parser { - /** PRIVATE **/ - unsigned int type : 2; /* enum http_parser_type */ - unsigned int flags : 8; /* F_* values from 'flags' enum; semi-public */ - unsigned int state : 7; /* enum state from http_parser.c */ - unsigned int header_state : 7; /* enum header_state from http_parser.c */ - unsigned int index : 7; /* index into current matcher */ - unsigned int lenient_http_headers : 1; - - uint32_t nread; /* # bytes read in various scenarios */ - uint64_t content_length; /* # bytes in body (0 if no Content-Length header) */ - - /** READ-ONLY **/ - unsigned short http_major; - unsigned short http_minor; - unsigned int status_code : 16; /* responses only */ - unsigned int method : 8; /* requests only */ - unsigned int http_errno : 7; - - /* 1 = Upgrade header was present and the parser has exited because of that. - * 0 = No upgrade header present. - * Should be checked when http_parser_execute() returns in addition to - * error checking. - */ - unsigned int upgrade : 1; - - /** PUBLIC **/ - void *data; /* A pointer to get hook to the "connection" or "socket" object */ -}; - - -struct http_parser_settings { - http_cb on_message_begin; - http_data_cb on_url; - http_data_cb on_status; - http_data_cb on_header_field; - http_data_cb on_header_value; - http_cb on_headers_complete; - http_data_cb on_body; - http_cb on_message_complete; - /* When on_chunk_header is called, the current chunk length is stored - * in parser->content_length. - */ - http_cb on_chunk_header; - http_cb on_chunk_complete; -}; - enum http_parser_url_fields { UF_SCHEMA = 0 @@ -379,39 +82,6 @@ struct http_parser_url { */ unsigned long http_parser_version(void); -void http_parser_init(http_parser *parser, enum http_parser_type type); - - -/* Initialize http_parser_settings members to 0 - */ -void http_parser_settings_init(http_parser_settings *settings); - - -/* Executes the parser. Returns number of parsed bytes. Sets - * `parser->http_errno` on error. */ -uint32_t http_parser_execute(http_parser *parser, - const http_parser_settings *settings, - const char *data, - uint32_t len); - - -/* If http_should_keep_alive() in the on_headers_complete or - * on_message_complete callback returns 0, then this should be - * the last message on the connection. - * If you are the server, respond with the "Connection: close" header. - * If you are the client, close the connection. - */ -int http_should_keep_alive(const http_parser *parser); - -/* Returns a string version of the HTTP method. */ -const char *http_method_str(enum http_method m); - -/* Return a string name of the given error */ -const char *http_errno_name(enum http_errno err); - -/* Return a string description of the given error */ -const char *http_errno_description(enum http_errno err); - /* Initialize all http_parser_url members to 0 */ void http_parser_url_init(struct http_parser_url *u); @@ -420,12 +90,6 @@ int http_parser_parse_url(const char *buf, uint32_t buflen, int is_connect, struct http_parser_url *u); -/* Pause or un-pause the parser; a nonzero value pauses */ -void http_parser_pause(http_parser *parser, int paused); - -/* Checks if this is the final chunk of the body. */ -int http_body_is_final(const http_parser *parser); - #ifdef __cplusplus } #endif From e5db3b5880a63522db4708fc653920b79a694477 Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Wed, 21 Feb 2024 15:32:21 +0100 Subject: [PATCH 111/125] imported wrapper class for url parsing --- src/URLParser.h | 94 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 src/URLParser.h diff --git a/src/URLParser.h b/src/URLParser.h new file mode 100644 index 0000000..eaac7e9 --- /dev/null +++ b/src/URLParser.h @@ -0,0 +1,94 @@ +/* + * PackageLicenseDeclared: Apache-2.0 + * Copyright (c) 2017 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _MBED_HTTP_PARSED_URL_H_ +#define _MBED_HTTP_PARSED_URL_H_ + +#include "utility/http_parser.h" + +class ParsedUrl { +public: + ParsedUrl(const char* url) { + struct http_parser_url parsed_url; + http_parser_parse_url(https://github.com/url,%20strlen(url), false, &parsed_url); + + for (size_t ix = 0; ix < UF_MAX; ix++) { + char* value; + if (parsed_url.field_set & (1 << ix)) { + value = (char*)calloc(parsed_url.field_data[ix].len + 1, 1); + memcpy(value, url + parsed_url.field_data[ix].off, + parsed_url.field_data[ix].len); + } + else { + value = (char*)calloc(1, 1); + } + + switch ((http_parser_url_fields)ix) { + case UF_SCHEMA: _schema = value; break; + case UF_HOST: _host = value; break; + case UF_PATH: _path = value; break; + case UF_QUERY: _query = value; break; + case UF_USERINFO: _userinfo = value; break; + default: + // PORT is already parsed, FRAGMENT is not relevant for HTTP requests + free(value); + break; + } + } + + _port = parsed_url.port; + if (!_port) { + if (strcmp(_schema, "https") == 0 || strcmp(_schema, "wss") == 0) { + _port = 443; + } + else { + _port = 80; + } + } + + if (strcmp(_path, "") == 0) { + free(_path); + _path = (char*)calloc(2, 1); + _path[0] = '/'; + } + } + + ~ParsedUrl() { + if (_schema) free(_schema); + if (_host) free(_host); + if (_path) free(_path); + if (_query) free(_query); + if (_userinfo) free(_userinfo); + } + + uint16_t port() const { return _port; } + char* schema() const { return _schema; } + char* host() const { return _host; } + char* path() const { return _path; } + char* query() const { return _query; } + char* userinfo() const { return _userinfo; } + +private: + uint16_t _port; + char* _schema; + char* _host; + char* _path; + char* _query; + char* _userinfo; +}; + +#endif // _MBED_HTTP_PARSED_URL_H_ From a1a79a59157f2c191229bf2729e038cfcf1bfe39 Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Wed, 21 Feb 2024 16:25:42 +0100 Subject: [PATCH 112/125] added example for url parsing --- examples/ParseURL/ParseURL.ino | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 examples/ParseURL/ParseURL.ino diff --git a/examples/ParseURL/ParseURL.ino b/examples/ParseURL/ParseURL.ino new file mode 100644 index 0000000..f69a2bc --- /dev/null +++ b/examples/ParseURL/ParseURL.ino @@ -0,0 +1,29 @@ +#include "URLParser.h" + +void setup() { + + Serial.begin(9600); + + while(!Serial); + + Serial.println("starting"); + + ParsedUrl url( + "https://www.google.com/search?q=arduino" + ); + + Serial.print("parsed url schema: \""); + Serial.print(url.schema()); + Serial.print("\"\nparsed url host: \""); + Serial.print(url.host()); + Serial.print("\"\nparsed url path: \""); + Serial.print(url.path()); + Serial.print("\"\nparsed url query: \""); + Serial.print(url.query()); + Serial.print("\"\nparsed url userinfo: \""); + Serial.print(url.userinfo()); + Serial.println("\""); + +} + +void loop() { } \ No newline at end of file From 9f01bfe879b149c312473c8f9da976c46542a241 Mon Sep 17 00:00:00 2001 From: Alexander Entinger Date: Wed, 21 Feb 2024 18:57:17 +0100 Subject: [PATCH 113/125] Revert "Fix regression: report size delta size on PR. (#172)" This reverts commit 7a6b39a1af31c175c7e999ac366f52b6a3efcba4. --- .github/workflows/compile-examples.yml | 27 ++++-------------------- .github/workflows/report-size-deltas.yml | 24 +++++++++++++++++++++ .gitignore | 1 - 3 files changed, 28 insertions(+), 24 deletions(-) create mode 100644 .github/workflows/report-size-deltas.yml diff --git a/.github/workflows/compile-examples.yml b/.github/workflows/compile-examples.yml index d7a183e..0dd5ac7 100644 --- a/.github/workflows/compile-examples.yml +++ b/.github/workflows/compile-examples.yml @@ -18,15 +18,14 @@ on: workflow_dispatch: repository_dispatch: -env: - # It's convenient to set variables for values used multiple times in the workflow. - SKETCHES_REPORTS_PATH: sketches-reports - jobs: - compile: + build: name: ${{ matrix.board.fqbn }} runs-on: ubuntu-latest + env: + SKETCHES_REPORTS_PATH: sketches-reports + strategy: fail-fast: false @@ -62,21 +61,3 @@ jobs: if-no-files-found: error path: ${{ env.SKETCHES_REPORTS_PATH }} name: sketches-report-${{ matrix.board.artifact-name-suffix }} - - # When using a matrix to compile for multiple boards, it's necessary to use a separate job for the deltas report - report: - needs: compile # Wait for the compile job to finish to get the data for the report - if: github.event_name == 'pull_request' # Only run the job when the workflow is triggered by a pull request - runs-on: ubuntu-latest - - steps: - # This step is needed to get the size data produced by the compile jobs - - name: Download sketches reports artifacts - uses: actions/download-artifact@v4 - with: - # All workflow artifacts will be downloaded to this location. - path: ${{ env.SKETCHES_REPORTS_PATH }} - - - uses: arduino/report-size-deltas@v1 - with: - sketches-reports-source: ${{ env.SKETCHES_REPORTS_PATH }} diff --git a/.github/workflows/report-size-deltas.yml b/.github/workflows/report-size-deltas.yml new file mode 100644 index 0000000..652be5d --- /dev/null +++ b/.github/workflows/report-size-deltas.yml @@ -0,0 +1,24 @@ +name: Report Size Deltas + +# See: https://docs.github.com/en/free-pro-team@latest/actions/reference/events-that-trigger-workflows +on: + push: + paths: + - ".github/workflows/report-size-deltas.yml" + schedule: + # Run at the minimum interval allowed by GitHub Actions. + # Note: GitHub Actions periodically has outages which result in workflow failures. + # In this event, the workflows will start passing again once the service recovers. + - cron: "*/5 * * * *" + workflow_dispatch: + repository_dispatch: + +jobs: + report: + runs-on: ubuntu-latest + steps: + - name: Comment size deltas reports to PRs + uses: arduino/report-size-deltas@v1 + with: + # The name of the workflow artifact created by the sketch compilation workflow + sketches-reports-source: sketches-reports diff --git a/.gitignore b/.gitignore index 24a0007..653acab 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,3 @@ examples/node_test_server/node_modules/ *.DS_Store */.DS_Store examples/.DS_Store -.idea/ From 8c7bff748a40d3bfdf214104b2bba6acceba3ee7 Mon Sep 17 00:00:00 2001 From: Alexander Entinger Date: Wed, 21 Feb 2024 18:57:45 +0100 Subject: [PATCH 114/125] Correct workflow artifact name pattern in size deltas report workflow The "sketches-reports-source" input of the "arduino/report-size-deltas" GitHub Actions action defines the regular expression that matches the names of the sketches report workflow artifacts produced by the "Compile Examples" workflow. The key string in the names of these artifacts was set to "sketches-report" when the "Compile Examples" workflow was adjusted for compatibility with the breaking changes introduced by updating to version 4.x of the workflow's "actions/upload-artifact" GitHub Actions action dependency. The pattern set in the size deltas report workflow was "sketches-reports". The "s" at the end of that pattern caused it to no longer match against the key string in the artifact names after that adjustment of the "Compile Examples" workflow, resulting in size deltas reports no longer being generated by the workflow. Although a minimal fix would be to simply remove the "s" from the end of the pattern, the decision was made to use a more strict regular expression. This will make it easier for maintainers and contributors to understand that this value is a regular expression and the exact nature of how that regular expression functions (which is less clear when relying on the "arduino/report-size-deltas" action's partial pattern matching behavior). --- .github/workflows/report-size-deltas.yml | 4 ++-- .gitignore | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/report-size-deltas.yml b/.github/workflows/report-size-deltas.yml index 652be5d..39e2a0a 100644 --- a/.github/workflows/report-size-deltas.yml +++ b/.github/workflows/report-size-deltas.yml @@ -20,5 +20,5 @@ jobs: - name: Comment size deltas reports to PRs uses: arduino/report-size-deltas@v1 with: - # The name of the workflow artifact created by the sketch compilation workflow - sketches-reports-source: sketches-reports + # Regex matching the names of the workflow artifacts created by the "Compile Examples" workflow + sketches-reports-source: ^sketches-report-.+ diff --git a/.gitignore b/.gitignore index 653acab..24a0007 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ examples/node_test_server/node_modules/ *.DS_Store */.DS_Store examples/.DS_Store +.idea/ From 8d44059cd5bdeccb29726ec53c796c85501f5caf Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni <4046444+andreagilardoni@users.noreply.github.com> Date: Wed, 13 Mar 2024 10:48:08 +0100 Subject: [PATCH 115/125] Update examples/ParseURL/ParseURL.ino Co-authored-by: per1234 --- examples/ParseURL/ParseURL.ino | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/ParseURL/ParseURL.ino b/examples/ParseURL/ParseURL.ino index f69a2bc..410ac85 100644 --- a/examples/ParseURL/ParseURL.ino +++ b/examples/ParseURL/ParseURL.ino @@ -12,15 +12,15 @@ void setup() { "https://www.google.com/search?q=arduino" ); - Serial.print("parsed url schema: \""); + Serial.print("parsed URL schema: \""); Serial.print(url.schema()); - Serial.print("\"\nparsed url host: \""); + Serial.print("\"\nparsed URL host: \""); Serial.print(url.host()); - Serial.print("\"\nparsed url path: \""); + Serial.print("\"\nparsed URL path: \""); Serial.print(url.path()); - Serial.print("\"\nparsed url query: \""); + Serial.print("\"\nparsed URL query: \""); Serial.print(url.query()); - Serial.print("\"\nparsed url userinfo: \""); + Serial.print("\"\nparsed URL userinfo: \""); Serial.print(url.userinfo()); Serial.println("\""); From dbe65d631e46e1f829d9aee8375614477a8b138e Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Mon, 4 Mar 2024 16:43:13 +0100 Subject: [PATCH 116/125] added include preprocessor for boards compatibility --- src/URLParser.h | 14 ++++++++++++++ src/utility/http_parser.c | 9 +++++++++ 2 files changed, 23 insertions(+) diff --git a/src/URLParser.h b/src/URLParser.h index eaac7e9..0424592 100644 --- a/src/URLParser.h +++ b/src/URLParser.h @@ -15,6 +15,18 @@ * limitations under the License. */ +/* + * The following class is defined in mbed libraries, in case of STM32H7 include the original library + */ +#if defined __has_include +# if __has_include() +# include +# else +# define NO_HTTP_PARSED +# endif +#endif + +#ifdef NO_HTTP_PARSED #ifndef _MBED_HTTP_PARSED_URL_H_ #define _MBED_HTTP_PARSED_URL_H_ @@ -92,3 +104,5 @@ class ParsedUrl { }; #endif // _MBED_HTTP_PARSED_URL_H_ +#endif // NO_HTTP_PARSED +#undef NO_HTTP_PARSED \ No newline at end of file diff --git a/src/utility/http_parser.c b/src/utility/http_parser.c index f1be647..a572a4c 100644 --- a/src/utility/http_parser.c +++ b/src/utility/http_parser.c @@ -1,3 +1,10 @@ +#if defined __has_include +# if ! __has_include() && ! __has_include() +# define NO_HTTP_PARSER +# endif +#endif + +#ifdef NO_HTTP_PARSER /* Based on src/http/ngx_http_parse.c from NGINX copyright Igor Sysoev * * Additional changes are licensed under the same terms as NGINX and @@ -580,3 +587,5 @@ http_parser_version(void) { HTTP_PARSER_VERSION_MINOR * 0x00100 | HTTP_PARSER_VERSION_PATCH * 0x00001; } + +#endif // NO_HTTP_PARSER \ No newline at end of file From 734f5b58a9fc4920eebfa063466434ef4c7ed486 Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Wed, 13 Mar 2024 10:43:48 +0100 Subject: [PATCH 117/125] fixing codespell ci for imported url lib --- .codespellrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.codespellrc b/.codespellrc index 101edae..2fcb936 100644 --- a/.codespellrc +++ b/.codespellrc @@ -4,4 +4,4 @@ ignore-words-list = , check-filenames = check-hidden = -skip = ./.git +skip = ./.git,./src/utility \ No newline at end of file From 482e088f93c09a62a5aa56fbe946534a94344e69 Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Thu, 14 Mar 2024 14:39:25 +0100 Subject: [PATCH 118/125] added README for imported library --- src/utility/README.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 src/utility/README.md diff --git a/src/utility/README.md b/src/utility/README.md new file mode 100644 index 0000000..56f8c96 --- /dev/null +++ b/src/utility/README.md @@ -0,0 +1,5 @@ +# http_parser library + +This code is imported from: https://github.com/arduino/ArduinoCore-mbed/tree/4.1.1/libraries/SocketWrapper/src/utility/http_parser + +The code is shrinked in size by deleting all the unrelated code to url parse. From 9ea6ace0d031396107d38cdb427ac01ae2b5c021 Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Wed, 13 Mar 2024 13:29:03 +0100 Subject: [PATCH 119/125] moving http_parser from utility to utility/URLParser --- .codespellrc | 2 +- src/URLParser.h | 2 +- src/utility/{ => URLParser}/LICENSE | 0 src/utility/{ => URLParser}/README.md | 0 src/utility/{ => URLParser}/http_parser.c | 0 src/utility/{ => URLParser}/http_parser.h | 0 6 files changed, 2 insertions(+), 2 deletions(-) rename src/utility/{ => URLParser}/LICENSE (100%) rename src/utility/{ => URLParser}/README.md (100%) rename src/utility/{ => URLParser}/http_parser.c (100%) rename src/utility/{ => URLParser}/http_parser.h (100%) diff --git a/.codespellrc b/.codespellrc index 2fcb936..c5233c0 100644 --- a/.codespellrc +++ b/.codespellrc @@ -4,4 +4,4 @@ ignore-words-list = , check-filenames = check-hidden = -skip = ./.git,./src/utility \ No newline at end of file +skip = ./.git,./src/utility/URLParser diff --git a/src/URLParser.h b/src/URLParser.h index 0424592..fd31e93 100644 --- a/src/URLParser.h +++ b/src/URLParser.h @@ -30,7 +30,7 @@ #ifndef _MBED_HTTP_PARSED_URL_H_ #define _MBED_HTTP_PARSED_URL_H_ -#include "utility/http_parser.h" +#include "utility/URLParser/http_parser.h" class ParsedUrl { public: diff --git a/src/utility/LICENSE b/src/utility/URLParser/LICENSE similarity index 100% rename from src/utility/LICENSE rename to src/utility/URLParser/LICENSE diff --git a/src/utility/README.md b/src/utility/URLParser/README.md similarity index 100% rename from src/utility/README.md rename to src/utility/URLParser/README.md diff --git a/src/utility/http_parser.c b/src/utility/URLParser/http_parser.c similarity index 100% rename from src/utility/http_parser.c rename to src/utility/URLParser/http_parser.c diff --git a/src/utility/http_parser.h b/src/utility/URLParser/http_parser.h similarity index 100% rename from src/utility/http_parser.h rename to src/utility/URLParser/http_parser.h From 9101851c3b9c7b19434b822ddbc627fa3eb8dc12 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Mar 2024 14:48:22 +0100 Subject: [PATCH 120/125] Bump geekyeggo/delete-artifact from 4 to 5 (#175) Bumps [geekyeggo/delete-artifact](https://github.com/geekyeggo/delete-artifact) from 4 to 5. - [Release notes](https://github.com/geekyeggo/delete-artifact/releases) - [Changelog](https://github.com/GeekyEggo/delete-artifact/blob/main/CHANGELOG.md) - [Commits](https://github.com/geekyeggo/delete-artifact/compare/v4...v5) --- updated-dependencies: - dependency-name: geekyeggo/delete-artifact dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/sync-labels.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sync-labels.yml b/.github/workflows/sync-labels.yml index 47ac50a..53a9f54 100644 --- a/.github/workflows/sync-labels.yml +++ b/.github/workflows/sync-labels.yml @@ -114,7 +114,7 @@ jobs: path: ${{ env.CONFIGURATIONS_FOLDER }} - name: Remove unneeded artifact - uses: geekyeggo/delete-artifact@v4 + uses: geekyeggo/delete-artifact@v5 with: name: ${{ env.CONFIGURATIONS_ARTIFACT }} From 6f2659de4c0523f0a5fa4bc1154d1e5dc1b01546 Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni <4046444+andreagilardoni@users.noreply.github.com> Date: Tue, 26 Mar 2024 10:25:16 +0100 Subject: [PATCH 121/125] Release v0.6.0 --- library.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library.properties b/library.properties index 3b7307e..e33fe6e 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=ArduinoHttpClient -version=0.5.0 +version=0.6.0 author=Arduino maintainer=Arduino sentence=[EXPERIMENTAL] Easily interact with web servers from Arduino, using HTTP and WebSockets. From 1a3fb989a5d92575a05b6ac3627de554723709ac Mon Sep 17 00:00:00 2001 From: Martino Facchin Date: Wed, 8 May 2024 17:25:48 +0200 Subject: [PATCH 122/125] Make sure iContentLength doesn't wrap around due to malformed packets --- src/HttpClient.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/HttpClient.cpp b/src/HttpClient.cpp index c0f5c27..31909d9 100644 --- a/src/HttpClient.cpp +++ b/src/HttpClient.cpp @@ -819,7 +819,11 @@ int HttpClient::readHeader() case eReadingContentLength: if (isdigit(c)) { - iContentLength = iContentLength*10 + (c - '0'); + long _iContentLength = iContentLength*10 + (c - '0'); + // Only apply if the value didn't wrap around + if (_iContentLength > iContentLength) { + iContentLength = _iContentLength; + } } else { From 657e2b08d984168f95ad0a552de56d3f0d27a83b Mon Sep 17 00:00:00 2001 From: Martino Facchin Date: Thu, 25 Jul 2024 12:04:37 +0200 Subject: [PATCH 123/125] Release 0.6.1 --- library.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library.properties b/library.properties index e33fe6e..c93d0de 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=ArduinoHttpClient -version=0.6.0 +version=0.6.1 author=Arduino maintainer=Arduino sentence=[EXPERIMENTAL] Easily interact with web servers from Arduino, using HTTP and WebSockets. From 54627fcb18d92e0b2b8499a73e2c65f4ded9d6d6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 8 Oct 2024 17:20:59 +0000 Subject: [PATCH 124/125] Bump arduino/arduino-lint-action from 1 to 2 Bumps [arduino/arduino-lint-action](https://github.com/arduino/arduino-lint-action) from 1 to 2. - [Release notes](https://github.com/arduino/arduino-lint-action/releases) - [Commits](https://github.com/arduino/arduino-lint-action/compare/v1...v2) --- updated-dependencies: - dependency-name: arduino/arduino-lint-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/check-arduino.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check-arduino.yml b/.github/workflows/check-arduino.yml index adb330f..e818685 100644 --- a/.github/workflows/check-arduino.yml +++ b/.github/workflows/check-arduino.yml @@ -19,7 +19,7 @@ jobs: uses: actions/checkout@v4 - name: Arduino Lint - uses: arduino/arduino-lint-action@v1 + uses: arduino/arduino-lint-action@v2 with: compliance: specification library-manager: update From 53816d037091eecf2c610437915549f786540986 Mon Sep 17 00:00:00 2001 From: per1234 Date: Fri, 9 May 2025 02:38:38 -0700 Subject: [PATCH 125/125] Add dedicated license file Standardization in license documentation is important because, in addition to making it easy for humans to find this vital information, it allows machines to automate the process of license type determination, which is useful both for discovering suitable open source projects as well as checking open source license compliance. Previously, the open source license of this project was not documented in a dedicated license file. Due to this, even though the project is licensed under a standardized open source license, the open license type could not be identified with 100% confidence by machines (e.g., the Licensee Gem used by the GitHub website), which meant identification could only be made by a human carefully evaluating the license text. The project's license is hereby documented in a standardized license file. The result is that the project's open source license type can now be automatically identified. This does not result in any change to the project licensing. --- LICENSE.txt | 201 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 LICENSE.txt diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License.