14#if PC_ENABLE_HTTP_CLIENT
29#if defined(ARDUINO) && PC_ENABLE_HTTP_CLIENT_TLS
31#include <mbedtls/ssl.h>
33bool http_client_parse_url(
const char *url,
bool *is_https,
char *host,
size_t host_cap, uint16_t *port,
char *path,
36 if (!url || !is_https || !host || !port || !path)
42 if (strncmp(p,
"https://", 8) == 0)
48 else if (strncmp(p,
"http://", 7) == 0)
61 while (*p && *p !=
':' && *p !=
'/')
65 size_t hlen = (size_t)(p - h);
66 if (hlen == 0 || hlen >= host_cap)
70 memcpy(host, h, hlen);
77 if (*p <
'0' || *p >
'9')
82 while (*p >=
'0' && *p <=
'9')
84 pn = pn * 10 + (uint32_t)(*p++ -
'0');
105 size_t plen = strnlen(p, path_cap + 1);
106 if (plen >= path_cap)
110 memcpy(path, p, plen + 1);
115size_t http_client_build_request(
const char *method,
const char *host, uint16_t port,
const char *path,
116 const char *content_type,
const uint8_t *body,
size_t body_len,
char *out,
size_t cap)
118 if (!method || !host || !path || !out || cap == 0)
125 if (port == 80 || port == 443)
127 pc_sb sb_hosthdr = {hosthdr,
sizeof(hosthdr), 0,
true};
136 pc_sb sb_hosthdr2 = {hosthdr,
sizeof(hosthdr), 0,
true};
139 pc_sb_u32(&sb_hosthdr2, (uint32_t)((
unsigned)port));
147 if (body && body_len)
149 pc_sb sb_out = {out, cap, 0,
true};
153 pc_sb_put(&sb_out,
" HTTP/1.1\r\nHost: ");
155 pc_sb_put(&sb_out,
"\r\nUser-Agent: PC\r\nContent-Type: ");
156 pc_sb_put(&sb_out, content_type ? content_type : PC_MIME_OCTET_STREAM);
157 pc_sb_put(&sb_out,
"\r\nContent-Length: ");
158 pc_sb_u32(&sb_out, (uint32_t)((
unsigned)body_len));
159 pc_sb_put(&sb_out,
"\r\nConnection: close\r\n\r\n");
165 if (!sb_out.
ok || (
size_t)n + body_len > cap)
169 memcpy(out + n, body, body_len);
170 return (
size_t)n + body_len;
173 pc_sb sb_out2 = {out, cap, 0,
true};
177 pc_sb_put(&sb_out2,
" HTTP/1.1\r\nHost: ");
179 pc_sb_put(&sb_out2,
"\r\nUser-Agent: PC\r\nConnection: close\r\n\r\n");
191static const char *find_header(
const uint8_t *buf,
const uint8_t *end,
const char *name)
193 size_t nlen = strnlen(name, (
size_t)(end - buf) + 1);
194 const uint8_t *p = buf;
195 while (p + nlen + 1 < end)
198 if (strncasecmp((
const char *)p, name, nlen) == 0 && p[nlen] ==
':')
200 const uint8_t *v = p + nlen + 1;
201 while (v < end && (*v ==
' ' || *v ==
'\t'))
205 return (
const char *)v;
208 while (p < end && *p !=
'\n')
220int http_client_parse_response(uint8_t *buf,
size_t len,
size_t *body_off,
size_t *body_len)
222 if (!buf || len < 12 || memcmp(buf,
"HTTP/1.", 7) != 0)
224 return (
int)HttpClientError::HTTP_CLIENT_ERR_RESPONSE;
228 const uint8_t *sp = (
const uint8_t *)memchr(buf,
' ', len);
229 if (!sp || sp + 4 > buf + len)
231 return (
int)HttpClientError::HTTP_CLIENT_ERR_RESPONSE;
233 int status = (sp[1] -
'0') * 100 + (sp[2] -
'0') * 10 + (sp[3] -
'0');
234 if (status < 100 || status > 599)
236 return (
int)HttpClientError::HTTP_CLIENT_ERR_RESPONSE;
240 uint8_t *hdr_end =
nullptr;
241 for (
size_t i = 0; i + 3 < len; i++)
243 if (buf[i] ==
'\r' && buf[i + 1] ==
'\n' && buf[i + 2] ==
'\r' && buf[i + 3] ==
'\n')
251 return (
int)HttpClientError::HTTP_CLIENT_ERR_RESPONSE;
254 size_t off = (size_t)(hdr_end + 4 - buf);
255 size_t avail = len - off;
257 const char *te = find_header(buf, hdr_end,
"Transfer-Encoding");
258 if (te && strncasecmp(te,
"chunked", 7) == 0)
261 size_t in = off, out = off;
267 while (in < len && buf[in] !=
'\r')
269 uint8_t c = buf[in++];
271 if (c >=
'0' && c <=
'9')
275 else if (c >=
'a' && c <=
'f')
279 else if (c >=
'A' && c <=
'F')
287 csz = csz * 16 + (size_t)d;
291 while (in < len && buf[in] !=
'\n')
314 memmove(buf + out, buf + in, csz);
318 while (in < len && (buf[in] ==
'\r' || buf[in] ==
'\n'))
324 *body_len = out - off;
328 const char *cl = find_header(buf, hdr_end,
"Content-Length");
336 size_t want = (size_t)n;
338 *body_len = (want < avail) ? want : avail;
355#ifdef PC_HTTP_CLIENT_DEBUG
356#define CL_DBG(...) printf(__VA_ARGS__)
358#define CL_DBG(...) ((void)0)
370static HttpClientCtx s_http;
372#if PC_ENABLE_HTTP_CLIENT_TLS
375static int cl_tls_send(
void *ctx,
const unsigned char *buf,
size_t len)
378 size_t cap = len > 0xFFFF ? 0xFFFF : len;
381static int cl_tls_recv(
void *ctx,
unsigned char *buf,
size_t len)
394static int http_request(
const char *method,
const char *url,
const char *content_type,
const uint8_t *body,
395 size_t body_len, HttpClientResult *out)
405 char host[80], path[160];
407 if (!http_client_parse_url(url, &is_https, host,
sizeof(host), &port, path,
sizeof(path)))
409 return (
int)HttpClientError::HTTP_CLIENT_ERR_URL;
411 CL_DBG(
"[hc] url host=%s port=%u https=%d path=%s\n", host, (
unsigned)port, (
int)is_https, path);
412#if !PC_ENABLE_HTTP_CLIENT_TLS
415 return (
int)HttpClientError::HTTP_CLIENT_ERR_TLS;
421 size_t reqlen = http_client_build_request(method, host, port, path, content_type, body, body_len, req,
sizeof(req));
424 return (
int)HttpClientError::HTTP_CLIENT_ERR_URL;
431 CL_DBG(
"[hc] pc_client_open cid=%d\n", s_http.cid);
434 return (s_http.cid == -2) ? (int)HttpClientError::HTTP_CLIENT_ERR_DNS
435 : (int)HttpClientError::HTTP_CLIENT_ERR_CONNECT;
440 size_t pc_resp_len = 0;
444#if PC_ENABLE_HTTP_CLIENT_TLS
445 int rc = pc_tls_client_run(host, (
const uint8_t *)req, reqlen, s_http.rx,
sizeof(s_http.rx), &pc_resp_len,
446 cl_tls_send, cl_tls_recv, deadline);
447 CL_DBG(
"[hc] tls rc=%d pt_len=%u\n", rc, (
unsigned)pc_resp_len);
452 return (
int)HttpClientError::HTTP_CLIENT_ERR_TLS;
457 return (
int)HttpClientError::HTTP_CLIENT_ERR_TLS;
468 return (
int)HttpClientError::HTTP_CLIENT_ERR_SEND;
470 while ((int32_t)(deadline - millis()) > 0)
472 size_t n =
pc_client_read(s_http.cid, s_http.rx + pc_resp_len,
sizeof(s_http.rx) - pc_resp_len);
474 if (pc_resp_len >=
sizeof(s_http.rx))
489 CL_DBG(
"[hc] done pc_resp_len=%u\n", (
unsigned)pc_resp_len);
493 if (pc_resp_len == 0)
495 return (
int)HttpClientError::HTTP_CLIENT_ERR_TIMEOUT;
498 size_t body_off = 0, blen = 0;
499 int status = http_client_parse_response(s_http.rx, pc_resp_len, &body_off, &blen);
502 return (
int)HttpClientError::HTTP_CLIENT_ERR_RESPONSE;
506 out->status = status;
507 out->body = s_http.rx + body_off;
508 out->body_len = blen;
513int http_get(
const char *url, HttpClientResult *out)
515 return http_request(
"GET", url,
nullptr,
nullptr, 0, out);
518int http_post(
const char *url,
const char *content_type,
const uint8_t *body,
size_t body_len, HttpClientResult *out)
520 return http_request(
"POST", url, content_type, body, body_len, out);
523void http_client_set_ca(
const uint8_t *ca,
size_t ca_len)
525#if PC_ENABLE_HTTP_CLIENT_TLS
526 pc_tls_client_set_ca(ca, ca_len);
532void http_client_set_pin(
const uint8_t sha256[32])
534#if PC_ENABLE_HTTP_CLIENT_TLS
535 pc_tls_client_set_pin(sha256);
540void http_client_clear_verify()
542#if PC_ENABLE_HTTP_CLIENT_TLS
543 pc_tls_client_clear_verify();
549int http_get(
const char *, HttpClientResult *)
551 return (
int)HttpClientError::HTTP_CLIENT_ERR_CONNECT;
553int http_post(
const char *,
const char *,
const uint8_t *,
size_t, HttpClientResult *)
555 return (
int)HttpClientError::HTTP_CLIENT_ERR_CONNECT;
557void http_client_set_ca(
const uint8_t *,
size_t)
560void http_client_set_pin(
const uint8_t *)
563void http_client_clear_verify()
bool pc_client_send(int, const void *, size_t)
Queue len wire bytes for transmission (marshaled tcp_write + output).
size_t pc_client_read(int, uint8_t *, size_t)
Drain up to cap buffered wire bytes into buf; returns the count.
bool pc_client_is_closed(int)
True once the peer closed (FIN) or the connection errored.
int pc_client_open(const char *, uint16_t, uint32_t)
Resolve host (dotted-quad fast path, else DNS) and connect to host : port, blocking up to timeout_ms.
size_t pc_client_available(int)
Wire bytes currently buffered and ready to read.
void pc_client_close(int)
Tear down the connection (marshaled) and return the slot to the pool.
Layer 4 outbound TCP client transport - the client-side peer of the (server) transport in tcp....
Pluggable monotonic clock for all library timing.
void pcdelay(uint32_t ms)
Block for at least ms milliseconds - the library's single delay primitive.
Zero-heap outbound HTTP(S) client over raw lwIP (PC_ENABLE_HTTP_CLIENT).
Shared HTTP Content-Type ("MIME") string constants (one source of truth).
Tiny no-stdlib base-10 number parsers (strtol/strtoul/strtof replacements).
long pc_strtol(const char *s, const char **end)
Parse a base-10 long; sets end past the digits (or to s if none).
#define PC_HTTP_CLIENT_TIMEOUT_MS
Outbound HTTP client connect/response timeout in milliseconds.
#define PC_HTTP_CLIENT_BUF_SIZE
Receive buffer (and max response size) for the outbound HTTP client, bytes.
Bounded no-heap string builder that fails closed on overflow (one shared copy).
size_t pc_sb_finish(pc_sb *b)
NUL-terminate and return the built length, or 0 if the build overflowed.
void pc_sb_put(pc_sb *b, const char *s)
Append NUL-terminated s; leaves the buffer untouched and clears ok if it would not fit.
void pc_sb_u32(pc_sb *b, uint32_t v)
Append v as decimal (no leading zeros; "0" for zero).
Bump-append target; ok latches false once an append would overflow cap.
Deterministic TLS engine: mbedTLS over a static memory pool (PC_ENABLE_TLS).