From 8ce948bef970d3708365172d66394a5a68e1acac Mon Sep 17 00:00:00 2001 From: Gabriela Moldovan Date: Mon, 14 Aug 2023 00:43:51 +0100 Subject: [PATCH] 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. --- crates/tor-dirclient/semver.md | 2 ++ crates/tor-dirclient/src/request.rs | 43 ++++++++++++++++++++++++++++- crates/tor-dirclient/src/util.rs | 6 ++-- crates/tor-dirmgr/src/docid.rs | 2 +- 4 files changed, 49 insertions(+), 4 deletions(-) diff --git a/crates/tor-dirclient/semver.md b/crates/tor-dirclient/semver.md index 42d5de6b1..89a97031f 100644 --- a/crates/tor-dirclient/semver.md +++ b/crates/tor-dirclient/semver.md @@ -1 +1,3 @@ BREAKING: `download()` is renamed to `send_request()` +BREAKING: `Requestable` now has an associated `Body` type +ADDED: `StringBody` trait diff --git a/crates/tor-dirclient/src/request.rs b/crates/tor-dirclient/src/request.rs index d6be07525..75a886b48 100644 --- a/crates/tor-dirclient/src/request.rs +++ b/crates/tor-dirclient/src/request.rs @@ -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>; + fn make_request(&self) -> Result>; /// 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> { // 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> { 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> { 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> { 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> { 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> { let hsid = Base64Unpadded::encode_string(self.hsid.as_ref()); // We hardcode version 3 here; if we ever have a v4 onion service diff --git a/crates/tor-dirclient/src/util.rs b/crates/tor-dirclient/src/util.rs index ccb713ce6..bd69dc60c 100644 --- a/crates/tor-dirclient/src/util.rs +++ b/crates/tor-dirclient/src/util.rs @@ -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(req: &http::Request) -> 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 } diff --git a/crates/tor-dirmgr/src/docid.rs b/crates/tor-dirmgr/src/docid.rs index 9486a6a64..78f5622d3 100644 --- a/crates/tor-dirmgr/src/docid.rs +++ b/crates/tor-dirmgr/src/docid.rs @@ -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 + Send + Sync) { use ClientRequest::*; match self { Consensus(a) => a,