parser.rs 24 KB

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