57int n_http3_parse_url(
const char* url,
char* host,
size_t hostsz,
char*
port,
size_t portsz,
char* path,
size_t pathsz) {
59 const char* host_start;
65 if (!url || strncmp(url,
"https://", 8) != 0) {
70 slash = strchr(p,
'/');
71 hostend = slash ? slash : (p + strlen(p));
74 if (*host_start ==
'[') {
75 const char* rb = memchr(host_start,
']', (
size_t)(hostend - host_start));
79 hlen = (size_t)(rb - host_start - 1);
81 colon = (rb + 1 < hostend && rb[1] ==
':') ? (rb + 1) : NULL;
83 colon = memchr(host_start,
':', (
size_t)(hostend - host_start));
84 hlen = colon ? (size_t)(colon - host_start) : (size_t)(hostend - host_start);
90 if (hlen + 1 > hostsz) {
93 memcpy(host, host_start, hlen);
98 plen = (size_t)(hostend - colon - 1);
99 if (plen == 0 || plen + 1 > portsz) {
102 memcpy(
port, colon + 1, plen);
108 memcpy(
port,
"443", 4);
113 plen = strlen(slash);
114 if (plen + 1 > pathsz) {
117 memcpy(path, slash, plen + 1);
122 memcpy(path,
"/", 2);
138#include <ngtcp2/ngtcp2.h>
139#include <ngtcp2/ngtcp2_crypto.h>
140#include <ngtcp2/ngtcp2_crypto_ossl.h>
141#include <nghttp3/nghttp3.h>
143#include <openssl/err.h>
144#include <openssl/rand.h>
145#include <openssl/ssl.h>
157#define H3_MAX_HEADERS 256
159#define H3_SEND_BUF 1500
161#define H3_RECV_BUF 65536
164typedef struct h3_body {
170typedef struct h3_hdr {
176typedef struct h3_client {
179 ngtcp2_crypto_conn_ref conn_ref;
182 ngtcp2_crypto_ossl_ctx* ossl_ctx;
185 struct sockaddr_storage local_addr;
186 socklen_t local_addrlen;
187 struct sockaddr_storage remote_addr;
188 socklen_t remote_addrlen;
195 const char* authority;
198 const char* extra_headers;
204 h3_hdr headers[H3_MAX_HEADERS];
216static uint64_t h3_now_ns(
void) {
218 clock_gettime(CLOCK_MONOTONIC, &ts);
219 return (uint64_t)ts.tv_sec * NGTCP2_SECONDS + (uint64_t)ts.tv_nsec;
222static int h3_set_nonblocking(
SOCKET fd) {
225 return ioctlsocket(fd, (
long)FIONBIO, &on) == 0 ? 0 : -1;
227 int fl = fcntl(fd, F_GETFL, 0);
231 return fcntl(fd, F_SETFL, fl | O_NONBLOCK) == 0 ? 0 : -1;
235static int h3_would_block(
void) {
237 int e = WSAGetLastError();
238 return e == WSAEWOULDBLOCK || e == WSAEINTR;
240 return errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR;
245static int h3_poll_read(
SOCKET fd,
int timeout_ms) {
249 pfd.events = POLLRDNORM;
251 return WSAPoll(&pfd, 1, timeout_ms);
257 return poll(&pfd, 1, timeout_ms);
261static char* h3_dupn(
const uint8_t* s,
size_t n) {
262 char* p = malloc(n + 1);
277static ngtcp2_conn* h3_get_conn(ngtcp2_crypto_conn_ref* ref) {
278 const h3_client* c = (
const h3_client*)ref->user_data;
282static void h3_rand_cb(uint8_t* dest,
size_t destlen,
const ngtcp2_rand_ctx* rctx) {
284 if (RAND_bytes(dest, (
int)destlen) != 1) {
286 for (i = 0; i < destlen; i++) {
287 dest[i] = (uint8_t)rand();
292static int h3_get_new_cid_cb(ngtcp2_conn* conn, ngtcp2_cid* cid, uint8_t* token,
size_t cidlen,
void* user_data) {
295 if (RAND_bytes(cid->data, (
int)cidlen) != 1) {
296 return NGTCP2_ERR_CALLBACK_FAILURE;
298 cid->datalen = cidlen;
299 if (RAND_bytes(token, NGTCP2_STATELESS_RESET_TOKENLEN) != 1) {
300 return NGTCP2_ERR_CALLBACK_FAILURE;
305static int h3_handshake_completed_cb(ngtcp2_conn* conn,
void* user_data) {
307 ((h3_client*)user_data)->handshake_done = 1;
311static int h3_recv_stream_data_cb(ngtcp2_conn* conn, uint32_t flags, int64_t stream_id, uint64_t offset,
const uint8_t* data,
size_t datalen,
void* user_data,
void* stream_user_data) {
312 h3_client* c = (h3_client*)user_data;
313 int fin = (flags & NGTCP2_STREAM_DATA_FLAG_FIN) != 0;
314 nghttp3_ssize nconsumed;
316 (void)stream_user_data;
320 nconsumed = nghttp3_conn_read_stream(c->h3, stream_id, data, datalen, fin);
322 n_log(
LOG_ERR,
"nghttp3_conn_read_stream: %s", nghttp3_strerror((
int)nconsumed));
323 return NGTCP2_ERR_CALLBACK_FAILURE;
325 ngtcp2_conn_extend_max_stream_offset(conn, stream_id, (uint64_t)nconsumed);
326 ngtcp2_conn_extend_max_offset(conn, (uint64_t)nconsumed);
330static int h3_acked_stream_data_offset_cb(ngtcp2_conn* conn, int64_t stream_id, uint64_t offset, uint64_t datalen,
void* user_data,
void* stream_user_data) {
331 h3_client* c = (h3_client*)user_data;
334 (void)stream_user_data;
336 nghttp3_conn_add_ack_offset(c->h3, stream_id, datalen);
341static int h3_stream_close_cb(ngtcp2_conn* conn, uint32_t flags, int64_t stream_id, uint64_t app_error_code,
void* user_data,
void* stream_user_data) {
342 h3_client* c = (h3_client*)user_data;
344 (void)stream_user_data;
345 if (!(flags & NGTCP2_STREAM_CLOSE_FLAG_APP_ERROR_CODE_SET)) {
346 app_error_code = NGHTTP3_H3_NO_ERROR;
349 int rv = nghttp3_conn_close_stream(c->h3, stream_id, app_error_code);
350 if (rv != 0 && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) {
351 n_log(
LOG_ERR,
"nghttp3_conn_close_stream: %s", nghttp3_strerror(rv));
352 return NGTCP2_ERR_CALLBACK_FAILURE;
355 if (stream_id == c->stream_id) {
361static int h3_stream_reset_cb(ngtcp2_conn* conn, int64_t stream_id, uint64_t final_size, uint64_t app_error_code,
void* user_data,
void* stream_user_data) {
362 h3_client* c = (h3_client*)user_data;
365 (void)app_error_code;
366 (void)stream_user_data;
368 nghttp3_conn_shutdown_stream_read(c->h3, stream_id);
377static int h3_recv_header_cb(nghttp3_conn* conn, int64_t stream_id, int32_t token, nghttp3_rcbuf* name, nghttp3_rcbuf* value, uint8_t flags,
void* conn_user_data,
void* stream_user_data) {
378 h3_client* c = (h3_client*)conn_user_data;
379 nghttp3_vec n = nghttp3_rcbuf_get_buf(name);
380 nghttp3_vec v = nghttp3_rcbuf_get_buf(value);
384 (void)stream_user_data;
385 if (token == NGHTTP3_QPACK_TOKEN__STATUS || (n.len == 7 && memcmp(n.base,
":status", 7) == 0)) {
386 snprintf(c->status,
sizeof(c->status),
"%.*s", (
int)v.len, (
const char*)v.base);
389 if (c->nheaders < H3_MAX_HEADERS) {
390 char* hn = h3_dupn(n.base, n.len);
391 char* hv = h3_dupn(v.base, v.len);
393 c->headers[c->nheaders].name = hn;
394 c->headers[c->nheaders].value = hv;
405static int h3_recv_data_cb(nghttp3_conn* conn, int64_t stream_id,
const uint8_t* data,
size_t datalen,
void* conn_user_data,
void* stream_user_data) {
406 h3_client* c = (h3_client*)conn_user_data;
409 (void)stream_user_data;
410 if (c->body_len + datalen > c->body_cap) {
411 size_t newcap = c->body_cap ? c->body_cap : 65536;
413 while (newcap < c->body_len + datalen) {
419 if (c->body_len + datalen > newcap) {
420 datalen = newcap - c->body_len;
425 nb = realloc(c->body, newcap);
428 return NGHTTP3_ERR_CALLBACK_FAILURE;
431 c->body_cap = newcap;
434 memcpy(c->body + c->body_len, data, datalen);
435 c->body_len += datalen;
440static int h3_deferred_consume_cb(nghttp3_conn* conn, int64_t stream_id,
size_t consumed,
void* conn_user_data,
void* stream_user_data) {
441 h3_client* c = (h3_client*)conn_user_data;
443 (void)stream_user_data;
444 ngtcp2_conn_extend_max_stream_offset(c->conn, stream_id, consumed);
445 ngtcp2_conn_extend_max_offset(c->conn, consumed);
449static int h3_end_stream_cb(nghttp3_conn* conn, int64_t stream_id,
void* conn_user_data,
void* stream_user_data) {
450 h3_client* c = (h3_client*)conn_user_data;
452 (void)stream_user_data;
453 if (stream_id == c->stream_id) {
459static int h3_stream_close_h3_cb(nghttp3_conn* conn, int64_t stream_id, uint64_t app_error_code,
void* conn_user_data,
void* stream_user_data) {
460 h3_client* c = (h3_client*)conn_user_data;
462 (void)app_error_code;
463 (void)stream_user_data;
464 if (stream_id == c->stream_id) {
471static nghttp3_ssize h3_body_read_cb(nghttp3_conn* conn, int64_t stream_id, nghttp3_vec* vec,
size_t veccnt, uint32_t* pflags,
void* conn_user_data,
void* stream_user_data) {
472 h3_client* c = (h3_client*)conn_user_data;
476 (void)stream_user_data;
477 *pflags = NGHTTP3_DATA_FLAG_EOF;
478 if (c->req_body.len == 0) {
481 vec[0].base = (uint8_t*)c->req_body.data;
482 vec[0].len = c->req_body.len;
490static SOCKET h3_udp_connect(
const char* host,
const char*
port,
struct sockaddr_storage* remote, socklen_t* remotelen) {
491 struct addrinfo hints;
492 struct addrinfo* res = NULL;
494 SOCKET fd = INVALID_SOCKET;
496 memset(&hints, 0,
sizeof(hints));
497 hints.ai_family = AF_UNSPEC;
498 hints.ai_socktype = SOCK_DGRAM;
499 hints.ai_protocol = IPPROTO_UDP;
500 rv = getaddrinfo(host,
port, &hints, &res);
503 return INVALID_SOCKET;
505 for (rp = res; rp; rp = rp->ai_next) {
506 fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
507 if (fd == INVALID_SOCKET) {
510 if (connect(fd, rp->ai_addr, (socklen_t)rp->ai_addrlen) == 0) {
511 memcpy(remote, rp->ai_addr, rp->ai_addrlen);
512 *remotelen = (socklen_t)rp->ai_addrlen;
519 if (fd == INVALID_SOCKET) {
521 return INVALID_SOCKET;
523 if (h3_set_nonblocking(fd) != 0) {
525 return INVALID_SOCKET;
530static int h3_setup_tls(h3_client* c) {
531 static const unsigned char alpn[] =
"\x02h3";
532 c->ssl_ctx = SSL_CTX_new(TLS_client_method());
536 SSL_CTX_set_min_proto_version(c->ssl_ctx, TLS1_3_VERSION);
537 SSL_CTX_set_max_proto_version(c->ssl_ctx, TLS1_3_VERSION);
538 c->ssl = SSL_new(c->ssl_ctx);
542 c->conn_ref.get_conn = h3_get_conn;
543 c->conn_ref.user_data = c;
544 SSL_set_app_data(c->ssl, &c->conn_ref);
545 if (ngtcp2_crypto_ossl_configure_client_session(c->ssl) != 0) {
546 n_log(
LOG_ERR,
"ngtcp2_crypto_ossl_configure_client_session failed");
549 SSL_set_connect_state(c->ssl);
550 if (SSL_set_alpn_protos(c->ssl, alpn,
sizeof(alpn) - 1) != 0) {
553 SSL_set_tlsext_host_name(c->ssl, c->host);
554 SSL_set1_host(c->ssl, c->host);
555 if (ngtcp2_crypto_ossl_ctx_new(&c->ossl_ctx, NULL) != 0) {
558 ngtcp2_crypto_ossl_ctx_set_ssl(c->ossl_ctx, c->ssl);
562static int h3_setup_conn(h3_client* c) {
563 ngtcp2_settings settings;
564 ngtcp2_transport_params params;
571 ngtcp2_settings_default(&settings);
572 settings.initial_ts = h3_now_ns();
574 ngtcp2_transport_params_default(¶ms);
575 params.initial_max_streams_bidi = 0;
576 params.initial_max_streams_uni = 3;
577 params.initial_max_data = 1024 * 1024;
578 params.initial_max_stream_data_bidi_local = 256 * 1024;
579 params.initial_max_stream_data_bidi_remote = 256 * 1024;
580 params.initial_max_stream_data_uni = 256 * 1024;
584 if (RAND_bytes(dcid.data, 16) != 1 || RAND_bytes(scid.data, 16) != 1) {
588 path.local.addr = (ngtcp2_sockaddr*)&c->local_addr;
589 path.local.addrlen = c->local_addrlen;
590 path.remote.addr = (ngtcp2_sockaddr*)&c->remote_addr;
591 path.remote.addrlen = c->remote_addrlen;
592 path.user_data = NULL;
594 memset(&cb, 0,
sizeof(cb));
595 cb.client_initial = ngtcp2_crypto_client_initial_cb;
596 cb.recv_crypto_data = ngtcp2_crypto_recv_crypto_data_cb;
597 cb.encrypt = ngtcp2_crypto_encrypt_cb;
598 cb.decrypt = ngtcp2_crypto_decrypt_cb;
599 cb.hp_mask = ngtcp2_crypto_hp_mask_cb;
600 cb.recv_retry = ngtcp2_crypto_recv_retry_cb;
601 cb.update_key = ngtcp2_crypto_update_key_cb;
602 cb.delete_crypto_aead_ctx = ngtcp2_crypto_delete_crypto_aead_ctx_cb;
603 cb.delete_crypto_cipher_ctx = ngtcp2_crypto_delete_crypto_cipher_ctx_cb;
604 cb.get_path_challenge_data = ngtcp2_crypto_get_path_challenge_data_cb;
605 cb.version_negotiation = ngtcp2_crypto_version_negotiation_cb;
606 cb.rand = h3_rand_cb;
607 cb.get_new_connection_id = h3_get_new_cid_cb;
608 cb.handshake_completed = h3_handshake_completed_cb;
609 cb.recv_stream_data = h3_recv_stream_data_cb;
610 cb.acked_stream_data_offset = h3_acked_stream_data_offset_cb;
611 cb.stream_close = h3_stream_close_cb;
612 cb.stream_reset = h3_stream_reset_cb;
614 rv = ngtcp2_conn_client_new(&c->conn, &dcid, &scid, &path, NGTCP2_PROTO_VER_V1, &cb, &settings, ¶ms, NULL, c);
616 n_log(
LOG_ERR,
"ngtcp2_conn_client_new: %s", ngtcp2_strerror(rv));
619 ngtcp2_conn_set_tls_native_handle(c->conn, c->ossl_ctx);
624static void h3_lower(
char* s,
size_t n) {
626 for (i = 0; i < n; i++) {
627 if (s[i] >=
'A' && s[i] <=
'Z') {
628 s[i] = (char)(s[i] -
'A' +
'a');
634static int h3_header_banned(
const char* name,
size_t n) {
635 static const char* banned[] = {
"host",
"connection",
"transfer-encoding",
"keep-alive",
"upgrade",
"proxy-connection",
"content-length", NULL};
637 for (i = 0; banned[i]; i++) {
638 if (strlen(banned[i]) == n && memcmp(name, banned[i], n) == 0) {
645static int h3_setup_h3(h3_client* c) {
646 nghttp3_settings h3settings;
647 nghttp3_callbacks h3cb;
648 nghttp3_nv nva[4 + H3_MAX_HEADERS];
653 char* hdr_copy = NULL;
654 int64_t ctrl_id = -1;
657 int64_t stream_id = -1;
658 nghttp3_data_reader dr;
659 const nghttp3_data_reader* drp = NULL;
662 nghttp3_settings_default(&h3settings);
663 memset(&h3cb, 0,
sizeof(h3cb));
664 h3cb.recv_header = h3_recv_header_cb;
665 h3cb.recv_data = h3_recv_data_cb;
666 h3cb.deferred_consume = h3_deferred_consume_cb;
667 h3cb.end_stream = h3_end_stream_cb;
668 h3cb.stream_close = h3_stream_close_h3_cb;
670 rv = nghttp3_conn_client_new(&c->h3, &h3cb, &h3settings, NULL, c);
672 n_log(
LOG_ERR,
"nghttp3_conn_client_new: %s", nghttp3_strerror(rv));
676 if (ngtcp2_conn_open_uni_stream(c->conn, &ctrl_id, NULL) != 0 || nghttp3_conn_bind_control_stream(c->h3, ctrl_id) != 0) {
679 if (ngtcp2_conn_open_uni_stream(c->conn, &enc_id, NULL) != 0 || ngtcp2_conn_open_uni_stream(c->conn, &dec_id, NULL) != 0 || nghttp3_conn_bind_qpack_streams(c->h3, enc_id, dec_id) != 0) {
682 if (ngtcp2_conn_open_bidi_stream(c->conn, &stream_id, NULL) != 0) {
685 c->stream_id = stream_id;
687 nva[nvlen++] = (nghttp3_nv){(uint8_t*)
":method", (uint8_t*)c->method, 7, strlen(c->method), NGHTTP3_NV_FLAG_NONE};
688 nva[nvlen++] = (nghttp3_nv){(uint8_t*)
":scheme", (uint8_t*)
"https", 7, 5, NGHTTP3_NV_FLAG_NONE};
689 nva[nvlen++] = (nghttp3_nv){(uint8_t*)
":authority", (uint8_t*)c->authority, 10, strlen(c->authority), NGHTTP3_NV_FLAG_NONE};
690 nva[nvlen++] = (nghttp3_nv){(uint8_t*)
":path", (uint8_t*)c->path, 5, strlen(c->path), NGHTTP3_NV_FLAG_NONE};
693 int wl = snprintf(clen,
sizeof(clen),
"%zu", c->req_body.len);
695 nva[nvlen++] = (nghttp3_nv){(uint8_t*)
"content-length", (uint8_t*)clen, 14, (size_t)wl, NGHTTP3_NV_FLAG_NONE};
700 if (c->extra_headers && c->extra_headers[0]) {
701 hdr_copy = strdup(c->extra_headers);
705 for (line = strtok_r(hdr_copy,
"\r\n", &save); line && nvlen < (
sizeof(nva) /
sizeof(nva[0])); line = strtok_r(NULL,
"\r\n", &save)) {
706 char* colon = strchr(line,
':');
714 h3_lower(line, nlen);
717 if (nlen == 0 || line[0] ==
':' || (!c->allow_illegal && h3_header_banned(line, nlen))) {
721 while (*val ==
' ' || *val ==
'\t') {
724 nva[nvlen++] = (nghttp3_nv){(uint8_t*)line, (uint8_t*)val, nlen, strlen(val), NGHTTP3_NV_FLAG_NONE};
730 dr.read_data = h3_body_read_cb;
733 rv = nghttp3_conn_submit_request(c->h3, stream_id, nva, nvlen, drp, c);
736 n_log(
LOG_ERR,
"nghttp3_conn_submit_request: %s", nghttp3_strerror(rv));
746static int h3_write(h3_client* c) {
747 uint8_t buf[H3_SEND_BUF];
748 ngtcp2_path_storage ps;
750 uint64_t ts = h3_now_ns();
754 ngtcp2_path_storage_zero(&ps);
755 memset(&pi, 0,
sizeof(pi));
756 max_udp = ngtcp2_conn_get_max_tx_udp_payload_size(c->conn);
757 if (max_udp >
sizeof(buf)) {
758 max_udp =
sizeof(buf);
762 int64_t stream_id = -1;
764 nghttp3_ssize sveccnt = 0;
765 ngtcp2_ssize ndatalen = 0;
766 uint32_t flags = NGTCP2_WRITE_STREAM_FLAG_MORE;
769 if (c->h3 && ngtcp2_conn_get_max_data_left(c->conn)) {
770 sveccnt = nghttp3_conn_writev_stream(c->h3, &stream_id, &fin, vec, 16);
772 n_log(
LOG_ERR,
"nghttp3_conn_writev_stream: %s", nghttp3_strerror((
int)sveccnt));
777 flags |= NGTCP2_WRITE_STREAM_FLAG_FIN;
779 nwrite = ngtcp2_conn_writev_stream(c->conn, &ps.path, &pi, buf, max_udp, &ndatalen, flags, stream_id, (
const ngtcp2_vec*)vec, (
size_t)sveccnt, ts);
782 case NGTCP2_ERR_STREAM_DATA_BLOCKED:
783 if (c->h3 && stream_id >= 0) {
784 nghttp3_conn_block_stream(c->h3, stream_id);
787 case NGTCP2_ERR_STREAM_SHUT_WR:
788 if (c->h3 && stream_id >= 0) {
789 nghttp3_conn_shutdown_stream_write(c->h3, stream_id);
792 case NGTCP2_ERR_WRITE_MORE:
793 if (c->h3 && stream_id >= 0 && ndatalen >= 0) {
794 if (nghttp3_conn_add_write_offset(c->h3, stream_id, (
size_t)ndatalen) != 0) {
800 n_log(
LOG_ERR,
"ngtcp2_conn_writev_stream: %s", ngtcp2_strerror((
int)nwrite));
804 if (c->h3 && stream_id >= 0 && ndatalen >= 0) {
805 if (nghttp3_conn_add_write_offset(c->h3, stream_id, (
size_t)ndatalen) != 0) {
813 ssize_t s = send(c->fd, (
const char*)buf, (
size_t)nwrite, 0);
815 if (h3_would_block()) {
826static int h3_read(h3_client* c) {
827 uint8_t buf[H3_RECV_BUF];
829 struct sockaddr_storage ss;
830 socklen_t sslen =
sizeof(ss);
834 ssize_t n = recvfrom(c->fd, (
char*)buf,
sizeof(buf), 0, (
struct sockaddr*)&ss, &sslen);
836 if (h3_would_block()) {
842 path.local.addr = (ngtcp2_sockaddr*)&c->local_addr;
843 path.local.addrlen = c->local_addrlen;
844 path.remote.addr = (ngtcp2_sockaddr*)&ss;
845 path.remote.addrlen = sslen;
846 path.user_data = NULL;
847 memset(&pi, 0,
sizeof(pi));
848 rv = ngtcp2_conn_read_pkt(c->conn, &path, &pi, buf, (
size_t)n, h3_now_ns());
850 if (rv == NGTCP2_ERR_DRAINING || rv == NGTCP2_ERR_CLOSING) {
854 n_log(
LOG_ERR,
"ngtcp2_conn_read_pkt: %s", ngtcp2_strerror(rv));
860static int h3_run(h3_client* c,
int timeout_ms) {
861 uint64_t start = h3_now_ns();
862 uint64_t budget_ns = (uint64_t)timeout_ms * NGTCP2_MILLISECONDS;
864 ngtcp2_tstamp expiry;
869 if (h3_write(c) != 0) {
872 if (c->handshake_done && !c->h3) {
873 if (h3_setup_h3(c) != 0) {
878 if (c->stream_done) {
881 if (h3_now_ns() - start > budget_ns) {
882 n_log(
LOG_ERR,
"n_http3: timed out after %d ms waiting for the response", timeout_ms);
885 expiry = ngtcp2_conn_get_expiry(c->conn);
887 if (expiry == UINT64_MAX) {
889 }
else if (expiry <= now) {
892 uint64_t d = (expiry - now) / NGTCP2_MILLISECONDS;
893 timeout = d > 1000 ? 1000 : (int)d;
895 pr = h3_poll_read(c->fd, timeout);
897 if (h3_would_block()) {
904 int rv = ngtcp2_conn_handle_expiry(c->conn, h3_now_ns());
906 n_log(
LOG_ERR,
"ngtcp2_conn_handle_expiry: %s", ngtcp2_strerror(rv));
911 if (h3_read(c) != 0) {
917static void h3_close_conn(h3_client* c) {
918 uint8_t buf[H3_SEND_BUF];
919 ngtcp2_path_storage ps;
926 ngtcp2_path_storage_zero(&ps);
927 memset(&pi, 0,
sizeof(pi));
928 ngtcp2_ccerr_default(&ccerr);
929 n = ngtcp2_conn_write_connection_close(c->conn, &ps.path, &pi, buf,
sizeof(buf), &ccerr, h3_now_ns());
931 ssize_t s = send(c->fd, (
const char*)buf, (
size_t)n, 0);
936static void h3_client_cleanup(h3_client* c) {
938 for (i = 0; i < c->nheaders; i++) {
939 free(c->headers[i].name);
940 free(c->headers[i].value);
944 nghttp3_conn_del(c->h3);
947 ngtcp2_conn_del(c->conn);
950 ngtcp2_crypto_ossl_ctx_del(c->ossl_ctx);
956 SSL_CTX_free(c->ssl_ctx);
958 if (c->fd != INVALID_SOCKET) {
964static char* h3_join_headers(
const h3_client* c) {
969 for (i = 0; i < c->nheaders; i++) {
970 total += strlen(c->headers[i].name) + 2 + strlen(c->headers[i].value) + 2;
977 for (i = 0; i < c->nheaders; i++) {
978 int wl = snprintf(p, total - (
size_t)(p - out),
"%s: %s\r\n", c->headers[i].name, c->headers[i].value);
988 if (out && !out->
error) {
989 out->
error = strdup(msg);
998int n_http3_request(
const char* method,
const char* url,
const char* headers,
const unsigned char* body,
size_t body_len,
int timeout_ms,
N_HTTP3_RESPONSE* out) {
1002int n_http3_request_ex(
const char* method,
const char* url,
const char* headers,
const unsigned char* body,
size_t body_len,
int timeout_ms,
unsigned flags,
N_HTTP3_RESPONSE* out) {
1003 static int ossl_ready = 0;
1008 char authority[300];
1014 memset(out, 0,
sizeof(*out));
1015 if (!url || !url[0]) {
1016 return h3_fail(out,
"empty url");
1019 return h3_fail(out,
"could not parse the https url");
1021 if (timeout_ms <= 0) {
1024 if (strcmp(
port,
"443") == 0) {
1025 snprintf(authority,
sizeof(authority),
"%s", host);
1027 snprintf(authority,
sizeof(authority),
"%s:%s", host,
port);
1031 if (ngtcp2_crypto_ossl_init() != 0) {
1032 return h3_fail(out,
"ngtcp2_crypto_ossl_init failed");
1037 WSAStartup(MAKEWORD(2, 2), &wsa);
1043 memset(&c, 0,
sizeof(c));
1046 c.authority = authority;
1047 c.method = (method && method[0]) ? method :
"GET";
1049 c.extra_headers = headers;
1051 if (body && body_len > 0) {
1052 c.req_body.data = body;
1053 c.req_body.len = body_len;
1057 c.fd = h3_udp_connect(host,
port, &c.remote_addr, &c.remote_addrlen);
1058 if (c.fd == INVALID_SOCKET) {
1059 h3_client_cleanup(&c);
1060 return h3_fail(out,
"could not open a QUIC (UDP) socket to the host");
1062 c.local_addrlen =
sizeof(c.local_addr);
1063 if (getsockname(c.fd, (
struct sockaddr*)&c.local_addr, &c.local_addrlen) != 0) {
1064 h3_client_cleanup(&c);
1065 return h3_fail(out,
"getsockname failed");
1067 if (h3_setup_tls(&c) != 0) {
1068 h3_client_cleanup(&c);
1069 return h3_fail(out,
"TLS setup for QUIC failed");
1071 if (h3_setup_conn(&c) != 0) {
1072 h3_client_cleanup(&c);
1073 return h3_fail(out,
"QUIC connection setup failed");
1076 if (h3_run(&c, timeout_ms) != 0) {
1078 h3_client_cleanup(&c);
1079 return h3_fail(out,
"the HTTP/3 exchange failed (see the log)");
1083 out->
status = c.status[0] ? atoi(c.status) : 0;
1084 out->
headers = h3_join_headers(&c);
1085 if (c.body && c.body_len) {
1086 out->
body = malloc(c.body_len);
1088 memcpy(out->
body, c.body, c.body_len);
1092 ret = out->
status ? 0 : h3_fail(out,
"no HTTP status was received");
1093 if (c.oom && !out->
error) {
1094 out->
error = strdup(
"some response data was dropped (out of memory or body cap)");
1096 h3_client_cleanup(&c);
1118 memset(out, 0,
sizeof(*out));
1119 out->
error = strdup(
"HTTP/3 support was not compiled in (build the library with HAVE_HTTP3)");
1125 return n_http3_request(method, url, headers, body, body_len, timeout_ms, out);
#define n_log(__LEVEL__,...)
Logging function wrapper to get line and func.
#define LOG_ERR
error conditions
char * headers
Response header block as joined "name: value\r\n" lines (lowercased names), or NULL.
int status
HTTP status code, or 0 if none was received.
size_t body_len
Number of valid bytes in body.
unsigned char * body
Response body bytes (NOT NUL-terminated), or NULL.
char * error
Failure reason (heap), or NULL on a completed exchange.
int n_http3_parse_url(const char *url, char *host, size_t hostsz, char *port, size_t portsz, char *path, size_t pathsz)
Split an https URL into host, port and path (pure; available in every build).
int n_http3_request_ex(const char *method, const char *url, const char *headers, const unsigned char *body, size_t body_len, int timeout_ms, unsigned flags, N_HTTP3_RESPONSE *out)
Perform one blocking HTTP/3 request, with request flags.
#define N_HTTP3_DEFAULT_TIMEOUT_MS
Default per-request budget (milliseconds) when the caller passes <= 0.
#define N_HTTP3_BODY_CAP
Hard cap on the response body kept in memory (bytes).
int n_http3_get(const char *url, int timeout_ms, N_HTTP3_RESPONSE *out)
Convenience wrapper: HTTP/3 GET of url.
int n_http3_request(const char *method, const char *url, const char *headers, const unsigned char *body, size_t body_len, int timeout_ms, N_HTTP3_RESPONSE *out)
Perform one blocking HTTP/3 request over a fresh QUIC connection.
int n_http3_available(void)
Report whether HTTP/3 support was compiled into the library.
void n_http3_response_free(N_HTTP3_RESPONSE *r)
Free the heap buffers held by r and zero the struct (safe on NULL / zeroed).
#define N_HTTP3_REQ_ALLOW_ILLEGAL_HEADERS
n_http3_request_ex flag: send the caller's extra headers WITHOUT the built-in connection-specific / f...
Result of an HTTP/3 exchange.
int SOCKET
default socket declaration
Minimal blocking HTTP/3 (over QUIC) client.