Parcourir la source

Make rust-encoding an optional feature.

Simon Sapin il y a 12 ans
Parent
commit
9ae1fd2fc3
6 fichiers modifiés avec 199 ajouts et 70 suppressions
  1. 5 1
      .travis.yml
  2. 5 0
      Cargo.toml
  3. 103 0
      src/encoding.rs
  4. 57 46
      src/form_urlencoded.rs
  5. 26 12
      src/lib.rs
  6. 3 11
      src/parser.rs

+ 5 - 1
.travis.yml

@@ -1,9 +1,13 @@
 language: rust
 
+script:
+  - cargo test --features query_encoding
+  - cargo test
+
 after_success: |
   [ $TRAVIS_BRANCH = master ] &&
   [ $TRAVIS_PULL_REQUEST = false ] &&
-  cargo doc &&
+  cargo doc --features query_encoding &&
   echo '<meta http-equiv=refresh content=0;url=url/index.html>' > target/doc/index.html &&
   cp github.png target/doc/ &&
   sudo pip install ghp-import &&

+ 5 - 0
Cargo.toml

@@ -4,6 +4,11 @@ name = "url"
 version = "0.1.0"
 authors = [ "Simon Sapin <simon.sapin@exyr.org>" ]
 
+[features]
+
+query_encoding = ["encoding"]
+
 [dependencies.encoding]
 
 git = "https://github.com/lifthrasiir/rust-encoding"
+optional = true

+ 103 - 0
src/encoding.rs

@@ -0,0 +1,103 @@
+// Copyright 2013-2014 Simon Sapin.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+
+//! Abstraction that conditionally compiles either to rust-encoding,
+//! or to only support UTF-8.
+
+#[cfg(feature = "query_encoding")]
+extern crate encoding;
+
+#[cfg(feature = "query_encoding")]
+use self::encoding::types::{DecoderTrap, EncoderTrap};
+
+#[cfg(feature = "query_encoding")]
+use self::encoding::label::encoding_from_whatwg_label;
+
+#[cfg(feature = "query_encoding")]
+pub use self::encoding::types::EncodingRef;
+
+
+#[cfg(feature = "query_encoding")]
+pub struct EncodingOverride {
+    /// `None` means UTF-8.
+    encoding: Option<EncodingRef>
+}
+
+#[cfg(feature = "query_encoding")]
+impl EncodingOverride {
+    pub fn from_opt_encoding(encoding: Option<EncodingRef>) -> EncodingOverride {
+        encoding.map(EncodingOverride::from_encoding).unwrap_or_else(EncodingOverride::utf8)
+    }
+
+    pub fn from_encoding(encoding: EncodingRef) -> EncodingOverride {
+        EncodingOverride {
+            encoding: if encoding.name() == "utf-8" { None } else { Some(encoding) }
+        }
+    }
+
+    pub fn utf8() -> EncodingOverride {
+        EncodingOverride { encoding: None }
+    }
+
+    pub fn lookup(label: &[u8]) -> Option<EncodingOverride> {
+        ::std::str::from_utf8(label.as_slice())
+        .and_then(encoding_from_whatwg_label)
+        .map(EncodingOverride::from_encoding)
+    }
+
+    pub fn is_utf8(&self) -> bool {
+        self.encoding.is_none()
+    }
+
+    pub fn decode(&self, input: &[u8]) -> String {
+        match self.encoding {
+            Some(encoding) => encoding.decode(input, DecoderTrap::Replace).unwrap(),
+            None => String::from_utf8_lossy(input).into_string(),
+        }
+    }
+
+    pub fn encode<'a>(&self, pair: &'a mut (&str, Vec<u8>)) -> &'a [u8] {
+        let &(ref input, ref mut tmp) = pair;
+        match self.encoding {
+            Some(encoding) => {
+                *tmp = encoding.encode(input.as_slice(), EncoderTrap::NcrEscape).unwrap();
+                tmp.as_slice()
+            },
+            None => input.as_bytes()  // UTF-8
+        }
+    }
+}
+
+
+#[cfg(not(feature = "query_encoding"))]
+pub struct EncodingOverride;
+
+#[cfg(not(feature = "query_encoding"))]
+impl EncodingOverride {
+    pub fn utf8() -> EncodingOverride {
+        EncodingOverride
+    }
+
+    pub fn lookup(_label: &[u8]) -> Option<EncodingOverride> {
+        None
+    }
+
+    pub fn is_utf8(&self) -> bool {
+        true
+    }
+
+    pub fn decode(&self, input: &[u8]) -> String {
+        String::from_utf8_lossy(input).into_string()
+    }
+
+    pub fn encode<'a>(&self, pair: &'a mut (&str, Vec<u8>)) -> &'a [u8] {
+        let &(ref query, _) = pair;
+        query.as_bytes()
+    }
+}

+ 57 - 46
src/form_urlencoded.rs

@@ -13,35 +13,43 @@
 //! Converts between a string (such as an URL’s query string)
 //! and a sequence of (name, value) pairs.
 
-use std::str;
-
-use encoding;
-use encoding::EncodingRef;
-use encoding::all::UTF_8;
-use encoding::label::encoding_from_whatwg_label;
-
+use encoding::EncodingOverride;
 use percent_encoding::{percent_encode_to, percent_decode, FORM_URLENCODED_ENCODE_SET};
 
 
-/// Convert a string in the `application/x-www-form-urlencoded` format
+/// Convert a byte string in the `application/x-www-form-urlencoded` format
 /// into a vector of (name, value) pairs.
+///
+/// Use `parse(input.as_bytes())` to parse a `&str` string.
 #[inline]
-pub fn parse_str(input: &str) -> Vec<(String, String)> {
-    parse_bytes(input.as_bytes(), None, false).unwrap()
+pub fn parse(input: &[u8]) -> Vec<(String, String)> {
+    parse_internal(input, EncodingOverride::utf8(), false).unwrap()
 }
 
 
 /// Convert a byte string in the `application/x-www-form-urlencoded` format
 /// into a vector of (name, value) pairs.
 ///
+/// Use `parse(input.as_bytes())` to parse a `&str` string.
+///
+/// This function is only available if the `query_encoding` Cargo feature is enabled.
+///
 /// Arguments:
 ///
 /// * `encoding_override`: The character encoding each name and values is decoded as
 ///    after percent-decoding. Defaults to UTF-8.
 /// * `use_charset`: The *use _charset_ flag*. If in doubt, set to `false`.
-pub fn parse_bytes(input: &[u8], encoding_override: Option<EncodingRef>,
-                   mut use_charset: bool) -> Option<Vec<(String, String)>> {
-    let mut encoding_override = encoding_override.unwrap_or(UTF_8 as EncodingRef);
+#[cfg(feature = "query_encoding")]
+#[inline]
+pub fn parse_with_encoding(input: &[u8], encoding_override: Option<::encoding::EncodingRef>,
+                           use_charset: bool)
+                           -> Option<Vec<(String, String)>> {
+    parse_internal(input, EncodingOverride::from_opt_encoding(encoding_override), use_charset)
+}
+
+
+fn parse_internal(input: &[u8], mut encoding_override: EncodingOverride, mut use_charset: bool)
+                  -> Option<Vec<(String, String)>> {
     let mut pairs = Vec::new();
     for piece in input.split(|&b| b == b'&') {
         if !piece.is_empty() {
@@ -49,10 +57,16 @@ pub fn parse_bytes(input: &[u8], encoding_override: Option<EncodingRef>,
                 Some(position) => (piece.slice_to(position), piece.slice_from(position + 1)),
                 None => (piece, [].as_slice())
             };
+
+            #[inline]
+            fn replace_plus(input: &[u8]) -> Vec<u8> {
+                input.iter().map(|&b| if b == b'+' { b' ' } else { b }).collect()
+            }
+
             let name = replace_plus(name);
             let value = replace_plus(value);
             if use_charset && name.as_slice() == b"_charset_" {
-                match str::from_utf8(value.as_slice()).and_then(encoding_from_whatwg_label) {
+                match EncodingOverride::lookup(value.as_slice()) {
                     Some(encoding) => encoding_override = encoding,
                     None => (),
                 }
@@ -61,25 +75,14 @@ pub fn parse_bytes(input: &[u8], encoding_override: Option<EncodingRef>,
             pairs.push((name, value));
         }
     }
-    if encoding_override.name() != "utf-8" && !input.is_ascii() {
+    if !(encoding_override.is_utf8() || input.is_ascii()) {
         return None
     }
 
-    #[inline]
-    fn replace_plus(input: &[u8]) -> Vec<u8> {
-        input.iter().map(|&b| if b == b'+' { b' ' } else { b }).collect()
-    }
-
-    #[inline]
-    fn decode(input: Vec<u8>, encoding_override: EncodingRef) -> String {
-        encoding_override.decode(
-            percent_decode(input.as_slice()).as_slice(),
-            encoding::DecoderTrap::Replace).unwrap()
-    }
-
-    Some(pairs.into_iter().map(
-        |(name, value)| (decode(name, encoding_override), decode(value, encoding_override))
-    ).collect())
+    Some(pairs.into_iter().map(|(name, value)| (
+        encoding_override.decode(percent_decode(name.as_slice()).as_slice()),
+        encoding_override.decode(percent_decode(value.as_slice()).as_slice())
+    )).collect())
 }
 
 
@@ -87,33 +90,41 @@ pub fn parse_bytes(input: &[u8], encoding_override: Option<EncodingRef>,
 /// into a string in the `application/x-www-form-urlencoded` format.
 #[inline]
 pub fn serialize_owned(pairs: &[(String, String)]) -> String {
-    serialize(pairs.iter().map(|&(ref n, ref v)| (n.as_slice(), v.as_slice())), None)
+    serialize(pairs.iter().map(|&(ref n, ref v)| (n.as_slice(), v.as_slice())))
 }
 
 
 /// Convert an iterator of (name, value) pairs
 /// into a string in the `application/x-www-form-urlencoded` format.
+#[inline]
+pub fn serialize<'a, I>(pairs: I) -> String where I: Iterator<(&'a str, &'a str)> {
+    serialize_internal(pairs, EncodingOverride::utf8())
+}
+
+/// 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.
-pub fn serialize<'a, I: Iterator<(&'a str, &'a str)>>(
-        mut pairs: I, encoding_override: Option<EncodingRef>)
-        -> String {
+#[cfg(feature = "query_encoding")]
+#[inline]
+pub fn serialize_with_encoding<'a, I>(pairs: I, encoding_override: Option<::encoding::EncodingRef>)
+                                      -> String
+                                      where I: Iterator<(&'a str, &'a str)> {
+    serialize_internal(pairs, EncodingOverride::from_opt_encoding(encoding_override))
+}
+
+fn serialize_internal<'a, I>(mut pairs: I, encoding_override: EncodingOverride) -> String
+                             where I: Iterator<(&'a str, &'a str)> {
     #[inline]
     fn byte_serialize(input: &str, output: &mut String,
-                      encoding_override: Option<EncodingRef>) {
-        let keep_alive;
-        let input = match encoding_override {
-            None => input.as_bytes(),  // "Encode" to UTF-8
-            Some(encoding) => {
-                keep_alive = encoding.encode(input, encoding::EncoderTrap::NcrEscape).unwrap();
-                keep_alive.as_slice()
-            }
-        };
-
-        for &byte in input.iter() {
+                      encoding_override: EncodingOverride) {
+        let mut pair = (input, vec![]);
+        for &byte in encoding_override.encode(&mut pair).iter() {
             if byte == b' ' {
                 output.push_str("+")
             } else {
@@ -144,5 +155,5 @@ fn test_form_urlencoded() {
     ];
     let encoded = serialize_owned(pairs.as_slice());
     assert_eq!(encoded.as_slice(), "foo=%C3%A9%26&bar=&foo=%23");
-    assert_eq!(parse_str(encoded.as_slice()), pairs.as_slice().to_vec());
+    assert_eq!(parse(encoded.as_bytes()), pairs.as_slice().to_vec());
 }

+ 26 - 12
src/lib.rs

@@ -22,9 +22,6 @@ To use it in your project, add this to your `Cargo.toml` file:
 git = "https://github.com/servo/rust-url"
 ```
 
-This will automatically pull in the
-[rust-encoding](https://github.com/lifthrasiir/rust-encoding) dependency.
-
 rust-url is a replacement of the [`url` crate](http://doc.rust-lang.org/url/index.html)
 currently distributed with Rust.
 rust-url’s crate is also named `url`.
@@ -34,6 +31,20 @@ but that means that you can not also use the old `url` in the same crate.
 If you’re not using Cargo, you’ll need to pass `--extern url=/path/to/liburl.rlib`
 explicitly to rustc.
 
+Supporting encodings other than UTF-8 in query strings is an optional feature
+that requires [rust-encoding](https://github.com/lifthrasiir/rust-encoding)
+and is off by default.
+You can enable it with
+[Cargo’s *features* mechanism](http://doc.crates.io/manifest.html#the-[features]-section):
+
+```Cargo
+[dependencies.url]
+git = "https://github.com/servo/rust-url"
+features = ["query_encoding"]
+```
+
+… or by passing `--cfg 'feature="query_encoding"'` to rustc.
+
 
 # URL parsing and data structures
 
@@ -119,15 +130,12 @@ assert!(css_url.serialize() == "http://servo.github.io/rust-url/main.css".to_str
 
 #![feature(macro_rules, default_type_params)]
 
-extern crate encoding;
 extern crate serialize;
 
 use std::fmt::{mod, Formatter, Show};
 use std::hash;
 use std::path;
 
-use encoding::EncodingRef;
-
 pub use host::{Host, Ipv6Address};
 pub use parser::{ErrorHandler, ParseResult, ParseError};
 
@@ -140,7 +148,9 @@ pub use percent_encoding::{
 };
 
 use format::{PathFormatter, UserInfoFormatter, UrlNoFragmentFormatter};
+use encoding::EncodingOverride;
 
+mod encoding;
 mod host;
 mod parser;
 mod urlutils;
@@ -248,7 +258,7 @@ impl<S: hash::Writer> hash::Hash<S> for Url {
 /// A set of optional parameters for URL parsing.
 pub struct UrlParser<'a> {
     base_url: Option<&'a Url>,
-    query_encoding_override: Option<EncodingRef>,
+    query_encoding_override: EncodingOverride,
     error_handler: ErrorHandler,
     scheme_type_mapper: fn(scheme: &str) -> SchemeType,
 }
@@ -262,7 +272,7 @@ impl<'a> UrlParser<'a> {
         fn silent_handler(_reason: ParseError) -> ParseResult<()> { Ok(()) }
         UrlParser {
             base_url: None,
-            query_encoding_override: None,
+            query_encoding_override: EncodingOverride::utf8(),
             error_handler: silent_handler,
             scheme_type_mapper: whatwg_scheme_type_mapper,
         }
@@ -280,9 +290,13 @@ impl<'a> UrlParser<'a> {
     /// and return the `UrlParser`.
     ///
     /// This legacy quirk is only relevant to HTML.
+    ///
+    /// This method is only available if the `query_encoding` Cargo feature is enabled.
+    #[cfg(feature = "query_encoding")]
     #[inline]
-    pub fn query_encoding_override<'b>(&'b mut self, value: EncodingRef) -> &'b mut UrlParser<'a> {
-        self.query_encoding_override = Some(value);
+    pub fn query_encoding_override<'b>(&'b mut self, value: encoding::EncodingRef)
+                                       -> &'b mut UrlParser<'a> {
+        self.query_encoding_override = EncodingOverride::from_encoding(value);
         self
     }
 
@@ -715,14 +729,14 @@ impl Url {
     /// and return a vector of (key, value) pairs.
     #[inline]
     pub fn query_pairs(&self) -> Option<Vec<(String, String)>> {
-        self.query.as_ref().map(|query| form_urlencoded::parse_str(query.as_slice()))
+        self.query.as_ref().map(|query| form_urlencoded::parse(query.as_bytes()))
     }
 
     /// Serialize an iterator of (key, value) pairs as `application/x-www-form-urlencoded`
     /// and set it as the URL’s query string.
     #[inline]
     pub fn set_query_from_pairs<'a, I: Iterator<(&'a str, &'a str)>>(&mut self, pairs: I) {
-        self.query = Some(form_urlencoded::serialize(pairs, None));
+        self.query = Some(form_urlencoded::serialize(pairs));
     }
 
     /// Percent-decode the URL’s query string, if any.

+ 3 - 11
src/parser.rs

@@ -11,8 +11,6 @@ use std::error::Error;
 use std::fmt::{mod, Formatter, Show};
 use std::str::CharRange;
 
-use encoding;
-
 use super::{UrlParser, Url, SchemeData, RelativeSchemeData, Host, SchemeType};
 use percent_encoding::{
     utf8_percent_encode_to, percent_encode,
@@ -645,15 +643,9 @@ pub fn parse_query<'a>(input: &'a str, context: Context, parser: &UrlParser)
             }
         }
     }
-    let encoded;
-    let query_bytes = match parser.query_encoding_override {
-        Some(encoding) => {
-            encoded = encoding.encode(query.as_slice(), encoding::EncoderTrap::Replace).unwrap();
-            encoded.as_slice()
-        },
-        None => query.as_bytes()  // UTF-8
-    };
-    ;
+
+    let mut pair = (query.as_slice(), vec![]);
+    let query_bytes = parser.query_encoding_override.encode(&mut pair);
     Ok((percent_encode(query_bytes.as_slice(), QUERY_ENCODE_SET), remaining))
 }