parser.rs 25 KB

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