From 51a4432ca8e71be202358ceb068f3047bb8ad762 Mon Sep 17 00:00:00 2001 From: copercini Date: Fri, 19 May 2017 05:18:20 -0300 Subject: [PATCH] HTTPClient Port (#347) * Fix possible infinite loop in the example * Remove workaround of sockets always return -76 Remove workaround of sockets always return -76 (because it's fixed on IDF now) Remove delay during handshake (improving stability) * Remove unusable mbedtls_net of context creation * Fix bad destructor * Compatibility with WiFiClient for HTTPClient * Initial port from ESP8266 Changed SHA1 fingerprint by Root CA verification Changed log system * Remove deprecated function --- .../examples/Authorization/Authorization.ino | 83 ++ .../BasicHttpClient/BasicHttpClient.ino | 101 ++ .../ReuseConnection/ReuseConnection.ino | 68 + .../StreamHttpClient/StreamHttpClient.ino | 100 ++ libraries/HTTPClient/library.properties | 9 + libraries/HTTPClient/src/HTTPClient.cpp | 1116 +++++++++++++++++ libraries/HTTPClient/src/HTTPClient.h | 218 ++++ .../WiFiClientSecure/WiFiClientSecure.ino | 91 +- .../WiFiClientSecure/src/WiFiClientSecure.cpp | 1 + .../WiFiClientSecure/src/WiFiClientSecure.h | 2 +- libraries/WiFiClientSecure/src/ssl_client.cpp | 9 +- libraries/WiFiClientSecure/src/ssl_client.h | 1 - 12 files changed, 1745 insertions(+), 54 deletions(-) create mode 100644 libraries/HTTPClient/examples/Authorization/Authorization.ino create mode 100644 libraries/HTTPClient/examples/BasicHttpClient/BasicHttpClient.ino create mode 100644 libraries/HTTPClient/examples/ReuseConnection/ReuseConnection.ino create mode 100644 libraries/HTTPClient/examples/StreamHttpClient/StreamHttpClient.ino create mode 100644 libraries/HTTPClient/library.properties create mode 100644 libraries/HTTPClient/src/HTTPClient.cpp create mode 100644 libraries/HTTPClient/src/HTTPClient.h diff --git a/libraries/HTTPClient/examples/Authorization/Authorization.ino b/libraries/HTTPClient/examples/Authorization/Authorization.ino new file mode 100644 index 00000000..400f93cc --- /dev/null +++ b/libraries/HTTPClient/examples/Authorization/Authorization.ino @@ -0,0 +1,83 @@ +/** + * Authorization.ino + * + * Created on: 09.12.2015 + * + */ + +#include + +#include +#include + +#include + +#define USE_SERIAL Serial + +WiFiMulti wifiMulti; + +void setup() { + + USE_SERIAL.begin(115200); + + USE_SERIAL.println(); + USE_SERIAL.println(); + USE_SERIAL.println(); + + for(uint8_t t = 4; t > 0; t--) { + USE_SERIAL.printf("[SETUP] WAIT %d...\n", t); + USE_SERIAL.flush(); + delay(1000); + } + + wifiMulti.addAP("SSID", "PASSWORD"); + +} + +void loop() { + // wait for WiFi connection + if((wifiMulti.run() == WL_CONNECTED)) { + + HTTPClient http; + + USE_SERIAL.print("[HTTP] begin...\n"); + // configure traged server and url + + + http.begin("http://user:password@192.168.1.12/test.html"); + + /* + // or + http.begin("http://192.168.1.12/test.html"); + http.setAuthorization("user", "password"); + + // or + http.begin("http://192.168.1.12/test.html"); + http.setAuthorization("dXNlcjpwYXN3b3Jk"); + */ + + + USE_SERIAL.print("[HTTP] GET...\n"); + // start connection and send HTTP header + int httpCode = http.GET(); + + // httpCode will be negative on error + if(httpCode > 0) { + // HTTP header has been send and Server response header has been handled + USE_SERIAL.printf("[HTTP] GET... code: %d\n", httpCode); + + // file found at server + if(httpCode == HTTP_CODE_OK) { + String payload = http.getString(); + USE_SERIAL.println(payload); + } + } else { + USE_SERIAL.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str()); + } + + http.end(); + } + + delay(10000); +} + diff --git a/libraries/HTTPClient/examples/BasicHttpClient/BasicHttpClient.ino b/libraries/HTTPClient/examples/BasicHttpClient/BasicHttpClient.ino new file mode 100644 index 00000000..9ee620ab --- /dev/null +++ b/libraries/HTTPClient/examples/BasicHttpClient/BasicHttpClient.ino @@ -0,0 +1,101 @@ +/** + * BasicHTTPClient.ino + * + * Created on: 24.05.2015 + * + */ + +#include + +#include +#include + +#include + +#define USE_SERIAL Serial + +WiFiMulti wifiMulti; + +/* +const char* ca = \ +"-----BEGIN CERTIFICATE-----\n" \ +"MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/\n" \ +"MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT\n" \ +"DkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0Nlow\n" \ +"SjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxIzAhBgNVBAMT\n" \ +"GkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzMIIBIjANBgkqhkiG9w0BAQEFAAOC\n" \ +"AQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7NoYzDq1zUmGSXhvb418XCSL7e4S0EF\n" \ +"q6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf15Gan/PQeGdxyGkOlZHP/uaZ6WA8\n" \ +"SMx+yk13EiSdRxta67nsHjcAHJyse6cF6s5K671B5TaYucv9bTyWaN8jKkKQDIZ0\n" \ +"Z8h/pZq4UmEUEz9l6YKHy9v6Dlb2honzhT+Xhq+w3Brvaw2VFn3EK6BlspkENnWA\n" \ +"a6xK8xuQSXgvopZPKiAlKQTGdMDQMc2PMTiVFrqoM7hD8bEfwzB/onkxEz0tNvjj\n" \ +"/PIzark5McWvxI0NHWQWM6r6hCm21AvA2H3DkwIDAQABo4IBfTCCAXkwEgYDVR0T\n" \ +"AQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwfwYIKwYBBQUHAQEEczBxMDIG\n" \ +"CCsGAQUFBzABhiZodHRwOi8vaXNyZy50cnVzdGlkLm9jc3AuaWRlbnRydXN0LmNv\n" \ +"bTA7BggrBgEFBQcwAoYvaHR0cDovL2FwcHMuaWRlbnRydXN0LmNvbS9yb290cy9k\n" \ +"c3Ryb290Y2F4My5wN2MwHwYDVR0jBBgwFoAUxKexpHsscfrb4UuQdf/EFWCFiRAw\n" \ +"VAYDVR0gBE0wSzAIBgZngQwBAgEwPwYLKwYBBAGC3xMBAQEwMDAuBggrBgEFBQcC\n" \ +"ARYiaHR0cDovL2Nwcy5yb290LXgxLmxldHNlbmNyeXB0Lm9yZzA8BgNVHR8ENTAz\n" \ +"MDGgL6AthitodHRwOi8vY3JsLmlkZW50cnVzdC5jb20vRFNUUk9PVENBWDNDUkwu\n" \ +"Y3JsMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTANBgkqhkiG9w0BAQsF\n" \ +"AAOCAQEA3TPXEfNjWDjdGBX7CVW+dla5cEilaUcne8IkCJLxWh9KEik3JHRRHGJo\n" \ +"uM2VcGfl96S8TihRzZvoroed6ti6WqEBmtzw3Wodatg+VyOeph4EYpr/1wXKtx8/\n" \ +"wApIvJSwtmVi4MFU5aMqrSDE6ea73Mj2tcMyo5jMd6jmeWUHK8so/joWUoHOUgwu\n" \ +"X4Po1QYz+3dszkDqMp4fklxBwXRsW10KXzPMTZ+sOPAveyxindmjkW8lGy+QsRlG\n" \ +"PfZ+G6Z6h7mjem0Y+iWlkYcV4PIWL1iwBi8saCbGS5jN2p8M+X+Q7UNKEkROb3N6\n" \ +"KOqkqm57TH2H3eDJAkSnh6/DNFu0Qg==\n" \ +"-----END CERTIFICATE-----\n"; +*/ + +void setup() { + + USE_SERIAL.begin(115200); + + USE_SERIAL.println(); + USE_SERIAL.println(); + USE_SERIAL.println(); + + for(uint8_t t = 4; t > 0; t--) { + USE_SERIAL.printf("[SETUP] WAIT %d...\n", t); + USE_SERIAL.flush(); + delay(1000); + } + + wifiMulti.addAP("SSID", "PASSWORD"); + +} + +void loop() { + // wait for WiFi connection + if((wifiMulti.run() == WL_CONNECTED)) { + + HTTPClient http; + + USE_SERIAL.print("[HTTP] begin...\n"); + // configure traged server and url + //http.begin("https://www.howsmyssl.com/a/check", ca); //HTTPS + http.begin("http://example.com/index.html"); //HTTP + + USE_SERIAL.print("[HTTP] GET...\n"); + // start connection and send HTTP header + int httpCode = http.GET(); + + // httpCode will be negative on error + if(httpCode > 0) { + // HTTP header has been send and Server response header has been handled + USE_SERIAL.printf("[HTTP] GET... code: %d\n", httpCode); + + // file found at server + if(httpCode == HTTP_CODE_OK) { + String payload = http.getString(); + USE_SERIAL.println(payload); + } + } else { + USE_SERIAL.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str()); + } + + http.end(); + } + + delay(5000); +} diff --git a/libraries/HTTPClient/examples/ReuseConnection/ReuseConnection.ino b/libraries/HTTPClient/examples/ReuseConnection/ReuseConnection.ino new file mode 100644 index 00000000..d3bcefbe --- /dev/null +++ b/libraries/HTTPClient/examples/ReuseConnection/ReuseConnection.ino @@ -0,0 +1,68 @@ +/** + * reuseConnection.ino + * + * Created on: 22.11.2015 + * + */ + + +#include + +#include +#include + +#include + +#define USE_SERIAL Serial + +WiFiMulti wifiMulti; + +HTTPClient http; + +void setup() { + + USE_SERIAL.begin(115200); + + USE_SERIAL.println(); + USE_SERIAL.println(); + USE_SERIAL.println(); + + for(uint8_t t = 4; t > 0; t--) { + USE_SERIAL.printf("[SETUP] WAIT %d...\n", t); + USE_SERIAL.flush(); + delay(1000); + } + + wifiMulti.addAP("SSID", "PASSWORD"); + + // allow reuse (if server supports it) + http.setReuse(true); +} + +void loop() { + // wait for WiFi connection + if((wifiMulti.run() == WL_CONNECTED)) { + + http.begin("http://192.168.1.12/test.html"); + //http.begin("192.168.1.12", 80, "/test.html"); + + int httpCode = http.GET(); + if(httpCode > 0) { + USE_SERIAL.printf("[HTTP] GET... code: %d\n", httpCode); + + // file found at server + if(httpCode == HTTP_CODE_OK) { + http.writeToStream(&USE_SERIAL); + } + } else { + USE_SERIAL.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str()); + } + + http.end(); + } + + delay(1000); +} + + + diff --git a/libraries/HTTPClient/examples/StreamHttpClient/StreamHttpClient.ino b/libraries/HTTPClient/examples/StreamHttpClient/StreamHttpClient.ino new file mode 100644 index 00000000..6ea07f66 --- /dev/null +++ b/libraries/HTTPClient/examples/StreamHttpClient/StreamHttpClient.ino @@ -0,0 +1,100 @@ +/** + * StreamHTTPClient.ino + * + * Created on: 24.05.2015 + * + */ + +#include + +#include +#include + +#include + +#define USE_SERIAL Serial + +WiFiMulti wifiMulti; + +void setup() { + + USE_SERIAL.begin(115200); + + USE_SERIAL.println(); + USE_SERIAL.println(); + USE_SERIAL.println(); + + for(uint8_t t = 4; t > 0; t--) { + USE_SERIAL.printf("[SETUP] WAIT %d...\n", t); + USE_SERIAL.flush(); + delay(1000); + } + + wifiMulti.addAP("SSID", "PASSWORD"); + +} + +void loop() { + // wait for WiFi connection + if((wifiMulti.run() == WL_CONNECTED)) { + + HTTPClient http; + + USE_SERIAL.print("[HTTP] begin...\n"); + + // configure server and url + http.begin("http://192.168.1.12/test.html"); + //http.begin("192.168.1.12", 80, "/test.html"); + + USE_SERIAL.print("[HTTP] GET...\n"); + // start connection and send HTTP header + int httpCode = http.GET(); + if(httpCode > 0) { + // HTTP header has been send and Server response header has been handled + USE_SERIAL.printf("[HTTP] GET... code: %d\n", httpCode); + + // file found at server + if(httpCode == HTTP_CODE_OK) { + + // get lenght of document (is -1 when Server sends no Content-Length header) + int len = http.getSize(); + + // create buffer for read + uint8_t buff[128] = { 0 }; + + // get tcp stream + WiFiClient * stream = http.getStreamPtr(); + + // read all data from server + while(http.connected() && (len > 0 || len == -1)) { + // get available data size + size_t size = stream->available(); + + if(size) { + // read up to 128 byte + int c = stream->readBytes(buff, ((size > sizeof(buff)) ? sizeof(buff) : size)); + + // write it to Serial + USE_SERIAL.write(buff, c); + + if(len > 0) { + len -= c; + } + } + delay(1); + } + + USE_SERIAL.println(); + USE_SERIAL.print("[HTTP] connection closed or file end.\n"); + + } + } else { + USE_SERIAL.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str()); + } + + http.end(); + } + + delay(10000); +} + diff --git a/libraries/HTTPClient/library.properties b/libraries/HTTPClient/library.properties new file mode 100644 index 00000000..2be999fe --- /dev/null +++ b/libraries/HTTPClient/library.properties @@ -0,0 +1,9 @@ +name=HTTPClient +version=1.1 +author=Markus Sattler +maintainer=Markus Sattler +sentence=http Client for ESP32 +paragraph= +category=Communication +url=https://github.com/Links2004/Arduino/tree/libraries/ESP8266HTTPClient +architectures=esp32 diff --git a/libraries/HTTPClient/src/HTTPClient.cpp b/libraries/HTTPClient/src/HTTPClient.cpp new file mode 100644 index 00000000..f469a093 --- /dev/null +++ b/libraries/HTTPClient/src/HTTPClient.cpp @@ -0,0 +1,1116 @@ +/** + * HTTPClient.cpp + * + * Created on: 02.11.2015 + * + * Copyright (c) 2015 Markus Sattler. All rights reserved. + * This file is part of the HTTPClient for Arduino. + * Port to ESP32 by Evandro Luis Copercini (2017), + * changed fingerprints to CA verification. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include +#include +#include +#include +#include +#include + +#include "HTTPClient.h" + +class TransportTraits +{ +public: + virtual ~TransportTraits() + { + } + + virtual std::unique_ptr create() + { + return std::unique_ptr(new WiFiClient()); + } + + virtual bool verify(WiFiClient& client, const char* host) + { + return true; + } +}; + +class TLSTraits : public TransportTraits +{ +public: + TLSTraits(const char* CAcert) : + _cacert(CAcert) + { + } + + std::unique_ptr create() override + { + return std::unique_ptr(new WiFiClientSecure()); + } + + bool verify(WiFiClient& client, const char* host) override + { + WiFiClientSecure& wcs = static_cast(client); + wcs.setCACert(_cacert); + return true; + } + +protected: + const char* _cacert; +}; + +/** + * constructor + */ +HTTPClient::HTTPClient() +{ +} + +/** + * destructor + */ +HTTPClient::~HTTPClient() +{ + if(_tcp) { + _tcp->stop(); + } + if(_currentHeaders) { + delete[] _currentHeaders; + } +} + +void HTTPClient::clear() +{ + _returnCode = 0; + _size = -1; + _headers = ""; +} + + +bool HTTPClient::begin(String url, const char* CAcert) +{ + _transportTraits.reset(nullptr); + _port = 443; + if (strlen(CAcert) == 0) { + return false; + } + if (!beginInternal(url, "https")) { + return false; + } + _transportTraits = TransportTraitsPtr(new TLSTraits(CAcert)); + //log_d("[HTTP-Client][begin] CAcert: %s", CAcert.c_str()); + return true; +} + +/** + * parsing the url for all needed parameters + * @param url String + */ +bool HTTPClient::begin(String url) +{ + _transportTraits.reset(nullptr); + _port = 80; + if (!beginInternal(url, "http")) { + return false; + } + _transportTraits = TransportTraitsPtr(new TransportTraits()); + return true; +} + +bool HTTPClient::beginInternal(String url, const char* expectedProtocol) +{ + log_d("[HTTP-Client][begin] url: %s", url.c_str()); + bool hasPort = false; + clear(); + + // check for : (http: or https: + int index = url.indexOf(':'); + if(index < 0) { + log_d("[HTTP-Client][begin] failed to parse protocol"); + return false; + } + + _protocol = url.substring(0, index); + url.remove(0, (index + 3)); // remove http:// or https:// + + index = url.indexOf('/'); + String host = url.substring(0, index); + url.remove(0, index); // remove host part + + // get Authorization + index = host.indexOf('@'); + if(index >= 0) { + // auth info + String auth = host.substring(0, index); + host.remove(0, index + 1); // remove auth part including @ + _base64Authorization = base64::encode(auth); + } + + // get port + index = host.indexOf(':'); + if(index >= 0) { + _host = host.substring(0, index); // hostname + host.remove(0, (index + 1)); // remove hostname + : + _port = host.toInt(); // get port + } else { + _host = host; + } + _uri = url; + if (_protocol != expectedProtocol) { + log_d("[HTTP-Client][begin] unexpected protocol: %s, expected %s", _protocol.c_str(), expectedProtocol); + return false; + } + log_d("[HTTP-Client][begin] host: %s port: %d url: %s", _host.c_str(), _port, _uri.c_str()); + return true; +} + +bool HTTPClient::begin(String host, uint16_t port, String uri) +{ + clear(); + _host = host; + _port = port; + _uri = uri; + _transportTraits = TransportTraitsPtr(new TransportTraits()); + log_d("[HTTP-Client][begin] host: %s port: %d uri: %s", host.c_str(), port, uri.c_str()); + return true; +} + +bool HTTPClient::begin(String host, uint16_t port, String uri, const char* CAcert) +{ + clear(); + _host = host; + _port = port; + _uri = uri; + + if (strlen(CAcert) == 0) { + return false; + } + _transportTraits = TransportTraitsPtr(new TLSTraits(CAcert)); + //log_d("[HTTP-Client][begin] host: %s port: %d url: %s httpsFingerprint: %s", host.c_str(), port, uri.c_str(), httpsFingerprint.c_str()); + return true; +} + +/** + * end + * called after the payload is handled + */ +void HTTPClient::end(void) +{ + if(connected()) { + if(_tcp->available() > 0) { + log_d("[HTTP-Client][end] still data in buffer (%d), clean up.", _tcp->available()); + while(_tcp->available() > 0) { + _tcp->read(); + } + } + if(_reuse && _canReuse) { + log_d("[HTTP-Client][end] tcp keep open for reuse"); + } else { + log_d("[HTTP-Client][end] tcp stop"); + _tcp->stop(); + } + } else { + log_d("[HTTP-Client][end] tcp is closed"); + } +} + +/** + * connected + * @return connected status + */ +bool HTTPClient::connected() +{ + if(_tcp) { + return (_tcp->connected() || (_tcp->available() > 0)); + } + return false; +} + +/** + * try to reuse the connection to the server + * keep-alive + * @param reuse bool + */ +void HTTPClient::setReuse(bool reuse) +{ + _reuse = reuse; +} + +/** + * set User Agent + * @param userAgent const char * + */ +void HTTPClient::setUserAgent(const String& userAgent) +{ + _userAgent = userAgent; +} + +/** + * set the Authorizatio for the http request + * @param user const char * + * @param password const char * + */ +void HTTPClient::setAuthorization(const char * user, const char * password) +{ + if(user && password) { + String auth = user; + auth += ":"; + auth += password; + _base64Authorization = base64::encode(auth); + } +} + +/** + * set the Authorizatio for the http request + * @param auth const char * base64 + */ +void HTTPClient::setAuthorization(const char * auth) +{ + if(auth) { + _base64Authorization = auth; + } +} + +/** + * set the timeout for the TCP connection + * @param timeout unsigned int + */ +void HTTPClient::setTimeout(uint16_t timeout) +{ + _tcpTimeout = timeout; + if(connected()) { + _tcp->setTimeout(timeout); + } +} + +/** + * use HTTP1.0 + * @param timeout + */ +void HTTPClient::useHTTP10(bool useHTTP10) +{ + _useHTTP10 = useHTTP10; +} + +/** + * send a GET request + * @return http code + */ +int HTTPClient::GET() +{ + return sendRequest("GET"); +} + +/** + * sends a post request to the server + * @param payload uint8_t * + * @param size size_t + * @return http code + */ +int HTTPClient::POST(uint8_t * payload, size_t size) +{ + return sendRequest("POST", payload, size); +} + +int HTTPClient::POST(String payload) +{ + return POST((uint8_t *) payload.c_str(), payload.length()); +} + +/** + * sends a put request to the server + * @param payload uint8_t * + * @param size size_t + * @return http code + */ +int HTTPClient::PUT(uint8_t * payload, size_t size) { + return sendRequest("PUT", payload, size); +} + +int HTTPClient::PUT(String payload) { + return PUT((uint8_t *) payload.c_str(), payload.length()); +} + +/** + * sendRequest + * @param type const char * "GET", "POST", .... + * @param payload String data for the message body + * @return + */ +int HTTPClient::sendRequest(const char * type, String payload) +{ + return sendRequest(type, (uint8_t *) payload.c_str(), payload.length()); +} + +/** + * sendRequest + * @param type const char * "GET", "POST", .... + * @param payload uint8_t * data for the message body if null not send + * @param size size_t size for the message body if 0 not send + * @return -1 if no info or > 0 when Content-Length is set by server + */ +int HTTPClient::sendRequest(const char * type, uint8_t * payload, size_t size) +{ + // connect to server + if(!connect()) { + return returnError(HTTPC_ERROR_CONNECTION_REFUSED); + } + + if(payload && size > 0) { + addHeader(F("Content-Length"), String(size)); + } + + // send Header + if(!sendHeader(type)) { + return returnError(HTTPC_ERROR_SEND_HEADER_FAILED); + } + + // send Payload if needed + if(payload && size > 0) { + if(_tcp->write(&payload[0], size) != size) { + return returnError(HTTPC_ERROR_SEND_PAYLOAD_FAILED); + } + } + + // handle Server Response (Header) + return returnError(handleHeaderResponse()); +} + +/** + * sendRequest + * @param type const char * "GET", "POST", .... + * @param stream Stream * data stream for the message body + * @param size size_t size for the message body if 0 not Content-Length is send + * @return -1 if no info or > 0 when Content-Length is set by server + */ +int HTTPClient::sendRequest(const char * type, Stream * stream, size_t size) +{ + + if(!stream) { + return returnError(HTTPC_ERROR_NO_STREAM); + } + + // connect to server + if(!connect()) { + return returnError(HTTPC_ERROR_CONNECTION_REFUSED); + } + + if(size > 0) { + addHeader("Content-Length", String(size)); + } + + // send Header + if(!sendHeader(type)) { + return returnError(HTTPC_ERROR_SEND_HEADER_FAILED); + } + + int buff_size = HTTP_TCP_BUFFER_SIZE; + + int len = size; + int bytesWritten = 0; + + if(len == 0) { + len = -1; + } + + // if possible create smaller buffer then HTTP_TCP_BUFFER_SIZE + if((len > 0) && (len < HTTP_TCP_BUFFER_SIZE)) { + buff_size = len; + } + + // create buffer for read + uint8_t * buff = (uint8_t *) malloc(buff_size); + + if(buff) { + // read all data from stream and send it to server + while(connected() && (stream->available() > -1) && (len > 0 || len == -1)) { + + // get available data size + int sizeAvailable = stream->available(); + + if(sizeAvailable) { + + int readBytes = sizeAvailable; + + // read only the asked bytes + if(len > 0 && readBytes > len) { + readBytes = len; + } + + // not read more the buffer can handle + if(readBytes > buff_size) { + readBytes = buff_size; + } + + // read data + int bytesRead = stream->readBytes(buff, readBytes); + + // write it to Stream + int bytesWrite = _tcp->write((const uint8_t *) buff, bytesRead); + bytesWritten += bytesWrite; + + // are all Bytes a writen to stream ? + if(bytesWrite != bytesRead) { + log_d("[HTTP-Client][sendRequest] short write, asked for %d but got %d retry...", bytesRead, bytesWrite); + + // check for write error + if(_tcp->getWriteError()) { + log_d("[HTTP-Client][sendRequest] stream write error %d", _tcp->getWriteError()); + + //reset write error for retry + _tcp->clearWriteError(); + } + + // some time for the stream + delay(1); + + int leftBytes = (readBytes - bytesWrite); + + // retry to send the missed bytes + bytesWrite = _tcp->write((const uint8_t *) (buff + bytesWrite), leftBytes); + bytesWritten += bytesWrite; + + if(bytesWrite != leftBytes) { + // failed again + log_d("[HTTP-Client][sendRequest] short write, asked for %d but got %d failed.", leftBytes, bytesWrite); + free(buff); + return returnError(HTTPC_ERROR_SEND_PAYLOAD_FAILED); + } + } + + // check for write error + if(_tcp->getWriteError()) { + log_d("[HTTP-Client][sendRequest] stream write error %d", _tcp->getWriteError()); + free(buff); + return returnError(HTTPC_ERROR_SEND_PAYLOAD_FAILED); + } + + // count bytes to read left + if(len > 0) { + len -= readBytes; + } + + delay(0); + } else { + delay(1); + } + } + + free(buff); + + if(size && (int) size != bytesWritten) { + log_d("[HTTP-Client][sendRequest] Stream payload bytesWritten %d and size %d mismatch!.", bytesWritten, size); + log_d("[HTTP-Client][sendRequest] ERROR SEND PAYLOAD FAILED!"); + return returnError(HTTPC_ERROR_SEND_PAYLOAD_FAILED); + } else { + log_d("[HTTP-Client][sendRequest] Stream payload written: %d", bytesWritten); + } + + } else { + log_d("[HTTP-Client][sendRequest] too less ram! need %d", HTTP_TCP_BUFFER_SIZE); + return returnError(HTTPC_ERROR_TOO_LESS_RAM); + } + + // handle Server Response (Header) + return returnError(handleHeaderResponse()); +} + +/** + * size of message body / payload + * @return -1 if no info or > 0 when Content-Length is set by server + */ +int HTTPClient::getSize(void) +{ + return _size; +} + +/** + * returns the stream of the tcp connection + * @return WiFiClient + */ +WiFiClient& HTTPClient::getStream(void) +{ + if(connected()) { + return *_tcp; + } + + log_d("[HTTP-Client] getStream: not connected"); + static WiFiClient empty; + return empty; +} + +/** + * returns the stream of the tcp connection + * @return WiFiClient * + */ +WiFiClient* HTTPClient::getStreamPtr(void) +{ + if(connected()) { + return _tcp.get(); + } + + log_d("[HTTP-Client] getStreamPtr: not connected"); + return nullptr; +} + +/** + * write all message body / payload to Stream + * @param stream Stream * + * @return bytes written ( negative values are error codes ) + */ +int HTTPClient::writeToStream(Stream * stream) +{ + + if(!stream) { + return returnError(HTTPC_ERROR_NO_STREAM); + } + + if(!connected()) { + return returnError(HTTPC_ERROR_NOT_CONNECTED); + } + + // get length of document (is -1 when Server sends no Content-Length header) + int len = _size; + int ret = 0; + + if(_transferEncoding == HTTPC_TE_IDENTITY) { + ret = writeToStreamDataBlock(stream, len); + + // have we an error? + if(ret < 0) { + return returnError(ret); + } + } else if(_transferEncoding == HTTPC_TE_CHUNKED) { + int size = 0; + while(1) { + if(!connected()) { + return returnError(HTTPC_ERROR_CONNECTION_LOST); + } + String chunkHeader = _tcp->readStringUntil('\n'); + + if(chunkHeader.length() <= 0) { + return returnError(HTTPC_ERROR_READ_TIMEOUT); + } + + chunkHeader.trim(); // remove \r + + // read size of chunk + len = (uint32_t) strtol((const char *) chunkHeader.c_str(), NULL, 16); + size += len; + log_d("[HTTP-Client] read chunk len: %d", len); + + // data left? + if(len > 0) { + int r = writeToStreamDataBlock(stream, len); + if(r < 0) { + // error in writeToStreamDataBlock + return returnError(r); + } + ret += r; + } else { + + // if no length Header use global chunk size + if(_size <= 0) { + _size = size; + } + + // check if we have write all data out + if(ret != _size) { + return returnError(HTTPC_ERROR_STREAM_WRITE); + } + break; + } + + // read trailing \r\n at the end of the chunk + char buf[2]; + auto trailing_seq_len = _tcp->readBytes((uint8_t*)buf, 2); + if (trailing_seq_len != 2 || buf[0] != '\r' || buf[1] != '\n') { + return returnError(HTTPC_ERROR_READ_TIMEOUT); + } + + delay(0); + } + } else { + return returnError(HTTPC_ERROR_ENCODING); + } + + end(); + return ret; +} + +/** + * return all payload as String (may need lot of ram or trigger out of memory!) + * @return String + */ +String HTTPClient::getString(void) +{ + StreamString sstring; + + if(_size) { + // try to reserve needed memmory + if(!sstring.reserve((_size + 1))) { + log_d("[HTTP-Client][getString] not enough memory to reserve a string! need: %d", (_size + 1)); + return ""; + } + } + + writeToStream(&sstring); + return sstring; +} + +/** + * converts error code to String + * @param error int + * @return String + */ +String HTTPClient::errorToString(int error) +{ + switch(error) { + case HTTPC_ERROR_CONNECTION_REFUSED: + return F("connection refused"); + case HTTPC_ERROR_SEND_HEADER_FAILED: + return F("send header failed"); + case HTTPC_ERROR_SEND_PAYLOAD_FAILED: + return F("send payload failed"); + case HTTPC_ERROR_NOT_CONNECTED: + return F("not connected"); + case HTTPC_ERROR_CONNECTION_LOST: + return F("connection lost"); + case HTTPC_ERROR_NO_STREAM: + return F("no stream"); + case HTTPC_ERROR_NO_HTTP_SERVER: + return F("no HTTP server"); + case HTTPC_ERROR_TOO_LESS_RAM: + return F("too less ram"); + case HTTPC_ERROR_ENCODING: + return F("Transfer-Encoding not supported"); + case HTTPC_ERROR_STREAM_WRITE: + return F("Stream write error"); + case HTTPC_ERROR_READ_TIMEOUT: + return F("read Timeout"); + default: + return String(); + } +} + +/** + * adds Header to the request + * @param name + * @param value + * @param first + */ +void HTTPClient::addHeader(const String& name, const String& value, bool first, bool replace) +{ + // not allow set of Header handled by code + if(!name.equalsIgnoreCase(F("Connection")) && + !name.equalsIgnoreCase(F("User-Agent")) && + !name.equalsIgnoreCase(F("Host")) && + !(name.equalsIgnoreCase(F("Authorization")) && _base64Authorization.length())){ + + String headerLine = name; + headerLine += ": "; + + if (replace) { + int headerStart = _headers.indexOf(headerLine); + if (headerStart != -1) { + int headerEnd = _headers.indexOf('\n', headerStart); + _headers = _headers.substring(0, headerStart) + _headers.substring(headerEnd + 1); + } + } + + headerLine += value; + headerLine += "\r\n"; + if(first) { + _headers = headerLine + _headers; + } else { + _headers += headerLine; + } + } + +} + +void HTTPClient::collectHeaders(const char* headerKeys[], const size_t headerKeysCount) +{ + _headerKeysCount = headerKeysCount; + if(_currentHeaders) { + delete[] _currentHeaders; + } + _currentHeaders = new RequestArgument[_headerKeysCount]; + for(size_t i = 0; i < _headerKeysCount; i++) { + _currentHeaders[i].key = headerKeys[i]; + } +} + +String HTTPClient::header(const char* name) +{ + for(size_t i = 0; i < _headerKeysCount; ++i) { + if(_currentHeaders[i].key == name) { + return _currentHeaders[i].value; + } + } + return String(); +} + +String HTTPClient::header(size_t i) +{ + if(i < _headerKeysCount) { + return _currentHeaders[i].value; + } + return String(); +} + +String HTTPClient::headerName(size_t i) +{ + if(i < _headerKeysCount) { + return _currentHeaders[i].key; + } + return String(); +} + +int HTTPClient::headers() +{ + return _headerKeysCount; +} + +bool HTTPClient::hasHeader(const char* name) +{ + for(size_t i = 0; i < _headerKeysCount; ++i) { + if((_currentHeaders[i].key == name) && (_currentHeaders[i].value.length() > 0)) { + return true; + } + } + return false; +} + +/** + * init TCP connection and handle ssl verify if needed + * @return true if connection is ok + */ +bool HTTPClient::connect(void) +{ + + if(connected()) { + log_d("[HTTP-Client] connect. already connected, try reuse!"); + while(_tcp->available() > 0) { + _tcp->read(); + } + return true; + } + + if (!_transportTraits) { + log_d("[HTTP-Client] connect: HTTPClient::begin was not called or returned error"); + return false; + } + + _tcp = _transportTraits->create(); + + + if (!_transportTraits->verify(*_tcp, _host.c_str())) { + log_d("[HTTP-Client] transport level verify failed"); + _tcp->stop(); + return false; + } + + if(!_tcp->connect(_host.c_str(), _port)) { + log_d("[HTTP-Client] failed connect to %s:%u", _host.c_str(), _port); + return false; + } + + log_d("[HTTP-Client] connected to %s:%u", _host.c_str(), _port); + + // set Timeout for readBytesUntil and readStringUntil + _tcp->setTimeout(_tcpTimeout); +/* +#ifdef ESP8266 + _tcp->setNoDelay(true); +#endif + */ + return connected(); +} + +/** + * sends HTTP request header + * @param type (GET, POST, ...) + * @return status + */ +bool HTTPClient::sendHeader(const char * type) +{ + if(!connected()) { + return false; + } + + String header = String(type) + " " + _uri + F(" HTTP/1."); + + if(_useHTTP10) { + header += "0"; + } else { + header += "1"; + } + + header += String(F("\r\nHost: ")) + _host; + if (_port != 80 && _port != 443) + { + header += ':'; + header += String(_port); + } + header += String(F("\r\nUser-Agent: ")) + _userAgent + + F("\r\nConnection: "); + + if(_reuse) { + header += F("keep-alive"); + } else { + header += F("close"); + } + header += "\r\n"; + + if(!_useHTTP10) { + header += F("Accept-Encoding: identity;q=1,chunked;q=0.1,*;q=0\r\n"); + } + + if(_base64Authorization.length()) { + _base64Authorization.replace("\n", ""); + header += F("Authorization: Basic "); + header += _base64Authorization; + header += "\r\n"; + } + + header += _headers + "\r\n"; + + return (_tcp->write((const uint8_t *) header.c_str(), header.length()) == header.length()); +} + +/** + * reads the response from the server + * @return int http code + */ +int HTTPClient::handleHeaderResponse() +{ + + if(!connected()) { + return HTTPC_ERROR_NOT_CONNECTED; + } + + String transferEncoding; + _returnCode = -1; + _size = -1; + _transferEncoding = HTTPC_TE_IDENTITY; + unsigned long lastDataTime = millis(); + + while(connected()) { + size_t len = _tcp->available(); + if(len > 0) { + String headerLine = _tcp->readStringUntil('\n'); + headerLine.trim(); // remove \r + + lastDataTime = millis(); + + log_d("[HTTP-Client][handleHeaderResponse] RX: '%s'", headerLine.c_str()); + + if(headerLine.startsWith("HTTP/1.")) { + _returnCode = headerLine.substring(9, headerLine.indexOf(' ', 9)).toInt(); + } else if(headerLine.indexOf(':')) { + String headerName = headerLine.substring(0, headerLine.indexOf(':')); + String headerValue = headerLine.substring(headerLine.indexOf(':') + 1); + headerValue.trim(); + + if(headerName.equalsIgnoreCase("Content-Length")) { + _size = headerValue.toInt(); + } + + if(headerName.equalsIgnoreCase("Connection")) { + _canReuse = headerValue.equalsIgnoreCase("keep-alive"); + } + + if(headerName.equalsIgnoreCase("Transfer-Encoding")) { + transferEncoding = headerValue; + } + + for(size_t i = 0; i < _headerKeysCount; i++) { + if(_currentHeaders[i].key.equalsIgnoreCase(headerName)) { + _currentHeaders[i].value = headerValue; + break; + } + } + } + + if(headerLine == "") { + log_d("[HTTP-Client][handleHeaderResponse] code: %d", _returnCode); + + if(_size > 0) { + log_d("[HTTP-Client][handleHeaderResponse] size: %d", _size); + } + + if(transferEncoding.length() > 0) { + log_d("[HTTP-Client][handleHeaderResponse] Transfer-Encoding: %s", transferEncoding.c_str()); + if(transferEncoding.equalsIgnoreCase("chunked")) { + _transferEncoding = HTTPC_TE_CHUNKED; + } else { + return HTTPC_ERROR_ENCODING; + } + } else { + _transferEncoding = HTTPC_TE_IDENTITY; + } + + if(_returnCode) { + return _returnCode; + } else { + log_d("[HTTP-Client][handleHeaderResponse] Remote host is not an HTTP Server!"); + return HTTPC_ERROR_NO_HTTP_SERVER; + } + } + + } else { + if((millis() - lastDataTime) > _tcpTimeout) { + return HTTPC_ERROR_READ_TIMEOUT; + } + delay(0); + } + } + + return HTTPC_ERROR_CONNECTION_LOST; +} + +/** + * write one Data Block to Stream + * @param stream Stream * + * @param size int + * @return < 0 = error >= 0 = size written + */ +int HTTPClient::writeToStreamDataBlock(Stream * stream, int size) +{ + int buff_size = HTTP_TCP_BUFFER_SIZE; + int len = size; + int bytesWritten = 0; + + // if possible create smaller buffer then HTTP_TCP_BUFFER_SIZE + if((len > 0) && (len < HTTP_TCP_BUFFER_SIZE)) { + buff_size = len; + } + + // create buffer for read + uint8_t * buff = (uint8_t *) malloc(buff_size); + + if(buff) { + // read all data from server + while(connected() && (len > 0 || len == -1)) { + + // get available data size + size_t sizeAvailable = _tcp->available(); + + if(sizeAvailable) { + + int readBytes = sizeAvailable; + + // read only the asked bytes + if(len > 0 && readBytes > len) { + readBytes = len; + } + + // not read more the buffer can handle + if(readBytes > buff_size) { + readBytes = buff_size; + } + + // read data + int bytesRead = _tcp->readBytes(buff, readBytes); + + // write it to Stream + int bytesWrite = stream->write(buff, bytesRead); + bytesWritten += bytesWrite; + + // are all Bytes a writen to stream ? + if(bytesWrite != bytesRead) { + log_d("[HTTP-Client][writeToStream] short write asked for %d but got %d retry...", bytesRead, bytesWrite); + + // check for write error + if(stream->getWriteError()) { + log_d("[HTTP-Client][writeToStreamDataBlock] stream write error %d", stream->getWriteError()); + + //reset write error for retry + stream->clearWriteError(); + } + + // some time for the stream + delay(1); + + int leftBytes = (readBytes - bytesWrite); + + // retry to send the missed bytes + bytesWrite = stream->write((buff + bytesWrite), leftBytes); + bytesWritten += bytesWrite; + + if(bytesWrite != leftBytes) { + // failed again + log_d("[HTTP-Client][writeToStream] short write asked for %d but got %d failed.", leftBytes, bytesWrite); + free(buff); + return HTTPC_ERROR_STREAM_WRITE; + } + } + + // check for write error + if(stream->getWriteError()) { + log_d("[HTTP-Client][writeToStreamDataBlock] stream write error %d", stream->getWriteError()); + free(buff); + return HTTPC_ERROR_STREAM_WRITE; + } + + // count bytes to read left + if(len > 0) { + len -= readBytes; + } + + delay(0); + } else { + delay(1); + } + } + + free(buff); + + log_d("[HTTP-Client][writeToStreamDataBlock] connection closed or file end (written: %d).", bytesWritten); + + if((size > 0) && (size != bytesWritten)) { + log_d("[HTTP-Client][writeToStreamDataBlock] bytesWritten %d and size %d mismatch!.", bytesWritten, size); + return HTTPC_ERROR_STREAM_WRITE; + } + + } else { + log_d("[HTTP-Client][writeToStreamDataBlock] too less ram! need %d", HTTP_TCP_BUFFER_SIZE); + return HTTPC_ERROR_TOO_LESS_RAM; + } + + return bytesWritten; +} + +/** + * called to handle error return, may disconnect the connection if still exists + * @param error + * @return error + */ +int HTTPClient::returnError(int error) +{ + if(error < 0) { + log_d("[HTTP-Client][returnError] error(%d): %s", error, errorToString(error).c_str()); + if(connected()) { + log_d("[HTTP-Client][returnError] tcp stop"); + _tcp->stop(); + } + } + return error; +} diff --git a/libraries/HTTPClient/src/HTTPClient.h b/libraries/HTTPClient/src/HTTPClient.h new file mode 100644 index 00000000..6f27b0f5 --- /dev/null +++ b/libraries/HTTPClient/src/HTTPClient.h @@ -0,0 +1,218 @@ +/** + * HTTPClient.h + * + * Created on: 02.11.2015 + * + * Copyright (c) 2015 Markus Sattler. All rights reserved. + * This file is part of the HTTPClient for Arduino. + * Port to ESP32 by Evandro Luis Copercini (2017), + * changed fingerprints to CA verification. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef HTTPClient_H_ +#define HTTPClient_H_ + +#include +#include +#include + +#define HTTPCLIENT_DEFAULT_TCP_TIMEOUT (5000) + +/// HTTP client errors +#define HTTPC_ERROR_CONNECTION_REFUSED (-1) +#define HTTPC_ERROR_SEND_HEADER_FAILED (-2) +#define HTTPC_ERROR_SEND_PAYLOAD_FAILED (-3) +#define HTTPC_ERROR_NOT_CONNECTED (-4) +#define HTTPC_ERROR_CONNECTION_LOST (-5) +#define HTTPC_ERROR_NO_STREAM (-6) +#define HTTPC_ERROR_NO_HTTP_SERVER (-7) +#define HTTPC_ERROR_TOO_LESS_RAM (-8) +#define HTTPC_ERROR_ENCODING (-9) +#define HTTPC_ERROR_STREAM_WRITE (-10) +#define HTTPC_ERROR_READ_TIMEOUT (-11) + +/// size for the stream handling +#define HTTP_TCP_BUFFER_SIZE (1460) + +/// HTTP codes see RFC7231 +typedef enum { + HTTP_CODE_CONTINUE = 100, + HTTP_CODE_SWITCHING_PROTOCOLS = 101, + HTTP_CODE_PROCESSING = 102, + HTTP_CODE_OK = 200, + HTTP_CODE_CREATED = 201, + HTTP_CODE_ACCEPTED = 202, + HTTP_CODE_NON_AUTHORITATIVE_INFORMATION = 203, + HTTP_CODE_NO_CONTENT = 204, + HTTP_CODE_RESET_CONTENT = 205, + HTTP_CODE_PARTIAL_CONTENT = 206, + HTTP_CODE_MULTI_STATUS = 207, + HTTP_CODE_ALREADY_REPORTED = 208, + HTTP_CODE_IM_USED = 226, + HTTP_CODE_MULTIPLE_CHOICES = 300, + HTTP_CODE_MOVED_PERMANENTLY = 301, + HTTP_CODE_FOUND = 302, + HTTP_CODE_SEE_OTHER = 303, + HTTP_CODE_NOT_MODIFIED = 304, + HTTP_CODE_USE_PROXY = 305, + HTTP_CODE_TEMPORARY_REDIRECT = 307, + HTTP_CODE_PERMANENT_REDIRECT = 308, + HTTP_CODE_BAD_REQUEST = 400, + HTTP_CODE_UNAUTHORIZED = 401, + HTTP_CODE_PAYMENT_REQUIRED = 402, + HTTP_CODE_FORBIDDEN = 403, + HTTP_CODE_NOT_FOUND = 404, + HTTP_CODE_METHOD_NOT_ALLOWED = 405, + HTTP_CODE_NOT_ACCEPTABLE = 406, + HTTP_CODE_PROXY_AUTHENTICATION_REQUIRED = 407, + HTTP_CODE_REQUEST_TIMEOUT = 408, + HTTP_CODE_CONFLICT = 409, + HTTP_CODE_GONE = 410, + HTTP_CODE_LENGTH_REQUIRED = 411, + HTTP_CODE_PRECONDITION_FAILED = 412, + HTTP_CODE_PAYLOAD_TOO_LARGE = 413, + HTTP_CODE_URI_TOO_LONG = 414, + HTTP_CODE_UNSUPPORTED_MEDIA_TYPE = 415, + HTTP_CODE_RANGE_NOT_SATISFIABLE = 416, + HTTP_CODE_EXPECTATION_FAILED = 417, + HTTP_CODE_MISDIRECTED_REQUEST = 421, + HTTP_CODE_UNPROCESSABLE_ENTITY = 422, + HTTP_CODE_LOCKED = 423, + HTTP_CODE_FAILED_DEPENDENCY = 424, + HTTP_CODE_UPGRADE_REQUIRED = 426, + HTTP_CODE_PRECONDITION_REQUIRED = 428, + HTTP_CODE_TOO_MANY_REQUESTS = 429, + HTTP_CODE_REQUEST_HEADER_FIELDS_TOO_LARGE = 431, + HTTP_CODE_INTERNAL_SERVER_ERROR = 500, + HTTP_CODE_NOT_IMPLEMENTED = 501, + HTTP_CODE_BAD_GATEWAY = 502, + HTTP_CODE_SERVICE_UNAVAILABLE = 503, + HTTP_CODE_GATEWAY_TIMEOUT = 504, + HTTP_CODE_HTTP_VERSION_NOT_SUPPORTED = 505, + HTTP_CODE_VARIANT_ALSO_NEGOTIATES = 506, + HTTP_CODE_INSUFFICIENT_STORAGE = 507, + HTTP_CODE_LOOP_DETECTED = 508, + HTTP_CODE_NOT_EXTENDED = 510, + HTTP_CODE_NETWORK_AUTHENTICATION_REQUIRED = 511 +} t_http_codes; + +typedef enum { + HTTPC_TE_IDENTITY, + HTTPC_TE_CHUNKED +} transferEncoding_t; + +class TransportTraits; +typedef std::unique_ptr TransportTraitsPtr; + +class HTTPClient +{ +public: + HTTPClient(); + ~HTTPClient(); + + bool begin(String url); + bool begin(String url, const char* CAcert); + bool begin(String host, uint16_t port, String uri = "/"); + bool begin(String host, uint16_t port, String uri, const char* CAcert); + + void end(void); + + bool connected(void); + + void setReuse(bool reuse); /// keep-alive + void setUserAgent(const String& userAgent); + void setAuthorization(const char * user, const char * password); + void setAuthorization(const char * auth); + void setTimeout(uint16_t timeout); + + void useHTTP10(bool usehttp10 = true); + + /// request handling + int GET(); + int POST(uint8_t * payload, size_t size); + int POST(String payload); + int PUT(uint8_t * payload, size_t size); + int PUT(String payload); + int sendRequest(const char * type, String payload); + int sendRequest(const char * type, uint8_t * payload = NULL, size_t size = 0); + int sendRequest(const char * type, Stream * stream, size_t size = 0); + + void addHeader(const String& name, const String& value, bool first = false, bool replace = true); + + /// Response handling + void collectHeaders(const char* headerKeys[], const size_t headerKeysCount); + String header(const char* name); // get request header value by name + String header(size_t i); // get request header value by number + String headerName(size_t i); // get request header name by number + int headers(); // get header count + bool hasHeader(const char* name); // check if header exists + + + int getSize(void); + + WiFiClient& getStream(void); + WiFiClient* getStreamPtr(void); + int writeToStream(Stream* stream); + String getString(void); + + static String errorToString(int error); + +protected: + struct RequestArgument { + String key; + String value; + }; + + bool beginInternal(String url, const char* expectedProtocol); + void clear(); + int returnError(int error); + bool connect(void); + bool sendHeader(const char * type); + int handleHeaderResponse(); + int writeToStreamDataBlock(Stream * stream, int len); + + + TransportTraitsPtr _transportTraits; + std::unique_ptr _tcp; + + /// request handling + String _host; + uint16_t _port = 0; + bool _reuse = false; + uint16_t _tcpTimeout = HTTPCLIENT_DEFAULT_TCP_TIMEOUT; + bool _useHTTP10 = false; + + String _uri; + String _protocol; + String _headers; + String _userAgent = "ESP32HTTPClient"; + String _base64Authorization; + + /// Response handling + RequestArgument* _currentHeaders = nullptr; + size_t _headerKeysCount = 0; + + int _returnCode = 0; + int _size = -1; + bool _canReuse = false; + transferEncoding_t _transferEncoding = HTTPC_TE_IDENTITY; +}; + + + +#endif /* HTTPClient_H_ */ diff --git a/libraries/WiFiClientSecure/examples/WiFiClientSecure/WiFiClientSecure.ino b/libraries/WiFiClientSecure/examples/WiFiClientSecure/WiFiClientSecure.ino index 25ad5342..030693c2 100644 --- a/libraries/WiFiClientSecure/examples/WiFiClientSecure/WiFiClientSecure.ino +++ b/libraries/WiFiClientSecure/examples/WiFiClientSecure/WiFiClientSecure.ino @@ -13,38 +13,38 @@ const char* password = "your-password"; // your network password const char* server = "www.howsmyssl.com"; // Server URL -// www.howsmyssl.com CA certificate, to verify the server -// change it to your server CA certificate +// www.howsmyssl.com root certificate authority, to verify the server +// change it to your server root CA // SHA1 fingerprint is broken now! -const char* test_ca_cert = \ -"-----BEGIN CERTIFICATE-----\n" \ -"MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/\n" \ -"MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT\n" \ -"DkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0Nlow\n" \ -"SjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxIzAhBgNVBAMT\n" \ -"GkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzMIIBIjANBgkqhkiG9w0BAQEFAAOC\n" \ -"AQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7NoYzDq1zUmGSXhvb418XCSL7e4S0EF\n" \ -"q6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf15Gan/PQeGdxyGkOlZHP/uaZ6WA8\n" \ -"SMx+yk13EiSdRxta67nsHjcAHJyse6cF6s5K671B5TaYucv9bTyWaN8jKkKQDIZ0\n" \ -"Z8h/pZq4UmEUEz9l6YKHy9v6Dlb2honzhT+Xhq+w3Brvaw2VFn3EK6BlspkENnWA\n" \ -"a6xK8xuQSXgvopZPKiAlKQTGdMDQMc2PMTiVFrqoM7hD8bEfwzB/onkxEz0tNvjj\n" \ -"/PIzark5McWvxI0NHWQWM6r6hCm21AvA2H3DkwIDAQABo4IBfTCCAXkwEgYDVR0T\n" \ -"AQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwfwYIKwYBBQUHAQEEczBxMDIG\n" \ -"CCsGAQUFBzABhiZodHRwOi8vaXNyZy50cnVzdGlkLm9jc3AuaWRlbnRydXN0LmNv\n" \ -"bTA7BggrBgEFBQcwAoYvaHR0cDovL2FwcHMuaWRlbnRydXN0LmNvbS9yb290cy9k\n" \ -"c3Ryb290Y2F4My5wN2MwHwYDVR0jBBgwFoAUxKexpHsscfrb4UuQdf/EFWCFiRAw\n" \ -"VAYDVR0gBE0wSzAIBgZngQwBAgEwPwYLKwYBBAGC3xMBAQEwMDAuBggrBgEFBQcC\n" \ -"ARYiaHR0cDovL2Nwcy5yb290LXgxLmxldHNlbmNyeXB0Lm9yZzA8BgNVHR8ENTAz\n" \ -"MDGgL6AthitodHRwOi8vY3JsLmlkZW50cnVzdC5jb20vRFNUUk9PVENBWDNDUkwu\n" \ -"Y3JsMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTANBgkqhkiG9w0BAQsF\n" \ -"AAOCAQEA3TPXEfNjWDjdGBX7CVW+dla5cEilaUcne8IkCJLxWh9KEik3JHRRHGJo\n" \ -"uM2VcGfl96S8TihRzZvoroed6ti6WqEBmtzw3Wodatg+VyOeph4EYpr/1wXKtx8/\n" \ -"wApIvJSwtmVi4MFU5aMqrSDE6ea73Mj2tcMyo5jMd6jmeWUHK8so/joWUoHOUgwu\n" \ -"X4Po1QYz+3dszkDqMp4fklxBwXRsW10KXzPMTZ+sOPAveyxindmjkW8lGy+QsRlG\n" \ -"PfZ+G6Z6h7mjem0Y+iWlkYcV4PIWL1iwBi8saCbGS5jN2p8M+X+Q7UNKEkROb3N6\n" \ -"KOqkqm57TH2H3eDJAkSnh6/DNFu0Qg==\n" \ -"-----END CERTIFICATE-----\n"; +const char* test_root_ca= \ + "-----BEGIN CERTIFICATE-----\n" \ + "MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/\n" \ + "MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT\n" \ + "DkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0Nlow\n" \ + "SjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxIzAhBgNVBAMT\n" \ + "GkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzMIIBIjANBgkqhkiG9w0BAQEFAAOC\n" \ + "AQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7NoYzDq1zUmGSXhvb418XCSL7e4S0EF\n" \ + "q6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf15Gan/PQeGdxyGkOlZHP/uaZ6WA8\n" \ + "SMx+yk13EiSdRxta67nsHjcAHJyse6cF6s5K671B5TaYucv9bTyWaN8jKkKQDIZ0\n" \ + "Z8h/pZq4UmEUEz9l6YKHy9v6Dlb2honzhT+Xhq+w3Brvaw2VFn3EK6BlspkENnWA\n" \ + "a6xK8xuQSXgvopZPKiAlKQTGdMDQMc2PMTiVFrqoM7hD8bEfwzB/onkxEz0tNvjj\n" \ + "/PIzark5McWvxI0NHWQWM6r6hCm21AvA2H3DkwIDAQABo4IBfTCCAXkwEgYDVR0T\n" \ + "AQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwfwYIKwYBBQUHAQEEczBxMDIG\n" \ + "CCsGAQUFBzABhiZodHRwOi8vaXNyZy50cnVzdGlkLm9jc3AuaWRlbnRydXN0LmNv\n" \ + "bTA7BggrBgEFBQcwAoYvaHR0cDovL2FwcHMuaWRlbnRydXN0LmNvbS9yb290cy9k\n" \ + "c3Ryb290Y2F4My5wN2MwHwYDVR0jBBgwFoAUxKexpHsscfrb4UuQdf/EFWCFiRAw\n" \ + "VAYDVR0gBE0wSzAIBgZngQwBAgEwPwYLKwYBBAGC3xMBAQEwMDAuBggrBgEFBQcC\n" \ + "ARYiaHR0cDovL2Nwcy5yb290LXgxLmxldHNlbmNyeXB0Lm9yZzA8BgNVHR8ENTAz\n" \ + "MDGgL6AthitodHRwOi8vY3JsLmlkZW50cnVzdC5jb20vRFNUUk9PVENBWDNDUkwu\n" \ + "Y3JsMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTANBgkqhkiG9w0BAQsF\n" \ + "AAOCAQEA3TPXEfNjWDjdGBX7CVW+dla5cEilaUcne8IkCJLxWh9KEik3JHRRHGJo\n" \ + "uM2VcGfl96S8TihRzZvoroed6ti6WqEBmtzw3Wodatg+VyOeph4EYpr/1wXKtx8/\n" \ + "wApIvJSwtmVi4MFU5aMqrSDE6ea73Mj2tcMyo5jMd6jmeWUHK8so/joWUoHOUgwu\n" \ + "X4Po1QYz+3dszkDqMp4fklxBwXRsW10KXzPMTZ+sOPAveyxindmjkW8lGy+QsRlG\n" \ + "PfZ+G6Z6h7mjem0Y+iWlkYcV4PIWL1iwBi8saCbGS5jN2p8M+X+Q7UNKEkROb3N6\n" \ + "KOqkqm57TH2H3eDJAkSnh6/DNFu0Qg==\n" \ + "-----END CERTIFICATE-----\n"; // You can use x.509 client certificates if you want //const char* test_client_key = ""; //to verify the client @@ -71,14 +71,14 @@ void setup() { Serial.print("Connected to "); Serial.println(ssid); - - client.setCACert(test_ca_cert); - //client.setCertificate(certificateBuff); // for client verification - //client.setPrivateKey(privateKeyBuff); // for client verification + + client.setCACert(test_root_ca); + //client.setCertificate(test_client_key); // for client verification + //client.setPrivateKey(test_client_cert); // for client verification Serial.println("\nStarting connection to server..."); if (!client.connect(server, 443)) - Serial.println("Connection failed!"); + Serial.println("Connection failed!"); else { Serial.println("Connected to server!"); // Make a HTTP request: @@ -87,11 +87,13 @@ void setup() { client.println("Connection: close"); client.println(); - Serial.print("Waiting for response "); //WiFiClientSecure uses a non blocking implementation - while (!client.available()){ - delay(50); // - Serial.print("."); - } + while (client.connected()) { + String line = client.readStringUntil('\n'); + if (line == "\r") { + Serial.println("headers received"); + break; + } + } // if there are incoming bytes available // from the server, read them and print them: while (client.available()) { @@ -99,15 +101,10 @@ void setup() { Serial.write(c); } - // if the server's disconnected, stop the client: - if (!client.connected()) { - Serial.println(); - Serial.println("disconnecting from server."); - client.stop(); - } + client.stop(); } } void loop() { // do nothing -} +} diff --git a/libraries/WiFiClientSecure/src/WiFiClientSecure.cpp b/libraries/WiFiClientSecure/src/WiFiClientSecure.cpp index d215c837..02fa838b 100644 --- a/libraries/WiFiClientSecure/src/WiFiClientSecure.cpp +++ b/libraries/WiFiClientSecure/src/WiFiClientSecure.cpp @@ -64,6 +64,7 @@ WiFiClientSecure::WiFiClientSecure(int sock) WiFiClientSecure::~WiFiClientSecure() { stop(); + delete sslclient; } WiFiClientSecure &WiFiClientSecure::operator=(const WiFiClientSecure &other) diff --git a/libraries/WiFiClientSecure/src/WiFiClientSecure.h b/libraries/WiFiClientSecure/src/WiFiClientSecure.h index f0f0a7bd..102bd49f 100644 --- a/libraries/WiFiClientSecure/src/WiFiClientSecure.h +++ b/libraries/WiFiClientSecure/src/WiFiClientSecure.h @@ -25,7 +25,7 @@ #include #include "ssl_client.h" -class WiFiClientSecure : public Client +class WiFiClientSecure : public WiFiClient { protected: bool _connected; diff --git a/libraries/WiFiClientSecure/src/ssl_client.cpp b/libraries/WiFiClientSecure/src/ssl_client.cpp index 0b7c3fea..09f67ea9 100644 --- a/libraries/WiFiClientSecure/src/ssl_client.cpp +++ b/libraries/WiFiClientSecure/src/ssl_client.cpp @@ -15,6 +15,7 @@ #include #include "ssl_client.h" + const char *pers = "esp32-tls"; static int handle_error(int err) @@ -153,11 +154,9 @@ int start_ssl_client(sslclient_context *ssl_client, uint32_t ipAddress, uint32_t log_i("Performing the SSL/TLS handshake..."); while ((ret = mbedtls_ssl_handshake(&ssl_client->ssl_ctx)) != 0) { - if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE && ret != -76) { //workaround for bug: https://github.com/espressif/esp-idf/issues/434 + if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) { return handle_error(ret); } - delay(10); - vPortYield(); } @@ -224,7 +223,7 @@ int data_to_read(sslclient_context *ssl_client) //log_e("RET: %i",ret); //for low level debug res = mbedtls_ssl_get_bytes_avail(&ssl_client->ssl_ctx); //log_e("RES: %i",res); - if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE && ret < 0 && ret != -76) { + if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE && ret < 0) { return handle_error(ret); } @@ -238,7 +237,7 @@ int send_ssl_data(sslclient_context *ssl_client, const uint8_t *data, uint16_t l int ret = -1; while ((ret = mbedtls_ssl_write(&ssl_client->ssl_ctx, data, len)) <= 0) { - if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE && ret != -76) { + if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) { return handle_error(ret); } } diff --git a/libraries/WiFiClientSecure/src/ssl_client.h b/libraries/WiFiClientSecure/src/ssl_client.h index dfa97048..18b13ce9 100644 --- a/libraries/WiFiClientSecure/src/ssl_client.h +++ b/libraries/WiFiClientSecure/src/ssl_client.h @@ -14,7 +14,6 @@ typedef struct sslclient_context { int socket; - mbedtls_net_context net_ctx; mbedtls_ssl_context ssl_ctx; mbedtls_ssl_config ssl_conf;