///|
/// What a connection knows about itself, carried to every scope made on it: the scheme it
/// arrived under, the two addresses, and its TLS facts when it is TLS.
priv struct Link {
scheme : String
bound : @socket.Addr?
peer : @socket.Addr?
tls : @moonasgi.TlsExtension?
}
///|
/// The ASGI TLS extension for a connection: the version, the suite, and the certificate the
/// server presented, PEM as the extension asks.
fn tls_facts(conn : @tcp.Conn, chain : Array[Bytes]) -> @moonasgi.TlsExtension? {
guard conn.tls() is Some(t) else { return None }
Some({
server_cert: if chain.length() > 0 {
Some(pem(chain[0]))
} else {
None
},
client_cert_chain: [],
client_cert_name: None,
client_cert_error: None,
tls_version: Some(0x0304),
cipher_suite: t.suite().map(s => s.code()),
})
}
///|
fn pem(der : Bytes) -> String {
let text = @base64.encode(der[:])
let out = StringBuilder()
out.write_string("-----BEGIN CERTIFICATE-----\n")
for i = 0; i < text.length(); i = i + 64 {
let end = if i + 64 < text.length() { i + 64 } else { text.length() }
out.write_string(text[i:end].to_owned())
out.write_string("\n")
}
out.write_string("-----END CERTIFICATE-----\n")
out.to_string()
}
///|
/// The extensions an HTTP scope here advertises: 103 Early Hints and response trailers, which
/// both versions honour, and the TLS facts on a TLS connection.
fn http_extensions(link : Link) -> @moonasgi.Extensions {
let base = @moonasgi.Extensions::none().enable_early_hint().enable_trailers()
match link.tls {
Some(t) => base.with_tls(t)
None => base
}
}
///|
/// The extensions a WebSocket scope here advertises: the TLS facts on a TLS connection.
fn ws_extensions(link : Link) -> @moonasgi.Extensions {
match link.tls {
Some(t) => @moonasgi.Extensions::none().with_tls(t)
None => @moonasgi.Extensions::none()
}
}
///|
/// The client and the scheme a request is to be seen from: the connection's, or what trusted
/// proxy headers say (← uvicorn's `--proxy-headers`).
fn seen_from(
link : Link,
config : @config.Config,
headers : Array[(String, String)],
scheme : String,
websocket~ : Bool = false,
) -> ((String, Int32)?, String) {
let peer = link.peer.map(addr_pair)
guard config.proxy_headers else { return (peer, scheme) }
let map : Map[String, String] = Map([])
for h in headers {
map[h.0.to_lower()] = h.1
}
@proxy.proxy_rewrite(
map,
peer,
scheme,
trusted=config.forwarded_allow_ips,
websocket~,
)
}
///|
/// An HTTP scope, the same for HTTP/1.1 and HTTP/2: the query split off, the path
/// percent-decoded beside the raw one (a router must tell `%2F` from `/`), the client and
/// scheme as `seen_from` gives them, the root path, the addresses and the extensions.
fn http_scope(
link : Link,
config : @config.Config,
version : String,
verb : String,
target : String,
headers : Array[(String, String)],
) -> @moonasgi.Scope {
let (raw_path, query) = split_query(target)
let (client, scheme) = seen_from(link, config, headers, link.scheme)
Http({
http_version: version,
http_method: verb,
scheme,
path: @utf8.decode_lossy(percent_decode(raw_path)),
raw_path: @utf8.encode(raw_path),
query_string: @utf8.encode(query),
root_path: config.root_path,
headers,
client,
server: server_pair(link.bound),
asgi: @moonasgi.AsgiVersion::http(),
extensions: http_extensions(link),
state: Map([]),
})
}