Simon Sapin 12 лет назад
Родитель
Сommit
61154630fb
3 измененных файлов с 172 добавлено и 29 удалено
  1. 37 26
      src/parser.rs
  2. 2 0
      src/punycode.rs
  3. 133 3
      src/url.rs

+ 37 - 26
src/parser.rs

@@ -30,7 +30,7 @@ macro_rules! is_match(
 pub fn parse_url(input: &str, base_url: Option<&Url>, parse_error: ErrorHandler)
                  -> ParseResult<Url> {
     let input = input.trim_chars(&[' ', '\t', '\n', '\r', '\x0C']);
-    match parse_scheme(input) {
+    match parse_scheme(input, /* in_setter = */ false) {
         Some((scheme, remaining)) => {
             if scheme.as_slice() == "file" {
                 // Relative state?
@@ -77,20 +77,25 @@ pub fn parse_url(input: &str, base_url: Option<&Url>, parse_error: ErrorHandler)
 }
 
 
-fn parse_scheme<'a>(input: &'a str) -> Option<(String, &'a str)> {
-    if !input.is_empty() && starts_with_ascii_alpha(input) {
-        for (i, c) in input.char_indices() {
-            match c {
-                'a'..'z' | 'A'..'Z' | '0'..'9' | '+' | '-' | '.' => (),
-                ':' => return Some((
-                    input.slice_to(i).to_ascii_lower(),
-                    input.slice_from(i + 1),
-                )),
-                _ => break,
-            }
+pub fn parse_scheme<'a>(input: &'a str, in_setter: bool) -> Option<(String, &'a str)> {
+    if input.is_empty() || !starts_with_ascii_alpha(input) {
+        return None
+    }
+    for (i, c) in input.char_indices() {
+        match c {
+            'a'..'z' | 'A'..'Z' | '0'..'9' | '+' | '-' | '.' => (),
+            ':' => return Some((
+                input.slice_to(i).to_ascii_lower(),
+                input.slice_from(i + 1),
+            )),
+            _ => return None,
         }
     }
-    None
+    if in_setter {
+        Some((input.to_ascii_lower(), ""))
+    } else {
+        None
+    }
 }
 
 
@@ -101,7 +106,8 @@ fn parse_absolute_url<'a>(scheme: String, input: &'a str, parse_error: ErrorHand
     // Authority state
     let (username, password, remaining) = try!(parse_userinfo(remaining, parse_error));
     // Host state
-    let (host, port, remaining) = try!(parse_hostname(remaining, scheme.as_slice(), parse_error));
+    let (host, port, remaining) = try!(
+        parse_hostname(remaining, scheme.as_slice(), parse_error, /* skip_port = */ false));
     let (path, remaining) = try!(parse_path_start(
         remaining,
         /* full_url= */ true,
@@ -311,8 +317,9 @@ fn parse_password(input: &str, parse_error: ErrorHandler) -> ParseResult<String>
 }
 
 
-fn parse_hostname<'a>(input: &'a str, scheme: &str, parse_error: ErrorHandler)
-                      -> ParseResult<(Host, String, &'a str)> {
+pub fn parse_hostname<'a>(input: &'a str, scheme: &str, parse_error: ErrorHandler,
+                          skip_port: bool)
+                          -> ParseResult<(Host, String, &'a str)> {
     let mut inside_square_brackets = false;
     let mut host_input = String::new();
     let mut end = input.len();
@@ -320,9 +327,13 @@ fn parse_hostname<'a>(input: &'a str, scheme: &str, parse_error: ErrorHandler)
         match c {
             ':' if !inside_square_brackets => {
                 let host = try!(Host::parse(host_input.as_slice()));
-                let (port, remaining) = try!(
-                    parse_port(input.slice_from(i + 1), scheme, parse_error));
-                return Ok((host, port, remaining))
+                return Ok(if skip_port {
+                    (host, String::new(), "")
+                } else {
+                    let (port, remaining) = try!(
+                        parse_port(input.slice_from(i + 1), scheme, parse_error));
+                    (host, port, remaining)
+                })
             },
             '/' | '\\' | '?' | '#' => {
                 end = i;
@@ -344,7 +355,7 @@ fn parse_hostname<'a>(input: &'a str, scheme: &str, parse_error: ErrorHandler)
 }
 
 
-fn parse_port<'a>(input: &'a str, scheme: &str, parse_error: ErrorHandler)
+pub fn parse_port<'a>(input: &'a str, scheme: &str, parse_error: ErrorHandler)
                   -> ParseResult<(String, &'a str)> {
     let mut port = String::new();
     let mut has_initial_zero = false;
@@ -373,7 +384,7 @@ fn parse_port<'a>(input: &'a str, scheme: &str, parse_error: ErrorHandler)
     match (scheme, port.as_slice()) {
         ("ftp", "21") | ("gopher", "70") | ("http", "80") |
         ("https", "443") | ("ws", "80") | ("wss", "443")
-        => port = String::new(),
+        => port.truncate(0),
         _ => (),
     }
     return Ok((port, input.slice_from(end)))
@@ -402,9 +413,9 @@ fn parse_file_host<'a>(input: &'a str, parse_error: ErrorHandler) -> ParseResult
 }
 
 
-fn parse_path_start<'a>(input: &'a str, full_url: bool, in_file_scheme: bool,
-                        parse_error: ErrorHandler)
-                        -> ParseResult<(Vec<String>, &'a str)> {
+pub fn parse_path_start<'a>(input: &'a str, full_url: bool, in_file_scheme: bool,
+                            parse_error: ErrorHandler)
+                            -> ParseResult<(Vec<String>, &'a str)> {
     let mut i = 0;
     // Relative path start state
     if !input.is_empty() {
@@ -552,7 +563,7 @@ fn parse_query_and_fragment(input: &str, parse_error: ErrorHandler)
 }
 
 
-fn parse_query<'a>(input: &'a str, encoding_override: EncodingRef, full_url: bool,
+pub fn parse_query<'a>(input: &'a str, encoding_override: EncodingRef, full_url: bool,
                    parse_error: ErrorHandler)
                    -> ParseResult<(String, Option<&'a str>)> {
     let mut query = String::new();
@@ -590,7 +601,7 @@ fn parse_query<'a>(input: &'a str, encoding_override: EncodingRef, full_url: boo
 }
 
 
-fn parse_fragment<'a>(input: &'a str, parse_error: ErrorHandler) -> ParseResult<String> {
+pub fn parse_fragment<'a>(input: &'a str, parse_error: ErrorHandler) -> ParseResult<String> {
     let mut fragment = String::new();
     for (i, c) in input.char_indices() {
         match c {

+ 2 - 0
src/punycode.rs

@@ -6,6 +6,8 @@
 // option. This file may not be copied, modified, or distributed
 // except according to those terms.
 
+/// Punycode implementation: http://tools.ietf.org/html/rfc3492
+
 use std::u32;
 use std::char;
 use std::str;

+ 133 - 3
src/url.rs

@@ -18,6 +18,9 @@ extern crate serialize;
 use std::cmp;
 use std::str::from_utf8_lossy;
 
+use encoding::all::UTF_8;
+use encoding::types::EncodingRef;
+
 
 mod parser;
 pub mod form_urlencoded;
@@ -87,16 +90,143 @@ pub type ParseResult<T> = Result<T, &'static str>;
 /// FIXME: make this a by-ref closure when that’s supported.
 pub type ErrorHandler = fn(reason: &'static str) -> ParseResult<()>;
 
+fn silent_handler(_reason: &'static str) -> ParseResult<()> {
+    Ok(())
+}
+
 
 impl Url {
     pub fn parse(input: &str, base_url: Option<&Url>)
                  -> ParseResult<Url> {
-        fn silent_handler(_reason: &'static str) -> ParseResult<()> {
-            Ok(())
-        }
         parser::parse_url(input, base_url, silent_handler)
     }
 
+    /// `URLUtils.protocol` setter
+    pub fn set_scheme(&mut self, input: &str) -> ParseResult<()> {
+        match parser::parse_scheme(input.as_slice(), /* in_setter = */ true) {
+            Some((scheme, _)) => {
+                self.scheme = scheme;
+                Ok(())
+            },
+            None => Err("Invalid scheme"),
+        }
+    }
+
+    /// `URLUtils.username` setter
+    pub fn set_username(&mut self, input: &str) -> ParseResult<()> {
+        match self.scheme_data {
+            RelativeSchemeData(SchemeRelativeUrl { ref mut username, .. }) => {
+                username.truncate(0);
+                utf8_percent_encode(input, UsernameEncodeSet, username);
+                Ok(())
+            },
+            OtherSchemeData(_) => Err("Can not set username on non-relative URL.")
+        }
+    }
+
+    /// `URLUtils.password` setter
+    pub fn set_password(&mut self, input: &str) -> ParseResult<()> {
+        match self.scheme_data {
+            RelativeSchemeData(SchemeRelativeUrl { ref mut password, .. }) => {
+                let mut new_password = String::new();
+                utf8_percent_encode(input, PasswordEncodeSet, &mut new_password);
+                *password = Some(new_password);
+                Ok(())
+            },
+            OtherSchemeData(_) => Err("Can not set password on non-relative URL.")
+        }
+    }
+
+    /// `URLUtils.host` setter
+    pub fn set_host_and_port(&mut self, input: &str) -> ParseResult<()> {
+        match self.scheme_data {
+            RelativeSchemeData(SchemeRelativeUrl { ref mut host, ref mut port, .. }) => {
+                let (new_host, new_port, _) = try!(parser::parse_hostname(
+                    input, self.scheme.as_slice(), silent_handler, /* skip_port = */ false));
+                *host = new_host;
+                *port = new_port;
+                Ok(())
+            },
+            OtherSchemeData(_) => Err("Can not set host/port on non-relative URL.")
+        }
+    }
+
+    /// `URLUtils.hostname` setter
+    pub fn set_host(&mut self, input: &str) -> ParseResult<()> {
+        match self.scheme_data {
+            RelativeSchemeData(SchemeRelativeUrl { ref mut host, .. }) => {
+                let (new_host, _, _) = try!(parser::parse_hostname(
+                    input, self.scheme.as_slice(), silent_handler, /* skip_port = */ true));
+                *host = new_host;
+                Ok(())
+            },
+            OtherSchemeData(_) => Err("Can not set host on non-relative URL.")
+        }
+    }
+
+    /// `URLUtils.port` setter
+    pub fn set_port(&mut self, input: &str) -> ParseResult<()> {
+        match self.scheme_data {
+            RelativeSchemeData(SchemeRelativeUrl { ref mut port, .. }) => {
+                if self.scheme.as_slice() == "file" {
+                    return Err("Can not set port on file: URL.")
+                }
+                let (new_port, _) = try!(parser::parse_port(
+                    input, self.scheme.as_slice(), silent_handler));
+                *port = new_port;
+                Ok(())
+            },
+            OtherSchemeData(_) => Err("Can not set port on non-relative URL.")
+        }
+    }
+
+    /// `URLUtils.pathname` setter
+    pub fn set_path(&mut self, input: &str) -> ParseResult<()> {
+        match self.scheme_data {
+            RelativeSchemeData(SchemeRelativeUrl { ref mut path, .. }) => {
+                let (new_path, _) = try!(parser::parse_path_start(
+                    input, /* full_url = */ false,
+                    self.scheme.as_slice() == "file", silent_handler));
+                *path = new_path;
+                Ok(())
+            },
+            OtherSchemeData(_) => Err("Can not set path on non-relative URL.")
+        }
+    }
+
+    /// `URLUtils.search` setter
+    pub fn set_query(&mut self, input: &str) -> ParseResult<()> {
+        // FIXME: This is in the spec, but seems superfluous.
+        match self.scheme_data {
+            RelativeSchemeData(_) => (),
+            OtherSchemeData(_) => return Err("Can not set query on non-relative URL.")
+        }
+        self.query = if input.is_empty() {
+            None
+        } else {
+            let input = if input.starts_with("?") { input.slice_from(1) } else { input };
+            let encoding_override = UTF_8 as EncodingRef;  // TODO
+            let (new_query, _) = try!(parser::parse_query(
+                input, encoding_override, /* full_url = */ false, silent_handler));
+            Some(new_query)
+        };
+        Ok(())
+    }
+
+    /// `URLUtils.hash` setter
+    pub fn set_fragment(&mut self, input: &str) -> ParseResult<()> {
+        if self.scheme.as_slice() == "javascript" {
+            return Err("Can not set fragment on a javascript: URL.")
+        }
+        self.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, silent_handler)))
+        };
+        Ok(())
+    }
+
     pub fn serialize(&self) -> String {
         let mut result = self.serialize_no_fragment();
         match self.fragment {