Răsfoiți Sursa

Cow wrangling. (Reduce allocations/copying.)

Simon Sapin 10 ani în urmă
părinte
comite
d3e9824a21
4 a modificat fișierele cu 60 adăugiri și 50 ștergeri
  1. 29 11
      src/encoding.rs
  2. 7 19
      src/form_urlencoded.rs
  3. 1 1
      src/parser.rs
  4. 23 19
      src/percent_encoding.rs

+ 29 - 11
src/encoding.rs

@@ -65,18 +65,17 @@ impl EncodingOverride {
         self.encoding.is_none()
     }
 
-    pub fn decode<'a>(&self, input: &'a [u8]) -> Cow<'a, str> {
+    pub fn decode<'a>(&self, input: Cow<'a, [u8]>) -> Cow<'a, str> {
         match self.encoding {
-            Some(encoding) => encoding.decode(input, DecoderTrap::Replace).unwrap().into(),
-            None => String::from_utf8_lossy(input),
+            Some(encoding) => encoding.decode(&input, DecoderTrap::Replace).unwrap().into(),
+            None => decode_utf8_lossy(input),
         }
     }
 
-    pub fn encode<'a>(&self, input: &'a str) -> Cow<'a, [u8]> {
+    pub fn encode<'a>(&self, input: Cow<'a, str>) -> Cow<'a, [u8]> {
         match self.encoding {
-            Some(encoding) => Cow::Owned(
-                encoding.encode(input, EncoderTrap::NcrEscape).unwrap()),
-            None => Cow::Borrowed(input.as_bytes()),  // UTF-8
+            Some(encoding) => encoding.encode(&input, EncoderTrap::NcrEscape).unwrap().into(),
+            None => encode_utf8(input)
         }
     }
 }
@@ -105,11 +104,30 @@ impl EncodingOverride {
         true
     }
 
-    pub fn decode<'a>(&self, input: &'a [u8]) -> Cow<'a, str> {
-        String::from_utf8_lossy(input)
+    pub fn decode<'a>(&self, input: Cow<'a, [u8]>) -> Cow<'a, str> {
+        decode_utf8_lossy(input)
     }
 
-    pub fn encode<'a>(&self, input: &'a str) -> Cow<'a, [u8]> {
-        Cow::Borrowed(input.as_bytes())
+    pub fn encode<'a>(&self, input: Cow<'a, str>) -> Cow<'a, [u8]> {
+        encode_utf8(input)
+    }
+}
+
+pub fn decode_utf8_lossy(input: Cow<[u8]>) -> Cow<str> {
+    match input {
+        Cow::Borrowed(bytes) => String::from_utf8_lossy(bytes),
+        Cow::Owned(bytes) => {
+            match String::from_utf8_lossy(&bytes) {
+                Cow::Borrowed(_) => unsafe { String::from_utf8_unchecked(bytes) }.into(),
+                Cow::Owned(s) => s.into(),
+            }
+        }
+    }
+}
+
+pub fn encode_utf8(input: Cow<str>) -> Cow<[u8]> {
+    match input {
+        Cow::Borrowed(s) => s.as_bytes().into(),
+        Cow::Owned(s) => s.into_bytes().into()
     }
 }

+ 7 - 19
src/form_urlencoded.rs

@@ -107,24 +107,12 @@ impl<'a> Iterator for Parser<'a> {
     }
 }
 
-/// * 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),
-            }
-        }
-    }
+fn decode(input: &[u8], encoding: EncodingOverride) -> Cow<str> {
+    let replaced = replace_plus(input);
+    encoding.decode(match percent_decode(&replaced).if_any() {
+        Some(vec) => vec.into(),
+        None => replaced,
+    })
 }
 
 /// Replace b'+' with b' '
@@ -175,7 +163,7 @@ 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).iter() {
+        for &byte in encoding_override.encode(input.into()).iter() {
             if byte == b' ' {
                 output.push_str("+")
             } else {

+ 1 - 1
src/parser.rs

@@ -945,7 +945,7 @@ impl<'a> Parser<'a> {
             "http" | "https" | "file" | "ftp" | "gopher" => self.query_encoding_override,
             _ => EncodingOverride::utf8(),
         };
-        let query_bytes = encoding.encode(&query);
+        let query_bytes = encoding.encode(query.into());
         self.serialization.extend(percent_encode(&query_bytes, QUERY_ENCODE_SET));
         remaining
     }

+ 23 - 19
src/percent_encoding.rs

@@ -6,6 +6,7 @@
 // option. This file may not be copied, modified, or distributed
 // except according to those terms.
 
+use encoding;
 use std::ascii::AsciiExt;
 use std::borrow::Cow;
 use std::fmt::{self, Write};
@@ -293,23 +294,34 @@ impl<'a> Iterator for PercentDecode<'a> {
 }
 
 impl<'a> From<PercentDecode<'a>> for Cow<'a, [u8]> {
-    fn from(mut iter: PercentDecode<'a>) -> Self {
-        let initial_bytes = iter.bytes.as_slice();
-        while iter.bytes.find(|&&b| b == b'%').is_some() {
-            if let Some(decoded_byte) = after_percent_sign(&mut iter.bytes) {
-                let unchanged_bytes_len = initial_bytes.len() - iter.bytes.len() - 3;
+    fn from(iter: PercentDecode<'a>) -> Self {
+        match iter.if_any() {
+            Some(vec) => vec.into(),
+            None => iter.bytes.as_slice().into(),
+        }
+    }
+}
+
+impl<'a> PercentDecode<'a> {
+    /// If the percent-decoding is different from the input, return it as a new bytes vector.
+    pub fn if_any(&self) -> Option<Vec<u8>> {
+        let mut bytes_iter = self.bytes.clone();
+        while bytes_iter.find(|&&b| b == b'%').is_some() {
+            if let Some(decoded_byte) = after_percent_sign(&mut bytes_iter) {
+                let initial_bytes = self.bytes.as_slice();
+                let unchanged_bytes_len = initial_bytes.len() - bytes_iter.len() - 3;
                 let mut decoded = initial_bytes[..unchanged_bytes_len].to_owned();
                 decoded.push(decoded_byte);
-                decoded.extend(iter);
-                return decoded.into()
+                decoded.extend(PercentDecode {
+                    bytes: bytes_iter
+                });
+                return Some(decoded)
             }
         }
         // Nothing to decode
-        initial_bytes.into()
+        None
     }
-}
 
-impl<'a> PercentDecode<'a> {
     /// Decode the result of percent-decoding as UTF-8.
     ///
     /// This is return `Err` when the percent-decoded bytes are not well-formed in UTF-8.
@@ -335,14 +347,6 @@ impl<'a> PercentDecode<'a> {
     /// Invalid UTF-8 percent-encoded byte sequences will be replaced � U+FFFD,
     /// the replacement character.
     pub fn decode_utf8_lossy(self) -> Cow<'a, str> {
-        match self.clone().into() {
-            Cow::Borrowed(bytes) => String::from_utf8_lossy(bytes),
-            Cow::Owned(bytes) => {
-                match String::from_utf8_lossy(&bytes) {
-                    Cow::Borrowed(_) => unsafe { String::from_utf8_unchecked(bytes) }.into(),
-                    Cow::Owned(s) => s.into(),
-                }
-            }
-        }
+        encoding::decode_utf8_lossy(self.clone().into())
     }
 }