Переглянути джерело

Make percent-decoding an iterator.

Simon Sapin 10 роки тому
батько
коміт
6db8b84f95
4 змінених файлів з 47 додано та 38 видалено
  1. 2 2
      src/form_urlencoded.rs
  2. 2 3
      src/host.rs
  3. 3 7
      src/lib.rs
  4. 40 26
      src/percent_encoding.rs

+ 2 - 2
src/form_urlencoded.rs

@@ -84,8 +84,8 @@ fn parse_internal(input: &[u8], mut encoding_override: EncodingOverride, mut use
     }
 
     Some(pairs.into_iter().map(|(name, value)| (
-        encoding_override.decode(&percent_decode(&name)),
-        encoding_override.decode(&percent_decode(&value))
+        encoding_override.decode(&percent_decode(&name).collect::<Vec<u8>>()),
+        encoding_override.decode(&percent_decode(&value).collect::<Vec<u8>>()),
     )).collect())
 }
 

+ 2 - 3
src/host.rs

@@ -10,7 +10,7 @@ use std::cmp;
 use std::fmt::{self, Formatter, Write};
 use std::net::{Ipv4Addr, Ipv6Addr};
 use parser::{ParseResult, ParseError};
-use percent_encoding::percent_decode;
+use percent_encoding::lossy_utf8_percent_decode;
 use idna;
 
 #[derive(Copy, Clone, Debug)]
@@ -64,8 +64,7 @@ impl Host<String> {
             }
             return parse_ipv6addr(&input[1..input.len() - 1]).map(Host::Ipv6)
         }
-        let decoded = percent_decode(input.as_bytes());
-        let domain = String::from_utf8_lossy(&decoded);
+        let domain = lossy_utf8_percent_decode(input.as_bytes());
         let domain = try!(idna::domain_to_ascii(&domain));
         if domain.find(|c| matches!(c,
             '\0' | '\t' | '\n' | '\r' | ' ' | '#' | '%' | '/' | ':' | '?' | '@' | '[' | '\\' | ']'

+ 3 - 7
src/lib.rs

@@ -126,7 +126,7 @@ assert_eq!(css_url.as_str(), "http://servo.github.io/rust-url/main.css")
 extern crate idna;
 
 use host::HostInternal;
-use percent_encoding::{PATH_SEGMENT_ENCODE_SET, percent_encode_to};
+use percent_encoding::{PATH_SEGMENT_ENCODE_SET, percent_encode_to, percent_decode};
 use std::cmp;
 use std::fmt;
 use std::hash;
@@ -635,12 +635,10 @@ fn file_url_segments_to_pathbuf(segments: str::Split<char>) -> Result<PathBuf, (
     use std::os::unix::prelude::OsStrExt;
     use std::path::PathBuf;
 
-    use percent_encoding::percent_decode_to;
-
     let mut bytes = Vec::new();
     for segment in segments {
         bytes.push(b'/');
-        percent_decode_to(segment.as_bytes(), &mut bytes);
+        bytes.extend(percent_decode(segment.as_bytes()));
     }
     let os_str = OsStr::from_bytes(&bytes);
     let path = PathBuf::from(os_str);
@@ -657,8 +655,6 @@ fn file_url_segments_to_pathbuf(segments: str::Split<char>) -> Result<PathBuf, (
 // Build this unconditionally to alleviate https://github.com/servo/rust-url/issues/102
 #[cfg_attr(not(windows), allow(dead_code))]
 fn file_url_segments_to_pathbuf_windows(mut segments: str::Split<char>) -> Result<PathBuf, ()> {
-    use percent_encoding::percent_decode;
-
     let first = try!(segments.next().ok_or(()));
     if first.len() != 2 || !first.starts_with(parser::ascii_alpha)
             || first.as_bytes()[1] != b':' {
@@ -669,7 +665,7 @@ fn file_url_segments_to_pathbuf_windows(mut segments: str::Split<char>) -> Resul
         string.push('\\');
 
         // Currently non-unicode windows paths cannot be represented
-        match String::from_utf8(percent_decode(segment.as_bytes())) {
+        match String::from_utf8(percent_decode(segment.as_bytes()).collect()) {
             Ok(s) => string.push_str(&s),
             Err(..) => return Err(()),
         }

+ 40 - 26
src/percent_encoding.rs

@@ -7,7 +7,9 @@
 // except according to those terms.
 
 use std::ascii::AsciiExt;
+use std::borrow::Cow;
 use std::fmt::Write;
+use std::slice;
 
 /// Represents a set of characters / bytes that should be percent-encoded.
 ///
@@ -163,41 +165,53 @@ pub fn utf8_percent_encode<E: EncodeSet>(input: &str, encode_set: E) -> String {
 }
 
 
-/// Percent-decode the given bytes, and push the result to `output`.
-pub fn percent_decode_to(input: &[u8], output: &mut Vec<u8>) {
-    let mut i = 0;
-    while i < input.len() {
-        let c = input[i];
-        if c == b'%' && i + 2 < input.len() {
-            let h = (input[i + 1] as char).to_digit(16);
-            let l = (input[i + 2] as char).to_digit(16);
-            if let (Some(h), Some(l)) = (h, l) {
-                output.push(h as u8 * 0x10 + l as u8);
-                i += 3;
-                continue
-            }
-        }
-
-        output.push(c);
-        i += 1;
+/// Percent-decode the given bytes and return an iterator of bytes.
+#[inline]
+pub fn percent_decode(input: &[u8]) -> PercentDecode {
+    PercentDecode {
+        iter: input.iter()
     }
 }
 
-
-/// Percent-decode the given bytes.
-#[inline]
-pub fn percent_decode(input: &[u8]) -> Vec<u8> {
-    let mut output = Vec::new();
-    percent_decode_to(input, &mut output);
-    output
+pub struct PercentDecode<'a> {
+    iter: slice::Iter<'a, u8>,
 }
 
+impl<'a> Iterator for PercentDecode<'a> {
+    type Item = u8;
+
+    fn next(&mut self) -> Option<u8> {
+        self.iter.next().map(|&byte| {
+            if byte == b'%' {
+                let after_percent_sign = self.iter.clone();
+                let h = self.iter.next().and_then(|&b| (b as char).to_digit(16));
+                let l = self.iter.next().and_then(|&b| (b as char).to_digit(16));
+                if let (Some(h), Some(l)) = (h, l) {
+                    return h as u8 * 0x10 + l as u8
+                }
+                self.iter = after_percent_sign;
+            }
+            byte
+        })
+    }
+
+    fn size_hint(&self) -> (usize, Option<usize>) {
+        let (low, high) = self.iter.size_hint();
+        (low, high.and_then(|high| high.checked_mul(3)))
+    }
+}
 
 /// Percent-decode the given bytes, and decode the result as UTF-8.
 ///
 /// This is “lossy”: invalid UTF-8 percent-encoded byte sequences
 /// will be replaced � U+FFFD, the replacement character.
-#[inline]
 pub fn lossy_utf8_percent_decode(input: &[u8]) -> String {
-    String::from_utf8_lossy(&percent_decode(input)).to_string()
+    let bytes = percent_decode(input).collect::<Vec<u8>>();
+    match String::from_utf8_lossy(&bytes) {
+        Cow::Owned(s) => return s,
+        Cow::Borrowed(_) => {}
+    }
+    unsafe {
+        String::from_utf8_unchecked(bytes)
+    }
 }