parser.rs 24 KB

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