Bug 1805739 - vendor bhttp, provide bindings for binary http (RFC 9292) r=necko-reviewers,supply-chain-reviewers,valentin

Differential Revision: https://phabricator.services.mozilla.com/D164720
This commit is contained in:
Dana Keeler 2022-12-19 20:09:35 +00:00
Родитель b3a467b55a
Коммит de4fbd4afe
22 изменённых файлов: 1891 добавлений и 10 удалений

21
Cargo.lock сгенерированный
Просмотреть файл

@ -422,6 +422,26 @@ dependencies = [
"fxhash",
]
[[package]]
name = "bhttp"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a561f43fe82923605345b977ebd5951126f0a1b4575e3c3d53e5954e5822de4"
dependencies = [
"url",
]
[[package]]
name = "binary_http"
version = "0.1.0"
dependencies = [
"bhttp",
"nserror",
"nsstring",
"thin-vec",
"xpcom",
]
[[package]]
name = "bincode"
version = "1.3.3"
@ -2154,6 +2174,7 @@ dependencies = [
"audioipc2-client",
"audioipc2-server",
"authenticator",
"binary_http",
"bitsdownload",
"bookmark_sync",
"cascade_bloom_filter",

Просмотреть файл

@ -0,0 +1,11 @@
[package]
name = "binary_http"
version = "0.1.0"
edition = "2021"
[dependencies]
nserror = { path = "../../../../xpcom/rust/nserror" }
nsstring = { path = "../../../../xpcom/rust/nsstring" }
bhttp = "0.2.3"
thin-vec = { version = "0.2.1", features = ["gecko-ffi"] }
xpcom = { path = "../../../../xpcom/rust/xpcom" }

Просмотреть файл

@ -0,0 +1,24 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef _binary_http_h_
#define _binary_http_h_
#include "nsISupportsUtils.h" // for nsresult, etc.
// {b43b3f73-8160-4ab2-9f5d-4129a9708081}
#define NS_BINARY_HTTP_CID \
{ \
0xb43b3f73, 0x8160, 0x4ab2, { \
0x9f, 0x5d, 0x41, 0x29, 0xa9, 0x70, 0x80, 0x81 \
} \
}
extern "C" {
nsresult binary_http_constructor(REFNSIID iid, void** result);
};
#endif // _binary_http_h_

Просмотреть файл

@ -0,0 +1,263 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
extern crate bhttp;
extern crate nserror;
extern crate nsstring;
extern crate thin_vec;
#[macro_use]
extern crate xpcom;
use bhttp::{Message, Mode};
use nserror::{nsresult, NS_ERROR_FAILURE, NS_ERROR_INVALID_ARG, NS_ERROR_UNEXPECTED, NS_OK};
use nsstring::{nsACString, nsCString};
use thin_vec::ThinVec;
use xpcom::interfaces::{nsIBinaryHttpRequest, nsIBinaryHttpResponse};
use xpcom::RefPtr;
enum HeaderComponent {
Name,
Value,
}
// Extracts either the names or the values of a slice of header (name, value) pairs.
fn extract_header_components(
headers: &[(Vec<u8>, Vec<u8>)],
component: HeaderComponent,
) -> ThinVec<nsCString> {
let mut header_components = ThinVec::with_capacity(headers.len());
for (name, value) in headers {
match component {
HeaderComponent::Name => header_components.push(nsCString::from(name.clone())),
HeaderComponent::Value => header_components.push(nsCString::from(value.clone())),
}
}
header_components
}
#[xpcom(implement(nsIBinaryHttpRequest), atomic)]
struct BinaryHttpRequest {
method: Vec<u8>,
scheme: Vec<u8>,
authority: Vec<u8>,
path: Vec<u8>,
headers: Vec<(Vec<u8>, Vec<u8>)>,
content: Vec<u8>,
}
impl BinaryHttpRequest {
xpcom_method!(get_method => GetMethod() -> nsACString);
fn get_method(&self) -> Result<nsCString, nsresult> {
Ok(nsCString::from(self.method.clone()))
}
xpcom_method!(get_scheme => GetScheme() -> nsACString);
fn get_scheme(&self) -> Result<nsCString, nsresult> {
Ok(nsCString::from(self.scheme.clone()))
}
xpcom_method!(get_authority => GetAuthority() -> nsACString);
fn get_authority(&self) -> Result<nsCString, nsresult> {
Ok(nsCString::from(self.authority.clone()))
}
xpcom_method!(get_path => GetPath() -> nsACString);
fn get_path(&self) -> Result<nsCString, nsresult> {
Ok(nsCString::from(self.path.clone()))
}
xpcom_method!(get_content => GetContent() -> ThinVec<u8>);
fn get_content(&self) -> Result<ThinVec<u8>, nsresult> {
Ok(self.content.clone().into_iter().collect())
}
xpcom_method!(get_header_names => GetHeaderNames() -> ThinVec<nsCString>);
fn get_header_names(&self) -> Result<ThinVec<nsCString>, nsresult> {
Ok(extract_header_components(
&self.headers,
HeaderComponent::Name,
))
}
xpcom_method!(get_header_values => GetHeaderValues() -> ThinVec<nsCString>);
fn get_header_values(&self) -> Result<ThinVec<nsCString>, nsresult> {
Ok(extract_header_components(
&self.headers,
HeaderComponent::Value,
))
}
}
#[xpcom(implement(nsIBinaryHttpResponse), atomic)]
struct BinaryHttpResponse {
status: u16,
headers: Vec<(Vec<u8>, Vec<u8>)>,
content: Vec<u8>,
}
impl BinaryHttpResponse {
xpcom_method!(get_status => GetStatus() -> u16);
fn get_status(&self) -> Result<u16, nsresult> {
Ok(self.status)
}
xpcom_method!(get_content => GetContent() -> ThinVec<u8>);
fn get_content(&self) -> Result<ThinVec<u8>, nsresult> {
Ok(self.content.clone().into_iter().collect())
}
xpcom_method!(get_header_names => GetHeaderNames() -> ThinVec<nsCString>);
fn get_header_names(&self) -> Result<ThinVec<nsCString>, nsresult> {
Ok(extract_header_components(
&self.headers,
HeaderComponent::Name,
))
}
xpcom_method!(get_header_values => GetHeaderValues() -> ThinVec<nsCString>);
fn get_header_values(&self) -> Result<ThinVec<nsCString>, nsresult> {
Ok(extract_header_components(
&self.headers,
HeaderComponent::Value,
))
}
}
#[xpcom(implement(nsIBinaryHttp), atomic)]
struct BinaryHttp {}
impl BinaryHttp {
xpcom_method!(encode_request => EncodeRequest(request: *const nsIBinaryHttpRequest) -> ThinVec<u8>);
fn encode_request(&self, request: &nsIBinaryHttpRequest) -> Result<ThinVec<u8>, nsresult> {
let mut method = nsCString::new();
unsafe { request.GetMethod(&mut *method) }.to_result()?;
let mut scheme = nsCString::new();
unsafe { request.GetScheme(&mut *scheme) }.to_result()?;
let mut authority = nsCString::new();
unsafe { request.GetAuthority(&mut *authority) }.to_result()?;
let mut path = nsCString::new();
unsafe { request.GetPath(&mut *path) }.to_result()?;
let mut message = Message::request(
method.to_vec(),
scheme.to_vec(),
authority.to_vec(),
path.to_vec(),
);
let mut header_names = ThinVec::new();
unsafe { request.GetHeaderNames(&mut header_names) }.to_result()?;
let mut header_values = ThinVec::with_capacity(header_names.len());
unsafe { request.GetHeaderValues(&mut header_values) }.to_result()?;
if header_names.len() != header_values.len() {
return Err(NS_ERROR_INVALID_ARG);
}
for (name, value) in header_names.iter().zip(header_values.iter()) {
message.put_header(name.to_vec(), value.to_vec());
}
let mut content = ThinVec::new();
unsafe { request.GetContent(&mut content) }.to_result()?;
message.write_content(content);
let mut encoded = ThinVec::new();
message
.write_bhttp(Mode::KnownLength, &mut encoded)
.map_err(|_| NS_ERROR_FAILURE)?;
Ok(encoded)
}
xpcom_method!(decode_response => DecodeResponse(response: *const ThinVec<u8>) -> *const nsIBinaryHttpResponse);
fn decode_response(
&self,
response: &ThinVec<u8>,
) -> Result<RefPtr<nsIBinaryHttpResponse>, nsresult> {
let decoded =
Message::read_bhttp(&mut response.as_slice()).map_err(|_| NS_ERROR_UNEXPECTED)?;
let status = decoded.control().status().ok_or(NS_ERROR_UNEXPECTED)?;
let headers = decoded
.header()
.iter()
.map(|field| (field.name().to_vec(), field.value().to_vec()))
.collect();
let content = decoded.content().to_vec();
let binary_http_response = BinaryHttpResponse::allocate(InitBinaryHttpResponse {
status,
headers,
content,
});
binary_http_response
.query_interface::<nsIBinaryHttpResponse>()
.ok_or(NS_ERROR_FAILURE)
}
xpcom_method!(decode_request => DecodeRequest(request: *const ThinVec<u8>) -> *const nsIBinaryHttpRequest);
fn decode_request(
&self,
request: &ThinVec<u8>,
) -> Result<RefPtr<nsIBinaryHttpRequest>, nsresult> {
let decoded =
Message::read_bhttp(&mut request.as_slice()).map_err(|_| NS_ERROR_UNEXPECTED)?;
let method = decoded
.control()
.method()
.ok_or(NS_ERROR_UNEXPECTED)?
.to_vec();
let scheme = decoded
.control()
.scheme()
.ok_or(NS_ERROR_UNEXPECTED)?
.to_vec();
// authority and path can be empty, in which case we return empty arrays
let authority = decoded.control().authority().unwrap_or(&[]).to_vec();
let path = decoded.control().path().unwrap_or(&[]).to_vec();
let headers = decoded
.header()
.iter()
.map(|field| (field.name().to_vec(), field.value().to_vec()))
.collect();
let content = decoded.content().to_vec();
let binary_http_request = BinaryHttpRequest::allocate(InitBinaryHttpRequest {
method,
scheme,
authority,
path,
headers,
content,
});
binary_http_request
.query_interface::<nsIBinaryHttpRequest>()
.ok_or(NS_ERROR_FAILURE)
}
xpcom_method!(encode_response => EncodeResponse(response: *const nsIBinaryHttpResponse) -> ThinVec<u8>);
fn encode_response(&self, response: &nsIBinaryHttpResponse) -> Result<ThinVec<u8>, nsresult> {
let mut status = 0;
unsafe { response.GetStatus(&mut status) }.to_result()?;
let mut message = Message::response(status);
let mut header_names = ThinVec::new();
unsafe { response.GetHeaderNames(&mut header_names) }.to_result()?;
let mut header_values = ThinVec::with_capacity(header_names.len());
unsafe { response.GetHeaderValues(&mut header_values) }.to_result()?;
if header_names.len() != header_values.len() {
return Err(NS_ERROR_INVALID_ARG);
}
for (name, value) in header_values.iter().zip(header_names.iter()) {
message.put_header(name.to_vec(), value.to_vec());
}
let mut content = ThinVec::new();
unsafe { response.GetContent(&mut content) }.to_result()?;
message.write_content(content);
let mut encoded = ThinVec::new();
message
.write_bhttp(Mode::KnownLength, &mut encoded)
.map_err(|_| NS_ERROR_FAILURE)?;
Ok(encoded)
}
}
#[no_mangle]
pub extern "C" fn binary_http_constructor(
iid: *const xpcom::nsIID,
result: *mut *mut xpcom::reexports::libc::c_void,
) -> nsresult {
let binary_http = BinaryHttp::allocate(InitBinaryHttp {});
unsafe { binary_http.QueryInterface(iid, result) }
}

Просмотреть файл

@ -11,6 +11,12 @@ Classes = [
'jsm': 'resource://gre/modules/WellKnownOpportunisticUtils.jsm',
'constructor': 'WellKnownOpportunisticUtils',
},
{
'cid': '{b43b3f73-8160-4ab2-9f5d-4129a9708081}',
'contract_ids': ['@mozilla.org/network/binary-http;1'],
'headers': ['/netwerk/protocol/http/binary_http/src/binary_http.h'],
'legacy_constructor': 'binary_http_constructor',
},
{
'cid': '{d581149e-3319-4563-b95e-46c64af5c4e8}',
'contract_ids': ['@mozilla.org/network/oblivious-http;1'],

Просмотреть файл

@ -9,6 +9,7 @@ with Files("**"):
XPIDL_SOURCES += [
"nsIBackgroundChannelRegistrar.idl",
"nsIBinaryHttp.idl",
"nsIEarlyHintObserver.idl",
"nsIHttpActivityObserver.idl",
"nsIHttpAuthenticableChannel.idl",

Просмотреть файл

@ -0,0 +1,40 @@
/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsISupports.idl"
[scriptable, uuid(f6f899cc-683a-43da-9206-0eb0c09cc758)]
interface nsIBinaryHttpRequest : nsISupports {
readonly attribute ACString method;
readonly attribute ACString scheme;
readonly attribute ACString authority;
readonly attribute ACString path;
readonly attribute Array<ACString> headerNames;
readonly attribute Array<ACString> headerValues;
readonly attribute Array<octet> content;
};
[scriptable, uuid(6ca85d9c-cdc5-45d4-9adc-005abedce9c9)]
interface nsIBinaryHttpResponse : nsISupports {
readonly attribute uint16_t status;
readonly attribute Array<ACString> headerNames;
readonly attribute Array<ACString> headerValues;
readonly attribute Array<octet> content;
};
// Implements Binary Representation of HTTP Messages (RFC 9292).
// In normal operation, encodeRequest and decodeResponse are expected to be
// used. For testing, decodeRequest and encodeResponse are available as well.
// Thread safety: this interface may be used on any thread, but objects
// returned by it are not inherently thread-safe and should only be used on the
// threads they were created on.
[scriptable, builtinclass, uuid(b43b3f73-8160-4ab2-9f5d-4129a9708081)]
interface nsIBinaryHttp : nsISupports {
Array<octet> encodeRequest(in nsIBinaryHttpRequest request);
nsIBinaryHttpRequest decodeRequest(in Array<octet> request);
nsIBinaryHttpResponse decodeResponse(in Array<octet> response);
Array<octet> encodeResponse(in nsIBinaryHttpResponse response);
};

Просмотреть файл

@ -447,3 +447,13 @@ function promiseAsyncOpen(chan) {
);
});
}
function hexStringToBytes(hex) {
let bytes = [];
for (let hexByteStr of hex.split(/(..)/)) {
if (hexByteStr.length) {
bytes.push(parseInt(hexByteStr, 16));
}
}
return bytes;
}

Просмотреть файл

@ -0,0 +1,237 @@
/* Any copyright is dedicated to the Public Domain.
* https://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Unit tests for the binary http bindings.
// Tests basic encoding and decoding of requests and responses.
function stringToBytes(str) {
return Array.from(str, chr => chr.charCodeAt(0));
}
function BinaryHttpRequest(
method,
scheme,
authority,
path,
headerNames,
headerValues,
content
) {
this.method = method;
this.scheme = scheme;
this.authority = authority;
this.path = path;
this.headerNames = headerNames;
this.headerValues = headerValues;
this.content = content;
}
BinaryHttpRequest.prototype = {
QueryInterface: ChromeUtils.generateQI(["nsIBinaryHttpRequest"]),
};
function BinaryHttpResponse(status, headerNames, headerValues, content) {
this.status = status;
this.headerNames = headerNames;
this.headerValues = headerValues;
this.content = content;
}
BinaryHttpResponse.prototype = {
QueryInterface: ChromeUtils.generateQI(["nsIBinaryHttpResponse"]),
};
function test_encode_request() {
let bhttp = Cc["@mozilla.org/network/binary-http;1"].getService(
Ci.nsIBinaryHttp
);
let request = new BinaryHttpRequest(
"GET",
"https",
"",
"/hello.txt",
["user-agent", "host", "accept-language"],
[
"curl/7.16.3 libcurl/7.16.3 OpenSSL/0.9.7l zlib/1.2.3",
"www.example.com",
"en, mi",
],
[]
);
let encoded = bhttp.encodeRequest(request);
// This example is from RFC 9292.
let expected = hexStringToBytes(
"0003474554056874747073000a2f6865" +
"6c6c6f2e747874406c0a757365722d61" +
"67656e74346375726c2f372e31362e33" +
"206c69626375726c2f372e31362e3320" +
"4f70656e53534c2f302e392e376c207a" +
"6c69622f312e322e3304686f73740f77" +
"77772e6578616d706c652e636f6d0f61" +
"63636570742d6c616e67756167650665" +
"6e2c206d690000"
);
deepEqual(encoded, expected);
let mismatchedHeaders = new BinaryHttpRequest(
"GET",
"https",
"",
"",
["whoops-only-one-header-name"],
["some-header-value", "some-other-header-value"],
[]
);
// The implementation uses "NS_ERROR_INVALID_ARG", because that's an
// appropriate description for the error. However, that is an alias to
// "NS_ERROR_ILLEGAL_VALUE", which is what the actual exception uses, so
// that's what is tested for here.
Assert.throws(
() => bhttp.encodeRequest(mismatchedHeaders),
/NS_ERROR_ILLEGAL_VALUE/
);
}
function test_decode_request() {
let bhttp = Cc["@mozilla.org/network/binary-http;1"].getService(
Ci.nsIBinaryHttp
);
// From RFC 9292.
let encoded = hexStringToBytes(
"0003474554056874747073000a2f6865" +
"6c6c6f2e747874406c0a757365722d61" +
"67656e74346375726c2f372e31362e33" +
"206c69626375726c2f372e31362e3320" +
"4f70656e53534c2f302e392e376c207a" +
"6c69622f312e322e3304686f73740f77" +
"77772e6578616d706c652e636f6d0f61" +
"63636570742d6c616e67756167650665" +
"6e2c206d690000"
);
let request = bhttp.decodeRequest(encoded);
equal(request.method, "GET");
equal(request.scheme, "https");
equal(request.authority, "");
equal(request.path, "/hello.txt");
let expectedHeaderNames = ["user-agent", "host", "accept-language"];
deepEqual(request.headerNames, expectedHeaderNames);
let expectedHeaderValues = [
"curl/7.16.3 libcurl/7.16.3 OpenSSL/0.9.7l zlib/1.2.3",
"www.example.com",
"en, mi",
];
deepEqual(request.headerValues, expectedHeaderValues);
deepEqual(request.content, []);
let garbage = hexStringToBytes("115f00ab64c0fa783fe4cb723eaa87fa78900a0b00");
Assert.throws(() => bhttp.decodeRequest(garbage), /NS_ERROR_UNEXPECTED/);
}
function test_decode_response() {
let bhttp = Cc["@mozilla.org/network/binary-http;1"].getService(
Ci.nsIBinaryHttp
);
// From RFC 9292.
let encoded = hexStringToBytes(
"0340660772756e6e696e670a22736c65" +
"657020313522004067046c696e6b233c" +
"2f7374796c652e6373733e3b2072656c" +
"3d7072656c6f61643b2061733d737479" +
"6c65046c696e6b243c2f736372697074" +
"2e6a733e3b2072656c3d7072656c6f61" +
"643b2061733d7363726970740040c804" +
"646174651d4d6f6e2c203237204a756c" +
"20323030392031323a32383a35332047" +
"4d540673657276657206417061636865" +
"0d6c6173742d6d6f6469666965641d57" +
"65642c203232204a756c203230303920" +
"31393a31353a353620474d5404657461" +
"671422333461613338372d642d313536" +
"3865623030220d6163636570742d7261" +
"6e6765730562797465730e636f6e7465" +
"6e742d6c656e67746802353104766172" +
"790f4163636570742d456e636f64696e" +
"670c636f6e74656e742d747970650a74" +
"6578742f706c61696e003348656c6c6f" +
"20576f726c6421204d7920636f6e7465" +
"6e7420696e636c756465732061207472" +
"61696c696e672043524c462e0d0a0000"
);
let response = bhttp.decodeResponse(encoded);
equal(response.status, 200);
deepEqual(
response.content,
stringToBytes("Hello World! My content includes a trailing CRLF.\r\n")
);
let expectedHeaderNames = [
"date",
"server",
"last-modified",
"etag",
"accept-ranges",
"content-length",
"vary",
"content-type",
];
deepEqual(response.headerNames, expectedHeaderNames);
let expectedHeaderValues = [
"Mon, 27 Jul 2009 12:28:53 GMT",
"Apache",
"Wed, 22 Jul 2009 19:15:56 GMT",
'"34aa387-d-1568eb00"',
"bytes",
"51",
"Accept-Encoding",
"text/plain",
];
deepEqual(response.headerValues, expectedHeaderValues);
let garbage = hexStringToBytes(
"0367890084cb0ab03115fa0b4c2ea0fa783f7a87fa00"
);
Assert.throws(() => bhttp.decodeResponse(garbage), /NS_ERROR_UNEXPECTED/);
}
function test_encode_response() {
let response = new BinaryHttpResponse(
418,
["content-type"],
["text/plain"],
stringToBytes("I'm a teapot")
);
let bhttp = Cc["@mozilla.org/network/binary-http;1"].getService(
Ci.nsIBinaryHttp
);
let encoded = bhttp.encodeResponse(response);
let expected = hexStringToBytes(
"0141a2180a746578742f706c61696e0c" +
"636f6e74656e742d747970650c49276d" +
"206120746561706f7400"
);
deepEqual(encoded, expected);
let mismatchedHeaders = new BinaryHttpResponse(
500,
["some-header", "some-other-header"],
["whoops-only-one-header-value"],
[]
);
// The implementation uses "NS_ERROR_INVALID_ARG", because that's an
// appropriate description for the error. However, that is an alias to
// "NS_ERROR_ILLEGAL_VALUE", which is what the actual exception uses, so
// that's what is tested for here.
Assert.throws(
() => bhttp.encodeResponse(mismatchedHeaders),
/NS_ERROR_ILLEGAL_VALUE/
);
}
function run_test() {
test_encode_request();
test_decode_request();
test_encode_response();
test_decode_response();
}

Просмотреть файл

@ -3,16 +3,6 @@
"use strict";
function hexStringToBytes(hex) {
let bytes = [];
for (let hexByteStr of hex.split(/(..)/)) {
if (hexByteStr.length) {
bytes.push(parseInt(hexByteStr, 16));
}
}
return bytes;
}
function test_known_config() {
let ohttp = Cc["@mozilla.org/network/oblivious-http;1"].getService(
Ci.nsIObliviousHttp

Просмотреть файл

@ -682,6 +682,7 @@ head = head_channels.js head_cache.js head_cookies.js head_trr.js head_http3.js
[test_coaleasing_h2_and_h3_connection.js]
skip-if = os == 'android'
run-sequentially = http3server
[test_bhttp.js]
[test_ohttp.js]
[test_websocket_500k.js]
skip-if = verify

Просмотреть файл

@ -113,6 +113,12 @@ criteria = "safe-to-deploy"
version = "1.1.0"
notes = "All code written or reviewed by Josh Stone."
[[audits.bhttp]]
who = "Dana Keeler <dkeeler@mozilla.com>"
criteria = "safe-to-deploy"
version = "0.2.3"
notes = "Mozilla-developed package, no unsafe code or powerful imports."
[[audits.bindgen]]
who = "Emilio Cobos Álvarez <emilio@crisal.io>"
criteria = "safe-to-deploy"

1
third_party/rust/bhttp/.cargo-checksum.json поставляемый Normal file
Просмотреть файл

@ -0,0 +1 @@
{"files":{"Cargo.toml":"c0467a1857c040076f2a4be2da9f34b52bbceda73ace86ccef35acad98d0ae7b","README.md":"329e4ce4fbfabd037f6202418fbb381b6f6c77728dbe39545dae7c4f4265bae7","src/err.rs":"f0861ce8656de7b714c99fbfaea8e60e516bc8e85d4a4969c413525a1b1394b7","src/lib.rs":"112aae36a450530635dc36fcc920ebfeb52527ec1349d5a57461c36c6b5acc1e","src/parse.rs":"b4691c1e39e42ffa4a4d1a56f8c6e7b1b7a38d8cf5a1786fdcee4d7d48be1ff6","src/rw.rs":"3de0c74d4bc669918ebd5e0cfe8c134278224627db7e69c68d2c0675795adb86","tests/test.rs":"f0ed8cdfef30253ae34a9bcd3c3990ce4fda8084f3c4253e458b75ef5c1640cc"},"package":"9a561f43fe82923605345b977ebd5951126f0a1b4575e3c3d53e5954e5822de4"}

41
third_party/rust/bhttp/Cargo.toml поставляемый Normal file
Просмотреть файл

@ -0,0 +1,41 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2021"
name = "bhttp"
version = "0.2.3"
authors = ["Martin Thomson <mt@lowentropy.net>"]
description = "Binary HTTP messages (draft-ietf-httpbis-binary-message)"
readme = "README.md"
license = "MIT OR Apache-2.0"
repository = "https://github.com/martinthomson/ohttp"
[dependencies.url]
version = "2"
[dev-dependencies.hex]
version = "0.4"
[features]
bhttp = [
"read-bhttp",
"write-bhttp",
]
default = ["bhttp"]
http = [
"read-http",
"write-http",
]
read-bhttp = []
read-http = []
write-bhttp = []
write-http = []

28
third_party/rust/bhttp/README.md поставляемый Normal file
Просмотреть файл

@ -0,0 +1,28 @@
# Binary HTTP Messages
This is a rust implementation of [Binary HTTP
Messages](https://httpwg.org/http-extensions/draft-ietf-httpbis-binary-message.html).
This work is undergoing active revision in the IETF and so are these
implementations. Use at your own risk.
## Using
The API documentation is currently sparse, but the API is fairly small and
descriptive.
The `bhttp` crate has the following features:
- `read-bhttp` enables parsing of binary HTTP messages. This is enabled by
default.
- `write-bhttp` enables writing of binary HTTP messages. This is enabled by
default.
- `read-http` enables a simple HTTP/1.1 message parser. This parser is fairly
basic and is not recommended for production use. Getting an HTTP/1.1 parser
right is a massive enterprise; this one only does the basics. This is
disabled by default.
- `write-http` enables writing of HTTP/1.1 messages. This is disabled by
default.

70
third_party/rust/bhttp/src/err.rs поставляемый Normal file
Просмотреть файл

@ -0,0 +1,70 @@
#[derive(Debug)]
pub enum Error {
/// A request used the CONNECT method.
ConnectUnsupported,
/// A field contained invalid Unicode.
CharacterEncoding(std::string::FromUtf8Error),
/// A field contained an integer value that was out of range.
IntRange(std::num::TryFromIntError),
/// The mode of the message was invalid.
InvalidMode,
/// An IO error.
Io(std::io::Error),
/// A field or line was missing a necessary character.
Missing(u8),
/// A URL was missing a key component.
MissingUrlComponent,
/// An obs-fold line was the first line of a field section.
ObsFold,
/// A field contained a non-integer value.
ParseInt(std::num::ParseIntError),
/// A field was truncated.
Truncated,
/// A message included the Upgrade field.
UpgradeUnsupported,
/// A URL could not be parsed into components.
UrlParse(url::ParseError),
}
macro_rules! forward_errors {
{$($(#[$a:meta])* $t:path => $v:ident),* $(,)?} => {
$(
impl From<$t> for Error {
fn from(e: $t) -> Self {
Self::$v(e)
}
}
)*
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
$( $(#[$a])* Self::$v(e) => Some(e), )*
_ => None,
}
}
}
};
}
forward_errors! {
std::io::Error => Io,
std::string::FromUtf8Error => CharacterEncoding,
std::num::ParseIntError => ParseInt,
std::num::TryFromIntError => IntRange,
url::ParseError => UrlParse,
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
write!(f, "{:?}", self)
}
}
#[cfg(any(
feature = "read-http",
feature = "write-http",
feature = "read-bhttp",
feature = "write-bhttp"
))]
pub type Res<T> = Result<T, Error>;

762
third_party/rust/bhttp/src/lib.rs поставляемый Normal file
Просмотреть файл

@ -0,0 +1,762 @@
#![deny(warnings, clippy::pedantic)]
#![allow(clippy::missing_errors_doc)] // Too lazy to document these.
#[cfg(feature = "read-bhttp")]
use std::convert::TryFrom;
#[cfg(any(
feature = "read-http",
feature = "write-http",
feature = "read-bhttp",
feature = "write-bhttp"
))]
use std::io;
#[cfg(feature = "read-http")]
use url::Url;
mod err;
mod parse;
#[cfg(any(feature = "read-bhttp", feature = "write-bhttp"))]
mod rw;
pub use err::Error;
#[cfg(any(
feature = "read-http",
feature = "write-http",
feature = "read-bhttp",
feature = "write-bhttp"
))]
use err::Res;
#[cfg(feature = "read-http")]
use parse::{downcase, is_ows, read_line, split_at, COLON, SEMICOLON, SLASH, SP};
use parse::{index_of, trim_ows, COMMA};
#[cfg(feature = "read-bhttp")]
use rw::{read_varint, read_vec};
#[cfg(feature = "write-bhttp")]
use rw::{write_len, write_varint, write_vec};
#[cfg(feature = "read-http")]
const CONTENT_LENGTH: &[u8] = b"content-length";
#[cfg(feature = "read-bhttp")]
const COOKIE: &[u8] = b"cookie";
const TRANSFER_ENCODING: &[u8] = b"transfer-encoding";
const CHUNKED: &[u8] = b"chunked";
pub type StatusCode = u16;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg(any(feature = "read-bhttp", feature = "write-bhttp"))]
pub enum Mode {
KnownLength,
IndefiniteLength,
}
pub struct Field {
name: Vec<u8>,
value: Vec<u8>,
}
impl Field {
#[must_use]
pub fn new(name: Vec<u8>, value: Vec<u8>) -> Self {
Self { name, value }
}
#[must_use]
pub fn name(&self) -> &[u8] {
&self.name
}
#[must_use]
pub fn value(&self) -> &[u8] {
&self.value
}
#[cfg(feature = "write-http")]
pub fn write_http(&self, w: &mut impl io::Write) -> Res<()> {
w.write_all(&self.name)?;
w.write_all(b": ")?;
w.write_all(&self.value)?;
w.write_all(b"\r\n")?;
Ok(())
}
#[cfg(feature = "write-bhttp")]
pub fn write_bhttp(&self, w: &mut impl io::Write) -> Res<()> {
write_vec(&self.name, w)?;
write_vec(&self.value, w)?;
Ok(())
}
#[cfg(feature = "read-http")]
pub fn obs_fold(&mut self, extra: &[u8]) {
self.value.push(SP);
self.value.extend(trim_ows(extra));
}
}
#[derive(Default)]
pub struct FieldSection(Vec<Field>);
impl FieldSection {
#[must_use]
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
/// Gets the value from the first instance of the field.
#[must_use]
pub fn get(&self, n: &[u8]) -> Option<&[u8]> {
for f in &self.0 {
if &f.name[..] == n {
return Some(&f.value);
}
}
None
}
pub fn put(&mut self, name: impl Into<Vec<u8>>, value: impl Into<Vec<u8>>) {
self.0.push(Field::new(name.into(), value.into()));
}
pub fn iter(&self) -> impl Iterator<Item = &Field> {
self.0.iter()
}
#[must_use]
pub fn fields(&self) -> &[Field] {
&self.0
}
#[must_use]
pub fn is_chunked(&self) -> bool {
// Look at the last symbol in Transfer-Encoding.
// This is very primitive decoding; structured field this is not.
if let Some(te) = self.get(TRANSFER_ENCODING) {
let mut slc = te;
while let Some(i) = index_of(COMMA, slc) {
slc = trim_ows(&slc[i + 1..]);
}
slc == CHUNKED
} else {
false
}
}
/// As required by the HTTP specification, remove the Connection header
/// field, everything it refers to, and a few extra fields.
#[cfg(feature = "read-http")]
fn strip_connection_headers(&mut self) {
const CONNECTION: &[u8] = b"connection";
const PROXY_CONNECTION: &[u8] = b"proxy-connection";
const SHOULD_REMOVE: &[&[u8]] = &[
CONNECTION,
PROXY_CONNECTION,
b"keep-alive",
b"te",
b"trailer",
b"transfer-encoding",
b"upgrade",
];
let mut listed = Vec::new();
let mut track = |n| {
let mut name = Vec::from(trim_ows(n));
downcase(&mut name);
if !listed.contains(&name) {
listed.push(name);
}
};
for f in self
.0
.iter()
.filter(|f| f.name() == CONNECTION || f.name == PROXY_CONNECTION)
{
let mut v = f.value();
while let Some(i) = index_of(COMMA, v) {
track(&v[..i]);
v = &v[i + 1..];
}
track(v);
}
self.0.retain(|f| {
!SHOULD_REMOVE.contains(&f.name()) && listed.iter().all(|x| &x[..] != f.name())
});
}
#[cfg(feature = "read-http")]
fn parse_line(fields: &mut Vec<Field>, line: Vec<u8>) -> Res<()> {
// obs-fold is helpful in specs, so support it here too
let f = if is_ows(line[0]) {
let mut e = fields.pop().ok_or(Error::ObsFold)?;
e.obs_fold(&line);
e
} else if let Some((n, v)) = split_at(COLON, line) {
let mut name = Vec::from(trim_ows(&n));
downcase(&mut name);
let value = Vec::from(trim_ows(&v));
Field::new(name, value)
} else {
return Err(Error::Missing(COLON));
};
fields.push(f);
Ok(())
}
#[cfg(feature = "read-http")]
pub fn read_http(r: &mut impl io::BufRead) -> Res<Self> {
let mut fields = Vec::new();
loop {
let line = read_line(r)?;
if trim_ows(&line).is_empty() {
return Ok(Self(fields));
}
Self::parse_line(&mut fields, line)?;
}
}
#[cfg(feature = "read-bhttp")]
fn read_bhttp_fields(terminator: bool, r: &mut impl io::BufRead) -> Res<Vec<Field>> {
let mut fields = Vec::new();
let mut cookie_index: Option<usize> = None;
loop {
if let Some(n) = read_vec(r)? {
if n.is_empty() {
if terminator {
return Ok(fields);
}
return Err(Error::Truncated);
}
let mut v = read_vec(r)?.ok_or(Error::Truncated)?;
if n == COOKIE {
if let Some(i) = &cookie_index {
fields[*i].value.extend_from_slice(b"; ");
fields[*i].value.append(&mut v);
continue;
}
cookie_index = Some(fields.len());
}
fields.push(Field::new(n, v));
} else if terminator {
return Err(Error::Truncated);
} else {
return Ok(fields);
}
}
}
#[cfg(feature = "read-bhttp")]
pub fn read_bhttp(mode: Mode, r: &mut impl io::BufRead) -> Res<Self> {
let fields = if mode == Mode::KnownLength {
if let Some(buf) = read_vec(r)? {
Self::read_bhttp_fields(false, &mut io::BufReader::new(&buf[..]))?
} else {
Vec::new()
}
} else {
Self::read_bhttp_fields(true, r)?
};
Ok(Self(fields))
}
#[cfg(feature = "write-bhttp")]
fn write_bhttp_headers(&self, w: &mut impl io::Write) -> Res<()> {
for f in &self.0 {
f.write_bhttp(w)?;
}
Ok(())
}
#[cfg(feature = "write-bhttp")]
pub fn write_bhttp(&self, mode: Mode, w: &mut impl io::Write) -> Res<()> {
if mode == Mode::KnownLength {
let mut buf = Vec::new();
self.write_bhttp_headers(&mut buf)?;
write_vec(&buf, w)?;
} else {
self.write_bhttp_headers(w)?;
write_len(0, w)?;
}
Ok(())
}
#[cfg(feature = "write-http")]
pub fn write_http(&self, w: &mut impl io::Write) -> Res<()> {
for f in &self.0 {
f.write_http(w)?;
}
w.write_all(b"\r\n")?;
Ok(())
}
}
pub enum ControlData {
Request {
method: Vec<u8>,
scheme: Vec<u8>,
authority: Vec<u8>,
path: Vec<u8>,
},
Response(StatusCode),
}
impl ControlData {
#[must_use]
pub fn is_request(&self) -> bool {
matches!(self, Self::Request { .. })
}
#[must_use]
pub fn method(&self) -> Option<&[u8]> {
if let Self::Request { method, .. } = self {
Some(method)
} else {
None
}
}
#[must_use]
pub fn scheme(&self) -> Option<&[u8]> {
if let Self::Request { scheme, .. } = self {
Some(scheme)
} else {
None
}
}
#[must_use]
pub fn authority(&self) -> Option<&[u8]> {
if let Self::Request { authority, .. } = self {
if authority.is_empty() {
None
} else {
Some(authority)
}
} else {
None
}
}
#[must_use]
pub fn path(&self) -> Option<&[u8]> {
if let Self::Request { path, .. } = self {
if path.is_empty() {
None
} else {
Some(path)
}
} else {
None
}
}
#[must_use]
pub fn status(&self) -> Option<StatusCode> {
if let Self::Response(code) = self {
Some(*code)
} else {
None
}
}
#[cfg(feature = "read-http")]
pub fn read_http(line: Vec<u8>) -> Res<Self> {
// request-line = method SP request-target SP HTTP-version
// status-line = HTTP-version SP status-code SP [reason-phrase]
let (a, r) = split_at(SP, line).ok_or(Error::Missing(SP))?;
let (b, _) = split_at(SP, r).ok_or(Error::Missing(SP))?;
if index_of(SLASH, &a).is_some() {
// Probably a response, so treat it as such.
let status_str = String::from_utf8(b)?;
let code = status_str.parse::<u16>()?;
Ok(Self::Response(code))
} else if index_of(COLON, &b).is_some() {
// Now try to parse the URL.
let url_str = String::from_utf8(b)?;
let parsed = Url::parse(&url_str)?;
let authority = parsed.host_str().map_or_else(String::new, |host| {
let mut authority = String::from(host);
if let Some(port) = parsed.port() {
authority.push(':');
authority.push_str(&port.to_string());
}
authority
});
let mut path = String::from(parsed.path());
if let Some(q) = parsed.query() {
path.push('?');
path.push_str(q);
}
Ok(Self::Request {
method: a,
scheme: Vec::from(parsed.scheme().as_bytes()),
authority: Vec::from(authority.as_bytes()),
path: Vec::from(path.as_bytes()),
})
} else {
if a == b"CONNECT" {
return Err(Error::ConnectUnsupported);
}
Ok(Self::Request {
method: a,
scheme: Vec::from(&b"https"[..]),
authority: Vec::new(),
path: b,
})
}
}
#[cfg(feature = "read-bhttp")]
pub fn read_bhttp(request: bool, r: &mut impl io::BufRead) -> Res<Self> {
let v = if request {
let method = read_vec(r)?.ok_or(Error::Truncated)?;
let scheme = read_vec(r)?.ok_or(Error::Truncated)?;
let authority = read_vec(r)?.ok_or(Error::Truncated)?;
let path = read_vec(r)?.ok_or(Error::Truncated)?;
Self::Request {
method,
scheme,
authority,
path,
}
} else {
Self::Response(u16::try_from(read_varint(r)?.ok_or(Error::Truncated)?)?)
};
Ok(v)
}
/// If this is an informational response.
#[cfg(any(feature = "read-bhttp", feature = "read-http"))]
#[must_use]
fn informational(&self) -> Option<StatusCode> {
match self {
Self::Response(v) if *v >= 100 && *v < 200 => Some(*v),
_ => None,
}
}
#[cfg(feature = "write-bhttp")]
#[must_use]
fn code(&self, mode: Mode) -> u64 {
match (self, mode) {
(Self::Request { .. }, Mode::KnownLength) => 0,
(Self::Response(_), Mode::KnownLength) => 1,
(Self::Request { .. }, Mode::IndefiniteLength) => 2,
(Self::Response(_), Mode::IndefiniteLength) => 3,
}
}
#[cfg(feature = "write-bhttp")]
pub fn write_bhttp(&self, w: &mut impl io::Write) -> Res<()> {
match self {
Self::Request {
method,
scheme,
authority,
path,
} => {
write_vec(method, w)?;
write_vec(scheme, w)?;
write_vec(authority, w)?;
write_vec(path, w)?;
}
Self::Response(status) => write_varint(*status, w)?,
}
Ok(())
}
#[cfg(feature = "write-http")]
pub fn write_http(&self, w: &mut impl io::Write) -> Res<()> {
match self {
Self::Request {
method,
scheme,
authority,
path,
} => {
w.write_all(method)?;
w.write_all(b" ")?;
if !authority.is_empty() {
w.write_all(scheme)?;
w.write_all(b"://")?;
w.write_all(authority)?;
}
w.write_all(path)?;
w.write_all(b" HTTP/1.1\r\n")?;
}
Self::Response(status) => {
let buf = format!("HTTP/1.1 {} Reason\r\n", *status);
w.write_all(buf.as_bytes())?;
}
}
Ok(())
}
}
pub struct InformationalResponse {
status: StatusCode,
fields: FieldSection,
}
impl InformationalResponse {
#[must_use]
pub fn new(status: StatusCode, fields: FieldSection) -> Self {
Self { status, fields }
}
#[must_use]
pub fn status(&self) -> StatusCode {
self.status
}
#[must_use]
pub fn fields(&self) -> &FieldSection {
&self.fields
}
#[cfg(feature = "write-bhttp")]
fn write_bhttp(&self, mode: Mode, w: &mut impl io::Write) -> Res<()> {
write_varint(self.status, w)?;
self.fields.write_bhttp(mode, w)?;
Ok(())
}
}
pub struct Message {
informational: Vec<InformationalResponse>,
control: ControlData,
header: FieldSection,
content: Vec<u8>,
trailer: FieldSection,
}
impl Message {
#[must_use]
pub fn request(method: Vec<u8>, scheme: Vec<u8>, authority: Vec<u8>, path: Vec<u8>) -> Self {
Self {
informational: Vec::new(),
control: ControlData::Request {
method,
scheme,
authority,
path,
},
header: FieldSection::default(),
content: Vec::new(),
trailer: FieldSection::default(),
}
}
#[must_use]
pub fn response(status: StatusCode) -> Self {
Self {
informational: Vec::new(),
control: ControlData::Response(status),
header: FieldSection::default(),
content: Vec::new(),
trailer: FieldSection::default(),
}
}
pub fn put_header(&mut self, name: impl Into<Vec<u8>>, value: impl Into<Vec<u8>>) {
self.header.put(name, value);
}
pub fn put_trailer(&mut self, name: impl Into<Vec<u8>>, value: impl Into<Vec<u8>>) {
self.trailer.put(name, value);
}
pub fn write_content(&mut self, d: impl AsRef<[u8]>) {
self.content.extend_from_slice(d.as_ref());
}
#[must_use]
pub fn informational(&self) -> &[InformationalResponse] {
&self.informational
}
#[must_use]
pub fn control(&self) -> &ControlData {
&self.control
}
#[must_use]
pub fn header(&self) -> &FieldSection {
&self.header
}
#[must_use]
pub fn content(&self) -> &[u8] {
&self.content
}
#[must_use]
pub fn trailer(&self) -> &FieldSection {
&self.trailer
}
#[cfg(feature = "read-http")]
fn read_chunked(r: &mut impl io::BufRead) -> Res<Vec<u8>> {
let mut content = Vec::new();
loop {
let mut line = read_line(r)?;
if let Some(i) = index_of(SEMICOLON, &line) {
std::mem::drop(line.split_off(i));
}
let count_str = String::from_utf8(line)?;
let count = usize::from_str_radix(&count_str, 16)?;
if count == 0 {
return Ok(content);
}
let mut buf = vec![0; count];
r.read_exact(&mut buf)?;
assert!(read_line(r)?.is_empty());
content.append(&mut buf);
}
}
#[cfg(feature = "read-http")]
#[allow(clippy::read_zero_byte_vec)] // https://github.com/rust-lang/rust-clippy/issues/9274
pub fn read_http(r: &mut impl io::BufRead) -> Res<Self> {
let line = read_line(r)?;
let mut control = ControlData::read_http(line)?;
let mut informational = Vec::new();
while let Some(status) = control.informational() {
let fields = FieldSection::read_http(r)?;
informational.push(InformationalResponse::new(status, fields));
let line = read_line(r)?;
control = ControlData::read_http(line)?;
}
let mut header = FieldSection::read_http(r)?;
let (content, trailer) = if matches!(control.status(), Some(204) | Some(304)) {
// 204 and 304 have no body, no matter what Content-Length says.
// Unfortunately, we can't do the same for responses to HEAD.
(Vec::new(), FieldSection::default())
} else if header.is_chunked() {
let content = Self::read_chunked(r)?;
let trailer = FieldSection::read_http(r)?;
(content, trailer)
} else {
let mut content = Vec::new();
if let Some(cl) = header.get(CONTENT_LENGTH) {
let cl_str = String::from_utf8(Vec::from(cl))?;
let cl_int = cl_str.parse::<usize>()?;
if cl_int > 0 {
content.resize(cl_int, 0);
r.read_exact(&mut content)?;
}
} else {
// Note that for a request, the spec states that the content is
// empty, but this just reads all input like for a response.
r.read_to_end(&mut content)?;
}
(content, FieldSection::default())
};
header.strip_connection_headers();
Ok(Self {
informational,
control,
header,
content,
trailer,
})
}
#[cfg(feature = "write-http")]
pub fn write_http(&self, w: &mut impl io::Write) -> Res<()> {
for info in &self.informational {
ControlData::Response(info.status()).write_http(w)?;
info.fields().write_http(w)?;
}
self.control.write_http(w)?;
if !self.content.is_empty() {
if self.trailer.is_empty() {
write!(w, "Content-Length: {}\r\n", self.content.len())?;
} else {
w.write_all(b"Transfer-Encoding: chunked\r\n")?;
}
}
self.header.write_http(w)?;
if self.header.is_chunked() {
write!(w, "{:x}\r\n", self.content.len())?;
w.write_all(&self.content)?;
w.write_all(b"\r\n0\r\n")?;
self.trailer.write_http(w)?;
} else {
w.write_all(&self.content)?;
}
Ok(())
}
/// Read a BHTTP message.
#[cfg(feature = "read-bhttp")]
pub fn read_bhttp(r: &mut impl io::BufRead) -> Res<Self> {
let t = read_varint(r)?.ok_or(Error::Truncated)?;
let request = t == 0 || t == 2;
let mode = match t {
0 | 1 => Mode::KnownLength,
2 | 3 => Mode::IndefiniteLength,
_ => return Err(Error::InvalidMode),
};
let mut control = ControlData::read_bhttp(request, r)?;
let mut informational = Vec::new();
while let Some(status) = control.informational() {
let fields = FieldSection::read_bhttp(mode, r)?;
informational.push(InformationalResponse::new(status, fields));
control = ControlData::read_bhttp(request, r)?;
}
let header = FieldSection::read_bhttp(mode, r)?;
let mut content = read_vec(r)?.unwrap_or_default();
if mode == Mode::IndefiniteLength && !content.is_empty() {
loop {
let mut extra = read_vec(r)?.unwrap_or_default();
if extra.is_empty() {
break;
}
content.append(&mut extra);
}
}
let trailer = FieldSection::read_bhttp(mode, r)?;
Ok(Self {
informational,
control,
header,
content,
trailer,
})
}
#[cfg(feature = "write-bhttp")]
pub fn write_bhttp(&self, mode: Mode, w: &mut impl io::Write) -> Res<()> {
write_varint(self.control.code(mode), w)?;
for info in &self.informational {
info.write_bhttp(mode, w)?;
}
self.control.write_bhttp(w)?;
self.header.write_bhttp(mode, w)?;
write_vec(&self.content, w)?;
if mode == Mode::IndefiniteLength && !self.content.is_empty() {
write_len(0, w)?;
}
self.trailer.write_bhttp(mode, w)?;
Ok(())
}
}
#[cfg(feature = "write-http")]
impl std::fmt::Debug for Message {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
let mut buf = Vec::new();
self.write_http(&mut buf).map_err(|_| std::fmt::Error)?;
write!(f, "{:?}", String::from_utf8_lossy(&buf))
}
}

75
third_party/rust/bhttp/src/parse.rs поставляемый Normal file
Просмотреть файл

@ -0,0 +1,75 @@
#[cfg(feature = "read-http")]
use crate::{Error, Res};
pub const HTAB: u8 = 0x09;
#[cfg(feature = "read-http")]
pub const NL: u8 = 0x0a;
#[cfg(feature = "read-http")]
pub const CR: u8 = 0x0d;
pub const SP: u8 = 0x20;
pub const COMMA: u8 = 0x2c;
#[cfg(feature = "read-http")]
pub const SLASH: u8 = 0x2f;
#[cfg(feature = "read-http")]
pub const COLON: u8 = 0x3a;
#[cfg(feature = "read-http")]
pub const SEMICOLON: u8 = 0x3b;
pub fn is_ows(x: u8) -> bool {
x == SP || x == HTAB
}
pub fn trim_ows(v: &[u8]) -> &[u8] {
for s in 0..v.len() {
if !is_ows(v[s]) {
for e in (s..v.len()).rev() {
if !is_ows(v[e]) {
return &v[s..=e];
}
}
}
}
&v[..0]
}
#[cfg(feature = "read-http")]
pub fn downcase(n: &mut [u8]) {
for i in n {
if *i >= 0x41 && *i <= 0x5a {
*i += 0x20;
}
}
}
pub fn index_of(v: u8, line: &[u8]) -> Option<usize> {
for (i, x) in line.iter().enumerate() {
if *x == v {
return Some(i);
}
}
None
}
#[cfg(feature = "read-http")]
pub fn split_at(v: u8, mut line: Vec<u8>) -> Option<(Vec<u8>, Vec<u8>)> {
index_of(v, &line).map(|i| {
let tail = line.split_off(i + 1);
let _ = line.pop();
(line, tail)
})
}
#[cfg(feature = "read-http")]
pub fn read_line(r: &mut impl std::io::BufRead) -> Res<Vec<u8>> {
let mut buf = Vec::new();
r.read_until(NL, &mut buf)?;
let tail = buf.pop();
if tail != Some(NL) {
return Err(Error::Truncated);
}
if buf.pop().ok_or(Error::Missing(CR))? == CR {
Ok(buf)
} else {
Err(Error::Missing(CR))
}
}

82
third_party/rust/bhttp/src/rw.rs поставляемый Normal file
Просмотреть файл

@ -0,0 +1,82 @@
#[cfg(feature = "read-bhttp")]
use crate::err::Error;
use crate::err::Res;
use std::convert::TryFrom;
use std::io;
#[cfg(feature = "write-bhttp")]
#[allow(clippy::cast_possible_truncation)]
fn write_uint(n: u8, v: impl Into<u64>, w: &mut impl io::Write) -> Res<()> {
let v = v.into();
assert!(n > 0 && usize::from(n) < std::mem::size_of::<u64>());
for i in 0..n {
w.write_all(&[((v >> (8 * (n - i - 1))) & 0xff) as u8])?;
}
Ok(())
}
#[cfg(feature = "write-bhttp")]
pub fn write_varint(v: impl Into<u64>, w: &mut impl io::Write) -> Res<()> {
let v = v.into();
match () {
_ if v < (1 << 6) => write_uint(1, v, w),
_ if v < (1 << 14) => write_uint(2, v | (1 << 14), w),
_ if v < (1 << 30) => write_uint(4, v | (2 << 30), w),
_ if v < (1 << 62) => write_uint(8, v | (3 << 62), w),
_ => panic!("Varint value too large"),
}
}
#[cfg(feature = "write-bhttp")]
pub fn write_len(len: usize, w: &mut impl io::Write) -> Res<()> {
write_varint(u64::try_from(len).unwrap(), w)
}
#[cfg(feature = "write-bhttp")]
pub fn write_vec(v: &[u8], w: &mut impl io::Write) -> Res<()> {
write_len(v.len(), w)?;
w.write_all(v)?;
Ok(())
}
#[cfg(feature = "read-bhttp")]
fn read_uint(n: usize, r: &mut impl io::BufRead) -> Res<Option<u64>> {
let mut buf = [0; 7];
let count = r.read(&mut buf[..n])?;
if count == 0 {
return Ok(None);
} else if count < n {
return Err(Error::Truncated);
}
let mut v = 0;
for i in &buf[..n] {
v = (v << 8) | u64::from(*i);
}
Ok(Some(v))
}
#[cfg(feature = "read-bhttp")]
pub fn read_varint(r: &mut impl io::BufRead) -> Res<Option<u64>> {
if let Some(b1) = read_uint(1, r)? {
Ok(Some(match b1 >> 6 {
0 => b1 & 0x3f,
1 => ((b1 & 0x3f) << 8) | read_uint(1, r)?.ok_or(Error::Truncated)?,
2 => ((b1 & 0x3f) << 24) | read_uint(3, r)?.ok_or(Error::Truncated)?,
3 => ((b1 & 0x3f) << 56) | read_uint(7, r)?.ok_or(Error::Truncated)?,
_ => unreachable!(),
}))
} else {
Ok(None)
}
}
#[cfg(feature = "read-bhttp")]
pub fn read_vec(r: &mut impl io::BufRead) -> Res<Option<Vec<u8>>> {
if let Some(len) = read_varint(r)? {
let mut v = vec![0; usize::try_from(len).unwrap()];
r.read_exact(&mut v)?;
Ok(Some(v))
} else {
Ok(None)
}
}

210
third_party/rust/bhttp/tests/test.rs поставляемый Normal file
Просмотреть файл

@ -0,0 +1,210 @@
// Rather than grapple with #[cfg(...)] for every variable and import.
#![cfg(all(feature = "http", feature = "bhttp"))]
use bhttp::{Error, Message, Mode};
use std::io::BufReader;
use std::mem::drop;
const CHUNKED_HTTP: &[u8] = b"HTTP/1.1 200 OK\r\n\
Transfer-Encoding: camel, chunked\r\n\
\r\n\
4\r\n\
This\r\n\
6\r\n \
conte\r\n\
13;chunk-extension=foo\r\n\
nt contains CRLF.\r\n\
\r\n\
0\r\n\
Trailer: text\r\n\
\r\n";
const TRANSFER_ENCODING: &[u8] = b"transfer-encoding";
const CHUNKED_KNOWN: &[u8] = &[
0x01, 0x40, 0xc8, 0x00, 0x1d, 0x54, 0x68, 0x69, 0x73, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e,
0x74, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x73, 0x20, 0x43, 0x52, 0x4c, 0x46, 0x2e,
0x0d, 0x0a, 0x0d, 0x07, 0x74, 0x72, 0x61, 0x69, 0x6c, 0x65, 0x72, 0x04, 0x74, 0x65, 0x78, 0x74,
];
const CHUNKED_INDEFINITE: &[u8] = &[
0x03, 0x40, 0xc8, 0x00, 0x1d, 0x54, 0x68, 0x69, 0x73, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e,
0x74, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x73, 0x20, 0x43, 0x52, 0x4c, 0x46, 0x2e,
0x0d, 0x0a, 0x00, 0x07, 0x74, 0x72, 0x61, 0x69, 0x6c, 0x65, 0x72, 0x04, 0x74, 0x65, 0x78, 0x74,
0x00,
];
const REQUEST: &[u8] = b"GET /hello.txt HTTP/1.1\r\n\
user-agent: curl/7.16.3 libcurl/7.16.3 OpenSSL/0.9.7l zlib/1.2.3\r\n\
host: www.example.com\r\n\
accept-language: en, mi\r\n\
\r\n";
const REQUEST_KNOWN: &[u8] = &[
0x00, 0x03, 0x47, 0x45, 0x54, 0x05, 0x68, 0x74, 0x74, 0x70, 0x73, 0x00, 0x0a, 0x2f, 0x68, 0x65,
0x6c, 0x6c, 0x6f, 0x2e, 0x74, 0x78, 0x74, 0x40, 0x6c, 0x0a, 0x75, 0x73, 0x65, 0x72, 0x2d, 0x61,
0x67, 0x65, 0x6e, 0x74, 0x34, 0x63, 0x75, 0x72, 0x6c, 0x2f, 0x37, 0x2e, 0x31, 0x36, 0x2e, 0x33,
0x20, 0x6c, 0x69, 0x62, 0x63, 0x75, 0x72, 0x6c, 0x2f, 0x37, 0x2e, 0x31, 0x36, 0x2e, 0x33, 0x20,
0x4f, 0x70, 0x65, 0x6e, 0x53, 0x53, 0x4c, 0x2f, 0x30, 0x2e, 0x39, 0x2e, 0x37, 0x6c, 0x20, 0x7a,
0x6c, 0x69, 0x62, 0x2f, 0x31, 0x2e, 0x32, 0x2e, 0x33, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x0f, 0x77,
0x77, 0x77, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x0f, 0x61,
0x63, 0x63, 0x65, 0x70, 0x74, 0x2d, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x06, 0x65,
0x6e, 0x2c, 0x20, 0x6d, 0x69, 0x00, 0x00,
];
#[test]
fn chunked_read() {
drop(Message::read_http(&mut BufReader::new(CHUNKED_HTTP)).unwrap());
}
#[test]
fn chunked_read_known() {
drop(Message::read_bhttp(&mut BufReader::new(CHUNKED_KNOWN)).unwrap());
}
#[test]
fn chunked_read_indefinite() {
drop(Message::read_bhttp(&mut BufReader::new(CHUNKED_INDEFINITE)).unwrap());
}
#[test]
fn chunked_to_known() {
let m = Message::read_http(&mut BufReader::new(CHUNKED_HTTP)).unwrap();
assert!(m.header().get(TRANSFER_ENCODING).is_none());
let mut buf = Vec::new();
m.write_bhttp(Mode::KnownLength, &mut buf).unwrap();
println!("result: {}", hex::encode(&buf));
assert_eq!(&buf[..], CHUNKED_KNOWN);
}
#[test]
fn chunked_to_indefinite() {
let m = Message::read_http(&mut BufReader::new(CHUNKED_HTTP)).unwrap();
assert!(m.header().get(TRANSFER_ENCODING).is_none());
let mut buf = Vec::new();
m.write_bhttp(Mode::IndefiniteLength, &mut buf).unwrap();
println!("result: {}", hex::encode(&buf));
assert_eq!(&buf[..], CHUNKED_INDEFINITE);
}
#[test]
fn convert_request() {
let m = Message::read_http(&mut BufReader::new(REQUEST)).unwrap();
let mut buf = Vec::new();
m.write_bhttp(Mode::KnownLength, &mut buf).unwrap();
println!("result: {}", hex::encode(&buf));
assert_eq!(&buf[..], REQUEST_KNOWN);
}
#[test]
fn padded_to_http() {
let mut padded = Vec::from(REQUEST_KNOWN);
padded.resize(padded.len() + 100, 0);
let m = Message::read_bhttp(&mut BufReader::new(&padded[..])).unwrap();
let mut buf = Vec::new();
m.write_http(&mut buf).unwrap();
assert_eq!(&buf[..], REQUEST);
}
#[test]
fn truncated_to_http() {
let mut padded = Vec::from(REQUEST_KNOWN);
assert_eq!(2, padded.iter().rev().take_while(|&x| *x == 0).count());
padded.truncate(padded.len() - 2);
let m = Message::read_bhttp(&mut BufReader::new(&padded[..])).unwrap();
let mut buf = Vec::new();
m.write_http(&mut buf).unwrap();
assert_eq!(&buf[..], REQUEST);
}
#[test]
fn tiny_request() {
const REQUEST: &[u8] = &[
0x00, 0x03, 0x47, 0x45, 0x54, 0x05, 0x68, 0x74, 0x74, 0x70, 0x73, 0x0b, 0x65, 0x78, 0x61,
0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x01, 0x2f,
];
let m = Message::read_bhttp(&mut BufReader::new(REQUEST)).unwrap();
assert_eq!(m.control().method().unwrap(), b"GET");
assert_eq!(m.control().scheme().unwrap(), b"https");
assert_eq!(m.control().authority().unwrap(), b"example.com");
assert_eq!(m.control().path().unwrap(), b"/");
assert!(m.control().status().is_none());
assert!(m.header().is_empty());
assert!(m.content().is_empty());
assert!(m.trailer().is_empty());
}
#[test]
fn tiny_response() {
const RESPONSE: &[u8] = &[0x01, 0x40, 0xc8];
let m = Message::read_bhttp(&mut BufReader::new(RESPONSE)).unwrap();
assert!(m.informational().is_empty());
assert_eq!(m.control().status().unwrap(), 200);
assert!(m.control().method().is_none());
assert!(m.control().scheme().is_none());
assert!(m.control().authority().is_none());
assert!(m.control().path().is_none());
assert!(m.header().is_empty());
assert!(m.content().is_empty());
assert!(m.trailer().is_empty());
}
#[test]
fn connect_request() {
const REQUEST: &[u8] = b"CONNECT test.example HTTP/1.1\r\n\
Host: example.com\r\n\
\r\n";
let err = Message::read_http(&mut BufReader::new(REQUEST)).unwrap_err();
assert!(matches!(err, Error::ConnectUnsupported));
}
/// Verify that Connection and Proxy-Connection are stripped out properly.
#[test]
fn connection_header() {
const REQUEST: &[u8] = b"POST test.example HTTP/1.1\r\n\
Host: example.com\r\n\
other: test\r\n\
Connection: sample\r\n\
Connection: other, garbage\r\n\
sample: test2\r\n\
px: test3\r\n\
proXy-connection: px\r\n\
\r\n";
let m = Message::read_http(&mut BufReader::new(REQUEST)).unwrap();
assert!(m.header().get(b"other").is_none());
assert!(m.header().get(b"sample").is_none());
assert!(m.header().get(b"garbage").is_none());
assert!(m.header().get(b"connection").is_none());
assert!(m.header().get(b"proxy-connection").is_none());
assert!(m.header().get(b"px").is_none());
}
/// Verify that hop-by-hop headers (other than transfer-encoding) are stripped out properly.
#[test]
fn hop_by_hop() {
const REQUEST: &[u8] = b"POST test.example HTTP/1.1\r\n\
Host: example.com\r\n\
keep-alive: 1\r\n\
te: trailers\r\n\
trailer: te\r\n\
upgrade: h2c\r\n\
\r\n";
let m = Message::read_http(&mut BufReader::new(REQUEST)).unwrap();
assert!(m.header().get(b"keep-alive").is_none());
assert!(m.header().get(b"te").is_none());
assert!(m.header().get(b"trailer").is_none());
assert!(m.header().get(b"transfer-encoding").is_none());
assert!(m.header().get(b"upgrade").is_none());
}
/// Verify that very bad chunked encoding produces a result.
#[test]
fn bad_chunked() {
const REQUEST: &[u8] = b"POST test.example HTTP/1.1\r\n\
Transfer-Encoding: chunked\r\n\
\r\n";
let e = Message::read_http(&mut BufReader::new(REQUEST)).unwrap_err();
assert!(matches!(e, Error::Truncated));
}

Просмотреть файл

@ -82,6 +82,7 @@ uniffi-example-sprites = { git = "https://github.com/mozilla/uniffi-rs.git", rev
uniffi-example-todolist = { git = "https://github.com/mozilla/uniffi-rs.git", rev = "bb2039f077a29dba0879372a67e764e6ace8e33f", optional = true }
uniffi-example-custom-types = { path = "../../../components/uniffi-example-custom-types/", optional = true }
uniffi-fixture-external-types = { path = "../../../components/uniffi-fixture-external-types/", optional = true }
binary_http = { path = "../../../../netwerk/protocol/http/binary_http" }
oblivious_http = { path = "../../../../netwerk/protocol/http/oblivious_http" }
# Note: `modern_sqlite` means rusqlite's bindings file be for a sqlite with

Просмотреть файл

@ -109,6 +109,7 @@ extern crate dap_ffi;
extern crate data_encoding_ffi;
extern crate binary_http;
extern crate oblivious_http;
#[cfg(feature = "uniffi_fixtures")]