Browse Source

Make percent-encoding an iterator.

Simon Sapin 10 years ago
parent
commit
a3210b9b90
4 changed files with 68 additions and 52 deletions
  1. 2 2
      src/form_urlencoded.rs
  2. 4 3
      src/lib.rs
  3. 9 9
      src/parser.rs
  4. 53 38
      src/percent_encoding.rs

+ 2 - 2
src/form_urlencoded.rs

@@ -16,7 +16,7 @@
 use std::borrow::Borrow;
 use std::ascii::AsciiExt;
 use encoding::EncodingOverride;
-use percent_encoding::{percent_encode_to, percent_decode, FORM_URLENCODED_ENCODE_SET};
+use percent_encoding::{percent_encode, percent_decode, FORM_URLENCODED_ENCODE_SET};
 
 
 /// Convert a byte string in the `application/x-www-form-urlencoded` format
@@ -125,7 +125,7 @@ where I: IntoIterator, I::Item: Borrow<(K, V)>, K: AsRef<str>, V: AsRef<str> {
             if byte == b' ' {
                 output.push_str("+")
             } else {
-                percent_encode_to(&[byte], FORM_URLENCODED_ENCODE_SET, output)
+                output.extend(percent_encode(&[byte], FORM_URLENCODED_ENCODE_SET))
             }
         }
     }

+ 4 - 3
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, percent_decode};
+use percent_encoding::{PATH_SEGMENT_ENCODE_SET, percent_encode, percent_decode};
 use std::cmp;
 use std::fmt;
 use std::hash;
@@ -585,7 +585,8 @@ fn path_to_file_url_segments(path: &Path, serialization: &mut String) -> Result<
     // skip the root component
     for component in path.components().skip(1) {
         serialization.push('/');
-        percent_encode_to(component.as_os_str().as_bytes(), PATH_SEGMENT_ENCODE_SET, serialization)
+        serialization.extend(percent_encode(
+            component.as_os_str().as_bytes(), PATH_SEGMENT_ENCODE_SET))
     }
     Ok(())
 }
@@ -624,7 +625,7 @@ fn path_to_file_url_segments_windows(path: &Path, serialization: &mut String) ->
         // FIXME: somehow work with non-unicode?
         let component = try!(component.as_os_str().to_str().ok_or(()));
         serialization.push('/');
-        percent_encode_to(component.as_bytes(), PATH_SEGMENT_ENCODE_SET, serialization);
+        serialization.extend(percent_encode(component.as_bytes(), PATH_SEGMENT_ENCODE_SET));
     }
     Ok(())
 }

+ 9 - 9
src/parser.rs

@@ -13,7 +13,7 @@ use std::fmt::{self, Formatter, Write};
 use super::{Url, EncodingOverride};
 use host::{self, HostInternal};
 use percent_encoding::{
-    utf8_percent_encode_to, percent_encode_to,
+    utf8_percent_encode, percent_encode,
     SIMPLE_ENCODE_SET, DEFAULT_ENCODE_SET, USERINFO_ENCODE_SET, QUERY_ENCODE_SET
 };
 
@@ -608,7 +608,7 @@ impl<'a> Parser<'a> {
                 _ => {
                     self.check_url_code_point(input, i, c);
                     let utf8_c = &input[i..next_i];
-                    utf8_percent_encode_to(utf8_c, USERINFO_ENCODE_SET, &mut self.serialization);
+                    self.serialization.extend(utf8_percent_encode(utf8_c, USERINFO_ENCODE_SET));
                 }
             }
         }
@@ -798,8 +798,8 @@ impl<'a> Parser<'a> {
                     '\t' | '\n' | '\r' => self.syntax_violation("invalid characters"),
                     _ => {
                         self.check_url_code_point(input, i, c);
-                        utf8_percent_encode_to(
-                            &input[i..next_i], DEFAULT_ENCODE_SET, &mut self.serialization);
+                        self.serialization.extend(utf8_percent_encode(
+                            &input[i..next_i], DEFAULT_ENCODE_SET));
                     }
                 }
             }
@@ -865,8 +865,8 @@ impl<'a> Parser<'a> {
                 '\t' | '\n' | '\r' => self.syntax_violation("invalid character"),
                 _ => {
                     self.check_url_code_point(input, i, c);
-                    utf8_percent_encode_to(
-                        &input[i..next_i], SIMPLE_ENCODE_SET, &mut self.serialization);
+                    self.serialization.extend(utf8_percent_encode(
+                        &input[i..next_i], SIMPLE_ENCODE_SET));
                 }
             }
         }
@@ -945,7 +945,7 @@ impl<'a> Parser<'a> {
             _ => EncodingOverride::utf8(),
         };
         let query_bytes = encoding.encode(&query);
-        percent_encode_to(&query_bytes, QUERY_ENCODE_SET, &mut self.serialization);
+        self.serialization.extend(percent_encode(&query_bytes, QUERY_ENCODE_SET));
         remaining
     }
 
@@ -973,8 +973,8 @@ impl<'a> Parser<'a> {
                 '\0' | '\t' | '\n' | '\r' => self.syntax_violation("invalid character"),
                 _ => {
                     self.check_url_code_point(input, i, c);
-                    utf8_percent_encode_to(
-                        &input[i..next_i], SIMPLE_ENCODE_SET, &mut self.serialization);
+                    self.serialization.extend(utf8_percent_encode(
+                        &input[i..next_i], SIMPLE_ENCODE_SET));
                 }
             }
         }

+ 53 - 38
src/percent_encoding.rs

@@ -8,7 +8,6 @@
 
 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.
@@ -49,7 +48,7 @@ pub trait EncodeSet {
 ///     pub QUERY_ENCODE_SET = [SIMPLE_ENCODE_SET] | {' ', '"', '#', '<', '>'}
 /// }
 /// # fn main() {
-/// assert_eq!(utf8_percent_encode("foo bar", QUERY_ENCODE_SET), "foo%20bar");
+/// assert_eq!(utf8_percent_encode("foo bar", QUERY_ENCODE_SET).collect::<String>(), "foo%20bar");
 /// # }
 /// ```
 #[macro_export]
@@ -116,54 +115,70 @@ define_encode_set! {
     }
 }
 
-/// Percent-encode the given bytes, and push the result to `output`.
-///
-/// The pushed strings are within the ASCII range.
+/// Percent-encode the given bytes and return an iterator of `char` in the ASCII range.
 #[inline]
-pub fn percent_encode_to<E: EncodeSet>(input: &[u8], encode_set: E, output: &mut String) {
-    for &byte in input {
-        if encode_set.contains(byte) {
-            write!(output, "%{:02X}", byte).unwrap();
-        } else {
-            assert!(byte.is_ascii());
-            unsafe {
-                output.as_mut_vec().push(byte)
-            }
-        }
+pub fn percent_encode<E: EncodeSet>(input: &[u8], encode_set: E) -> PercentEncode<E> {
+    PercentEncode {
+        iter: input.iter(),
+        encode_set: encode_set,
+        state: PercentEncodeState::NextByte,
     }
 }
 
-
-/// Percent-encode the given bytes.
-///
-/// The returned string is within the ASCII range.
+/// Percent-encode the UTF-8 encoding of the given string
+/// and return an iterator of `char` in the ASCII range.
 #[inline]
-pub fn percent_encode<E: EncodeSet>(input: &[u8], encode_set: E) -> String {
-    let mut output = String::new();
-    percent_encode_to(input, encode_set, &mut output);
-    output
+pub fn utf8_percent_encode<E: EncodeSet>(input: &str, encode_set: E) -> PercentEncode<E> {
+    percent_encode(input.as_bytes(), encode_set)
 }
 
+pub struct PercentEncode<'a, E: EncodeSet> {
+    iter: slice::Iter<'a, u8>,
+    encode_set: E,
+    state: PercentEncodeState,
+}
 
-/// Percent-encode the UTF-8 encoding of the given string, and push the result to `output`.
-///
-/// The pushed strings are within the ASCII range.
-#[inline]
-pub fn utf8_percent_encode_to<E: EncodeSet>(input: &str, encode_set: E, output: &mut String) {
-    percent_encode_to(input.as_bytes(), encode_set, output)
+enum PercentEncodeState {
+    NextByte,
+    HexHigh(u8),
+    HexLow(u8),
 }
 
+impl<'a, E: EncodeSet> Iterator for PercentEncode<'a, E> {
+    type Item = char;
 
-/// Percent-encode the UTF-8 encoding of the given string.
-///
-/// The returned string is within the ASCII range.
-#[inline]
-pub fn utf8_percent_encode<E: EncodeSet>(input: &str, encode_set: E) -> String {
-    let mut output = String::new();
-    utf8_percent_encode_to(input, encode_set, &mut output);
-    output
-}
+    fn next(&mut self) -> Option<char> {
+        // str::char::from_digit always returns lowercase.
+        const UPPER_HEX: [char; 16] = ['0', '1', '2', '3', '4', '5', '6', '7',
+                                       '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'];
+        match self.state {
+            PercentEncodeState::HexHigh(byte) => {
+                self.state = PercentEncodeState::HexLow(byte);
+                Some(UPPER_HEX[(byte >> 4) as usize])
+            }
+            PercentEncodeState::HexLow(byte) => {
+                self.state = PercentEncodeState::NextByte;
+                Some(UPPER_HEX[(byte & 0x0F) as usize])
+            }
+            PercentEncodeState::NextByte => {
+                self.iter.next().map(|&byte| {
+                    if self.encode_set.contains(byte) {
+                        self.state = PercentEncodeState::HexHigh(byte);
+                        '%'
+                    } else {
+                        assert!(byte.is_ascii());
+                        byte as char
+                    }
+                })
+            }
+        }
+    }
 
+    fn size_hint(&self) -> (usize, Option<usize>) {
+        let (low, high) = self.iter.size_hint();
+        (low.saturating_add(2) / 3, high)
+    }
+}
 
 /// Percent-decode the given bytes and return an iterator of bytes.
 #[inline]