idna.rs 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. //! International domain names
  2. //!
  3. //! https://url.spec.whatwg.org/#idna
  4. use idna_mapping::{TABLE, Mapping};
  5. use punycode;
  6. use std::ascii::AsciiExt;
  7. use unicode_normalization::UnicodeNormalization;
  8. fn map_char(codepoint: char, flags: Uts46Flags, output: &mut String) -> Result<(), Error> {
  9. let mut min = 0;
  10. let mut max = TABLE.len() - 1;
  11. while max > min {
  12. let mid = (min + max) >> 1;
  13. if codepoint > TABLE[mid].to {
  14. min = mid;
  15. } else if codepoint < TABLE[mid].from {
  16. max = mid;
  17. } else {
  18. min = mid;
  19. max = mid;
  20. }
  21. }
  22. match TABLE[min].mapping {
  23. Mapping::Valid => output.push(codepoint),
  24. Mapping::Ignored => {},
  25. Mapping::Mapped(mapping) => output.push_str(mapping),
  26. Mapping::Deviation(mapping) => {
  27. if flags.transitional_processing {
  28. output.push_str(mapping)
  29. } else {
  30. output.push(codepoint)
  31. }
  32. }
  33. Mapping::Disallowed => return Err(Error::DissallowedCharacter),
  34. Mapping::DisallowedStd3Valid => {
  35. if flags.use_std3_ascii_rules {
  36. return Err(Error::DissallowedByStd3AsciiRules);
  37. } else {
  38. output.push(codepoint)
  39. }
  40. }
  41. Mapping::DisallowedStd3Mapped(mapping) => {
  42. if flags.use_std3_ascii_rules {
  43. return Err(Error::DissallowedMappedInStd3);
  44. } else {
  45. output.push_str(mapping)
  46. }
  47. }
  48. }
  49. Ok(())
  50. }
  51. /// http://www.unicode.org/reports/tr46/#Processing
  52. fn uts46_processing(domain: &str, flags: Uts46Flags) -> Result<String, Error> {
  53. let mut mapped = String::new();
  54. for c in domain.chars() {
  55. try!(map_char(c, flags, &mut mapped))
  56. }
  57. Ok(mapped.nfc().collect())
  58. // FIXME: steps 3 & 4: Break & Convert/Validate
  59. }
  60. #[derive(Copy, Clone)]
  61. pub struct Uts46Flags {
  62. pub use_std3_ascii_rules: bool,
  63. pub transitional_processing: bool,
  64. // FIXME: verify_dns_length: bool,
  65. }
  66. pub enum Error {
  67. PunycodeEncodingError,
  68. DissallowedByStd3AsciiRules,
  69. DissallowedMappedInStd3,
  70. DissallowedCharacter,
  71. }
  72. /// http://www.unicode.org/reports/tr46/#ToASCII
  73. pub fn uts46_to_ascii(domain: &str, flags: Uts46Flags) -> Result<String, Error> {
  74. let mut result = String::new();
  75. for label in try!(uts46_processing(domain, flags)).split(".") {
  76. if label.is_ascii() {
  77. if result.len() > 0 {
  78. result.push('.');
  79. }
  80. result.push_str(label);
  81. } else {
  82. match punycode::encode_str(label) {
  83. Some(x) => {
  84. if result.len() > 0 {
  85. result.push('.');
  86. }
  87. result.push_str("xn--");
  88. result.push_str(&x);
  89. },
  90. None => return Err(Error::PunycodeEncodingError)
  91. }
  92. }
  93. }
  94. // FIXME: step 4: optionally verify dns length
  95. return Ok(result);
  96. }
  97. /// https://url.spec.whatwg.org/#concept-domain-to-ascii
  98. pub fn domain_to_ascii(domain: &str) -> Result<String, Error> {
  99. uts46_to_ascii(domain, Uts46Flags {
  100. use_std3_ascii_rules: false,
  101. transitional_processing: true,
  102. //verify_dns_length: false,
  103. })
  104. }