lib.rs 38 KB

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