Parcourir la source

Move most top-level items into modules.

Simon Sapin il y a 12 ans
Parent
commit
553d003051
6 fichiers modifiés avec 691 ajouts et 634 suppressions
  1. 1 1
      src/form_urlencoded.rs
  2. 291 0
      src/host.rs
  3. 22 628
      src/lib.rs
  4. 68 5
      src/parser.rs
  5. 148 0
      src/percent_encoding.rs
  6. 161 0
      src/urlutils.rs

+ 1 - 1
src/form_urlencoded.rs

@@ -20,7 +20,7 @@ use encoding::EncodingRef;
 use encoding::all::UTF_8;
 use encoding::all::UTF_8;
 use encoding::label::encoding_from_whatwg_label;
 use encoding::label::encoding_from_whatwg_label;
 
 
-use super::{percent_encode_to, percent_decode, FORM_URLENCODED_ENCODE_SET};
+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 string in the `application/x-www-form-urlencoded` format

+ 291 - 0
src/host.rs

@@ -0,0 +1,291 @@
+// 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.
+
+use std::ascii::OwnedStrAsciiExt;
+use std::cmp;
+use std::fmt::{Formatter, FormatError, Show};
+use parser::{
+    ParseResult,
+    InvalidIpv6Address, EmptyHost, NonAsciiDomainsNotSupportedYet, InvalidDomainCharacter,
+};
+use percent_encoding::{from_hex, percent_decode};
+
+
+/// The host name of an URL.
+#[deriving(PartialEq, Eq, Clone)]
+pub enum Host {
+    /// A (DNS) domain name or an IPv4 address.
+    ///
+    /// FIXME: IPv4 probably should be a separate variant.
+    /// See https://www.w3.org/Bugs/Public/show_bug.cgi?id=26431
+    Domain(String),
+
+    /// An IPv6 address, represented inside `[...]` square brackets
+    /// so that `:` colon characters in the address are not ambiguous
+    /// with the port number delimiter.
+    Ipv6(Ipv6Address),
+}
+
+
+/// A 128 bit IPv6 address
+pub struct Ipv6Address {
+    pub pieces: [u16, ..8]
+}
+
+impl Clone for Ipv6Address {
+    fn clone(&self) -> Ipv6Address {
+        Ipv6Address { pieces: self.pieces }
+    }
+}
+
+impl Eq for Ipv6Address {}
+
+impl PartialEq for Ipv6Address {
+    fn eq(&self, other: &Ipv6Address) -> bool {
+        self.pieces == other.pieces
+    }
+}
+
+
+impl Host {
+    /// Parse a host: either an IPv6 address in [] square brackets, or a domain.
+    ///
+    /// Returns `Err` for an empty host, an invalid IPv6 address,
+    /// or a or invalid non-ASCII domain.
+    ///
+    /// FIXME: Add IDNA support for non-ASCII domains.
+    pub fn parse(input: &str) -> ParseResult<Host> {
+        if input.len() == 0 {
+            Err(EmptyHost)
+        } else if input.starts_with("[") {
+            if input.ends_with("]") {
+                Ipv6Address::parse(input.slice(1, input.len() - 1)).map(Ipv6)
+            } else {
+                Err(InvalidIpv6Address)
+            }
+        } else {
+            let decoded = percent_decode(input.as_bytes());
+            let domain = String::from_utf8_lossy(decoded.as_slice());
+            // TODO: Remove this check and use IDNA "domain to ASCII"
+            if !domain.as_slice().is_ascii() {
+                Err(NonAsciiDomainsNotSupportedYet)
+            } else if domain.as_slice().find(&[
+                '\0', '\t', '\n', '\r', ' ', '#', '%', '/', ':', '?', '@', '[', '\\', ']'
+            ]).is_some() {
+                Err(InvalidDomainCharacter)
+            } else {
+                Ok(Domain(domain.into_string().into_ascii_lower()))
+            }
+        }
+    }
+
+    /// Serialize the host as a string.
+    ///
+    /// A domain a returned as-is, an IPv6 address between [] square brackets.
+    pub fn serialize(&self) -> String {
+        self.to_string()
+    }
+}
+
+
+impl Show for Host {
+    fn fmt(&self, formatter: &mut Formatter) -> Result<(), FormatError> {
+        match *self {
+            Domain(ref domain) => domain.fmt(formatter),
+            Ipv6(ref address) => {
+                try!(formatter.write(b"["));
+                try!(address.fmt(formatter));
+                formatter.write(b"]")
+            }
+        }
+    }
+}
+
+
+impl Ipv6Address {
+    /// Parse an IPv6 address, without the [] square brackets.
+    pub fn parse(input: &str) -> ParseResult<Ipv6Address> {
+        let input = input.as_bytes();
+        let len = input.len();
+        let mut is_ip_v4 = false;
+        let mut pieces = [0, 0, 0, 0, 0, 0, 0, 0];
+        let mut piece_pointer = 0u;
+        let mut compress_pointer = None;
+        let mut i = 0u;
+        if input[0] == b':' {
+            if input[1] != b':' {
+                return Err(InvalidIpv6Address)
+            }
+            i = 2;
+            piece_pointer = 1;
+            compress_pointer = Some(1u);
+        }
+
+        while i < len {
+            if piece_pointer == 8 {
+                return Err(InvalidIpv6Address)
+            }
+            if input[i] == b':' {
+                if compress_pointer.is_some() {
+                    return Err(InvalidIpv6Address)
+                }
+                i += 1;
+                piece_pointer += 1;
+                compress_pointer = Some(piece_pointer);
+                continue
+            }
+            let start = i;
+            let end = cmp::min(len, start + 4);
+            let mut value = 0u16;
+            while i < end {
+                match from_hex(input[i]) {
+                    Some(digit) => {
+                        value = value * 0x10 + digit as u16;
+                        i += 1;
+                    },
+                    None => break
+                }
+            }
+            if i < len {
+                match input[i] {
+                    b'.' => {
+                        if i == start {
+                            return Err(InvalidIpv6Address)
+                        }
+                        i = start;
+                        is_ip_v4 = true;
+                    },
+                    b':' => {
+                        i += 1;
+                        if i == len {
+                            return Err(InvalidIpv6Address)
+                        }
+                    },
+                    _ => return Err(InvalidIpv6Address)
+                }
+            }
+            if is_ip_v4 {
+                break
+            }
+            pieces[piece_pointer] = value;
+            piece_pointer += 1;
+        }
+
+        if is_ip_v4 {
+            if piece_pointer > 6 {
+                return Err(InvalidIpv6Address)
+            }
+            let mut dots_seen = 0u;
+            while i < len {
+                // FIXME: https://github.com/whatwg/url/commit/1c22aa119c354e0020117e02571cec53f7c01064
+                let mut value = 0u16;
+                while i < len {
+                    let digit = match input[i] {
+                        c @ b'0' .. b'9' => c - b'0',
+                        _ => break
+                    };
+                    value = value * 10 + digit as u16;
+                    if value == 0 || value > 255 {
+                        return Err(InvalidIpv6Address)
+                    }
+                }
+                if dots_seen < 3 && !(i < len && input[i] == b'.') {
+                    return Err(InvalidIpv6Address)
+                }
+                pieces[piece_pointer] = pieces[piece_pointer] * 0x100 + value;
+                if dots_seen == 0 || dots_seen == 2 {
+                    piece_pointer += 1;
+                }
+                i += 1;
+                if dots_seen == 3 && i < len {
+                    return Err(InvalidIpv6Address)
+                }
+                dots_seen += 1;
+            }
+        }
+
+        match compress_pointer {
+            Some(compress_pointer) => {
+                let mut swaps = piece_pointer - compress_pointer;
+                piece_pointer = 7;
+                while swaps > 0 {
+                    pieces[piece_pointer] = pieces[compress_pointer + swaps - 1];
+                    pieces[compress_pointer + swaps - 1] = 0;
+                    swaps -= 1;
+                    piece_pointer -= 1;
+                }
+            }
+            _ => if piece_pointer != 8 {
+                return Err(InvalidIpv6Address)
+            }
+        }
+        Ok(Ipv6Address { pieces: pieces })
+    }
+
+    /// Serialize the IPv6 address to a string.
+    pub fn serialize(&self) -> String {
+        self.to_string()
+    }
+}
+
+
+impl Show for Ipv6Address {
+    fn fmt(&self, formatter: &mut Formatter) -> Result<(), FormatError> {
+        let (compress_start, compress_end) = longest_zero_sequence(&self.pieces);
+        let mut i = 0;
+        while i < 8 {
+            if i == compress_start {
+                try!(formatter.write(b":"));
+                if i == 0 {
+                    try!(formatter.write(b":"));
+                }
+                if compress_end < 8 {
+                    i = compress_end;
+                } else {
+                    break;
+                }
+            }
+            try!(write!(formatter, "{:x}", self.pieces[i as uint]));
+            if i < 7 {
+                try!(formatter.write(b":"));
+            }
+            i += 1;
+        }
+        Ok(())
+    }
+}
+
+
+fn longest_zero_sequence(pieces: &[u16, ..8]) -> (int, int) {
+    let mut longest = -1;
+    let mut longest_length = -1;
+    let mut start = -1;
+    macro_rules! finish_sequence(
+        ($end: expr) => {
+            if start >= 0 {
+                let length = $end - start;
+                if length > longest_length {
+                    longest = start;
+                    longest_length = length;
+                }
+            }
+        };
+    );
+    for i in range(0, 8) {
+        if pieces[i as uint] == 0 {
+            if start < 0 {
+                start = i;
+            }
+        } else {
+            finish_sequence!(i);
+            start = -1;
+        }
+    }
+    finish_sequence!(8);
+    (longest, longest + longest_length)
+}

+ 22 - 628
src/lib.rs

@@ -126,17 +126,35 @@ extern crate encoding;
 #[cfg(test)]
 #[cfg(test)]
 extern crate serialize;
 extern crate serialize;
 
 
-use std::cmp;
 use std::fmt::{Formatter, FormatError, Show};
 use std::fmt::{Formatter, FormatError, Show};
 use std::hash;
 use std::hash;
 use std::path;
 use std::path;
-use std::ascii::OwnedStrAsciiExt;
 
 
 use encoding::EncodingRef;
 use encoding::EncodingRef;
 
 
+pub use host::{Host, Domain, Ipv6, Ipv6Address};
+pub use parser::{
+    ErrorHandler, ParseResult, ParseError,
+    EmptyHost, InvalidScheme, InvalidPort, InvalidIpv6Address, InvalidDomainCharacter,
+    InvalidCharacter, InvalidBackslash, InvalidPercentEncoded, InvalidAtSymbolInUser,
+    ExpectedTwoSlashes, NonUrlCodePoint, RelativeUrlWithScheme, RelativeUrlWithoutBase,
+    RelativeUrlWithNonRelativeBase, NonAsciiDomainsNotSupportedYet,
+    CannotSetFileScheme, CannotSetJavascriptScheme, CannotSetNonRelativeScheme,
+};
+
+#[deprecated = "Moved to the `percent_encoding` module"]
+pub use percent_encoding::{
+    percent_decode, percent_decode_to, percent_encode, percent_encode_to,
+    utf8_percent_encode, utf8_percent_encode_to, lossy_utf8_percent_decode,
+    SIMPLE_ENCODE_SET, QUERY_ENCODE_SET, DEFAULT_ENCODE_SET, USERINFO_ENCODE_SET,
+    PASSWORD_ENCODE_SET, USERNAME_ENCODE_SET, FORM_URLENCODED_ENCODE_SET, EncodeSet,
+};
 
 
-mod encode_sets;
+
+mod host;
 mod parser;
 mod parser;
+mod urlutils;
+pub mod percent_encoding;
 pub mod form_urlencoded;
 pub mod form_urlencoded;
 pub mod punycode;
 pub mod punycode;
 
 
@@ -225,42 +243,6 @@ pub struct RelativeSchemeData {
     pub path: Vec<String>,
     pub path: Vec<String>,
 }
 }
 
 
-
-/// The host name of an URL.
-#[deriving(PartialEq, Eq, Clone)]
-pub enum Host {
-    /// A (DNS) domain name or an IPv4 address.
-    ///
-    /// FIXME: IPv4 probably should be a separate variant.
-    /// See https://www.w3.org/Bugs/Public/show_bug.cgi?id=26431
-    Domain(String),
-
-    /// An IPv6 address, represented inside `[...]` square brackets
-    /// so that `:` colon characters in the address are not ambiguous
-    /// with the port number delimiter.
-    Ipv6(Ipv6Address),
-}
-
-
-/// A 128 bit IPv6 address
-pub struct Ipv6Address {
-    pub pieces: [u16, ..8]
-}
-
-impl Clone for Ipv6Address {
-    fn clone(&self) -> Ipv6Address {
-        Ipv6Address { pieces: self.pieces }
-    }
-}
-
-impl Eq for Ipv6Address {}
-
-impl PartialEq for Ipv6Address {
-    fn eq(&self, other: &Ipv6Address) -> bool {
-        self.pieces == other.pieces
-    }
-}
-
 impl<S: hash::Writer> hash::Hash<S> for Url {
 impl<S: hash::Writer> hash::Hash<S> for Url {
     fn hash(&self, state: &mut S) {
     fn hash(&self, state: &mut S) {
         self.serialize().hash(state)
         self.serialize().hash(state)
@@ -282,6 +264,7 @@ impl<'a> UrlParser<'a> {
     /// Return a new UrlParser with default parameters.
     /// Return a new UrlParser with default parameters.
     #[inline]
     #[inline]
     pub fn new() -> UrlParser<'a> {
     pub fn new() -> UrlParser<'a> {
+        fn silent_handler(_reason: ParseError) -> ParseResult<()> { Ok(()) }
         UrlParser {
         UrlParser {
             base_url: None,
             base_url: None,
             query_encoding_override: None,
             query_encoding_override: None,
@@ -423,72 +406,6 @@ pub fn whatwg_scheme_type_mapper(scheme: &str) -> SchemeType {
 }
 }
 
 
 
 
-pub type ParseResult<T> = Result<T, ParseError>;
-
-/// Errors that can occur during parsing.
-#[deriving(PartialEq, Eq, Clone)]
-pub enum ParseError {
-    EmptyHost,
-    InvalidScheme,
-    InvalidPort,
-    InvalidIpv6Address,
-    InvalidDomainCharacter,
-    InvalidCharacter,
-    InvalidBackslash,
-    InvalidPercentEncoded,
-    InvalidAtSymbolInUser,
-    ExpectedTwoSlashes,
-    NonUrlCodePoint,
-    RelativeUrlWithScheme,
-    RelativeUrlWithoutBase,
-    RelativeUrlWithNonRelativeBase,
-    NonAsciiDomainsNotSupportedYet,
-    CannotSetFileScheme(&'static str),
-    CannotSetJavascriptScheme(&'static str),
-    CannotSetNonRelativeScheme(&'static str)
-}
-
-impl Show for ParseError {
-    fn fmt(&self, fmt: &mut Formatter) -> Result<(), FormatError> {
-        match *self {
-            EmptyHost => "Empty host",
-            InvalidScheme => "Invalid scheme",
-            InvalidPort => "Invalid port number",
-            InvalidIpv6Address => "Invalid IPv6 address",
-            InvalidDomainCharacter => "Invalid domain character",
-            InvalidCharacter => "Invalid character",
-            InvalidBackslash => "Invalid backslash",
-            InvalidPercentEncoded => "Invalid percent-encoded sequence",
-            InvalidAtSymbolInUser => "Invalid @-symbol in user",
-            ExpectedTwoSlashes => "Expected two slashes (//)",
-            NonUrlCodePoint => "Non URL code point",
-            RelativeUrlWithScheme => "Relative URL with scheme",
-            RelativeUrlWithoutBase => "Relative URL without a base",
-            RelativeUrlWithNonRelativeBase => "Relative URL with a non-relative base",
-            NonAsciiDomainsNotSupportedYet => "Non Ascii domains are not support yet",
-            CannotSetFileScheme(ref part) =>
-                return write!(fmt, "Cannot set {} on file: URLs", part),
-            CannotSetJavascriptScheme(ref part) =>
-                return write!(fmt, "Cannot set {} on javascript: URLs", part),
-            CannotSetNonRelativeScheme(ref part) =>
-                return write!(fmt, "Cannot set {} on non-relative URLs", part),
-        }.fmt(fmt)
-    }
-}
-
-/// This is called on non-fatal parse errors.
-///
-/// The handler can choose to continue or abort parsing by returning Ok() or Err(), respectively.
-/// See the `UrlParser::error_handler` method.
-///
-/// FIXME: make this a by-ref closure when that’s supported.
-pub type ErrorHandler = fn(reason: ParseError) -> ParseResult<()>;
-
-fn silent_handler(_reason: ParseError) -> ParseResult<()> {
-    Ok(())
-}
-
-
 impl Url {
 impl Url {
     /// Parse an URL with the default `UrlParser` parameters.
     /// Parse an URL with the default `UrlParser` parameters.
     ///
     ///
@@ -929,529 +846,6 @@ impl Show for RelativeSchemeData {
 }
 }
 
 
 
 
-#[allow(dead_code)]
-struct UrlUtilsWrapper<'a> {
-    url: &'a mut Url,
-    parser: &'a UrlParser<'a>,
-}
-
-
-/// These methods are not meant for use in Rust code,
-/// only to help implement the JavaScript URLUtils API: http://url.spec.whatwg.org/#urlutils
-#[doc(hidden)]
-trait UrlUtils {
-    fn set_scheme(&mut self, input: &str) -> ParseResult<()>;
-    fn set_username(&mut self, input: &str) -> ParseResult<()>;
-    fn set_password(&mut self, input: &str) -> ParseResult<()>;
-    fn set_host_and_port(&mut self, input: &str) -> ParseResult<()>;
-    fn set_host(&mut self, input: &str) -> ParseResult<()>;
-    fn set_port(&mut self, input: &str) -> ParseResult<()>;
-    fn set_path(&mut self, input: &str) -> ParseResult<()>;
-    fn set_query(&mut self, input: &str) -> ParseResult<()>;
-    fn set_fragment(&mut self, input: &str) -> ParseResult<()>;
-}
-
-impl<'a> UrlUtils for UrlUtilsWrapper<'a> {
-    /// `URLUtils.protocol` setter
-    fn set_scheme(&mut self, input: &str) -> ParseResult<()> {
-        match parser::parse_scheme(input.as_slice(), parser::SetterContext) {
-            Some((scheme, _)) => {
-                self.url.scheme = scheme;
-                Ok(())
-            },
-            None => Err(InvalidScheme),
-        }
-    }
-
-    /// `URLUtils.username` setter
-    fn set_username(&mut self, input: &str) -> ParseResult<()> {
-        match self.url.scheme_data {
-            RelativeSchemeData(RelativeSchemeData { ref mut username, .. }) => {
-                username.truncate(0);
-                utf8_percent_encode_to(input, USERNAME_ENCODE_SET, username);
-                Ok(())
-            },
-            NonRelativeSchemeData(_) => Err(CannotSetNonRelativeScheme("username"))
-        }
-    }
-
-    /// `URLUtils.password` setter
-    fn set_password(&mut self, input: &str) -> ParseResult<()> {
-        match self.url.scheme_data {
-            RelativeSchemeData(RelativeSchemeData { ref mut password, .. }) => {
-                let mut new_password = String::new();
-                utf8_percent_encode_to(input, PASSWORD_ENCODE_SET, &mut new_password);
-                *password = Some(new_password);
-                Ok(())
-            },
-            NonRelativeSchemeData(_) => Err(CannotSetNonRelativeScheme("password"))
-        }
-    }
-
-    /// `URLUtils.host` setter
-    fn set_host_and_port(&mut self, input: &str) -> ParseResult<()> {
-        match self.url.scheme_data {
-            RelativeSchemeData(RelativeSchemeData { ref mut host, ref mut port, .. }) => {
-                let scheme_type = self.parser.get_scheme_type(self.url.scheme.as_slice());
-                let (new_host, new_port, _) = try!(parser::parse_host(
-                    input, scheme_type, self.parser));
-                *host = new_host;
-                *port = new_port;
-                Ok(())
-            },
-            NonRelativeSchemeData(_) => Err(CannotSetNonRelativeScheme("host/port"))
-        }
-    }
-
-    /// `URLUtils.hostname` setter
-    fn set_host(&mut self, input: &str) -> ParseResult<()> {
-        match self.url.scheme_data {
-            RelativeSchemeData(RelativeSchemeData { ref mut host, .. }) => {
-                let (new_host, _) = try!(parser::parse_hostname(input, self.parser));
-                *host = new_host;
-                Ok(())
-            },
-            NonRelativeSchemeData(_) => Err(CannotSetNonRelativeScheme("host"))
-        }
-    }
-
-    /// `URLUtils.port` setter
-    fn set_port(&mut self, input: &str) -> ParseResult<()> {
-        match self.url.scheme_data {
-            RelativeSchemeData(RelativeSchemeData { ref mut port, .. }) => {
-                let scheme_type = self.parser.get_scheme_type(self.url.scheme.as_slice());
-                if scheme_type == FileLikeRelativeScheme {
-                    return Err(CannotSetFileScheme("port"));
-                }
-                let (new_port, _) = try!(parser::parse_port(input, scheme_type, self.parser));
-                *port = new_port;
-                Ok(())
-            },
-            NonRelativeSchemeData(_) => Err(CannotSetNonRelativeScheme("port"))
-        }
-    }
-
-    /// `URLUtils.pathname` setter
-    fn set_path(&mut self, input: &str) -> ParseResult<()> {
-        match self.url.scheme_data {
-            RelativeSchemeData(RelativeSchemeData { ref mut path, .. }) => {
-                let scheme_type = self.parser.get_scheme_type(self.url.scheme.as_slice());
-                let (new_path, _) = try!(parser::parse_path_start(
-                    input, parser::SetterContext, scheme_type, self.parser));
-                *path = new_path;
-                Ok(())
-            },
-            NonRelativeSchemeData(_) => Err(CannotSetNonRelativeScheme("path"))
-        }
-    }
-
-    /// `URLUtils.search` setter
-    fn set_query(&mut self, input: &str) -> ParseResult<()> {
-        self.url.query = if input.is_empty() {
-            None
-        } else {
-            let input = if input.starts_with("?") { input.slice_from(1) } else { input };
-            let (new_query, _) = try!(parser::parse_query(
-                input, parser::SetterContext, self.parser));
-            Some(new_query)
-        };
-        Ok(())
-    }
-
-    /// `URLUtils.hash` setter
-    fn set_fragment(&mut self, input: &str) -> ParseResult<()> {
-        if self.url.scheme.as_slice() == "javascript" {
-            return Err(CannotSetJavascriptScheme("fragment"))
-        }
-        self.url.fragment = if input.is_empty() {
-            None
-        } else {
-            let input = if input.starts_with("#") { input.slice_from(1) } else { input };
-            Some(try!(parser::parse_fragment(input, self.parser)))
-        };
-        Ok(())
-    }
-}
-
-
-impl Host {
-    /// Parse a host: either an IPv6 address in [] square brackets, or a domain.
-    ///
-    /// Returns `Err` for an empty host, an invalid IPv6 address,
-    /// or a or invalid non-ASCII domain.
-    ///
-    /// FIXME: Add IDNA support for non-ASCII domains.
-    pub fn parse(input: &str) -> ParseResult<Host> {
-        if input.len() == 0 {
-            Err(EmptyHost)
-        } else if input.starts_with("[") {
-            if input.ends_with("]") {
-                Ipv6Address::parse(input.slice(1, input.len() - 1)).map(Ipv6)
-            } else {
-                Err(InvalidIpv6Address)
-            }
-        } else {
-            let decoded = percent_decode(input.as_bytes());
-            let domain = String::from_utf8_lossy(decoded.as_slice());
-            // TODO: Remove this check and use IDNA "domain to ASCII"
-            if !domain.as_slice().is_ascii() {
-                Err(NonAsciiDomainsNotSupportedYet)
-            } else if domain.as_slice().find(&[
-                '\0', '\t', '\n', '\r', ' ', '#', '%', '/', ':', '?', '@', '[', '\\', ']'
-            ]).is_some() {
-                Err(InvalidDomainCharacter)
-            } else {
-                Ok(Domain(domain.into_string().into_ascii_lower()))
-            }
-        }
-    }
-
-    /// Serialize the host as a string.
-    ///
-    /// A domain a returned as-is, an IPv6 address between [] square brackets.
-    pub fn serialize(&self) -> String {
-        self.to_string()
-    }
-}
-
-
-impl Show for Host {
-    fn fmt(&self, formatter: &mut Formatter) -> Result<(), FormatError> {
-        match *self {
-            Domain(ref domain) => domain.fmt(formatter),
-            Ipv6(ref address) => {
-                try!(formatter.write(b"["));
-                try!(address.fmt(formatter));
-                formatter.write(b"]")
-            }
-        }
-    }
-}
-
-
-impl Ipv6Address {
-    /// Parse an IPv6 address, without the [] square brackets.
-    pub fn parse(input: &str) -> ParseResult<Ipv6Address> {
-        let input = input.as_bytes();
-        let len = input.len();
-        let mut is_ip_v4 = false;
-        let mut pieces = [0, 0, 0, 0, 0, 0, 0, 0];
-        let mut piece_pointer = 0u;
-        let mut compress_pointer = None;
-        let mut i = 0u;
-        if input[0] == b':' {
-            if input[1] != b':' {
-                return Err(InvalidIpv6Address)
-            }
-            i = 2;
-            piece_pointer = 1;
-            compress_pointer = Some(1u);
-        }
-
-        while i < len {
-            if piece_pointer == 8 {
-                return Err(InvalidIpv6Address)
-            }
-            if input[i] == b':' {
-                if compress_pointer.is_some() {
-                    return Err(InvalidIpv6Address)
-                }
-                i += 1;
-                piece_pointer += 1;
-                compress_pointer = Some(piece_pointer);
-                continue
-            }
-            let start = i;
-            let end = cmp::min(len, start + 4);
-            let mut value = 0u16;
-            while i < end {
-                match from_hex(input[i]) {
-                    Some(digit) => {
-                        value = value * 0x10 + digit as u16;
-                        i += 1;
-                    },
-                    None => break
-                }
-            }
-            if i < len {
-                match input[i] {
-                    b'.' => {
-                        if i == start {
-                            return Err(InvalidIpv6Address)
-                        }
-                        i = start;
-                        is_ip_v4 = true;
-                    },
-                    b':' => {
-                        i += 1;
-                        if i == len {
-                            return Err(InvalidIpv6Address)
-                        }
-                    },
-                    _ => return Err(InvalidIpv6Address)
-                }
-            }
-            if is_ip_v4 {
-                break
-            }
-            pieces[piece_pointer] = value;
-            piece_pointer += 1;
-        }
-
-        if is_ip_v4 {
-            if piece_pointer > 6 {
-                return Err(InvalidIpv6Address)
-            }
-            let mut dots_seen = 0u;
-            while i < len {
-                // FIXME: https://github.com/whatwg/url/commit/1c22aa119c354e0020117e02571cec53f7c01064
-                let mut value = 0u16;
-                while i < len {
-                    let digit = match input[i] {
-                        c @ b'0' .. b'9' => c - b'0',
-                        _ => break
-                    };
-                    value = value * 10 + digit as u16;
-                    if value == 0 || value > 255 {
-                        return Err(InvalidIpv6Address)
-                    }
-                }
-                if dots_seen < 3 && !(i < len && input[i] == b'.') {
-                    return Err(InvalidIpv6Address)
-                }
-                pieces[piece_pointer] = pieces[piece_pointer] * 0x100 + value;
-                if dots_seen == 0 || dots_seen == 2 {
-                    piece_pointer += 1;
-                }
-                i += 1;
-                if dots_seen == 3 && i < len {
-                    return Err(InvalidIpv6Address)
-                }
-                dots_seen += 1;
-            }
-        }
-
-        match compress_pointer {
-            Some(compress_pointer) => {
-                let mut swaps = piece_pointer - compress_pointer;
-                piece_pointer = 7;
-                while swaps > 0 {
-                    pieces[piece_pointer] = pieces[compress_pointer + swaps - 1];
-                    pieces[compress_pointer + swaps - 1] = 0;
-                    swaps -= 1;
-                    piece_pointer -= 1;
-                }
-            }
-            _ => if piece_pointer != 8 {
-                return Err(InvalidIpv6Address)
-            }
-        }
-        Ok(Ipv6Address { pieces: pieces })
-    }
-
-    /// Serialize the IPv6 address to a string.
-    pub fn serialize(&self) -> String {
-        self.to_string()
-    }
-}
-
-
-impl Show for Ipv6Address {
-    fn fmt(&self, formatter: &mut Formatter) -> Result<(), FormatError> {
-        let (compress_start, compress_end) = longest_zero_sequence(&self.pieces);
-        let mut i = 0;
-        while i < 8 {
-            if i == compress_start {
-                try!(formatter.write(b":"));
-                if i == 0 {
-                    try!(formatter.write(b":"));
-                }
-                if compress_end < 8 {
-                    i = compress_end;
-                } else {
-                    break;
-                }
-            }
-            try!(write!(formatter, "{:x}", self.pieces[i as uint]));
-            if i < 7 {
-                try!(formatter.write(b":"));
-            }
-            i += 1;
-        }
-        Ok(())
-    }
-}
-
-
-fn longest_zero_sequence(pieces: &[u16, ..8]) -> (int, int) {
-    let mut longest = -1;
-    let mut longest_length = -1;
-    let mut start = -1;
-    macro_rules! finish_sequence(
-        ($end: expr) => {
-            if start >= 0 {
-                let length = $end - start;
-                if length > longest_length {
-                    longest = start;
-                    longest_length = length;
-                }
-            }
-        };
-    );
-    for i in range(0, 8) {
-        if pieces[i as uint] == 0 {
-            if start < 0 {
-                start = i;
-            }
-        } else {
-            finish_sequence!(i);
-            start = -1;
-        }
-    }
-    finish_sequence!(8);
-    (longest, longest + longest_length)
-}
-
-
-#[inline]
-fn from_hex(byte: u8) -> Option<u8> {
-    match byte {
-        b'0' .. b'9' => Some(byte - b'0'),  // 0..9
-        b'A' .. b'F' => Some(byte + 10 - b'A'),  // A..F
-        b'a' .. b'f' => Some(byte + 10 - b'a'),  // a..f
-        _ => None
-    }
-}
-
-
-/// Represents a set of characters / bytes that should be percent-encoded.
-///
-/// See [encode sets specification](http://url.spec.whatwg.org/#simple-encode-set).
-///
-/// Different characters need to be encoded in different parts of an URL.
-/// For example, a literal `?` question mark in an URL’s path would indicate
-/// the start of the query string.
-/// A question mark meant to be part of the path therefore needs to be percent-encoded.
-/// In the query string however, a question mark does not have any special meaning
-/// and does not need to be percent-encoded.
-///
-/// Since the implementation details of `EncodeSet` are private,
-/// the set of available encode sets is not extensible beyond the ones
-/// provided here.
-/// If you need a different encode set,
-/// please [file a bug](https://github.com/servo/rust-url/issues)
-/// explaining the use case.
-pub struct EncodeSet {
-    map: &'static [&'static str, ..256],
-}
-
-/// This encode set is used for fragment identifier and non-relative scheme data.
-pub static SIMPLE_ENCODE_SET: EncodeSet = EncodeSet { map: &encode_sets::SIMPLE };
-
-/// This encode set is used in the URL parser for query strings.
-pub static QUERY_ENCODE_SET: EncodeSet = EncodeSet { map: &encode_sets::QUERY };
-
-/// This encode set is used for path components.
-pub static DEFAULT_ENCODE_SET: EncodeSet = EncodeSet { map: &encode_sets::DEFAULT };
-
-/// This encode set is used in the URL parser for usernames and passwords.
-pub static USERINFO_ENCODE_SET: EncodeSet = EncodeSet { map: &encode_sets::USERINFO };
-
-/// This encode set should be used when setting the password field of a parsed URL.
-pub static PASSWORD_ENCODE_SET: EncodeSet = EncodeSet { map: &encode_sets::PASSWORD };
-
-/// This encode set should be used when setting the username field of a parsed URL.
-pub static USERNAME_ENCODE_SET: EncodeSet = EncodeSet { map: &encode_sets::USERNAME };
-
-/// This encode set is used in `application/x-www-form-urlencoded` serialization.
-pub static FORM_URLENCODED_ENCODE_SET: EncodeSet = EncodeSet {
-    map: &encode_sets::FORM_URLENCODED,
-};
-
-
-/// Percent-encode the given bytes, and push the result to `output`.
-///
-/// The pushed strings are within the ASCII range.
-#[inline]
-pub fn percent_encode_to(input: &[u8], encode_set: EncodeSet, output: &mut String) {
-    for &byte in input.iter() {
-        output.push_str(encode_set.map[byte as uint])
-    }
-}
-
-
-/// Percent-encode the given bytes.
-///
-/// The returned string is within the ASCII range.
-#[inline]
-pub fn percent_encode(input: &[u8], encode_set: EncodeSet) -> String {
-    let mut output = String::new();
-    percent_encode_to(input, encode_set, &mut output);
-    output
-}
-
-
-/// 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(input: &str, encode_set: EncodeSet, output: &mut String) {
-    percent_encode_to(input.as_bytes(), encode_set, output)
-}
-
-
-/// Percent-encode the UTF-8 encoding of the given string.
-///
-/// The returned string is within the ASCII range.
-#[inline]
-pub fn utf8_percent_encode(input: &str, encode_set: EncodeSet) -> String {
-    let mut output = String::new();
-    utf8_percent_encode_to(input, encode_set, &mut output);
-    output
-}
-
-
-/// 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 = 0u;
-    while i < input.len() {
-        let c = input[i];
-        if c == b'%' && i + 2 < input.len() {
-            match (from_hex(input[i + 1]), from_hex(input[i + 2])) {
-                (Some(h), Some(l)) => {
-                    output.push(h * 0x10 + l);
-                    i += 3;
-                    continue
-                },
-                _ => (),
-            }
-        }
-
-        output.push(c);
-        i += 1;
-    }
-}
-
-
-/// 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
-}
-
-
-/// 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).as_slice()).into_string()
-}
-
-
 trait ToUrlPath {
 trait ToUrlPath {
     fn to_url_path(&self) -> Result<Vec<String>, ()>;
     fn to_url_path(&self) -> Result<Vec<String>, ()>;
 }
 }

+ 68 - 5
src/parser.rs

@@ -8,18 +8,19 @@
 
 
 
 
 use std::ascii::StrAsciiExt;
 use std::ascii::StrAsciiExt;
+use std::fmt::{Formatter, FormatError, Show};
 use std::str::CharRange;
 use std::str::CharRange;
 
 
 use encoding;
 use encoding;
 
 
 use super::{
 use super::{
-    ParseResult, UrlParser, Url, RelativeSchemeData, NonRelativeSchemeData, Host, Domain,
+    UrlParser, Url, RelativeSchemeData, NonRelativeSchemeData, Host, Domain,
     SchemeType, FileLikeRelativeScheme, RelativeScheme, NonRelativeScheme,
     SchemeType, FileLikeRelativeScheme, RelativeScheme, NonRelativeScheme,
-    InvalidPort, InvalidCharacter, InvalidBackslash, RelativeUrlWithScheme,
-    ExpectedTwoSlashes, InvalidAtSymbolInUser, InvalidPercentEncoded, NonUrlCodePoint,
-    RelativeUrlWithNonRelativeBase, RelativeUrlWithoutBase,
+};
+use percent_encoding::{
     utf8_percent_encode_to, percent_encode,
     utf8_percent_encode_to, percent_encode,
-    SIMPLE_ENCODE_SET, DEFAULT_ENCODE_SET, USERINFO_ENCODE_SET, QUERY_ENCODE_SET};
+    SIMPLE_ENCODE_SET, DEFAULT_ENCODE_SET, USERINFO_ENCODE_SET, QUERY_ENCODE_SET
+};
 
 
 
 
 macro_rules! is_match(
 macro_rules! is_match(
@@ -29,6 +30,68 @@ macro_rules! is_match(
 )
 )
 
 
 
 
+pub type ParseResult<T> = Result<T, ParseError>;
+
+/// Errors that can occur during parsing.
+#[deriving(PartialEq, Eq, Clone)]
+pub enum ParseError {
+    EmptyHost,
+    InvalidScheme,
+    InvalidPort,
+    InvalidIpv6Address,
+    InvalidDomainCharacter,
+    InvalidCharacter,
+    InvalidBackslash,
+    InvalidPercentEncoded,
+    InvalidAtSymbolInUser,
+    ExpectedTwoSlashes,
+    NonUrlCodePoint,
+    RelativeUrlWithScheme,
+    RelativeUrlWithoutBase,
+    RelativeUrlWithNonRelativeBase,
+    NonAsciiDomainsNotSupportedYet,
+    CannotSetFileScheme(&'static str),
+    CannotSetJavascriptScheme(&'static str),
+    CannotSetNonRelativeScheme(&'static str)
+}
+
+impl Show for ParseError {
+    fn fmt(&self, fmt: &mut Formatter) -> Result<(), FormatError> {
+        match *self {
+            EmptyHost => "Empty host",
+            InvalidScheme => "Invalid scheme",
+            InvalidPort => "Invalid port number",
+            InvalidIpv6Address => "Invalid IPv6 address",
+            InvalidDomainCharacter => "Invalid domain character",
+            InvalidCharacter => "Invalid character",
+            InvalidBackslash => "Invalid backslash",
+            InvalidPercentEncoded => "Invalid percent-encoded sequence",
+            InvalidAtSymbolInUser => "Invalid @-symbol in user",
+            ExpectedTwoSlashes => "Expected two slashes (//)",
+            NonUrlCodePoint => "Non URL code point",
+            RelativeUrlWithScheme => "Relative URL with scheme",
+            RelativeUrlWithoutBase => "Relative URL without a base",
+            RelativeUrlWithNonRelativeBase => "Relative URL with a non-relative base",
+            NonAsciiDomainsNotSupportedYet => "Non Ascii domains are not support yet",
+            CannotSetFileScheme(ref part) =>
+                return write!(fmt, "Cannot set {} on file: URLs", part),
+            CannotSetJavascriptScheme(ref part) =>
+                return write!(fmt, "Cannot set {} on javascript: URLs", part),
+            CannotSetNonRelativeScheme(ref part) =>
+                return write!(fmt, "Cannot set {} on non-relative URLs", part),
+        }.fmt(fmt)
+    }
+}
+
+/// This is called on non-fatal parse errors.
+///
+/// The handler can choose to continue or abort parsing by returning Ok() or Err(), respectively.
+/// See the `UrlParser::error_handler` method.
+///
+/// FIXME: make this a by-ref closure when that’s supported.
+pub type ErrorHandler = fn(reason: ParseError) -> ParseResult<()>;
+
+
 #[deriving(PartialEq, Eq)]
 #[deriving(PartialEq, Eq)]
 pub enum Context {
 pub enum Context {
     UrlParserContext,
     UrlParserContext,

+ 148 - 0
src/percent_encoding.rs

@@ -0,0 +1,148 @@
+// 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.
+
+
+#[path = "encode_sets.rs"]
+mod encode_sets;
+
+/// Represents a set of characters / bytes that should be percent-encoded.
+///
+/// See [encode sets specification](http://url.spec.whatwg.org/#simple-encode-set).
+///
+/// Different characters need to be encoded in different parts of an URL.
+/// For example, a literal `?` question mark in an URL’s path would indicate
+/// the start of the query string.
+/// A question mark meant to be part of the path therefore needs to be percent-encoded.
+/// In the query string however, a question mark does not have any special meaning
+/// and does not need to be percent-encoded.
+///
+/// Since the implementation details of `EncodeSet` are private,
+/// the set of available encode sets is not extensible beyond the ones
+/// provided here.
+/// If you need a different encode set,
+/// please [file a bug](https://github.com/servo/rust-url/issues)
+/// explaining the use case.
+pub struct EncodeSet {
+    map: &'static [&'static str, ..256],
+}
+
+/// This encode set is used for fragment identifier and non-relative scheme data.
+pub static SIMPLE_ENCODE_SET: EncodeSet = EncodeSet { map: &encode_sets::SIMPLE };
+
+/// This encode set is used in the URL parser for query strings.
+pub static QUERY_ENCODE_SET: EncodeSet = EncodeSet { map: &encode_sets::QUERY };
+
+/// This encode set is used for path components.
+pub static DEFAULT_ENCODE_SET: EncodeSet = EncodeSet { map: &encode_sets::DEFAULT };
+
+/// This encode set is used in the URL parser for usernames and passwords.
+pub static USERINFO_ENCODE_SET: EncodeSet = EncodeSet { map: &encode_sets::USERINFO };
+
+/// This encode set should be used when setting the password field of a parsed URL.
+pub static PASSWORD_ENCODE_SET: EncodeSet = EncodeSet { map: &encode_sets::PASSWORD };
+
+/// This encode set should be used when setting the username field of a parsed URL.
+pub static USERNAME_ENCODE_SET: EncodeSet = EncodeSet { map: &encode_sets::USERNAME };
+
+/// This encode set is used in `application/x-www-form-urlencoded` serialization.
+pub static FORM_URLENCODED_ENCODE_SET: EncodeSet = EncodeSet {
+    map: &encode_sets::FORM_URLENCODED,
+};
+
+
+/// Percent-encode the given bytes, and push the result to `output`.
+///
+/// The pushed strings are within the ASCII range.
+#[inline]
+pub fn percent_encode_to(input: &[u8], encode_set: EncodeSet, output: &mut String) {
+    for &byte in input.iter() {
+        output.push_str(encode_set.map[byte as uint])
+    }
+}
+
+
+/// Percent-encode the given bytes.
+///
+/// The returned string is within the ASCII range.
+#[inline]
+pub fn percent_encode(input: &[u8], encode_set: EncodeSet) -> String {
+    let mut output = String::new();
+    percent_encode_to(input, encode_set, &mut output);
+    output
+}
+
+
+/// 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(input: &str, encode_set: EncodeSet, output: &mut String) {
+    percent_encode_to(input.as_bytes(), encode_set, output)
+}
+
+
+/// Percent-encode the UTF-8 encoding of the given string.
+///
+/// The returned string is within the ASCII range.
+#[inline]
+pub fn utf8_percent_encode(input: &str, encode_set: EncodeSet) -> String {
+    let mut output = String::new();
+    utf8_percent_encode_to(input, encode_set, &mut output);
+    output
+}
+
+
+/// 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 = 0u;
+    while i < input.len() {
+        let c = input[i];
+        if c == b'%' && i + 2 < input.len() {
+            match (from_hex(input[i + 1]), from_hex(input[i + 2])) {
+                (Some(h), Some(l)) => {
+                    output.push(h * 0x10 + l);
+                    i += 3;
+                    continue
+                },
+                _ => (),
+            }
+        }
+
+        output.push(c);
+        i += 1;
+    }
+}
+
+
+/// 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
+}
+
+
+/// 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).as_slice()).into_string()
+}
+
+#[inline]
+pub fn from_hex(byte: u8) -> Option<u8> {
+    match byte {
+        b'0' .. b'9' => Some(byte - b'0'),  // 0..9
+        b'A' .. b'F' => Some(byte + 10 - b'A'),  // A..F
+        b'a' .. b'f' => Some(byte + 10 - b'a'),  // a..f
+        _ => None
+    }
+}

+ 161 - 0
src/urlutils.rs

@@ -0,0 +1,161 @@
+// 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.
+
+
+//! These methods are not meant for use in Rust code,
+//! only to help implement the JavaScript URLUtils API: http://url.spec.whatwg.org/#urlutils
+
+use super::{Url, UrlParser, RelativeSchemeData, NonRelativeSchemeData, FileLikeRelativeScheme};
+use parser::{
+    ParseResult, InvalidScheme,
+    CannotSetFileScheme, CannotSetJavascriptScheme, CannotSetNonRelativeScheme,
+};
+use percent_encoding::{utf8_percent_encode_to, USERNAME_ENCODE_SET, PASSWORD_ENCODE_SET};
+
+
+#[allow(dead_code)]
+struct UrlUtilsWrapper<'a> {
+    url: &'a mut Url,
+    parser: &'a UrlParser<'a>,
+}
+
+
+#[doc(hidden)]
+trait UrlUtils {
+    fn set_scheme(&mut self, input: &str) -> ParseResult<()>;
+    fn set_username(&mut self, input: &str) -> ParseResult<()>;
+    fn set_password(&mut self, input: &str) -> ParseResult<()>;
+    fn set_host_and_port(&mut self, input: &str) -> ParseResult<()>;
+    fn set_host(&mut self, input: &str) -> ParseResult<()>;
+    fn set_port(&mut self, input: &str) -> ParseResult<()>;
+    fn set_path(&mut self, input: &str) -> ParseResult<()>;
+    fn set_query(&mut self, input: &str) -> ParseResult<()>;
+    fn set_fragment(&mut self, input: &str) -> ParseResult<()>;
+}
+
+impl<'a> UrlUtils for UrlUtilsWrapper<'a> {
+    /// `URLUtils.protocol` setter
+    fn set_scheme(&mut self, input: &str) -> ParseResult<()> {
+        match ::parser::parse_scheme(input.as_slice(), ::parser::SetterContext) {
+            Some((scheme, _)) => {
+                self.url.scheme = scheme;
+                Ok(())
+            },
+            None => Err(InvalidScheme),
+        }
+    }
+
+    /// `URLUtils.username` setter
+    fn set_username(&mut self, input: &str) -> ParseResult<()> {
+        match self.url.scheme_data {
+            RelativeSchemeData(RelativeSchemeData { ref mut username, .. }) => {
+                username.truncate(0);
+                utf8_percent_encode_to(input, USERNAME_ENCODE_SET, username);
+                Ok(())
+            },
+            NonRelativeSchemeData(_) => Err(CannotSetNonRelativeScheme("username"))
+        }
+    }
+
+    /// `URLUtils.password` setter
+    fn set_password(&mut self, input: &str) -> ParseResult<()> {
+        match self.url.scheme_data {
+            RelativeSchemeData(RelativeSchemeData { ref mut password, .. }) => {
+                let mut new_password = String::new();
+                utf8_percent_encode_to(input, PASSWORD_ENCODE_SET, &mut new_password);
+                *password = Some(new_password);
+                Ok(())
+            },
+            NonRelativeSchemeData(_) => Err(CannotSetNonRelativeScheme("password"))
+        }
+    }
+
+    /// `URLUtils.host` setter
+    fn set_host_and_port(&mut self, input: &str) -> ParseResult<()> {
+        match self.url.scheme_data {
+            RelativeSchemeData(RelativeSchemeData { ref mut host, ref mut port, .. }) => {
+                let scheme_type = self.parser.get_scheme_type(self.url.scheme.as_slice());
+                let (new_host, new_port, _) = try!(::parser::parse_host(
+                    input, scheme_type, self.parser));
+                *host = new_host;
+                *port = new_port;
+                Ok(())
+            },
+            NonRelativeSchemeData(_) => Err(CannotSetNonRelativeScheme("host/port"))
+        }
+    }
+
+    /// `URLUtils.hostname` setter
+    fn set_host(&mut self, input: &str) -> ParseResult<()> {
+        match self.url.scheme_data {
+            RelativeSchemeData(RelativeSchemeData { ref mut host, .. }) => {
+                let (new_host, _) = try!(::parser::parse_hostname(input, self.parser));
+                *host = new_host;
+                Ok(())
+            },
+            NonRelativeSchemeData(_) => Err(CannotSetNonRelativeScheme("host"))
+        }
+    }
+
+    /// `URLUtils.port` setter
+    fn set_port(&mut self, input: &str) -> ParseResult<()> {
+        match self.url.scheme_data {
+            RelativeSchemeData(RelativeSchemeData { ref mut port, .. }) => {
+                let scheme_type = self.parser.get_scheme_type(self.url.scheme.as_slice());
+                if scheme_type == FileLikeRelativeScheme {
+                    return Err(CannotSetFileScheme("port"));
+                }
+                let (new_port, _) = try!(::parser::parse_port(input, scheme_type, self.parser));
+                *port = new_port;
+                Ok(())
+            },
+            NonRelativeSchemeData(_) => Err(CannotSetNonRelativeScheme("port"))
+        }
+    }
+
+    /// `URLUtils.pathname` setter
+    fn set_path(&mut self, input: &str) -> ParseResult<()> {
+        match self.url.scheme_data {
+            RelativeSchemeData(RelativeSchemeData { ref mut path, .. }) => {
+                let scheme_type = self.parser.get_scheme_type(self.url.scheme.as_slice());
+                let (new_path, _) = try!(::parser::parse_path_start(
+                    input, ::parser::SetterContext, scheme_type, self.parser));
+                *path = new_path;
+                Ok(())
+            },
+            NonRelativeSchemeData(_) => Err(CannotSetNonRelativeScheme("path"))
+        }
+    }
+
+    /// `URLUtils.search` setter
+    fn set_query(&mut self, input: &str) -> ParseResult<()> {
+        self.url.query = if input.is_empty() {
+            None
+        } else {
+            let input = if input.starts_with("?") { input.slice_from(1) } else { input };
+            let (new_query, _) = try!(::parser::parse_query(
+                input, ::parser::SetterContext, self.parser));
+            Some(new_query)
+        };
+        Ok(())
+    }
+
+    /// `URLUtils.hash` setter
+    fn set_fragment(&mut self, input: &str) -> ParseResult<()> {
+        if self.url.scheme.as_slice() == "javascript" {
+            return Err(CannotSetJavascriptScheme("fragment"))
+        }
+        self.url.fragment = if input.is_empty() {
+            None
+        } else {
+            let input = if input.starts_with("#") { input.slice_from(1) } else { input };
+            Some(try!(::parser::parse_fragment(input, self.parser)))
+        };
+        Ok(())
+    }
+}