tor-dirclient: Make the body type of a `Requestable` type configurable.

Previously, the `Requestable` trait assumed the body of the request
would always be empty (`http::Request<()>`). This change replaces the
hardcoded `()` body type with the `Requestable::Body` associated type
(which will allow implementors to create requests with non-empty
bodies). This will enable us to reuse the `Requestable` trait for
building `POST` requests for uploading descriptors.
This commit is contained in:
Gabriela Moldovan 2023-08-14 00:43:51 +01:00
parent 0fde1d09f5
commit 8ce948bef9
No known key found for this signature in database
GPG Key ID: 3946E0ADE72BAC99
4 changed files with 49 additions and 4 deletions

View File

@ -1 +1,3 @@
BREAKING: `download()` is renamed to `send_request()`
BREAKING: `Requestable` now has an associated `Body` type
ADDED: `StringBody` trait

View File

@ -24,11 +24,40 @@ use itertools::Itertools;
use crate::err::RequestError;
/// The body of an [`http::Request`] made by a `Requestable` implementation.
pub trait StringBody {
/// The string representation of the request body.
///
/// Bodies consisting of binary data are not supported.
fn str(&self) -> &str;
}
impl StringBody for () {
fn str(&self) -> &str {
""
}
}
impl StringBody for String {
fn str(&self) -> &str {
self.as_ref()
}
}
impl StringBody for &str {
fn str(&self) -> &str {
self
}
}
/// A request for an object that can be served over the Tor directory system.
pub trait Requestable {
/// The body type of the [`http::Request`].
type Body: StringBody;
/// Build an [`http::Request`] from this Requestable, if
/// it is well-formed.
fn make_request(&self) -> Result<http::Request<()>>;
fn make_request(&self) -> Result<http::Request<Self::Body>>;
/// Return true if partial downloads are potentially useful. This
/// is true for request types where we're going to be downloading
@ -190,6 +219,8 @@ impl Default for ConsensusRequest {
}
impl Requestable for ConsensusRequest {
type Body = ();
fn make_request(&self) -> Result<http::Request<()>> {
// Build the URL.
let mut uri = "/tor/status-vote/current/consensus".to_string();
@ -273,6 +304,8 @@ impl AuthCertRequest {
}
impl Requestable for AuthCertRequest {
type Body = ();
fn make_request(&self) -> Result<http::Request<()>> {
if self.ids.is_empty() {
return Err(RequestError::EmptyRequest);
@ -343,6 +376,8 @@ impl MicrodescRequest {
}
impl Requestable for MicrodescRequest {
type Body = ();
fn make_request(&self) -> Result<http::Request<()>> {
let d_encode_b64 = |d: &[u8; 32]| Base64Unpadded::encode_string(&d[..]);
let ids = digest_list_stringify(&self.digests, d_encode_b64, "-")
@ -418,6 +453,8 @@ impl RouterDescRequest {
#[cfg(feature = "routerdesc")]
impl Requestable for RouterDescRequest {
type Body = ();
fn make_request(&self) -> Result<http::Request<()>> {
let mut uri = "/tor/server/".to_string();
@ -484,6 +521,8 @@ impl RoutersOwnDescRequest {
#[cfg(feature = "routerdesc")]
impl Requestable for RoutersOwnDescRequest {
type Body = ();
fn make_request(&self) -> Result<http::Request<()>> {
let uri = "/tor/server/authority.z";
let req = http::Request::builder().method("GET").uri(uri);
@ -530,6 +569,8 @@ impl HsDescDownloadRequest {
#[cfg(feature = "hs-client")]
impl Requestable for HsDescDownloadRequest {
type Body = ();
fn make_request(&self) -> Result<http::Request<()>> {
let hsid = Base64Unpadded::encode_string(self.hsid.as_ref());
// We hardcode version 3 here; if we ever have a v4 onion service

View File

@ -2,8 +2,10 @@
use std::fmt::Write;
use crate::request::StringBody;
/// Encode an HTTP request in a quick and dirty HTTP 1.0 format.
pub(crate) fn encode_request(req: &http::Request<()>) -> String {
pub(crate) fn encode_request<B: StringBody>(req: &http::Request<B>) -> String {
let mut s = format!("{} {} HTTP/1.0\r\n", req.method(), req.uri());
for (key, val) in req.headers().iter() {
@ -16,8 +18,8 @@ pub(crate) fn encode_request(req: &http::Request<()>) -> String {
)
.unwrap();
}
// TODO HSS encode the body
s.push_str("\r\n");
s.push_str(req.body().str());
s
}

View File

@ -84,7 +84,7 @@ pub(crate) enum ClientRequest {
impl ClientRequest {
/// Turn a ClientRequest into a Requestable.
pub(crate) fn as_requestable(&self) -> &(dyn request::Requestable + Send + Sync) {
pub(crate) fn as_requestable(&self) -> &(dyn request::Requestable<Body = ()> + Send + Sync) {
use ClientRequest::*;
match self {
Consensus(a) => a,