Просмотр исходного кода

form_urlencoded::Serializer is a "stateful" object.

Simon Sapin 10 лет назад
Родитель
Сommit
bea7a484af
5 измененных файлов с 141 добавлено и 89 удалено
  1. 7 12
      src/encoding.rs
  2. 121 50
      src/form_urlencoded.rs
  3. 2 2
      src/lib.rs
  4. 0 8
      src/percent_encoding.rs
  5. 11 17
      tests/tests.rs

+ 7 - 12
src/encoding.rs

@@ -65,6 +65,13 @@ impl EncodingOverride {
         self.encoding.is_none()
     }
 
+    pub fn name(&self) -> &'static str {
+        match self.encoding {
+            Some(encoding) => encoding.name(),
+            None => "utf-8",
+        }
+    }
+
     pub fn decode<'a>(&self, input: Cow<'a, [u8]>) -> Cow<'a, str> {
         match self.encoding {
             Some(encoding) => encoding.decode(&input, DecoderTrap::Replace).unwrap().into(),
@@ -92,18 +99,6 @@ impl EncodingOverride {
         EncodingOverride
     }
 
-    pub fn lookup(_label: &[u8]) -> Option<Self> {
-        None
-    }
-
-    pub fn to_output_encoding(self) -> Self {
-        self
-    }
-
-    pub fn is_utf8(&self) -> bool {
-        true
-    }
-
     pub fn decode<'a>(&self, input: Cow<'a, [u8]>) -> Cow<'a, str> {
         decode_utf8_lossy(input)
     }

+ 121 - 50
src/form_urlencoded.rs

@@ -13,10 +13,10 @@
 //! Converts between a string (such as an URL’s query string)
 //! and a sequence of (name, value) pairs.
 
-use std::ascii::AsciiExt;
-use std::borrow::{Borrow, Cow};
 use encoding::EncodingOverride;
-use percent_encoding::{percent_encode, percent_decode, FORM_URLENCODED_ENCODE_SET};
+use percent_encoding::{percent_encode_byte, percent_decode};
+use std::borrow::{Borrow, Cow};
+use std::str;
 
 
 /// Convert a byte string in the `application/x-www-form-urlencoded` format
@@ -27,8 +27,8 @@ use percent_encoding::{percent_encode, percent_decode, FORM_URLENCODED_ENCODE_SE
 /// The names and values are percent-decoded. For instance, `%23first=%25try%25` will be
 /// converted to `[("#first", "%try%")]`.
 #[inline]
-pub fn parse(input: &[u8]) -> Parser {
-    Parser {
+pub fn parse(input: &[u8]) -> Parse {
+    Parse {
         input: input,
         encoding: EncodingOverride::utf8(),
     }
@@ -51,7 +51,9 @@ pub fn parse(input: &[u8]) -> Parser {
 pub fn parse_with_encoding<'a>(input: &'a [u8],
                                encoding_override: Option<::encoding::EncodingRef>,
                                use_charset: bool)
-                               -> Result<Parser<'a>, ()> {
+                               -> Result<Parse<'a>, ()> {
+    use std::ascii::AsciiExt;
+
     let mut encoding = EncodingOverride::from_opt_encoding(encoding_override);
     if !(encoding.is_utf8() || input.is_ascii()) {
         return Err(())
@@ -70,19 +72,19 @@ pub fn parse_with_encoding<'a>(input: &'a [u8],
             }
         }
     }
-    Ok(Parser {
+    Ok(Parse {
         input: input,
         encoding: encoding,
     })
 }
 
 /// The return type of `parse()`.
-pub struct Parser<'a> {
+pub struct Parse<'a> {
     input: &'a [u8],
     encoding: EncodingOverride,
 }
 
-impl<'a> Iterator for Parser<'a> {
+impl<'a> Iterator for Parse<'a> {
     type Item = (Cow<'a, str>, Cow<'a, str>);
 
     fn next(&mut self) -> Option<Self::Item> {
@@ -132,55 +134,124 @@ fn replace_plus<'a>(input: &'a [u8]) -> Cow<'a, [u8]> {
     }
 }
 
-/// Convert an iterator of (name, value) pairs
-/// into a string in the `application/x-www-form-urlencoded` format.
-#[inline]
-pub fn serialize<I, K, V>(pairs: I) -> String
-where I: IntoIterator, I::Item: Borrow<(K, V)>, K: AsRef<str>, V: AsRef<str> {
-    serialize_internal(pairs, EncodingOverride::utf8())
+/// The [`application/x-www-form-urlencoded` byte serializer](
+/// https://url.spec.whatwg.org/#concept-urlencoded-byte-serializer).
+///
+/// Return an iterator of `&str` slices.
+pub fn byte_serialize(input: &[u8]) -> ByteSerialize {
+    ByteSerialize {
+        bytes: input,
+    }
 }
 
-/// Convert an iterator of (name, value) pairs
-/// into a string in the `application/x-www-form-urlencoded` format.
-///
-/// This function is only available if the `query_encoding` Cargo feature is enabled.
-///
-/// Arguments:
-///
-/// * `encoding_override`: The character encoding each name and values is encoded as
-///    before percent-encoding. Defaults to UTF-8.
-#[cfg(feature = "query_encoding")]
-#[inline]
-pub fn serialize_with_encoding<I, K, V>(pairs: I,
-                                        encoding_override: Option<::encoding::EncodingRef>)
-                                        -> String
-where I: IntoIterator, I::Item: Borrow<(K, V)>, K: AsRef<str>, V: AsRef<str> {
-    serialize_internal(pairs, EncodingOverride::from_opt_encoding(encoding_override).to_output_encoding())
+/// Return value of `byte_serialize()`.
+pub struct ByteSerialize<'a> {
+    bytes: &'a [u8],
+}
+
+fn byte_serialized_unchanged(byte: u8) -> bool {
+    matches!(byte, b'*' | b'-' | b'.' | b'0' ... b'9' | b'A' ... b'Z' | b'_' | b'a' ... b'z')
 }
 
-fn serialize_internal<I, K, V>(pairs: I, encoding_override: EncodingOverride) -> String
-where I: IntoIterator, I::Item: Borrow<(K, V)>, K: AsRef<str>, V: AsRef<str> {
-    #[inline]
-    fn byte_serialize(input: &str, output: &mut String,
-                      encoding_override: EncodingOverride) {
-        for &byte in encoding_override.encode(input.into()).iter() {
-            if byte == b' ' {
-                output.push_str("+")
-            } else {
-                output.extend(percent_encode(&[byte], FORM_URLENCODED_ENCODE_SET))
+impl<'a> Iterator for ByteSerialize<'a> {
+    type Item = &'a str;
+
+    fn next(&mut self) -> Option<&'a str> {
+        if let Some((&first, tail)) = self.bytes.split_first() {
+            if !byte_serialized_unchanged(first) {
+                self.bytes = tail;
+                return Some(if first == b' ' { "+" } else { percent_encode_byte(first) })
             }
+            let position = tail.iter().position(|&b| !byte_serialized_unchanged(b));
+            let (unchanged_slice, remaining) = match position {
+                // 1 for first_byte + i unchanged in tail
+                Some(i) => self.bytes.split_at(1 + i),
+                None => (self.bytes, &[][..]),
+            };
+            self.bytes = remaining;
+            Some(unsafe { str::from_utf8_unchecked(unchanged_slice) })
+        } else {
+            None
         }
     }
 
-    let mut output = String::new();
-    for pair in pairs {
-        let &(ref name, ref value) = pair.borrow();
-        if !output.is_empty() {
-            output.push_str("&");
+    fn size_hint(&self) -> (usize, Option<usize>) {
+        if self.bytes.is_empty() {
+            (0, Some(0))
+        } else {
+            (1, Some(self.bytes.len()))
         }
-        byte_serialize(name.as_ref(), &mut output, encoding_override);
-        output.push_str("=");
-        byte_serialize(value.as_ref(), &mut output, encoding_override);
     }
-    output
+}
+
+/// The [`application/x-www-form-urlencoded` serializer](
+/// https://url.spec.whatwg.org/#concept-urlencoded-serializer).
+pub struct Serializer<'a> {
+    string: &'a mut String,
+    start_position: usize,
+    encoding: EncodingOverride,
+}
+
+impl<'a> Serializer<'a> {
+    /// Create a new `application/x-www-form-urlencoded` serializer
+    /// for the given range of the given string.
+    ///
+    /// If the range is non-empty, the corresponding slice of the string is assumed
+    /// to already be in `application/x-www-form-urlencoded` format.
+    pub fn new(string: &'a mut String, start_position: usize) -> Self {
+        &string[start_position..];  // Panic if out of bounds
+        Serializer {
+            string: string,
+            start_position: start_position,
+            encoding: EncodingOverride::utf8(),
+        }
+    }
+
+    /// Remove any existing name/value pair.
+    pub fn clear(&mut self) {
+        self.string.truncate(self.start_position)
+    }
+
+    /// Set the character encoding to be used for names and values before percent-encoding.
+    #[cfg(feature = "query_encoding")]
+    pub fn encoding_override(&mut self, new: Option<::encoding::EncodingRef>) {
+        self.encoding = EncodingOverride::from_opt_encoding(new).to_output_encoding();;
+    }
+
+    fn append_separator_if_needed(&mut self) {
+        if self.string.len() > self.start_position {
+            self.string.push('&')
+        }
+    }
+
+    /// Serialize and append a name/value pair.
+    pub fn append_pair(&mut self, name: &str, value: &str) {
+        self.append_separator_if_needed();
+        self.string.extend(byte_serialize(&self.encoding.encode(name.into())));
+        self.string.push('=');
+        self.string.extend(byte_serialize(&self.encoding.encode(value.into())));
+    }
+
+    /// Serialize and append a number of name/value pairs.
+    ///
+    /// This simply calls `append_pair` repeatedly.
+    /// This can be more convenient, so the user doesn’t need to introduce a block
+    /// to limit the scope of `Serializer`’s borrow of its string.
+    pub fn append_pairs<I, K, V>(&mut self, iter: I)
+    where I: IntoIterator, I::Item: Borrow<(K, V)>, K: AsRef<str>, V: AsRef<str> {
+        for pair in iter {
+            let &(ref k, ref v) = pair.borrow();
+            self.append_pair(k.as_ref(), v.as_ref())
+        }
+    }
+
+    /// Add a name/value pair whose name is `_charset_`
+    /// and whose value is the character encoding’s name.
+    /// (See the `encoding_override()` method.)
+    #[cfg(feature = "query_encoding")]
+    pub fn append_charset(&mut self) {
+        self.append_separator_if_needed();
+        self.string.push_str("_charset_=");
+        self.string.push_str(self.encoding.name());
+    }
 }

+ 2 - 2
src/lib.rs

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

+ 0 - 8
src/percent_encoding.rs

@@ -110,14 +110,6 @@ define_encode_set! {
     }
 }
 
-define_encode_set! {
-    /// This encode set is used in `application/x-www-form-urlencoded` serialization.
-    pub FORM_URLENCODED_ENCODE_SET = [SIMPLE_ENCODE_SET] | {
-        ' ', '!', '"', '#', '$', '%', '&', '\'', '(', ')', '+', ',', '/', ':', ';',
-        '<', '=', '>', '?', '@', '[', '\\', ']', '^', '`', '{', '|', '}', '~'
-    }
-}
-
 /// Return the percent-encoding of the given bytes.
 ///
 /// This is unconditional, unlike `percent_encode()` which uses an encode set.

+ 11 - 17
tests/tests.rs

@@ -11,7 +11,7 @@ extern crate url;
 use std::borrow::Cow;
 use std::net::{Ipv4Addr, Ipv6Addr};
 use std::path::{Path, PathBuf};
-use url::{Host, Url};
+use url::{Host, Url, form_urlencoded};
 
 macro_rules! assert_from_file_path {
     ($path: expr) => { assert_from_file_path!($path, $path) };
@@ -212,30 +212,24 @@ fn test_serialization() {
 
 #[test]
 fn test_form_urlencoded() {
-    use url::form_urlencoded::*;
-
     let pairs: &[(Cow<str>, Cow<str>)] = &[
         ("foo".into(), "é&".into()),
         ("bar".into(), "".into()),
         ("foo".into(), "#".into())
     ];
-    let encoded = serialize(pairs);
+    let mut encoded = String::new();
+    form_urlencoded::Serializer::new(&mut encoded, 0).append_pairs(pairs);
     assert_eq!(encoded, "foo=%C3%A9%26&bar=&foo=%23");
-    assert_eq!(parse(encoded.as_bytes()).collect::<Vec<_>>(), pairs.to_vec());
+    assert_eq!(form_urlencoded::parse(encoded.as_bytes()).collect::<Vec<_>>(), pairs.to_vec());
 }
 
 #[test]
 fn test_form_serialize() {
-    use url::form_urlencoded::*;
-
-    let pairs = [("foo", "é&"),
-                 ("bar", ""),
-                 ("foo", "#")];
-
-    let want = "foo=%C3%A9%26&bar=&foo=%23";
-    // Works with referenced tuples
-    assert_eq!(serialize(pairs.iter()), want);
-    // Works with owned tuples
-    assert_eq!(serialize(pairs.iter().map(|p| (p.0, p.1))), want);
-
+    let mut encoded = String::new();
+    form_urlencoded::Serializer::new(&mut encoded, 0).append_pairs(&[
+        ("foo", "é&"),
+        ("bar", ""),
+        ("foo", "#")
+    ]);
+    assert_eq!(encoded, "foo=%C3%A9%26&bar=&foo=%23");
 }