parser.rs 24 KB

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