Unverified Commit 0d157234 authored by Earle F. Philhower, III's avatar Earle F. Philhower, III Committed by GitHub

Add HTTPClient, ported from the ESP8266 (#784)

Remove the need to have a separate WiFiClient that's destroyed after
the HTTPClient.  Let the object handle its own client, and pass through
any SSL requests.

Also supports the original ::begin methods which need a
WiFiClient(Secure) to be passed in and managed by the app.
parent 6e0d6a27
HTTPClient Library
==================
A simple HTTP requestor that can handle both HTTP and HTTP requests is
included as the ``HTTPClient`` library.
Check the examples for use under HTTP and HTTPS configurations. In general,
for HTTP connections (unsecured and very uncommon on the internet today) simply
passing in a URL and performiung a GET is sufficient to transfer data.
.. code:: cpp
// Error checking is left as an exercise for the reader...
HTTPClient http;
if (http.begin("http://my.server/url")) {
if (http.GET() > 0) {
String data = http.getString();
}
http.end();
}
For HTTPS connections, simply add the appropriate WiFiClientSecure calls
as needed (i.e. ``setInsecure()``, ``setTrustAnchor``, etc.). See the
WiFiClientSecure documentation for more details.
.. code:: cpp
// Error checking is left as an exercise for the reader...
HTTPClient https;
https.setInsecure(); // Use certs, but do not check their authenticity
if (https.begin("https://my.secure.server/url")) {
if (http.GET() > 0) {
String data = http.getString();
}
http.end();
}
Unlike the ESP8266 and ESP32 ``HTTPClient`` implementations it is not necessary
to create a ``WiFiClient`` or ``WiFiClientSecure`` to pass in to the ``HTTPClient``
object.
......@@ -50,6 +50,8 @@ For the latest version, always check https://github.com/earlephilhower/arduino-p
WiFiClientSecure (TLS/SSL/HTTPS) <bearssl-client-secure-class>
WiFiServerSecure (TLS/SSL/HTTPS) <bearssl-server-secure-class>
HTTP/HTTPS Client <httpclient>
Over-the-Air (OTA) Updates <ota>
Ported/Optimized Libraries <libraries>
......
......@@ -2,6 +2,8 @@
# Syntax Coloring Map
#######################################
Arduino KEYWORD3 RESERVED_WORD
#######################################
# Datatypes (KEYWORD1)
#######################################
......
/**
Authorization.ino
Created on: 09.12.2015
*/
#include <Arduino.h>
#include <WiFi.h>
#include <HTTPClient.h>
#ifndef STASSID
#define STASSID "your-ssid"
#define STAPSK "your-password"
#endif
const char *ssid = STASSID;
const char *pass = STAPSK;
WiFiMulti WiFiMulti;
void setup() {
Serial.begin(115200);
// Serial.setDebugOutput(true);
Serial.println();
Serial.println();
Serial.println();
for (uint8_t t = 4; t > 0; t--) {
Serial.printf("[SETUP] WAIT %d...\n", t);
Serial.flush();
delay(1000);
}
WiFi.mode(WIFI_STA);
WiFiMulti.addAP(ssid, pass);
}
void loop() {
// wait for WiFi connection
if ((WiFiMulti.run() == WL_CONNECTED)) {
HTTPClient http;
http.setInsecure();
Serial.print("[HTTP] begin...\n");
// configure traged server and url
http.begin("https://guest:guest@jigsaw.w3.org/HTTP/Basic/");
/*
// or
http.begin(client, "http://jigsaw.w3.org/HTTP/Basic/");
http.setAuthorization("guest", "guest");
// or
http.begin(client, "http://jigsaw.w3.org/HTTP/Basic/");
http.setAuthorization("Z3Vlc3Q6Z3Vlc3Q=");
*/
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
Serial.printf("[HTTP] GET... code: %d\n", httpCode);
// file found at server
if (httpCode == HTTP_CODE_OK) {
String payload = http.getString();
Serial.println(payload);
}
} else {
Serial.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str());
}
http.end();
}
delay(10000);
}
/**
BasicHTTPClient.ino
Created on: 24.05.2015
*/
#include <Arduino.h>
#include <WiFi.h>
#include <HTTPClient.h>
#ifndef STASSID
#define STASSID "your-ssid"
#define STAPSK "your-password"
#endif
const char *ssid = STASSID;
const char *pass = STAPSK;
WiFiMulti WiFiMulti;
void setup() {
Serial.begin(115200);
// Serial.setDebugOutput(true);
Serial.println();
Serial.println();
Serial.println();
for (uint8_t t = 4; t > 0; t--) {
Serial.printf("[SETUP] WAIT %d...\n", t);
Serial.flush();
delay(1000);
}
WiFiMulti.addAP(ssid, pass);
}
void loop() {
// wait for WiFi connection
if ((WiFiMulti.run() == WL_CONNECTED)) {
HTTPClient http;
Serial.print("[HTTP] begin...\n");
if (http.begin("http://httpbin.org")) { // HTTP
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
Serial.printf("[HTTP] GET... code: %d\n", httpCode);
// file found at server
if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY) {
String payload = http.getString();
Serial.println(payload);
}
} else {
Serial.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str());
}
http.end();
} else {
Serial.printf("[HTTP} Unable to connect\n");
}
}
delay(10000);
}
/**
BasicHTTPSClient-Hard.ino
Demonstrates the manual way of making a WiFiClient and passing it in to the HTTPClient
Created on: 20.08.2018
*/
#include <Arduino.h>
#include <WiFi.h>
#include <HTTPClient.h>
#ifndef STASSID
#define STASSID "your-ssid"
#define STAPSK "your-password"
#endif
const char *ssid = STASSID;
const char *pass = STAPSK;
WiFiMulti WiFiMulti;
void setup() {
Serial.begin(115200);
Serial.println();
Serial.println();
Serial.println();
for (uint8_t t = 4; t > 0; t--) {
Serial.printf("[SETUP] WAIT %d...\n", t);
Serial.flush();
delay(1000);
}
WiFi.mode(WIFI_STA);
WiFiMulti.addAP(ssid, pass);
}
void loop() {
// wait for WiFi connection
if ((WiFiMulti.run() == WL_CONNECTED)) {
WiFiClientSecure client;
client.setInsecure(); // Not safe against MITM attacks
HTTPClient https;
Serial.print("[HTTPS] begin...\n");
if (https.begin(client, "https://jigsaw.w3.org/HTTP/connection.html")) { // HTTPS
Serial.print("[HTTPS] GET...\n");
// start connection and send HTTP header
int httpCode = https.GET();
// httpCode will be negative on error
if (httpCode > 0) {
// HTTP header has been send and Server response header has been handled
Serial.printf("[HTTPS] GET... code: %d\n", httpCode);
// file found at server
if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY) {
String payload = https.getString();
Serial.println(payload);
}
} else {
Serial.printf("[HTTPS] GET... failed, error: %s\n", https.errorToString(httpCode).c_str());
}
https.end();
} else {
Serial.printf("[HTTPS] Unable to connect\n");
}
}
Serial.println("Wait 10s before next round...");
delay(10000);
}
/**
BasicHTTPSClient.ino
Created on: 20.08.2018
*/
#include <Arduino.h>
#include <WiFi.h>
#include <HTTPClient.h>
#ifndef STASSID
#define STASSID "your-ssid"
#define STAPSK "your-password"
#endif
const char *ssid = STASSID;
const char *pass = STAPSK;
WiFiMulti WiFiMulti;
void setup() {
Serial.begin(115200);
// Serial.setDebugOutput(true);
Serial.println();
Serial.println();
Serial.println();
for (uint8_t t = 4; t > 0; t--) {
Serial.printf("[SETUP] WAIT %d...\n", t);
Serial.flush();
delay(1000);
}
WiFi.mode(WIFI_STA);
WiFiMulti.addAP(ssid, pass);
}
const char *jigsaw_cert = R"EOF(
-----BEGIN CERTIFICATE-----
MIIFKTCCBM+gAwIBAgIQAbTKhAICxb7iDJbE6qU/NzAKBggqhkjOPQQDAjBKMQsw
CQYDVQQGEwJVUzEZMBcGA1UEChMQQ2xvdWRmbGFyZSwgSW5jLjEgMB4GA1UEAxMX
Q2xvdWRmbGFyZSBJbmMgRUNDIENBLTMwHhcNMjIwMzE3MDAwMDAwWhcNMjMwMzE2
MjM1OTU5WjB1MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQG
A1UEBxMNU2FuIEZyYW5jaXNjbzEZMBcGA1UEChMQQ2xvdWRmbGFyZSwgSW5jLjEe
MBwGA1UEAxMVc25pLmNsb3VkZmxhcmVzc2wuY29tMFkwEwYHKoZIzj0CAQYIKoZI
zj0DAQcDQgAEYnkGDyrIltjRnxoVdy/xgndo+WGMOASzs2hHeCjbJ1KplKJc/ciK
XCWq/4+pTzSiVgTFhRmCdLcU1Fa05YFNQaOCA2owggNmMB8GA1UdIwQYMBaAFKXO
N+rrsHUOlGeItEX62SQQh5YfMB0GA1UdDgQWBBRIzOWGCDBB/PMrMucSrjIKqlgE
uDAvBgNVHREEKDAmghVzbmkuY2xvdWRmbGFyZXNzbC5jb22CDWppZ3Nhdy53My5v
cmcwDgYDVR0PAQH/BAQDAgeAMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcD
AjB7BgNVHR8EdDByMDegNaAzhjFodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vQ2xv
dWRmbGFyZUluY0VDQ0NBLTMuY3JsMDegNaAzhjFodHRwOi8vY3JsNC5kaWdpY2Vy
dC5jb20vQ2xvdWRmbGFyZUluY0VDQ0NBLTMuY3JsMD4GA1UdIAQ3MDUwMwYGZ4EM
AQICMCkwJwYIKwYBBQUHAgEWG2h0dHA6Ly93d3cuZGlnaWNlcnQuY29tL0NQUzB2
BggrBgEFBQcBAQRqMGgwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0
LmNvbTBABggrBgEFBQcwAoY0aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0Ns
b3VkZmxhcmVJbmNFQ0NDQS0zLmNydDAMBgNVHRMBAf8EAjAAMIIBfwYKKwYBBAHW
eQIEAgSCAW8EggFrAWkAdQDoPtDaPvUGNTLnVyi8iWvJA9PL0RFr7Otp4Xd9bQa9
bgAAAX+aFPh6AAAEAwBGMEQCICivjuh2ywUYvVpTKHo65JEheR8dFq8QvBgEiXfw
m6q6AiAkxAgz77oboGQGetNmab45+peY+nAGOfyW9vi9S1gMaAB3ADXPGRu/sWxX
vw+tTG1Cy7u2JyAmUeo/4SrvqAPDO9ZMAAABf5oU+GEAAAQDAEgwRgIhANKeTNMy
GqUsCo7ph7YMWzrhMuDeyP8xPSiCtFzKcn/eAiEAyv5lgCUQ6K14V13zYfL99wZD
LFcIP/KZ1y7nuPAksTAAdwCzc3cH4YRQ+GOG1gWp3BEJSnktsWcMC4fc8AMOeTal
mgAAAX+aFPiWAAAEAwBIMEYCIQD6535jWw776D4vjyupP2fBw26CBMpVT5++k4rR
xqeOXwIhAIbEaEKkEq6JtpWWfVpTyDkMpMfTuiqYVe6REy2XsmEhMAoGCCqGSM49
BAMCA0gAMEUCIH3r/puXZcX1bfUoBq2njuHe0bxWtvzDaz5k6WLYrazTAiEA+ePL
N6K5xrmaof185pVCxACPLc/BoKyUwMeC8iXCm00=
-----END CERTIFICATE-----
)EOF";
static int cnt = 0;
void loop() {
// wait for WiFi connection
if ((WiFiMulti.run() == WL_CONNECTED)) {
HTTPClient https;
switch (cnt) {
case 0:
Serial.println("[HTTPS] using insecure SSL, not validating certificate");
https.setInsecure(); // Note this is unsafe against MITM attacks
cnt++;
break;
case 1:
Serial.println("[HTTPS] using secure SSL, validating certificate");
https.setCACert(jigsaw_cert);
cnt++;
break;
default:
Serial.println("[HTTPS] not setting any SSL verification settings, will fail");
cnt = 0;
}
Serial.print("[HTTPS] begin...\n");
if (https.begin("https://jigsaw.w3.org/HTTP/connection.html")) { // HTTPS
Serial.print("[HTTPS] GET...\n");
// start connection and send HTTP header
int httpCode = https.GET();
// httpCode will be negative on error
if (httpCode > 0) {
// HTTP header has been send and Server response header has been handled
Serial.printf("[HTTPS] GET... code: %d\n", httpCode);
// file found at server
if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY) {
String payload = https.getString();
Serial.println(payload);
}
} else {
Serial.printf("[HTTPS] GET... failed, error: %s\n", https.errorToString(httpCode).c_str());
}
https.end();
} else {
Serial.printf("[HTTPS] Unable to connect\n");
}
}
Serial.println("Wait 10s before next round...");
delay(10000);
}
/**
ChunkedClient.ino
*/
#include <Arduino.h>
#include <WiFi.h>
#include <HTTPClient.h>
#ifndef STASSID
#define STASSID "your-ssid"
#define STAPSK "your-password"
#endif
const char *ssid = STASSID;
const char *pass = STAPSK;
WiFiMulti WiFiMulti;
void setup() {
Serial.begin(115200);
// Serial.setDebugOutput(true);
Serial.println();
Serial.println();
Serial.println();
for (uint8_t t = 4; t > 0; t--) {
Serial.printf("[SETUP] WAIT %d...\n", t);
Serial.flush();
delay(1000);
}
WiFi.mode(WIFI_STA);
WiFiMulti.addAP(ssid, pass);
}
void loop() {
// wait for WiFi connection
if ((WiFiMulti.run() == WL_CONNECTED)) {
HTTPClient http;
Serial.print("[HTTP] begin...\n");
if (http.begin("http://anglesharp.azurewebsites.net/Chunked")) {
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
Serial.printf("[HTTP] GET... code: %d\n", httpCode);
// file found at server
if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY) {
String payload = http.getString();
Serial.println(payload);
}
} else {
Serial.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str());
}
http.end();
} else {
Serial.printf("[HTTP] Unable to connect\n");
}
}
Serial.println("Wait forever...");
while (1) {
continue;
}
}
/*
This sketch shows how to handle HTTP Digest Authorization.
Written by Parham Alvani and Sajjad Rahnama, 2018-01-07.
This example is released into public domain,
or, at your option, CC0 licensed.
*/
#include <WiFi.h>
#include <HTTPClient.h>
#ifndef STASSID
#define STASSID "NOBABIES"
#define STAPSK "ElephantsAreGreat"
#endif
const char* ssid = STASSID;
const char* ssidPassword = STAPSK;
const char* username = "admin";
const char* password = "admin";
const char* server = "http://httpbin.org";
const char* uri = "/digest-auth/auth/admin/admin/MD5";
String exractParam(String& authReq, const String& param, const char delimit) {
int _begin = authReq.indexOf(param);
if (_begin == -1) {
return "";
}
return authReq.substring(_begin + param.length(), authReq.indexOf(delimit, _begin + param.length()));
}
String getCNonce(const int len) {
static const char alphanum[] = "0123456789"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz";
String s = "";
for (int i = 0; i < len; ++i) {
s += alphanum[rand() % (sizeof(alphanum) - 1)];
}
return s;
}
String getDigestAuth(String& authReq, const String& username, const String& password, const String& method, const String& uri, unsigned int counter) {
// extracting required parameters for RFC 2069 simpler Digest
String realm = exractParam(authReq, "realm=\"", '"');
String nonce = exractParam(authReq, "nonce=\"", '"');
String cNonce = getCNonce(8);
char nc[9];
snprintf(nc, sizeof(nc), "%08x", counter);
// parameters for the RFC 2617 newer Digest
MD5Builder md5;
md5.begin();
md5.add(username + ":" + realm + ":" + password); // md5 of the user:realm:user
md5.calculate();
String h1 = md5.toString();
md5.begin();
md5.add(method + ":" + uri);
md5.calculate();
String h2 = md5.toString();
md5.begin();
md5.add(h1 + ":" + nonce + ":" + String(nc) + ":" + cNonce + ":" + "auth" + ":" + h2);
md5.calculate();
String response = md5.toString();
String authorization = "Digest username=\"" + username + "\", realm=\"" + realm + "\", nonce=\"" + nonce + "\", uri=\"" + uri + "\", algorithm=\"MD5\", qop=auth, nc=" + String(nc) + ", cnonce=\"" + cNonce + "\", response=\"" + response + "\"";
Serial.println(authorization);
return authorization;
}
void setup() {
Serial.begin(115200);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, ssidPassword);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi connected");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
srand(rp2040.getCycleCount());
}
void loop() {
HTTPClient http;
Serial.print("[HTTP] begin...\n");
// configure target server and url
http.begin(String(server) + String(uri));
const char* keys[] = { "WWW-Authenticate" };
http.collectHeaders(keys, 1);
Serial.print("[HTTP] GET...\n");
// start connection and send HTTP header
int httpCode = http.GET();
if (httpCode > 0) {
String authReq = http.header("WWW-Authenticate");
Serial.println(authReq);
String authorization = getDigestAuth(authReq, String(username), String(password), "GET", String(uri), 1);
http.end();
http.begin(String(server) + String(uri));
http.addHeader("Authorization", authorization);
int httpCode = http.GET();
if (httpCode > 0) {
String payload = http.getString();
Serial.println(payload);
} else {
Serial.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str());
}
} else {
Serial.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str());
}
http.end();
delay(10000);
}
/**
PostHTTPClient.ino
Created on: 21.11.2016
*/
#include <WiFi.h>
#include <HTTPClient.h>
#ifndef STASSID
#define STASSID "your-ssid"
#define STAPSK "your-password"
#endif
void setup() {
Serial.begin(115200);
Serial.println();
Serial.println();
Serial.println();
WiFi.begin(STASSID, STAPSK);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.print("Connected! IP address: ");
Serial.println(WiFi.localIP());
}
void loop() {
// wait for WiFi connection
if ((WiFi.status() == WL_CONNECTED)) {
HTTPClient http;
http.setInsecure();
Serial.print("[HTTP] begin...\n");
// configure target server and url
http.begin("https://httpbin.org/post");
http.addHeader("Content-Type", "application/json");
Serial.print("[HTTP] POST...\n");
// start connection and send HTTP header and body
int httpCode = http.POST("{\"hello\":\"world\"}");
// httpCode will be negative on error
if (httpCode > 0) {
// HTTP header has been send and Server response header has been handled
Serial.printf("[HTTP] POST... code: %d\n", httpCode);
// file found at server
if (httpCode == HTTP_CODE_OK) {
const String& payload = http.getString();
Serial.println("received payload:\n<<");
Serial.println(payload);
Serial.println(">>");
}
} else {
Serial.printf("[HTTP] POST... failed, error: %s\n", http.errorToString(httpCode).c_str());
}
http.end();
}
delay(10000);
}
/**
reuseConnectionV2.ino
Created on: 22.11.2015
This example reuses the http connection and also restores the connection if the connection is lost
*/
#include <WiFi.h>
#include <WiFiMulti.h>
#include <HTTPClient.h>
#ifndef STASSID
#define STASSID "your-ssid"
#define STAPSK "your-password"
#endif
WiFiMulti WiFiMulti;
HTTPClient http;
void setup() {
Serial.begin(115200);
// Serial.setDebugOutput(true);
Serial.println();
Serial.println();
Serial.println("Connecting to WiFi...");
WiFi.mode(WIFI_STA);
WiFiMulti.addAP(STASSID, STAPSK);
// wait for WiFi connection
while ((WiFiMulti.run() != WL_CONNECTED)) {
Serial.write('.');
delay(500);
}
Serial.println(" connected to WiFi");
// allow reuse (if server supports it)
http.setReuse(true);
http.setInsecure();
http.begin("https://jigsaw.w3.org/HTTP/connection.html");
// http.begin(client, "jigsaw.w3.org", 80, "/HTTP/connection.html");
}
int pass = 0;
void loop() {
// First 10 loop()s, retrieve the URL
if (pass < 10) {
pass++;
Serial.printf("Reuse connection example, GET url for the %d time\n", pass);
int httpCode = http.GET();
if (httpCode > 0) {
Serial.printf("[HTTP] GET... code: %d\n", httpCode);
// file found at server
if (httpCode == HTTP_CODE_OK) {
http.writeToStream(&Serial);
}
} else {
Serial.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str());
// Something went wrong with the connection, try to reconnect
http.end();
http.begin("https://jigsaw.w3.org/HTTP/connection.html");
// http.begin(client, "jigsaw.w3.org", 80, "/HTTP/connection.html");
}
if (pass == 10) {
http.end();
Serial.println("Done testing");
} else {
Serial.println("\n\n\nWait 5 second...\n");
delay(5000);
}
}
}
/**
StreamHTTPClient.ino
Created on: 24.05.2015
*/
#include <Arduino.h>
#include <WiFi.h>
#include <HTTPClient.h>
#ifndef STASSID
#define STASSID "your-ssid"
#define STAPSK "your-password"
#endif
const char *ssid = STASSID;
const char *pass = STAPSK;
WiFiMulti WiFiMulti;
void setup() {
Serial.begin(115200);
// Serial.setDebugOutput(true);
Serial.println();
Serial.println();
Serial.println();
for (uint8_t t = 4; t > 0; t--) {
Serial.printf("[SETUP] WAIT %d...\n", t);
Serial.flush();
delay(1000);
}
WiFi.mode(WIFI_STA);
WiFiMulti.addAP(ssid, pass);
}
void loop() {
// wait for WiFi connection
if ((WiFiMulti.run() == WL_CONNECTED)) {
Serial.print("[HTTPS] begin...\n");
// configure server and url
const char *fp = "41:FA:FD:B6:96:5F:33:09:F4:ED:09:28:BF:66:4D:5B:A2:88:03:65";
HTTPClient https;
https.setFingerprint(fp);
if (https.begin("https://www.trustedfirmware.org/projects/mbed-tls")) {
Serial.print("[HTTPS] GET...\n");
// start connection and send HTTP header
int httpCode = https.GET();
if (httpCode > 0) {
// HTTP header has been send and Server response header has been handled
Serial.printf("[HTTPS] GET... code: %d\n", httpCode);
// file found at server
if (httpCode == HTTP_CODE_OK) {
// get length of document (is -1 when Server sends no Content-Length header)
int len = https.getSize();
// create buffer for read
static uint8_t buff[128] = { 0 };
// read all data from server
while (https.connected() && (len > 0 || len == -1)) {
// get available data size
size_t size = https.getStreamPtr()->available();
if (size) {
// read up to 128 byte
int c = https.getStreamPtr()->readBytes(buff, ((size > sizeof(buff)) ? sizeof(buff) : size));
// write it to Serial
Serial.write(buff, c);
if (len > 0) {
len -= c;
}
}
delay(1);
}
Serial.println();
Serial.print("[HTTPS] connection closed or file end.\n");
}
} else {
Serial.printf("[HTTPS] GET... failed, error: %s\n", https.errorToString(httpCode).c_str());
}
https.end();
} else {
Serial.printf("Unable to connect\n");
}
}
Serial.println("Wait 10s before the next round...");
delay(10000);
}
#######################################
# Syntax Coloring Map For HTTPClient
#######################################
#######################################
# Library (KEYWORD3)
#######################################
HTTPClient KEYWORD3 RESERVED_WORD
#######################################
# Datatypes (KEYWORD1)
#######################################
t_http_codes KEYWORD1 DATA_TYPE
transferEncoding_t KEYWORD1 DATA_TYPE
TransportTraits KEYWORD1 DATA_TYPE
TransportTraitsPtr KEYWORD1 DATA_TYPE
StreamString KEYWORD1 DATA_TYPE
HTTPClient KEYWORD1 DATA_TYPE
#######################################
# Methods and Functions (KEYWORD2)
#######################################
begin KEYWORD2
end KEYWORD2
connected KEYWORD2
setReuse KEYWORD2
setUserAgent KEYWORD2
setAuthorization KEYWORD2
setTimeout KEYWORD2
useHTTP10 KEYWORD2
GET KEYWORD2
POST KEYWORD2
PUT KEYWORD2
PATCH KEYWORD2
sendRequest KEYWORD2
addHeader KEYWORD2
collectHeaders KEYWORD2
header KEYWORD2
headerName KEYWORD2
headers KEYWORD2
hasHeader KEYWORD2
getSize KEYWORD2
getStream KEYWORD2
getStreamPtr KEYWORD2
writeToStream KEYWORD2
getString KEYWORD2
errorToString KEYWORD2
setSession KEYWORD2
setInsecure KEYWORD2
setKnownKey KEYWORD2
setFingerprint KEYWORD2
allowSelfSignedCerts KEYWORD2
setTrustAnchors KEYWORD2
setX509Time KEYWORD2
setClientRSACert KEYWORD2
setClientECCert KEYWORD2
setBufferSizes KEYWORD2
setCertStore KEYWORD2
setCiphers KEYWORD2
setCiphersLessSecure KEYWORD2
setSSLVersion KEYWORD2
setCACert KEYWORD2
setCertificate KEYWORD2
setPrivateKey KEYWORD2
loadCACert KEYWORD2
loadCertificate KEYWORD2
loadPrivateKey KEYWORD2
#######################################
# Constants (LITERAL1)
#######################################
HTTPCLIENT_DEFAULT_TCP_TIMEOUT LITERAL1 RESERVED_WORD_2
HTTPC_ERROR_CONNECTION_REFUSED LITERAL1 RESERVED_WORD_2
HTTPC_ERROR_SEND_HEADER_FAILED LITERAL1 RESERVED_WORD_2
HTTPC_ERROR_SEND_PAYLOAD_FAILED LITERAL1 RESERVED_WORD_2
HTTPC_ERROR_NOT_CONNECTED LITERAL1 RESERVED_WORD_2
HTTPC_ERROR_CONNECTION_LOST LITERAL1 RESERVED_WORD_2
HTTPC_ERROR_NO_STREAM LITERAL1 RESERVED_WORD_2
HTTPC_ERROR_NO_HTTP_SERVER LITERAL1 RESERVED_WORD_2
HTTPC_ERROR_TOO_LESS_RAM LITERAL1 RESERVED_WORD_2
HTTPC_ERROR_ENCODING LITERAL1 RESERVED_WORD_2
HTTPC_ERROR_STREAM_WRITE LITERAL1 RESERVED_WORD_2
HTTPC_ERROR_READ_TIMEOUT LITERAL1 RESERVED_WORD_2
HTTP_TCP_BUFFER_SIZE LITERAL1 RESERVED_WORD_2
HTTP_CODE_CONTINUE LITERAL1 RESERVED_WORD_2
HTTP_CODE_SWITCHING_PROTOCOLS LITERAL1 RESERVED_WORD_2
HTTP_CODE_PROCESSING LITERAL1 RESERVED_WORD_2
HTTP_CODE_OK LITERAL1 RESERVED_WORD_2
HTTP_CODE_CREATED LITERAL1 RESERVED_WORD_2
HTTP_CODE_ACCEPTED LITERAL1 RESERVED_WORD_2
HTTP_CODE_NON_AUTHORITATIVE_INFORMATION LITERAL1 RESERVED_WORD_2
HTTP_CODE_NO_CONTENT LITERAL1 RESERVED_WORD_2
HTTP_CODE_RESET_CONTENT LITERAL1 RESERVED_WORD_2
HTTP_CODE_PARTIAL_CONTENT LITERAL1 RESERVED_WORD_2
HTTP_CODE_MULTI_STATUS LITERAL1 RESERVED_WORD_2
HTTP_CODE_ALREADY_REPORTED LITERAL1 RESERVED_WORD_2
HTTP_CODE_IM_USED LITERAL1 RESERVED_WORD_2
HTTP_CODE_MULTIPLE_CHOICES LITERAL1 RESERVED_WORD_2
HTTP_CODE_MOVED_PERMANENTLY LITERAL1 RESERVED_WORD_2
HTTP_CODE_FOUND LITERAL1 RESERVED_WORD_2
HTTP_CODE_SEE_OTHER LITERAL1 RESERVED_WORD_2
HTTP_CODE_NOT_MODIFIED LITERAL1 RESERVED_WORD_2
HTTP_CODE_USE_PROXY LITERAL1 RESERVED_WORD_2
HTTP_CODE_TEMPORARY_REDIRECT LITERAL1 RESERVED_WORD_2
HTTP_CODE_PERMANENT_REDIRECT LITERAL1 RESERVED_WORD_2
HTTP_CODE_BAD_REQUEST LITERAL1 RESERVED_WORD_2
HTTP_CODE_UNAUTHORIZED LITERAL1 RESERVED_WORD_2
HTTP_CODE_PAYMENT_REQUIRED LITERAL1 RESERVED_WORD_2
HTTP_CODE_FORBIDDEN LITERAL1 RESERVED_WORD_2
HTTP_CODE_NOT_FOUND LITERAL1 RESERVED_WORD_2
HTTP_CODE_METHOD_NOT_ALLOWED LITERAL1 RESERVED_WORD_2
HTTP_CODE_NOT_ACCEPTABLE LITERAL1 RESERVED_WORD_2
HTTP_CODE_PROXY_AUTHENTICATION_REQUIRED LITERAL1 RESERVED_WORD_2
HTTP_CODE_REQUEST_TIMEOUT LITERAL1 RESERVED_WORD_2
HTTP_CODE_CONFLICT LITERAL1 RESERVED_WORD_2
HTTP_CODE_GONE LITERAL1 RESERVED_WORD_2
HTTP_CODE_LENGTH_REQUIRED LITERAL1 RESERVED_WORD_2
HTTP_CODE_PRECONDITION_FAILED LITERAL1 RESERVED_WORD_2
HTTP_CODE_PAYLOAD_TOO_LARGE LITERAL1 RESERVED_WORD_2
HTTP_CODE_URI_TOO_LONG LITERAL1 RESERVED_WORD_2
HTTP_CODE_UNSUPPORTED_MEDIA_TYPE LITERAL1 RESERVED_WORD_2
HTTP_CODE_RANGE_NOT_SATISFIABLE LITERAL1 RESERVED_WORD_2
HTTP_CODE_EXPECTATION_FAILED LITERAL1 RESERVED_WORD_2
HTTP_CODE_MISDIRECTED_REQUEST LITERAL1 RESERVED_WORD_2
HTTP_CODE_UNPROCESSABLE_ENTITY LITERAL1 RESERVED_WORD_2
HTTP_CODE_LOCKED LITERAL1 RESERVED_WORD_2
HTTP_CODE_FAILED_DEPENDENCY LITERAL1 RESERVED_WORD_2
HTTP_CODE_UPGRADE_REQUIRED LITERAL1 RESERVED_WORD_2
HTTP_CODE_PRECONDITION_REQUIRED LITERAL1 RESERVED_WORD_2
HTTP_CODE_TOO_MANY_REQUESTS LITERAL1 RESERVED_WORD_2
HTTP_CODE_REQUEST_HEADER_FIELDS_TOO_LARGE LITERAL1 RESERVED_WORD_2
HTTP_CODE_INTERNAL_SERVER_ERROR LITERAL1 RESERVED_WORD_2
HTTP_CODE_NOT_IMPLEMENTED LITERAL1 RESERVED_WORD_2
HTTP_CODE_BAD_GATEWAY LITERAL1 RESERVED_WORD_2
HTTP_CODE_SERVICE_UNAVAILABLE LITERAL1 RESERVED_WORD_2
HTTP_CODE_GATEWAY_TIMEOUT LITERAL1 RESERVED_WORD_2
HTTP_CODE_HTTP_VERSION_NOT_SUPPORTED LITERAL1 RESERVED_WORD_2
HTTP_CODE_VARIANT_ALSO_NEGOTIATES LITERAL1 RESERVED_WORD_2
HTTP_CODE_INSUFFICIENT_STORAGE LITERAL1 RESERVED_WORD_2
HTTP_CODE_LOOP_DETECTED LITERAL1 RESERVED_WORD_2
HTTP_CODE_NOT_EXTENDED LITERAL1 RESERVED_WORD_2
HTTP_CODE_NETWORK_AUTHENTICATION_REQUIRED LITERAL1 RESERVED_WORD_2
HTTPC_TE_IDENTITY LITERAL1 RESERVED_WORD_2
HTTPC_TE_CHUNKED LITERAL1 RESERVED_WORD_2
name=HTTPClient
version=1.2
author=Markus Sattler
maintainer=Earle F. Philhower, III <earlephilhower@yahoo.com>
sentence=http Client for ESP8266, portes to the Pico
paragraph=
category=Communication
url=https://github.com/earlephilhower/arduino-pico/blob/master/libraries/HTTPClient
architectures=rp2040
dot_a_linkage=true
/**
HTTPClient.cpp
Created on: 02.11.2015
Copyright (c) 2015 Markus Sattler. All rights reserved.
This file is part of the ESP8266HTTPClient for Arduino.
Modified 2022 by Earle F. Philhower, III for the Pico RP2040
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 <Arduino.h>
#include "HTTPClient.h"
#include <WiFi.h>
#include "base64.h"
// per https://github.com/esp8266/Arduino/issues/8231
// make sure HTTPClient can be utilized as a movable class member
static_assert(std::is_default_constructible_v<HTTPClient>, "");
static_assert(!std::is_copy_constructible_v<HTTPClient>, "");
static_assert(std::is_move_constructible_v<HTTPClient>, "");
static_assert(std::is_move_assignable_v<HTTPClient>, "");
static const char defaultUserAgentPstr[] PROGMEM = "Pico";
const String HTTPClient::defaultUserAgent = defaultUserAgentPstr;
//static int StreamReportToHttpClientReport (Stream::Report streamSendError)
//{
// switch (streamSendError)
// {
// case Stream::Report::TimedOut: return HTTPC_ERROR_READ_TIMEOUT;
// case Stream::Report::ReadError: return HTTPC_ERROR_NO_STREAM;
// case Stream::Report::WriteError: return HTTPC_ERROR_STREAM_WRITE;
// case Stream::Report::ShortOperation: return HTTPC_ERROR_STREAM_WRITE;
// case Stream::Report::Success: return 0;
// }
// return 0; // never reached, keep gcc quiet
//}
void HTTPClient::clear() {
_returnCode = 0;
_size = -1;
_headers = "";
_location = "";
_payload.reset();
}
/**
parsing the url for all needed parameters
@param client Client&
@param url String
@param https bool
@return success bool
*/
bool HTTPClient::begin(String url) {
// check for : (http: or https:)
int index = url.indexOf(':');
if (index < 0) {
DEBUG_HTTPCLIENT("[HTTP-Client][begin] failed to parse protocol\n");
return false;
}
String protocol = url.substring(0, index);
protocol.toLowerCase();
if (protocol != "http" && protocol != "https") {
DEBUG_HTTPCLIENT("[HTTP-Client][begin] unknown protocol '%s'\n", protocol.c_str());
return false;
}
_port = (protocol == "https" ? 443 : 80);
if (!_client()) {
if (protocol == "https") {
_tls();
} else {
_clientMade = new WiFiClient();
_clientGiven = false;
}
}
return beginInternal(url, protocol.c_str());
}
/**
directly supply all needed parameters
@param client Client&
@param host String
@param port uint16_t
@param uri String
@param https bool
@return success bool
*/
bool HTTPClient::begin(String host, uint16_t port, String uri, bool https) {
// Disconnect when reusing HTTPClient to talk to a different host
if (_host != host) {
_canReuse = false;
disconnect(true);
}
clear();
_host = host;
_port = port;
_uri = uri;
_protocol = (https ? "https" : "http");
if (!_client()) {
if (https) {
_tls();
} else {
_clientMade = new WiFiClient();
_clientGiven = false;
}
}
return true;
}
/**
parsing the url for all needed parameters
@param client Client&
@param url String
@param https bool
@return success bool
*/
bool HTTPClient::begin(WiFiClient &client, const String& url) {
// check for : (http: or https:)
int index = url.indexOf(':');
if (index < 0) {
DEBUG_HTTPCLIENT("[HTTP-Client][begin] failed to parse protocol\n");
return false;
}
String protocol = url.substring(0, index);
protocol.toLowerCase();
if (protocol != "http" && protocol != "https") {
DEBUG_HTTPCLIENT("[HTTP-Client][begin] unknown protocol '%s'\n", protocol.c_str());
return false;
}
_port = (protocol == "https" ? 443 : 80);
_clientIn = client.clone();
_clientGiven = true;
if (_clientMade) {
delete _clientMade;
_clientMade = nullptr;
}
return beginInternal(url, protocol.c_str());
}
/**
directly supply all needed parameters
@param client Client&
@param host String
@param port uint16_t
@param uri String
@param https bool
@return success bool
*/
bool HTTPClient::begin(WiFiClient &client, const String& host, uint16_t port, const String& uri, bool https) {
// Disconnect when reusing HTTPClient to talk to a different host
if ((_host != "") && (_host != host)) {
_canReuse = false;
disconnect(true);
}
_clientIn = client.clone();
_clientGiven = true;
if (_clientMade) {
delete _clientMade;
_clientMade = nullptr;
}
clear();
_host = host;
_port = port;
_uri = uri;
_protocol = (https ? "https" : "http");
return true;
}
bool HTTPClient::beginInternal(const String& __url, const char* expectedProtocol) {
String url(__url);
DEBUG_HTTPCLIENT("[HTTP-Client][begin] url: %s\n", url.c_str());
clear();
// check for : (http: or https:
int index = url.indexOf(':');
if (index < 0) {
DEBUG_HTTPCLIENT("[HTTP-Client][begin] failed to parse protocol\n");
return false;
}
_protocol = url.substring(0, index);
_protocol.toLowerCase();
url.remove(0, (index + 3)); // remove http:// or https://
if (_protocol == "http") {
// set default port for 'http'
_port = 80;
} else if (_protocol == "https") {
// set default port for 'https'
_port = 443;
} else {
DEBUG_HTTPCLIENT("[HTTP-Client][begin] unsupported protocol: %s\n", _protocol.c_str());
return false;
}
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, false /* doNewLines */);
}
const String oldHost = _host;
// 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;
}
// Disconnect when reusing HTTPClient to talk to a different host
if (oldHost != "" && _host != oldHost) {
_canReuse = false;
disconnect(true);
}
_uri = url;
if (expectedProtocol != nullptr && _protocol != expectedProtocol) {
DEBUG_HTTPCLIENT("[HTTP-Client][begin] unexpected protocol: %s, expected %s\n", _protocol.c_str(), expectedProtocol);
return false;
}
DEBUG_HTTPCLIENT("[HTTP-Client][begin] host: %s port: %d url: %s\n", _host.c_str(), _port, _uri.c_str());
return true;
}
/**
end
called after the payload is handled
*/
void HTTPClient::end(void) {
disconnect(false);
clear();
if (_clientMade) {
delete _clientMade;
_clientMade = nullptr;
_clientTLS = false;
}
}
/**
disconnect
close the TCP socket
*/
void HTTPClient::disconnect(bool preserveClient) {
if (connected()) {
if (_client()->available() > 0) {
DEBUG_HTTPCLIENT("[HTTP-Client][end] still data in buffer (%d), clean up.\n", _client()->available());
while (_client()->available() > 0) {
_client()->read();
}
}
if (_reuse && _canReuse) {
DEBUG_HTTPCLIENT("[HTTP-Client][end] tcp keep open for reuse\n");
} else {
DEBUG_HTTPCLIENT("[HTTP-Client][end] tcp stop\n");
if (_client()) {
_client()->stop();
if (!preserveClient) {
_clientIn = nullptr;
if (_clientMade) {
delete _clientMade;
_clientMade = nullptr;
}
_clientGiven = false;
}
}
}
} else {
if (!preserveClient && _client()) { // Also destroy _client if not connected()
_clientIn = nullptr;
if (_clientMade) {
delete _clientMade;
_clientMade = nullptr;
}
_clientGiven = false;
}
DEBUG_HTTPCLIENT("[HTTP-Client][end] tcp is closed\n");
}
}
/**
connected
@return connected status
*/
bool HTTPClient::connected() {
if (_client()) {
return (_client()->connected() || (_client()->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, false /* doNewLines */);
}
}
/**
set the Authorization for the http request
@param auth const char * base64
*/
void HTTPClient::setAuthorization(const char * auth) {
if (auth) {
setAuthorization(String(auth));
}
}
/**
set the Authorization for the http request
@param auth String base64
*/
void HTTPClient::setAuthorization(String auth) {
_base64Authorization = std::move(auth);
_base64Authorization.replace(String('\n'), "");
}
/**
set the timeout for the TCP connection
@param timeout unsigned int
*/
void HTTPClient::setTimeout(uint16_t timeout) {
_tcpTimeout = timeout;
if (connected()) {
_client()->setTimeout(timeout);
}
}
/**
set the URL to a new value. Handy for following redirects.
@param url
*/
bool HTTPClient::setURL(const String& url) {
// if the new location is only a path then only update the URI
if (url && url[0] == '/') {
_uri = url;
clear();
return true;
}
if (!url.startsWith(_protocol + ':')) {
DEBUG_HTTPCLIENT("[HTTP-Client][setURL] new URL not the same protocol, expected '%s', URL: '%s'\n", _protocol.c_str(), url.c_str());
return false;
}
// disconnect but preserve _client (clear _canReuse so disconnect will close the connection)
_canReuse = false;
disconnect(true);
return beginInternal(url, nullptr);
}
/**
set redirect follow mode. See `followRedirects_t` enum for available modes.
@param follow
*/
void HTTPClient::setFollowRedirects(followRedirects_t follow) {
_followRedirects = follow;
}
void HTTPClient::setRedirectLimit(uint16_t limit) {
_redirectLimit = limit;
}
/**
use HTTP1.0
@param useHTTP10 bool
*/
void HTTPClient::useHTTP10(bool useHTTP10) {
_useHTTP10 = useHTTP10;
_reuse = !useHTTP10;
}
/**
send a GET request
@return http code
*/
int HTTPClient::GET() {
return sendRequest("GET");
}
/**
send a DELETE request
@return http code
*/
int HTTPClient::DELETE() {
return sendRequest("DELETE");
}
/**
sends a post request to the server
@param payload const uint8_t
@param size size_t
@return http code
*/
int HTTPClient::POST(const uint8_t* payload, size_t size) {
return sendRequest("POST", payload, size);
}
int HTTPClient::POST(const 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(const uint8_t* payload, size_t size) {
return sendRequest("PUT", payload, size);
}
int HTTPClient::PUT(const String& payload) {
return PUT((const uint8_t *) payload.c_str(), payload.length());
}
/**
sends a patch request to the server
@param payload const uint8_t
@param size size_t
@return http code
*/
int HTTPClient::PATCH(const uint8_t * payload, size_t size) {
return sendRequest("PATCH", payload, size);
}
int HTTPClient::PATCH(const String& payload) {
return PATCH((const 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, const String& payload) {
return sendRequest(type, (const uint8_t *) payload.c_str(), payload.length());
}
class StreamConstPtr {
public:
StreamConstPtr(const uint8_t *payload, size_t size) {
_payload = payload;
_size = size;
}
StreamConstPtr(const String& string) {
_payload = (const uint8_t *)string.c_str();
_size = string.length();
}
size_t sendAll(Client *dst) {
uint32_t start = millis();
size_t sent = 0;
while ((sent < _size) && (millis() - start < 5000)) {
size_t towrite = std::min((size_t)128, _size - sent);
auto wrote = dst->write(_payload, towrite);
if (wrote <= 0) {
break;
}
sent += wrote;
_payload += wrote;
}
return sent;
}
const uint8_t *_payload;
size_t _size;
};
/**
sendRequest
@param type const char * "GET", "POST", ....
@param payload const 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, const uint8_t * payload, size_t size) {
int code;
bool redirect = false;
uint16_t redirectCount = 0;
do {
// wipe out any existing headers from previous request
for (size_t i = 0; i < _headerKeysCount; i++) {
if (_currentHeaders[i].value.length() > 0) {
_currentHeaders[i].value = "";
}
}
DEBUG_HTTPCLIENT("[HTTP-Client][sendRequest] type: '%s' redirCount: %d\n", type, redirectCount);
// connect to server
if (!connect()) {
return returnError(HTTPC_ERROR_CONNECTION_FAILED);
}
addHeader(F("Content-Length"), String(payload && size > 0 ? size : 0));
// send Header
if (!sendHeader(type)) {
return returnError(HTTPC_ERROR_SEND_HEADER_FAILED);
}
// transfer all of it, with send-timeout
if (size && StreamConstPtr(payload, size).sendAll(_client()) != size) {
return returnError(HTTPC_ERROR_SEND_PAYLOAD_FAILED);
}
// handle Server Response (Header)
code = handleHeaderResponse();
//
// Handle redirections as stated in RFC document:
// https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
//
// Implementing HTTP_CODE_FOUND as redirection with GET method,
// to follow most of existing user agent implementations.
//
redirect = false;
if (
_followRedirects != HTTPC_DISABLE_FOLLOW_REDIRECTS &&
redirectCount < _redirectLimit &&
_location.length() > 0
) {
switch (code) {
// redirecting using the same method
case HTTP_CODE_MOVED_PERMANENTLY:
case HTTP_CODE_TEMPORARY_REDIRECT: {
if (
// allow to force redirections on other methods
// (the RFC require user to accept the redirection)
_followRedirects == HTTPC_FORCE_FOLLOW_REDIRECTS ||
// allow GET and HEAD methods without force
!strcmp(type, "GET") ||
!strcmp(type, "HEAD")
) {
redirectCount += 1;
DEBUG_HTTPCLIENT("[HTTP-Client][sendRequest] following redirect (the same method): '%s' redirCount: %d\n", _location.c_str(), redirectCount);
if (!setURL(_location)) {
DEBUG_HTTPCLIENT("[HTTP-Client][sendRequest] failed setting URL for redirection\n");
// no redirection
break;
}
// redirect using the same request method and payload, different URL
redirect = true;
}
break;
}
// redirecting with method dropped to GET or HEAD
// note: it does not need `HTTPC_FORCE_FOLLOW_REDIRECTS` for any method
case HTTP_CODE_FOUND:
case HTTP_CODE_SEE_OTHER: {
redirectCount += 1;
DEBUG_HTTPCLIENT("[HTTP-Client][sendRequest] following redirect (dropped to GET/HEAD): '%s' redirCount: %d\n", _location.c_str(), redirectCount);
if (!setURL(_location)) {
DEBUG_HTTPCLIENT("[HTTP-Client][sendRequest] failed setting URL for redirection\n");
// no redirection
break;
}
// redirect after changing method to GET/HEAD and dropping payload
type = "GET";
payload = nullptr;
size = 0;
redirect = true;
break;
}
default:
break;
}
}
} while (redirect);
// handle Server Response (Header)
return returnError(code);
}
size_t StreamSendSize(Stream *s, Print *c, int size) {
int sent = 0;
if (size < 0) {
size = 999999; // Transfer until read fails
}
uint32_t start = millis();
while ((sent < size) && (millis() - start < 5000)) {
int x = s->read();
if (x < 0) {
break;
} else if (c->write(x)) {
sent++;
} else {
break;
}
}
return sent;
}
/**
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_FAILED);
}
if (size > 0) {
addHeader(F("Content-Length"), String(size));
}
// send Header
if (!sendHeader(type)) {
return returnError(HTTPC_ERROR_SEND_HEADER_FAILED);
}
// transfer all of it, with timeout
size_t transferred = StreamSendSize(stream, _client(), size);
if (transferred != size) {
DEBUG_HTTPCLIENT("[HTTP-Client][sendRequest] short write, asked for %zu but got %zu failed.\n", size, transferred);
return returnError(HTTPC_ERROR_SEND_PAYLOAD_FAILED);
}
// 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;
}
/**
Location if redirect
*/
const String& HTTPClient::getLocation(void) {
return _location;
}
/**
returns the stream of the tcp connection
@return WiFiClient
*/
WiFiClient& HTTPClient::getStream(void) {
if (connected()) {
return *_client();
}
DEBUG_HTTPCLIENT("[HTTP-Client] getStream: not connected\n");
static WiFiClient empty;
return empty;
}
/**
returns the stream of the tcp connection
@return WiFiClient
*/
WiFiClient* HTTPClient::getStreamPtr(void) {
if (connected()) {
return _client();
}
DEBUG_HTTPCLIENT("[HTTP-Client] getStreamPtr: not connected\n");
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) {
return writeToPrint(stream);
}
/**
write all message body / payload to Print
@param print Print
@return bytes written ( negative values are error codes )
*/
int HTTPClient::writeToPrint(Print * print) {
if (!print) {
return returnError(HTTPC_ERROR_NO_STREAM);
}
// Only return error if not connected and no data available, because otherwise ::getString() will return an error instead of an empty
// string when the server returned a http code 204 (no content)
if (!connected() && _transferEncoding != HTTPC_TE_IDENTITY && _size > 0) {
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) {
// len < 0: transfer all of it, with timeout
// len >= 0: max:len, with timeout
ret = StreamSendSize(_client(), print, len);
if (len > 0 && ret != len) {
return HTTPC_ERROR_NO_STREAM;
}
// do we have an error?
// if(_client->getLastSendReport() != Stream::Report::Success) {
// return returnError(StreamReportToHttpClientReport(_client->getLastSendReport()));
// }
} else if (_transferEncoding == HTTPC_TE_CHUNKED) {
int size = 0;
while (1) {
if (!connected()) {
return returnError(HTTPC_ERROR_CONNECTION_LOST);
}
String chunkHeader = _client()->readStringUntil('\n');
if (chunkHeader.length() <= 0) {
return returnError(HTTPC_ERROR_READ_TIMEOUT);
}
chunkHeader.trim(); // remove \r
DEBUG_HTTPCLIENT("[HTTP-Client] chunk header: '%s'\n", chunkHeader.c_str());
// read size of chunk
len = (uint32_t) strtol((const char *) chunkHeader.c_str(), NULL, 16);
size += len;
DEBUG_HTTPCLIENT("[HTTP-Client] read chunk len: %d\n", len);
// data left?
if (len > 0) {
// read len bytes with timeout
int r = StreamSendSize(_client(), print, len);
if (r != len) {
return HTTPC_ERROR_NO_STREAM;
}
// if (_client->getLastSendReport() != Stream::Report::Success)
// // not all data transferred
// return returnError(StreamReportToHttpClientReport(_client->getLastSendReport()));
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 = _client()->readBytes((uint8_t*)buf, 2);
if (trailing_seq_len != 2 || buf[0] != '\r' || buf[1] != '\n') {
return returnError(HTTPC_ERROR_READ_TIMEOUT);
}
}
} else {
return returnError(HTTPC_ERROR_ENCODING);
}
disconnect(true);
return ret;
}
/**
return all payload as String (may need lot of ram or trigger out of memory!)
@return String
*/
const String& HTTPClient::getString(void) {
if (_payload) {
return *_payload;
}
_payload.reset(new StreamString());
if (_size > 0) {
// try to reserve needed memory
if (!_payload->reserve((_size + 1))) {
DEBUG_HTTPCLIENT("[HTTP-Client][getString] not enough memory to reserve a string! need: %d\n", (_size + 1));
return *_payload;
}
}
writeToStream(_payload.get());
return *_payload;
}
/**
converts error code to String
@param error int
@return String
*/
String HTTPClient::errorToString(int error) {
switch (error) {
case HTTPC_ERROR_CONNECTION_FAILED:
return F("connection failed");
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("not enough 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;
headerLine.reserve(name.length() + value.length() + 4);
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;
_currentHeaders = std::make_unique<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 (_reuse && _canReuse && connected()) {
DEBUG_HTTPCLIENT("[HTTP-Client] connect: already connected, reusing connection\n");
// clear _client's output (all of it, no timeout)
while (_client()->available()) {
_client()->read();
}
return true;
}
if (!_client()) {
DEBUG_HTTPCLIENT("[HTTP-Client] connect: HTTPClient::begin was not called or returned error\n");
return false;
}
_client()->setTimeout(_tcpTimeout);
if (!_client()->connect(_host.c_str(), _port)) {
DEBUG_HTTPCLIENT("[HTTP-Client] failed connect to %s:%u\n", _host.c_str(), _port);
return false;
}
DEBUG_HTTPCLIENT("[HTTP-Client] connected to %s:%u\n", _host.c_str(), _port);
_client()->setNoDelay(true);
return connected();
}
/**
sends HTTP request header
@param type (GET, POST, ...)
@return status
*/
bool HTTPClient::sendHeader(const char * type) {
if (!connected()) {
return false;
}
String header;
// 128: Arbitrarily chosen to have enough buffer space for avoiding internal reallocations
header.reserve(_headers.length() + _uri.length() +
_base64Authorization.length() + _host.length() + _userAgent.length() + 128);
header += type;
header += ' ';
if (_uri.length()) {
header += _uri;
} else {
header += '/';
}
header += F(" HTTP/1.");
if (_useHTTP10) {
header += '0';
} else {
header += '1';
}
header += F("\r\nHost: ");
header += _host;
if (_port != 80 && _port != 443) {
header += ':';
header += String(_port);
}
if (_userAgent.length()) {
header += F("\r\nUser-Agent: ");
header += _userAgent;
}
if (!_useHTTP10) {
header += F("\r\nAccept-Encoding: identity;q=1,chunked;q=0.1,*;q=0");
}
if (_base64Authorization.length()) {
header += F("\r\nAuthorization: Basic ");
header += _base64Authorization;
}
header += F("\r\nConnection: ");
header += _reuse ? F("keep-alive") : F("close");
header += "\r\n";
header += _headers;
header += "\r\n";
DEBUG_HTTPCLIENT("[HTTP-Client] sending request header\n-----\n%s-----\n", header.c_str());
// transfer all of it, with timeout
return StreamConstPtr(header).sendAll(_client()) == header.length();
}
/**
reads the response from the server
@return int http code
*/
int HTTPClient::handleHeaderResponse() {
if (!connected()) {
return HTTPC_ERROR_NOT_CONNECTED;
}
clear();
_canReuse = _reuse;
String transferEncoding;
_transferEncoding = HTTPC_TE_IDENTITY;
unsigned long lastDataTime = millis();
while (connected()) {
size_t len = _client()->available();
if (len > 0) {
int headerSeparator = -1;
String headerLine = _client()->readStringUntil('\n');
lastDataTime = millis();
DEBUG_HTTPCLIENT("[HTTP-Client][handleHeaderResponse] RX: '%s'\n", headerLine.c_str());
if (headerLine.startsWith(F("HTTP/1."))) {
constexpr auto httpVersionIdx = sizeof "HTTP/1." - 1;
_canReuse = _canReuse && (headerLine[httpVersionIdx] != '0');
_returnCode = headerLine.substring(httpVersionIdx + 2, headerLine.indexOf(' ', httpVersionIdx + 2)).toInt();
_canReuse = _canReuse && (_returnCode > 0) && (_returnCode < 500);
} else if ((headerSeparator = headerLine.indexOf(':')) > 0) {
String headerName = headerLine.substring(0, headerSeparator);
String headerValue = headerLine.substring(headerSeparator + 1);
headerValue.trim();
if (headerName.equalsIgnoreCase(F("Content-Length"))) {
_size = headerValue.toInt();
}
if (_canReuse && headerName.equalsIgnoreCase(F("Connection"))) {
if (headerValue.indexOf(F("close")) >= 0 &&
headerValue.indexOf(F("keep-alive")) < 0) {
_canReuse = false;
}
}
if (headerName.equalsIgnoreCase(F("Transfer-Encoding"))) {
transferEncoding = headerValue;
}
if (headerName.equalsIgnoreCase(F("Location"))) {
_location = headerValue;
}
for (size_t i = 0; i < _headerKeysCount; i++) {
if (_currentHeaders[i].key.equalsIgnoreCase(headerName)) {
if (_currentHeaders[i].value != "") {
// Existing value, append this one with a comma
_currentHeaders[i].value += ',';
_currentHeaders[i].value += headerValue;
} else {
_currentHeaders[i].value = headerValue;
}
break; // We found a match, stop looking
}
}
continue;
}
headerLine.trim(); // remove \r
if (headerLine == "") {
DEBUG_HTTPCLIENT("[HTTP-Client][handleHeaderResponse] code: %d\n", _returnCode);
if (_size > 0) {
DEBUG_HTTPCLIENT("[HTTP-Client][handleHeaderResponse] size: %d\n", _size);
}
if (transferEncoding.length() > 0) {
DEBUG_HTTPCLIENT("[HTTP-Client][handleHeaderResponse] Transfer-Encoding: %s\n", transferEncoding.c_str());
if (transferEncoding.equalsIgnoreCase(F("chunked"))) {
_transferEncoding = HTTPC_TE_CHUNKED;
} else {
_returnCode = HTTPC_ERROR_ENCODING;
return _returnCode;
}
} else {
_transferEncoding = HTTPC_TE_IDENTITY;
}
if (_returnCode <= 0) {
DEBUG_HTTPCLIENT("[HTTP-Client][handleHeaderResponse] Remote host is not an HTTP Server!");
_returnCode = HTTPC_ERROR_NO_HTTP_SERVER;
}
return _returnCode;
}
} else {
if ((millis() - lastDataTime) > _tcpTimeout) {
return HTTPC_ERROR_READ_TIMEOUT;
}
}
}
return HTTPC_ERROR_CONNECTION_LOST;
}
/**
called to handle error return, may disconnect the connection if still exists
@param error
@return error
*/
int HTTPClient::returnError(int error) {
if (error < 0) {
DEBUG_HTTPCLIENT("[HTTP-Client][returnError] error(%d): %s\n", error, errorToString(error).c_str());
if (connected()) {
DEBUG_HTTPCLIENT("[HTTP-Client][returnError] tcp stop\n");
_client()->stop();
}
}
return error;
}
/**
HTTPClient.h
Modified 2022 by Earle F. Philhower, III
Created on: 02.11.2015
Copyright (c) 2015 Markus Sattler. All rights reserved.
This file is part of the ESP8266HTTPClient for Arduino.
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
Modified by Jeroen Döll, June 2018
*/
#pragma once
#include <Arduino.h>
#include <StreamString.h>
#include <WiFiClient.h>
#include <WiFiClientSecure.h>
#include <memory>
#ifdef DEBUG_ESP_HTTP_CLIENT
#ifdef DEBUG_ESP_PORT
#define DEBUG_HTTPCLIENT(fmt, ...) DEBUG_ESP_PORT.printf_P( (PGM_P)PSTR(fmt), ## __VA_ARGS__ )
#endif
#endif
//#define DEBUG_HTTPCLIENT(fmt, ...) Serial.printf(fmt, ## __VA_ARGS__ )
#ifndef DEBUG_HTTPCLIENT
#define DEBUG_HTTPCLIENT(...) do { (void)0; } while (0)
#endif
#define HTTPCLIENT_DEFAULT_TCP_TIMEOUT (5000)
/// HTTP client errors
#define HTTPC_ERROR_CONNECTION_FAILED (-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)
constexpr int HTTPC_ERROR_CONNECTION_REFUSED __attribute__((deprecated)) = HTTPC_ERROR_CONNECTION_FAILED;
/// 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;
/**
redirection follow mode.
+ `HTTPC_DISABLE_FOLLOW_REDIRECTS` - no redirection will be followed.
+ `HTTPC_STRICT_FOLLOW_REDIRECTS` - strict RFC2616, only requests using
GET or HEAD methods will be redirected (using the same method),
since the RFC requires end-user confirmation in other cases.
+ `HTTPC_FORCE_FOLLOW_REDIRECTS` - all redirections will be followed,
regardless of a used method. New request will use the same method,
and they will include the same body data and the same headers.
In the sense of the RFC, it's just like every redirection is confirmed.
*/
typedef enum {
HTTPC_DISABLE_FOLLOW_REDIRECTS,
HTTPC_STRICT_FOLLOW_REDIRECTS,
HTTPC_FORCE_FOLLOW_REDIRECTS
} followRedirects_t;
class TransportTraits;
typedef std::unique_ptr<TransportTraits> TransportTraitsPtr;
class HTTPClient {
public:
HTTPClient() = default;
~HTTPClient() = default;
HTTPClient(HTTPClient&&) = default;
HTTPClient& operator=(HTTPClient&&) = default;
// The easier way
bool begin(String url);
bool begin(String host, uint16_t port, String uri = "/", bool https = false);
bool begin(String url, const uint8_t httpsFingerprint[20]) {
setFingerprint(httpsFingerprint);
return begin(url);
}
bool begin(String host, uint16_t port, String uri, const uint8_t httpsFingerprint[20]) {
setFingerprint(httpsFingerprint);
return begin(host, port, uri);
}
// Let's do it the hard way, too
bool begin(WiFiClient &client, const String& url);
bool begin(WiFiClient &client, const String& host, uint16_t port, const String& uri = "/", bool https = false);
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 setAuthorization(String auth);
void setTimeout(uint16_t timeout);
// Redirections
void setFollowRedirects(followRedirects_t follow);
void setRedirectLimit(uint16_t limit); // max redirects to follow for a single request
bool setURL(const String& url); // handy for handling redirects
void useHTTP10(bool usehttp10 = true);
/// request handling
int GET();
int DELETE();
int POST(const uint8_t* payload, size_t size);
int POST(const String& payload);
int PUT(const uint8_t* payload, size_t size);
int PUT(const String& payload);
int PATCH(const uint8_t* payload, size_t size);
int PATCH(const String& payload);
int sendRequest(const char* type, const String& payload);
int sendRequest(const char* type, const 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);
const String& getLocation(void); // Location header from redirect if 3XX
WiFiClient& getStream(void);
WiFiClient* getStreamPtr(void);
int writeToPrint(Print* print);
int writeToStream(Stream* stream);
const String& getString(void);
static String errorToString(int error);
// ----------------------------------------------------------------------------------------------
// HTTPS support, mirrors the WiFiClientSecure interface
// Could possibly use a virtual interface class between the two, but for now it is more
// straightforward to simply feed calls through manually here.
void setSession(Session *session) {
_tls()->setSession(session);
}
void setInsecure() {
_tls()->setInsecure();
}
void setKnownKey(const PublicKey *pk, unsigned usages = BR_KEYTYPE_KEYX | BR_KEYTYPE_SIGN) {
_tls()->setKnownKey(pk, usages);
}
bool setFingerprint(const uint8_t fingerprint[20]) {
return _tls()->setFingerprint(fingerprint);
}
bool setFingerprint(const char *fpStr) {
return _tls()->setFingerprint(fpStr);
}
void allowSelfSignedCerts() {
_tls()->allowSelfSignedCerts();
}
void setTrustAnchors(const X509List *ta) {
_tls()->setTrustAnchors(ta);
}
void setX509Time(time_t now) {
_tls()->setX509Time(now);
}
void setClientRSACert(const X509List *cert, const PrivateKey *sk) {
_tls()->setClientRSACert(cert, sk);
}
void setClientECCert(const X509List *cert, const PrivateKey *sk, unsigned allowed_usages, unsigned cert_issuer_key_type) {
_tls()->setClientECCert(cert, sk, allowed_usages, cert_issuer_key_type);
}
void setBufferSizes(int recv, int xmit) {
_tls()->setBufferSizes(recv, xmit);
}
void setCertStore(CertStoreBase *certStore) {
_tls()->setCertStore(certStore);
}
bool setCiphers(const uint16_t *cipherAry, int cipherCount) {
return _tls()->setCiphers(cipherAry, cipherCount);
}
bool setCiphers(const std::vector<uint16_t>& list) {
return _tls()->setCiphers(list);
}
bool setCiphersLessSecure() {
return _tls()->setCiphersLessSecure();
}
bool setSSLVersion(uint32_t min = BR_TLS10, uint32_t max = BR_TLS12) {
return _tls()->setSSLVersion(min, max);
}
void setCACert(const char *rootCA) {
_tls()->setCACert(rootCA);
}
void setCertificate(const char *client_ca) {
_tls()->setCertificate(client_ca);
}
void setPrivateKey(const char *private_key) {
_tls()->setPrivateKey(private_key);
}
bool loadCACert(Stream& stream, size_t size) {
return _tls()->loadCACert(stream, size);
}
bool loadCertificate(Stream& stream, size_t size) {
return _tls()->loadCertificate(stream, size);
}
bool loadPrivateKey(Stream& stream, size_t size) {
return _tls()->loadPrivateKey(stream, size);
}
protected:
// HTTPS helpers
WiFiClientSecure *_tls() {
if (!_clientMade) {
_clientMade = new WiFiClientSecure();
_clientGiven = false;
}
_clientTLS = true;
return (WiFiClientSecure*)_clientMade;
}
struct RequestArgument {
String key;
String value;
};
bool beginInternal(const String& url, const char* expectedProtocol);
void disconnect(bool preserveClient = false);
void clear();
int returnError(int error);
bool connect(void);
bool sendHeader(const char * type);
int handleHeaderResponse();
int writeToStreamDataBlock(Stream * stream, int len);
WiFiClient *_clientMade = nullptr;
bool _clientTLS = false;
std::unique_ptr<WiFiClient> _clientIn;
bool _clientGiven = false;
WiFiClient *_client() {
if (_clientGiven) {
return _clientIn.get();
} else {
return _clientMade;
}
}
/// request handling
String _host;
uint16_t _port = 0;
bool _reuse = true;
uint16_t _tcpTimeout = HTTPCLIENT_DEFAULT_TCP_TIMEOUT;
bool _useHTTP10 = false;
String _uri;
String _protocol;
String _headers;
String _base64Authorization;
static const String defaultUserAgent;
String _userAgent = defaultUserAgent;
/// Response handling
std::unique_ptr<RequestArgument[]> _currentHeaders;
size_t _headerKeysCount = 0;
int _returnCode = 0;
int _size = -1;
bool _canReuse = false;
followRedirects_t _followRedirects = HTTPC_DISABLE_FOLLOW_REDIRECTS;
uint16_t _redirectLimit = 10;
String _location;
transferEncoding_t _transferEncoding = HTTPC_TE_IDENTITY;
std::unique_ptr<StreamString> _payload;
};
/**
base64.cpp
Created on: 09.12.2015
Copyright (c) 2015 Markus Sattler. All rights reserved.
This file is part of the ESP8266 core for Arduino.
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 "Arduino.h"
extern "C" {
#include "libb64/cencode.h"
}
#include "base64.h"
/**
convert input data to base64
@param data const uint8_t
@param length size_t
@return String
*/
String base64::encode(const uint8_t * data, size_t length, bool doNewLines) {
String base64;
// base64 needs more size then the source data, use cencode.h macros
size_t size = ((doNewLines ? base64_encode_expected_len(length)
: base64_encode_expected_len_nonewlines(length)) + 1);
if (base64.reserve(size)) {
base64_encodestate _state;
if (doNewLines) {
base64_init_encodestate(&_state);
} else {
base64_init_encodestate_nonewlines(&_state);
}
constexpr size_t BUFSIZE = 48;
char buf[BUFSIZE + 1 /* newline */ + 1 /* NUL */];
for (size_t len = 0; len < length; len += BUFSIZE * 3 / 4) {
size_t blocklen = base64_encode_block((const char*) data + len,
std::min(BUFSIZE * 3 / 4, length - len), buf, &_state);
buf[blocklen] = '\0';
base64 += buf;
}
if (base64_encode_blockend(buf, &_state)) {
base64 += buf;
}
} else {
base64 = F("-FAIL-");
}
return base64;
}
/**
base64.h
Created on: 09.12.2015
Copyright (c) 2015 Markus Sattler. All rights reserved.
This file is part of the ESP8266 core for Arduino.
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
*/
#pragma once
#include <api/String.h>
class base64 {
public:
// NOTE: The default behaviour of backend (lib64)
// is to add a newline every 72 (encoded) characters output.
// This may 'break' longer uris and json variables
static String encode(const uint8_t * data, size_t length, bool doNewLines);
static inline String encode(const String& text, bool doNewLines) {
return encode((const uint8_t *) text.c_str(), text.length(), doNewLines);
}
// esp32 compat:
static inline String encode(const uint8_t * data, size_t length) {
return encode(data, length, false);
}
static inline String encode(const String& text) {
return encode(text, false);
}
};
libb64: Base64 Encoding/Decoding Routines
======================================
Authors:
-------
Chris Venter chris.venter@gmail.com http://rocketpod.blogspot.com
Copyright-Only Dedication (based on United States law)
or Public Domain Certification
The person or persons who have associated work with this document (the
"Dedicator" or "Certifier") hereby either (a) certifies that, to the best of
his knowledge, the work of authorship identified is in the public domain of the
country from which the work is published, or (b) hereby dedicates whatever
copyright the dedicators holds in the work of authorship identified below (the
"Work") to the public domain. A certifier, moreover, dedicates any copyright
interest he may have in the associated work, and for these purposes, is
described as a "dedicator" below.
A certifier has taken reasonable steps to verify the copyright status of this
work. Certifier recognizes that his good faith efforts may not shield him from
liability if in fact the work certified is not in the public domain.
Dedicator makes this dedication for the benefit of the public at large and to
the detriment of the Dedicator's heirs and successors. Dedicator intends this
dedication to be an overt act of relinquishment in perpetuity of all present
and future rights under copyright law, whether vested or contingent, in the
Work. Dedicator understands that such relinquishment of all rights includes
the relinquishment of all rights to enforce (by lawsuit or otherwise) those
copyrights in the Work.
Dedicator recognizes that, once placed in the public domain, the Work may be
freely reproduced, distributed, transmitted, used, modified, built upon, or
otherwise exploited by anyone for any purpose, commercial or non-commercial,
and in any way, including by methods that have not yet been invented or
conceived.
\ No newline at end of file
/*
cdecoder.c - c source to a base64 decoding algorithm implementation
This is part of the libb64 project, and has been placed in the public domain.
For details, see http://sourceforge.net/projects/libb64
*/
#include <pgmspace.h>
#include <stdint.h>
#include "cdecode.h"
extern "C" {
static int base64_decode_value_signed(int8_t value_in) {
static const int8_t decoding[] PROGMEM = {62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51};
static const int8_t decoding_size = sizeof(decoding);
value_in -= 43;
if (value_in < 0 || value_in > decoding_size) {
return -1;
}
return pgm_read_byte(&decoding[(int)value_in]);
}
void base64_init_decodestate(base64_decodestate* state_in) {
state_in->step = step_a;
state_in->plainchar = 0;
}
static int base64_decode_block_signed(const int8_t* code_in, const int length_in, int8_t* plaintext_out, base64_decodestate* state_in) {
const int8_t* codechar = code_in;
int8_t* plainchar = plaintext_out;
int8_t fragment;
*plainchar = state_in->plainchar;
switch (state_in->step) {
while (1) {
case step_a:
do {
if (codechar == code_in + length_in) {
state_in->step = step_a;
state_in->plainchar = *plainchar;
return plainchar - plaintext_out;
}
fragment = (int8_t)base64_decode_value_signed(*codechar++);
} while (fragment < 0);
*plainchar = (fragment & 0x03f) << 2;
// falls through
case step_b:
do {
if (codechar == code_in + length_in) {
state_in->step = step_b;
state_in->plainchar = *plainchar;
return plainchar - plaintext_out;
}
fragment = (int8_t)base64_decode_value_signed(*codechar++);
} while (fragment < 0);
*plainchar++ |= (fragment & 0x030) >> 4;
*plainchar = (fragment & 0x00f) << 4;
// falls through
case step_c:
do {
if (codechar == code_in + length_in) {
state_in->step = step_c;
state_in->plainchar = *plainchar;
return plainchar - plaintext_out;
}
fragment = (int8_t)base64_decode_value_signed(*codechar++);
} while (fragment < 0);
*plainchar++ |= (fragment & 0x03c) >> 2;
*plainchar = (fragment & 0x003) << 6;
// falls through
case step_d:
do {
if (codechar == code_in + length_in) {
state_in->step = step_d;
state_in->plainchar = *plainchar;
return plainchar - plaintext_out;
}
fragment = (int8_t)base64_decode_value_signed(*codechar++);
} while (fragment < 0);
*plainchar++ |= (fragment & 0x03f);
}
}
/* control should not reach here */
return plainchar - plaintext_out;
}
static int base64_decode_chars_signed(const int8_t* code_in, const int length_in, int8_t* plaintext_out) {
base64_decodestate _state;
base64_init_decodestate(&_state);
int len = base64_decode_block_signed(code_in, length_in, plaintext_out, &_state);
if (len > 0) {
plaintext_out[len] = 0;
}
return len;
}
int base64_decode_value(char value_in) {
return base64_decode_value_signed(*((int8_t *) &value_in));
}
int base64_decode_block(const char* code_in, const int length_in, char* plaintext_out, base64_decodestate* state_in) {
return base64_decode_block_signed((int8_t *) code_in, length_in, (int8_t *) plaintext_out, state_in);
}
int base64_decode_chars(const char* code_in, const int length_in, char* plaintext_out) {
return base64_decode_chars_signed((int8_t *) code_in, length_in, (int8_t *) plaintext_out);
}
};
/*
cdecode.h - c header for a base64 decoding algorithm
This is part of the libb64 project, and has been placed in the public domain.
For details, see http://sourceforge.net/projects/libb64
*/
#ifndef BASE64_CDECODE_H
#define BASE64_CDECODE_H
#define base64_decode_expected_len(n) ((n * 3) / 4)
#ifdef __cplusplus
extern "C" {
#endif
typedef enum {
step_a, step_b, step_c, step_d
} base64_decodestep;
typedef struct {
base64_decodestep step;
char plainchar;
} base64_decodestate;
void base64_init_decodestate(base64_decodestate* state_in);
int base64_decode_value(char value_in);
int base64_decode_block(const char* code_in, const int length_in, char* plaintext_out, base64_decodestate* state_in);
int base64_decode_chars(const char* code_in, const int length_in, char* plaintext_out);
#ifdef __cplusplus
} // extern "C"
#endif
#endif /* BASE64_CDECODE_H */
/*
cencoder.c - c source to a base64 encoding algorithm implementation
This is part of the libb64 project, and has been placed in the public domain.
For details, see http://sourceforge.net/projects/libb64
*/
#include "cencode.h"
extern "C" {
void base64_init_encodestate(base64_encodestate* state_in) {
state_in->step = step_A;
state_in->result = 0;
state_in->stepcount = 0;
state_in->stepsnewline = BASE64_CHARS_PER_LINE;
}
void base64_init_encodestate_nonewlines(base64_encodestate* state_in) {
base64_init_encodestate(state_in);
state_in->stepsnewline = -1;
}
char base64_encode_value(const char n) {
char r;
if (n < 26) {
r = n + 'A';
} else if (n < 26 + 26) {
r = n - 26 + 'a';
} else if (n < 26 + 26 + 10) {
r = n - 26 - 26 + '0';
} else if (n == 62) {
r = '+';
} else {
r = '/';
}
return r;
}
int base64_encode_block(const char* plaintext_in, int length_in, char* code_out, base64_encodestate* state_in) {
const char* plainchar = plaintext_in;
const char* const plaintextend = plaintext_in + length_in;
char* codechar = code_out;
char result;
char fragment;
result = state_in->result;
switch (state_in->step) {
while (1) {
case step_A:
if (plainchar == plaintextend) {
state_in->result = result;
state_in->step = step_A;
return codechar - code_out;
}
fragment = *plainchar++;
result = (fragment & 0x0fc) >> 2;
*codechar++ = base64_encode_value(result);
result = (fragment & 0x003) << 4;
// falls through
case step_B:
if (plainchar == plaintextend) {
state_in->result = result;
state_in->step = step_B;
return codechar - code_out;
}
fragment = *plainchar++;
result |= (fragment & 0x0f0) >> 4;
*codechar++ = base64_encode_value(result);
result = (fragment & 0x00f) << 2;
// falls through
case step_C:
if (plainchar == plaintextend) {
state_in->result = result;
state_in->step = step_C;
return codechar - code_out;
}
fragment = *plainchar++;
result |= (fragment & 0x0c0) >> 6;
*codechar++ = base64_encode_value(result);
result = (fragment & 0x03f) >> 0;
*codechar++ = base64_encode_value(result);
++(state_in->stepcount);
if ((state_in->stepcount == BASE64_CHARS_PER_LINE / 4) && (state_in->stepsnewline > 0)) {
*codechar++ = '\n';
state_in->stepcount = 0;
}
}
}
/* control should not reach here */
return codechar - code_out;
}
int base64_encode_blockend(char* code_out, base64_encodestate* state_in) {
char* codechar = code_out;
switch (state_in->step) {
case step_B:
*codechar++ = base64_encode_value(state_in->result);
*codechar++ = '=';
*codechar++ = '=';
break;
case step_C:
*codechar++ = base64_encode_value(state_in->result);
*codechar++ = '=';
break;
case step_A:
break;
}
*codechar = 0x00;
return codechar - code_out;
}
int base64_encode_chars(const char* plaintext_in, int length_in, char* code_out) {
base64_encodestate _state;
base64_init_encodestate(&_state);
int len = base64_encode_block(plaintext_in, length_in, code_out, &_state);
return len + base64_encode_blockend((code_out + len), &_state);
}
};
/*
cencode.h - c header for a base64 encoding algorithm
This is part of the libb64 project, and has been placed in the public domain.
For details, see http://sourceforge.net/projects/libb64
*/
#ifndef BASE64_CENCODE_H
#define BASE64_CENCODE_H
#define BASE64_CHARS_PER_LINE 72
#define base64_encode_expected_len_nonewlines(n) ((((4 * (n)) / 3) + 3) & ~3)
#define base64_encode_expected_len(n) \
(base64_encode_expected_len_nonewlines(n) + ((n / ((BASE64_CHARS_PER_LINE * 3) / 4)) + 1))
#ifdef __cplusplus
extern "C" {
#endif
typedef enum {
step_A, step_B, step_C
} base64_encodestep;
typedef struct {
base64_encodestep step;
char result;
int stepcount;
int stepsnewline;
} base64_encodestate;
void base64_init_encodestate(base64_encodestate* state_in);
void base64_init_encodestate_nonewlines(base64_encodestate* state_in);
char base64_encode_value(char value_in);
int base64_encode_block(const char* plaintext_in, int length_in, char* code_out, base64_encodestate* state_in);
int base64_encode_blockend(char* code_out, base64_encodestate* state_in);
int base64_encode_chars(const char* plaintext_in, int length_in, char* code_out);
#ifdef __cplusplus
} // extern "C"
#endif
#endif /* BASE64_CENCODE_H */
......@@ -63,6 +63,26 @@ beginMulticast KEYWORD2
setTimeout KEYWORD2
waitSet KEYWORD2
setSession KEYWORD2
setInsecure KEYWORD2
setKnownKey KEYWORD2
setFingerprint KEYWORD2
allowSelfSignedCerts KEYWORD2
setTrustAnchors KEYWORD2
setX509Time KEYWORD2
setClientRSACert KEYWORD2
setClientECCert KEYWORD2
setBufferSizes KEYWORD2
setCertStore KEYWORD2
setCiphers KEYWORD2
setCiphersLessSecure KEYWORD2
setSSLVersion KEYWORD2
setCACert KEYWORD2
setCertificate KEYWORD2
setPrivateKey KEYWORD2
loadCACert KEYWORD2
loadCertificate KEYWORD2
loadPrivateKey KEYWORD2
#######################################
# Constants (LITERAL1)
......
......@@ -53,6 +53,7 @@ bool WiFiMulti::addAP(const char *ssid, const char *pass) {
} else {
ap.pass = nullptr;
}
DEBUGV("[WIFIMULTI] Adding: '%s' %s' to list\n", ap.ssid, ap.pass);
_list.push_front(ap);
return true;
}
......@@ -77,6 +78,7 @@ uint8_t WiFiMulti::run(uint32_t to) {
for (int i = 0; i < cnt; i++) {
if (WiFi.RSSI(i) > maxRSSID) {
for (auto j = _list.begin(); j != _list.end(); j++) {
DEBUGV("[WIFIMULTI] Checking for '%s' at %d\n", WiFi.SSID(i), WiFi.RSSI(i));
if (!strcmp(j->ssid, WiFi.SSID(i))) {
hit = j;
maxRSSID = WiFi.RSSI(i);
......@@ -90,6 +92,7 @@ uint8_t WiFiMulti::run(uint32_t to) {
}
// Connect!
DEBUGV("[WIFIMULTI] Connecting to '%s' and '%s'\n", hit->ssid, hit->pass);
uint32_t start = millis();
if (hit->pass) {
WiFi.begin(hit->ssid, hit->pass);
......
......@@ -32,7 +32,7 @@ public:
bool addAP(const char *ssid, const char *pass = NULL);
uint8_t run(uint32_t to = 5000);
uint8_t run(uint32_t to = 10000);
private:
struct _AP {
......
......@@ -7,7 +7,7 @@ for dir in ./cores/rp2040 ./libraries/EEPROM ./libraries/I2S \
./libraries/WiFi ./libraries/lwIP_Ethernet ./libraries/lwIP_CYW43 \
./libraries/FreeRTOS/src ./libraries/LEAmDNS ./libraries/MD5Builder \
./libraries/PicoOTA ./libraries/SDFS ./libraries/ArduinoOTA \
./libraries/Updater; do
./libraries/Updater ./libraries/HTTPClient; do
find $dir -type f \( -name "*.c" -o -name "*.h" -o -name "*.cpp" \) -a \! -path '*api*' -exec astyle --suffix=none --options=./tests/astyle_core.conf \{\} \;
find $dir -type f -name "*.ino" -exec astyle --suffix=none --options=./tests/astyle_examples.conf \{\} \;
done
......
......@@ -65,9 +65,7 @@ def compile(tmp_dir, sketch, cache, tools_dir, hardware_dir, ide_path, f, args):
'dbgport={dbgport},' \
'dbglvl={dbglvl},' \
'usbstack={usbstack}'.format(**vars(args))
if "/WiFi" in sketch:
fqbn = fqbn.replace("rpipico", "rpipicow")
if "/ArduinoOTA" in sketch:
if ("/WiFi" in sketch) or ("/ArduinoOTA" in sketch) or ("/HTTPClient" in sketch):
fqbn = fqbn.replace("rpipico", "rpipicow")
cmd += [fqbn]
cmd += ['-built-in-libraries', ide_path + '/libraries']
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment