parser.rs 24 KB

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