parser.rs 25 KB

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