v / vlib / net / http / vschannel_alpn_windows.c.v
56 lines · 52 sloc · 2.37 KB · ed17e5fb05090c62e99bc92ae94e4dbbde5acf24
Raw
1// Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved.
2// Use of this source code is governed by an MIT license
3// that can be found in the LICENSE file.
4module http
5
6// ALPN (RFC 7301) support for the Windows SChannel backend. The handshake-level
7// plumbing lives in thirdparty/vschannel/vschannel.c; this exposes it to V.
8//
9// This adds the *capability* to advertise ALPN and read the negotiated protocol
10// on SChannel. The one-shot vschannel request() path still speaks HTTP/1.1, so
11// it is not wired into fetch() yet (advertising `h2` without an HTTP/2 driver
12// would let a server pick a protocol we cannot speak). Negotiation is exercised
13// via schannel_alpn_probe(). Wiring the request path to HTTP/2 is the follow-up.
14// See vlang/v#27383.
15
16fn C.vschannel_set_alpn(tls_ctx &C.TlsContext, wire &char, len int)
17fn C.vschannel_get_alpn(tls_ctx &C.TlsContext, out &char, out_cap int) int
18fn C.vschannel_alpn_probe(tls_ctx &C.TlsContext, iport int, host &u16, out &char, out_cap int) int
19
20// alpn_wire encodes ALPN protocol names into the wire format SChannel expects:
21// each name preceded by a single length byte, e.g. `['h2', 'http/1.1']` becomes
22// "\x02h2\x08http/1.1". Empty names, or names longer than 255 bytes, are skipped.
23fn alpn_wire(protocols []string) []u8 {
24 mut out := []u8{}
25 for p in protocols {
26 if p.len == 0 || p.len > 255 {
27 continue
28 }
29 out << u8(p.len)
30 out << p.bytes()
31 }
32 return out
33}
34
35// schannel_alpn_probe performs a TLS handshake to `host`:`port` advertising
36// `protocols` via ALPN and returns the protocol the server selected (e.g. 'h2'),
37// or '' if none was negotiated. It sends no application data, so it works as a
38// pure ALPN-negotiation check independent of the HTTP/1.1 request path.
39// Windows/SChannel only. Pass `validate` = false to skip certificate validation
40// (e.g. against a local test server with a self-signed cert).
41fn schannel_alpn_probe(host string, port int, protocols []string, validate bool) string {
42 mut ctx := C.new_tls_context()
43 C.vschannel_use_tls12_client_protocol()
44 C.vschannel_init(&ctx, C.BOOL(if validate { 1 } else { 0 }))
45 wire := alpn_wire(protocols)
46 if wire.len > 0 {
47 C.vschannel_set_alpn(&ctx, &char(wire.data), wire.len)
48 }
49 mut buf := []u8{len: 256}
50 n := C.vschannel_alpn_probe(&ctx, port, host.to_wide(), &char(buf.data), buf.len)
51 C.vschannel_cleanup(&ctx)
52 if n <= 0 {
53 return ''
54 }
55 return buf[..n].bytestr()
56}
57