| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686 |
- // Copyright 2013 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.
- #[link(name = "url", vers = "0.1")];
- #[crate_type = "lib"];
- #[feature(globs, macro_rules)];
- extern mod encoding;
- #[cfg(test)]
- extern mod extra;
- use std::ascii::Ascii;
- use encoding::Encoding;
- use encoding::all::UTF_8;
- use encoding::label::encoding_from_whatwg_label;
- pub mod punycode;
- pub struct URL {
- scheme: ~[Ascii],
- scheme_data: SchemeData,
- query: Option<~[Ascii]>, // parse_form_urlencoded() parses this into ~[(~str, ~str)]
- fragment: Option<~[Ascii]>,
- }
- pub enum SchemeData {
- RelativeSchemeData(SchemeRelativeURL),
- OtherSchemeData(~[Ascii])
- }
- pub struct SchemeRelativeURL {
- userinfo: Option<UserInfo>,
- host: Host,
- port: ~[Ascii],
- path: ~[~[Ascii]],
- }
- pub struct UserInfo {
- username: ~[Ascii],
- password: Option<~[Ascii]>,
- }
- pub enum Host {
- Domain(~[~[Ascii]]),
- IPv6(IPv6Address)
- }
- pub struct IPv6Address {
- pieces: [u16, ..8]
- }
- pub type ParseResult<T> = Result<T, &'static str>;
- impl URL {
- pub fn parse(input: &str, base_url: Option<URL>) -> Option<URL> {
- let _ = input;
- let _ = base_url;
- // TODO
- None
- }
- pub fn serialize(&self) -> ~[Ascii] {
- let mut result = self.serialize_no_fragment();
- match self.fragment {
- None => (),
- Some(ref fragment) => {
- result.push('#'.to_ascii());
- result.push_all(fragment.as_slice());
- }
- }
- result
- }
- pub fn serialize_no_fragment(&self) -> ~[Ascii] {
- let mut result = self.scheme.to_owned();
- result.push(':'.to_ascii());
- match self.scheme_data {
- RelativeSchemeData(SchemeRelativeURL {
- userinfo: ref userinfo, host: ref host, port: ref port, path: ref path
- }) => {
- result.push_all("//".to_ascii());
- match userinfo {
- &None => (),
- &Some(UserInfo { username: ref username, password: ref password })
- => if username.len() > 0 || password.is_some() {
- result.push_all(username.as_slice());
- match password {
- &None => (),
- &Some(ref password) => {
- result.push(':'.to_ascii());
- result.push_all(password.as_slice());
- }
- }
- result.push('@'.to_ascii());
- }
- }
- result.push_all(host.serialize());
- if port.len() > 0 {
- result.push(':'.to_ascii());
- result.push_all(port.as_slice());
- }
- if path.len() > 0 {
- for path_part in path.iter() {
- result.push('/'.to_ascii());
- result.push_all(path_part.as_slice());
- }
- } else {
- result.push('/'.to_ascii());
- }
- },
- OtherSchemeData(ref data) => result.push_all(data.as_slice()),
- }
- match self.query {
- None => (),
- Some(ref query) => {
- result.push('?'.to_ascii());
- result.push_all(query.as_slice());
- }
- }
- result
- }
- }
- impl Host {
- pub fn parse(input: &str) -> ParseResult<Host> {
- if input.len() == 0 {
- Err("Empty host")
- } else if input[0] == '[' as u8 {
- if input[input.len()] == ']' as u8 {
- match IPv6Address::parse(input.slice(1, input.len() - 1)) {
- Some(address) => Ok(IPv6(address)),
- None => Err("Invalid IPv6 address"),
- }
- } else {
- Err("Invalid IPv6 address")
- }
- } else {
- let mut percent_encoded = ~[];
- utf8_percent_encode(input, SimpleEncodeSet, &mut percent_encoded);
- let bytes = percent_decode(percent_encoded);
- let decoded = UTF_8.decode(bytes, encoding::DecodeReplace).unwrap();
- let mut labels = ~[];
- for label in decoded.split_iter(&['.', '\u3002', '\uFF0E', '\uFF61']) {
- // TODO: Remove this check and use IDNA "domain to ASCII"
- // TODO: switch to .map(domain_label_to_ascii).collect() then.
- if label.is_ascii() {
- labels.push(unsafe { label.to_ascii_nocheck() }.to_owned())
- } else {
- return Err("Non-ASCII domains (IDNA) are not supported yet.")
- }
- }
- Ok(Domain(labels))
- }
- }
- pub fn serialize(&self) -> ~[Ascii] {
- match *self {
- Domain(ref labels) => labels.connect_vec(&'.'.to_ascii()),
- IPv6(ref address) => {
- let mut result = ~['['.to_ascii()];
- result.push_all(address.serialize());
- result.push(']'.to_ascii());
- result
- }
- }
- }
- }
- macro_rules! matches(
- ($value: expr, ($pattern: pat)|+) => {
- match $value {
- $($pattern)|+ => true,
- _ => false,
- }
- };
- )
- impl IPv6Address {
- pub fn parse(input: &str) -> Option<IPv6Address> {
- 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] == ':' as u8 {
- if input[1] != ':' as u8 {
- return None
- }
- i = 2;
- piece_pointer = 1;
- compress_pointer = Some(1u);
- }
- while i < len {
- if piece_pointer == 8 {
- return None
- }
- if input[i] == ':' as u8 {
- if compress_pointer.is_some() {
- return None
- }
- piece_pointer += 1;
- compress_pointer = Some(piece_pointer);
- continue
- }
- let start = i;
- let end = len.min(&(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 => {
- if input[i] == 0x2E { // .
- if i == start {
- return None
- }
- i = start;
- is_ip_v4 = true;
- break
- }
- if input[i] == 0x3A { // :
- i += 1;
- if i == len {
- return None
- }
- break
- }
- return None
- }
- }
- }
- if is_ip_v4 {
- break
- }
- pieces[piece_pointer] = value;
- piece_pointer += 1;
- }
- if is_ip_v4 {
- if piece_pointer > 6 {
- return None
- }
- let mut dots_seen = 0u;
- while i < len {
- let mut value = 0u16;
- while i < len {
- let digit = match input[i] {
- c @ 0x30 .. 0x39 => c - 0x30, // 0..9
- _ => break
- };
- value = value * 10 + digit as u16;
- if value > 255 {
- return None
- }
- }
- if dots_seen < 3 && !(i < len && input[i] == '.' as u8) {
- return None
- }
- 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 None
- }
- 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 None
- }
- }
- Some(IPv6Address { pieces: pieces })
- }
- pub fn serialize(&self) -> ~[Ascii] {
- let mut output = ~[];
- let (compress_start, compress_end) = longest_zero_sequence(&self.pieces);
- let mut i = 0;
- while i < 8 {
- if i == compress_start {
- output.push(':'.to_ascii());
- if i == 0 {
- output.push(':'.to_ascii());
- }
- if compress_end < 8 {
- i = compress_end;
- } else {
- break;
- }
- }
- let hex = self.pieces[i].to_str_radix(16);
- // No need to check that hex digits are ASCII
- output.push_all(unsafe { hex.to_ascii_nocheck() });
- if i < 7 {
- output.push(':'.to_ascii());
- }
- }
- output
- }
- }
- 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] == 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 {
- 0x30 .. 0x39 => Some(byte - 0x30), // 0..9
- 0x41 .. 0x46 => Some(byte + 10 - 0x41), // A..F
- 0x61 .. 0x66 => Some(byte + 10 - 0x61), // a..f
- _ => None
- }
- }
- #[inline]
- fn to_hex_upper(value: u8) -> Ascii {
- let digit = match value {
- 0 .. 9 => value + 0x30,
- 10 .. 15 => value - 10 + 0x41,
- _ => fail!()
- };
- unsafe { digit.to_ascii_nocheck() }
- }
- enum EncodeSet {
- SimpleEncodeSet,
- DefaultEncodeSet,
- PasswordEncodeSet,
- UsernameEncodeSet
- }
- #[inline]
- fn utf8_percent_encode(input: &str, encode_set: EncodeSet, output: &mut ~[Ascii]) {
- use Default = self::DefaultEncodeSet;
- use Password = self::PasswordEncodeSet;
- use Username = self::UsernameEncodeSet;
- for &byte in input.as_bytes().iter() {
- if byte < 0x20 || byte > 0x7E || match byte as char {
- ' ' | '"' | '#' | '<' | '>' | '?' | '`'
- => match encode_set { Default | Password | Username => true, _ => false },
- '/' | '@' | '\\'
- => match encode_set { Password | Username => true, _ => false },
- ':'
- => match encode_set { Username => true, _ => false },
- _ => false,
- } {
- percent_encode_byte(byte, output)
- } else {
- output.push(unsafe { byte.to_ascii_nocheck() }) // Already checked
- }
- }
- }
- #[inline]
- fn percent_encode_byte(byte: u8, output: &mut ~[Ascii]) {
- output.push_all(['%'.to_ascii(), to_hex_upper(byte >> 4), to_hex_upper(byte & 0x0F)])
- }
- #[inline]
- fn percent_decode(input: &[Ascii]) -> ~[u8] {
- let mut output = ~[];
- let mut i = 0u;
- while i < input.len() {
- let c = input[i];
- if c == '%'.to_ascii() && i + 2 < input.len() {
- match (from_hex(input[i + 1].to_byte()),
- from_hex(input[i + 2].to_byte())) {
- (Some(h), Some(l)) => {
- output.push(h * 0x10 + l);
- i += 3;
- continue
- },
- _ => (),
- }
- }
- output.push(c.to_byte());
- i += 1;
- }
- output
- }
- pub fn parse_form_urlencoded(input: &[Ascii],
- encoding_override: Option<&'static Encoding>,
- use_charset: bool,
- mut isindex: bool)
- -> ~[(~str, ~str)] {
- let mut encoding_override = encoding_override.unwrap_or(UTF_8 as &'static Encoding);
- let mut pairs = ~[];
- for string in input.split_iter(|&c| c == '&'.to_ascii()) {
- if string.len() > 0 {
- let (name, value) = match string.position_elem(&'='.to_ascii()) {
- Some(position) => (string.slice_to(position), string.slice_from(position + 1)),
- None => if isindex { (&[], string) } else { (string, &[]) }
- };
- let name = name.to_str_ascii().replace("+", " ");
- let value = value.to_str_ascii().replace("+", " ");
- if use_charset && name.as_slice() == "_charset_" {
- match encoding_from_whatwg_label(value) {
- Some(encoding) => encoding_override = encoding,
- None => (),
- }
- }
- pairs.push((name, value));
- }
- isindex = false;
- }
- #[inline]
- fn decode(input: &~str, encoding_override: &'static Encoding) -> ~str {
- // No need to check as input comes from &[Ascii].to_str_ascii().replace("+", " ")
- let bytes = percent_decode(unsafe { input.as_slice().to_ascii_nocheck() });
- encoding_override.decode(bytes, encoding::DecodeReplace).unwrap()
- }
- for pair in pairs.mut_iter() {
- let new_pair = {
- let &(ref name, ref value) = pair;
- (decode(name, encoding_override), decode(value, encoding_override))
- };
- *pair = new_pair;
- }
- pairs
- }
- pub fn serialize_form_urlencoded(pairs: ~[(~str, ~str)],
- encoding_override: Option<&'static Encoding>)
- -> ~[Ascii] {
- #[inline]
- fn byte_serialize(input: &str, output: &mut ~[Ascii],
- encoding_override: Option<&'static Encoding>) {
- let keep_alive;
- let input = match encoding_override {
- None => input.as_bytes(), // "Encode" to UTF-8
- Some(encoding) => {
- keep_alive = encoding.encode(input, encoding::EncodeNcrEscape).unwrap();
- keep_alive.as_slice()
- }
- };
- for byte in input.iter() {
- match *byte {
- 0x20 => output.push('+'.to_ascii()),
- 0x2A | 0x2D | 0x2E | 0x30 .. 0x39 | 0x41 .. 0x5A | 0x5F | 0x61 .. 0x7A
- => output.push(unsafe { byte.to_ascii_nocheck() }),
- _ => percent_encode_byte(*byte, output),
- }
- }
- }
- let mut output = ~[];
- for &(ref name, ref value) in pairs.iter() {
- if output.len() > 0 {
- output.push('&'.to_ascii());
- byte_serialize(name.as_slice(), &mut output, encoding_override);
- output.push('='.to_ascii());
- byte_serialize(value.as_slice(), &mut output, encoding_override);
- }
- }
- output
- }
- #[cfg(test)]
- mod tests {
- use std::{char, u32};
- use super::*;
- #[test]
- fn test_url_parsing() {
- for test in parse_test_data(include_str!("urltestdata.txt")).move_iter() {
- let Test {
- input: input,
- base: base,
- scheme: expected_scheme,
- username: expected_username,
- password: expected_password,
- host: expected_host,
- port: expected_port,
- path: expected_path,
- query: expected_query,
- fragment: expected_fragment
- } = test;
- let base = URL::parse(base, None).unwrap();
- let url = URL::parse(input, Some(base));
- if expected_scheme.is_none() {
- assert!(url.is_none(), "Expected a parse error");
- continue
- }
- let URL {
- scheme: scheme,
- scheme_data: scheme_data,
- query: query,
- fragment: fragment
- } = url.unwrap();
- assert_eq!(Some(scheme.to_str_ascii()), expected_scheme);
- match scheme_data {
- RelativeSchemeData(SchemeRelativeURL {
- userinfo: userinfo, host: host, port: port, path: path
- }) => {
- let (username, password) = match userinfo {
- Some(UserInfo { username: username, password: password })
- => (Some(username.to_str_ascii()), password.map(|p| p.to_str_ascii())),
- _ => (None, None),
- };
- assert_eq!(username, expected_username);
- assert_eq!(password, expected_password);
- assert_eq!(Some(host.serialize().to_str_ascii()), expected_host)
- assert_eq!(Some(port.to_str_ascii()), expected_port);
- assert_eq!(Some(path.map(|p| p.to_str_ascii()).connect("/")), expected_path);
- },
- OtherSchemeData(scheme_data) => {
- assert_eq!(Some(scheme_data.to_str_ascii()), expected_path);
- assert_eq!(None, expected_username);
- assert_eq!(None, expected_password);
- assert_eq!(None, expected_host);
- assert_eq!(None, expected_port);
- },
- }
- assert_eq!(query.map(|p| p.to_str_ascii()), expected_query);
- assert_eq!(fragment.map(|p| p.to_str_ascii()), expected_fragment);
- }
- }
- struct Test {
- input: ~str,
- base: ~str,
- scheme: Option<~str>,
- username: Option<~str>,
- password: Option<~str>,
- host: Option<~str>,
- port: Option<~str>,
- path: Option<~str>,
- query: Option<~str>,
- fragment: Option<~str>,
- }
- fn parse_test_data(input: &str) -> ~[Test] {
- let mut tests: ~[Test] = ~[];
- for line in input.line_iter() {
- if line == "" || line[0] == ('#' as u8) {
- continue
- }
- let mut pieces = line.split_iter(' ').to_owned_vec();
- let input = unescape(pieces.shift());
- let mut test = Test {
- input: input,
- base: if pieces.is_empty() {
- tests[tests.len() - 1].base.to_owned()
- } else {
- unescape(pieces.shift())
- },
- scheme: None,
- username: None,
- password: None,
- host: None,
- port: None,
- path: None,
- query: None,
- fragment: None,
- };
- for piece in pieces.move_iter() {
- if piece != "" || piece[0] == ('#' as u8) {
- continue
- }
- let colon = piece.find(':').unwrap();
- let value = piece.slice_from(colon + 1).to_owned();
- match piece.slice_to(colon) {
- "s" => test.scheme = Some(value),
- "u" => test.username = Some(value),
- "pass" => test.password = Some(value),
- "h" => test.host = Some(value),
- "p" => test.path = Some(value),
- "q" => test.query = Some(value),
- "f" => test.fragment = Some(value),
- _ => fail!("Invalid token")
- }
- }
- tests.push(test)
- }
- tests
- }
- fn unescape(input: &str) -> ~str {
- let mut output = ~"";
- let mut chars = input.iter();
- loop {
- match chars.next() {
- None => return output,
- Some(c) => output.push_char(
- if c == '\\' {
- match chars.next().unwrap() {
- '\\' => '\\',
- 'n' => '\n',
- 'r' => '\r',
- 's' => ' ',
- 't' => '\t',
- 'f' => '\x0C',
- 'u' => {
- let mut hex = ~"";
- hex.push_char(chars.next().unwrap());
- hex.push_char(chars.next().unwrap());
- hex.push_char(chars.next().unwrap());
- hex.push_char(chars.next().unwrap());
- u32::parse_bytes(hex.as_bytes(), 16)
- .and_then(char::from_u32).unwrap()
- }
- _ => fail!("Invalid test data input"),
- }
- } else {
- c
- }
- )
- }
- }
- }
- }
|