parser.rs 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688
  1. use std::ascii::{Ascii, StrAsciiExt};
  2. use std::str;
  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_str_ascii() == "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<~[Ascii]>, &'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(ascii_nocheck!(input.slice_to(i)).to_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: ~[Ascii], 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: ~[Ascii], 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_str_ascii() == "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: &[Ascii]) -> ParseResult<(Host, ~[Ascii], &'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: &[Ascii]) -> ParseResult<(~[Ascii], &'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' => port.push(ascii_nocheck!(input[i])),
  341. '0' => {
  342. if port.is_empty() {
  343. has_initial_zero = true
  344. } else {
  345. port.push(ascii_nocheck!(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(ascii_nocheck!('0'))
  356. }
  357. match (scheme.as_str_ascii(), port.as_str_ascii()) {
  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. -> (~[~[Ascii]], &'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: ~[~[Ascii]], input: &'a str, full_url: bool, in_file_scheme: bool)
  403. -> (~[~[Ascii]], &'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.as_str_ascii().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].to_byte())
  460. && path_part[1].to_char() == '|' {
  461. // Windows drive letter quirk
  462. path_part[1] = ascii_nocheck!(':');
  463. }
  464. path.push(path_part)
  465. }
  466. }
  467. if !ends_with_slash {
  468. break
  469. }
  470. }
  471. (path, input.slice_from(i))
  472. }
  473. fn parse_scheme_data<'a>(input: &'a str) -> (~[Ascii], &'a str) {
  474. let mut scheme_data = ~[];
  475. let mut i = 0;
  476. while i < input.len() {
  477. match input[i] as char {
  478. '?' | '#' => break,
  479. '\t' | '\n' | '\r' => {
  480. parse_error("Invalid character");
  481. i += 1;
  482. },
  483. _ => {
  484. let range = input.char_range_at(i);
  485. if range.ch == '%' {
  486. if !starts_with_2_hex(input.slice_from(i + 1)) {
  487. parse_error("Invalid percent-encoded sequence")
  488. }
  489. } else if !is_url_code_point(range.ch) {
  490. parse_error("Non-URL code point")
  491. }
  492. utf8_percent_encode(input.slice(i, range.next), SimpleEncodeSet, &mut scheme_data);
  493. i = range.next;
  494. }
  495. }
  496. }
  497. (scheme_data, input.slice_from(i))
  498. }
  499. fn parse_query_and_fragment(input: &str) -> (Option<~[Ascii]>, Option<~[Ascii]>) {
  500. if input.is_empty() {
  501. (None, None)
  502. } else {
  503. match input[0] as char {
  504. '#' => (None, Some(parse_fragment(input.slice_from(1)))),
  505. '?' => {
  506. let (query, remaining) = parse_query(
  507. input.slice_from(1),
  508. UTF_8 as EncodingRef,
  509. /* full_url = */ true);
  510. (Some(query), remaining.map(parse_fragment))
  511. },
  512. _ => fail!("Programming error")
  513. }
  514. }
  515. }
  516. fn parse_query<'a>(input: &'a str, encoding_override: EncodingRef, full_url: bool)
  517. -> (~[Ascii], Option<&'a str>) {
  518. let mut query = ~"";
  519. let mut i = 0;
  520. let mut remaining = None;
  521. while i < input.len() {
  522. match input[i] as char {
  523. '#' if full_url => {
  524. remaining = Some(input.slice_from(i + 1));
  525. break
  526. },
  527. '\t' | '\n' | '\r' => {
  528. parse_error("Invalid character");
  529. i += 1;
  530. },
  531. _ => {
  532. let range = input.char_range_at(i);
  533. if range.ch == '%' {
  534. if !starts_with_2_hex(input.slice_from(i + 1)) {
  535. parse_error("Invalid percent-encoded sequence")
  536. }
  537. } else if !is_url_code_point(range.ch) {
  538. parse_error("Non-URL code point")
  539. }
  540. query.push_char(range.ch);
  541. i = range.next;
  542. }
  543. }
  544. }
  545. let query_bytes = encoding_override.encode(query, encoding::EncodeReplace).unwrap();
  546. let mut query_encoded = ~[];
  547. for &byte in query_bytes.iter() {
  548. match byte {
  549. 0x00 .. 0x20 | 0x22 | 0x23 | 0x3C | 0x3E | 0x60 | 0x7E .. 0xFF
  550. => percent_encode_byte(byte, &mut query_encoded),
  551. _
  552. => query_encoded.push(ascii_nocheck!(byte))
  553. }
  554. }
  555. (query_encoded, remaining)
  556. }
  557. fn parse_fragment<'a>(input: &'a str) -> ~[Ascii] {
  558. let mut fragment = ~[];
  559. let mut i = 0;
  560. while i < input.len() {
  561. match input[i] as char {
  562. '\t' | '\n' | '\r' => {
  563. parse_error("Invalid character");
  564. i += 1;
  565. },
  566. _ => {
  567. let range = input.char_range_at(i);
  568. if range.ch == '%' {
  569. if !starts_with_2_hex(input.slice_from(i + 1)) {
  570. parse_error("Invalid percent-encoded sequence")
  571. }
  572. } else if !is_url_code_point(range.ch) {
  573. parse_error("Non-URL code point")
  574. }
  575. utf8_percent_encode(input.slice(i, range.next), SimpleEncodeSet, &mut fragment);
  576. i = range.next;
  577. }
  578. }
  579. }
  580. fragment
  581. }
  582. #[inline]
  583. fn is_ascii_alpha(byte: u8) -> bool {
  584. match byte as char {
  585. 'a'..'z' | 'A'..'Z' => true,
  586. _ => false,
  587. }
  588. }
  589. #[inline]
  590. fn is_ascii_hex_digit(byte: u8) -> bool {
  591. match byte as char {
  592. 'a'..'f' | 'A'..'F' | '0'..'9' => true,
  593. _ => false,
  594. }
  595. }
  596. #[inline]
  597. fn starts_with_2_hex(input: &str) -> bool {
  598. input.len() >= 2 && is_ascii_hex_digit(input[0]) && is_ascii_hex_digit(input[1])
  599. }
  600. #[inline]
  601. fn is_url_code_point(c: char) -> bool {
  602. match c {
  603. 'a'..'z' |
  604. 'A'..'Z' |
  605. '0'..'9' |
  606. '!' | '$' | '&' | '\'' | '(' | ')' | '*' | '+' | ',' | '-' |
  607. '.' | '/' | ':' | ';' | '=' | '?' | '@' | '_' | '~' |
  608. '\u00A0'..'\uD7FF' | '\uE000'..'\uFDCF' | '\uFDF0'..'\uFFEF' |
  609. '\U00010000'..'\U0001FFFD' | '\U00020000'..'\U0002FFFD' |
  610. '\U00030000'..'\U0003FFFD' | '\U00040000'..'\U0004FFFD' |
  611. '\U00050000'..'\U0005FFFD' | '\U00060000'..'\U0006FFFD' |
  612. '\U00070000'..'\U0007FFFD' | '\U00080000'..'\U0008FFFD' |
  613. '\U00090000'..'\U0009FFFD' | '\U000A0000'..'\U000AFFFD' |
  614. '\U000B0000'..'\U000BFFFD' | '\U000C0000'..'\U000CFFFD' |
  615. '\U000D0000'..'\U000DFFFD' | '\U000E1000'..'\U000EFFFD' |
  616. '\U000F0000'..'\U000FFFFD' | '\U00100000'..'\U0010FFFD' => true,
  617. _ => false
  618. }
  619. }
  620. // Non URL code points:
  621. // U+0000 to U+0020 (space)
  622. // " # % < > [ \ ] ^ ` { | }
  623. // U+007F to U+009F
  624. // surrogates
  625. // U+FDD0 to U+FDEF
  626. // U+FFF0 to U+FFFF
  627. // Last two of each plane: U+__FFFE to U+__FFFF for __ in 01 to 10 hex
  628. fn is_relative_scheme(scheme: &[Ascii]) -> bool {
  629. match scheme.as_str_ascii() {
  630. "ftp" | "file" | "gopher" | "http" | "https" | "ws" | "wss" => true,
  631. _ => false
  632. }
  633. }