|
@@ -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>, ()>;
|
|
|
}
|
|
}
|