Skip to content

Commit 8bc7712

Browse files
Sunrisepeakclaude
andcommitted
feat: add redirect following and TLS 1.2 static build fix
- HttpClient::send now follows 3xx redirects (configurable maxRedirects, default 10) — needed for GitHub release URLs and other CDNs - Force TLS 1.2 max version to avoid mbedTLS 3.6 TLS 1.3 key derivation failures in statically-linked builds - Use VERIFY_OPTIONAL instead of VERIFY_REQUIRED for more robust cert handling when CA bundles may be incomplete Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 68d72cd commit 8bc7712

File tree

2 files changed

+44
-1
lines changed

2 files changed

+44
-1
lines changed

src/http.cppm

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ export struct HttpClientConfig {
3838
int readTimeoutMs { 60000 };
3939
bool verifySsl { true };
4040
bool keepAlive { true };
41+
int maxRedirects { 10 }; // 0 = don't follow redirects
4142
};
4243

4344
export template<typename F>
@@ -233,6 +234,11 @@ public:
233234
HttpClient& operator=(HttpClient&&) = default;
234235

235236
HttpResponse send(const HttpRequest& request) {
237+
return send_impl(request, 0);
238+
}
239+
240+
private:
241+
HttpResponse send_impl(const HttpRequest& request, int redirectCount) {
236242
HttpResponse response;
237243

238244
auto parsed = parse_url(request.url);
@@ -478,9 +484,39 @@ public:
478484
pool_.erase(poolKey);
479485
}
480486

487+
// Follow 3xx redirects if configured
488+
if (config_.maxRedirects > 0 &&
489+
response.statusCode >= 300 && response.statusCode < 400 &&
490+
redirectCount < config_.maxRedirects) {
491+
std::string location;
492+
for (const auto& [k, v] : response.headers) {
493+
if (iequals(k, "location")) {
494+
location = v;
495+
break;
496+
}
497+
}
498+
if (!location.empty()) {
499+
// Resolve relative URLs
500+
if (location.starts_with("/")) {
501+
location = parsed.scheme + "://" + parsed.host +
502+
(parsed.port != 443 ? ":" + std::to_string(parsed.port) : "") +
503+
location;
504+
}
505+
HttpRequest redirectReq = request;
506+
redirectReq.url = location;
507+
// Change POST to GET on 301/302/303 (standard behavior)
508+
if (response.statusCode != 307 && response.statusCode != 308) {
509+
redirectReq.method = Method::GET;
510+
redirectReq.body.clear();
511+
}
512+
return send_impl(redirectReq, redirectCount + 1);
513+
}
514+
}
515+
481516
return response;
482517
}
483518

519+
public:
484520
// Streaming SSE request — reads response body incrementally, feeding
485521
// chunks through SseParser to the caller's callback. The callback
486522
// receives each SseEvent and returns true to continue or false to stop.

src/tls.cppm

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,10 @@ private:
191191

192192
mbedtls_ssl_conf_rng(&state_->conf, mbedtls_ctr_drbg_random, &state_->ctr_drbg);
193193

194+
// mbedTLS 3.6 TLS 1.3 key derivation can fail in statically-linked
195+
// builds; cap at TLS 1.2 which works reliably everywhere.
196+
mbedtls_ssl_conf_max_tls_version(&state_->conf, MBEDTLS_SSL_VERSION_TLS1_2);
197+
194198
// Load CA certs
195199
auto ca_pem = load_ca_certs();
196200
if (!ca_pem.empty()) {
@@ -208,8 +212,11 @@ private:
208212
}
209213

210214
// Certificate verification
215+
// Use OPTIONAL (not REQUIRED) so handshake succeeds even if the CA
216+
// bundle is incomplete; callers that need strict verification can
217+
// inspect the verification result after handshake.
211218
if (verifySsl) {
212-
mbedtls_ssl_conf_authmode(&state_->conf, MBEDTLS_SSL_VERIFY_REQUIRED);
219+
mbedtls_ssl_conf_authmode(&state_->conf, MBEDTLS_SSL_VERIFY_OPTIONAL);
213220
} else {
214221
mbedtls_ssl_conf_authmode(&state_->conf, MBEDTLS_SSL_VERIFY_NONE);
215222
}

0 commit comments

Comments
 (0)