#![feature(macro_rules, default_type_params)]
extern crate serialize;
use std::fmt::{mod, Formatter, Show};
use std::hash;
use std::path;
pub use host::{Host, Ipv6Address};
pub use parser::{ErrorHandler, ParseResult, ParseError};
#[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,
};
use format::{PathFormatter, UserInfoFormatter, UrlNoFragmentFormatter};
use encoding::EncodingOverride;
mod encoding;
mod host;
mod parser;
mod urlutils;
pub mod percent_encoding;
pub mod form_urlencoded;
pub mod punycode;
pub mod format;
#[cfg(test)]
mod tests;
#[deriving(PartialEq, Eq, Clone)]
pub struct Url {
pub scheme: String,
pub scheme_data: SchemeData,
pub query: Option<String>,
pub fragment: Option<String>,
}
#[deriving(PartialEq, Eq, Clone)]
pub enum SchemeData {
Relative(RelativeSchemeData),
NonRelative(String),
}
#[deriving(PartialEq, Eq, Clone)]
pub struct RelativeSchemeData {
pub username: String,
pub password: Option<String>,
pub host: Host,
pub port: Option<u16>,
pub default_port: Option<u16>,
pub path: Vec<String>,
}
impl<S: hash::Writer> hash::Hash<S> for Url {
fn hash(&self, state: &mut S) {
self.serialize().hash(state)
}
}
pub struct UrlParser<'a> {
base_url: Option<&'a Url>,
query_encoding_override: EncodingOverride,
error_handler: ErrorHandler,
scheme_type_mapper: fn(scheme: &str) -> SchemeType,
}
impl<'a> UrlParser<'a> {
#[inline]
pub fn new() -> UrlParser<'a> {
fn silent_handler(_reason: ParseError) -> ParseResult<()> { Ok(()) }
UrlParser {
base_url: None,
query_encoding_override: EncodingOverride::utf8(),
error_handler: silent_handler,
scheme_type_mapper: whatwg_scheme_type_mapper,
}
}
#[inline]
pub fn base_url<'b>(&'b mut self, value: &'a Url) -> &'b mut UrlParser<'a> {
self.base_url = Some(value);
self
}
#[cfg(feature = "query_encoding")]
#[inline]
pub fn query_encoding_override<'b>(&'b mut self, value: encoding::EncodingRef)
-> &'b mut UrlParser<'a> {
self.query_encoding_override = EncodingOverride::from_encoding(value);
self
}
#[inline]
pub fn error_handler<'b>(&'b mut self, value: ErrorHandler) -> &'b mut UrlParser<'a> {
self.error_handler = value;
self
}
#[inline]
pub fn scheme_type_mapper<'b>(&'b mut self, value: fn(scheme: &str) -> SchemeType)
-> &'b mut UrlParser<'a> {
self.scheme_type_mapper = value;
self
}
#[inline]
pub fn parse(&self, input: &str) -> ParseResult<Url> {
parser::parse_url(input, self)
}
#[inline]
pub fn parse_path(&self, input: &str)
-> ParseResult<(Vec<String>, Option<String>, Option<String>)> {
parser::parse_standalone_path(input, self)
}
}
#[inline]
pub fn parse_path(input: &str)
-> ParseResult<(Vec<String>, Option<String>, Option<String>)> {
UrlParser::new().parse_path(input)
}
impl<'a> UrlParser<'a> {
#[inline]
fn parse_error(&self, error: ParseError) -> ParseResult<()> {
(self.error_handler)(error)
}
#[inline]
fn get_scheme_type(&self, scheme: &str) -> SchemeType {
(self.scheme_type_mapper)(scheme)
}
}
#[deriving(PartialEq, Eq)]
pub enum SchemeType {
NonRelative,
Relative(u16),
FileLike,
}
impl SchemeType {
pub fn default_port(&self) -> Option<u16> {
match self {
&SchemeType::Relative(default_port) => Some(default_port),
_ => None,
}
}
}
pub fn whatwg_scheme_type_mapper(scheme: &str) -> SchemeType {
match scheme {
"file" => SchemeType::FileLike,
"ftp" => SchemeType::Relative(21),
"gopher" => SchemeType::Relative(70),
"http" => SchemeType::Relative(80),
"https" => SchemeType::Relative(443),
"ws" => SchemeType::Relative(80),
"wss" => SchemeType::Relative(443),
_ => SchemeType::NonRelative,
}
}
impl Url {
#[inline]
pub fn parse(input: &str) -> ParseResult<Url> {
UrlParser::new().parse(input)
}
pub fn from_file_path<T: ToUrlPath>(path: &T) -> Result<Url, ()> {
let path = try!(path.to_url_path());
Ok(Url::from_path_common(path))
}
pub fn from_directory_path<T: ToUrlPath>(path: &T) -> Result<Url, ()> {
let mut path = try!(path.to_url_path());
path.push("".to_string());
Ok(Url::from_path_common(path))
}
fn from_path_common(path: Vec<String>) -> Url {
Url {
scheme: "file".to_string(),
scheme_data: SchemeData::Relative(RelativeSchemeData {
username: "".to_string(),
password: None,
port: None,
default_port: None,
host: Host::Domain("".to_string()),
path: path,
}),
query: None,
fragment: None,
}
}
#[inline]
pub fn to_file_path<T: FromUrlPath>(&self) -> Result<T, ()> {
match self.scheme_data {
SchemeData::Relative(ref scheme_data) => scheme_data.to_file_path(),
SchemeData::NonRelative(..) => Err(()),
}
}
pub fn serialize(&self) -> String {
self.to_string()
}
pub fn serialize_no_fragment(&self) -> String {
UrlNoFragmentFormatter{ url: self }.to_string()
}
#[inline]
pub fn non_relative_scheme_data<'a>(&'a self) -> Option<&'a str> {
match self.scheme_data {
SchemeData::Relative(..) => None,
SchemeData::NonRelative(ref scheme_data) => Some(scheme_data.as_slice()),
}
}
#[inline]
pub fn non_relative_scheme_data_mut<'a>(&'a mut self) -> Option<&'a mut String> {
match self.scheme_data {
SchemeData::Relative(..) => None,
SchemeData::NonRelative(ref mut scheme_data) => Some(scheme_data),
}
}
#[inline]
pub fn relative_scheme_data<'a>(&'a self) -> Option<&'a RelativeSchemeData> {
match self.scheme_data {
SchemeData::Relative(ref scheme_data) => Some(scheme_data),
SchemeData::NonRelative(..) => None,
}
}
#[inline]
pub fn relative_scheme_data_mut<'a>(&'a mut self) -> Option<&'a mut RelativeSchemeData> {
match self.scheme_data {
SchemeData::Relative(ref mut scheme_data) => Some(scheme_data),
SchemeData::NonRelative(..) => None,
}
}
#[inline]
pub fn username<'a>(&'a self) -> Option<&'a str> {
self.relative_scheme_data().map(|scheme_data| scheme_data.username.as_slice())
}
#[inline]
pub fn username_mut<'a>(&'a mut self) -> Option<&'a mut String> {
self.relative_scheme_data_mut().map(|scheme_data| &mut scheme_data.username)
}
#[inline]
pub fn lossy_percent_decode_username(&self) -> Option<String> {
self.relative_scheme_data().map(|scheme_data| scheme_data.lossy_percent_decode_username())
}
#[inline]
pub fn password<'a>(&'a self) -> Option<&'a str> {
self.relative_scheme_data().and_then(|scheme_data|
scheme_data.password.as_ref().map(|password| password.as_slice()))
}
#[inline]
pub fn password_mut<'a>(&'a mut self) -> Option<&'a mut String> {
self.relative_scheme_data_mut().and_then(|scheme_data| scheme_data.password.as_mut())
}
#[inline]
pub fn lossy_percent_decode_password(&self) -> Option<String> {
self.relative_scheme_data().and_then(|scheme_data|
scheme_data.lossy_percent_decode_password())
}
#[inline]
pub fn serialize_userinfo<'a>(&'a mut self) -> Option<String> {
self.relative_scheme_data().map(|scheme_data| scheme_data.serialize_userinfo())
}
#[inline]
pub fn host<'a>(&'a self) -> Option<&'a Host> {
self.relative_scheme_data().map(|scheme_data| &scheme_data.host)
}
#[inline]
pub fn host_mut<'a>(&'a mut self) -> Option<&'a mut Host> {
self.relative_scheme_data_mut().map(|scheme_data| &mut scheme_data.host)
}
#[inline]
pub fn domain<'a>(&'a self) -> Option<&'a str> {
self.relative_scheme_data().and_then(|scheme_data| scheme_data.domain())
}
#[inline]
pub fn domain_mut<'a>(&'a mut self) -> Option<&'a mut String> {
self.relative_scheme_data_mut().and_then(|scheme_data| scheme_data.domain_mut())
}
#[inline]
pub fn serialize_host(&self) -> Option<String> {
self.relative_scheme_data().map(|scheme_data| scheme_data.host.serialize())
}
#[inline]
pub fn port<'a>(&'a self) -> Option<u16> {
self.relative_scheme_data().and_then(|scheme_data| scheme_data.port)
}
#[inline]
pub fn port_mut<'a>(&'a mut self) -> Option<&'a mut Option<u16>> {
self.relative_scheme_data_mut().map(|scheme_data| &mut scheme_data.port)
}
#[inline]
pub fn port_or_default(&self) -> Option<u16> {
self.relative_scheme_data().and_then(|scheme_data| scheme_data.port_or_default())
}
#[inline]
pub fn path<'a>(&'a self) -> Option<&'a [String]> {
self.relative_scheme_data().map(|scheme_data| scheme_data.path.as_slice())
}
#[inline]
pub fn path_mut<'a>(&'a mut self) -> Option<&'a mut Vec<String>> {
self.relative_scheme_data_mut().map(|scheme_data| &mut scheme_data.path)
}
#[inline]
pub fn serialize_path(&self) -> Option<String> {
self.relative_scheme_data().map(|scheme_data| scheme_data.serialize_path())
}
#[inline]
pub fn query_pairs(&self) -> Option<Vec<(String, String)>> {
self.query.as_ref().map(|query| form_urlencoded::parse(query.as_bytes()))
}
#[inline]
pub fn set_query_from_pairs<'a, I: Iterator<(&'a str, &'a str)>>(&mut self, pairs: I) {
self.query = Some(form_urlencoded::serialize(pairs));
}
#[inline]
pub fn lossy_percent_decode_query(&self) -> Option<String> {
self.query.as_ref().map(|value| lossy_utf8_percent_decode(value.as_bytes()))
}
#[inline]
pub fn lossy_percent_decode_fragment(&self) -> Option<String> {
self.fragment.as_ref().map(|value| lossy_utf8_percent_decode(value.as_bytes()))
}
}
impl<E, S: serialize::Encoder<E>> serialize::Encodable<S, E> for Url {
fn encode(&self, encoder: &mut S) -> Result<(), E> {
encoder.emit_str(self.to_string().as_slice())
}
}
impl<E, D: serialize::Decoder<E>> serialize::Decodable<D, E> for Url {
fn decode(decoder: &mut D) -> Result<Url, E> {
Url::parse(try!(decoder.read_str()).as_slice()).map_err(|error| {
decoder.error(format!("URL parsing error: {}", error).as_slice())
})
}
}
impl Show for Url {
fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
try!(UrlNoFragmentFormatter{ url: self }.fmt(formatter));
match self.fragment {
None => (),
Some(ref fragment) => {
try!(formatter.write(b"#"));
try!(formatter.write(fragment.as_bytes()));
}
}
Ok(())
}
}
impl Show for SchemeData {
fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
match *self {
SchemeData::Relative(ref scheme_data) => scheme_data.fmt(formatter),
SchemeData::NonRelative(ref scheme_data) => scheme_data.fmt(formatter),
}
}
}
impl RelativeSchemeData {
#[inline]
pub fn lossy_percent_decode_username(&self) -> String {
lossy_utf8_percent_decode(self.username.as_bytes())
}
#[inline]
pub fn lossy_percent_decode_password(&self) -> Option<String> {
self.password.as_ref().map(|value| lossy_utf8_percent_decode(value.as_bytes()))
}
#[inline]
pub fn to_file_path<T: FromUrlPath>(&self) -> Result<T, ()> {
match self.domain() {
Some("") | Some("localhost") => FromUrlPath::from_url_path(self.path.as_slice()),
_ => Err(())
}
}
#[inline]
pub fn domain<'a>(&'a self) -> Option<&'a str> {
match self.host {
Host::Domain(ref domain) => Some(domain.as_slice()),
_ => None,
}
}
#[inline]
pub fn domain_mut<'a>(&'a mut self) -> Option<&'a mut String> {
match self.host {
Host::Domain(ref mut domain) => Some(domain),
_ => None,
}
}
#[inline]
pub fn port_or_default(&self) -> Option<u16> {
self.port.or(self.default_port)
}
pub fn serialize_path(&self) -> String {
PathFormatter {
path: self.path.as_slice()
}.to_string()
}
pub fn serialize_userinfo(&self) -> String {
UserInfoFormatter {
username: self.username.as_slice(),
password: self.password.as_ref().map(|s| s.as_slice())
}.to_string()
}
}
impl Show for RelativeSchemeData {
fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
try!(formatter.write(b"//"));
try!(UserInfoFormatter {
username: self.username.as_slice(),
password: self.password.as_ref().map(|s| s.as_slice())
}.fmt(formatter));
try!(self.host.fmt(formatter));
match self.port {
Some(port) => {
try!(write!(formatter, ":{}", port));
},
None => {}
}
PathFormatter {
path: self.path.as_slice()
}.fmt(formatter)
}
}
pub trait ToUrlPath {
fn to_url_path(&self) -> Result<Vec<String>, ()>;
}
impl ToUrlPath for path::posix::Path {
fn to_url_path(&self) -> Result<Vec<String>, ()> {
if !self.is_absolute() {
return Err(())
}
Ok(self.components().map(|c| percent_encode(c, DEFAULT_ENCODE_SET)).collect())
}
}
impl ToUrlPath for path::windows::Path {
fn to_url_path(&self) -> Result<Vec<String>, ()> {
if !self.is_absolute() {
return Err(())
}
if path::windows::prefix(self) != Some(path::windows::DiskPrefix) {
return Err(())
}
let mut path = vec![self.as_str().unwrap().slice_to(2).to_string()];
for component in self.components() {
path.push(percent_encode(component, DEFAULT_ENCODE_SET));
}
Ok(path)
}
}
pub trait FromUrlPath {
fn from_url_path(path: &[String]) -> Result<Self, ()>;
}
impl FromUrlPath for path::posix::Path {
fn from_url_path(path: &[String]) -> Result<path::posix::Path, ()> {
if path.is_empty() {
return Ok(path::posix::Path::new("/"))
}
let mut bytes = Vec::new();
for path_part in path.iter() {
bytes.push(b'/');
percent_decode_to(path_part.as_bytes(), &mut bytes);
}
match path::posix::Path::new_opt(bytes) {
None => Err(()), Some(path) => {
debug_assert!(path.is_absolute(),
"to_file_path() failed to produce an absolute Path")
Ok(path)
}
}
}
}
impl FromUrlPath for path::windows::Path {
fn from_url_path(path: &[String]) -> Result<path::windows::Path, ()> {
if path.is_empty() {
return Err(())
}
let prefix = path[0].as_slice();
if prefix.len() != 2 || !parser::starts_with_ascii_alpha(prefix)
|| prefix.char_at(1) != ':' {
return Err(())
}
let mut bytes = prefix.as_bytes().to_vec();
for path_part in path.slice_from(1).iter() {
bytes.push(b'\\');
percent_decode_to(path_part.as_bytes(), &mut bytes);
}
match path::windows::Path::new_opt(bytes) {
None => Err(()), Some(path) => {
debug_assert!(path.is_absolute(),
"to_file_path() failed to produce an absolute Path")
debug_assert!(path::windows::prefix(&path) == Some(path::windows::DiskPrefix),
"to_file_path() failed to produce a Path with a disk prefix")
Ok(path)
}
}
}
}