Coverage Report

Created: 2026-10-28 07:08

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/ureq-3.3.0/src/util.rs
Line
Count
Source
1
use std::convert::TryFrom;
2
use std::io::{self, ErrorKind};
3
use std::{fmt, iter};
4
5
use http::header::{ACCEPT, ACCEPT_CHARSET, ACCEPT_ENCODING};
6
use http::header::{CONNECTION, CONTENT_ENCODING, CONTENT_LENGTH, CONTENT_TYPE};
7
use http::header::{DATE, HOST, LOCATION, SERVER, TRANSFER_ENCODING, USER_AGENT};
8
use http::uri::{Authority, Scheme};
9
use http::{HeaderMap, HeaderName, HeaderValue, Method, Response, Uri, Version};
10
11
use crate::Error;
12
use crate::http;
13
use crate::proxy::ProxyProtocol;
14
15
pub(crate) mod private {
16
    pub trait Private {}
17
}
18
19
pub(crate) trait AuthorityExt {
20
    fn userinfo(&self) -> Option<&str>;
21
    fn username(&self) -> Option<&str>;
22
    fn password(&self) -> Option<&str>;
23
}
24
25
// NB: Treating &str with direct indexes is OK, since Uri parsed the Authority,
26
// and ensured it's all ASCII (or %-encoded).
27
impl AuthorityExt for Authority {
28
0
    fn userinfo(&self) -> Option<&str> {
29
0
        let s = self.as_str();
30
0
        s.rfind('@').map(|i| &s[..i])
31
0
    }
32
33
0
    fn username(&self) -> Option<&str> {
34
0
        self.userinfo()
35
0
            .map(|a| a.rfind(':').map(|i| &a[..i]).unwrap_or(a))
36
0
    }
37
38
0
    fn password(&self) -> Option<&str> {
39
0
        self.userinfo()
40
0
            .and_then(|a| a.rfind(':').map(|i| &a[i + 1..]))
41
0
    }
42
}
43
44
pub(crate) trait SchemeExt {
45
    fn default_port(&self) -> Option<u16>;
46
}
47
48
impl SchemeExt for Scheme {
49
0
    fn default_port(&self) -> Option<u16> {
50
0
        if *self == Scheme::HTTPS {
51
0
            Some(443)
52
0
        } else if *self == Scheme::HTTP {
53
0
            Some(80)
54
0
        } else if let Ok(proxy) = ProxyProtocol::try_from(self.as_str()) {
55
0
            Some(proxy.default_port())
56
        } else {
57
0
            debug!("Unknown scheme: {}", self);
58
0
            None
59
        }
60
0
    }
61
}
62
63
/// Windows causes kind `TimedOut` while unix does `WouldBlock`. Since we are not
64
/// using non-blocking streams, we normalize `WouldBlock` -> `TimedOut`.
65
pub(crate) trait IoResultExt {
66
    fn normalize_would_block(self) -> Self;
67
}
68
69
impl<T> IoResultExt for io::Result<T> {
70
0
    fn normalize_would_block(self) -> Self {
71
0
        match self {
72
0
            Ok(v) => Ok(v),
73
0
            Err(e) if e.kind() == ErrorKind::WouldBlock => {
74
0
                Err(io::Error::new(ErrorKind::TimedOut, e))
75
            }
76
0
            Err(e) => Err(e),
77
        }
78
0
    }
Unexecuted instantiation: <core::result::Result<std::net::tcp::TcpStream, core::io::error::Error> as ureq::util::IoResultExt>::normalize_would_block
Unexecuted instantiation: <core::result::Result<usize, core::io::error::Error> as ureq::util::IoResultExt>::normalize_would_block
Unexecuted instantiation: <core::result::Result<(), core::io::error::Error> as ureq::util::IoResultExt>::normalize_would_block
79
}
80
81
#[derive(Debug)]
82
pub(crate) struct ConsumeBuf {
83
    buf: Vec<u8>,
84
    filled: usize,
85
    consumed: usize,
86
}
87
88
impl ConsumeBuf {
89
0
    pub fn new(size: usize) -> Self {
90
0
        ConsumeBuf {
91
0
            buf: vec![0; size],
92
0
            filled: 0,
93
0
            consumed: 0,
94
0
        }
95
0
    }
96
97
0
    pub fn resize(&mut self, size: usize) {
98
0
        if size > 100 * 1024 * 1024 {
99
0
            panic!("ConsumeBuf grown to unreasonable size (>100MB)");
100
0
        }
101
0
        self.buf.resize(size, 0);
102
0
    }
103
104
0
    pub fn add_space(&mut self, size: usize) {
105
0
        if size == 0 {
106
0
            return;
107
0
        }
108
0
        let wanted = self.buf.len() + size;
109
0
        self.resize(wanted);
110
0
    }
111
112
0
    pub fn free_mut(&mut self) -> &mut [u8] {
113
0
        self.maybe_shift();
114
0
        &mut self.buf[self.filled..]
115
0
    }
116
117
0
    pub fn add_filled(&mut self, amount: usize) {
118
0
        self.filled += amount;
119
0
        assert!(self.filled <= self.buf.len());
120
0
    }
121
122
0
    pub fn unconsumed(&self) -> &[u8] {
123
0
        &self.buf[self.consumed..self.filled]
124
0
    }
125
126
0
    pub fn unconsumed_mut(&mut self) -> &mut [u8] {
127
0
        &mut self.buf[self.consumed..self.filled]
128
0
    }
129
130
0
    pub fn consume(&mut self, amount: usize) {
131
0
        self.consumed += amount;
132
0
        assert!(self.consumed <= self.filled);
133
0
    }
134
135
0
    fn maybe_shift(&mut self) {
136
0
        if self.consumed == 0 {
137
0
            return;
138
0
        }
139
140
0
        if self.consumed == self.filled {
141
0
            self.consumed = 0;
142
0
            self.filled = 0;
143
0
        } else if self.filled > self.buf.len() / 2 {
144
0
            self.buf.copy_within(self.consumed..self.filled, 0);
145
0
            self.filled -= self.consumed;
146
0
            self.consumed = 0;
147
0
        }
148
0
    }
149
}
150
151
/// Wrapper to only log non-sensitive data.
152
pub(crate) struct DebugRequest<'a> {
153
    pub method: &'a Method,
154
    pub uri: &'a Uri,
155
    pub version: Version,
156
    pub headers: HeaderMap<HeaderValue>,
157
}
158
159
impl<'a> fmt::Debug for DebugRequest<'a> {
160
0
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
161
0
        f.debug_struct("Request")
162
0
            .field("method", &self.method)
163
0
            .field("uri", &DebugUri(self.uri))
164
0
            .field("version", &self.version)
165
0
            .field("headers", &DebugHeaders(&self.headers))
166
0
            .finish()
167
0
    }
168
}
169
170
/// Wrapper to only log non-sensitive data.
171
pub(crate) struct DebugResponse<'a, B>(pub &'a Response<B>);
172
173
impl<'a, B> fmt::Debug for DebugResponse<'a, B> {
174
0
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
175
0
        f.debug_struct("Response")
176
0
            .field("status", &self.0.status())
177
0
            .field("version", &self.0.version())
178
0
            .field("headers", &DebugHeaders(self.0.headers()))
179
0
            .finish()
180
0
    }
181
}
182
183
pub(crate) struct DebugHeaders<'a>(pub &'a HeaderMap);
184
185
const NON_SENSITIVE_HEADERS: &[HeaderName] = &[
186
    DATE,
187
    CONTENT_TYPE,
188
    CONTENT_LENGTH,
189
    TRANSFER_ENCODING,
190
    CONNECTION,
191
    CONTENT_ENCODING,
192
    HOST,
193
    ACCEPT,
194
    ACCEPT_ENCODING,
195
    ACCEPT_CHARSET,
196
    SERVER,
197
    USER_AGENT,
198
    // LOCATION is also logged in a redacted form
199
];
200
201
impl<'a> fmt::Debug for DebugHeaders<'a> {
202
0
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
203
0
        let mut debug = f.debug_map();
204
205
        static REDACTED_LOCATION: HeaderValue = HeaderValue::from_static("******");
206
0
        let has_location = self.0.has_location();
207
208
0
        let filtered_headers = self
209
0
            .0
210
0
            .iter()
211
0
            .filter(|(name, _)| NON_SENSITIVE_HEADERS.contains(name));
212
213
0
        if has_location {
214
0
            let location_header = if log_enabled!(log::Level::Trace) {
215
0
                iter::once((&LOCATION, self.0.get(LOCATION).unwrap()))
216
            } else {
217
0
                iter::once((&LOCATION, &REDACTED_LOCATION))
218
            };
219
0
            debug.entries(filtered_headers.chain(location_header));
220
0
        } else {
221
0
            debug.entries(filtered_headers);
222
0
        }
223
224
0
        let redact_count = self
225
0
            .0
226
0
            .iter()
227
0
            .filter(|(name, _)| {
228
                // println!("{}", name);
229
0
                !NON_SENSITIVE_HEADERS.contains(name)
230
0
            })
231
0
            .count()
232
            // location is logged, but redacted, so do not include in the count
233
0
            - if has_location { 1 } else { 0 };
234
235
0
        if redact_count > 0 {
236
0
            debug.entry(
237
0
                &"<NOTICE>",
238
0
                &format!("{} HEADERS ARE REDACTED", redact_count),
239
0
            );
240
0
        }
241
242
0
        debug.finish()
243
0
    }
244
}
245
246
/// Wrapper to only log non-sensitive data.
247
pub(crate) struct DebugUri<'a>(pub &'a Uri);
248
249
impl<'a> fmt::Debug for DebugUri<'a> {
250
0
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
251
0
        if let Some(s) = self.0.scheme_str() {
252
0
            write!(f, "{}://", s)?;
253
0
        }
254
255
0
        if let Some(a) = self.0.authority() {
256
0
            write!(f, "{:?}", DebugAuthority(a))?;
257
0
        }
258
259
0
        if let Some(q) = self.0.path_and_query() {
260
0
            if log_enabled!(log::Level::Trace) {
261
0
                write!(f, "{}", q)?;
262
            } else {
263
0
                write!(f, "/******")?;
264
            }
265
0
        }
266
267
0
        Ok(())
268
0
    }
269
}
270
271
pub(crate) struct DebugAuthority<'a>(pub &'a Authority);
272
273
impl<'a> fmt::Debug for DebugAuthority<'a> {
274
0
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
275
0
        let mut at = false;
276
277
0
        if let Some(u) = self.0.username() {
278
0
            at = true;
279
0
            if let Some(x) = u.chars().next() {
280
0
                write!(f, "{}*****", x)?;
281
0
            }
282
0
        }
283
284
0
        if self.0.password().is_some() {
285
0
            at = true;
286
0
            write!(f, ":******")?;
287
0
        }
288
289
0
        if at {
290
0
            write!(f, "@")?;
291
0
        }
292
293
0
        write!(f, "{}", self.0.host())?;
294
295
0
        if let Some(p) = self.0.port_u16() {
296
0
            write!(f, ":{}", p)?;
297
0
        }
298
299
0
        Ok(())
300
0
    }
301
}
302
303
pub(crate) trait UriExt {
304
    fn ensure_valid_url(&self) -> Result<(), Error>;
305
306
    #[cfg(feature = "_url")]
307
    fn try_into_url(&self) -> Result<url::Url, Error>;
308
309
    #[allow(unused)]
310
    fn host_port(&self) -> (&str, u16);
311
}
312
313
impl UriExt for Uri {
314
0
    fn ensure_valid_url(&self) -> Result<(), Error> {
315
0
        let scheme = self
316
0
            .scheme()
317
0
            .ok_or_else(|| Error::BadUri(format!("{} is missing scheme", self)))?;
318
319
0
        scheme
320
0
            .default_port()
321
0
            .ok_or_else(|| Error::BadUri(format!("unknown scheme: {}", scheme)))?;
322
323
0
        self.authority()
324
0
            .ok_or_else(|| Error::BadUri(format!("{} is missing host", self)))?;
325
326
0
        Ok(())
327
0
    }
328
329
    #[cfg(feature = "_url")]
330
    fn try_into_url(&self) -> Result<url::Url, Error> {
331
        self.ensure_valid_url()?;
332
        let uri = self.to_string();
333
334
        // If ensure_full_url() works, we expect to be able to parse it to a url
335
        let url = url::Url::parse(&uri).expect("parsed url");
336
337
        Ok(url)
338
    }
339
340
0
    fn host_port(&self) -> (&str, u16) {
341
0
        (
342
0
            self.host().unwrap(),
343
0
            self.port_u16()
344
0
                .unwrap_or(self.scheme().unwrap().default_port().unwrap_or(80)),
345
0
        )
346
0
    }
347
}
348
349
pub(crate) trait HeaderMapExt {
350
    fn get_str(&self, k: HeaderName) -> Option<&str>;
351
    fn is_chunked(&self) -> bool;
352
    fn content_length(&self) -> Option<u64>;
353
    fn has_accept_encoding(&self) -> bool;
354
    fn has_user_agent(&self) -> bool;
355
0
    fn has_send_body_mode(&self) -> bool {
356
0
        self.is_chunked() || self.content_length().is_some()
357
0
    }
358
    fn has_accept(&self) -> bool;
359
    fn has_content_type(&self) -> bool;
360
    fn has_location(&self) -> bool;
361
}
362
363
impl HeaderMapExt for HeaderMap {
364
0
    fn get_str(&self, k: HeaderName) -> Option<&str> {
365
0
        self.get(k).and_then(|v| v.to_str().ok())
366
0
    }
367
368
0
    fn is_chunked(&self) -> bool {
369
0
        self.get_str(TRANSFER_ENCODING)
370
0
            .map(|v| v.contains("chunked"))
371
0
            .unwrap_or(false)
372
0
    }
373
374
0
    fn content_length(&self) -> Option<u64> {
375
0
        let h = self.get_str(CONTENT_LENGTH)?;
376
0
        let len: u64 = h.parse().ok()?;
377
0
        Some(len)
378
0
    }
379
380
0
    fn has_accept_encoding(&self) -> bool {
381
0
        self.contains_key(ACCEPT_ENCODING)
382
0
    }
383
384
0
    fn has_user_agent(&self) -> bool {
385
0
        self.contains_key(USER_AGENT)
386
0
    }
387
388
0
    fn has_accept(&self) -> bool {
389
0
        self.contains_key(ACCEPT)
390
0
    }
391
392
0
    fn has_content_type(&self) -> bool {
393
0
        self.contains_key(CONTENT_TYPE)
394
0
    }
395
396
0
    fn has_location(&self) -> bool {
397
0
        self.contains_key(LOCATION)
398
0
    }
399
}