Przeglądaj źródła

ASCII turtles all the way down.

Simon Sapin 12 lat temu
rodzic
commit
d56b2fbb12
1 zmienionych plików z 100 dodań i 79 usunięć
  1. 100 79
      url.rs

+ 100 - 79
url.rs

@@ -16,6 +16,8 @@ extern mod encoding;
 #[cfg(test)]
 extern mod extra;
 
+use std::ascii::Ascii;
+
 use encoding::Encoding;
 use encoding::all::UTF_8;
 use encoding::label::encoding_from_whatwg_label;
@@ -25,31 +27,31 @@ pub mod punycode;
 
 
 pub struct URL {
-    scheme: ~str,
+    scheme: ~[Ascii],
     scheme_data: SchemeData,
-    query: Option<~str>,  // parse_form_urlencoded() parses this into ~[(~str, ~str)]
-    fragment: Option<~str>,
+    query: Option<~[Ascii]>,  // parse_form_urlencoded() parses this into ~[(~str, ~str)]
+    fragment: Option<~[Ascii]>,
 }
 
 pub enum SchemeData {
     RelativeSchemeData(SchemeRelativeURL),
-    OtherSchemeData(~str)
+    OtherSchemeData(~[Ascii])
 }
 
 pub struct SchemeRelativeURL {
     userinfo: Option<UserInfo>,
     host: Host,
-    port: ~str,
-    path: ~[~str],
+    port: ~[Ascii],
+    path: ~[~[Ascii]],
 }
 
 pub struct UserInfo {
-    username: ~str,
-    password: Option<~str>,
+    username: ~[Ascii],
+    password: Option<~[Ascii]>,
 }
 
 pub enum Host {
-    Domain(~[~str]),
+    Domain(~[~[Ascii]]),
     IPv6(IPv6Address)
 }
 
@@ -70,62 +72,62 @@ impl URL {
         None
     }
 
-    pub fn serialize(&self) -> ~str {
+    pub fn serialize(&self) -> ~[Ascii] {
         let mut result = self.serialize_no_fragment();
         match self.fragment {
             None => (),
             Some(ref fragment) => {
-                result.push_str("#");
-                result.push_str(fragment.as_slice());
+                result.push('#'.to_ascii());
+                result.push_all(fragment.as_slice());
             }
         }
         result
     }
 
-    pub fn serialize_no_fragment(&self) -> ~str {
+    pub fn serialize_no_fragment(&self) -> ~[Ascii] {
         let mut result = self.scheme.to_owned();
-        result.push_str(":");
+        result.push(':'.to_ascii());
         match self.scheme_data {
             RelativeSchemeData(SchemeRelativeURL {
                 userinfo: ref userinfo, host: ref host, port: ref port, path: ref path
             }) => {
-                result.push_str("//");
+                result.push_all("//".to_ascii());
                 match userinfo {
                     &None => (),
                     &Some(UserInfo { username: ref username, password: ref password })
                     => if username.len() > 0 || password.is_some() {
-                        result.push_str(username.as_slice());
+                        result.push_all(username.as_slice());
                         match password {
                             &None => (),
                             &Some(ref password) => {
-                                result.push_str(":");
-                                result.push_str(password.as_slice());
+                                result.push(':'.to_ascii());
+                                result.push_all(password.as_slice());
                             }
                         }
-                        result.push_str("@");
+                        result.push('@'.to_ascii());
                     }
                 }
-                result.push_str(host.serialize());
+                result.push_all(host.serialize());
                 if port.len() > 0 {
-                    result.push_str(":");
-                    result.push_str(port.as_slice());
+                    result.push(':'.to_ascii());
+                    result.push_all(port.as_slice());
                 }
                 if path.len() > 0 {
                     for path_part in path.iter() {
-                        result.push_str("/");
-                        result.push_str(path_part.as_slice());
+                        result.push('/'.to_ascii());
+                        result.push_all(path_part.as_slice());
                     }
                 } else {
-                    result.push_str("/");
+                    result.push('/'.to_ascii());
                 }
             },
-            OtherSchemeData(ref data) => result.push_str(data.as_slice()),
+            OtherSchemeData(ref data) => result.push_all(data.as_slice()),
         }
         match self.query {
             None => (),
             Some(ref query) => {
-                result.push_str("?");
-                result.push_str(query.as_slice());
+                result.push('?'.to_ascii());
+                result.push_all(query.as_slice());
             }
         }
         result
@@ -147,7 +149,7 @@ impl Host {
                 Err("Invalid IPv6 address")
             }
         } else {
-            let mut percent_encoded = ~"";
+            let mut percent_encoded = ~[];
             utf8_percent_encode(input, SimpleEncodeSet, &mut percent_encoded);
             let bytes = percent_decode(percent_encoded);
             let decoded = UTF_8.decode(bytes, encoding::DecodeReplace).unwrap();
@@ -155,19 +157,25 @@ impl Host {
             for label in decoded.split_iter(&['.', '\u3002', '\uFF0E', '\uFF61']) {
                 // TODO: Remove this check and use IDNA "domain to ASCII"
                 // TODO: switch to .map(domain_label_to_ascii).collect() then.
-                if label.as_bytes().iter().any(|b| *b >= 0x80) {
+                if label.is_ascii() {
+                    labels.push(unsafe { label.to_ascii_nocheck() }.to_owned())
+                } else {
                     return Err("Non-ASCII domains (IDNA) are not supported yet.")
                 }
-                labels.push(label.to_owned())
             }
             Ok(Domain(labels))
         }
     }
 
-    pub fn serialize(&self) -> ~str {
+    pub fn serialize(&self) -> ~[Ascii] {
         match *self {
-            Domain(ref labels) => labels.connect("."),
-            IPv6(ref address) => format!("[{}]", address.serialize()),
+            Domain(ref labels) => labels.connect_vec(&'.'.to_ascii()),
+            IPv6(ref address) => {
+                let mut result = ~['['.to_ascii()];
+                result.push_all(address.serialize());
+                result.push(']'.to_ascii());
+                result
+            }
         }
     }
 }
@@ -216,7 +224,7 @@ impl IPv6Address {
             let end = len.min(&(start + 4));
             let mut value = 0u16;
             while i < end {
-                match byte_to_hex(input[i]) {
+                match from_hex(input[i]) {
                     Some(digit) => {
                         value = value * 0x10 + digit as u16;
                         i += 1;
@@ -298,22 +306,27 @@ impl IPv6Address {
         Some(IPv6Address { pieces: pieces })
     }
 
-    pub fn serialize(&self) -> ~str {
-        let mut output = ~"";
+    pub fn serialize(&self) -> ~[Ascii] {
+        let mut output = ~[];
         let (compress_start, compress_end) = longest_zero_sequence(&self.pieces);
         let mut i = 0;
         while i < 8 {
             if i == compress_start {
-                output.push_str(if i == 0 { "::" } else { ":" });
+                output.push(':'.to_ascii());
+                if i == 0 {
+                    output.push(':'.to_ascii());
+                }
                 if compress_end < 8 {
                     i = compress_end;
                 } else {
                     break;
                 }
             }
-            output.push_str(self.pieces[i].to_str_radix(16));
+            let hex = self.pieces[i].to_str_radix(16);
+            // No need to check that hex digits are ASCII
+            output.push_all(unsafe { hex.to_ascii_nocheck() });
             if i < 7 {
-                output.push_str(":");
+                output.push(':'.to_ascii());
             }
         }
         output
@@ -352,7 +365,7 @@ fn longest_zero_sequence(pieces: &[u16, ..8]) -> (int, int) {
 
 
 #[inline]
-fn byte_to_hex(byte: u8) -> Option<u8> {
+fn from_hex(byte: u8) -> Option<u8> {
     match byte {
         0x30 .. 0x39 => Some(byte - 0x30),  // 0..9
         0x41 .. 0x46 => Some(byte + 10 - 0x41),  // A..F
@@ -361,6 +374,16 @@ fn byte_to_hex(byte: u8) -> Option<u8> {
     }
 }
 
+#[inline]
+fn to_hex_upper(value: u8) -> Ascii {
+    let digit = match value {
+        0 .. 9 => value + 0x30,
+        10 .. 15 => value - 10 + 0x41,
+        _ => fail!()
+    };
+    unsafe { digit.to_ascii_nocheck() }
+}
+
 
 enum EncodeSet {
     SimpleEncodeSet,
@@ -371,7 +394,7 @@ enum EncodeSet {
 
 
 #[inline]
-fn utf8_percent_encode(input: &str, encode_set: EncodeSet, output: &mut ~str) {
+fn utf8_percent_encode(input: &str, encode_set: EncodeSet, output: &mut ~[Ascii]) {
     use Default = self::DefaultEncodeSet;
     use Password = self::PasswordEncodeSet;
     use Username = self::UsernameEncodeSet;
@@ -385,30 +408,29 @@ fn utf8_percent_encode(input: &str, encode_set: EncodeSet, output: &mut ~str) {
             => match encode_set { Username => true, _ => false },
             _ => false,
         } {
-            output.push_str(percent_encode_byte(byte))
+            percent_encode_byte(byte, output)
         } else {
-            use std::str::raw::push_byte;
-            unsafe { push_byte(output, byte) }
+            output.push(unsafe { byte.to_ascii_nocheck() })  // Already checked
         }
     }
 }
 
 
 #[inline]
-fn percent_encode_byte(byte: u8) -> ~str {
-    format!("%{:02X}", byte)
+fn percent_encode_byte(byte: u8, output: &mut ~[Ascii]) {
+    output.push_all(['%'.to_ascii(), to_hex_upper(byte >> 4), to_hex_upper(byte & 0x0F)])
 }
 
 
-/// Fails on non-ASCII input
 #[inline]
-fn percent_decode(input: &str) -> ~[u8] {
+fn percent_decode(input: &[Ascii]) -> ~[u8] {
     let mut output = ~[];
     let mut i = 0u;
     while i < input.len() {
         let c = input[i];
-        if c == '%' as u8 && i + 2 < input.len() {
-            match (byte_to_hex(input[i + 1]), byte_to_hex(input[i + 2])) {
+        if c == '%'.to_ascii() && i + 2 < input.len() {
+            match (from_hex(input[i + 1].to_byte()),
+                   from_hex(input[i + 2].to_byte())) {
                 (Some(h), Some(l)) => {
                     output.push(h * 0x10 + l);
                     i += 3;
@@ -418,15 +440,14 @@ fn percent_decode(input: &str) -> ~[u8] {
             }
         }
 
-        assert!(c < 0x80);
-        output.push(c);
+        output.push(c.to_byte());
         i += 1;
     }
     output
 }
 
 
-pub fn parse_form_urlencoded(input: &str,
+pub fn parse_form_urlencoded(input: &[Ascii],
                              encoding_override: Option<&'static Encoding>,
                              use_charset: bool,
                              mut isindex: bool)
@@ -436,14 +457,14 @@ pub fn parse_form_urlencoded(input: &str,
         None => UTF_8 as &'static Encoding,
     };
     let mut pairs = ~[];
-    for string in input.split_iter('&') {
+    for string in input.split_iter(|&c| c == '&'.to_ascii()) {
         if string.len() > 0 {
-            let (name, value) = match string.find('=') {
+            let (name, value) = match string.position_elem(&'='.to_ascii()) {
                 Some(position) => (string.slice_to(position), string.slice_from(position + 1)),
-                None => if isindex { ("", string) } else { (string, "") }
+                None => if isindex { (&[], string) } else { (string, &[]) }
             };
-            let name = name.replace("+", " ");
-            let value = value.replace("+", " ");
+            let name = name.to_str_ascii().replace("+", " ");
+            let value = value.to_str_ascii().replace("+", " ");
             if use_charset && name.as_slice() == "_charset_" {
                 match encoding_from_whatwg_label(value) {
                     Some(encoding) => encoding_override = encoding,
@@ -457,7 +478,8 @@ pub fn parse_form_urlencoded(input: &str,
 
     #[inline]
     fn decode(input: &~str, encoding_override: &'static Encoding) -> ~str {
-        let bytes = percent_decode(input.as_slice());
+        // No need to check as input comes from &[Ascii].to_str_ascii().replace("+", " ")
+        let bytes = percent_decode(unsafe { input.as_slice().to_ascii_nocheck() });
         encoding_override.decode(bytes, encoding::DecodeReplace).unwrap()
     }
 
@@ -473,13 +495,11 @@ pub fn parse_form_urlencoded(input: &str,
 
 
 pub fn serialize_form_urlencoded(pairs: ~[(~str, ~str)],
-                                 encoding_override: Option<&'static Encoding>) {
-
+                                 encoding_override: Option<&'static Encoding>)
+                              -> ~[Ascii] {
     #[inline]
-    fn byte_serialize(input: &str, output: &mut ~str,
+    fn byte_serialize(input: &str, output: &mut ~[Ascii],
                      encoding_override: Option<&'static Encoding>) {
-        use std::cast::transmute;
-
         let keep_alive;
         let input = match encoding_override {
             None => input.as_bytes(),  // "Encode" to UTF-8
@@ -491,24 +511,25 @@ pub fn serialize_form_urlencoded(pairs: ~[(~str, ~str)],
 
         for byte in input.iter() {
             match *byte {
-                0x20 => output.push_str("+"),
+                0x20 => output.push('+'.to_ascii()),
                 0x2A | 0x2D | 0x2E | 0x30 .. 0x39 | 0x41 .. 0x5A | 0x5F | 0x61 .. 0x7A
-                => output.push_str(unsafe { transmute(&[*byte]) }),
-                _ => output.push_str(percent_encode_byte(*byte)),
+                => output.push(unsafe { byte.to_ascii_nocheck() }),
+                _ => percent_encode_byte(*byte, output),
             }
         }
     }
 
-    let mut output = ~"";
+    let mut output = ~[];
     for &(ref name, ref value) in pairs.iter() {
         // TODO: add an encoding_override parameter and support other encodings.
         if output.len() > 0 {
-            output.push_str("&");
+            output.push('&'.to_ascii());
             byte_serialize(name.as_slice(), &mut output, encoding_override);
-            output.push_str("=");
+            output.push('='.to_ascii());
             byte_serialize(value.as_slice(), &mut output, encoding_override);
         }
     }
+    output
 }
 
 
@@ -545,32 +566,32 @@ mod tests {
                 fragment: fragment
             } = url.unwrap();
 
-            assert_eq!(Some(scheme), expected_scheme);
+            assert_eq!(Some(scheme.to_str_ascii()), expected_scheme);
             match scheme_data {
                 RelativeSchemeData(SchemeRelativeURL {
                     userinfo: userinfo, host: host, port: port, path: path
                 }) => {
                     let (username, password) = match userinfo {
                         Some(UserInfo { username: username, password: password })
-                        => (Some(username), password),
+                        => (Some(username.to_str_ascii()), password.map(|p| p.to_str_ascii())),
                         _ => (None, None),
                     };
                     assert_eq!(username, expected_username);
                     assert_eq!(password, expected_password);
-                    assert_eq!(Some(host.serialize()), expected_host)
-                    assert_eq!(Some(port), expected_port);
-                    assert_eq!(Some(path.connect("/")), expected_path);
+                    assert_eq!(Some(host.serialize().to_str_ascii()), expected_host)
+                    assert_eq!(Some(port.to_str_ascii()), expected_port);
+                    assert_eq!(Some(path.map(|p| p.to_str_ascii()).connect("/")), expected_path);
                 },
                 OtherSchemeData(scheme_data) => {
-                    assert_eq!(Some(scheme_data), expected_path);
+                    assert_eq!(Some(scheme_data.to_str_ascii()), expected_path);
                     assert_eq!(None, expected_username);
                     assert_eq!(None, expected_password);
                     assert_eq!(None, expected_host);
                     assert_eq!(None, expected_port);
                 },
             }
-            assert_eq!(query, expected_query);
-            assert_eq!(fragment, expected_fragment);
+            assert_eq!(query.map(|p| p.to_str_ascii()), expected_query);
+            assert_eq!(fragment.map(|p| p.to_str_ascii()), expected_fragment);
         }
     }