Coverage Report

Created: 2026-11-12 06:36

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/rust-url/form_urlencoded/src/lib.rs
Line
Count
Source
1
// Copyright 2013-2016 The rust-url developers.
2
//
3
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6
// option. This file may not be copied, modified, or distributed
7
// except according to those terms.
8
9
//! Parser and serializer for the [`application/x-www-form-urlencoded` syntax](
10
//! http://url.spec.whatwg.org/#application/x-www-form-urlencoded),
11
//! as used by HTML forms.
12
//!
13
//! Converts between a string (such as an URL’s query string)
14
//! and a sequence of (name, value) pairs.
15
#![no_std]
16
17
// For forwards compatibility
18
#[cfg(feature = "std")]
19
extern crate std as _;
20
21
extern crate alloc;
22
23
#[cfg(not(feature = "alloc"))]
24
compile_error!("the `alloc` feature must currently be enabled");
25
26
use alloc::borrow::{Borrow, Cow, ToOwned};
27
use alloc::string::String;
28
use core::str;
29
use percent_encoding::{percent_decode, percent_encode_byte};
30
31
/// Convert a byte string in the `application/x-www-form-urlencoded` syntax
32
/// into a iterator of (name, value) pairs.
33
///
34
/// Use `parse(input.as_bytes())` to parse a `&str` string.
35
///
36
/// The names and values are percent-decoded. For instance, `%23first=%25try%25` will be
37
/// converted to `[("#first", "%try%")]`.
38
#[inline]
39
0
pub fn parse(input: &[u8]) -> Parse<'_> {
40
0
    Parse { input }
41
0
}
42
/// The return type of `parse()`.
43
#[derive(Copy, Clone)]
44
pub struct Parse<'a> {
45
    input: &'a [u8],
46
}
47
48
impl<'a> Iterator for Parse<'a> {
49
    type Item = (Cow<'a, str>, Cow<'a, str>);
50
51
0
    fn next(&mut self) -> Option<Self::Item> {
52
        loop {
53
0
            if self.input.is_empty() {
54
0
                return None;
55
0
            }
56
0
            let mut split2 = self.input.splitn(2, |&b| b == b'&');
57
0
            let sequence = split2.next().unwrap();
58
0
            self.input = split2.next().unwrap_or(&[][..]);
59
0
            if sequence.is_empty() {
60
0
                continue;
61
0
            }
62
0
            let mut split2 = sequence.splitn(2, |&b| b == b'=');
63
0
            let name = split2.next().unwrap();
64
0
            let value = split2.next().unwrap_or(&[][..]);
65
0
            return Some((decode(name), decode(value)));
66
        }
67
0
    }
68
}
69
70
0
fn decode(input: &[u8]) -> Cow<'_, str> {
71
0
    let replaced = replace_plus(input);
72
0
    decode_utf8_lossy(match percent_decode(&replaced).into() {
73
0
        Cow::Owned(vec) => Cow::Owned(vec),
74
0
        Cow::Borrowed(_) => replaced,
75
    })
76
0
}
77
78
/// Replace b'+' with b' '
79
0
fn replace_plus(input: &[u8]) -> Cow<'_, [u8]> {
80
0
    match input.iter().position(|&b| b == b'+') {
81
0
        None => Cow::Borrowed(input),
82
0
        Some(first_position) => {
83
0
            let mut replaced = input.to_owned();
84
0
            replaced[first_position] = b' ';
85
0
            for byte in &mut replaced[first_position + 1..] {
86
0
                if *byte == b'+' {
87
0
                    *byte = b' ';
88
0
                }
89
            }
90
0
            Cow::Owned(replaced)
91
        }
92
    }
93
0
}
94
95
impl<'a> Parse<'a> {
96
    /// Return a new iterator that yields pairs of `String` instead of pairs of `Cow<str>`.
97
0
    pub fn into_owned(self) -> ParseIntoOwned<'a> {
98
0
        ParseIntoOwned { inner: self }
99
0
    }
100
}
101
102
/// Like `Parse`, but yields pairs of `String` instead of pairs of `Cow<str>`.
103
pub struct ParseIntoOwned<'a> {
104
    inner: Parse<'a>,
105
}
106
107
impl Iterator for ParseIntoOwned<'_> {
108
    type Item = (String, String);
109
110
0
    fn next(&mut self) -> Option<Self::Item> {
111
0
        self.inner
112
0
            .next()
113
0
            .map(|(k, v)| (k.into_owned(), v.into_owned()))
114
0
    }
115
}
116
117
/// The [`application/x-www-form-urlencoded` byte serializer](
118
/// https://url.spec.whatwg.org/#concept-urlencoded-byte-serializer).
119
///
120
/// Return an iterator of `&str` slices.
121
0
pub fn byte_serialize(input: &[u8]) -> ByteSerialize<'_> {
122
0
    ByteSerialize { bytes: input }
123
0
}
124
125
/// Return value of `byte_serialize()`.
126
#[derive(Debug)]
127
pub struct ByteSerialize<'a> {
128
    bytes: &'a [u8],
129
}
130
131
0
fn byte_serialized_unchanged(byte: u8) -> bool {
132
0
    matches!(byte, b'*' | b'-' | b'.' | b'0' ..= b'9' | b'A' ..= b'Z' | b'_' | b'a' ..= b'z')
133
0
}
134
135
impl<'a> Iterator for ByteSerialize<'a> {
136
    type Item = &'a str;
137
138
0
    fn next(&mut self) -> Option<&'a str> {
139
0
        if let Some((&first, tail)) = self.bytes.split_first() {
140
0
            if !byte_serialized_unchanged(first) {
141
0
                self.bytes = tail;
142
0
                return Some(if first == b' ' {
143
0
                    "+"
144
                } else {
145
0
                    percent_encode_byte(first)
146
                });
147
0
            }
148
0
            let position = tail.iter().position(|&b| !byte_serialized_unchanged(b));
149
0
            let (unchanged_slice, remaining) = match position {
150
                // 1 for first_byte + i unchanged in tail
151
0
                Some(i) => self.bytes.split_at(1 + i),
152
0
                None => (self.bytes, &[][..]),
153
            };
154
0
            self.bytes = remaining;
155
            // This unsafe is appropriate because we have already checked these
156
            // bytes in byte_serialized_unchanged, which checks for a subset
157
            // of UTF-8. So we know these bytes are valid UTF-8, and doing
158
            // another UTF-8 check would be wasteful.
159
0
            Some(unsafe { str::from_utf8_unchecked(unchanged_slice) })
160
        } else {
161
0
            None
162
        }
163
0
    }
164
165
0
    fn size_hint(&self) -> (usize, Option<usize>) {
166
0
        if self.bytes.is_empty() {
167
0
            (0, Some(0))
168
        } else {
169
0
            (1, Some(self.bytes.len()))
170
        }
171
0
    }
172
}
173
174
/// The [`application/x-www-form-urlencoded` serializer](
175
/// https://url.spec.whatwg.org/#concept-urlencoded-serializer).
176
pub struct Serializer<'a, T: Target> {
177
    target: Option<T>,
178
    start_position: usize,
179
    encoding: EncodingOverride<'a>,
180
}
181
182
pub trait Target {
183
    fn as_mut_string(&mut self) -> &mut String;
184
    fn finish(self) -> Self::Finished;
185
    type Finished;
186
}
187
188
impl Target for String {
189
0
    fn as_mut_string(&mut self) -> &mut String {
190
0
        self
191
0
    }
192
0
    fn finish(self) -> Self {
193
0
        self
194
0
    }
195
    type Finished = Self;
196
}
197
198
impl Target for &mut String {
199
0
    fn as_mut_string(&mut self) -> &mut String {
200
0
        self
201
0
    }
202
0
    fn finish(self) -> Self {
203
0
        self
204
0
    }
205
    type Finished = Self;
206
}
207
208
impl<'a, T: Target> Serializer<'a, T> {
209
    /// Create a new `application/x-www-form-urlencoded` serializer for the given target.
210
    ///
211
    /// If the target is non-empty,
212
    /// its content is assumed to already be in `application/x-www-form-urlencoded` syntax.
213
0
    pub fn new(target: T) -> Self {
214
0
        Self::for_suffix(target, 0)
215
0
    }
216
217
    /// Create a new `application/x-www-form-urlencoded` serializer
218
    /// for a suffix of the given target.
219
    ///
220
    /// If that suffix is non-empty,
221
    /// its content is assumed to already be in `application/x-www-form-urlencoded` syntax.
222
    /// If `start_position` exceeds the target's length, it is set to the target's length.
223
0
    pub fn for_suffix(mut target: T, start_position: usize) -> Self {
224
0
        let start_position = start_position.min(target.as_mut_string().len());
225
226
0
        Serializer {
227
0
            target: Some(target),
228
0
            start_position,
229
0
            encoding: None,
230
0
        }
231
0
    }
Unexecuted instantiation: <form_urlencoded::Serializer<url::UrlQuery>>::for_suffix
Unexecuted instantiation: <form_urlencoded::Serializer<_>>::for_suffix
232
233
    /// Remove any existing name/value pair.
234
    ///
235
    /// Panics if called after `.finish()`.
236
0
    pub fn clear(&mut self) -> &mut Self {
237
0
        string(&mut self.target).truncate(self.start_position);
238
0
        self
239
0
    }
240
241
    /// Set the character encoding to be used for names and values before percent-encoding.
242
0
    pub fn encoding_override(&mut self, new: EncodingOverride<'a>) -> &mut Self {
243
0
        self.encoding = new;
244
0
        self
245
0
    }
246
247
    /// Serialize and append a name/value pair.
248
    ///
249
    /// Panics if called after `.finish()`.
250
0
    pub fn append_pair(&mut self, name: &str, value: &str) -> &mut Self {
251
0
        append_pair(
252
0
            string(&mut self.target),
253
0
            self.start_position,
254
0
            self.encoding,
255
0
            name,
256
0
            value,
257
        );
258
0
        self
259
0
    }
260
261
    /// Serialize and append a name of parameter without any value.
262
    ///
263
    /// Panics if called after `.finish()`.
264
0
    pub fn append_key_only(&mut self, name: &str) -> &mut Self {
265
0
        append_key_only(
266
0
            string(&mut self.target),
267
0
            self.start_position,
268
0
            self.encoding,
269
0
            name,
270
        );
271
0
        self
272
0
    }
273
274
    /// Serialize and append a number of name/value pairs.
275
    ///
276
    /// This simply calls `append_pair` repeatedly.
277
    /// This can be more convenient, so the user doesn’t need to introduce a block
278
    /// to limit the scope of `Serializer`’s borrow of its string.
279
    ///
280
    /// Panics if called after `.finish()`.
281
0
    pub fn extend_pairs<I, K, V>(&mut self, iter: I) -> &mut Self
282
0
    where
283
0
        I: IntoIterator,
284
0
        I::Item: Borrow<(K, V)>,
285
0
        K: AsRef<str>,
286
0
        V: AsRef<str>,
287
    {
288
        {
289
0
            let string = string(&mut self.target);
290
0
            for pair in iter {
291
0
                let (k, v) = pair.borrow();
292
0
                append_pair(
293
0
                    string,
294
0
                    self.start_position,
295
0
                    self.encoding,
296
0
                    k.as_ref(),
297
0
                    v.as_ref(),
298
0
                );
299
0
            }
300
        }
301
0
        self
302
0
    }
303
304
    /// Serialize and append a number of names without values.
305
    ///
306
    /// This simply calls `append_key_only` repeatedly.
307
    /// This can be more convenient, so the user doesn’t need to introduce a block
308
    /// to limit the scope of `Serializer`’s borrow of its string.
309
    ///
310
    /// Panics if called after `.finish()`.
311
0
    pub fn extend_keys_only<I, K>(&mut self, iter: I) -> &mut Self
312
0
    where
313
0
        I: IntoIterator,
314
0
        I::Item: Borrow<K>,
315
0
        K: AsRef<str>,
316
    {
317
        {
318
0
            let string = string(&mut self.target);
319
0
            for key in iter {
320
0
                let k = key.borrow().as_ref();
321
0
                append_key_only(string, self.start_position, self.encoding, k);
322
0
            }
323
        }
324
0
        self
325
0
    }
326
327
    /// If this serializer was constructed with a string, take and return that string.
328
    ///
329
    /// ```rust
330
    /// use form_urlencoded;
331
    /// let encoded: String = form_urlencoded::Serializer::new(String::new())
332
    ///     .append_pair("foo", "bar & baz")
333
    ///     .append_pair("saison", "Été+hiver")
334
    ///     .finish();
335
    /// assert_eq!(encoded, "foo=bar+%26+baz&saison=%C3%89t%C3%A9%2Bhiver");
336
    /// ```
337
    ///
338
    /// Panics if called more than once.
339
0
    pub fn finish(&mut self) -> T::Finished {
340
0
        self.target
341
0
            .take()
342
0
            .expect("url::form_urlencoded::Serializer double finish")
343
0
            .finish()
344
0
    }
345
}
346
347
0
fn append_separator_if_needed(string: &mut String, start_position: usize) {
348
0
    if string.len() > start_position {
349
0
        string.push('&')
350
0
    }
351
0
}
352
353
0
fn string<T: Target>(target: &mut Option<T>) -> &mut String {
354
0
    target
355
0
        .as_mut()
356
0
        .expect("url::form_urlencoded::Serializer finished")
357
0
        .as_mut_string()
358
0
}
359
360
0
fn append_pair(
361
0
    string: &mut String,
362
0
    start_position: usize,
363
0
    encoding: EncodingOverride<'_>,
364
0
    name: &str,
365
0
    value: &str,
366
0
) {
367
0
    append_separator_if_needed(string, start_position);
368
0
    append_encoded(name, string, encoding);
369
0
    string.push('=');
370
0
    append_encoded(value, string, encoding);
371
0
}
372
373
0
fn append_key_only(
374
0
    string: &mut String,
375
0
    start_position: usize,
376
0
    encoding: EncodingOverride,
377
0
    name: &str,
378
0
) {
379
0
    append_separator_if_needed(string, start_position);
380
0
    append_encoded(name, string, encoding);
381
0
}
382
383
0
fn append_encoded(s: &str, string: &mut String, encoding: EncodingOverride<'_>) {
384
0
    string.extend(byte_serialize(&encode(encoding, s)))
385
0
}
386
387
0
pub(crate) fn encode<'a>(encoding_override: EncodingOverride<'_>, input: &'a str) -> Cow<'a, [u8]> {
388
0
    if let Some(o) = encoding_override {
389
0
        return o(input);
390
0
    }
391
0
    input.as_bytes().into()
392
0
}
393
394
// std::ptr::addr_eq was stabilized in rust 1.76. Once we upgrade
395
// the MSRV we can remove this lint override.
396
#[allow(ambiguous_wide_pointer_comparisons)]
397
0
pub(crate) fn decode_utf8_lossy(input: Cow<'_, [u8]>) -> Cow<'_, str> {
398
    // Note: This function is duplicated in `percent_encoding/lib.rs`.
399
0
    match input {
400
0
        Cow::Borrowed(bytes) => String::from_utf8_lossy(bytes),
401
0
        Cow::Owned(bytes) => {
402
0
            match String::from_utf8_lossy(&bytes) {
403
0
                Cow::Borrowed(utf8) => {
404
                    // If from_utf8_lossy returns a Cow::Borrowed, then we can
405
                    // be sure our original bytes were valid UTF-8. This is because
406
                    // if the bytes were invalid UTF-8 from_utf8_lossy would have
407
                    // to allocate a new owned string to back the Cow so it could
408
                    // replace invalid bytes with a placeholder.
409
410
                    // First we do a debug_assert to confirm our description above.
411
0
                    let raw_utf8: *const [u8] = utf8.as_bytes();
412
0
                    debug_assert!(core::ptr::eq(raw_utf8, &*bytes));
413
414
                    // Given we know the original input bytes are valid UTF-8,
415
                    // and we have ownership of those bytes, we re-use them and
416
                    // return a Cow::Owned here.
417
0
                    Cow::Owned(unsafe { String::from_utf8_unchecked(bytes) })
418
                }
419
0
                Cow::Owned(s) => Cow::Owned(s),
420
            }
421
        }
422
    }
423
0
}
424
425
pub type EncodingOverride<'a> = Option<&'a dyn Fn(&str) -> Cow<'_, [u8]>>;