lib.rs 35 KB

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