Coverage Report

Created: 2026-01-25 06:45

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/ureq-proto-0.5.3/src/client/sendreq.rs
Line
Count
Source
1
use std::io::Write;
2
3
use base64::prelude::BASE64_STANDARD;
4
use base64::Engine;
5
use http::uri::Scheme;
6
use http::{header, HeaderMap, HeaderName, HeaderValue, Method, Uri, Version};
7
8
use crate::client::amended::AmendedRequest;
9
use crate::ext::{AuthorityExt, MethodExt, SchemeExt};
10
use crate::util::Writer;
11
use crate::Error;
12
13
use super::state::SendRequest;
14
use super::{BodyState, Call, RequestPhase, SendRequestResult};
15
16
impl Call<SendRequest> {
17
    /// Write the request to the buffer.
18
    ///
19
    /// Writes incrementally, it can be called repeatedly in situations where the output
20
    /// buffer is small.
21
    ///
22
    /// This includes the first row, i.e. `GET / HTTP/1.1` and all headers.
23
    /// The output buffer needs to be large enough for the longest row.
24
    ///
25
    /// Example:
26
    ///
27
    /// ```text
28
    /// POST /bar HTTP/1.1\r\n
29
    /// Host: my.server.test\r\n
30
    /// User-Agent: myspecialthing\r\n
31
    /// \r\n
32
    /// <body data>
33
    /// ```
34
    ///
35
    /// The buffer would need to be at least 28 bytes big, since the `User-Agent` row is
36
    /// 28 bytes long.
37
    ///
38
    /// If the output is too small for the longest line, the result is an `OutputOverflow` error.
39
    ///
40
    /// The `Ok(usize)` is the number of bytes of the `output` buffer that was used.
41
0
    pub fn write(&mut self, output: &mut [u8]) -> Result<usize, Error> {
42
0
        self.maybe_analyze_request()?;
43
44
0
        let mut w = Writer::new(output);
45
0
        try_write_prelude(&self.inner.request, &mut self.inner.state, &mut w)?;
46
47
0
        let output_used = w.len();
48
49
0
        Ok(output_used)
50
0
    }
51
52
    /// The configured method.
53
0
    pub fn method(&self) -> &Method {
54
0
        self.inner.request.method()
55
0
    }
56
57
    /// The uri being requested.
58
0
    pub fn uri(&self) -> &Uri {
59
0
        self.inner.request.uri()
60
0
    }
61
62
    /// Version of the request.
63
    ///
64
    /// This can only be 1.0 or 1.1.
65
0
    pub fn version(&self) -> Version {
66
0
        self.inner.request.version()
67
0
    }
68
69
    /// The configured headers.
70
0
    pub fn headers_map(&mut self) -> Result<HeaderMap, Error> {
71
0
        self.maybe_analyze_request()?;
72
0
        let mut map = HeaderMap::new();
73
0
        for (k, v) in self.inner.request.headers() {
74
0
            map.insert(k, v.clone());
75
0
        }
76
0
        Ok(map)
77
0
    }
78
79
    /// Check whether the entire request has been sent.
80
    ///
81
    /// This is useful when the output buffer is small and we need to repeatedly
82
    /// call `write()` to send the entire request.
83
0
    pub fn can_proceed(&self) -> bool {
84
0
        !self.inner.state.phase.is_prelude()
85
0
    }
86
87
    /// Attempt to proceed from this state to the next.
88
    ///
89
    /// Returns `None` if the entire request has not been sent. It is guaranteed that if
90
    /// `can_proceed()` returns `true`, this will return `Some`.
91
0
    pub fn proceed(mut self) -> Result<Option<SendRequestResult>, Error> {
92
0
        if !self.can_proceed() {
93
0
            return Ok(None);
94
0
        }
95
96
0
        if self.inner.state.writer.has_body() {
97
0
            if self.inner.await_100_continue {
98
0
                Ok(Some(SendRequestResult::Await100(Call::wrap(self.inner))))
99
            } else {
100
                // TODO(martin): is this needed?
101
0
                self.maybe_analyze_request()?;
102
0
                let call = Call::wrap(self.inner);
103
0
                Ok(Some(SendRequestResult::SendBody(call)))
104
            }
105
        } else {
106
0
            let call = Call::wrap(self.inner);
107
0
            Ok(Some(SendRequestResult::RecvResponse(call)))
108
        }
109
0
    }
110
111
0
    pub(crate) fn maybe_analyze_request(&mut self) -> Result<(), Error> {
112
0
        if self.inner.analyzed {
113
0
            return Ok(());
114
0
        }
115
116
0
        let info = self.inner.request.analyze(
117
0
            self.inner.state.writer,
118
0
            self.inner.state.allow_non_standard_methods,
119
0
        )?;
120
121
0
        let method = self.inner.request.method();
122
0
        let send_body = (method.allow_request_body() || self.inner.force_send_body)
123
0
            && info.body_mode.has_body();
124
125
0
        if !send_body && info.body_mode.has_body() {
126
0
            return Err(Error::BodyNotAllowed);
127
0
        }
128
129
0
        if !info.req_host_header && method != Method::CONNECT {
130
0
            if let Some(host) = self.inner.request.uri().host() {
131
                // User did not set a host header, and there is one in uri, we set that.
132
                // We need an owned value to set the host header.
133
134
                // This might append the port if it differs from the scheme default.
135
0
                let value = maybe_with_port(host, self.inner.request.uri())?;
136
137
0
                self.inner.request.set_header(header::HOST, value)?;
138
0
            }
139
0
        }
140
141
0
        if let Some(auth) = self.inner.request.uri().authority() {
142
0
            if self.inner.request.method() != Method::CONNECT {
143
0
                if auth.userinfo().is_some() && !info.req_auth_header {
144
0
                    let user = auth.username().unwrap_or_default();
145
0
                    let pass = auth.password().unwrap_or_default();
146
0
                    let creds = BASE64_STANDARD.encode(format!("{}:{}", user, pass));
147
0
                    let auth = format!("Basic {}", creds);
148
0
                    self.inner.request.set_header(header::AUTHORIZATION, auth)?;
149
0
                }
150
0
            } else if !info.req_host_header {
151
0
                self.inner
152
0
                    .request
153
0
                    .set_header(header::HOST, auth.clone().as_str())?;
154
0
            }
155
0
        }
156
157
0
        if !info.req_body_header && info.body_mode.has_body() {
158
            // User did not set a body header, we set one.
159
0
            let header = info.body_mode.body_header();
160
0
            self.inner.request.set_header(header.0, header.1)?;
161
0
        }
162
163
0
        self.inner.state.writer = info.body_mode;
164
165
0
        self.inner.analyzed = true;
166
0
        Ok(())
167
0
    }
168
}
169
170
0
fn maybe_with_port(host: &str, uri: &Uri) -> Result<HeaderValue, Error> {
171
0
    fn from_str(src: &str) -> Result<HeaderValue, Error> {
172
0
        HeaderValue::from_str(src).map_err(|e| Error::BadHeader(e.to_string()))
173
0
    }
174
175
0
    if let Some(port) = uri.port() {
176
0
        let scheme = uri.scheme().unwrap_or(&Scheme::HTTP);
177
0
        if let Some(scheme_default) = scheme.default_port() {
178
0
            if port != scheme_default {
179
                // This allocates, so we only do it if we absolutely have to.
180
0
                let host_port = format!("{}:{}", host, port);
181
0
                return from_str(&host_port);
182
0
            }
183
0
        }
184
0
    }
185
186
    // Fall back on no port (without allocating).
187
0
    from_str(host)
188
0
}
189
190
0
fn try_write_prelude(
191
0
    request: &AmendedRequest,
192
0
    state: &mut BodyState,
193
0
    w: &mut Writer,
194
0
) -> Result<(), Error> {
195
0
    let at_start = w.len();
196
197
    loop {
198
0
        if try_write_prelude_part(request, state, w) {
199
0
            continue;
200
0
        }
201
202
0
        let written = w.len() - at_start;
203
204
0
        if written > 0 || state.phase.is_body() {
205
0
            return Ok(());
206
        } else {
207
0
            return Err(Error::OutputOverflow);
208
        }
209
    }
210
0
}
211
212
0
fn try_write_prelude_part(request: &AmendedRequest, state: &mut BodyState, w: &mut Writer) -> bool {
213
0
    match &mut state.phase {
214
        RequestPhase::Line => {
215
0
            let success = do_write_send_line(request.prelude(), w);
216
0
            if success {
217
0
                state.phase = RequestPhase::Headers(0);
218
0
            }
219
0
            success
220
        }
221
222
0
        RequestPhase::Headers(index) => {
223
0
            let header_count = request.headers_len();
224
0
            let all = request.headers();
225
0
            let skipped = all.skip(*index);
226
227
0
            if header_count > 0 {
228
0
                do_write_headers(skipped, index, header_count - 1, w);
229
0
            }
230
231
0
            if *index == header_count {
232
0
                state.phase = RequestPhase::Body;
233
0
            }
234
0
            false
235
        }
236
237
        // We're past the header.
238
0
        _ => false,
239
    }
240
0
}
241
242
0
fn do_write_send_line(line: (&Method, &str, Version), w: &mut Writer) -> bool {
243
    // Ensure origin-form request-target starts with "/" when only a query is present
244
    // per RFC 9112 ยง3.2.1 (@https://datatracker.ietf.org/doc/html/rfc9112#section-3.2.1).
245
0
    let need_initial_slash = line.1.starts_with('?');
246
0
    let slash = if need_initial_slash { "/" } else { "" };
247
248
0
    w.try_write(|w| write!(w, "{} {}{} {:?}\r\n", line.0, slash, line.1, line.2))
249
0
}
250
251
0
fn do_write_headers<'a, I>(headers: I, index: &mut usize, last_index: usize, w: &mut Writer)
252
0
where
253
0
    I: Iterator<Item = (&'a HeaderName, &'a HeaderValue)>,
254
{
255
0
    for h in headers {
256
0
        let success = w.try_write(|w| {
257
0
            write!(w, "{}: ", h.0)?;
258
0
            w.write_all(h.1.as_bytes())?;
259
0
            write!(w, "\r\n")?;
260
0
            if *index == last_index {
261
0
                write!(w, "\r\n")?;
262
0
            }
263
0
            Ok(())
264
0
        });
265
266
0
        if success {
267
0
            *index += 1;
268
0
        } else {
269
0
            break;
270
        }
271
    }
272
0
}