lib.rs 40 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165
  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. For convenience, the `join` method on `Url` is also provided to achieve the same result:
  87. ```
  88. use url::Url;
  89. let this_document = Url::parse("http://servo.github.io/rust-url/url/index.html").unwrap();
  90. let css_url = this_document.join("../main.css").unwrap();
  91. assert!(&*css_url.serialize() == "http://servo.github.io/rust-url/main.css")
  92. */
  93. #![cfg_attr(feature="heap_size", feature(plugin, custom_derive))]
  94. #![cfg_attr(feature="heap_size", plugin(heapsize_plugin))]
  95. extern crate rustc_serialize;
  96. extern crate uuid;
  97. #[macro_use]
  98. extern crate matches;
  99. #[cfg(feature="serde_serialization")]
  100. extern crate serde;
  101. #[cfg(feature="heap_size")]
  102. #[macro_use] extern crate heapsize;
  103. use std::fmt::{self, Formatter};
  104. use std::str;
  105. use std::path::{Path, PathBuf};
  106. use std::borrow::Borrow;
  107. use std::hash::{Hash, Hasher};
  108. use std::cmp::Ordering;
  109. #[cfg(feature="serde_serialization")]
  110. use std::str::FromStr;
  111. pub use host::Host;
  112. pub use parser::{ErrorHandler, ParseResult, ParseError};
  113. use percent_encoding::{percent_encode, lossy_utf8_percent_decode, DEFAULT_ENCODE_SET};
  114. use format::{PathFormatter, UserInfoFormatter, UrlNoFragmentFormatter};
  115. use encoding::EncodingOverride;
  116. use uuid::Uuid;
  117. mod encoding;
  118. mod host;
  119. mod parser;
  120. pub mod urlutils;
  121. pub mod percent_encoding;
  122. pub mod form_urlencoded;
  123. pub mod punycode;
  124. pub mod format;
  125. /// The parsed representation of an absolute URL.
  126. #[derive(PartialEq, Eq, Clone, Debug, Hash, PartialOrd, Ord)]
  127. #[cfg_attr(feature="heap_size", derive(HeapSizeOf))]
  128. pub struct Url {
  129. /// The scheme (a.k.a. protocol) of the URL, in ASCII lower case.
  130. pub scheme: String,
  131. /// The components of the URL whose representation depends on where the scheme is *relative*.
  132. pub scheme_data: SchemeData,
  133. /// The query string of the URL.
  134. ///
  135. /// `None` if the `?` delimiter character was not part of the parsed input,
  136. /// otherwise a possibly empty, percent-encoded string.
  137. ///
  138. /// Percent encoded strings are within the ASCII range.
  139. ///
  140. /// See also the `query_pairs`, `set_query_from_pairs`,
  141. /// and `lossy_percent_decode_query` methods.
  142. pub query: Option<String>,
  143. /// The fragment identifier of the URL.
  144. ///
  145. /// `None` if the `#` delimiter character was not part of the parsed input,
  146. /// otherwise a possibly empty, percent-encoded string.
  147. ///
  148. /// Percent encoded strings are within the ASCII range.
  149. ///
  150. /// See also the `lossy_percent_decode_fragment` method.
  151. pub fragment: Option<String>,
  152. }
  153. /// Opaque identifier for URLs that have file or other schemes
  154. #[derive(PartialEq, Eq, Clone, Debug)]
  155. pub struct OpaqueOrigin(Uuid);
  156. #[cfg(feature="heap_size")]
  157. known_heap_size!(0, OpaqueOrigin);
  158. impl OpaqueOrigin {
  159. /// Creates a new opaque origin with a random UUID.
  160. pub fn new() -> OpaqueOrigin {
  161. OpaqueOrigin(Uuid::new_v4())
  162. }
  163. }
  164. /// The origin of the URL
  165. #[derive(PartialEq, Eq, Clone, Debug)]
  166. #[cfg_attr(feature="heap_size", derive(HeapSizeOf))]
  167. pub enum Origin {
  168. /// A globally unique identifier
  169. UID(OpaqueOrigin),
  170. /// Consists of the URL's scheme, host and port
  171. Tuple(String, Host, u16)
  172. }
  173. /// The components of the URL whose representation depends on where the scheme is *relative*.
  174. #[derive(PartialEq, Eq, Clone, Debug, Hash, PartialOrd, Ord)]
  175. #[cfg_attr(feature="heap_size", derive(HeapSizeOf))]
  176. pub enum SchemeData {
  177. /// Components for URLs in a *relative* scheme such as HTTP.
  178. Relative(RelativeSchemeData),
  179. /// No further structure is assumed for *non-relative* schemes such as `data` and `mailto`.
  180. ///
  181. /// This is a single percent-encoded string, whose interpretation depends on the scheme.
  182. ///
  183. /// Percent encoded strings are within the ASCII range.
  184. NonRelative(String),
  185. }
  186. /// Components for URLs in a *relative* scheme such as HTTP.
  187. #[derive(Clone, Debug)]
  188. #[cfg_attr(feature="heap_size", derive(HeapSizeOf))]
  189. pub struct RelativeSchemeData {
  190. /// The username of the URL, as a possibly empty, percent-encoded string.
  191. ///
  192. /// Percent encoded strings are within the ASCII range.
  193. ///
  194. /// See also the `lossy_percent_decode_username` method.
  195. pub username: String,
  196. /// The password of the URL.
  197. ///
  198. /// `None` if the `:` delimiter character was not part of the parsed input,
  199. /// otherwise a possibly empty, percent-encoded string.
  200. ///
  201. /// Percent encoded strings are within the ASCII range.
  202. ///
  203. /// See also the `lossy_percent_decode_password` method.
  204. pub password: Option<String>,
  205. /// The host of the URL, either a domain name or an IPv4 address
  206. pub host: Host,
  207. /// The port number of the URL.
  208. /// `None` for file-like schemes, or to indicate the default port number.
  209. pub port: Option<u16>,
  210. /// The default port number for the URL’s scheme.
  211. /// `None` for file-like schemes.
  212. pub default_port: Option<u16>,
  213. /// The path of the URL, as vector of percent-encoded strings.
  214. ///
  215. /// Percent encoded strings are within the ASCII range.
  216. ///
  217. /// See also the `serialize_path` method and,
  218. /// for URLs in the `file` scheme, the `to_file_path` method.
  219. pub path: Vec<String>,
  220. }
  221. impl RelativeSchemeData {
  222. fn get_identity_key(&self) -> (&String, &Option<String>, &Host, Option<u16>, Option<u16>, &Vec<String>) {
  223. (
  224. &self.username,
  225. &self.password,
  226. &self.host,
  227. self.port.or(self.default_port),
  228. self.default_port,
  229. &self.path
  230. )
  231. }
  232. }
  233. impl PartialEq for RelativeSchemeData {
  234. fn eq(&self, other: &RelativeSchemeData) -> bool {
  235. self.get_identity_key() == other.get_identity_key()
  236. }
  237. }
  238. impl Eq for RelativeSchemeData {}
  239. impl Hash for RelativeSchemeData {
  240. fn hash<H: Hasher>(&self, state: &mut H) {
  241. self.get_identity_key().hash(state)
  242. }
  243. }
  244. impl PartialOrd for RelativeSchemeData {
  245. fn partial_cmp(&self, other: &RelativeSchemeData) -> Option<Ordering> {
  246. self.get_identity_key().partial_cmp(&other.get_identity_key())
  247. }
  248. }
  249. impl Ord for RelativeSchemeData {
  250. fn cmp(&self, other: &Self) -> Ordering {
  251. self.get_identity_key().cmp(&other.get_identity_key())
  252. }
  253. }
  254. impl str::FromStr for Url {
  255. type Err = ParseError;
  256. fn from_str(url: &str) -> ParseResult<Url> {
  257. Url::parse(url)
  258. }
  259. }
  260. /// A set of optional parameters for URL parsing.
  261. pub struct UrlParser<'a> {
  262. base_url: Option<&'a Url>,
  263. query_encoding_override: EncodingOverride,
  264. error_handler: ErrorHandler,
  265. scheme_type_mapper: fn(scheme: &str) -> SchemeType,
  266. }
  267. /// A method-chaining API to provide a set of optional parameters for URL parsing.
  268. impl<'a> UrlParser<'a> {
  269. /// Return a new UrlParser with default parameters.
  270. #[inline]
  271. pub fn new() -> UrlParser<'a> {
  272. fn silent_handler(_reason: ParseError) -> ParseResult<()> { Ok(()) }
  273. UrlParser {
  274. base_url: None,
  275. query_encoding_override: EncodingOverride::utf8(),
  276. error_handler: silent_handler,
  277. scheme_type_mapper: whatwg_scheme_type_mapper,
  278. }
  279. }
  280. /// Set the base URL used for resolving relative URL references, and return the `UrlParser`.
  281. /// The default is no base URL, so that relative URLs references fail to parse.
  282. #[inline]
  283. pub fn base_url<'b>(&'b mut self, value: &'a Url) -> &'b mut UrlParser<'a> {
  284. self.base_url = Some(value);
  285. self
  286. }
  287. /// Set the character encoding the query string is encoded as before percent-encoding,
  288. /// and return the `UrlParser`.
  289. ///
  290. /// This legacy quirk is only relevant to HTML.
  291. ///
  292. /// This method is only available if the `query_encoding` Cargo feature is enabled.
  293. #[cfg(feature = "query_encoding")]
  294. #[inline]
  295. pub fn query_encoding_override<'b>(&'b mut self, value: encoding::EncodingRef)
  296. -> &'b mut UrlParser<'a> {
  297. self.query_encoding_override = EncodingOverride::from_encoding(value);
  298. self
  299. }
  300. /// Set an error handler for non-fatal parse errors, and return the `UrlParser`.
  301. ///
  302. /// Non-fatal parse errors are normally ignored by the parser,
  303. /// but indicate violations of authoring requirements.
  304. /// An error handler can be used, for example, to log these errors in the console
  305. /// of a browser’s developer tools.
  306. ///
  307. /// The error handler can choose to make the error fatal by returning `Err(..)`
  308. #[inline]
  309. pub fn error_handler<'b>(&'b mut self, value: ErrorHandler) -> &'b mut UrlParser<'a> {
  310. self.error_handler = value;
  311. self
  312. }
  313. /// Set a *scheme type mapper*, and return the `UrlParser`.
  314. ///
  315. /// The URL parser behaves differently based on the `SchemeType` of the URL.
  316. /// See the documentation for `SchemeType` for more details.
  317. /// A *scheme type mapper* returns a `SchemeType`
  318. /// based on the scheme as an ASCII lower case string,
  319. /// as found in the `scheme` field of an `Url` struct.
  320. ///
  321. /// The default scheme type mapper is as follows:
  322. ///
  323. /// ```ignore
  324. /// fn whatwg_scheme_type_mapper(scheme: &str) -> SchemeType {
  325. /// match scheme {
  326. /// "file" => SchemeType::FileLike,
  327. /// "ftp" => SchemeType::Relative(21),
  328. /// "gopher" => SchemeType::Relative(70),
  329. /// "http" => SchemeType::Relative(80),
  330. /// "https" => SchemeType::Relative(443),
  331. /// "ws" => SchemeType::Relative(80),
  332. /// "wss" => SchemeType::Relative(443),
  333. /// _ => NonRelative,
  334. /// }
  335. /// }
  336. /// ```
  337. ///
  338. /// Note that unknown schemes default to non-relative.
  339. /// Overriding the scheme type mapper can allow, for example,
  340. /// parsing URLs in the `git` or `irc` scheme as relative.
  341. #[inline]
  342. pub fn scheme_type_mapper<'b>(&'b mut self, value: fn(scheme: &str) -> SchemeType)
  343. -> &'b mut UrlParser<'a> {
  344. self.scheme_type_mapper = value;
  345. self
  346. }
  347. /// Parse `input` as an URL, with all the parameters previously set in the `UrlParser`.
  348. #[inline]
  349. pub fn parse(&self, input: &str) -> ParseResult<Url> {
  350. parser::parse_url(input, self)
  351. }
  352. /// Parse `input` as a “standalone” URL path,
  353. /// with an optional query string and fragment identifier.
  354. ///
  355. /// This is typically found in the start line of an HTTP header.
  356. ///
  357. /// Note that while the start line has no fragment identifier in the HTTP RFC,
  358. /// servers typically parse it and ignore it
  359. /// (rather than having it be part of the path or query string.)
  360. ///
  361. /// On success, return `(path, query_string, fragment_identifier)`
  362. #[inline]
  363. pub fn parse_path(&self, input: &str)
  364. -> ParseResult<(Vec<String>, Option<String>, Option<String>)> {
  365. parser::parse_standalone_path(input, self)
  366. }
  367. }
  368. /// Parse `input` as a “standalone” URL path,
  369. /// with an optional query string and fragment identifier.
  370. ///
  371. /// This is typically found in the start line of an HTTP header.
  372. ///
  373. /// Note that while the start line has no fragment identifier in the HTTP RFC,
  374. /// servers typically parse it and ignore it
  375. /// (rather than having it be part of the path or query string.)
  376. ///
  377. /// On success, return `(path, query_string, fragment_identifier)`
  378. ///
  379. /// ```rust
  380. /// let (path, query, fragment) = url::parse_path("/foo/bar/../baz?q=42").unwrap();
  381. /// assert_eq!(path, vec!["foo".to_string(), "baz".to_string()]);
  382. /// assert_eq!(query, Some("q=42".to_string()));
  383. /// assert_eq!(fragment, None);
  384. /// ```
  385. ///
  386. /// The query string returned by `url::parse_path` can be decoded with
  387. /// `url::form_urlencoded::parse`.
  388. #[inline]
  389. pub fn parse_path(input: &str)
  390. -> ParseResult<(Vec<String>, Option<String>, Option<String>)> {
  391. UrlParser::new().parse_path(input)
  392. }
  393. /// Private convenience methods for use in parser.rs
  394. impl<'a> UrlParser<'a> {
  395. #[inline]
  396. fn parse_error(&self, error: ParseError) -> ParseResult<()> {
  397. (self.error_handler)(error)
  398. }
  399. #[inline]
  400. fn get_scheme_type(&self, scheme: &str) -> SchemeType {
  401. (self.scheme_type_mapper)(scheme)
  402. }
  403. }
  404. /// Determines the behavior of the URL parser for a given scheme.
  405. #[derive(PartialEq, Eq, Copy, Debug, Clone, Hash, PartialOrd, Ord)]
  406. pub enum SchemeType {
  407. /// Indicate that the scheme is *non-relative*.
  408. ///
  409. /// The *scheme data* of the URL
  410. /// (everything other than the scheme, query string, and fragment identifier)
  411. /// is parsed as a single percent-encoded string of which no structure is assumed.
  412. /// That string may need to be parsed further, per a scheme-specific format.
  413. NonRelative,
  414. /// Indicate that the scheme is *relative*, and what the default port number is.
  415. ///
  416. /// The *scheme data* is structured as
  417. /// *username*, *password*, *host*, *port number*, and *path*.
  418. /// Relative URL references are supported, if a base URL was given.
  419. /// The string value indicates the default port number as a string of ASCII digits,
  420. /// or the empty string to indicate no default port number.
  421. Relative(u16),
  422. /// Indicate a *relative* scheme similar to the *file* scheme.
  423. ///
  424. /// For example, you might want to have distinct `git+file` and `hg+file` URL schemes.
  425. ///
  426. /// This is like `Relative` except the host can be empty, there is no port number,
  427. /// and path parsing has (platform-independent) quirks to support Windows filenames.
  428. FileLike,
  429. }
  430. impl SchemeType {
  431. pub fn default_port(&self) -> Option<u16> {
  432. match *self {
  433. SchemeType::Relative(default_port) => Some(default_port),
  434. _ => None,
  435. }
  436. }
  437. pub fn same_as(&self, other: SchemeType) -> bool {
  438. match (self, other) {
  439. (&SchemeType::NonRelative, SchemeType::NonRelative) => true,
  440. (&SchemeType::Relative(_), SchemeType::Relative(_)) => true,
  441. (&SchemeType::FileLike, SchemeType::FileLike) => true,
  442. _ => false
  443. }
  444. }
  445. }
  446. /// http://url.spec.whatwg.org/#relative-scheme
  447. pub fn whatwg_scheme_type_mapper(scheme: &str) -> SchemeType {
  448. match scheme {
  449. "file" => SchemeType::FileLike,
  450. "ftp" => SchemeType::Relative(21),
  451. "gopher" => SchemeType::Relative(70),
  452. "http" => SchemeType::Relative(80),
  453. "https" => SchemeType::Relative(443),
  454. "ws" => SchemeType::Relative(80),
  455. "wss" => SchemeType::Relative(443),
  456. _ => SchemeType::NonRelative,
  457. }
  458. }
  459. impl Url {
  460. /// Parse an URL with the default `UrlParser` parameters.
  461. ///
  462. /// In particular, relative URL references are parse errors since no base URL is provided.
  463. #[inline]
  464. pub fn parse(input: &str) -> ParseResult<Url> {
  465. UrlParser::new().parse(input)
  466. }
  467. /// Convert a file name as `std::path::Path` into an URL in the `file` scheme.
  468. ///
  469. /// This returns `Err` if the given path is not absolute
  470. /// or, with a Windows path, if the prefix is not a disk prefix (e.g. `C:`).
  471. pub fn from_file_path<P: AsRef<Path>>(path: P) -> Result<Url, ()> {
  472. let path = try!(path_to_file_url_path(path.as_ref()));
  473. Ok(Url::from_path_common(path))
  474. }
  475. /// Convert a directory name as `std::path::Path` into an URL in the `file` scheme.
  476. ///
  477. /// This returns `Err` if the given path is not absolute
  478. /// or, with a Windows path, if the prefix is not a disk prefix (e.g. `C:`).
  479. ///
  480. /// Compared to `from_file_path`, this adds an empty component to the path
  481. /// (or, in terms of URL syntax, adds a trailing slash)
  482. /// so that the entire path is considered when using this URL as a base URL.
  483. ///
  484. /// For example:
  485. ///
  486. /// * `"index.html"` parsed with `Url::from_directory_path(Path::new("/var/www"))`
  487. /// as the base URL is `file:///var/www/index.html`
  488. /// * `"index.html"` parsed with `Url::from_file_path(Path::new("/var/www/"))`
  489. /// as the base URL is `file:///var/index.html`, which might not be what was intended.
  490. ///
  491. /// (Note that `Path::new` removes any trailing slash.)
  492. pub fn from_directory_path<P: AsRef<Path>>(path: P) -> Result<Url, ()> {
  493. let mut path = try!(path_to_file_url_path(path.as_ref()));
  494. // Add an empty path component (i.e. a trailing slash in serialization)
  495. // so that the entire path is used as a base URL.
  496. path.push("".to_owned());
  497. Ok(Url::from_path_common(path))
  498. }
  499. fn from_path_common(path: Vec<String>) -> Url {
  500. Url {
  501. scheme: "file".to_owned(),
  502. scheme_data: SchemeData::Relative(RelativeSchemeData {
  503. username: "".to_owned(),
  504. password: None,
  505. port: None,
  506. default_port: None,
  507. host: Host::Domain("".to_owned()),
  508. path: path,
  509. }),
  510. query: None,
  511. fragment: None,
  512. }
  513. }
  514. /// Assuming the URL is in the `file` scheme or similar,
  515. /// convert its path to an absolute `std::path::Path`.
  516. ///
  517. /// **Note:** This does not actually check the URL’s `scheme`,
  518. /// and may give nonsensical results for other schemes.
  519. /// It is the user’s responsibility to check the URL’s scheme before calling this.
  520. ///
  521. /// The return type (when `Ok()`) is generic and can be either `std::path::posix::Path`
  522. /// or `std::path::windows::Path`.
  523. /// (Use `std::path::Path` to pick one of them depending on the local system.)
  524. /// If the compiler can not infer the desired type from context, you may have to specify it:
  525. ///
  526. /// ```ignore
  527. /// let path = url.to_file_path::<std::path::posix::Path>();
  528. /// ```
  529. ///
  530. /// Returns `Err` if the host is neither empty nor `"localhost"`,
  531. /// or if `Path::new_opt()` returns `None`.
  532. /// (That is, if the percent-decoded path contains a NUL byte or,
  533. /// for a Windows path, is not UTF-8.)
  534. #[inline]
  535. pub fn to_file_path(&self) -> Result<PathBuf, ()> {
  536. match self.scheme_data {
  537. SchemeData::Relative(ref scheme_data) => scheme_data.to_file_path(),
  538. SchemeData::NonRelative(..) => Err(()),
  539. }
  540. }
  541. /// Return the serialization of this URL as a string.
  542. pub fn serialize(&self) -> String {
  543. self.to_string()
  544. }
  545. /// Return the origin of this URL (https://url.spec.whatwg.org/#origin)
  546. pub fn origin(&self) -> Origin {
  547. match &*self.scheme {
  548. "blob" => {
  549. let result = Url::parse(self.non_relative_scheme_data().unwrap());
  550. match result {
  551. Ok(ref url) => url.origin(),
  552. Err(_) => Origin::UID(OpaqueOrigin::new())
  553. }
  554. },
  555. "ftp" | "gopher" | "http" | "https" | "ws" | "wss" => {
  556. Origin::Tuple(self.scheme.clone(), self.host().unwrap().clone(),
  557. self.port_or_default().unwrap())
  558. },
  559. // TODO: Figure out what to do if the scheme is a file
  560. "file" => Origin::UID(OpaqueOrigin::new()),
  561. _ => Origin::UID(OpaqueOrigin::new())
  562. }
  563. }
  564. /// Return the serialization of this URL, without the fragment identifier, as a string
  565. pub fn serialize_no_fragment(&self) -> String {
  566. UrlNoFragmentFormatter{ url: self }.to_string()
  567. }
  568. /// If the URL is *non-relative*, return the string scheme data.
  569. #[inline]
  570. pub fn non_relative_scheme_data(&self) -> Option<&str> {
  571. match self.scheme_data {
  572. SchemeData::Relative(..) => None,
  573. SchemeData::NonRelative(ref scheme_data) => Some(scheme_data),
  574. }
  575. }
  576. /// If the URL is *non-relative*, return a mutable reference to the string scheme data.
  577. #[inline]
  578. pub fn non_relative_scheme_data_mut(&mut self) -> Option<&mut String> {
  579. match self.scheme_data {
  580. SchemeData::Relative(..) => None,
  581. SchemeData::NonRelative(ref mut scheme_data) => Some(scheme_data),
  582. }
  583. }
  584. /// If the URL is in a *relative scheme*, return the structured scheme data.
  585. #[inline]
  586. pub fn relative_scheme_data(&self) -> Option<&RelativeSchemeData> {
  587. match self.scheme_data {
  588. SchemeData::Relative(ref scheme_data) => Some(scheme_data),
  589. SchemeData::NonRelative(..) => None,
  590. }
  591. }
  592. /// If the URL is in a *relative scheme*,
  593. /// return a mutable reference to the structured scheme data.
  594. #[inline]
  595. pub fn relative_scheme_data_mut(&mut self) -> Option<&mut RelativeSchemeData> {
  596. match self.scheme_data {
  597. SchemeData::Relative(ref mut scheme_data) => Some(scheme_data),
  598. SchemeData::NonRelative(..) => None,
  599. }
  600. }
  601. /// If the URL is in a *relative scheme*, return its username.
  602. #[inline]
  603. pub fn username(&self) -> Option<&str> {
  604. self.relative_scheme_data().map(|scheme_data| &*scheme_data.username)
  605. }
  606. /// If the URL is in a *relative scheme*, return a mutable reference to its username.
  607. #[inline]
  608. pub fn username_mut(&mut self) -> Option<&mut String> {
  609. self.relative_scheme_data_mut().map(|scheme_data| &mut scheme_data.username)
  610. }
  611. /// Percent-decode the URL’s username, if any.
  612. ///
  613. /// This is “lossy”: invalid UTF-8 percent-encoded byte sequences
  614. /// will be replaced � U+FFFD, the replacement character.
  615. #[inline]
  616. pub fn lossy_percent_decode_username(&self) -> Option<String> {
  617. self.relative_scheme_data().map(|scheme_data| scheme_data.lossy_percent_decode_username())
  618. }
  619. /// If the URL is in a *relative scheme*, return its password, if any.
  620. #[inline]
  621. pub fn password(&self) -> Option<&str> {
  622. self.relative_scheme_data().and_then(|scheme_data|
  623. scheme_data.password.as_ref().map(|password| password as &str))
  624. }
  625. /// If the URL is in a *relative scheme*, return a mutable reference to its password, if any.
  626. #[inline]
  627. pub fn password_mut(&mut self) -> Option<&mut String> {
  628. self.relative_scheme_data_mut().and_then(|scheme_data| scheme_data.password.as_mut())
  629. }
  630. /// Percent-decode the URL’s password, if any.
  631. ///
  632. /// This is “lossy”: invalid UTF-8 percent-encoded byte sequences
  633. /// will be replaced � U+FFFD, the replacement character.
  634. #[inline]
  635. pub fn lossy_percent_decode_password(&self) -> Option<String> {
  636. self.relative_scheme_data().and_then(|scheme_data|
  637. scheme_data.lossy_percent_decode_password())
  638. }
  639. /// Serialize the URL's username and password, if any.
  640. ///
  641. /// Format: "<username>:<password>@"
  642. #[inline]
  643. pub fn serialize_userinfo(&mut self) -> Option<String> {
  644. self.relative_scheme_data().map(|scheme_data| scheme_data.serialize_userinfo())
  645. }
  646. /// If the URL is in a *relative scheme*, return its structured host.
  647. #[inline]
  648. pub fn host(&self) -> Option<&Host> {
  649. self.relative_scheme_data().map(|scheme_data| &scheme_data.host)
  650. }
  651. /// If the URL is in a *relative scheme*, return a mutable reference to its structured host.
  652. #[inline]
  653. pub fn host_mut(&mut self) -> Option<&mut Host> {
  654. self.relative_scheme_data_mut().map(|scheme_data| &mut scheme_data.host)
  655. }
  656. /// If the URL is in a *relative scheme* and its host is a domain,
  657. /// return the domain as a string.
  658. #[inline]
  659. pub fn domain(&self) -> Option<&str> {
  660. self.relative_scheme_data().and_then(|scheme_data| scheme_data.domain())
  661. }
  662. /// If the URL is in a *relative scheme* and its host is a domain,
  663. /// return a mutable reference to the domain string.
  664. #[inline]
  665. pub fn domain_mut(&mut self) -> Option<&mut String> {
  666. self.relative_scheme_data_mut().and_then(|scheme_data| scheme_data.domain_mut())
  667. }
  668. /// If the URL is in a *relative scheme*, serialize its host as a string.
  669. ///
  670. /// A domain a returned as-is, an IPv6 address between [] square brackets.
  671. #[inline]
  672. pub fn serialize_host(&self) -> Option<String> {
  673. self.relative_scheme_data().map(|scheme_data| scheme_data.host.serialize())
  674. }
  675. /// If the URL is in a *relative scheme* and has a port number, return it.
  676. #[inline]
  677. pub fn port(&self) -> Option<u16> {
  678. self.relative_scheme_data().and_then(|scheme_data| scheme_data.port)
  679. }
  680. /// If the URL is in a *relative scheme*, return a mutable reference to its port.
  681. #[inline]
  682. pub fn port_mut(&mut self) -> Option<&mut Option<u16>> {
  683. self.relative_scheme_data_mut().map(|scheme_data| &mut scheme_data.port)
  684. }
  685. /// If the URL is in a *relative scheme* that is not a file-like,
  686. /// return its port number, even if it is the default.
  687. #[inline]
  688. pub fn port_or_default(&self) -> Option<u16> {
  689. self.relative_scheme_data().and_then(|scheme_data| scheme_data.port_or_default())
  690. }
  691. /// If the URL is in a *relative scheme*, return its path components.
  692. #[inline]
  693. pub fn path(&self) -> Option<&[String]> {
  694. self.relative_scheme_data().map(|scheme_data| &*scheme_data.path)
  695. }
  696. /// If the URL is in a *relative scheme*, return a mutable reference to its path components.
  697. #[inline]
  698. pub fn path_mut(&mut self) -> Option<&mut Vec<String>> {
  699. self.relative_scheme_data_mut().map(|scheme_data| &mut scheme_data.path)
  700. }
  701. /// If the URL is in a *relative scheme*, serialize its path as a string.
  702. ///
  703. /// The returned string starts with a "/" slash, and components are separated by slashes.
  704. /// A trailing slash represents an empty last component.
  705. #[inline]
  706. pub fn serialize_path(&self) -> Option<String> {
  707. self.relative_scheme_data().map(|scheme_data| scheme_data.serialize_path())
  708. }
  709. /// Parse the URL’s query string, if any, as `application/x-www-form-urlencoded`
  710. /// and return a vector of (key, value) pairs.
  711. #[inline]
  712. pub fn query_pairs(&self) -> Option<Vec<(String, String)>> {
  713. self.query.as_ref().map(|query| form_urlencoded::parse(query.as_bytes()))
  714. }
  715. /// Serialize an iterator of (key, value) pairs as `application/x-www-form-urlencoded`
  716. /// and set it as the URL’s query string.
  717. #[inline]
  718. pub fn set_query_from_pairs<I, K, V>(&mut self, pairs: I)
  719. where I: IntoIterator, I::Item: Borrow<(K, V)>, K: AsRef<str>, V: AsRef<str> {
  720. self.query = Some(form_urlencoded::serialize(pairs));
  721. }
  722. /// Percent-decode the URL’s query string, if any.
  723. ///
  724. /// This is “lossy”: invalid UTF-8 percent-encoded byte sequences
  725. /// will be replaced � U+FFFD, the replacement character.
  726. #[inline]
  727. pub fn lossy_percent_decode_query(&self) -> Option<String> {
  728. self.query.as_ref().map(|value| lossy_utf8_percent_decode(value.as_bytes()))
  729. }
  730. /// Percent-decode the URL’s fragment identifier, if any.
  731. ///
  732. /// This is “lossy”: invalid UTF-8 percent-encoded byte sequences
  733. /// will be replaced � U+FFFD, the replacement character.
  734. #[inline]
  735. pub fn lossy_percent_decode_fragment(&self) -> Option<String> {
  736. self.fragment.as_ref().map(|value| lossy_utf8_percent_decode(value.as_bytes()))
  737. }
  738. /// Join a path with a base URL.
  739. ///
  740. /// Corresponds to the basic URL parser where `self` is the given base URL.
  741. #[inline]
  742. pub fn join(&self, input: &str) -> ParseResult<Url> {
  743. UrlParser::new().base_url(self).parse(input)
  744. }
  745. }
  746. impl rustc_serialize::Encodable for Url {
  747. fn encode<S: rustc_serialize::Encoder>(&self, encoder: &mut S) -> Result<(), S::Error> {
  748. encoder.emit_str(&self.to_string())
  749. }
  750. }
  751. impl rustc_serialize::Decodable for Url {
  752. fn decode<D: rustc_serialize::Decoder>(decoder: &mut D) -> Result<Url, D::Error> {
  753. Url::parse(&*try!(decoder.read_str())).map_err(|error| {
  754. decoder.error(&format!("URL parsing error: {}", error))
  755. })
  756. }
  757. }
  758. /// Serializes this URL into a `serde` stream.
  759. ///
  760. /// This implementation is only available if the `serde_serialization` Cargo feature is enabled.
  761. #[cfg(feature="serde_serialization")]
  762. impl serde::Serialize for Url {
  763. fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error> where S: serde::Serializer {
  764. format!("{}", self).serialize(serializer)
  765. }
  766. }
  767. /// Deserializes this URL from a `serde` stream.
  768. ///
  769. /// This implementation is only available if the `serde_serialization` Cargo feature is enabled.
  770. #[cfg(feature="serde_serialization")]
  771. impl serde::Deserialize for Url {
  772. fn deserialize<D>(deserializer: &mut D) -> Result<Url, D::Error> where D: serde::Deserializer {
  773. let string_representation: String = try!(serde::Deserialize::deserialize(deserializer));
  774. Ok(FromStr::from_str(&string_representation[..]).unwrap())
  775. }
  776. }
  777. impl fmt::Display for Url {
  778. fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
  779. try!(UrlNoFragmentFormatter{ url: self }.fmt(formatter));
  780. if let Some(ref fragment) = self.fragment {
  781. try!(formatter.write_str("#"));
  782. try!(formatter.write_str(fragment));
  783. }
  784. Ok(())
  785. }
  786. }
  787. impl fmt::Display for SchemeData {
  788. fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
  789. match *self {
  790. SchemeData::Relative(ref scheme_data) => scheme_data.fmt(formatter),
  791. SchemeData::NonRelative(ref scheme_data) => scheme_data.fmt(formatter),
  792. }
  793. }
  794. }
  795. impl RelativeSchemeData {
  796. /// Percent-decode the URL’s username.
  797. ///
  798. /// This is “lossy”: invalid UTF-8 percent-encoded byte sequences
  799. /// will be replaced � U+FFFD, the replacement character.
  800. #[inline]
  801. pub fn lossy_percent_decode_username(&self) -> String {
  802. lossy_utf8_percent_decode(self.username.as_bytes())
  803. }
  804. /// Percent-decode the URL’s password, if any.
  805. ///
  806. /// This is “lossy”: invalid UTF-8 percent-encoded byte sequences
  807. /// will be replaced � U+FFFD, the replacement character.
  808. #[inline]
  809. pub fn lossy_percent_decode_password(&self) -> Option<String> {
  810. self.password.as_ref().map(|value| lossy_utf8_percent_decode(value.as_bytes()))
  811. }
  812. /// Assuming the URL is in the `file` scheme or similar,
  813. /// convert its path to an absolute `std::path::Path`.
  814. ///
  815. /// **Note:** This does not actually check the URL’s `scheme`,
  816. /// and may give nonsensical results for other schemes.
  817. /// It is the user’s responsibility to check the URL’s scheme before calling this.
  818. ///
  819. /// The return type (when `Ok()`) is generic and can be either `std::path::posix::Path`
  820. /// or `std::path::windows::Path`.
  821. /// (Use `std::path::Path` to pick one of them depending on the local system.)
  822. /// If the compiler can not infer the desired type from context, you may have to specifiy it:
  823. ///
  824. /// ```ignore
  825. /// let path = url.to_file_path::<std::path::posix::Path>();
  826. /// ```
  827. ///
  828. /// Returns `Err` if the host is neither empty nor `"localhost"`,
  829. /// or if `Path::new_opt()` returns `None`.
  830. /// (That is, if the percent-decoded path contains a NUL byte or,
  831. /// for a Windows path, is not UTF-8.)
  832. #[inline]
  833. pub fn to_file_path(&self) -> Result<PathBuf, ()> {
  834. // FIXME: Figure out what to do w.r.t host.
  835. if !matches!(self.domain(), Some("") | Some("localhost")) {
  836. return Err(())
  837. }
  838. file_url_path_to_pathbuf(&self.path)
  839. }
  840. /// If the host is a domain, return the domain as a string.
  841. #[inline]
  842. pub fn domain(&self) -> Option<&str> {
  843. match self.host {
  844. Host::Domain(ref domain) => Some(domain),
  845. _ => None,
  846. }
  847. }
  848. /// If the host is a domain, return a mutable reference to the domain string.
  849. #[inline]
  850. pub fn domain_mut(&mut self) -> Option<&mut String> {
  851. match self.host {
  852. Host::Domain(ref mut domain) => Some(domain),
  853. _ => None,
  854. }
  855. }
  856. /// Return the port number of the URL, even if it is the default.
  857. /// Return `None` for file-like URLs.
  858. #[inline]
  859. pub fn port_or_default(&self) -> Option<u16> {
  860. self.port.or(self.default_port)
  861. }
  862. /// Serialize the path as a string.
  863. ///
  864. /// The returned string starts with a "/" slash, and components are separated by slashes.
  865. /// A trailing slash represents an empty last component.
  866. pub fn serialize_path(&self) -> String {
  867. PathFormatter {
  868. path: &self.path
  869. }.to_string()
  870. }
  871. /// Serialize the userinfo as a string.
  872. ///
  873. /// Format: "<username>:<password>@".
  874. pub fn serialize_userinfo(&self) -> String {
  875. UserInfoFormatter {
  876. username: &self.username,
  877. password: self.password.as_ref().map(|s| s as &str)
  878. }.to_string()
  879. }
  880. }
  881. impl fmt::Display for RelativeSchemeData {
  882. fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
  883. // Write the scheme-trailing double slashes.
  884. try!(formatter.write_str("//"));
  885. // Write the user info.
  886. try!(UserInfoFormatter {
  887. username: &self.username,
  888. password: self.password.as_ref().map(|s| s as &str)
  889. }.fmt(formatter));
  890. // Write the host.
  891. try!(self.host.fmt(formatter));
  892. // Write the port.
  893. match self.port {
  894. Some(port) => {
  895. try!(write!(formatter, ":{}", port));
  896. },
  897. None => {}
  898. }
  899. // Write the path.
  900. PathFormatter {
  901. path: &self.path
  902. }.fmt(formatter)
  903. }
  904. }
  905. #[cfg(unix)]
  906. fn path_to_file_url_path(path: &Path) -> Result<Vec<String>, ()> {
  907. use std::os::unix::prelude::OsStrExt;
  908. if !path.is_absolute() {
  909. return Err(())
  910. }
  911. // skip the root component
  912. Ok(path.components().skip(1).map(|c| {
  913. percent_encode(c.as_os_str().as_bytes(), DEFAULT_ENCODE_SET)
  914. }).collect())
  915. }
  916. #[cfg(windows)]
  917. fn path_to_file_url_path(path: &Path) -> Result<Vec<String>, ()> {
  918. path_to_file_url_path_windows(path)
  919. }
  920. // Build this unconditionally to alleviate https://github.com/servo/rust-url/issues/102
  921. #[cfg_attr(not(windows), allow(dead_code))]
  922. fn path_to_file_url_path_windows(path: &Path) -> Result<Vec<String>, ()> {
  923. use std::path::{Prefix, Component};
  924. if !path.is_absolute() {
  925. return Err(())
  926. }
  927. let mut components = path.components();
  928. let disk = match components.next() {
  929. Some(Component::Prefix(ref p)) => match p.kind() {
  930. Prefix::Disk(byte) => byte,
  931. _ => return Err(()),
  932. },
  933. // FIXME: do something with UNC and other prefixes?
  934. _ => return Err(())
  935. };
  936. // Start with the prefix, e.g. "C:"
  937. let mut path = vec![format!("{}:", disk as char)];
  938. for component in components {
  939. if component == Component::RootDir { continue }
  940. // FIXME: somehow work with non-unicode?
  941. let part = match component.as_os_str().to_str() {
  942. Some(s) => s,
  943. None => return Err(()),
  944. };
  945. path.push(percent_encode(part.as_bytes(), DEFAULT_ENCODE_SET));
  946. }
  947. Ok(path)
  948. }
  949. #[cfg(unix)]
  950. fn file_url_path_to_pathbuf(path: &[String]) -> Result<PathBuf, ()> {
  951. use std::ffi::OsStr;
  952. use std::os::unix::prelude::OsStrExt;
  953. use std::path::PathBuf;
  954. use percent_encoding::percent_decode_to;
  955. if path.is_empty() {
  956. return Ok(PathBuf::from("/"))
  957. }
  958. let mut bytes = Vec::new();
  959. for path_part in path {
  960. bytes.push(b'/');
  961. percent_decode_to(path_part.as_bytes(), &mut bytes);
  962. }
  963. let os_str = OsStr::from_bytes(&bytes);
  964. let path = PathBuf::from(os_str);
  965. debug_assert!(path.is_absolute(),
  966. "to_file_path() failed to produce an absolute Path");
  967. Ok(path)
  968. }
  969. #[cfg(windows)]
  970. fn file_url_path_to_pathbuf(path: &[String]) -> Result<PathBuf, ()> {
  971. file_url_path_to_pathbuf_windows(path)
  972. }
  973. // Build this unconditionally to alleviate https://github.com/servo/rust-url/issues/102
  974. #[cfg_attr(not(windows), allow(dead_code))]
  975. fn file_url_path_to_pathbuf_windows(path: &[String]) -> Result<PathBuf, ()> {
  976. use percent_encoding::percent_decode;
  977. if path.is_empty() {
  978. return Err(())
  979. }
  980. let prefix = &*path[0];
  981. if prefix.len() != 2 || !parser::starts_with_ascii_alpha(prefix)
  982. || prefix.as_bytes()[1] != b':' {
  983. return Err(())
  984. }
  985. let mut string = prefix.to_owned();
  986. for path_part in &path[1..] {
  987. string.push('\\');
  988. // Currently non-unicode windows paths cannot be represented
  989. match String::from_utf8(percent_decode(path_part.as_bytes())) {
  990. Ok(s) => string.push_str(&s),
  991. Err(..) => return Err(()),
  992. }
  993. }
  994. let path = PathBuf::from(string);
  995. debug_assert!(path.is_absolute(),
  996. "to_file_path() failed to produce an absolute Path");
  997. Ok(path)
  998. }