Page:
n__http3_8c
Pages
Examples
Files
Home
Module AABB3D
Module ACCEPT_POOL
Module ALLEGRO4
Module ALLEGRO5
Module ANIM_LIB
Module ASTAR
Module ATLAS_LIB
Module AVRO
Module CLOCK_SYNC
Module COMMONS
Module CONFIGFILE
Module CYPHER_BASE64
Module CYPHER_VIGENERE
Module DEAD_RECKONING
Module ENUMS
Module EXCEPTIONS
Module GAME_ENV
Module GIT
Module GUI
Module HASH_TABLE
Module ISOMETRIC_ENGINE
Module LIST
Module LOG
Module LOGNODUP
Module LZ4
Module NETWORKING
Module NETWORK_MSG
Module N_COOKIES
Module N_DIFF
Module N_DIGEST
Module N_DISK_CACHE
Module N_DNS
Module N_ECC
Module N_ENTROPY
Module N_FILES
Module N_FLUIDS
Module N_GUI_COLOR
Module N_GUI_COLORWHEEL
Module N_GUI_CURVE
Module N_GUI_GRID
Module N_GUI_POPUP
Module N_HEX
Module N_HTTP3
Module N_HTTP_COOKIE
Module N_HTTP_FORM
Module N_HTTP_MIME
Module N_HTTP_REQ
Module N_HTTP_RESP
Module N_HTTP_ROUTER
Module N_HTTP_SERVER
Module N_ICC
Module N_JSON
Module N_KAFKA
Module N_LINALG
Module N_MODULE
Module N_OPENSSL_COMPAT
Module N_PRETTY
Module N_PTH
Module N_QUERY
Module N_RANDOM
Module N_SHA256
Module N_STR
Module N_TIFF
Module N_TIME
Module N_USER
Module N_X509
Module N_ZIP
Module PARTICLES
Module PCRE
Module PHYSICS
Module SIGNALS
Module STACK
Module THREADS
Module TRAJECTORY
Module TREE
Module ZLIB
Modules
Pages
Struct A4_BMP
Struct A4_TIMER
Struct AABB3D
Struct ANIM_DATA
Struct ANIM_FRAME
Struct ANIM_GFX
Struct ANIM_LIB
Struct ASTAR_BOX
Struct ASTAR_CELL
Struct ASTAR_GRID
Struct ASTAR_HEAP
Struct ASTAR_HEAP_NODE
Struct ASTAR_NODE
Struct ASTAR_PATH
Struct AVRO_FIELD
Struct AVRO_READER
Struct AVRO_SCHEMA
Struct BITMAP
Struct CALLBACK_DATA
Struct CA_CTX
Struct CLIENT_JOB
Struct CLIENT_STATE
Struct CONFIG_FILE
Struct CONFIG_FILE_SECTION
Struct CYPHER_PARAM
Struct DATA
Struct DEMO_PANEL
Struct DICTIONARY_DEFINITION
Struct DICTIONARY_ENTRY
Struct DIFF_OUT
Struct DIFF_TEXT
Struct DRAW_STATE
Struct DR_ENTITY
Struct DR_STATE
Struct DR_VEC3
Struct ExceptionContextList
Struct FONT
Struct GAME_ENV
Struct H2BUF
Struct H2_DYN_ENTRY
Struct H2_STREAM
Struct HASH_NODE
Struct HASH_TABLE
Struct ISO_DRAW_ENTRY
Struct ISO_MAP
Struct ISO_PROJECTION
Struct ISO_TILE_SEGMENT
Struct ISO_TILE_SEGMENTS
Struct ISO_VISIBLE_EDGES
Struct JS_STATE
Struct LEX_ITEM
Struct LIST
Struct LIST_NODE
Struct LOG_LEVELS
Struct LexMenu
Struct MEMBER
Struct MIDI
Struct NESTED
Struct NETWORK
Struct NETWORK_HTTP_INFO
Struct NETWORK_POOL
Struct NETWORK_SSL_THREAD_PARAMS
Struct NETW_ACCEPT_POOL
Struct NETW_ACCEPT_POOL_STATS
Struct NETW_MSG
Struct NETW_SNI_ARG
Struct NODE_DATA
Struct N_ATLAS
Struct N_ATLAS_PAGE
Struct N_CLOCK_SYNC
Struct N_COOKIE
Struct N_COOKIE_JAR
Struct N_DIFF_LINE
Struct N_DIFF_RESULT
Struct N_DISK_CACHE
Struct N_ENTROPY_BIT
Struct N_ENTROPY_POS
Struct N_FILE_INFO
Struct N_FLUID
Struct N_FLUID_THREAD_PARAMS
Struct N_FORM_FIELD
Struct N_GIT_COMMIT_INFO
Struct N_GIT_REMOTE_AUTH
Struct N_GIT_REPO
Struct N_GIT_STATUS_ENTRY
Struct N_GUI_BUTTON_DATA
Struct N_GUI_CHECKBOX_DATA
Struct N_GUI_COLOR_PICKER
Struct N_GUI_COMBOBOX_DATA
Struct N_GUI_CTX
Struct N_GUI_CUSTOM_DATA
Struct N_GUI_DATAGRID_COL
Struct N_GUI_DATAGRID_DATA
Struct N_GUI_DROPMENU_DATA
Struct N_GUI_DROPMENU_ENTRY
Struct N_GUI_GRID_CALLBACKS
Struct N_GUI_HEXVIEW_DATA
Struct N_GUI_IMAGE_DATA
Struct N_GUI_KVTABLE
Struct N_GUI_KV_CONTENT
Struct N_GUI_KV_ROW
Struct N_GUI_LABEL_DATA
Struct N_GUI_LISTBOX_DATA
Struct N_GUI_LISTITEM
Struct N_GUI_MARKDOWNVIEW_DATA
Struct N_GUI_MD_FONTS
Struct N_GUI_MD_STYLE
Struct N_GUI_MEAS_ENTRY
Struct N_GUI_MOUSE_EVENT
Struct N_GUI_PENDING_GEOM
Struct N_GUI_PENDING_WIDGET
Struct N_GUI_POPUP
Struct N_GUI_PROGRESSBAR_DATA
Struct N_GUI_RADIOLIST_DATA
Struct N_GUI_SB_TRACK
Struct N_GUI_SCROLLBAR_DATA
Struct N_GUI_SECTION
Struct N_GUI_SECTIONLIST
Struct N_GUI_SLIDER_DATA
Struct N_GUI_SPLITPANE_DATA
Struct N_GUI_STYLE
Struct N_GUI_SUGGEST_SINK
Struct N_GUI_SYNTAXVIEW_DATA
Struct N_GUI_TAB_PANEL
Struct N_GUI_TB_BUTTONS
Struct N_GUI_TEXTAREA_DATA
Struct N_GUI_TEXT_DIMS
Struct N_GUI_THEME
Struct N_GUI_TREE
Struct N_GUI_TREE_NODE
Struct N_GUI_TREE_STATE
Struct N_GUI_WIDGET
Struct N_GUI_WINDOW
Struct N_H2_FRAME
Struct N_H2_HEADER
Struct N_H2_HPACK
Struct N_H2_REQUEST
Struct N_H2_SERVER
Struct N_H2_SETTING
Struct N_HTML_ATTR_
Struct N_HTML_FORM
Struct N_HTML_TAG_
Struct N_HTTP3_RESPONSE
Struct N_HTTP_FIELD
Struct N_HTTP_FORM
Struct N_HTTP_FORM_MULTIPART
Struct N_HTTP_FORM_PART
Struct N_HTTP_MULTIPART_PART
Struct N_HTTP_REQUEST
Struct N_HTTP_RESP
Struct N_HTTP_RESPONSE
Struct N_HTTP_SERVER
Struct N_HTTP_SERVER_CONFIG
Struct N_HTTP_SERVER_REQUEST
Struct N_HTTP_SERVER_STATS
Struct N_HTTP_SET_COOKIE
Struct N_HTTP_STREAM
Struct N_ISO_CAMERA
Struct N_ISO_OBJECT
Struct N_KAFKA
Struct N_KAFKA_EVENT
Struct N_MOCK_SERVER
Struct N_MODULE
Struct N_PARALLEL_SLICE
Struct N_PARALLEL_TEAM
Struct N_PARALLEL_WORKER
Struct N_PCRE
Struct N_PROXY_CFG
Struct N_PTH
Struct N_PTH_TENSOR
Struct N_QUERY
Struct N_ROUTE
Struct N_ROUTER
Struct N_ROUTE_MATCH
Struct N_SHA256
Struct N_SOCKET
Struct N_SPAWN_PIPE
Struct N_SSE_CONN
Struct N_SSE_EVENT
Struct N_STR
Struct N_TIFF
Struct N_TIFF_ENTRY
Struct N_TIME
Struct N_URL
Struct N_URL_PARAM
Struct N_USER
Struct N_USERLIST
Struct N_WS_CONN
Struct N_WS_FRAME
Struct N_WS_MESSAGE
Struct N_ZIP_MEMBER
Struct OCTREE
Struct OCTREE_NODE
Struct PARSER
Struct PARTICLE
Struct PARTICLE_EMITTER
Struct PARTICLE_SYSTEM
Struct PEER
Struct PHYSICS
Struct PK_OBJ
Struct PK_VM
Struct POINT2D
Struct POINT3D
Struct PRETTY_OUT
Struct QNODE
Struct QUADTREE
Struct QUADTREE_NODE
Struct REC
Struct RGB
Struct SAMPLE
Struct SEEN
Struct SHARER
Struct SRV
Struct SRV_ARG
Struct STACK
Struct STACK_ITEM
Struct SYNTAX_RUN
Struct THREAD_POOL
Struct THREAD_POOL_NODE
Struct THREAD_WAITING_PROC
Struct TRAJECTORY
Struct TRAJECTORY_POINT
Struct TREE
Struct TREE_NODE
Struct TS_LOG
Struct WAYPOINT3D
Struct WORKER_ARG
Struct ZIP_SINK
Struct _N_DC_ENTRY
Struct _N_DC_GROUP_ENTRY
Struct _N_GUI_COLORWHEEL
Struct _N_GUI_COLORWHEEL_BLOCK
Struct _N_GUI_COLORWHEEL_MOUSE
Struct _N_GUI_CURVE
Struct _N_GUI_CURVE_BLOCK
Struct _N_GUI_CURVE_MOUSE
Struct _N_GUI_GRID
Struct _N_GUI_POPUP_ENTRY
Struct _N_GUI_POPUP_LEVEL
Struct _N_GUI_SB_SKIN
Struct _N_GUI_TA_STEP
Struct _N_GUI_THUMB
Struct _N_GUI_WRAP_ACC
Struct _compiled
Struct _conn
Struct _ip_slot
Struct _mp_param
Struct _n_civil
Struct _seg
Struct client
Struct collector
Struct date_vector
Struct md_atom
Struct md_cells
Struct md_hit
Struct md_line_ctx
Struct mline
Struct n_http_req_header
Struct n_http_req_limits
Struct n_http_request
Struct n_markdown_block
Struct n_markdown_doc
Struct n_markdown_inline
Struct n_markdown_span
Struct n_reactor_stats
Struct n_url_safe
Struct n_url_safe_head
Struct n_wrap_item
Struct producer_state
Struct rects
Struct reply
Struct runner
Struct sink
Struct tbuf
Struct wrap_hit
Struct wrap_span
Structures
Union COORD_VALUE
Union HASH_DATA
Union NODE_DATA_TYPES
Union STACK_DATA
dir_310dcdd4703e877a6b99d5dc3fc54d27
dir_68267d1309a1af8e8297ef4c3efbcdba
dir_d28a4824dc47e487b107a5db32ef43c4
dir_d44c64559bbebec7f509842c48db8b23
ex__accept__pool__client_8c
ex__accept__pool__server_8c
ex__allegro__4_8c
ex__atlas_8c
ex__avro_8c
ex__base64_8c
ex__bind__exclusive_8c
ex__clipboard_8c
ex__clock__sync_8c
ex__common_8c
ex__configfile_8c
ex__cookies_8c
ex__crypto_8c
ex__diff_8c
ex__digest_8c
ex__disk__cache_8c
ex__dns_8c
ex__ecc_8c
ex__entropy_8c
ex__exceptions_8c
ex__file_8c
ex__file__copy_8c
ex__fluid_8c
ex__fluid__config_8c
ex__fluid__config_8h
ex__git_8c
ex__gui_8c
ex__gui__bench_8c
ex__gui__button__keys_8c
ex__gui__checkbox__label_8c
ex__gui__checkbox__release_8c
ex__gui__color__picker_8c
ex__gui__colorwheel_8c
ex__gui__combobox__items_8c
ex__gui__combobox__wheel_8c
ex__gui__cursor_8c
ex__gui__curve_8c
ex__gui__custom_8c
ex__gui__custom__mouse_8c
ex__gui__datagrid_8c
ex__gui__datagrid__keys_8c
ex__gui__datagrid__sort_8c
ex__gui__detach_8c
ex__gui__dictionary_8c
ex__gui__display__fallback_8c
ex__gui__double__click_8c
ex__gui__dropmenu_8c
ex__gui__frameless__drag_8c
ex__gui__global__scroll_8c
ex__gui__glyph_8c
ex__gui__grid_8c
ex__gui__hexview_8c
ex__gui__hover_8c
ex__gui__isometric_8c
ex__gui__kvtable_8c
ex__gui__label__fits_8c
ex__gui__label__lines_8c
ex__gui__layout__persist_8c
ex__gui__listbox_8c
ex__gui__md_8c
ex__gui__multiwin_8c
ex__gui__network_8c
ex__gui__particles_8c
ex__gui__placeholder_8c
ex__gui__popup_8c
ex__gui__progressbar_8c
ex__gui__reentrant_8c
ex__gui__round_8c
ex__gui__sectionlist_8c
ex__gui__slider__default_8c
ex__gui__slider__handle_8c
ex__gui__slider__step_8c
ex__gui__splitpane_8c
ex__gui__suggest_8c
ex__gui__syntaxview_8c
ex__gui__text__shorten_8c
ex__gui__textarea__selection_8c
ex__gui__tooltip_8c
ex__gui__tree__lazy_8c
ex__gui__tree__order_8c
ex__gui__tree__state_8c
ex__gui__window__rect_8c
ex__gui__zorder_8c
ex__hash_8c
ex__hex_8c
ex__html_8c
ex__http2_8c
ex__http2__server_8c
ex__http3_8c
ex__http__cookie_8c
ex__http__form_8c
ex__http__mime_8c
ex__http__req_8c
ex__http__response_8c
ex__http__router_8c
ex__http__server_8c
ex__icc_8c
ex__iso__astar_8c
ex__json_8c
ex__kafka_8c
ex__linalg_8c
ex__list_8c
ex__log_8c
ex__markdown_8c
ex__module_8c
ex__monolith_8c
ex__network_8c
ex__network_8h
ex__network__accept_8c
ex__network__alpn_8c
ex__network__connect__abort_8c
ex__network__mock_8c
ex__network__proxy_8c
ex__network__reactor_8c
ex__network__sni_8c
ex__network__sse_8c
ex__network__ssl_8c
ex__network__ssl__hardened_8c
ex__network__ssl__hostname_8c
ex__network__ssl__nonblock_8c
ex__network__tls__defaults_8c
ex__network__ws_8c
ex__network__ws__limits_8c
ex__nstr_8c
ex__parallel__for_8c
ex__parallel__team_8c
ex__pcre_8c
ex__pretty_8c
ex__pth_8c
ex__query_8c
ex__random_8c
ex__sha256_8c
ex__signals_8c
ex__spawn_8c
ex__ssrf_8c
ex__stack_8c
ex__thread__pool__discard_8c
ex__thread__pool__wait_8c
ex__threads_8c
ex__tiff_8c
ex__time__formats_8c
ex__trajectory_8c
ex__trees_8c
ex__url_8c
ex__url__safe_8c
ex__wrap_8c
ex__x509_8c
ex__zip_8c
ex__zlib_8c
ex_accept_pool_client_8c example
ex_accept_pool_server_8c example
ex_allegro_4_8c example
ex_atlas_8c example
ex_base64_8c example
ex_clock_sync_8c example
ex_common_8c example
ex_configfile_8c example
ex_cookies_8c example
ex_crypto_8c example
ex_diff_8c example
ex_digest_8c example
ex_disk_cache_8c example
ex_dns_8c example
ex_ecc_8c example
ex_entropy_8c example
ex_exceptions_8c example
ex_file_8c example
ex_file_copy_8c example
ex_fluid_8c example
ex_fluid_config_8c example
ex_fluid_config_8h example
ex_git_8c example
ex_gui_8c example
ex_gui_bench_8c example
ex_gui_button_keys_8c example
ex_gui_checkbox_label_8c example
ex_gui_checkbox_release_8c example
ex_gui_color_picker_8c example
ex_gui_colorwheel_8c example
ex_gui_combobox_items_8c example
ex_gui_combobox_wheel_8c example
ex_gui_cursor_8c example
ex_gui_curve_8c example
ex_gui_custom_8c example
ex_gui_custom_mouse_8c example
ex_gui_datagrid_8c example
ex_gui_datagrid_keys_8c example
ex_gui_datagrid_sort_8c example
ex_gui_detach_8c example
ex_gui_dictionary_8c example
ex_gui_display_fallback_8c example
ex_gui_double_click_8c example
ex_gui_dropmenu_8c example
ex_gui_frameless_drag_8c example
ex_gui_global_scroll_8c example
ex_gui_glyph_8c example
ex_gui_grid_8c example
ex_gui_hexview_8c example
ex_gui_hover_8c example
ex_gui_isometric_8c example
ex_gui_kvtable_8c example
ex_gui_label_fits_8c example
ex_gui_label_lines_8c example
ex_gui_layout_persist_8c example
ex_gui_listbox_8c example
ex_gui_md_8c example
ex_gui_multiwin_8c example
ex_gui_network_8c example
ex_gui_particles_8c example
ex_gui_placeholder_8c example
ex_gui_popup_8c example
ex_gui_progressbar_8c example
ex_gui_reentrant_8c example
ex_gui_round_8c example
ex_gui_sectionlist_8c example
ex_gui_slider_default_8c example
ex_gui_slider_handle_8c example
ex_gui_slider_step_8c example
ex_gui_splitpane_8c example
ex_gui_suggest_8c example
ex_gui_syntaxview_8c example
ex_gui_text_shorten_8c example
ex_gui_textarea_selection_8c example
ex_gui_tooltip_8c example
ex_gui_tree_lazy_8c example
ex_gui_tree_order_8c example
ex_gui_tree_state_8c example
ex_gui_window_rect_8c example
ex_gui_zorder_8c example
ex_hash_8c example
ex_hex_8c example
ex_html_8c example
ex_http2_8c example
ex_http2_server_8c example
ex_http_cookie_8c example
ex_http_form_8c example
ex_http_mime_8c example
ex_http_req_8c example
ex_http_response_8c example
ex_http_router_8c example
ex_http_server_8c example
ex_icc_8c example
ex_iso_astar_8c example
ex_json_8c example
ex_kafka_8c example
ex_linalg_8c example
ex_list_8c example
ex_log_8c example
ex_markdown_8c example
ex_module_8c example
ex_monolith_8c example
ex_network_8c example
ex_network_8h example
ex_network_accept_8c example
ex_network_alpn_8c example
ex_network_connect_abort_8c example
ex_network_mock_8c example
ex_network_proxy_8c example
ex_network_reactor_8c example
ex_network_sni_8c example
ex_network_sse_8c example
ex_network_ssl_8c example
ex_network_ssl_hardened_8c example
ex_network_ssl_hostname_8c example
ex_network_ssl_nonblock_8c example
ex_network_tls_defaults_8c example
ex_network_ws_8c example
ex_network_ws_limits_8c example
ex_nstr_8c example
ex_parallel_for_8c example
ex_parallel_team_8c example
ex_pcre_8c example
ex_pretty_8c example
ex_pth_8c example
ex_query_8c example
ex_random_8c example
ex_sha256_8c example
ex_signals_8c example
ex_spawn_8c example
ex_ssrf_8c example
ex_stack_8c example
ex_threads_8c example
ex_tiff_8c example
ex_time_formats_8c example
ex_trajectory_8c example
ex_trees_8c example
ex_url_8c example
ex_url_safe_8c example
ex_wrap_8c example
ex_x509_8c example
ex_zip_8c example
ex_zlib_8c example
mainpage_8dox
n__3d_8c
n__3d_8h
n__aabb_8c
n__aabb_8h
n__allegro5_8c
n__allegro5_8h
n__allegro__4_8c
n__allegro__4_8h
n__anim_8c
n__anim_8h
n__astar_8c
n__astar_8h
n__atlas_8c
n__atlas_8h
n__avro_8c
n__avro_8h
n__base64_8c
n__base64_8h
n__clipboard_8c
n__clipboard_8h
n__clock__sync_8c
n__clock__sync_8h
n__common_8c
n__common_8h
n__config__file_8c
n__config__file_8h
n__cookies_8c
n__cookies_8h
n__crypto_8c
n__crypto_8h
n__dead__reckoning_8c
n__dead__reckoning_8h
n__diff_8c
n__diff_8h
n__digest_8c
n__digest_8h
n__disk__cache_8c
n__disk__cache_8h
n__dns_8c
n__dns_8h
n__ecc_8c
n__ecc_8h
n__entropy_8c
n__entropy_8h
n__enum_8h
n__exceptions_8c
n__exceptions_8h
n__files_8c
n__files_8h
n__fluids_8c
n__fluids_8h
n__games_8c
n__games_8h
n__git_8c
n__git_8h
n__gui_8c
n__gui_8h
n__gui__color_8c
n__gui__color_8h
n__gui__colorwheel_8c
n__gui__colorwheel_8h
n__gui__curve_8c
n__gui__curve_8h
n__gui__grid_8c
n__gui__grid_8h
n__gui__md_8c
n__gui__md_8h
n__gui__popup_8c
n__gui__popup_8h
n__hash_8c
n__hash_8h
n__hex_8c
n__hex_8h
n__html_8c
n__html_8h
n__http2_8c
n__http2_8h
n__http2__server_8c
n__http2__server_8h
n__http3_8c
n__http3_8h
n__http__cookie_8c
n__http__cookie_8h
n__http__form_8c
n__http__form_8h
n__http__mime_8c
n__http__mime_8h
n__http__req_8c
n__http__req_8h
n__http__response_8c
n__http__response_8h
n__http__router_8c
n__http__router_8h
n__http__server_8c
n__http__server_8h
n__icc_8c
n__icc_8h
n__iso__engine_8c
n__iso__engine_8h
n__json_8c
n__json_8h
n__kafka_8c
n__kafka_8h
n__linalg_8c
n__linalg_8h
n__list_8c
n__list_8h
n__log_8c
n__log_8h
n__lz4_8c
n__lz4_8h
n__markdown_8c
n__markdown_8h
n__module_8c
n__module_8h
n__network_8c
n__network_8h
n__network__accept__pool_8c
n__network__accept__pool_8h
n__network__msg_8c
n__network__msg_8h
n__nodup__log_8c
n__nodup__log_8h
n__openssl__compat_8h
n__particles_8c
n__particles_8h
n__pcre_8c
n__pcre_8h
n__pretty_8c
n__pretty_8h
n__pth_8c
n__pth_8h
n__query_8c
n__query_8h
n__random_8c
n__random_8h
n__reactor_8c
n__reactor_8h
n__sha256_8c
n__sha256_8h
n__signals_8c
n__signals_8h
n__ssrf_8c
n__ssrf_8h
n__stack_8c
n__stack_8h
n__str_8c
n__str_8h
n__thread__pool_8c
n__thread__pool_8h
n__tiff_8c
n__tiff_8h
n__time_8c
n__time_8h
n__trajectory_8c
n__trajectory_8h
n__trees_8c
n__trees_8h
n__url__safe_8c
n__url__safe_8h
n__user_8c
n__user_8h
n__windows_8h
n__wrap_8c
n__wrap_8h
n__x509_8c
n__x509_8h
n__zip_8c
n__zip_8h
n__zlib_8c
n__zlib_8h
nilorea_8h
No results
7
n__http3_8c
gullradriel edited this page 2026-10-29 20:54:40 +01:00
Table of Contents
src/n_http3.c
Implementation of the minimal blocking HTTP/3 client (see n_http3.h). More...
Detailed Description
Implementation of the minimal blocking HTTP/3 client (see n_http3.h).
Author: Castagnier Mickael
Version: 1.0
The QUIC transport is driven by ngtcp2 and the HTTP/3 framing + QPACK by nghttp3; TLS 1.3 is handled by OpenSSL through the ngtcp2 "ossl" crypto shim (the OpenSSL native QUIC handshake). Everything below the pure helpers is compiled only under HAVE_HTTP3.
Source code
/*
* Nilorea Library
* Copyright (C) 2005-2026 Castagnier Mickael
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied. See the License for the specific language governing
* permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "nilorea/n_http3.h"
#include "nilorea/n_log.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* ---------------------------------------------------------------- */
/* pure helpers (available in every build) */
/* ---------------------------------------------------------------- */
void n_http3_response_free(N_HTTP3_RESPONSE* r) {
if (!r) {
return;
}
free(r->headers);
free(r->body);
free(r->error);
r->headers = NULL;
r->body = NULL;
r->error = NULL;
r->body_len = 0;
r->status = 0;
}
int n_http3_parse_url(const char* url, char* host, size_t hostsz, char* port, size_t portsz, char* path, size_t pathsz) {
const char* p;
const char* host_start;
const char* slash;
const char* hostend;
const char* colon;
size_t hlen;
size_t plen;
if (!url || strncmp(url, "https://", 8) != 0) {
return -1;
}
p = url + 8;
host_start = p;
slash = strchr(p, '/');
hostend = slash ? slash : (p + strlen(p));
/* IPv6 literal in brackets: [::1]:443 */
if (*host_start == '[') {
const char* rb = memchr(host_start, ']', (size_t)(hostend - host_start));
if (!rb) {
return -1;
}
hlen = (size_t)(rb - host_start - 1);
host_start++;
colon = (rb + 1 < hostend && rb[1] == ':') ? (rb + 1) : NULL;
} else {
colon = memchr(host_start, ':', (size_t)(hostend - host_start));
hlen = colon ? (size_t)(colon - host_start) : (size_t)(hostend - host_start);
}
if (hlen == 0) {
return -1;
}
if (host) {
if (hlen + 1 > hostsz) {
return -1;
}
memcpy(host, host_start, hlen);
host[hlen] = '\0';
}
if (port) {
if (colon) {
plen = (size_t)(hostend - colon - 1);
if (plen == 0 || plen + 1 > portsz) {
return -1;
}
memcpy(port, colon + 1, plen);
port[plen] = '\0';
} else {
if (portsz < 4) {
return -1;
}
memcpy(port, "443", 4);
}
}
if (path) {
if (slash) {
plen = strlen(slash);
if (plen + 1 > pathsz) {
return -1;
}
memcpy(path, slash, plen + 1);
} else {
if (pathsz < 2) {
return -1;
}
memcpy(path, "/", 2);
}
}
return 0;
}
int n_http3_get(const char* url, int timeout_ms, N_HTTP3_RESPONSE* out) {
return n_http3_request("GET", url, NULL, NULL, 0, timeout_ms, out);
}
/* ================================================================ */
#ifdef HAVE_HTTP3
/* ================================================================ */
#include "nilorea/n_network.h" /* portable SOCKET / closesocket / INVALID_SOCKET + socket includes */
#include <ngtcp2/ngtcp2.h>
#include <ngtcp2/ngtcp2_crypto.h>
#include <ngtcp2/ngtcp2_crypto_ossl.h>
#include <nghttp3/nghttp3.h>
#include <openssl/err.h>
#include <openssl/rand.h>
#include <openssl/ssl.h>
#include <stdint.h>
#include <time.h>
#ifdef _WIN32
#include <winsock2.h> /* WSAPoll, already pulled by n_network.h on Windows */
#else
#include <poll.h>
#endif
#define H3_MAX_HEADERS 256
#define H3_SEND_BUF 1500
#define H3_RECV_BUF 65536
typedef struct h3_body {
const uint8_t* data;
size_t len;
} h3_body;
typedef struct h3_hdr {
char* name;
char* value;
} h3_hdr;
typedef struct h3_client {
SOCKET fd;
ngtcp2_conn* conn;
ngtcp2_crypto_conn_ref conn_ref;
SSL_CTX* ssl_ctx;
SSL* ssl;
ngtcp2_crypto_ossl_ctx* ossl_ctx;
nghttp3_conn* h3;
struct sockaddr_storage local_addr;
socklen_t local_addrlen;
struct sockaddr_storage remote_addr;
socklen_t remote_addrlen;
int64_t stream_id;
int handshake_done;
int stream_done;
const char* host;
const char* authority;
const char* method;
const char* path;
const char* extra_headers;
int allow_illegal;
h3_body req_body;
int has_body;
char status[8];
h3_hdr headers[H3_MAX_HEADERS];
int nheaders;
uint8_t* body;
size_t body_len;
size_t body_cap;
int oom;
} h3_client;
/* ---------------------------------------------------------------- */
/* small platform + string helpers */
/* ---------------------------------------------------------------- */
static uint64_t h3_now_ns(void) {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (uint64_t)ts.tv_sec * NGTCP2_SECONDS + (uint64_t)ts.tv_nsec;
}
static int h3_set_nonblocking(SOCKET fd) {
#ifdef _WIN32
u_long on = 1;
return ioctlsocket(fd, (long)FIONBIO, &on) == 0 ? 0 : -1;
#else
int fl = fcntl(fd, F_GETFL, 0);
if (fl < 0) {
return -1;
}
return fcntl(fd, F_SETFL, fl | O_NONBLOCK) == 0 ? 0 : -1;
#endif
}
static int h3_would_block(void) {
#ifdef _WIN32
int e = WSAGetLastError();
return e == WSAEWOULDBLOCK || e == WSAEINTR;
#else
return errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR;
#endif
}
/* poll the socket for readability. returns >0 readable, 0 timeout, -1 error. */
static int h3_poll_read(SOCKET fd, int timeout_ms) {
#ifdef _WIN32
WSAPOLLFD pfd;
pfd.fd = fd;
pfd.events = POLLRDNORM;
pfd.revents = 0;
return WSAPoll(&pfd, 1, timeout_ms);
#else
struct pollfd pfd;
pfd.fd = fd;
pfd.events = POLLIN;
pfd.revents = 0;
return poll(&pfd, 1, timeout_ms);
#endif
}
static char* h3_dupn(const uint8_t* s, size_t n) {
char* p = malloc(n + 1);
if (!p) {
return NULL;
}
memcpy(p, s, n);
p[n] = '\0';
return p;
}
/* ---------------------------------------------------------------- */
/* ngtcp2 <-> tls glue */
/* ---------------------------------------------------------------- */
/* signature must match ngtcp2_crypto_conn_ref.get_conn, so ref stays non-const */
/* cppcheck-suppress constParameterCallback */
static ngtcp2_conn* h3_get_conn(ngtcp2_crypto_conn_ref* ref) {
const h3_client* c = (const h3_client*)ref->user_data;
return c->conn;
}
static void h3_rand_cb(uint8_t* dest, size_t destlen, const ngtcp2_rand_ctx* rctx) {
(void)rctx;
if (RAND_bytes(dest, (int)destlen) != 1) {
size_t i;
for (i = 0; i < destlen; i++) {
dest[i] = (uint8_t)rand();
}
}
}
static int h3_get_new_cid_cb(ngtcp2_conn* conn, ngtcp2_cid* cid, uint8_t* token, size_t cidlen, void* user_data) {
(void)conn;
(void)user_data;
if (RAND_bytes(cid->data, (int)cidlen) != 1) {
return NGTCP2_ERR_CALLBACK_FAILURE;
}
cid->datalen = cidlen;
if (RAND_bytes(token, NGTCP2_STATELESS_RESET_TOKENLEN) != 1) {
return NGTCP2_ERR_CALLBACK_FAILURE;
}
return 0;
}
static int h3_handshake_completed_cb(ngtcp2_conn* conn, void* user_data) {
(void)conn;
((h3_client*)user_data)->handshake_done = 1;
return 0;
}
static 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) {
h3_client* c = (h3_client*)user_data;
int fin = (flags & NGTCP2_STREAM_DATA_FLAG_FIN) != 0;
nghttp3_ssize nconsumed;
(void)offset;
(void)stream_user_data;
if (!c->h3) {
return 0;
}
nconsumed = nghttp3_conn_read_stream(c->h3, stream_id, data, datalen, fin);
if (nconsumed < 0) {
n_log(LOG_ERR, "nghttp3_conn_read_stream: %s", nghttp3_strerror((int)nconsumed));
return NGTCP2_ERR_CALLBACK_FAILURE;
}
ngtcp2_conn_extend_max_stream_offset(conn, stream_id, (uint64_t)nconsumed);
ngtcp2_conn_extend_max_offset(conn, (uint64_t)nconsumed);
return 0;
}
static 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) {
h3_client* c = (h3_client*)user_data;
(void)conn;
(void)offset;
(void)stream_user_data;
if (c->h3) {
nghttp3_conn_add_ack_offset(c->h3, stream_id, datalen);
}
return 0;
}
static 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) {
h3_client* c = (h3_client*)user_data;
(void)conn;
(void)stream_user_data;
if (!(flags & NGTCP2_STREAM_CLOSE_FLAG_APP_ERROR_CODE_SET)) {
app_error_code = NGHTTP3_H3_NO_ERROR;
}
if (c->h3) {
int rv = nghttp3_conn_close_stream(c->h3, stream_id, app_error_code);
if (rv != 0 && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) {
n_log(LOG_ERR, "nghttp3_conn_close_stream: %s", nghttp3_strerror(rv));
return NGTCP2_ERR_CALLBACK_FAILURE;
}
}
if (stream_id == c->stream_id) {
c->stream_done = 1;
}
return 0;
}
static 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) {
h3_client* c = (h3_client*)user_data;
(void)conn;
(void)final_size;
(void)app_error_code;
(void)stream_user_data;
if (c->h3) {
nghttp3_conn_shutdown_stream_read(c->h3, stream_id);
}
return 0;
}
/* ---------------------------------------------------------------- */
/* nghttp3 callbacks */
/* ---------------------------------------------------------------- */
static 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) {
h3_client* c = (h3_client*)conn_user_data;
nghttp3_vec n = nghttp3_rcbuf_get_buf(name);
nghttp3_vec v = nghttp3_rcbuf_get_buf(value);
(void)conn;
(void)stream_id;
(void)flags;
(void)stream_user_data;
if (token == NGHTTP3_QPACK_TOKEN__STATUS || (n.len == 7 && memcmp(n.base, ":status", 7) == 0)) {
snprintf(c->status, sizeof(c->status), "%.*s", (int)v.len, (const char*)v.base);
return 0;
}
if (c->nheaders < H3_MAX_HEADERS) {
char* hn = h3_dupn(n.base, n.len);
char* hv = h3_dupn(v.base, v.len);
if (hn && hv) {
c->headers[c->nheaders].name = hn;
c->headers[c->nheaders].value = hv;
c->nheaders++;
} else {
free(hn);
free(hv);
c->oom = 1;
}
}
return 0;
}
static 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) {
h3_client* c = (h3_client*)conn_user_data;
(void)conn;
(void)stream_id;
(void)stream_user_data;
if (c->body_len + datalen > c->body_cap) {
size_t newcap = c->body_cap ? c->body_cap : 65536;
uint8_t* nb;
while (newcap < c->body_len + datalen) {
newcap *= 2;
}
if (newcap > N_HTTP3_BODY_CAP) {
newcap = N_HTTP3_BODY_CAP;
}
if (c->body_len + datalen > newcap) {
datalen = newcap - c->body_len; /* body exceeds the cap: keep what fits */
}
if (datalen == 0) {
return 0;
}
nb = realloc(c->body, newcap);
if (!nb) {
c->oom = 1;
return NGHTTP3_ERR_CALLBACK_FAILURE;
}
c->body = nb;
c->body_cap = newcap;
}
if (datalen) {
memcpy(c->body + c->body_len, data, datalen);
c->body_len += datalen;
}
return 0;
}
static int h3_deferred_consume_cb(nghttp3_conn* conn, int64_t stream_id, size_t consumed, void* conn_user_data, void* stream_user_data) {
h3_client* c = (h3_client*)conn_user_data;
(void)conn;
(void)stream_user_data;
ngtcp2_conn_extend_max_stream_offset(c->conn, stream_id, consumed);
ngtcp2_conn_extend_max_offset(c->conn, consumed);
return 0;
}
static int h3_end_stream_cb(nghttp3_conn* conn, int64_t stream_id, void* conn_user_data, void* stream_user_data) {
h3_client* c = (h3_client*)conn_user_data;
(void)conn;
(void)stream_user_data;
if (stream_id == c->stream_id) {
c->stream_done = 1;
}
return 0;
}
static 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) {
h3_client* c = (h3_client*)conn_user_data;
(void)conn;
(void)app_error_code;
(void)stream_user_data;
if (stream_id == c->stream_id) {
c->stream_done = 1;
}
return 0;
}
/* request-body data reader: hand nghttp3 the whole buffer once, then EOF */
static 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) {
h3_client* c = (h3_client*)conn_user_data;
(void)conn;
(void)stream_id;
(void)veccnt;
(void)stream_user_data;
*pflags = NGHTTP3_DATA_FLAG_EOF;
if (c->req_body.len == 0) {
return 0;
}
vec[0].base = (uint8_t*)c->req_body.data;
vec[0].len = c->req_body.len;
return 1;
}
/* ---------------------------------------------------------------- */
/* socket + connection setup */
/* ---------------------------------------------------------------- */
static SOCKET h3_udp_connect(const char* host, const char* port, struct sockaddr_storage* remote, socklen_t* remotelen) {
struct addrinfo hints;
struct addrinfo* res = NULL;
struct addrinfo* rp;
SOCKET fd = INVALID_SOCKET;
int rv;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
hints.ai_protocol = IPPROTO_UDP;
rv = getaddrinfo(host, port, &hints, &res);
if (rv != 0) {
n_log(LOG_ERR, "getaddrinfo(%s:%s) failed", host, port);
return INVALID_SOCKET;
}
for (rp = res; rp; rp = rp->ai_next) {
fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
if (fd == INVALID_SOCKET) {
continue;
}
if (connect(fd, rp->ai_addr, (socklen_t)rp->ai_addrlen) == 0) {
memcpy(remote, rp->ai_addr, rp->ai_addrlen);
*remotelen = (socklen_t)rp->ai_addrlen;
break;
}
closesocket(fd);
fd = INVALID_SOCKET;
}
freeaddrinfo(res);
if (fd == INVALID_SOCKET) {
n_log(LOG_ERR, "could not open a UDP socket to %s:%s", host, port);
return INVALID_SOCKET;
}
if (h3_set_nonblocking(fd) != 0) {
closesocket(fd);
return INVALID_SOCKET;
}
return fd;
}
static int h3_setup_tls(h3_client* c) {
static const unsigned char alpn[] = "\x02h3";
c->ssl_ctx = SSL_CTX_new(TLS_client_method());
if (!c->ssl_ctx) {
return -1;
}
SSL_CTX_set_min_proto_version(c->ssl_ctx, TLS1_3_VERSION);
SSL_CTX_set_max_proto_version(c->ssl_ctx, TLS1_3_VERSION);
c->ssl = SSL_new(c->ssl_ctx);
if (!c->ssl) {
return -1;
}
c->conn_ref.get_conn = h3_get_conn;
c->conn_ref.user_data = c;
SSL_set_app_data(c->ssl, &c->conn_ref);
if (ngtcp2_crypto_ossl_configure_client_session(c->ssl) != 0) {
n_log(LOG_ERR, "ngtcp2_crypto_ossl_configure_client_session failed");
return -1;
}
SSL_set_connect_state(c->ssl);
if (SSL_set_alpn_protos(c->ssl, alpn, sizeof(alpn) - 1) != 0) {
return -1;
}
SSL_set_tlsext_host_name(c->ssl, c->host);
SSL_set1_host(c->ssl, c->host);
if (ngtcp2_crypto_ossl_ctx_new(&c->ossl_ctx, NULL) != 0) {
return -1;
}
ngtcp2_crypto_ossl_ctx_set_ssl(c->ossl_ctx, c->ssl);
return 0;
}
static int h3_setup_conn(h3_client* c) {
ngtcp2_settings settings;
ngtcp2_transport_params params;
ngtcp2_cid dcid;
ngtcp2_cid scid;
ngtcp2_path path;
ngtcp2_callbacks cb;
int rv;
ngtcp2_settings_default(&settings);
settings.initial_ts = h3_now_ns();
ngtcp2_transport_params_default(¶ms);
params.initial_max_streams_bidi = 0;
params.initial_max_streams_uni = 3;
params.initial_max_data = 1024 * 1024;
params.initial_max_stream_data_bidi_local = 256 * 1024;
params.initial_max_stream_data_bidi_remote = 256 * 1024;
params.initial_max_stream_data_uni = 256 * 1024;
dcid.datalen = 16;
scid.datalen = 16;
if (RAND_bytes(dcid.data, 16) != 1 || RAND_bytes(scid.data, 16) != 1) {
return -1;
}
path.local.addr = (ngtcp2_sockaddr*)&c->local_addr;
path.local.addrlen = c->local_addrlen;
path.remote.addr = (ngtcp2_sockaddr*)&c->remote_addr;
path.remote.addrlen = c->remote_addrlen;
path.user_data = NULL;
memset(&cb, 0, sizeof(cb));
cb.client_initial = ngtcp2_crypto_client_initial_cb;
cb.recv_crypto_data = ngtcp2_crypto_recv_crypto_data_cb;
cb.encrypt = ngtcp2_crypto_encrypt_cb;
cb.decrypt = ngtcp2_crypto_decrypt_cb;
cb.hp_mask = ngtcp2_crypto_hp_mask_cb;
cb.recv_retry = ngtcp2_crypto_recv_retry_cb;
cb.update_key = ngtcp2_crypto_update_key_cb;
cb.delete_crypto_aead_ctx = ngtcp2_crypto_delete_crypto_aead_ctx_cb;
cb.delete_crypto_cipher_ctx = ngtcp2_crypto_delete_crypto_cipher_ctx_cb;
cb.get_path_challenge_data = ngtcp2_crypto_get_path_challenge_data_cb;
cb.version_negotiation = ngtcp2_crypto_version_negotiation_cb;
cb.rand = h3_rand_cb;
cb.get_new_connection_id = h3_get_new_cid_cb;
cb.handshake_completed = h3_handshake_completed_cb;
cb.recv_stream_data = h3_recv_stream_data_cb;
cb.acked_stream_data_offset = h3_acked_stream_data_offset_cb;
cb.stream_close = h3_stream_close_cb;
cb.stream_reset = h3_stream_reset_cb;
rv = ngtcp2_conn_client_new(&c->conn, &dcid, &scid, &path, NGTCP2_PROTO_VER_V1, &cb, &settings, ¶ms, NULL, c);
if (rv != 0) {
n_log(LOG_ERR, "ngtcp2_conn_client_new: %s", ngtcp2_strerror(rv));
return -1;
}
ngtcp2_conn_set_tls_native_handle(c->conn, c->ossl_ctx);
return 0;
}
/* lowercase a header name in place (ASCII) */
static void h3_lower(char* s, size_t n) {
size_t i;
for (i = 0; i < n; i++) {
if (s[i] >= 'A' && s[i] <= 'Z') {
s[i] = (char)(s[i] - 'A' + 'a');
}
}
}
/* a header name HTTP/3 forbids (connection-specific) or that we set ourselves */
static int h3_header_banned(const char* name, size_t n) {
static const char* banned[] = {"host", "connection", "transfer-encoding", "keep-alive", "upgrade", "proxy-connection", "content-length", NULL};
int i;
for (i = 0; banned[i]; i++) {
if (strlen(banned[i]) == n && memcmp(name, banned[i], n) == 0) {
return 1;
}
}
return 0;
}
static int h3_setup_h3(h3_client* c) {
nghttp3_settings h3settings;
nghttp3_callbacks h3cb;
nghttp3_nv nva[4 + H3_MAX_HEADERS];
size_t nvlen = 0;
/* a pointer to clen is kept in nva until submit_request, so it must live to the end of the function */
/* cppcheck-suppress variableScope */
char clen[32];
char* hdr_copy = NULL;
int64_t ctrl_id = -1;
int64_t enc_id = -1;
int64_t dec_id = -1;
int64_t stream_id = -1;
nghttp3_data_reader dr;
const nghttp3_data_reader* drp = NULL;
int rv;
nghttp3_settings_default(&h3settings);
memset(&h3cb, 0, sizeof(h3cb));
h3cb.recv_header = h3_recv_header_cb;
h3cb.recv_data = h3_recv_data_cb;
h3cb.deferred_consume = h3_deferred_consume_cb;
h3cb.end_stream = h3_end_stream_cb;
h3cb.stream_close = h3_stream_close_h3_cb;
rv = nghttp3_conn_client_new(&c->h3, &h3cb, &h3settings, NULL, c);
if (rv != 0) {
n_log(LOG_ERR, "nghttp3_conn_client_new: %s", nghttp3_strerror(rv));
return -1;
}
if (ngtcp2_conn_open_uni_stream(c->conn, &ctrl_id, NULL) != 0 || nghttp3_conn_bind_control_stream(c->h3, ctrl_id) != 0) {
return -1;
}
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) {
return -1;
}
if (ngtcp2_conn_open_bidi_stream(c->conn, &stream_id, NULL) != 0) {
return -1;
}
c->stream_id = stream_id;
nva[nvlen++] = (nghttp3_nv){(uint8_t*)":method", (uint8_t*)c->method, 7, strlen(c->method), NGHTTP3_NV_FLAG_NONE};
nva[nvlen++] = (nghttp3_nv){(uint8_t*)":scheme", (uint8_t*)"https", 7, 5, NGHTTP3_NV_FLAG_NONE};
nva[nvlen++] = (nghttp3_nv){(uint8_t*)":authority", (uint8_t*)c->authority, 10, strlen(c->authority), NGHTTP3_NV_FLAG_NONE};
nva[nvlen++] = (nghttp3_nv){(uint8_t*)":path", (uint8_t*)c->path, 5, strlen(c->path), NGHTTP3_NV_FLAG_NONE};
if (c->has_body) {
int wl = snprintf(clen, sizeof(clen), "%zu", c->req_body.len);
if (wl > 0) {
nva[nvlen++] = (nghttp3_nv){(uint8_t*)"content-length", (uint8_t*)clen, 14, (size_t)wl, NGHTTP3_NV_FLAG_NONE};
}
}
/* caller-supplied extra headers ("Name: Value\r\n" lines) */
if (c->extra_headers && c->extra_headers[0]) {
hdr_copy = strdup(c->extra_headers);
if (hdr_copy) {
char* save = NULL;
char* line;
for (line = strtok_r(hdr_copy, "\r\n", &save); line && nvlen < (sizeof(nva) / sizeof(nva[0])); line = strtok_r(NULL, "\r\n", &save)) {
char* colon = strchr(line, ':');
char* val;
size_t nlen;
if (!colon) {
continue;
}
*colon = '\0';
nlen = strlen(line);
h3_lower(line, nlen);
/* a pseudo-header (':...') is never caller-injectable; the connection-specific /
framing filter is bypassed when allow_illegal is set (security testing) */
if (nlen == 0 || line[0] == ':' || (!c->allow_illegal && h3_header_banned(line, nlen))) {
continue;
}
val = colon + 1;
while (*val == ' ' || *val == '\t') {
val++;
}
nva[nvlen++] = (nghttp3_nv){(uint8_t*)line, (uint8_t*)val, nlen, strlen(val), NGHTTP3_NV_FLAG_NONE};
}
}
}
if (c->has_body) {
dr.read_data = h3_body_read_cb;
drp = &dr;
}
rv = nghttp3_conn_submit_request(c->h3, stream_id, nva, nvlen, drp, c);
free(hdr_copy);
if (rv != 0) {
n_log(LOG_ERR, "nghttp3_conn_submit_request: %s", nghttp3_strerror(rv));
return -1;
}
return 0;
}
/* ---------------------------------------------------------------- */
/* read / write pumps + event loop */
/* ---------------------------------------------------------------- */
static int h3_write(h3_client* c) {
uint8_t buf[H3_SEND_BUF];
ngtcp2_path_storage ps;
ngtcp2_pkt_info pi;
uint64_t ts = h3_now_ns();
nghttp3_vec vec[16];
size_t max_udp;
ngtcp2_path_storage_zero(&ps);
memset(&pi, 0, sizeof(pi));
max_udp = ngtcp2_conn_get_max_tx_udp_payload_size(c->conn);
if (max_udp > sizeof(buf)) {
max_udp = sizeof(buf);
}
for (;;) {
int64_t stream_id = -1;
int fin = 0;
nghttp3_ssize sveccnt = 0;
ngtcp2_ssize ndatalen = 0;
uint32_t flags = NGTCP2_WRITE_STREAM_FLAG_MORE;
ngtcp2_ssize nwrite;
if (c->h3 && ngtcp2_conn_get_max_data_left(c->conn)) {
sveccnt = nghttp3_conn_writev_stream(c->h3, &stream_id, &fin, vec, 16);
if (sveccnt < 0) {
n_log(LOG_ERR, "nghttp3_conn_writev_stream: %s", nghttp3_strerror((int)sveccnt));
return -1;
}
}
if (fin) {
flags |= NGTCP2_WRITE_STREAM_FLAG_FIN;
}
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);
if (nwrite < 0) {
switch (nwrite) {
case NGTCP2_ERR_STREAM_DATA_BLOCKED:
if (c->h3 && stream_id >= 0) {
nghttp3_conn_block_stream(c->h3, stream_id);
}
continue;
case NGTCP2_ERR_STREAM_SHUT_WR:
if (c->h3 && stream_id >= 0) {
nghttp3_conn_shutdown_stream_write(c->h3, stream_id);
}
continue;
case NGTCP2_ERR_WRITE_MORE:
if (c->h3 && stream_id >= 0 && ndatalen >= 0) {
if (nghttp3_conn_add_write_offset(c->h3, stream_id, (size_t)ndatalen) != 0) {
return -1;
}
}
continue;
default:
n_log(LOG_ERR, "ngtcp2_conn_writev_stream: %s", ngtcp2_strerror((int)nwrite));
return -1;
}
}
if (c->h3 && stream_id >= 0 && ndatalen >= 0) {
if (nghttp3_conn_add_write_offset(c->h3, stream_id, (size_t)ndatalen) != 0) {
return -1;
}
}
if (nwrite == 0) {
return 0; /* nothing more to send now */
}
for (;;) {
ssize_t s = send(c->fd, (const char*)buf, (size_t)nwrite, 0);
if (s < 0) {
if (h3_would_block()) {
break; /* buffer full / interrupted: QUIC will retransmit */
}
n_log(LOG_ERR, "send failed on the QUIC socket");
return -1;
}
break;
}
}
}
static int h3_read(h3_client* c) {
uint8_t buf[H3_RECV_BUF];
for (;;) {
struct sockaddr_storage ss;
socklen_t sslen = sizeof(ss);
ngtcp2_path path;
ngtcp2_pkt_info pi;
int rv;
ssize_t n = recvfrom(c->fd, (char*)buf, sizeof(buf), 0, (struct sockaddr*)&ss, &sslen);
if (n < 0) {
if (h3_would_block()) {
return 0;
}
n_log(LOG_ERR, "recvfrom failed on the QUIC socket");
return -1;
}
path.local.addr = (ngtcp2_sockaddr*)&c->local_addr;
path.local.addrlen = c->local_addrlen;
path.remote.addr = (ngtcp2_sockaddr*)&ss;
path.remote.addrlen = sslen;
path.user_data = NULL;
memset(&pi, 0, sizeof(pi));
rv = ngtcp2_conn_read_pkt(c->conn, &path, &pi, buf, (size_t)n, h3_now_ns());
if (rv != 0) {
if (rv == NGTCP2_ERR_DRAINING || rv == NGTCP2_ERR_CLOSING) {
c->stream_done = 1;
return 0;
}
n_log(LOG_ERR, "ngtcp2_conn_read_pkt: %s", ngtcp2_strerror(rv));
return -1;
}
}
}
static int h3_run(h3_client* c, int timeout_ms) {
uint64_t start = h3_now_ns();
uint64_t budget_ns = (uint64_t)timeout_ms * NGTCP2_MILLISECONDS;
for (;;) {
ngtcp2_tstamp expiry;
uint64_t now;
int timeout;
int pr;
if (h3_write(c) != 0) {
return -1;
}
if (c->handshake_done && !c->h3) {
if (h3_setup_h3(c) != 0) {
return -1;
}
continue; /* flush the request immediately */
}
if (c->stream_done) {
return 0;
}
if (h3_now_ns() - start > budget_ns) {
n_log(LOG_ERR, "n_http3: timed out after %d ms waiting for the response", timeout_ms);
return -1;
}
expiry = ngtcp2_conn_get_expiry(c->conn);
now = h3_now_ns();
if (expiry == UINT64_MAX) {
timeout = 1000;
} else if (expiry <= now) {
timeout = 0;
} else {
uint64_t d = (expiry - now) / NGTCP2_MILLISECONDS;
timeout = d > 1000 ? 1000 : (int)d;
}
pr = h3_poll_read(c->fd, timeout);
if (pr < 0) {
if (h3_would_block()) {
continue;
}
n_log(LOG_ERR, "poll failed on the QUIC socket");
return -1;
}
if (pr == 0) {
int rv = ngtcp2_conn_handle_expiry(c->conn, h3_now_ns());
if (rv != 0) {
n_log(LOG_ERR, "ngtcp2_conn_handle_expiry: %s", ngtcp2_strerror(rv));
return -1;
}
continue;
}
if (h3_read(c) != 0) {
return -1;
}
}
}
static void h3_close_conn(h3_client* c) {
uint8_t buf[H3_SEND_BUF];
ngtcp2_path_storage ps;
ngtcp2_pkt_info pi;
ngtcp2_ccerr ccerr;
ngtcp2_ssize n;
if (!c->conn) {
return;
}
ngtcp2_path_storage_zero(&ps);
memset(&pi, 0, sizeof(pi));
ngtcp2_ccerr_default(&ccerr);
n = ngtcp2_conn_write_connection_close(c->conn, &ps.path, &pi, buf, sizeof(buf), &ccerr, h3_now_ns());
if (n > 0) {
ssize_t s = send(c->fd, (const char*)buf, (size_t)n, 0);
(void)s;
}
}
static void h3_client_cleanup(h3_client* c) {
int i;
for (i = 0; i < c->nheaders; i++) {
free(c->headers[i].name);
free(c->headers[i].value);
}
free(c->body);
if (c->h3) {
nghttp3_conn_del(c->h3);
}
if (c->conn) {
ngtcp2_conn_del(c->conn);
}
if (c->ossl_ctx) {
ngtcp2_crypto_ossl_ctx_del(c->ossl_ctx);
}
if (c->ssl) {
SSL_free(c->ssl);
}
if (c->ssl_ctx) {
SSL_CTX_free(c->ssl_ctx);
}
if (c->fd != INVALID_SOCKET) {
closesocket(c->fd);
}
}
/* build out->headers by joining the collected header lines */
static char* h3_join_headers(const h3_client* c) {
size_t total = 1;
char* out;
char* p;
int i;
for (i = 0; i < c->nheaders; i++) {
total += strlen(c->headers[i].name) + 2 + strlen(c->headers[i].value) + 2;
}
out = malloc(total);
if (!out) {
return NULL;
}
p = out;
for (i = 0; i < c->nheaders; i++) {
int wl = snprintf(p, total - (size_t)(p - out), "%s: %s\r\n", c->headers[i].name, c->headers[i].value);
if (wl > 0) {
p += wl;
}
}
*p = '\0';
return out;
}
static int h3_fail(N_HTTP3_RESPONSE* out, const char* msg) {
if (out && !out->error) {
out->error = strdup(msg);
}
return -1;
}
int n_http3_available(void) {
return 1;
}
static int h3_ossl_ok = 0;
static void h3_ossl_init_once(void) {
if (ngtcp2_crypto_ossl_init() != 0) {
return;
}
#ifdef _WIN32
{
WSADATA wsa;
WSAStartup(MAKEWORD(2, 2), &wsa); /* ensure winsock is up (idempotent per process) */
}
#endif
h3_ossl_ok = 1;
}
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) {
return n_http3_request_ex(method, url, headers, body, body_len, timeout_ms, 0u, out);
}
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) {
/* once-per-process init: see h3_ossl_init_once */
static pthread_once_t ossl_once = PTHREAD_ONCE_INIT;
h3_client c;
char host[256];
char port[16];
char path[2048];
char authority[300];
int ret;
if (!out) {
return -1;
}
memset(out, 0, sizeof(*out));
if (!url || !url[0]) {
return h3_fail(out, "empty url");
}
if (n_http3_parse_url(url, host, sizeof(host), port, sizeof(port), path, sizeof(path)) != 0) {
return h3_fail(out, "could not parse the https url");
}
if (timeout_ms <= 0) {
timeout_ms = N_HTTP3_DEFAULT_TIMEOUT_MS;
}
if (strcmp(port, "443") == 0) {
snprintf(authority, sizeof(authority), "%s", host);
} else {
snprintf(authority, sizeof(authority), "%s:%s", host, port);
}
pthread_once(&ossl_once, h3_ossl_init_once);
if (!h3_ossl_ok) {
return h3_fail(out, "ngtcp2_crypto_ossl_init failed");
}
memset(&c, 0, sizeof(c));
c.stream_id = -1;
c.host = host;
c.authority = authority;
c.method = (method && method[0]) ? method : "GET";
c.path = path;
c.extra_headers = headers;
c.allow_illegal = (flags & N_HTTP3_REQ_ALLOW_ILLEGAL_HEADERS) ? 1 : 0;
if (body && body_len > 0) {
c.req_body.data = body;
c.req_body.len = body_len;
c.has_body = 1;
}
c.fd = h3_udp_connect(host, port, &c.remote_addr, &c.remote_addrlen);
if (c.fd == INVALID_SOCKET) {
h3_client_cleanup(&c);
return h3_fail(out, "could not open a QUIC (UDP) socket to the host");
}
c.local_addrlen = sizeof(c.local_addr);
if (getsockname(c.fd, (struct sockaddr*)&c.local_addr, &c.local_addrlen) != 0) {
h3_client_cleanup(&c);
return h3_fail(out, "getsockname failed");
}
if (h3_setup_tls(&c) != 0) {
h3_client_cleanup(&c);
return h3_fail(out, "TLS setup for QUIC failed");
}
if (h3_setup_conn(&c) != 0) {
h3_client_cleanup(&c);
return h3_fail(out, "QUIC connection setup failed");
}
if (h3_run(&c, timeout_ms) != 0) {
h3_close_conn(&c);
h3_client_cleanup(&c);
return h3_fail(out, "the HTTP/3 exchange failed (see the log)");
}
h3_close_conn(&c);
out->status = c.status[0] ? atoi(c.status) : 0;
out->headers = h3_join_headers(&c);
if (c.body && c.body_len) {
out->body = malloc(c.body_len);
if (out->body) {
memcpy(out->body, c.body, c.body_len);
out->body_len = c.body_len;
}
}
ret = out->status ? 0 : h3_fail(out, "no HTTP status was received");
if (c.oom && !out->error) {
out->error = strdup("some response data was dropped (out of memory or body cap)");
}
h3_client_cleanup(&c);
return ret;
}
/* ================================================================ */
#else /* !HAVE_HTTP3 */
/* ================================================================ */
int n_http3_available(void) {
return 0;
}
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) {
(void)method;
(void)url;
(void)headers;
(void)body;
(void)body_len;
(void)timeout_ms;
if (!out) {
return -1;
}
memset(out, 0, sizeof(*out));
out->error = strdup("HTTP/3 support was not compiled in (build the library with HAVE_HTTP3)");
return -1;
}
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) {
(void)flags;
return n_http3_request(method, url, headers, body, body_len, timeout_ms, out);
}
#endif /* HAVE_HTTP3 */
Modules
- AABB3D
- ACCEPT_POOL
- ALLEGRO4
- ALLEGRO5
- ANIM_LIB
- ASTAR
- ATLAS_LIB
- AVRO
- CLOCK_SYNC
- COMMONS
- CONFIGFILE
- CYPHER_BASE64
- CYPHER_VIGENERE
- DEAD_RECKONING
- ENUMS
- EXCEPTIONS
- GAME_ENV
- GIT
- GUI
- HASH_TABLE
- ISOMETRIC_ENGINE
- LIST
- LOG
- LOGNODUP
- LZ4
- NETWORKING
- NETWORK_MSG
- N_COOKIES
- N_DIFF
- N_DIGEST
- N_DISK_CACHE
- N_DNS
- N_ECC
- N_ENTROPY
- N_FILES
- N_FLUIDS
- N_GUI_COLOR
- N_GUI_COLORWHEEL
- N_GUI_CURVE
- N_GUI_GRID
- N_GUI_POPUP
- N_HEX
- N_HTTP3
- N_HTTP_COOKIE
- N_HTTP_FORM
- N_HTTP_MIME
- N_HTTP_REQ
- N_HTTP_RESP
- N_HTTP_ROUTER
- N_HTTP_SERVER
- N_ICC
- N_JSON
- N_KAFKA
- N_LINALG
- N_MODULE
- N_OPENSSL_COMPAT
- N_PRETTY
- N_PTH
- N_QUERY
- N_RANDOM
- N_SHA256
- N_STR
- N_TIFF
- N_TIME
- N_USER
- N_X509
- N_ZIP
- PARTICLES
- PCRE
- PHYSICS
- SIGNALS
- STACK
- THREADS
- TRAJECTORY
- TREE
- ZLIB