Public Access
The announcement was cut at the notification's 48 bytes; it now reads "v0.11.0 is out: see Settings > Firmware". README: Updates from Gitea and its limits. ADR 0009: the device trusts the two ISRG roots. R1.md: what was built and the checks on the device, with what wasn't checked. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
112 lines
4.1 KiB
C++
112 lines
4.1 KiB
C++
#include "platform/https_get.h"
|
|
|
|
#include <Arduino.h>
|
|
#include <esp_heap_caps.h>
|
|
|
|
#include <algorithm>
|
|
#include <ctime>
|
|
|
|
#include "platform/ca_roots.h"
|
|
#include "version.h"
|
|
|
|
namespace roro {
|
|
|
|
namespace {
|
|
constexpr time_t kClockSetAfter = 1700000000; // 2023-11: anything earlier is the clock's default
|
|
|
|
// What mbedTLS says in the way it says it, for the cases a person can act on.
|
|
std::string whyNotConnected(NetworkClientSecure& tls, const std::string& host) {
|
|
char text[100] = "";
|
|
int err = tls.lastError(text, sizeof text);
|
|
std::string detail = text;
|
|
if (detail.find("X509") != std::string::npos || detail.find("certificate") != std::string::npos)
|
|
return "The certificate of " + host + " isn't accepted";
|
|
if (err != 0 && !detail.empty()) return "TLS to " + host + ": " + detail;
|
|
return "Can't connect to " + host;
|
|
}
|
|
} // namespace
|
|
|
|
std::string HttpsGet::open(const std::string& host, const std::string& path, const char* accept) {
|
|
close();
|
|
if (time(nullptr) < kClockSetAfter) return "The clock isn't set: can't check certificates";
|
|
if (esp_get_free_heap_size() < kNeedFree) return "Not enough memory for a secure connection";
|
|
|
|
tls_.setCACert(kTrustedRootsPem);
|
|
tls_.setTimeout(15);
|
|
if (!tls_.connect(host.c_str(), 443)) return whyNotConnected(tls_, host);
|
|
|
|
raw_.reset(new (std::nothrow) uint8_t[kRaw]);
|
|
if (!raw_) return "Not enough memory";
|
|
tls_.print(("GET " + path + " HTTP/1.1\r\nHost: " + host + "\r\nUser-Agent: roro9stack/" + versionString() +
|
|
"\r\nAccept: " + accept + "\r\nAccept-Encoding: identity\r\nConnection: close\r\n\r\n")
|
|
.c_str());
|
|
|
|
release::HttpHeadParser parser;
|
|
while (!parser.complete()) {
|
|
int n = readRaw(raw_.get(), kRaw);
|
|
if (n <= 0) return "The server " + host + " stopped answering";
|
|
size_t used = parser.feed(reinterpret_cast<const char*>(raw_.get()), n);
|
|
if (parser.failed()) return host + " didn't answer with HTTP";
|
|
if (parser.complete() && used < static_cast<size_t>(n)) early_.assign(reinterpret_cast<const char*>(raw_.get()) + used, n - used);
|
|
}
|
|
head_ = parser.head();
|
|
if (head_.status != 200) return host + " answered " + std::to_string(head_.status) + (head_.status / 100 == 3 ? " (a redirect)" : "");
|
|
left_ = head_.chunked ? -1 : head_.contentLength;
|
|
return "";
|
|
}
|
|
|
|
int HttpsGet::readRaw(uint8_t* into, size_t len) {
|
|
uint32_t since = millis();
|
|
while (!tls_.available()) {
|
|
if (!tls_.connected()) return 0;
|
|
if (millis() - since > kStallMs) return -1;
|
|
delay(5);
|
|
}
|
|
return tls_.read(into, len);
|
|
}
|
|
|
|
int HttpsGet::read(uint8_t* buf, size_t len) {
|
|
if (done_ || len == 0) return 0;
|
|
if (!head_.chunked && left_ == 0) return done_ = true, 0;
|
|
|
|
for (;;) {
|
|
// Raw bytes: first those that came with the head, then the connection's.
|
|
size_t want = head_.chunked ? std::min(len, kRaw) : len;
|
|
if (!head_.chunked && left_ > 0) want = std::min<size_t>(want, left_);
|
|
int n;
|
|
if (earlyAt_ < early_.size()) {
|
|
n = static_cast<int>(std::min(want, early_.size() - earlyAt_));
|
|
std::copy(early_.data() + earlyAt_, early_.data() + earlyAt_ + n, head_.chunked ? raw_.get() : buf);
|
|
earlyAt_ += n;
|
|
} else {
|
|
n = readRaw(head_.chunked ? raw_.get() : buf, want);
|
|
}
|
|
if (n < 0) return -1;
|
|
if (n == 0) { // the server closed: the end, if it's where it said it would be
|
|
done_ = true;
|
|
bool whole = head_.chunked ? chunks_.done() : left_ <= 0;
|
|
return whole ? 0 : -1;
|
|
}
|
|
if (!head_.chunked) {
|
|
if (left_ > 0) left_ -= n;
|
|
return n;
|
|
}
|
|
size_t out = chunks_.decode(raw_.get(), n, buf);
|
|
if (chunks_.failed()) return -1;
|
|
if (out > 0) return static_cast<int>(out);
|
|
if (chunks_.done()) return done_ = true, 0;
|
|
}
|
|
}
|
|
|
|
void HttpsGet::close() {
|
|
tls_.stop();
|
|
raw_.reset();
|
|
std::string().swap(early_);
|
|
earlyAt_ = 0;
|
|
done_ = false;
|
|
head_ = release::HttpHead();
|
|
chunks_ = release::ChunkedDecoder();
|
|
}
|
|
|
|
} // namespace roro
|