lib.rs 50 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474
  1. // Copyright 2013-2014 Simon Sapin.
  2. //
  3. // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
  4. // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
  5. // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
  6. // option. This file may not be copied, modified, or distributed
  7. // except according to those terms.
  8. #![crate_name = "url"]
  9. /*!
  10. <a href="https://github.com/servo/rust-url"><img style="position: absolute; top: 0; left: 0; border: 0;" src="../github.png" alt="Fork me on GitHub"></a>
  11. <style>.sidebar { margin-top: 53px }</style>
  12. rust-url is an implementation of the [URL Standard](http://url.spec.whatwg.org/)
  13. for the [Rust](http://rust-lang.org/) programming language.
  14. It builds with [Cargo](http://crates.io/).
  15. To use it in your project, add this to your `Cargo.toml` file:
  16. ```Cargo
  17. [dependencies.url]
  18. git = "https://github.com/servo/rust-url"
  19. ```
  20. This will automatically pull in the
  21. [rust-encoding](https://github.com/lifthrasiir/rust-encoding) dependency.
  22. rust-url is a replacement of the [`url` crate](http://doc.rust-lang.org/url/index.html)
  23. currently distributed with Rust.
  24. rust-url’s crate is also named `url`.
  25. Cargo will automatically resolve the name conflict,
  26. but that means that you can not also use the old `url` in the same crate.
  27. If you’re not using Cargo, you’ll need to pass `--extern url=/path/to/liburl.rlib`
  28. explicitly to rustc.
  29. # URL parsing and data structures
  30. First, URL parsing may fail for various reasons and therefore returns a `Result`.
  31. ```
  32. use url::{Url, InvalidIpv6Address};
  33. assert!(Url::parse("http://[:::1]") == Err(InvalidIpv6Address))
  34. ```
  35. Let’s parse a valid URL and look at its components.
  36. ```
  37. use url::{Url, RelativeSchemeData, NonRelativeSchemeData};
  38. let issue_list_url = Url::parse(
  39. "https://github.com/rust-lang/rust/issues?labels=E-easy&state=open"
  40. ).unwrap();
  41. assert!(issue_list_url.scheme == "https".to_string());
  42. assert!(issue_list_url.domain() == Some("github.com"));
  43. assert!(issue_list_url.port() == Some(""));
  44. assert!(issue_list_url.path() == Some(&["rust-lang".to_string(),
  45. "rust".to_string(),
  46. "issues".to_string()]));
  47. assert!(issue_list_url.query == Some("labels=E-easy&state=open".to_string()));
  48. assert!(issue_list_url.fragment == None);
  49. match issue_list_url.scheme_data {
  50. RelativeSchemeData(..) => {}, // Expected
  51. NonRelativeSchemeData(..) => fail!(),
  52. }
  53. ```
  54. The `scheme`, `query`, and `fragment` are directly fields of the `Url` struct:
  55. they apply to all URLs.
  56. Every other components has accessors because they only apply to URLs said to be
  57. “in a relative scheme”. `https` is a relative scheme, but `data` is not:
  58. ```
  59. use url::{Url, NonRelativeSchemeData};
  60. let data_url = Url::parse("data:text/plain,Hello#").unwrap();
  61. assert!(data_url.scheme == "data".to_string());
  62. assert!(data_url.scheme_data == NonRelativeSchemeData("text/plain,Hello".to_string()));
  63. assert!(data_url.non_relative_scheme_data() == Some("text/plain,Hello"));
  64. assert!(data_url.query == None);
  65. assert!(data_url.fragment == Some("".to_string()));
  66. ```
  67. # Base URL
  68. Many contexts allow URL *references* that can be relative to a *base URL*:
  69. ```html
  70. <link rel="stylesheet" href="../main.css">
  71. ```
  72. Since parsed URL are absolute, giving a base is required:
  73. ```
  74. use url::{Url, RelativeUrlWithoutBase};
  75. assert!(Url::parse("../main.css") == Err(RelativeUrlWithoutBase))
  76. ```
  77. `UrlParser` is a method-chaining API to provide various optional parameters
  78. to URL parsing, including a base URL.
  79. ```
  80. use url::{Url, UrlParser};
  81. let this_document = Url::parse("http://servo.github.io/rust-url/url/index.html").unwrap();
  82. let css_url = UrlParser::new().base_url(&this_document).parse("../main.css").unwrap();
  83. assert!(css_url.serialize() == "http://servo.github.io/rust-url/main.css".to_string());
  84. ```
  85. */
  86. #![feature(macro_rules, default_type_params)]
  87. extern crate encoding;
  88. #[cfg(test)]
  89. extern crate serialize;
  90. use std::cmp;
  91. use std::fmt::{Formatter, FormatError, Show};
  92. use std::hash;
  93. use std::path;
  94. use std::ascii::OwnedStrAsciiExt;
  95. use encoding::EncodingRef;
  96. mod encode_sets;
  97. mod parser;
  98. pub mod form_urlencoded;
  99. pub mod punycode;
  100. #[cfg(test)]
  101. mod tests;
  102. /// The parsed representation of an absolute URL.
  103. #[deriving(PartialEq, Eq, Clone)]
  104. pub struct Url {
  105. /// The scheme (a.k.a. protocol) of the URL, in ASCII lower case.
  106. pub scheme: String,
  107. /// The components of the URL whose representation depends on where the scheme is *relative*.
  108. pub scheme_data: SchemeData,
  109. /// The query string of the URL.
  110. ///
  111. /// `None` if the `?` delimiter character was not part of the parsed input,
  112. /// otherwise a possibly empty, pecent-encoded string.
  113. ///
  114. /// Percent encoded strings are within the ASCII range.
  115. ///
  116. /// See also the `query_pairs`, `set_query_from_pairs`,
  117. /// and `lossy_precent_decode_query` methods.
  118. pub query: Option<String>,
  119. /// The fragment identifier of the URL.
  120. ///
  121. /// `None` if the `#` delimiter character was not part of the parsed input,
  122. /// otherwise a possibly empty, pecent-encoded string.
  123. ///
  124. /// Percent encoded strings are within the ASCII range.
  125. ///
  126. /// See also the `lossy_precent_decode_fragment` method.
  127. pub fragment: Option<String>,
  128. }
  129. /// The components of the URL whose representation depends on where the scheme is *relative*.
  130. #[deriving(PartialEq, Eq, Clone)]
  131. pub enum SchemeData {
  132. /// Components for URLs in a *relative* scheme such as HTTP.
  133. RelativeSchemeData(RelativeSchemeData),
  134. /// No further structure is assumed for *non-relative* schemes such as `data` and `mailto`.
  135. ///
  136. /// This is a single percent-encoded string, whose interpretation depends on the scheme.
  137. ///
  138. /// Percent encoded strings are within the ASCII range.
  139. NonRelativeSchemeData(String),
  140. }
  141. /// Components for URLs in a *relative* scheme such as HTTP.
  142. #[deriving(PartialEq, Eq, Clone)]
  143. pub struct RelativeSchemeData {
  144. /// The username of the URL, as a possibly empty, pecent-encoded string.
  145. ///
  146. /// Percent encoded strings are within the ASCII range.
  147. ///
  148. /// See also the `lossy_precent_decode_username` method.
  149. pub username: String,
  150. /// The password of the URL.
  151. ///
  152. /// `None` if the `:` delimiter character was not part of the parsed input,
  153. /// otherwise a possibly empty, pecent-encoded string.
  154. ///
  155. /// Percent encoded strings are within the ASCII range.
  156. ///
  157. /// See also the `lossy_precent_decode_password` method.
  158. pub password: Option<String>,
  159. /// The host of the URL, either a domain name or an IPv4 address
  160. pub host: Host,
  161. /// The port number of the URL, in ASCII decimal,
  162. /// or the empty string for no port number (in the file scheme) or the default port number.
  163. pub port: String,
  164. /// The path of the URL, as vector of pecent-encoded strings.
  165. ///
  166. /// Percent encoded strings are within the ASCII range.
  167. ///
  168. /// See also the `serialize_path` method and,
  169. /// for URLs in the `file` scheme, the `to_file_path` method.
  170. pub path: Vec<String>,
  171. }
  172. /// The host name of an URL.
  173. #[deriving(PartialEq, Eq, Clone)]
  174. pub enum Host {
  175. /// A (DNS) domain name or an IPv4 address.
  176. ///
  177. /// FIXME: IPv4 probably should be a separate variant.
  178. /// See https://www.w3.org/Bugs/Public/show_bug.cgi?id=26431
  179. Domain(String),
  180. /// An IPv6 address, represented inside `[...]` square brackets
  181. /// so that `:` colon characters in the address are not ambiguous
  182. /// with the port number delimiter.
  183. Ipv6(Ipv6Address),
  184. }
  185. /// A 128 bit IPv6 address
  186. pub struct Ipv6Address {
  187. pub pieces: [u16, ..8]
  188. }
  189. impl Clone for Ipv6Address {
  190. fn clone(&self) -> Ipv6Address {
  191. Ipv6Address { pieces: self.pieces }
  192. }
  193. }
  194. impl Eq for Ipv6Address {}
  195. impl PartialEq for Ipv6Address {
  196. fn eq(&self, other: &Ipv6Address) -> bool {
  197. self.pieces == other.pieces
  198. }
  199. }
  200. impl<S: hash::Writer> hash::Hash<S> for Url {
  201. fn hash(&self, state: &mut S) {
  202. self.serialize().hash(state)
  203. }
  204. }
  205. /// A set of optional parameters for URL parsing.
  206. pub struct UrlParser<'a> {
  207. base_url: Option<&'a Url>,
  208. query_encoding_override: Option<EncodingRef>,
  209. error_handler: ErrorHandler,
  210. scheme_type_mapper: fn(scheme: &str) -> SchemeType,
  211. }
  212. /// A method-chaining API to provide a set of optional parameters for URL parsing.
  213. impl<'a> UrlParser<'a> {
  214. /// Return a new UrlParser with default parameters.
  215. #[inline]
  216. pub fn new() -> UrlParser<'a> {
  217. UrlParser {
  218. base_url: None,
  219. query_encoding_override: None,
  220. error_handler: silent_handler,
  221. scheme_type_mapper: whatwg_scheme_type_mapper,
  222. }
  223. }
  224. /// Set the base URL used for resolving relative URL references, and return the `UrlParser`.
  225. /// The default is no base URL, so that relative URLs references fail to parse.
  226. #[inline]
  227. pub fn base_url<'b>(&'b mut self, value: &'a Url) -> &'b mut UrlParser<'a> {
  228. self.base_url = Some(value);
  229. self
  230. }
  231. /// Set the character encoding the query string is encoded as before percent-encoding,
  232. /// and return the `UrlParser`.
  233. ///
  234. /// This legacy quirk is only relevant to HTML.
  235. #[inline]
  236. pub fn query_encoding_override<'b>(&'b mut self, value: EncodingRef) -> &'b mut UrlParser<'a> {
  237. self.query_encoding_override = Some(value);
  238. self
  239. }
  240. /// Set an error handler for non-fatal parse errors, and return the `UrlParser`.
  241. ///
  242. /// Non-fatal parse errors are normally ignored by the parser,
  243. /// but indicate violations of authoring requirements.
  244. /// An error handler can be used, for example, to log these errors in the console
  245. /// of a browser’s developer tools.
  246. ///
  247. /// The error handler can choose to make the error fatal by returning `Err(..)`
  248. #[inline]
  249. pub fn error_handler<'b>(&'b mut self, value: ErrorHandler) -> &'b mut UrlParser<'a> {
  250. self.error_handler = value;
  251. self
  252. }
  253. /// Set a *scheme type mapper*, and return the `UrlParser`.
  254. ///
  255. /// The URL parser behaves differently based on the `SchemeType` of the URL.
  256. /// See the documentation for `SchemeType` for more details.
  257. /// A *scheme type mapper* returns a `SchemeType`
  258. /// based on the scheme as an ASCII lower case string,
  259. /// as found in the `scheme` field of an `Url` struct.
  260. ///
  261. /// The default scheme type mapper is as follows:
  262. ///
  263. /// ```ignore
  264. /// fn whatwg_scheme_type_mapper(scheme: &str) -> SchemeType {
  265. /// match scheme {
  266. /// "file" => FileLikeRelativeScheme,
  267. /// "ftp" => RelativeScheme("21"),
  268. /// "gopher" => RelativeScheme("70"),
  269. /// "http" => RelativeScheme("80"),
  270. /// "https" => RelativeScheme("443"),
  271. /// "ws" => RelativeScheme("80"),
  272. /// "wss" => RelativeScheme("443"),
  273. /// _ => NonRelativeScheme,
  274. /// }
  275. /// }
  276. /// ```
  277. ///
  278. /// Note that unknown schemes default to non-relative.
  279. /// Overriding the scheme type mapper can allow, for example,
  280. /// parsing URLs in the `git` or `irc` scheme as relative.
  281. #[inline]
  282. pub fn scheme_type_mapper<'b>(&'b mut self, value: fn(scheme: &str) -> SchemeType)
  283. -> &'b mut UrlParser<'a> {
  284. self.scheme_type_mapper = value;
  285. self
  286. }
  287. /// Parse `input` as an URL, with all the parameters previously set in the `UrlParser`.
  288. #[inline]
  289. pub fn parse(&self, input: &str) -> ParseResult<Url> {
  290. parser::parse_url(input, self)
  291. }
  292. }
  293. /// Private convenience methods for use in parser.rs
  294. impl<'a> UrlParser<'a> {
  295. #[inline]
  296. fn parse_error(&self, error: ParseError) -> ParseResult<()> {
  297. (self.error_handler)(error)
  298. }
  299. #[inline]
  300. fn get_scheme_type(&self, scheme: &str) -> SchemeType {
  301. (self.scheme_type_mapper)(scheme)
  302. }
  303. }
  304. /// Determines the behavior of the URL parser for a given scheme.
  305. #[deriving(PartialEq, Eq)]
  306. pub enum SchemeType {
  307. /// Indicate that the scheme is *non-relative*.
  308. ///
  309. /// The *scheme data* of the URL
  310. /// (everything other than the scheme, query string, and fragment identifier)
  311. /// is parsed as a single percent-encoded string of which no structure is assumed.
  312. /// That string may need to be parsed further, per a scheme-specific format.
  313. NonRelativeScheme,
  314. /// Indicate that the scheme is *relative*, and what the default port number is.
  315. ///
  316. /// The *scheme data* is structured as
  317. /// *username*, *password*, *host*, *port number*, and *path*.
  318. /// Relative URL references are supported, if a base URL was given.
  319. /// The string value indicates the default port number as a string of ASCII digits,
  320. /// or the empty string to indicate no default port number.
  321. RelativeScheme(&'static str),
  322. /// Indicate a *relative* scheme similar to the *file* scheme.
  323. ///
  324. /// For example, you might want to have distinct `git+file` and `hg+file` URL schemes.
  325. ///
  326. /// This is like `RelativeScheme` except the host can be empty, there is no port number,
  327. /// and path parsing has (platform-independent) quirks to support Windows filenames.
  328. FileLikeRelativeScheme,
  329. }
  330. /// http://url.spec.whatwg.org/#relative-scheme
  331. pub fn whatwg_scheme_type_mapper(scheme: &str) -> SchemeType {
  332. match scheme {
  333. "file" => FileLikeRelativeScheme,
  334. "ftp" => RelativeScheme("21"),
  335. "gopher" => RelativeScheme("70"),
  336. "http" => RelativeScheme("80"),
  337. "https" => RelativeScheme("443"),
  338. "ws" => RelativeScheme("80"),
  339. "wss" => RelativeScheme("443"),
  340. _ => NonRelativeScheme,
  341. }
  342. }
  343. pub type ParseResult<T> = Result<T, ParseError>;
  344. /// Errors that can occur during parsing.
  345. #[deriving(PartialEq, Eq, Clone)]
  346. pub enum ParseError {
  347. EmptyHost,
  348. InvalidScheme,
  349. InvalidPort,
  350. InvalidIpv6Address,
  351. InvalidDomainCharacter,
  352. InvalidCharacter,
  353. InvalidBackslash,
  354. InvalidPercentEncoded,
  355. InvalidAtSymbolInUser,
  356. ExpectedTwoSlashes,
  357. NonUrlCodePoint,
  358. RelativeUrlWithScheme,
  359. RelativeUrlWithoutBase,
  360. RelativeUrlWithNonRelativeBase,
  361. NonAsciiDomainsNotSupportedYet,
  362. CannotSetFileScheme(&'static str),
  363. CannotSetJavascriptScheme(&'static str),
  364. CannotSetNonRelativeScheme(&'static str)
  365. }
  366. impl Show for ParseError {
  367. fn fmt(&self, fmt: &mut Formatter) -> Result<(), FormatError> {
  368. match *self {
  369. EmptyHost => "Empty host",
  370. InvalidScheme => "Invalid scheme",
  371. InvalidPort => "Invalid port number",
  372. InvalidIpv6Address => "Invalid IPv6 address",
  373. InvalidDomainCharacter => "Invalid domain character",
  374. InvalidCharacter => "Invalid character",
  375. InvalidBackslash => "Invalid backslash",
  376. InvalidPercentEncoded => "Invalid percent-encoded sequence",
  377. InvalidAtSymbolInUser => "Invalid @-symbol in user",
  378. ExpectedTwoSlashes => "Expected two slashes (//)",
  379. NonUrlCodePoint => "Non URL code point",
  380. RelativeUrlWithScheme => "Relative URL with scheme",
  381. RelativeUrlWithoutBase => "Relative URL without a base",
  382. RelativeUrlWithNonRelativeBase => "Relative URL with a non-relative base",
  383. NonAsciiDomainsNotSupportedYet => "Non Ascii domains are not support yet",
  384. CannotSetFileScheme(ref part) =>
  385. return write!(fmt, "Cannot set {} on file: URLs", part),
  386. CannotSetJavascriptScheme(ref part) =>
  387. return write!(fmt, "Cannot set {} on javascript: URLs", part),
  388. CannotSetNonRelativeScheme(ref part) =>
  389. return write!(fmt, "Cannot set {} on non-relative URLs", part),
  390. }.fmt(fmt)
  391. }
  392. }
  393. /// This is called on non-fatal parse errors.
  394. ///
  395. /// The handler can choose to continue or abort parsing by returning Ok() or Err(), respectively.
  396. /// See the `UrlParser::error_handler` method.
  397. ///
  398. /// FIXME: make this a by-ref closure when that’s supported.
  399. pub type ErrorHandler = fn(reason: ParseError) -> ParseResult<()>;
  400. fn silent_handler(_reason: ParseError) -> ParseResult<()> {
  401. Ok(())
  402. }
  403. impl Url {
  404. /// Parse an URL with the default `UrlParser` parameters.
  405. ///
  406. /// In particular, relative URL references are parse errors since no base URL is provided.
  407. #[inline]
  408. pub fn parse(input: &str) -> ParseResult<Url> {
  409. UrlParser::new().parse(input)
  410. }
  411. /// Convert a file name as `std::path::Path` into an URL in the `file` scheme.
  412. ///
  413. /// This returns `Err` if the given path is not absolute
  414. /// or, with a Windows path, if the prefix is not a disk prefix (e.g. `C:`).
  415. pub fn from_file_path<T: ToUrlPath>(path: &T) -> Result<Url, ()> {
  416. let path = try!(path.to_url_path());
  417. Ok(Url::from_path_common(path))
  418. }
  419. /// Convert a directory name as `std::path::Path` into an URL in the `file` scheme.
  420. ///
  421. /// This returns `Err` if the given path is not absolute
  422. /// or, with a Windows path, if the prefix is not a disk prefix (e.g. `C:`).
  423. ///
  424. /// Compared to `from_file_path`, this adds an empty component to the path
  425. /// (or, in terms of URL syntax, adds a trailing slash)
  426. /// so that the entire path is considered when using this URL as a base URL.
  427. ///
  428. /// For example:
  429. ///
  430. /// * `"index.html"` parsed with `Url::from_directory_path(Path::new("/var/www"))`
  431. /// as the base URL is `file:///var/www/index.html`
  432. /// * `"index.html"` parsed with `Url::from_file_path(Path::new("/var/www/"))`
  433. /// as the base URL is `file:///var/index.html`, which might not be what was intended.
  434. ///
  435. /// (Note that `Path::new` removes any trailing slash.)
  436. pub fn from_directory_path<T: ToUrlPath>(path: &T) -> Result<Url, ()> {
  437. let mut path = try!(path.to_url_path());
  438. // Add an empty path component (i.e. a trailing slash in serialization)
  439. // so that the entire path is used as a base URL.
  440. path.push("".to_string());
  441. Ok(Url::from_path_common(path))
  442. }
  443. fn from_path_common(path: Vec<String>) -> Url {
  444. Url {
  445. scheme: "file".to_string(),
  446. scheme_data: RelativeSchemeData(RelativeSchemeData {
  447. username: "".to_string(),
  448. password: None,
  449. port: "".to_string(),
  450. host: Domain("".to_string()),
  451. path: path,
  452. }),
  453. query: None,
  454. fragment: None,
  455. }
  456. }
  457. /// Assuming the URL is in the `file` scheme or similar,
  458. /// convert its path to an absolute `std::path::Path`.
  459. ///
  460. /// **Note:** This does not actually check the URL’s `scheme`,
  461. /// and may give nonsensical results for other schemes.
  462. /// It is the user’s responsibility to check the URL’s scheme before calling this.
  463. ///
  464. /// The return type (when `Ok()`) is generic and can be either `std::path::posix::Path`
  465. /// or `std::path::windows::Path`.
  466. /// (Use `std::path::Path` to pick one of them depending on the local system.)
  467. /// If the compiler can not infer the desired type from context, you may have to specifiy it:
  468. ///
  469. /// ```rust
  470. /// let path = url.to_file_path::<std::path::posix::Path>();
  471. /// ```
  472. ///
  473. /// Returns `Err` if the host is neither empty nor `"localhost"`,
  474. /// or if `Path::new_opt()` returns `None`.
  475. /// (That is, if the percent-decoded path contains a NUL byte or,
  476. /// for a Windows path, is not UTF-8.)
  477. #[inline]
  478. pub fn to_file_path<T: FromUrlPath>(&self) -> Result<T, ()> {
  479. match self.scheme_data {
  480. RelativeSchemeData(ref scheme_data) => scheme_data.to_file_path(),
  481. NonRelativeSchemeData(..) => Err(()),
  482. }
  483. }
  484. /// Return the serialization of this URL as a string.
  485. pub fn serialize(&self) -> String {
  486. self.to_string()
  487. }
  488. /// Return the serialization of this URL, without the fragment identifier, as a string
  489. pub fn serialize_no_fragment(&self) -> String {
  490. UrlNoFragmentFormatter{ url: self }.to_string()
  491. }
  492. /// If the URL is *non-relative*, return the string scheme data.
  493. #[inline]
  494. pub fn non_relative_scheme_data<'a>(&'a self) -> Option<&'a str> {
  495. match self.scheme_data {
  496. RelativeSchemeData(..) => None,
  497. NonRelativeSchemeData(ref scheme_data) => Some(scheme_data.as_slice()),
  498. }
  499. }
  500. /// If the URL is in a *relative scheme*, return the structured scheme data.
  501. #[inline]
  502. pub fn relative_scheme_data<'a>(&'a self) -> Option<&'a RelativeSchemeData> {
  503. match self.scheme_data {
  504. RelativeSchemeData(ref scheme_data) => Some(scheme_data),
  505. NonRelativeSchemeData(..) => None,
  506. }
  507. }
  508. /// If the URL is in a *relative scheme*, return its username.
  509. #[inline]
  510. pub fn username<'a>(&'a self) -> Option<&'a str> {
  511. self.relative_scheme_data().map(|scheme_data| scheme_data.username.as_slice())
  512. }
  513. /// Percent-decode the URL’s username, if any.
  514. ///
  515. /// This is “lossy”: invalid UTF-8 percent-encoded byte sequences
  516. /// will be replaced � U+FFFD, the replacement character.
  517. #[inline]
  518. pub fn lossy_precent_decode_username(&self) -> Option<String> {
  519. self.relative_scheme_data().map(|scheme_data| scheme_data.lossy_precent_decode_username())
  520. }
  521. /// If the URL is in a *relative scheme*, return its password, if any.
  522. #[inline]
  523. pub fn password<'a>(&'a self) -> Option<&'a str> {
  524. self.relative_scheme_data().and_then(|scheme_data|
  525. scheme_data.password.as_ref().map(|password| password.as_slice()))
  526. }
  527. /// Percent-decode the URL’s password, if any.
  528. ///
  529. /// This is “lossy”: invalid UTF-8 percent-encoded byte sequences
  530. /// will be replaced � U+FFFD, the replacement character.
  531. #[inline]
  532. pub fn lossy_precent_decode_password(&self) -> Option<String> {
  533. self.relative_scheme_data().and_then(|scheme_data|
  534. scheme_data.lossy_precent_decode_password())
  535. }
  536. /// If the URL is in a *relative scheme*, return its structured host.
  537. #[inline]
  538. pub fn host<'a>(&'a self) -> Option<&'a Host> {
  539. self.relative_scheme_data().map(|scheme_data| &scheme_data.host)
  540. }
  541. /// If the URL is in a *relative scheme* and its host is a domain,
  542. /// return the domain as a string.
  543. #[inline]
  544. pub fn domain<'a>(&'a self) -> Option<&'a str> {
  545. self.relative_scheme_data().and_then(|scheme_data| scheme_data.domain())
  546. }
  547. /// If the URL is in a *relative scheme*, serialize its host as a string.
  548. ///
  549. /// A domain a returned as-is, an IPv6 address between [] square brackets.
  550. #[inline]
  551. pub fn serialize_host(&self) -> Option<String> {
  552. self.relative_scheme_data().map(|scheme_data| scheme_data.host.serialize())
  553. }
  554. /// If the URL is in a *relative scheme*, return its port.
  555. #[inline]
  556. pub fn port<'a>(&'a self) -> Option<&'a str> {
  557. self.relative_scheme_data().map(|scheme_data| scheme_data.port.as_slice())
  558. }
  559. /// If the URL is in a *relative scheme*, return its path components.
  560. #[inline]
  561. pub fn path<'a>(&'a self) -> Option<&'a [String]> {
  562. self.relative_scheme_data().map(|scheme_data| scheme_data.path.as_slice())
  563. }
  564. /// If the URL is in a *relative scheme*, serialize its path as a string.
  565. ///
  566. /// The returned string starts with a "/" slash, and components are separated by slashes.
  567. /// A trailing slash represents an empty last component.
  568. #[inline]
  569. pub fn serialize_path(&self) -> Option<String> {
  570. self.relative_scheme_data().map(|scheme_data| scheme_data.serialize_path())
  571. }
  572. /// Parse the URL’s query string, if any, as `application/x-www-form-urlencoded`
  573. /// and return a vector of (key, value) pairs.
  574. #[inline]
  575. pub fn query_pairs(&self) -> Option<Vec<(String, String)>> {
  576. self.query.as_ref().map(|query| form_urlencoded::parse_str(query.as_slice()))
  577. }
  578. /// Serialize an iterator of (key, value) pairs as `application/x-www-form-urlencoded`
  579. /// and set it as the URL’s query string.
  580. #[inline]
  581. pub fn set_query_from_pairs<'a, I: Iterator<(&'a str, &'a str)>>(&mut self, pairs: I) {
  582. self.query = Some(form_urlencoded::serialize(pairs, None));
  583. }
  584. /// Percent-decode the URL’s query string, if any.
  585. ///
  586. /// This is “lossy”: invalid UTF-8 percent-encoded byte sequences
  587. /// will be replaced � U+FFFD, the replacement character.
  588. #[inline]
  589. pub fn lossy_precent_decode_query(&self) -> Option<String> {
  590. self.query.as_ref().map(|value| lossy_utf8_percent_decode(value.as_bytes()))
  591. }
  592. /// Percent-decode the URL’s fragment identifier, if any.
  593. ///
  594. /// This is “lossy”: invalid UTF-8 percent-encoded byte sequences
  595. /// will be replaced � U+FFFD, the replacement character.
  596. #[inline]
  597. pub fn lossy_precent_decode_fragment(&self) -> Option<String> {
  598. self.fragment.as_ref().map(|value| lossy_utf8_percent_decode(value.as_bytes()))
  599. }
  600. }
  601. impl Show for Url {
  602. fn fmt(&self, formatter: &mut Formatter) -> Result<(), FormatError> {
  603. try!(UrlNoFragmentFormatter{ url: self }.fmt(formatter));
  604. match self.fragment {
  605. None => (),
  606. Some(ref fragment) => {
  607. try!(formatter.write(b"#"));
  608. try!(formatter.write(fragment.as_bytes()));
  609. }
  610. }
  611. Ok(())
  612. }
  613. }
  614. struct UrlNoFragmentFormatter<'a> {
  615. url: &'a Url
  616. }
  617. impl<'a> Show for UrlNoFragmentFormatter<'a> {
  618. fn fmt(&self, formatter: &mut Formatter) -> Result<(), FormatError> {
  619. try!(formatter.write(self.url.scheme.as_bytes()));
  620. try!(formatter.write(b":"));
  621. try!(self.url.scheme_data.fmt(formatter));
  622. match self.url.query {
  623. None => (),
  624. Some(ref query) => {
  625. try!(formatter.write(b"?"));
  626. try!(formatter.write(query.as_bytes()));
  627. }
  628. }
  629. Ok(())
  630. }
  631. }
  632. impl Show for SchemeData {
  633. fn fmt(&self, formatter: &mut Formatter) -> Result<(), FormatError> {
  634. match *self {
  635. RelativeSchemeData(ref scheme_data) => scheme_data.fmt(formatter),
  636. NonRelativeSchemeData(ref scheme_data) => scheme_data.fmt(formatter),
  637. }
  638. }
  639. }
  640. impl RelativeSchemeData {
  641. /// Percent-decode the URL’s username.
  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_precent_decode_username(&self) -> String {
  647. lossy_utf8_percent_decode(self.username.as_bytes())
  648. }
  649. /// Percent-decode the URL’s password, if any.
  650. ///
  651. /// This is “lossy”: invalid UTF-8 percent-encoded byte sequences
  652. /// will be replaced � U+FFFD, the replacement character.
  653. #[inline]
  654. pub fn lossy_precent_decode_password(&self) -> Option<String> {
  655. self.password.as_ref().map(|value| lossy_utf8_percent_decode(value.as_bytes()))
  656. }
  657. /// Assuming the URL is in the `file` scheme or similar,
  658. /// convert its path to an absolute `std::path::Path`.
  659. ///
  660. /// **Note:** This does not actually check the URL’s `scheme`,
  661. /// and may give nonsensical results for other schemes.
  662. /// It is the user’s responsibility to check the URL’s scheme before calling this.
  663. ///
  664. /// The return type (when `Ok()`) is generic and can be either `std::path::posix::Path`
  665. /// or `std::path::windows::Path`.
  666. /// (Use `std::path::Path` to pick one of them depending on the local system.)
  667. /// If the compiler can not infer the desired type from context, you may have to specifiy it:
  668. ///
  669. /// ```rust
  670. /// let path = url.to_file_path::<std::path::posix::Path>();
  671. /// ```
  672. ///
  673. /// Returns `Err` if the host is neither empty nor `"localhost"`,
  674. /// or if `Path::new_opt()` returns `None`.
  675. /// (That is, if the percent-decoded path contains a NUL byte or,
  676. /// for a Windows path, is not UTF-8.)
  677. #[inline]
  678. pub fn to_file_path<T: FromUrlPath>(&self) -> Result<T, ()> {
  679. // FIXME: Figure out what to do w.r.t host.
  680. match self.domain() {
  681. Some("") | Some("localhost") => FromUrlPath::from_url_path(self.path.as_slice()),
  682. _ => Err(())
  683. }
  684. }
  685. /// If the host is a domain, return the domain as a string.
  686. #[inline]
  687. pub fn domain<'a>(&'a self) -> Option<&'a str> {
  688. match self.host {
  689. Domain(ref domain) => Some(domain.as_slice()),
  690. _ => None,
  691. }
  692. }
  693. /// Serialize the path as a string.
  694. ///
  695. /// The returned string starts with a "/" slash, and components are separated by slashes.
  696. /// A trailing slash represents an empty last component.
  697. pub fn serialize_path(&self) -> String {
  698. PathFormatter { path: &self.path }.to_string()
  699. }
  700. }
  701. struct PathFormatter<'a> {
  702. path: &'a Vec<String>
  703. }
  704. impl<'a> Show for PathFormatter<'a> {
  705. fn fmt(&self, formatter: &mut Formatter) -> Result<(), FormatError> {
  706. if self.path.is_empty() {
  707. formatter.write(b"/")
  708. } else {
  709. for path_part in self.path.iter() {
  710. try!(formatter.write(b"/"));
  711. try!(formatter.write(path_part.as_bytes()));
  712. }
  713. Ok(())
  714. }
  715. }
  716. }
  717. impl Show for RelativeSchemeData {
  718. fn fmt(&self, formatter: &mut Formatter) -> Result<(), FormatError> {
  719. try!(formatter.write(b"//"));
  720. if !self.username.is_empty() || self.password.is_some() {
  721. try!(formatter.write(self.username.as_bytes()));
  722. match self.password {
  723. None => (),
  724. Some(ref password) => {
  725. try!(formatter.write(b":"));
  726. try!(formatter.write(password.as_bytes()));
  727. }
  728. }
  729. try!(formatter.write(b"@"));
  730. }
  731. try!(self.host.fmt(formatter));
  732. if !self.port.is_empty() {
  733. try!(formatter.write(b":"));
  734. try!(formatter.write(self.port.as_bytes()));
  735. }
  736. PathFormatter { path: &self.path }.fmt(formatter)
  737. }
  738. }
  739. #[allow(dead_code)]
  740. struct UrlUtilsWrapper<'a> {
  741. url: &'a mut Url,
  742. parser: &'a UrlParser<'a>,
  743. }
  744. /// These methods are not meant for use in Rust code,
  745. /// only to help implement the JavaScript URLUtils API: http://url.spec.whatwg.org/#urlutils
  746. #[doc(hidden)]
  747. trait UrlUtils {
  748. fn set_scheme(&mut self, input: &str) -> ParseResult<()>;
  749. fn set_username(&mut self, input: &str) -> ParseResult<()>;
  750. fn set_password(&mut self, input: &str) -> ParseResult<()>;
  751. fn set_host_and_port(&mut self, input: &str) -> ParseResult<()>;
  752. fn set_host(&mut self, input: &str) -> ParseResult<()>;
  753. fn set_port(&mut self, input: &str) -> ParseResult<()>;
  754. fn set_path(&mut self, input: &str) -> ParseResult<()>;
  755. fn set_query(&mut self, input: &str) -> ParseResult<()>;
  756. fn set_fragment(&mut self, input: &str) -> ParseResult<()>;
  757. }
  758. impl<'a> UrlUtils for UrlUtilsWrapper<'a> {
  759. /// `URLUtils.protocol` setter
  760. fn set_scheme(&mut self, input: &str) -> ParseResult<()> {
  761. match parser::parse_scheme(input.as_slice(), parser::SetterContext) {
  762. Some((scheme, _)) => {
  763. self.url.scheme = scheme;
  764. Ok(())
  765. },
  766. None => Err(InvalidScheme),
  767. }
  768. }
  769. /// `URLUtils.username` setter
  770. fn set_username(&mut self, input: &str) -> ParseResult<()> {
  771. match self.url.scheme_data {
  772. RelativeSchemeData(RelativeSchemeData { ref mut username, .. }) => {
  773. username.truncate(0);
  774. utf8_percent_encode_to(input, USERNAME_ENCODE_SET, username);
  775. Ok(())
  776. },
  777. NonRelativeSchemeData(_) => Err(CannotSetNonRelativeScheme("username"))
  778. }
  779. }
  780. /// `URLUtils.password` setter
  781. fn set_password(&mut self, input: &str) -> ParseResult<()> {
  782. match self.url.scheme_data {
  783. RelativeSchemeData(RelativeSchemeData { ref mut password, .. }) => {
  784. let mut new_password = String::new();
  785. utf8_percent_encode_to(input, PASSWORD_ENCODE_SET, &mut new_password);
  786. *password = Some(new_password);
  787. Ok(())
  788. },
  789. NonRelativeSchemeData(_) => Err(CannotSetNonRelativeScheme("password"))
  790. }
  791. }
  792. /// `URLUtils.host` setter
  793. fn set_host_and_port(&mut self, input: &str) -> ParseResult<()> {
  794. match self.url.scheme_data {
  795. RelativeSchemeData(RelativeSchemeData { ref mut host, ref mut port, .. }) => {
  796. let scheme_type = self.parser.get_scheme_type(self.url.scheme.as_slice());
  797. let (new_host, new_port, _) = try!(parser::parse_host(
  798. input, scheme_type, self.parser));
  799. *host = new_host;
  800. *port = new_port;
  801. Ok(())
  802. },
  803. NonRelativeSchemeData(_) => Err(CannotSetNonRelativeScheme("host/port"))
  804. }
  805. }
  806. /// `URLUtils.hostname` setter
  807. fn set_host(&mut self, input: &str) -> ParseResult<()> {
  808. match self.url.scheme_data {
  809. RelativeSchemeData(RelativeSchemeData { ref mut host, .. }) => {
  810. let (new_host, _) = try!(parser::parse_hostname(input, self.parser));
  811. *host = new_host;
  812. Ok(())
  813. },
  814. NonRelativeSchemeData(_) => Err(CannotSetNonRelativeScheme("host"))
  815. }
  816. }
  817. /// `URLUtils.port` setter
  818. fn set_port(&mut self, input: &str) -> ParseResult<()> {
  819. match self.url.scheme_data {
  820. RelativeSchemeData(RelativeSchemeData { ref mut port, .. }) => {
  821. let scheme_type = self.parser.get_scheme_type(self.url.scheme.as_slice());
  822. if scheme_type == FileLikeRelativeScheme {
  823. return Err(CannotSetFileScheme("port"));
  824. }
  825. let (new_port, _) = try!(parser::parse_port(input, scheme_type, self.parser));
  826. *port = new_port;
  827. Ok(())
  828. },
  829. NonRelativeSchemeData(_) => Err(CannotSetNonRelativeScheme("port"))
  830. }
  831. }
  832. /// `URLUtils.pathname` setter
  833. fn set_path(&mut self, input: &str) -> ParseResult<()> {
  834. match self.url.scheme_data {
  835. RelativeSchemeData(RelativeSchemeData { ref mut path, .. }) => {
  836. let scheme_type = self.parser.get_scheme_type(self.url.scheme.as_slice());
  837. let (new_path, _) = try!(parser::parse_path_start(
  838. input, parser::SetterContext, scheme_type, self.parser));
  839. *path = new_path;
  840. Ok(())
  841. },
  842. NonRelativeSchemeData(_) => Err(CannotSetNonRelativeScheme("path"))
  843. }
  844. }
  845. /// `URLUtils.search` setter
  846. fn set_query(&mut self, input: &str) -> ParseResult<()> {
  847. self.url.query = if input.is_empty() {
  848. None
  849. } else {
  850. let input = if input.starts_with("?") { input.slice_from(1) } else { input };
  851. let (new_query, _) = try!(parser::parse_query(
  852. input, parser::SetterContext, self.parser));
  853. Some(new_query)
  854. };
  855. Ok(())
  856. }
  857. /// `URLUtils.hash` setter
  858. fn set_fragment(&mut self, input: &str) -> ParseResult<()> {
  859. if self.url.scheme.as_slice() == "javascript" {
  860. return Err(CannotSetJavascriptScheme("fragment"))
  861. }
  862. self.url.fragment = if input.is_empty() {
  863. None
  864. } else {
  865. let input = if input.starts_with("#") { input.slice_from(1) } else { input };
  866. Some(try!(parser::parse_fragment(input, self.parser)))
  867. };
  868. Ok(())
  869. }
  870. }
  871. impl Host {
  872. /// Parse a host: either an IPv6 address in [] square brackets, or a domain.
  873. ///
  874. /// Returns `Err` for an empty host, an invalid IPv6 address,
  875. /// or a or invalid non-ASCII domain.
  876. ///
  877. /// FIXME: Add IDNA support for non-ASCII domains.
  878. pub fn parse(input: &str) -> ParseResult<Host> {
  879. if input.len() == 0 {
  880. Err(EmptyHost)
  881. } else if input.starts_with("[") {
  882. if input.ends_with("]") {
  883. Ipv6Address::parse(input.slice(1, input.len() - 1)).map(Ipv6)
  884. } else {
  885. Err(InvalidIpv6Address)
  886. }
  887. } else {
  888. let decoded = percent_decode(input.as_bytes());
  889. let domain = String::from_utf8_lossy(decoded.as_slice());
  890. // TODO: Remove this check and use IDNA "domain to ASCII"
  891. if !domain.as_slice().is_ascii() {
  892. Err(NonAsciiDomainsNotSupportedYet)
  893. } else if domain.as_slice().find(&[
  894. '\0', '\t', '\n', '\r', ' ', '#', '%', '/', ':', '?', '@', '[', '\\', ']'
  895. ]).is_some() {
  896. Err(InvalidDomainCharacter)
  897. } else {
  898. Ok(Domain(domain.into_string().into_ascii_lower()))
  899. }
  900. }
  901. }
  902. /// Serialize the host as a string.
  903. ///
  904. /// A domain a returned as-is, an IPv6 address between [] square brackets.
  905. pub fn serialize(&self) -> String {
  906. self.to_string()
  907. }
  908. }
  909. impl Show for Host {
  910. fn fmt(&self, formatter: &mut Formatter) -> Result<(), FormatError> {
  911. match *self {
  912. Domain(ref domain) => domain.fmt(formatter),
  913. Ipv6(ref address) => {
  914. try!(formatter.write(b"["));
  915. try!(address.fmt(formatter));
  916. formatter.write(b"]")
  917. }
  918. }
  919. }
  920. }
  921. impl Ipv6Address {
  922. /// Parse an IPv6 address, without the [] square brackets.
  923. pub fn parse(input: &str) -> ParseResult<Ipv6Address> {
  924. let input = input.as_bytes();
  925. let len = input.len();
  926. let mut is_ip_v4 = false;
  927. let mut pieces = [0, 0, 0, 0, 0, 0, 0, 0];
  928. let mut piece_pointer = 0u;
  929. let mut compress_pointer = None;
  930. let mut i = 0u;
  931. if input[0] == b':' {
  932. if input[1] != b':' {
  933. return Err(InvalidIpv6Address)
  934. }
  935. i = 2;
  936. piece_pointer = 1;
  937. compress_pointer = Some(1u);
  938. }
  939. while i < len {
  940. if piece_pointer == 8 {
  941. return Err(InvalidIpv6Address)
  942. }
  943. if input[i] == b':' {
  944. if compress_pointer.is_some() {
  945. return Err(InvalidIpv6Address)
  946. }
  947. i += 1;
  948. piece_pointer += 1;
  949. compress_pointer = Some(piece_pointer);
  950. continue
  951. }
  952. let start = i;
  953. let end = cmp::min(len, start + 4);
  954. let mut value = 0u16;
  955. while i < end {
  956. match from_hex(input[i]) {
  957. Some(digit) => {
  958. value = value * 0x10 + digit as u16;
  959. i += 1;
  960. },
  961. None => break
  962. }
  963. }
  964. if i < len {
  965. match input[i] {
  966. b'.' => {
  967. if i == start {
  968. return Err(InvalidIpv6Address)
  969. }
  970. i = start;
  971. is_ip_v4 = true;
  972. },
  973. b':' => {
  974. i += 1;
  975. if i == len {
  976. return Err(InvalidIpv6Address)
  977. }
  978. },
  979. _ => return Err(InvalidIpv6Address)
  980. }
  981. }
  982. if is_ip_v4 {
  983. break
  984. }
  985. pieces[piece_pointer] = value;
  986. piece_pointer += 1;
  987. }
  988. if is_ip_v4 {
  989. if piece_pointer > 6 {
  990. return Err(InvalidIpv6Address)
  991. }
  992. let mut dots_seen = 0u;
  993. while i < len {
  994. // FIXME: https://github.com/whatwg/url/commit/1c22aa119c354e0020117e02571cec53f7c01064
  995. let mut value = 0u16;
  996. while i < len {
  997. let digit = match input[i] {
  998. c @ b'0' .. b'9' => c - b'0',
  999. _ => break
  1000. };
  1001. value = value * 10 + digit as u16;
  1002. if value == 0 || value > 255 {
  1003. return Err(InvalidIpv6Address)
  1004. }
  1005. }
  1006. if dots_seen < 3 && !(i < len && input[i] == b'.') {
  1007. return Err(InvalidIpv6Address)
  1008. }
  1009. pieces[piece_pointer] = pieces[piece_pointer] * 0x100 + value;
  1010. if dots_seen == 0 || dots_seen == 2 {
  1011. piece_pointer += 1;
  1012. }
  1013. i += 1;
  1014. if dots_seen == 3 && i < len {
  1015. return Err(InvalidIpv6Address)
  1016. }
  1017. dots_seen += 1;
  1018. }
  1019. }
  1020. match compress_pointer {
  1021. Some(compress_pointer) => {
  1022. let mut swaps = piece_pointer - compress_pointer;
  1023. piece_pointer = 7;
  1024. while swaps > 0 {
  1025. pieces[piece_pointer] = pieces[compress_pointer + swaps - 1];
  1026. pieces[compress_pointer + swaps - 1] = 0;
  1027. swaps -= 1;
  1028. piece_pointer -= 1;
  1029. }
  1030. }
  1031. _ => if piece_pointer != 8 {
  1032. return Err(InvalidIpv6Address)
  1033. }
  1034. }
  1035. Ok(Ipv6Address { pieces: pieces })
  1036. }
  1037. /// Serialize the IPv6 address to a string.
  1038. pub fn serialize(&self) -> String {
  1039. self.to_string()
  1040. }
  1041. }
  1042. impl Show for Ipv6Address {
  1043. fn fmt(&self, formatter: &mut Formatter) -> Result<(), FormatError> {
  1044. let (compress_start, compress_end) = longest_zero_sequence(&self.pieces);
  1045. let mut i = 0;
  1046. while i < 8 {
  1047. if i == compress_start {
  1048. try!(formatter.write(b":"));
  1049. if i == 0 {
  1050. try!(formatter.write(b":"));
  1051. }
  1052. if compress_end < 8 {
  1053. i = compress_end;
  1054. } else {
  1055. break;
  1056. }
  1057. }
  1058. try!(write!(formatter, "{:x}", self.pieces[i as uint]));
  1059. if i < 7 {
  1060. try!(formatter.write(b":"));
  1061. }
  1062. i += 1;
  1063. }
  1064. Ok(())
  1065. }
  1066. }
  1067. fn longest_zero_sequence(pieces: &[u16, ..8]) -> (int, int) {
  1068. let mut longest = -1;
  1069. let mut longest_length = -1;
  1070. let mut start = -1;
  1071. macro_rules! finish_sequence(
  1072. ($end: expr) => {
  1073. if start >= 0 {
  1074. let length = $end - start;
  1075. if length > longest_length {
  1076. longest = start;
  1077. longest_length = length;
  1078. }
  1079. }
  1080. };
  1081. );
  1082. for i in range(0, 8) {
  1083. if pieces[i as uint] == 0 {
  1084. if start < 0 {
  1085. start = i;
  1086. }
  1087. } else {
  1088. finish_sequence!(i);
  1089. start = -1;
  1090. }
  1091. }
  1092. finish_sequence!(8);
  1093. (longest, longest + longest_length)
  1094. }
  1095. #[inline]
  1096. fn from_hex(byte: u8) -> Option<u8> {
  1097. match byte {
  1098. b'0' .. b'9' => Some(byte - b'0'), // 0..9
  1099. b'A' .. b'F' => Some(byte + 10 - b'A'), // A..F
  1100. b'a' .. b'f' => Some(byte + 10 - b'a'), // a..f
  1101. _ => None
  1102. }
  1103. }
  1104. /// Represents a set of characters / bytes that should be percent-encoded.
  1105. ///
  1106. /// See [encode sets specification](http://url.spec.whatwg.org/#simple-encode-set).
  1107. ///
  1108. /// Different characters need to be encoded in different parts of an URL.
  1109. /// For example, a literal `?` question mark in an URL’s path would indicate
  1110. /// the start of the query string.
  1111. /// A question mark meant to be part of the path therefore needs to be percent-encoded.
  1112. /// In the query string however, a question mark does not have any special meaning
  1113. /// and does not need to be percent-encoded.
  1114. ///
  1115. /// Since the implementation details of `EncodeSet` are private,
  1116. /// the set of available encode sets is not extensible beyond the ones
  1117. /// provided here.
  1118. /// If you need a different encode set,
  1119. /// please [file a bug](https://github.com/servo/rust-url/issues)
  1120. /// explaining the use case.
  1121. pub struct EncodeSet {
  1122. map: &'static [&'static str, ..256],
  1123. }
  1124. /// This encode set is used for fragment identifier and non-relative scheme data.
  1125. pub static SIMPLE_ENCODE_SET: EncodeSet = EncodeSet { map: &encode_sets::SIMPLE };
  1126. /// This encode set is used in the URL parser for query strings.
  1127. pub static QUERY_ENCODE_SET: EncodeSet = EncodeSet { map: &encode_sets::QUERY };
  1128. /// This encode set is used for path components.
  1129. pub static DEFAULT_ENCODE_SET: EncodeSet = EncodeSet { map: &encode_sets::DEFAULT };
  1130. /// This encode set is used in the URL parser for usernames and passwords.
  1131. pub static USERINFO_ENCODE_SET: EncodeSet = EncodeSet { map: &encode_sets::USERINFO };
  1132. /// This encode set should be used when setting the password field of a parsed URL.
  1133. pub static PASSWORD_ENCODE_SET: EncodeSet = EncodeSet { map: &encode_sets::PASSWORD };
  1134. /// This encode set should be used when setting the username field of a parsed URL.
  1135. pub static USERNAME_ENCODE_SET: EncodeSet = EncodeSet { map: &encode_sets::USERNAME };
  1136. /// This encode set is used in `application/x-www-form-urlencoded` serialization.
  1137. pub static FORM_URLENCODED_ENCODE_SET: EncodeSet = EncodeSet {
  1138. map: &encode_sets::FORM_URLENCODED,
  1139. };
  1140. /// Percent-encode the given bytes, and push the result to `output`.
  1141. ///
  1142. /// The pushed strings are within the ASCII range.
  1143. #[inline]
  1144. pub fn percent_encode_to(input: &[u8], encode_set: EncodeSet, output: &mut String) {
  1145. for &byte in input.iter() {
  1146. output.push_str(encode_set.map[byte as uint])
  1147. }
  1148. }
  1149. /// Percent-encode the given bytes.
  1150. ///
  1151. /// The returned string is within the ASCII range.
  1152. #[inline]
  1153. pub fn percent_encode(input: &[u8], encode_set: EncodeSet) -> String {
  1154. let mut output = String::new();
  1155. percent_encode_to(input, encode_set, &mut output);
  1156. output
  1157. }
  1158. /// Percent-encode the UTF-8 encoding of the given string, and push the result to `output`.
  1159. ///
  1160. /// The pushed strings are within the ASCII range.
  1161. #[inline]
  1162. pub fn utf8_percent_encode_to(input: &str, encode_set: EncodeSet, output: &mut String) {
  1163. percent_encode_to(input.as_bytes(), encode_set, output)
  1164. }
  1165. /// Percent-encode the UTF-8 encoding of the given string.
  1166. ///
  1167. /// The returned string is within the ASCII range.
  1168. #[inline]
  1169. pub fn utf8_percent_encode(input: &str, encode_set: EncodeSet) -> String {
  1170. let mut output = String::new();
  1171. utf8_percent_encode_to(input, encode_set, &mut output);
  1172. output
  1173. }
  1174. /// Percent-decode the given bytes, and push the result to `output`.
  1175. pub fn percent_decode_to(input: &[u8], output: &mut Vec<u8>) {
  1176. let mut i = 0u;
  1177. while i < input.len() {
  1178. let c = input[i];
  1179. if c == b'%' && i + 2 < input.len() {
  1180. match (from_hex(input[i + 1]), from_hex(input[i + 2])) {
  1181. (Some(h), Some(l)) => {
  1182. output.push(h * 0x10 + l);
  1183. i += 3;
  1184. continue
  1185. },
  1186. _ => (),
  1187. }
  1188. }
  1189. output.push(c);
  1190. i += 1;
  1191. }
  1192. }
  1193. /// Percent-decode the given bytes.
  1194. #[inline]
  1195. pub fn percent_decode(input: &[u8]) -> Vec<u8> {
  1196. let mut output = Vec::new();
  1197. percent_decode_to(input, &mut output);
  1198. output
  1199. }
  1200. /// Percent-decode the given bytes, and decode the result as UTF-8.
  1201. ///
  1202. /// This is “lossy”: invalid UTF-8 percent-encoded byte sequences
  1203. /// will be replaced � U+FFFD, the replacement character.
  1204. #[inline]
  1205. pub fn lossy_utf8_percent_decode(input: &[u8]) -> String {
  1206. String::from_utf8_lossy(percent_decode(input).as_slice()).into_string()
  1207. }
  1208. trait ToUrlPath {
  1209. fn to_url_path(&self) -> Result<Vec<String>, ()>;
  1210. }
  1211. impl ToUrlPath for path::posix::Path {
  1212. fn to_url_path(&self) -> Result<Vec<String>, ()> {
  1213. if !self.is_absolute() {
  1214. return Err(())
  1215. }
  1216. Ok(self.components().map(|c| percent_encode(c, DEFAULT_ENCODE_SET)).collect())
  1217. }
  1218. }
  1219. impl ToUrlPath for path::windows::Path {
  1220. fn to_url_path(&self) -> Result<Vec<String>, ()> {
  1221. if !self.is_absolute() {
  1222. return Err(())
  1223. }
  1224. if path::windows::prefix(self) != Some(path::windows::DiskPrefix) {
  1225. // FIXME: do something with UNC and other prefixes?
  1226. return Err(())
  1227. }
  1228. // Start with the prefix, e.g. "C:"
  1229. let mut path = vec![self.as_str().unwrap().slice_to(2).to_string()];
  1230. // self.components() does not include the prefix
  1231. for component in self.components() {
  1232. path.push(percent_encode(component, DEFAULT_ENCODE_SET));
  1233. }
  1234. Ok(path)
  1235. }
  1236. }
  1237. trait FromUrlPath {
  1238. fn from_url_path(path: &[String]) -> Result<Self, ()>;
  1239. }
  1240. impl FromUrlPath for path::posix::Path {
  1241. fn from_url_path(path: &[String]) -> Result<path::posix::Path, ()> {
  1242. if path.is_empty() {
  1243. return Ok(path::posix::Path::new("/"))
  1244. }
  1245. let mut bytes = Vec::new();
  1246. for path_part in path.iter() {
  1247. bytes.push(b'/');
  1248. percent_decode_to(path_part.as_bytes(), &mut bytes);
  1249. }
  1250. match path::posix::Path::new_opt(bytes) {
  1251. None => Err(()), // Path contains a NUL byte
  1252. Some(path) => {
  1253. debug_assert!(path.is_absolute(),
  1254. "to_file_path() failed to produce an absolute Path")
  1255. Ok(path)
  1256. }
  1257. }
  1258. }
  1259. }
  1260. impl FromUrlPath for path::windows::Path {
  1261. fn from_url_path(path: &[String]) -> Result<path::windows::Path, ()> {
  1262. if path.is_empty() {
  1263. return Err(())
  1264. }
  1265. let prefix = path[0].as_slice();
  1266. if prefix.len() != 2 || !parser::starts_with_ascii_alpha(prefix)
  1267. || prefix.char_at(1) != ':' {
  1268. return Err(())
  1269. }
  1270. let mut bytes = prefix.as_bytes().to_vec();
  1271. for path_part in path.slice_from(1).iter() {
  1272. bytes.push(b'\\');
  1273. percent_decode_to(path_part.as_bytes(), &mut bytes);
  1274. }
  1275. match path::windows::Path::new_opt(bytes) {
  1276. None => Err(()), // Path contains a NUL byte or invalid UTF-8
  1277. Some(path) => {
  1278. debug_assert!(path.is_absolute(),
  1279. "to_file_path() failed to produce an absolute Path")
  1280. debug_assert!(path::windows::prefix(&path) != Some(path::windows::DiskPrefix),
  1281. "to_file_path() failed to produce a Path with a disk prefix")
  1282. Ok(path)
  1283. }
  1284. }
  1285. }
  1286. }