parser.rs 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682
  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. use std::ascii::StrAsciiExt;
  9. use encoding;
  10. use encoding::EncodingRef;
  11. use encoding::all::UTF_8;
  12. use super::{
  13. ParseResult, ErrorHandler, Url, RelativeSchemeData, OtherSchemeData,
  14. SchemeRelativeUrl, Host, Domain,
  15. utf8_percent_encode, percent_encode_byte,
  16. SimpleEncodeSet, DefaultEncodeSet, UserInfoEncodeSet};
  17. macro_rules! is_match(
  18. ($value:expr, $($pattern:pat)|+) => (
  19. match $value { $($pattern)|+ => true, _ => false }
  20. );
  21. )
  22. pub fn parse_url(input: &str, base_url: Option<&Url>, parse_error: ErrorHandler)
  23. -> ParseResult<Url> {
  24. let input = input.trim_chars(&[' ', '\t', '\n', '\r', '\x0C']);
  25. match parse_scheme(input, /* in_setter = */ false) {
  26. Some((scheme, remaining)) => {
  27. if scheme.as_slice() == "file" {
  28. // Relative state?
  29. match base_url {
  30. Some(base) if scheme == base.scheme => {
  31. try!(parse_error("Relative URL with a scheme"));
  32. parse_relative_url(scheme, remaining, base, parse_error)
  33. },
  34. _ => parse_relative_url(scheme, remaining, &Url {
  35. scheme: String::new(), query: None, fragment: None,
  36. scheme_data: RelativeSchemeData(SchemeRelativeUrl {
  37. username: String::new(), password: None, host: Domain(String::new()),
  38. port: String::new(), path: Vec::new()
  39. })
  40. }, parse_error),
  41. }
  42. } else if is_relative_scheme(scheme.as_slice()) {
  43. match base_url {
  44. Some(base) if scheme == base.scheme => {
  45. // Relative or authority state
  46. if remaining.starts_with("//") {
  47. parse_absolute_url(scheme, remaining, parse_error)
  48. } else {
  49. try!(parse_error("Relative URL with a scheme"));
  50. parse_relative_url(scheme, remaining, base, parse_error)
  51. }
  52. },
  53. _ => parse_absolute_url(scheme, remaining, parse_error),
  54. }
  55. } else {
  56. // Scheme data state
  57. let (scheme_data, remaining) = try!(parse_scheme_data(remaining, parse_error));
  58. let (query, fragment) = try!(parse_query_and_fragment(remaining, parse_error));
  59. Ok(Url { scheme: scheme, scheme_data: OtherSchemeData(scheme_data),
  60. query: query, fragment: fragment })
  61. }
  62. },
  63. // No-scheme state
  64. None => match base_url {
  65. None => Err("Relative URL without a base"),
  66. Some(base) => parse_relative_url(base.scheme.clone(), input, base, parse_error)
  67. }
  68. }
  69. }
  70. pub fn parse_scheme<'a>(input: &'a str, in_setter: bool) -> Option<(String, &'a str)> {
  71. if input.is_empty() || !starts_with_ascii_alpha(input) {
  72. return None
  73. }
  74. for (i, c) in input.char_indices() {
  75. match c {
  76. 'a'..'z' | 'A'..'Z' | '0'..'9' | '+' | '-' | '.' => (),
  77. ':' => return Some((
  78. input.slice_to(i).to_ascii_lower(),
  79. input.slice_from(i + 1),
  80. )),
  81. _ => return None,
  82. }
  83. }
  84. if in_setter {
  85. Some((input.to_ascii_lower(), ""))
  86. } else {
  87. None
  88. }
  89. }
  90. fn parse_absolute_url<'a>(scheme: String, input: &'a str, parse_error: ErrorHandler)
  91. -> ParseResult<Url> {
  92. // Authority first slash state
  93. let remaining = try!(skip_slashes(input, parse_error));
  94. // Authority state
  95. let (username, password, remaining) = try!(parse_userinfo(remaining, parse_error));
  96. // Host state
  97. let (host, port, remaining) = try!(
  98. parse_hostname(remaining, scheme.as_slice(), parse_error, /* skip_port = */ false));
  99. let (path, remaining) = try!(parse_path_start(
  100. remaining,
  101. /* full_url= */ true,
  102. /* in_file_scheme= */ false,
  103. parse_error));
  104. let scheme_data = RelativeSchemeData(SchemeRelativeUrl {
  105. username: username, password: password, host: host, port: port, path: path });
  106. let (query, fragment) = try!(parse_query_and_fragment(remaining, parse_error));
  107. Ok(Url { scheme: scheme, scheme_data: scheme_data, query: query, fragment: fragment })
  108. }
  109. fn parse_relative_url<'a>(scheme: String, input: &'a str, base: &Url, parse_error: ErrorHandler)
  110. -> ParseResult<Url> {
  111. match base.scheme_data {
  112. OtherSchemeData(_) => Err("Relative URL with a non-relative-scheme base"),
  113. RelativeSchemeData(ref base_scheme_data) => if input.is_empty() {
  114. Ok(Url { scheme: scheme, scheme_data: base.scheme_data.clone(),
  115. query: base.query.clone(), fragment: None })
  116. } else {
  117. let in_file_scheme = scheme.as_slice() == "file";
  118. match input.char_at(0) {
  119. '/' | '\\' => {
  120. // Relative slash state
  121. if input.len() > 1 && is_match!(input.char_at(1), '/' | '\\') {
  122. if in_file_scheme {
  123. let remaining = input.slice_from(2);
  124. let (host, remaining) = if remaining.len() >= 2
  125. && starts_with_ascii_alpha(remaining)
  126. && is_match!(remaining.char_at(1), ':' | '|')
  127. && (remaining.len() == 2
  128. || is_match!(remaining.char_at(2),
  129. '/' | '\\' | '?' | '#'))
  130. {
  131. // Windows drive letter quirk
  132. (Domain(String::new()), remaining)
  133. } else {
  134. // File host state
  135. try!(parse_file_host(remaining, parse_error))
  136. };
  137. let (path, remaining) = try!(parse_path_start(
  138. remaining, /* full_url= */ true,
  139. in_file_scheme, parse_error));
  140. let scheme_data = RelativeSchemeData(SchemeRelativeUrl {
  141. username: String::new(), password: None,
  142. host: host, port: String::new(), path: path
  143. });
  144. let (query, fragment) = try!(parse_query_and_fragment(
  145. remaining, parse_error));
  146. Ok(Url { scheme: scheme, scheme_data: scheme_data,
  147. query: query, fragment: fragment })
  148. } else {
  149. parse_absolute_url(scheme, input, parse_error)
  150. }
  151. } else {
  152. // Relative path state
  153. let (path, remaining) = try!(parse_path(
  154. Vec::new(), input.slice_from(1), /* full_url= */ true,
  155. in_file_scheme, parse_error));
  156. let scheme_data = RelativeSchemeData(if in_file_scheme {
  157. SchemeRelativeUrl {
  158. username: String::new(), password: None, host:
  159. Domain(String::new()), port: String::new(), path: path
  160. }
  161. } else {
  162. SchemeRelativeUrl {
  163. username: base_scheme_data.username.clone(),
  164. password: base_scheme_data.password.clone(),
  165. host: base_scheme_data.host.clone(),
  166. port: base_scheme_data.port.clone(),
  167. path: path
  168. }
  169. });
  170. let (query, fragment) = try!(
  171. parse_query_and_fragment(remaining, parse_error));
  172. Ok(Url { scheme: scheme, scheme_data: scheme_data,
  173. query: query, fragment: fragment })
  174. }
  175. },
  176. '?' => {
  177. let (query, fragment) = try!(parse_query_and_fragment(input, parse_error));
  178. Ok(Url { scheme: scheme, scheme_data: base.scheme_data.clone(),
  179. query: query, fragment: fragment })
  180. },
  181. '#' => {
  182. Ok(Url { scheme: scheme, scheme_data: base.scheme_data.clone(),
  183. query: base.query.clone(),
  184. fragment: Some(try!(
  185. parse_fragment(input.slice_from(1), parse_error))) })
  186. }
  187. _ => {
  188. let (scheme_data, remaining) = if in_file_scheme
  189. && input.len() >= 2
  190. && starts_with_ascii_alpha(input)
  191. && is_match!(input.char_at(1), ':' | '|')
  192. && (input.len() == 2
  193. || is_match!(input.char_at(2), '/' | '\\' | '?' | '#'))
  194. {
  195. // Windows drive letter quirk
  196. let (path, remaining) = try!(parse_path(
  197. Vec::new(), input, /* full_url= */ true,
  198. in_file_scheme, parse_error));
  199. (RelativeSchemeData(SchemeRelativeUrl {
  200. username: String::new(), password: None,
  201. host: Domain(String::new()),
  202. port: String::new(),
  203. path: path
  204. }), remaining)
  205. } else {
  206. let base_path = base_scheme_data.path.as_slice();
  207. let initial_path = Vec::from_slice(
  208. base_path.slice_to(base_path.len() - 1));
  209. // Relative path state
  210. let (path, remaining) = try!(parse_path(
  211. initial_path, input, /* full_url= */ true,
  212. in_file_scheme, parse_error));
  213. (RelativeSchemeData(SchemeRelativeUrl {
  214. username: base_scheme_data.username.clone(),
  215. password: base_scheme_data.password.clone(),
  216. host: base_scheme_data.host.clone(),
  217. port: base_scheme_data.port.clone(),
  218. path: path
  219. }), remaining)
  220. };
  221. let (query, fragment) = try!(parse_query_and_fragment(remaining, parse_error));
  222. Ok(Url { scheme: scheme, scheme_data: scheme_data,
  223. query: query, fragment: fragment })
  224. }
  225. }
  226. }
  227. }
  228. }
  229. fn skip_slashes<'a>(input: &'a str, parse_error: ErrorHandler) -> ParseResult<&'a str> {
  230. let first_non_slash = input.find(|c| !is_match!(c, '/' | '\\')).unwrap_or(input.len());
  231. if input.slice_to(first_non_slash) != "//" {
  232. try!(parse_error("Expected two slashes"));
  233. }
  234. Ok(input.slice_from(first_non_slash))
  235. }
  236. fn parse_userinfo<'a>(input: &'a str, parse_error: ErrorHandler)
  237. -> ParseResult<(String, Option<String>, &'a str)> {
  238. let mut last_at = None;
  239. for (i, c) in input.char_indices() {
  240. match c {
  241. '@' => last_at = Some(i),
  242. '/' | '\\' | '?' | '#' => break,
  243. _ => (),
  244. }
  245. }
  246. let (input, remaining) = match last_at {
  247. Some(at) => (input.slice_to(at), input.slice_from(at + 1)),
  248. None => return Ok((String::new(), None, input)),
  249. };
  250. let mut username = String::new();
  251. let mut password = None;
  252. for (i, c) in input.char_indices() {
  253. match c {
  254. ':' => {
  255. password = Some(try!(parse_password(input.slice_from(i + 1), parse_error)));
  256. break
  257. },
  258. '\t' | '\n' | '\r' => try!(parse_error("Invalid character")),
  259. _ => {
  260. if c == '%' {
  261. if !starts_with_2_hex(input.slice_from(i + 1)) {
  262. try!(parse_error("Invalid percent-encoded sequence"));
  263. }
  264. } else if !is_url_code_point(c) {
  265. try!(parse_error("Non-URL code point"));
  266. }
  267. utf8_percent_encode(input.slice(i, i + c.len_utf8_bytes()),
  268. UserInfoEncodeSet, &mut username);
  269. }
  270. }
  271. }
  272. Ok((username, password, remaining))
  273. }
  274. fn parse_password(input: &str, parse_error: ErrorHandler) -> ParseResult<String> {
  275. let mut password = String::new();
  276. for (i, c) in input.char_indices() {
  277. match c {
  278. '\t' | '\n' | '\r' => try!(parse_error("Invalid character")),
  279. _ => {
  280. if c == '%' {
  281. if !starts_with_2_hex(input.slice_from(i + 1)) {
  282. try!(parse_error("Invalid percent-encoded sequence"));
  283. }
  284. } else if !is_url_code_point(c) {
  285. try!(parse_error("Non-URL code point"));
  286. }
  287. utf8_percent_encode(input.slice(i, i + c.len_utf8_bytes()),
  288. UserInfoEncodeSet, &mut password);
  289. }
  290. }
  291. }
  292. Ok(password)
  293. }
  294. pub fn parse_hostname<'a>(input: &'a str, scheme: &str, parse_error: ErrorHandler,
  295. skip_port: bool)
  296. -> ParseResult<(Host, String, &'a str)> {
  297. let mut inside_square_brackets = false;
  298. let mut host_input = String::new();
  299. let mut end = input.len();
  300. for (i, c) in input.char_indices() {
  301. match c {
  302. ':' if !inside_square_brackets => {
  303. let host = try!(Host::parse(host_input.as_slice()));
  304. return Ok(if skip_port {
  305. (host, String::new(), "")
  306. } else {
  307. let (port, remaining) = try!(
  308. parse_port(input.slice_from(i + 1), scheme, parse_error));
  309. (host, port, remaining)
  310. })
  311. },
  312. '/' | '\\' | '?' | '#' => {
  313. end = i;
  314. break
  315. },
  316. '\t' | '\n' | '\r' => try!(parse_error("Invalid character")),
  317. c => {
  318. match c {
  319. '[' => inside_square_brackets = true,
  320. ']' => inside_square_brackets = false,
  321. _ => (),
  322. }
  323. host_input.push_char(c)
  324. }
  325. }
  326. }
  327. let host = try!(Host::parse(host_input.as_slice()));
  328. Ok((host, String::new(), input.slice_from(end)))
  329. }
  330. pub fn parse_port<'a>(input: &'a str, scheme: &str, parse_error: ErrorHandler)
  331. -> ParseResult<(String, &'a str)> {
  332. let mut port = String::new();
  333. let mut has_initial_zero = false;
  334. let mut end = input.len();
  335. for (i, c) in input.char_indices() {
  336. match c {
  337. '1'..'9' => port.push_char(c),
  338. '0' => {
  339. if port.is_empty() {
  340. has_initial_zero = true
  341. } else {
  342. port.push_char(c)
  343. }
  344. },
  345. '/' | '\\' | '?' | '#' => {
  346. end = i;
  347. break
  348. },
  349. '\t' | '\n' | '\r' => try!(parse_error("Invalid character")),
  350. _ => return Err("Invalid port number")
  351. }
  352. }
  353. if port.is_empty() && has_initial_zero {
  354. port.push_str("0")
  355. }
  356. match (scheme, port.as_slice()) {
  357. ("ftp", "21") | ("gopher", "70") | ("http", "80") |
  358. ("https", "443") | ("ws", "80") | ("wss", "443")
  359. => port.truncate(0),
  360. _ => (),
  361. }
  362. return Ok((port, input.slice_from(end)))
  363. }
  364. fn parse_file_host<'a>(input: &'a str, parse_error: ErrorHandler) -> ParseResult<(Host, &'a str)> {
  365. let mut host_input = String::new();
  366. let mut end = input.len();
  367. for (i, c) in input.char_indices() {
  368. match c {
  369. '/' | '\\' | '?' | '#' => {
  370. end = i;
  371. break
  372. },
  373. '\t' | '\n' | '\r' => try!(parse_error("Invalid character")),
  374. _ => host_input.push_char(c)
  375. }
  376. }
  377. let host = if host_input.is_empty() {
  378. Domain(String::new())
  379. } else {
  380. try!(Host::parse(host_input.as_slice()))
  381. };
  382. Ok((host, input.slice_from(end)))
  383. }
  384. pub fn parse_path_start<'a>(input: &'a str, full_url: bool, in_file_scheme: bool,
  385. parse_error: ErrorHandler)
  386. -> ParseResult<(Vec<String>, &'a str)> {
  387. let mut i = 0;
  388. // Relative path start state
  389. if !input.is_empty() {
  390. match input.char_at(0) {
  391. '/' => i = 1,
  392. '\\' => {
  393. try!(parse_error("Backslash"));
  394. i = 1;
  395. },
  396. _ => ()
  397. }
  398. }
  399. parse_path(Vec::new(), input.slice_from(i), full_url, in_file_scheme, parse_error)
  400. }
  401. fn parse_path<'a>(base_path: Vec<String>, input: &'a str, full_url: bool, in_file_scheme: bool,
  402. parse_error: ErrorHandler)
  403. -> ParseResult<(Vec<String>, &'a str)> {
  404. // Relative path state
  405. let mut path = base_path;
  406. let mut iter = input.char_indices();
  407. let mut end;
  408. loop {
  409. let mut path_part = String::new();
  410. let mut ends_with_slash = false;
  411. end = input.len();
  412. for (i, c) in iter {
  413. match c {
  414. '/' => {
  415. ends_with_slash = true;
  416. end = i;
  417. break
  418. },
  419. '\\' => {
  420. try!(parse_error("Backslash"));
  421. ends_with_slash = true;
  422. end = i;
  423. break
  424. },
  425. '?' | '#' if full_url => {
  426. end = i;
  427. break
  428. },
  429. '\t' | '\n' | '\r' => try!(parse_error("Invalid character")),
  430. _ => {
  431. if c == '%' {
  432. if !starts_with_2_hex(input.slice_from(i + 1)) {
  433. try!(parse_error("Invalid percent-encoded sequence"));
  434. }
  435. } else if !is_url_code_point(c) {
  436. try!(parse_error("Non-URL code point"));
  437. }
  438. utf8_percent_encode(input.slice(i, i + c.len_utf8_bytes()),
  439. DefaultEncodeSet, &mut path_part);
  440. }
  441. }
  442. }
  443. match path_part.as_slice() {
  444. ".." | ".%2e" | ".%2E" | "%2e." | "%2E." |
  445. "%2e%2e" | "%2E%2e" | "%2e%2E" | "%2E%2E" => {
  446. path.pop();
  447. if !ends_with_slash {
  448. path.push(String::new());
  449. }
  450. },
  451. "." | "%2e" | "%2E" => {
  452. if !ends_with_slash {
  453. path.push(String::new());
  454. }
  455. },
  456. _ => {
  457. if in_file_scheme
  458. && path.is_empty()
  459. && path_part.len() == 2
  460. && starts_with_ascii_alpha(path_part.as_slice())
  461. && path_part.as_slice().char_at(1) == '|' {
  462. // Windows drive letter quirk
  463. unsafe {
  464. *path_part.as_mut_vec().get_mut(1) = b':'
  465. }
  466. }
  467. path.push(path_part)
  468. }
  469. }
  470. if !ends_with_slash {
  471. break
  472. }
  473. }
  474. Ok((path, input.slice_from(end)))
  475. }
  476. fn parse_scheme_data<'a>(input: &'a str, parse_error: ErrorHandler)
  477. -> ParseResult<(String, &'a str)> {
  478. let mut scheme_data = String::new();
  479. let mut end = input.len();
  480. for (i, c) in input.char_indices() {
  481. match c {
  482. '?' | '#' => {
  483. end = i;
  484. break
  485. },
  486. '\t' | '\n' | '\r' => try!(parse_error("Invalid character")),
  487. _ => {
  488. if c == '%' {
  489. if !starts_with_2_hex(input.slice_from(i + 1)) {
  490. try!(parse_error("Invalid percent-encoded sequence"));
  491. }
  492. } else if !is_url_code_point(c) {
  493. try!(parse_error("Non-URL code point"));
  494. }
  495. utf8_percent_encode(input.slice(i, i + c.len_utf8_bytes()),
  496. SimpleEncodeSet, &mut scheme_data);
  497. }
  498. }
  499. }
  500. Ok((scheme_data, input.slice_from(end)))
  501. }
  502. fn parse_query_and_fragment(input: &str, parse_error: ErrorHandler)
  503. -> ParseResult<(Option<String>, Option<String>)> {
  504. Ok(if input.is_empty() {
  505. (None, None)
  506. } else {
  507. match input.char_at(0) {
  508. '#' => (None, Some(try!(parse_fragment(input.slice_from(1), parse_error)))),
  509. '?' => {
  510. let (query, remaining) = try!(parse_query(
  511. input.slice_from(1),
  512. UTF_8 as EncodingRef, // TODO
  513. /* full_url = */ true,
  514. parse_error));
  515. (Some(query), match remaining {
  516. Some(remaining) => Some(try!(parse_fragment(remaining, parse_error))),
  517. None => None
  518. })
  519. },
  520. _ => fail!("Programming error")
  521. }
  522. })
  523. }
  524. pub fn parse_query<'a>(input: &'a str, encoding_override: EncodingRef, full_url: bool,
  525. parse_error: ErrorHandler)
  526. -> ParseResult<(String, Option<&'a str>)> {
  527. let mut query = String::new();
  528. let mut remaining = None;
  529. for (i, c) in input.char_indices() {
  530. match c {
  531. '#' if full_url => {
  532. remaining = Some(input.slice_from(i + 1));
  533. break
  534. },
  535. '\t' | '\n' | '\r' => try!(parse_error("Invalid character")),
  536. _ => {
  537. if c == '%' {
  538. if !starts_with_2_hex(input.slice_from(i + 1)) {
  539. try!(parse_error("Invalid percent-encoded sequence"));
  540. }
  541. } else if !is_url_code_point(c) {
  542. try!(parse_error("Non-URL code point"));
  543. }
  544. query.push_char(c);
  545. }
  546. }
  547. }
  548. let query_bytes = encoding_override.encode(query.as_slice(), encoding::EncodeReplace).unwrap();
  549. let mut query_encoded = String::new();
  550. for &byte in query_bytes.iter() {
  551. match byte {
  552. b'\x00'.. b' ' | b'"' | b'#' | b'<' | b'>' | b'`' | b'~'..b'\xFF'
  553. => percent_encode_byte(byte, &mut query_encoded),
  554. _
  555. => unsafe { query_encoded.push_byte(byte) }
  556. }
  557. }
  558. Ok((query_encoded, remaining))
  559. }
  560. pub fn parse_fragment<'a>(input: &'a str, parse_error: ErrorHandler) -> ParseResult<String> {
  561. let mut fragment = String::new();
  562. for (i, c) in input.char_indices() {
  563. match c {
  564. '\t' | '\n' | '\r' => try!(parse_error("Invalid character")),
  565. _ => {
  566. if c == '%' {
  567. if !starts_with_2_hex(input.slice_from(i + 1)) {
  568. try!(parse_error("Invalid percent-encoded sequence"));
  569. }
  570. } else if !is_url_code_point(c) {
  571. try!(parse_error("Non-URL code point"));
  572. }
  573. utf8_percent_encode(input.slice(i, i + c.len_utf8_bytes()),
  574. SimpleEncodeSet, &mut fragment);
  575. }
  576. }
  577. }
  578. Ok(fragment)
  579. }
  580. #[inline]
  581. fn starts_with_ascii_alpha(string: &str) -> bool {
  582. match string.char_at(0) {
  583. 'a'..'z' | 'A'..'Z' => true,
  584. _ => false,
  585. }
  586. }
  587. #[inline]
  588. fn is_ascii_hex_digit(byte: u8) -> bool {
  589. match byte {
  590. b'a'..b'f' | b'A'..b'F' | b'0'..b'9' => true,
  591. _ => false,
  592. }
  593. }
  594. #[inline]
  595. fn starts_with_2_hex(input: &str) -> bool {
  596. input.len() >= 2
  597. && is_ascii_hex_digit(input.as_bytes()[0])
  598. && is_ascii_hex_digit(input.as_bytes()[1])
  599. }
  600. #[inline]
  601. fn is_url_code_point(c: char) -> bool {
  602. match c {
  603. 'a'..'z' |
  604. 'A'..'Z' |
  605. '0'..'9' |
  606. '!' | '$' | '&' | '\'' | '(' | ')' | '*' | '+' | ',' | '-' |
  607. '.' | '/' | ':' | ';' | '=' | '?' | '@' | '_' | '~' |
  608. '\u00A0'..'\uD7FF' | '\uE000'..'\uFDCF' | '\uFDF0'..'\uFFFD' |
  609. '\U00010000'..'\U0001FFFD' | '\U00020000'..'\U0002FFFD' |
  610. '\U00030000'..'\U0003FFFD' | '\U00040000'..'\U0004FFFD' |
  611. '\U00050000'..'\U0005FFFD' | '\U00060000'..'\U0006FFFD' |
  612. '\U00070000'..'\U0007FFFD' | '\U00080000'..'\U0008FFFD' |
  613. '\U00090000'..'\U0009FFFD' | '\U000A0000'..'\U000AFFFD' |
  614. '\U000B0000'..'\U000BFFFD' | '\U000C0000'..'\U000CFFFD' |
  615. '\U000D0000'..'\U000DFFFD' | '\U000E1000'..'\U000EFFFD' |
  616. '\U000F0000'..'\U000FFFFD' | '\U00100000'..'\U0010FFFD' => true,
  617. _ => false
  618. }
  619. }
  620. // Non URL code points:
  621. // U+0000 to U+0020 (space)
  622. // " # % < > [ \ ] ^ ` { | }
  623. // U+007F to U+009F
  624. // surrogates
  625. // U+FDD0 to U+FDEF
  626. // Last two of each plane: U+__FFFE to U+__FFFF for __ in 00 to 10 hex
  627. fn is_relative_scheme(scheme: &str) -> bool {
  628. is_match!(scheme, "ftp" | "file" | "gopher" | "http" | "https" | "ws" | "wss")
  629. }