diff --git a/Cargo.lock b/Cargo.lock index c38b77d0..99bcb76f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -316,9 +316,9 @@ checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" [[package]] name = "hoot" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cb7f5853c196b50c6ae5077a928111a7095123b642ad44e40c4cacc952a9b1c" +checksum = "6a018c1f2075066355e95ac5ab7909d35a84c9d14d1fae84c4bacdf6dec188d8" dependencies = [ "http", "httparse", diff --git a/Cargo.toml b/Cargo.toml index 655dc82d..7a07969c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,7 +37,7 @@ _test = [] [dependencies] base64 = "0.22.1" -hoot = "0.2.4" +hoot = "0.2.5" # hoot = { path = "../hoot/hoot" } http = "1.1.0" log = "0.4.22" diff --git a/src/config.rs b/src/config.rs index e5da1c08..fa8aff05 100644 --- a/src/config.rs +++ b/src/config.rs @@ -106,6 +106,10 @@ pub struct Config { pub(crate) max_idle_connections_per_host: usize, pub(crate) max_idle_age: Duration, pub(crate) middleware: MiddlewareChain, + + // Techically not config, but here to pass as argument from + // RequestBuilder::force_send_body() to run() + pub(crate) force_send_body: bool, } impl Config { @@ -508,6 +512,7 @@ impl Default for Config { max_idle_connections_per_host: 3, max_idle_age: Duration::from_secs(15), middleware: MiddlewareChain::default(), + force_send_body: false, } } } diff --git a/src/request.rs b/src/request.rs index 6efa1df9..d2e8f884 100644 --- a/src/request.rs +++ b/src/request.rs @@ -227,6 +227,38 @@ impl RequestBuilder { let request = self.builder.body(())?; do_call(self.agent, request, self.query_extra, SendBody::none()) } + + /// Force sending a body. + /// + /// This is an escape hatch to interact with broken services. + /// + /// According to the spec, methods such as GET, DELETE and TRACE should + /// not have a body. Despite that there are broken API services and + /// servers that use it. + /// + /// Example using DELETE while sending a body. + /// + /// ``` + /// let res = ureq::delete("http://httpbin.org/delete") + /// // this "unlocks" send() below + /// .force_send_body() + /// .send("DELETE with body is not correct")?; + /// # Ok::<_, ureq::Error>(()) + /// ``` + pub fn force_send_body(mut self) -> RequestBuilder { + // This is how we communicate to run() that we want to disable + // the method-body-compliance check. + let config = self.request_level_config(); + config.force_send_body = true; + + RequestBuilder { + agent: self.agent, + builder: self.builder, + query_extra: self.query_extra, + dummy_config: None, + _ph: PhantomData, + } + } } impl RequestBuilder { diff --git a/src/run.rs b/src/run.rs index 64560ef9..2d313f9a 100644 --- a/src/run.rs +++ b/src/run.rs @@ -44,6 +44,10 @@ pub(crate) fn run( let mut flow = Flow::new(request)?; + if config.force_send_body { + flow.send_body_despite_method(); + } + let (response, handler) = loop { let timeout = timings.next_timeout(Timeout::Global); let timed_out = match timeout.after { diff --git a/src/transport/test.rs b/src/transport/test.rs index b20ec17e..2ad48e44 100644 --- a/src/transport/test.rs +++ b/src/transport/test.rs @@ -239,6 +239,13 @@ fn setup_default_handlers(handlers: &mut Vec) { handlers, ); + maybe_add( + TestHandler::new("/delete", |_uri, _req, w| { + write!(w, "HTTP/1.1 200 OK\r\n\r\ndeleted\n") + }), + handlers, + ); + maybe_add( TestHandler::new("/robots.txt", |_uri, _req, w| { write!(