lib.rs 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957
  1. // Copyright 2013-2014 Simon Sapin.
  2. //
  3. // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
  4. // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
  5. // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
  6. // option. This file may not be copied, modified, or distributed
  7. // except according to those terms.
  8. #![crate_name = "url"]
  9. /*!
  10. <a href="https://github.com/servo/rust-url"><img style="position: absolute; top: 0; left: 0; border: 0;" src="../github.png" alt="Fork me on GitHub"></a>
  11. <style>.sidebar { margin-top: 53px }</style>
  12. rust-url is an implementation of the [URL Standard](http://url.spec.whatwg.org/)
  13. for the [Rust](http://rust-lang.org/) programming language.
  14. It builds with [Cargo](http://crates.io/).
  15. To use it in your project, add this to your `Cargo.toml` file:
  16. ```Cargo
  17. [dependencies.url]
  18. git = "https://github.com/servo/rust-url"
  19. ```
  20. This will automatically pull in the
  21. [rust-encoding](https://github.com/lifthrasiir/rust-encoding) dependency.
  22. rust-url is a replacement of the [`url` crate](http://doc.rust-lang.org/url/index.html)
  23. currently distributed with Rust.
  24. rust-url’s crate is also named `url`.
  25. Cargo will automatically resolve the name conflict,
  26. but that means that you can not also use the old `url` in the same crate.
  27. If you’re not using Cargo, you’ll need to pass `--extern url=/path/to/liburl.rlib`
  28. explicitly to rustc.
  29. # URL parsing and data structures
  30. First, URL parsing may fail for various reasons and therefore returns a `Result`.
  31. ```
  32. use url::{Url, InvalidIpv6Address};
  33. assert!(Url::parse("http://[:::1]") == Err(InvalidIpv6Address))
  34. ```
  35. Let’s parse a valid URL and look at its components.
  36. ```
  37. use url::{Url, RelativeSchemeData, NonRelativeSchemeData};
  38. let issue_list_url = Url::parse(
  39. "https://github.com/rust-lang/rust/issues?labels=E-easy&state=open"
  40. ).unwrap();
  41. assert!(issue_list_url.scheme == "https".to_string());
  42. assert!(issue_list_url.domain() == Some("github.com"));
  43. assert!(issue_list_url.port() == None);
  44. assert!(issue_list_url.path() == Some(&["rust-lang".to_string(),
  45. "rust".to_string(),
  46. "issues".to_string()]));
  47. assert!(issue_list_url.query == Some("labels=E-easy&state=open".to_string()));
  48. assert!(issue_list_url.fragment == None);
  49. match issue_list_url.scheme_data {
  50. RelativeSchemeData(..) => {}, // Expected
  51. NonRelativeSchemeData(..) => fail!(),
  52. }
  53. ```
  54. The `scheme`, `query`, and `fragment` are directly fields of the `Url` struct:
  55. they apply to all URLs.
  56. Every other components has accessors because they only apply to URLs said to be
  57. “in a relative scheme”. `https` is a relative scheme, but `data` is not:
  58. ```
  59. use url::{Url, NonRelativeSchemeData};
  60. let data_url = Url::parse("data:text/plain,Hello#").unwrap();
  61. assert!(data_url.scheme == "data".to_string());
  62. assert!(data_url.scheme_data == NonRelativeSchemeData("text/plain,Hello".to_string()));
  63. assert!(data_url.non_relative_scheme_data() == Some("text/plain,Hello"));
  64. assert!(data_url.query == None);
  65. assert!(data_url.fragment == Some("".to_string()));
  66. ```
  67. # Base URL
  68. Many contexts allow URL *references* that can be relative to a *base URL*:
  69. ```html
  70. <link rel="stylesheet" href="../main.css">
  71. ```
  72. Since parsed URL are absolute, giving a base is required:
  73. ```
  74. use url::{Url, RelativeUrlWithoutBase};
  75. assert!(Url::parse("../main.css") == Err(RelativeUrlWithoutBase))
  76. ```
  77. `UrlParser` is a method-chaining API to provide various optional parameters
  78. to URL parsing, including a base URL.
  79. ```
  80. use url::{Url, UrlParser};
  81. let this_document = Url::parse("http://servo.github.io/rust-url/url/index.html").unwrap();
  82. let css_url = UrlParser::new().base_url(&this_document).parse("../main.css").unwrap();
  83. assert!(css_url.serialize() == "http://servo.github.io/rust-url/main.css".to_string());
  84. ```
  85. */
  86. #![feature(macro_rules, default_type_params)]
  87. extern crate encoding;
  88. #[cfg(test)]
  89. extern crate serialize;
  90. use std::fmt::{Formatter, FormatError, Show};
  91. use std::hash;
  92. use std::path;
  93. use encoding::EncodingRef;
  94. pub use host::{Host, Domain, Ipv6, Ipv6Address};
  95. pub use parser::{
  96. ErrorHandler, ParseResult, ParseError,
  97. EmptyHost, InvalidScheme, InvalidPort, InvalidIpv6Address, InvalidDomainCharacter,
  98. InvalidCharacter, InvalidBackslash, InvalidPercentEncoded, InvalidAtSymbolInUser,
  99. ExpectedTwoSlashes, NonUrlCodePoint, RelativeUrlWithScheme, RelativeUrlWithoutBase,
  100. RelativeUrlWithNonRelativeBase, NonAsciiDomainsNotSupportedYet,
  101. CannotSetFileScheme, CannotSetJavascriptScheme, CannotSetNonRelativeScheme,
  102. };
  103. #[deprecated = "Moved to the `percent_encoding` module"]
  104. pub use percent_encoding::{
  105. percent_decode, percent_decode_to, percent_encode, percent_encode_to,
  106. utf8_percent_encode, utf8_percent_encode_to, lossy_utf8_percent_decode,
  107. SIMPLE_ENCODE_SET, QUERY_ENCODE_SET, DEFAULT_ENCODE_SET, USERINFO_ENCODE_SET,
  108. PASSWORD_ENCODE_SET, USERNAME_ENCODE_SET, FORM_URLENCODED_ENCODE_SET, EncodeSet,
  109. };
  110. use format::{PathFormatter, UserInfoFormatter, UrlNoFragmentFormatter};
  111. mod host;
  112. mod parser;
  113. mod urlutils;
  114. pub mod percent_encoding;
  115. pub mod form_urlencoded;
  116. pub mod punycode;
  117. pub mod format;
  118. #[cfg(test)]
  119. mod tests;
  120. /// The parsed representation of an absolute URL.
  121. #[deriving(PartialEq, Eq, Clone)]
  122. pub struct Url {
  123. /// The scheme (a.k.a. protocol) of the URL, in ASCII lower case.
  124. pub scheme: String,
  125. /// The components of the URL whose representation depends on where the scheme is *relative*.
  126. pub scheme_data: SchemeData,
  127. /// The query string of the URL.
  128. ///
  129. /// `None` if the `?` delimiter character was not part of the parsed input,
  130. /// otherwise a possibly empty, pecent-encoded string.
  131. ///
  132. /// Percent encoded strings are within the ASCII range.
  133. ///
  134. /// See also the `query_pairs`, `set_query_from_pairs`,
  135. /// and `lossy_percent_decode_query` methods.
  136. pub query: Option<String>,
  137. /// The fragment identifier of the URL.
  138. ///
  139. /// `None` if the `#` delimiter character was not part of the parsed input,
  140. /// otherwise a possibly empty, pecent-encoded string.
  141. ///
  142. /// Percent encoded strings are within the ASCII range.
  143. ///
  144. /// See also the `lossy_percent_decode_fragment` method.
  145. pub fragment: Option<String>,
  146. }
  147. /// The components of the URL whose representation depends on where the scheme is *relative*.
  148. #[deriving(PartialEq, Eq, Clone)]
  149. pub enum SchemeData {
  150. /// Components for URLs in a *relative* scheme such as HTTP.
  151. RelativeSchemeData(RelativeSchemeData),
  152. /// No further structure is assumed for *non-relative* schemes such as `data` and `mailto`.
  153. ///
  154. /// This is a single percent-encoded string, whose interpretation depends on the scheme.
  155. ///
  156. /// Percent encoded strings are within the ASCII range.
  157. NonRelativeSchemeData(String),
  158. }
  159. /// Components for URLs in a *relative* scheme such as HTTP.
  160. #[deriving(PartialEq, Eq, Clone)]
  161. pub struct RelativeSchemeData {
  162. /// The username of the URL, as a possibly empty, pecent-encoded string.
  163. ///
  164. /// Percent encoded strings are within the ASCII range.
  165. ///
  166. /// See also the `lossy_percent_decode_username` method.
  167. pub username: String,
  168. /// The password of the URL.
  169. ///
  170. /// `None` if the `:` delimiter character was not part of the parsed input,
  171. /// otherwise a possibly empty, pecent-encoded string.
  172. ///
  173. /// Percent encoded strings are within the ASCII range.
  174. ///
  175. /// See also the `lossy_percent_decode_password` method.
  176. pub password: Option<String>,
  177. /// The host of the URL, either a domain name or an IPv4 address
  178. pub host: Host,
  179. /// The port number of the URL.
  180. /// `None` for file-like schemes, or to indicate the default port number.
  181. pub port: Option<u16>,
  182. /// The default port number for the URL’s scheme.
  183. /// `None` for file-like schemes.
  184. pub default_port: Option<u16>,
  185. /// The path of the URL, as vector of pecent-encoded strings.
  186. ///
  187. /// Percent encoded strings are within the ASCII range.
  188. ///
  189. /// See also the `serialize_path` method and,
  190. /// for URLs in the `file` scheme, the `to_file_path` method.
  191. pub path: Vec<String>,
  192. }
  193. impl<S: hash::Writer> hash::Hash<S> for Url {
  194. fn hash(&self, state: &mut S) {
  195. self.serialize().hash(state)
  196. }
  197. }
  198. /// A set of optional parameters for URL parsing.
  199. pub struct UrlParser<'a> {
  200. base_url: Option<&'a Url>,
  201. query_encoding_override: Option<EncodingRef>,
  202. error_handler: ErrorHandler,
  203. scheme_type_mapper: fn(scheme: &str) -> SchemeType,
  204. }
  205. /// A method-chaining API to provide a set of optional parameters for URL parsing.
  206. impl<'a> UrlParser<'a> {
  207. /// Return a new UrlParser with default parameters.
  208. #[inline]
  209. pub fn new() -> UrlParser<'a> {
  210. fn silent_handler(_reason: ParseError) -> ParseResult<()> { Ok(()) }
  211. UrlParser {
  212. base_url: None,
  213. query_encoding_override: None,
  214. error_handler: silent_handler,
  215. scheme_type_mapper: whatwg_scheme_type_mapper,
  216. }
  217. }
  218. /// Set the base URL used for resolving relative URL references, and return the `UrlParser`.
  219. /// The default is no base URL, so that relative URLs references fail to parse.
  220. #[inline]
  221. pub fn base_url<'b>(&'b mut self, value: &'a Url) -> &'b mut UrlParser<'a> {
  222. self.base_url = Some(value);
  223. self
  224. }
  225. /// Set the character encoding the query string is encoded as before percent-encoding,
  226. /// and return the `UrlParser`.
  227. ///
  228. /// This legacy quirk is only relevant to HTML.
  229. #[inline]
  230. pub fn query_encoding_override<'b>(&'b mut self, value: EncodingRef) -> &'b mut UrlParser<'a> {
  231. self.query_encoding_override = Some(value);
  232. self
  233. }
  234. /// Set an error handler for non-fatal parse errors, and return the `UrlParser`.
  235. ///
  236. /// Non-fatal parse errors are normally ignored by the parser,
  237. /// but indicate violations of authoring requirements.
  238. /// An error handler can be used, for example, to log these errors in the console
  239. /// of a browser’s developer tools.
  240. ///
  241. /// The error handler can choose to make the error fatal by returning `Err(..)`
  242. #[inline]
  243. pub fn error_handler<'b>(&'b mut self, value: ErrorHandler) -> &'b mut UrlParser<'a> {
  244. self.error_handler = value;
  245. self
  246. }
  247. /// Set a *scheme type mapper*, and return the `UrlParser`.
  248. ///
  249. /// The URL parser behaves differently based on the `SchemeType` of the URL.
  250. /// See the documentation for `SchemeType` for more details.
  251. /// A *scheme type mapper* returns a `SchemeType`
  252. /// based on the scheme as an ASCII lower case string,
  253. /// as found in the `scheme` field of an `Url` struct.
  254. ///
  255. /// The default scheme type mapper is as follows:
  256. ///
  257. /// ```ignore
  258. /// fn whatwg_scheme_type_mapper(scheme: &str) -> SchemeType {
  259. /// match scheme {
  260. /// "file" => FileLikeRelativeScheme,
  261. /// "ftp" => RelativeScheme(21),
  262. /// "gopher" => RelativeScheme(70),
  263. /// "http" => RelativeScheme(80),
  264. /// "https" => RelativeScheme(443),
  265. /// "ws" => RelativeScheme(80),
  266. /// "wss" => RelativeScheme(443),
  267. /// _ => NonRelativeScheme,
  268. /// }
  269. /// }
  270. /// ```
  271. ///
  272. /// Note that unknown schemes default to non-relative.
  273. /// Overriding the scheme type mapper can allow, for example,
  274. /// parsing URLs in the `git` or `irc` scheme as relative.
  275. #[inline]
  276. pub fn scheme_type_mapper<'b>(&'b mut self, value: fn(scheme: &str) -> SchemeType)
  277. -> &'b mut UrlParser<'a> {
  278. self.scheme_type_mapper = value;
  279. self
  280. }
  281. /// Parse `input` as an URL, with all the parameters previously set in the `UrlParser`.
  282. #[inline]
  283. pub fn parse(&self, input: &str) -> ParseResult<Url> {
  284. parser::parse_url(input, self)
  285. }
  286. }
  287. /// Private convenience methods for use in parser.rs
  288. impl<'a> UrlParser<'a> {
  289. #[inline]
  290. fn parse_error(&self, error: ParseError) -> ParseResult<()> {
  291. (self.error_handler)(error)
  292. }
  293. #[inline]
  294. fn get_scheme_type(&self, scheme: &str) -> SchemeType {
  295. (self.scheme_type_mapper)(scheme)
  296. }
  297. }
  298. /// Determines the behavior of the URL parser for a given scheme.
  299. #[deriving(PartialEq, Eq)]
  300. pub enum SchemeType {
  301. /// Indicate that the scheme is *non-relative*.
  302. ///
  303. /// The *scheme data* of the URL
  304. /// (everything other than the scheme, query string, and fragment identifier)
  305. /// is parsed as a single percent-encoded string of which no structure is assumed.
  306. /// That string may need to be parsed further, per a scheme-specific format.
  307. NonRelativeScheme,
  308. /// Indicate that the scheme is *relative*, and what the default port number is.
  309. ///
  310. /// The *scheme data* is structured as
  311. /// *username*, *password*, *host*, *port number*, and *path*.
  312. /// Relative URL references are supported, if a base URL was given.
  313. /// The string value indicates the default port number as a string of ASCII digits,
  314. /// or the empty string to indicate no default port number.
  315. RelativeScheme(u16),
  316. /// Indicate a *relative* scheme similar to the *file* scheme.
  317. ///
  318. /// For example, you might want to have distinct `git+file` and `hg+file` URL schemes.
  319. ///
  320. /// This is like `RelativeScheme` except the host can be empty, there is no port number,
  321. /// and path parsing has (platform-independent) quirks to support Windows filenames.
  322. FileLikeRelativeScheme,
  323. }
  324. impl SchemeType {
  325. pub fn default_port(&self) -> Option<u16> {
  326. match self {
  327. &RelativeScheme(default_port) => Some(default_port),
  328. _ => None,
  329. }
  330. }
  331. }
  332. /// http://url.spec.whatwg.org/#relative-scheme
  333. pub fn whatwg_scheme_type_mapper(scheme: &str) -> SchemeType {
  334. match scheme {
  335. "file" => FileLikeRelativeScheme,
  336. "ftp" => RelativeScheme(21),
  337. "gopher" => RelativeScheme(70),
  338. "http" => RelativeScheme(80),
  339. "https" => RelativeScheme(443),
  340. "ws" => RelativeScheme(80),
  341. "wss" => RelativeScheme(443),
  342. _ => NonRelativeScheme,
  343. }
  344. }
  345. impl Url {
  346. /// Parse an URL with the default `UrlParser` parameters.
  347. ///
  348. /// In particular, relative URL references are parse errors since no base URL is provided.
  349. #[inline]
  350. pub fn parse(input: &str) -> ParseResult<Url> {
  351. UrlParser::new().parse(input)
  352. }
  353. /// Convert a file name as `std::path::Path` into an URL in the `file` scheme.
  354. ///
  355. /// This returns `Err` if the given path is not absolute
  356. /// or, with a Windows path, if the prefix is not a disk prefix (e.g. `C:`).
  357. pub fn from_file_path<T: ToUrlPath>(path: &T) -> Result<Url, ()> {
  358. let path = try!(path.to_url_path());
  359. Ok(Url::from_path_common(path))
  360. }
  361. /// Convert a directory name as `std::path::Path` into an URL in the `file` scheme.
  362. ///
  363. /// This returns `Err` if the given path is not absolute
  364. /// or, with a Windows path, if the prefix is not a disk prefix (e.g. `C:`).
  365. ///
  366. /// Compared to `from_file_path`, this adds an empty component to the path
  367. /// (or, in terms of URL syntax, adds a trailing slash)
  368. /// so that the entire path is considered when using this URL as a base URL.
  369. ///
  370. /// For example:
  371. ///
  372. /// * `"index.html"` parsed with `Url::from_directory_path(Path::new("/var/www"))`
  373. /// as the base URL is `file:///var/www/index.html`
  374. /// * `"index.html"` parsed with `Url::from_file_path(Path::new("/var/www/"))`
  375. /// as the base URL is `file:///var/index.html`, which might not be what was intended.
  376. ///
  377. /// (Note that `Path::new` removes any trailing slash.)
  378. pub fn from_directory_path<T: ToUrlPath>(path: &T) -> Result<Url, ()> {
  379. let mut path = try!(path.to_url_path());
  380. // Add an empty path component (i.e. a trailing slash in serialization)
  381. // so that the entire path is used as a base URL.
  382. path.push("".to_string());
  383. Ok(Url::from_path_common(path))
  384. }
  385. fn from_path_common(path: Vec<String>) -> Url {
  386. Url {
  387. scheme: "file".to_string(),
  388. scheme_data: RelativeSchemeData(RelativeSchemeData {
  389. username: "".to_string(),
  390. password: None,
  391. port: None,
  392. default_port: None,
  393. host: Domain("".to_string()),
  394. path: path,
  395. }),
  396. query: None,
  397. fragment: None,
  398. }
  399. }
  400. /// Assuming the URL is in the `file` scheme or similar,
  401. /// convert its path to an absolute `std::path::Path`.
  402. ///
  403. /// **Note:** This does not actually check the URL’s `scheme`,
  404. /// and may give nonsensical results for other schemes.
  405. /// It is the user’s responsibility to check the URL’s scheme before calling this.
  406. ///
  407. /// The return type (when `Ok()`) is generic and can be either `std::path::posix::Path`
  408. /// or `std::path::windows::Path`.
  409. /// (Use `std::path::Path` to pick one of them depending on the local system.)
  410. /// If the compiler can not infer the desired type from context, you may have to specifiy it:
  411. ///
  412. /// ```ignore
  413. /// let path = url.to_file_path::<std::path::posix::Path>();
  414. /// ```
  415. ///
  416. /// Returns `Err` if the host is neither empty nor `"localhost"`,
  417. /// or if `Path::new_opt()` returns `None`.
  418. /// (That is, if the percent-decoded path contains a NUL byte or,
  419. /// for a Windows path, is not UTF-8.)
  420. #[inline]
  421. pub fn to_file_path<T: FromUrlPath>(&self) -> Result<T, ()> {
  422. match self.scheme_data {
  423. RelativeSchemeData(ref scheme_data) => scheme_data.to_file_path(),
  424. NonRelativeSchemeData(..) => Err(()),
  425. }
  426. }
  427. /// Return the serialization of this URL as a string.
  428. pub fn serialize(&self) -> String {
  429. self.to_string()
  430. }
  431. /// Return the serialization of this URL, without the fragment identifier, as a string
  432. pub fn serialize_no_fragment(&self) -> String {
  433. UrlNoFragmentFormatter{ url: self }.to_string()
  434. }
  435. /// If the URL is *non-relative*, return the string scheme data.
  436. #[inline]
  437. pub fn non_relative_scheme_data<'a>(&'a self) -> Option<&'a str> {
  438. match self.scheme_data {
  439. RelativeSchemeData(..) => None,
  440. NonRelativeSchemeData(ref scheme_data) => Some(scheme_data.as_slice()),
  441. }
  442. }
  443. /// If the URL is *non-relative*, return a mutable reference to the string scheme data.
  444. #[inline]
  445. pub fn non_relative_scheme_data_mut<'a>(&'a mut self) -> Option<&'a mut String> {
  446. match self.scheme_data {
  447. RelativeSchemeData(..) => None,
  448. NonRelativeSchemeData(ref mut scheme_data) => Some(scheme_data),
  449. }
  450. }
  451. /// If the URL is in a *relative scheme*, return the structured scheme data.
  452. #[inline]
  453. pub fn relative_scheme_data<'a>(&'a self) -> Option<&'a RelativeSchemeData> {
  454. match self.scheme_data {
  455. RelativeSchemeData(ref scheme_data) => Some(scheme_data),
  456. NonRelativeSchemeData(..) => None,
  457. }
  458. }
  459. /// If the URL is in a *relative scheme*,
  460. /// return a mutable reference to the structured scheme data.
  461. #[inline]
  462. pub fn relative_scheme_data_mut<'a>(&'a mut self) -> Option<&'a mut RelativeSchemeData> {
  463. match self.scheme_data {
  464. RelativeSchemeData(ref mut scheme_data) => Some(scheme_data),
  465. NonRelativeSchemeData(..) => None,
  466. }
  467. }
  468. /// If the URL is in a *relative scheme*, return its username.
  469. #[inline]
  470. pub fn username<'a>(&'a self) -> Option<&'a str> {
  471. self.relative_scheme_data().map(|scheme_data| scheme_data.username.as_slice())
  472. }
  473. /// If the URL is in a *relative scheme*, return a mutable reference to its username.
  474. #[inline]
  475. pub fn username_mut<'a>(&'a mut self) -> Option<&'a mut String> {
  476. self.relative_scheme_data_mut().map(|scheme_data| &mut scheme_data.username)
  477. }
  478. /// Percent-decode the URL’s username, if any.
  479. ///
  480. /// This is “lossy”: invalid UTF-8 percent-encoded byte sequences
  481. /// will be replaced � U+FFFD, the replacement character.
  482. #[inline]
  483. pub fn lossy_percent_decode_username(&self) -> Option<String> {
  484. self.relative_scheme_data().map(|scheme_data| scheme_data.lossy_percent_decode_username())
  485. }
  486. /// If the URL is in a *relative scheme*, return its password, if any.
  487. #[inline]
  488. pub fn password<'a>(&'a self) -> Option<&'a str> {
  489. self.relative_scheme_data().and_then(|scheme_data|
  490. scheme_data.password.as_ref().map(|password| password.as_slice()))
  491. }
  492. /// If the URL is in a *relative scheme*, return a mutable reference to its password, if any.
  493. #[inline]
  494. pub fn password_mut<'a>(&'a mut self) -> Option<&'a mut String> {
  495. self.relative_scheme_data_mut().and_then(|scheme_data| scheme_data.password.as_mut())
  496. }
  497. /// Percent-decode the URL’s password, if any.
  498. ///
  499. /// This is “lossy”: invalid UTF-8 percent-encoded byte sequences
  500. /// will be replaced � U+FFFD, the replacement character.
  501. #[inline]
  502. pub fn lossy_percent_decode_password(&self) -> Option<String> {
  503. self.relative_scheme_data().and_then(|scheme_data|
  504. scheme_data.lossy_percent_decode_password())
  505. }
  506. /// Serialize the URL's username and password, if any.
  507. ///
  508. /// Format: "<username>:<password>@"
  509. #[inline]
  510. pub fn serialize_userinfo<'a>(&'a mut self) -> Option<String> {
  511. self.relative_scheme_data().map(|scheme_data| scheme_data.serialize_userinfo())
  512. }
  513. /// If the URL is in a *relative scheme*, return its structured host.
  514. #[inline]
  515. pub fn host<'a>(&'a self) -> Option<&'a Host> {
  516. self.relative_scheme_data().map(|scheme_data| &scheme_data.host)
  517. }
  518. /// If the URL is in a *relative scheme*, return a mutable reference to its structured host.
  519. #[inline]
  520. pub fn host_mut<'a>(&'a mut self) -> Option<&'a mut Host> {
  521. self.relative_scheme_data_mut().map(|scheme_data| &mut scheme_data.host)
  522. }
  523. /// If the URL is in a *relative scheme* and its host is a domain,
  524. /// return the domain as a string.
  525. #[inline]
  526. pub fn domain<'a>(&'a self) -> Option<&'a str> {
  527. self.relative_scheme_data().and_then(|scheme_data| scheme_data.domain())
  528. }
  529. /// If the URL is in a *relative scheme* and its host is a domain,
  530. /// return a mutable reference to the domain string.
  531. #[inline]
  532. pub fn domain_mut<'a>(&'a mut self) -> Option<&'a mut String> {
  533. self.relative_scheme_data_mut().and_then(|scheme_data| scheme_data.domain_mut())
  534. }
  535. /// If the URL is in a *relative scheme*, serialize its host as a string.
  536. ///
  537. /// A domain a returned as-is, an IPv6 address between [] square brackets.
  538. #[inline]
  539. pub fn serialize_host(&self) -> Option<String> {
  540. self.relative_scheme_data().map(|scheme_data| scheme_data.host.serialize())
  541. }
  542. /// If the URL is in a *relative scheme* and has a port number, return it.
  543. #[inline]
  544. pub fn port<'a>(&'a self) -> Option<u16> {
  545. self.relative_scheme_data().and_then(|scheme_data| scheme_data.port)
  546. }
  547. /// If the URL is in a *relative scheme*, return a mutable reference to its port.
  548. #[inline]
  549. pub fn port_mut<'a>(&'a mut self) -> Option<&'a mut Option<u16>> {
  550. self.relative_scheme_data_mut().map(|scheme_data| &mut scheme_data.port)
  551. }
  552. /// If the URL is in a *relative scheme* that is not a file-like,
  553. /// return its port number, even if it is the default.
  554. #[inline]
  555. pub fn port_or_default(&self) -> Option<u16> {
  556. self.relative_scheme_data().and_then(|scheme_data| scheme_data.port_or_default())
  557. }
  558. /// If the URL is in a *relative scheme*, return its path components.
  559. #[inline]
  560. pub fn path<'a>(&'a self) -> Option<&'a [String]> {
  561. self.relative_scheme_data().map(|scheme_data| scheme_data.path.as_slice())
  562. }
  563. /// If the URL is in a *relative scheme*, return a mutable reference to its path components.
  564. #[inline]
  565. pub fn path_mut<'a>(&'a mut self) -> Option<&'a mut Vec<String>> {
  566. self.relative_scheme_data_mut().map(|scheme_data| &mut scheme_data.path)
  567. }
  568. /// If the URL is in a *relative scheme*, serialize its path as a string.
  569. ///
  570. /// The returned string starts with a "/" slash, and components are separated by slashes.
  571. /// A trailing slash represents an empty last component.
  572. #[inline]
  573. pub fn serialize_path(&self) -> Option<String> {
  574. self.relative_scheme_data().map(|scheme_data| scheme_data.serialize_path())
  575. }
  576. /// Parse the URL’s query string, if any, as `application/x-www-form-urlencoded`
  577. /// and return a vector of (key, value) pairs.
  578. #[inline]
  579. pub fn query_pairs(&self) -> Option<Vec<(String, String)>> {
  580. self.query.as_ref().map(|query| form_urlencoded::parse_str(query.as_slice()))
  581. }
  582. /// Serialize an iterator of (key, value) pairs as `application/x-www-form-urlencoded`
  583. /// and set it as the URL’s query string.
  584. #[inline]
  585. pub fn set_query_from_pairs<'a, I: Iterator<(&'a str, &'a str)>>(&mut self, pairs: I) {
  586. self.query = Some(form_urlencoded::serialize(pairs, None));
  587. }
  588. /// Percent-decode the URL’s query string, if any.
  589. ///
  590. /// This is “lossy”: invalid UTF-8 percent-encoded byte sequences
  591. /// will be replaced � U+FFFD, the replacement character.
  592. #[inline]
  593. pub fn lossy_percent_decode_query(&self) -> Option<String> {
  594. self.query.as_ref().map(|value| lossy_utf8_percent_decode(value.as_bytes()))
  595. }
  596. /// Percent-decode the URL’s fragment identifier, if any.
  597. ///
  598. /// This is “lossy”: invalid UTF-8 percent-encoded byte sequences
  599. /// will be replaced � U+FFFD, the replacement character.
  600. #[inline]
  601. pub fn lossy_percent_decode_fragment(&self) -> Option<String> {
  602. self.fragment.as_ref().map(|value| lossy_utf8_percent_decode(value.as_bytes()))
  603. }
  604. }
  605. impl Show for Url {
  606. fn fmt(&self, formatter: &mut Formatter) -> Result<(), FormatError> {
  607. try!(UrlNoFragmentFormatter{ url: self }.fmt(formatter));
  608. match self.fragment {
  609. None => (),
  610. Some(ref fragment) => {
  611. try!(formatter.write(b"#"));
  612. try!(formatter.write(fragment.as_bytes()));
  613. }
  614. }
  615. Ok(())
  616. }
  617. }
  618. impl Show for SchemeData {
  619. fn fmt(&self, formatter: &mut Formatter) -> Result<(), FormatError> {
  620. match *self {
  621. RelativeSchemeData(ref scheme_data) => scheme_data.fmt(formatter),
  622. NonRelativeSchemeData(ref scheme_data) => scheme_data.fmt(formatter),
  623. }
  624. }
  625. }
  626. impl RelativeSchemeData {
  627. /// Percent-decode the URL’s username.
  628. ///
  629. /// This is “lossy”: invalid UTF-8 percent-encoded byte sequences
  630. /// will be replaced � U+FFFD, the replacement character.
  631. #[inline]
  632. pub fn lossy_percent_decode_username(&self) -> String {
  633. lossy_utf8_percent_decode(self.username.as_bytes())
  634. }
  635. /// Percent-decode the URL’s password, if any.
  636. ///
  637. /// This is “lossy”: invalid UTF-8 percent-encoded byte sequences
  638. /// will be replaced � U+FFFD, the replacement character.
  639. #[inline]
  640. pub fn lossy_percent_decode_password(&self) -> Option<String> {
  641. self.password.as_ref().map(|value| lossy_utf8_percent_decode(value.as_bytes()))
  642. }
  643. /// Assuming the URL is in the `file` scheme or similar,
  644. /// convert its path to an absolute `std::path::Path`.
  645. ///
  646. /// **Note:** This does not actually check the URL’s `scheme`,
  647. /// and may give nonsensical results for other schemes.
  648. /// It is the user’s responsibility to check the URL’s scheme before calling this.
  649. ///
  650. /// The return type (when `Ok()`) is generic and can be either `std::path::posix::Path`
  651. /// or `std::path::windows::Path`.
  652. /// (Use `std::path::Path` to pick one of them depending on the local system.)
  653. /// If the compiler can not infer the desired type from context, you may have to specifiy it:
  654. ///
  655. /// ```ignore
  656. /// let path = url.to_file_path::<std::path::posix::Path>();
  657. /// ```
  658. ///
  659. /// Returns `Err` if the host is neither empty nor `"localhost"`,
  660. /// or if `Path::new_opt()` returns `None`.
  661. /// (That is, if the percent-decoded path contains a NUL byte or,
  662. /// for a Windows path, is not UTF-8.)
  663. #[inline]
  664. pub fn to_file_path<T: FromUrlPath>(&self) -> Result<T, ()> {
  665. // FIXME: Figure out what to do w.r.t host.
  666. match self.domain() {
  667. Some("") | Some("localhost") => FromUrlPath::from_url_path(self.path.as_slice()),
  668. _ => Err(())
  669. }
  670. }
  671. /// If the host is a domain, return the domain as a string.
  672. #[inline]
  673. pub fn domain<'a>(&'a self) -> Option<&'a str> {
  674. match self.host {
  675. Domain(ref domain) => Some(domain.as_slice()),
  676. _ => None,
  677. }
  678. }
  679. /// If the host is a domain, return a mutable reference to the domain string.
  680. #[inline]
  681. pub fn domain_mut<'a>(&'a mut self) -> Option<&'a mut String> {
  682. match self.host {
  683. Domain(ref mut domain) => Some(domain),
  684. _ => None,
  685. }
  686. }
  687. /// Return the port number of the URL, even if it is the default.
  688. /// Return `None` for file-like URLs.
  689. #[inline]
  690. pub fn port_or_default(&self) -> Option<u16> {
  691. self.port.or(self.default_port)
  692. }
  693. /// Serialize the path as a string.
  694. ///
  695. /// The returned string starts with a "/" slash, and components are separated by slashes.
  696. /// A trailing slash represents an empty last component.
  697. pub fn serialize_path(&self) -> String {
  698. PathFormatter {
  699. path: self.path.as_slice()
  700. }.to_string()
  701. }
  702. /// Serialize the userinfo as a string.
  703. ///
  704. /// Format: "<username>:<password>@".
  705. pub fn serialize_userinfo(&self) -> String {
  706. UserInfoFormatter {
  707. username: self.username.as_slice(),
  708. password: self.password.as_ref().map(|s| s.as_slice())
  709. }.to_string()
  710. }
  711. }
  712. impl Show for RelativeSchemeData {
  713. fn fmt(&self, formatter: &mut Formatter) -> Result<(), FormatError> {
  714. // Write the scheme-trailing double slashes.
  715. try!(formatter.write(b"//"));
  716. // Write the user info.
  717. try!(UserInfoFormatter {
  718. username: self.username.as_slice(),
  719. password: self.password.as_ref().map(|s| s.as_slice())
  720. }.fmt(formatter));
  721. // Write the host.
  722. try!(self.host.fmt(formatter));
  723. // Write the port.
  724. match self.port {
  725. Some(port) => {
  726. try!(write!(formatter, ":{}", port));
  727. },
  728. None => {}
  729. }
  730. // Write the path.
  731. PathFormatter {
  732. path: self.path.as_slice()
  733. }.fmt(formatter)
  734. }
  735. }
  736. trait ToUrlPath {
  737. fn to_url_path(&self) -> Result<Vec<String>, ()>;
  738. }
  739. impl ToUrlPath for path::posix::Path {
  740. fn to_url_path(&self) -> Result<Vec<String>, ()> {
  741. if !self.is_absolute() {
  742. return Err(())
  743. }
  744. Ok(self.components().map(|c| percent_encode(c, DEFAULT_ENCODE_SET)).collect())
  745. }
  746. }
  747. impl ToUrlPath for path::windows::Path {
  748. fn to_url_path(&self) -> Result<Vec<String>, ()> {
  749. if !self.is_absolute() {
  750. return Err(())
  751. }
  752. if path::windows::prefix(self) != Some(path::windows::DiskPrefix) {
  753. // FIXME: do something with UNC and other prefixes?
  754. return Err(())
  755. }
  756. // Start with the prefix, e.g. "C:"
  757. let mut path = vec![self.as_str().unwrap().slice_to(2).to_string()];
  758. // self.components() does not include the prefix
  759. for component in self.components() {
  760. path.push(percent_encode(component, DEFAULT_ENCODE_SET));
  761. }
  762. Ok(path)
  763. }
  764. }
  765. trait FromUrlPath {
  766. fn from_url_path(path: &[String]) -> Result<Self, ()>;
  767. }
  768. impl FromUrlPath for path::posix::Path {
  769. fn from_url_path(path: &[String]) -> Result<path::posix::Path, ()> {
  770. if path.is_empty() {
  771. return Ok(path::posix::Path::new("/"))
  772. }
  773. let mut bytes = Vec::new();
  774. for path_part in path.iter() {
  775. bytes.push(b'/');
  776. percent_decode_to(path_part.as_bytes(), &mut bytes);
  777. }
  778. match path::posix::Path::new_opt(bytes) {
  779. None => Err(()), // Path contains a NUL byte
  780. Some(path) => {
  781. debug_assert!(path.is_absolute(),
  782. "to_file_path() failed to produce an absolute Path")
  783. Ok(path)
  784. }
  785. }
  786. }
  787. }
  788. impl FromUrlPath for path::windows::Path {
  789. fn from_url_path(path: &[String]) -> Result<path::windows::Path, ()> {
  790. if path.is_empty() {
  791. return Err(())
  792. }
  793. let prefix = path[0].as_slice();
  794. if prefix.len() != 2 || !parser::starts_with_ascii_alpha(prefix)
  795. || prefix.char_at(1) != ':' {
  796. return Err(())
  797. }
  798. let mut bytes = prefix.as_bytes().to_vec();
  799. for path_part in path.slice_from(1).iter() {
  800. bytes.push(b'\\');
  801. percent_decode_to(path_part.as_bytes(), &mut bytes);
  802. }
  803. match path::windows::Path::new_opt(bytes) {
  804. None => Err(()), // Path contains a NUL byte or invalid UTF-8
  805. Some(path) => {
  806. debug_assert!(path.is_absolute(),
  807. "to_file_path() failed to produce an absolute Path")
  808. debug_assert!(path::windows::prefix(&path) == Some(path::windows::DiskPrefix),
  809. "to_file_path() failed to produce a Path with a disk prefix")
  810. Ok(path)
  811. }
  812. }
  813. }
  814. }