فهرست منبع

form_urlencoded::parse returns an iterator.

Simon Sapin 10 سال پیش
والد
کامیت
31cdce5a52
5فایلهای تغییر یافته به همراه109 افزوده شده و 52 حذف شده
  1. 7 5
      src/encoding.rs
  2. 93 39
      src/form_urlencoded.rs
  3. 2 2
      src/lib.rs
  4. 1 1
      src/percent_encoding.rs
  5. 6 5
      tests/tests.rs

+ 7 - 5
src/encoding.rs

@@ -43,6 +43,8 @@ impl EncodingOverride {
     }
 
     pub fn lookup(label: &[u8]) -> Option<EncodingOverride> {
+        // Don't use String::from_utf8_lossy since no encoding label contains U+FFFD
+        // https://encoding.spec.whatwg.org/#names-and-labels
         ::std::str::from_utf8(label)
         .ok()
         .and_then(encoding_from_whatwg_label)
@@ -53,10 +55,10 @@ impl EncodingOverride {
         self.encoding.is_none()
     }
 
-    pub fn decode(&self, input: &[u8]) -> String {
+    pub fn decode<'a>(&self, input: &'a [u8]) -> Cow<'a, str> {
         match self.encoding {
-            Some(encoding) => encoding.decode(input, DecoderTrap::Replace).unwrap(),
-            None => String::from_utf8_lossy(input).to_string(),
+            Some(encoding) => encoding.decode(input, DecoderTrap::Replace).unwrap().into(),
+            None => String::from_utf8_lossy(input),
         }
     }
 
@@ -89,8 +91,8 @@ impl EncodingOverride {
         true
     }
 
-    pub fn decode(&self, input: &[u8]) -> String {
-        String::from_utf8_lossy(input).into_owned()
+    pub fn decode<'a>(&self, input: &'a [u8]) -> Cow<'a, str> {
+        String::from_utf8_lossy(input)
     }
 
     pub fn encode<'a>(&self, input: &'a str) -> Cow<'a, [u8]> {

+ 93 - 39
src/form_urlencoded.rs

@@ -13,27 +13,30 @@
 //! Converts between a string (such as an URL’s query string)
 //! and a sequence of (name, value) pairs.
 
-use std::borrow::Borrow;
 use std::ascii::AsciiExt;
+use std::borrow::{Borrow, Cow};
 use encoding::EncodingOverride;
 use percent_encoding::{percent_encode, percent_decode, FORM_URLENCODED_ENCODE_SET};
 
 
 /// Convert a byte string in the `application/x-www-form-urlencoded` format
-/// into a vector of (name, value) pairs.
+/// into a iterator of (name, value) pairs.
 ///
 /// Use `parse(input.as_bytes())` to parse a `&str` string.
 ///
-/// The names and values are URL-decoded. For instance, `%23first=%25try%25` will be
+/// The names and values are percent-decoded. For instance, `%23first=%25try%25` will be
 /// converted to `[("#first", "%try%")]`.
 #[inline]
-pub fn parse(input: &[u8]) -> Vec<(String, String)> {
-    parse_internal(input, EncodingOverride::utf8(), false).unwrap()
+pub fn parse(input: &[u8]) -> Parser {
+    Parser {
+        input: input,
+        encoding: EncodingOverride::utf8(),
+    }
 }
 
 
 /// Convert a byte string in the `application/x-www-form-urlencoded` format
-/// into a vector of (name, value) pairs.
+/// into a iterator of (name, value) pairs.
 ///
 /// Use `parse(input.as_bytes())` to parse a `&str` string.
 ///
@@ -45,50 +48,101 @@ pub fn parse(input: &[u8]) -> Vec<(String, String)> {
 ///    after percent-decoding. Defaults to UTF-8.
 /// * `use_charset`: The *use _charset_ flag*. If in doubt, set to `false`.
 #[cfg(feature = "query_encoding")]
-#[inline]
-pub fn parse_with_encoding(input: &[u8], encoding_override: Option<::encoding::EncodingRef>,
-                           use_charset: bool)
-                           -> Option<Vec<(String, String)>> {
-    parse_internal(input, EncodingOverride::from_opt_encoding(encoding_override), use_charset)
+pub fn parse_with_encoding<'a>(input: &'a [u8],
+                               encoding_override: Option<::encoding::EncodingRef>,
+                               use_charset: bool)
+                               -> Result<Parser<'a>, ()> {
+    let mut encoding = EncodingOverride::from_opt_encoding(encoding_override);
+    if !(encoding.is_utf8() || input.is_ascii()) {
+        return Err(())
+    }
+    if use_charset {
+        for sequence in input.split(|&b| b == b'&') {
+            // No '+' in "_charset_" to replace with ' '.
+            if sequence.starts_with(b"_charset_=") {
+                let value = &sequence[b"_charset_=".len()..];
+                // Skip replacing '+' with ' ' in value since no encoding label contains either:
+                // https://encoding.spec.whatwg.org/#names-and-labels
+                if let Some(e) = EncodingOverride::lookup(value) {
+                    encoding = e;
+                    break
+                }
+            }
+        }
+    }
+    Ok(Parser {
+        input: input,
+        encoding: encoding,
+    })
 }
 
+/// The return type of `parse()`.
+pub struct Parser<'a> {
+    input: &'a [u8],
+    encoding: EncodingOverride,
+}
 
-fn parse_internal(input: &[u8], mut encoding_override: EncodingOverride, mut use_charset: bool)
-                  -> Option<Vec<(String, String)>> {
-    let mut pairs = Vec::new();
-    for piece in input.split(|&b| b == b'&') {
-        if !piece.is_empty() {
-            let (name, value) = match piece.iter().position(|b| *b == b'=') {
-                Some(position) => (&piece[..position], &piece[position + 1..]),
-                None => (piece, &[][..])
-            };
+impl<'a> Iterator for Parser<'a> {
+    type Item = (Cow<'a, str>, Cow<'a, str>);
 
-            #[inline]
-            fn replace_plus(input: &[u8]) -> Vec<u8> {
-                input.iter().map(|&b| if b == b'+' { b' ' } else { b }).collect()
+    fn next(&mut self) -> Option<Self::Item> {
+        loop {
+            if self.input.is_empty() {
+                return None
             }
-
-            let name = replace_plus(name);
-            let value = replace_plus(value);
-            if use_charset && name == b"_charset_" {
-                if let Some(encoding) = EncodingOverride::lookup(&value) {
-                    encoding_override = encoding;
-                }
-                use_charset = false;
+            let mut split2 = self.input.splitn(2, |&b| b == b'&');
+            let sequence = split2.next().unwrap();
+            self.input = split2.next().unwrap_or(&[][..]);
+            if sequence.is_empty() {
+                continue
             }
-            pairs.push((name, value));
+            let mut split2 = sequence.splitn(2, |&b| b == b'=');
+            let name = split2.next().unwrap();
+            let value = split2.next().unwrap_or(&[][..]);
+            return Some((
+                decode(name, self.encoding),
+                decode(value, self.encoding),
+            ))
         }
     }
-    if !(encoding_override.is_utf8() || input.is_ascii()) {
-        return None
-    }
+}
 
-    Some(pairs.into_iter().map(|(name, value)| (
-        encoding_override.decode(&percent_decode(&name).collect::<Vec<u8>>()),
-        encoding_override.decode(&percent_decode(&value).collect::<Vec<u8>>()),
-    )).collect())
+/// * Replace b'+' with b' '
+/// * Then percent-decode
+/// * Then decode with `encoding`
+fn decode<'a>(input: &'a [u8], encoding: EncodingOverride) -> Cow<'a, str> {
+    // The return value can borrow `input` but not an intermediate Cow,
+    // so we need to return Owned if either of the intermediate Cow is Owned
+    match replace_plus(input) {
+        Cow::Owned(replaced) => {
+            let decoded: Cow<_> = percent_decode(&replaced).into();
+            encoding.decode(&decoded).into_owned().into()
+        }
+        Cow::Borrowed(replaced) => {
+            match percent_decode(replaced).into() {
+                Cow::Owned(decoded) => encoding.decode(&decoded).into_owned().into(),
+                Cow::Borrowed(decoded) => encoding.decode(decoded),
+            }
+        }
+    }
 }
 
+/// Replace b'+' with b' '
+fn replace_plus<'a>(input: &'a [u8]) -> Cow<'a, [u8]> {
+    match input.iter().position(|&b| b == b'+') {
+        None => input.into(),
+        Some(first_position) => {
+            let mut replaced = input.to_owned();
+            replaced[first_position] = b' ';
+            for byte in &mut replaced[first_position + 1..] {
+                if *byte == b'+' {
+                    *byte = b' ';
+                }
+            }
+            replaced.into()
+        }
+    }
+}
 
 /// Convert an iterator of (name, value) pairs
 /// into a string in the `application/x-www-form-urlencoded` format.

+ 2 - 2
src/lib.rs

@@ -988,9 +988,9 @@ impl Url {
     }
 
     /// Parse the URL’s query string, if any, as `application/x-www-form-urlencoded`
-    /// and return a vector of (key, value) pairs.
+    /// and return an iterator of (key, value) pairs.
     #[inline]
-    pub fn query_pairs(&self) -> Option<Vec<(String, String)>> {
+    pub fn query_pairs(&self) -> Option<form_urlencoded::Parser> {
         self.query().map(|query| form_urlencoded::parse(query.as_bytes()))
     }
 

+ 1 - 1
src/percent_encoding.rs

@@ -249,7 +249,7 @@ impl<'a, E: EncodeSet> From<PercentEncode<'a, E>> for Cow<'a, str> {
 /// (which returns `Cow::Borrowed` when `input` contains no percent-encoded sequence)
 /// and has `decode_utf8()` and `decode_utf8_lossy()` methods.
 #[inline]
-pub fn percent_decode(input: &[u8]) -> PercentDecode {
+pub fn percent_decode<'a>(input: &'a [u8]) -> PercentDecode<'a> {
     PercentDecode {
         bytes: input.iter()
     }

+ 6 - 5
tests/tests.rs

@@ -8,6 +8,7 @@
 
 extern crate url;
 
+use std::borrow::Cow;
 use std::net::{Ipv4Addr, Ipv6Addr};
 use std::path::{Path, PathBuf};
 use url::{Host, Url};
@@ -213,14 +214,14 @@ fn test_serialization() {
 fn test_form_urlencoded() {
     use url::form_urlencoded::*;
 
-    let pairs = &[
-        ("foo".to_string(), "é&".to_string()),
-        ("bar".to_string(), "".to_string()),
-        ("foo".to_string(), "#".to_string())
+    let pairs: &[(Cow<str>, Cow<str>)] = &[
+        ("foo".into(), "é&".into()),
+        ("bar".into(), "".into()),
+        ("foo".into(), "#".into())
     ];
     let encoded = serialize(pairs);
     assert_eq!(encoded, "foo=%C3%A9%26&bar=&foo=%23");
-    assert_eq!(parse(encoded.as_bytes()), pairs.to_vec());
+    assert_eq!(parse(encoded.as_bytes()).collect::<Vec<_>>(), pairs.to_vec());
 }
 
 #[test]