Public Access
Updates from Gitea, step 2: the connection (check and list work on the device)
The roots (ISRG X1 and X2), an HTTPS client that reads the answer as a stream, GiteaReleases (the latest and a list of ten), the Update Service's requests, install from Gitea through the existing install path, the daily check's schedule, the Check for updates setting, and console commands: update check | list | status | install <tag>. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
#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 size_t kFloor = 55 * 1024; // Q86, Q172: no connection below this
|
||||
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 yet: certificates can't be checked";
|
||||
if (esp_get_free_heap_size() < kFloor) return "Not enough memory: close IRC or a Gemini page";
|
||||
|
||||
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
|
||||
Reference in New Issue
Block a user