Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions form_urlencoded/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,70 @@ impl<'a, T: Target> Serializer<'a, T> {
self
}

/// Remove all name/value pairs whose name is `name`.
///
/// Names are compared after percent-decoding, so this matches the same
/// names that [`parse`] and `Url::query_pairs` would yield. Pairs that are
/// kept keep their original serialization.
///
/// ```rust
/// use form_urlencoded;
/// let encoded: String = form_urlencoded::Serializer::new(String::from("a=1&b=2&a=3"))
/// .remove_all("a")
/// .finish();
/// assert_eq!(encoded, "b=2");
/// ```
///
/// Panics if called after `.finish()`.
pub fn remove_all(&mut self, name: &str) -> &mut Self {
self.remove_pairs(name, false);
self
}

/// Remove the first name/value pair whose name is `name`.
///
/// Names are compared after percent-decoding, so this matches the same
/// names that [`parse`] and `Url::query_pairs` would yield. Pairs that are
/// kept keep their original serialization.
///
/// ```rust
/// use form_urlencoded;
/// let encoded: String = form_urlencoded::Serializer::new(String::from("a=1&b=2&a=3"))
/// .remove_first("a")
/// .finish();
/// assert_eq!(encoded, "b=2&a=3");
/// ```
///
/// Panics if called after `.finish()`.
pub fn remove_first(&mut self, name: &str) -> &mut Self {
self.remove_pairs(name, true);
self
}

fn remove_pairs(&mut self, name: &str, first_only: bool) {
let start = self.start_position;
let string = string(&mut self.target);
if string.len() <= start {
return;
}
let mut kept = String::with_capacity(string.len() - start);
let mut removed = false;
for segment in string[start..].split('&') {
let matches = !(first_only && removed)
&& decode(segment.split('=').next().unwrap_or("").as_bytes()).as_ref() == name;
if matches {
removed = true;
continue;
}
if !kept.is_empty() {
kept.push('&');
}
kept.push_str(segment);
}
string.truncate(start);
string.push_str(&kept);
}

/// Set the character encoding to be used for names and values before percent-encoding.
pub fn encoding_override(&mut self, new: EncodingOverride<'a>) -> &mut Self {
self.encoding = new;
Expand Down
41 changes: 41 additions & 0 deletions url/tests/unit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -531,6 +531,47 @@ fn extend_query_pairs_then_mutate() {
);
}

#[test]
/// https://github.com/servo/rust-url/issues/1087
fn remove_query_pairs() {
let mut url: Url = "http://example.com/?a=1&b=2&a=3#frag".parse().unwrap();
url.query_pairs_mut().remove_all("a");
url.check_invariants().unwrap();
assert_eq!(url.to_string(), "http://example.com/?b=2#frag");

let mut url: Url = "http://example.com/?a=1&b=2&a=3".parse().unwrap();
url.query_pairs_mut().remove_first("a");
url.check_invariants().unwrap();
assert_eq!(url.to_string(), "http://example.com/?b=2&a=3");

// Removing a name that isn't present leaves the query untouched.
let mut url: Url = "http://example.com/?a=1&b=2".parse().unwrap();
url.query_pairs_mut().remove_all("c");
url.check_invariants().unwrap();
assert_eq!(url.to_string(), "http://example.com/?a=1&b=2");

// Removing every pair leaves an empty query, like `clear`.
let mut url: Url = "http://example.com/?a=1&a=2".parse().unwrap();
url.query_pairs_mut().remove_all("a");
url.check_invariants().unwrap();
assert_eq!(url.to_string(), "http://example.com/?");
}

#[test]
fn remove_query_pairs_percent_decoded() {
// The name is matched after percent-decoding, so `%61` matches `a`,
// and retained pairs keep their original serialization.
let mut url: Url = "http://example.com/?%61=1&b=2".parse().unwrap();
url.query_pairs_mut().remove_all("a");
url.check_invariants().unwrap();
assert_eq!(url.to_string(), "http://example.com/?b=2");

let encoded: String = form_urlencoded::Serializer::new(String::from("a+b=1&c=2"))
.remove_all("a b")
.finish();
assert_eq!(encoded, "c=2");
}

#[test]
/// https://github.com/servo/rust-url/issues/222
fn append_empty_segment_then_mutate() {
Expand Down