|
@@ -258,16 +258,16 @@ fn passes_bidi(label: &str, is_bidi_domain: bool) -> bool {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/// http://www.unicode.org/reports/tr46/#Validity_Criteria
|
|
/// http://www.unicode.org/reports/tr46/#Validity_Criteria
|
|
|
-fn validate_full(label: &str, is_bidi_domain: bool, flags: Flags, errors: &mut Vec<Error>) {
|
|
|
|
|
|
|
+fn validate_full(label: &str, is_bidi_domain: bool, config: Config, errors: &mut Vec<Error>) {
|
|
|
// V1: Must be in NFC form.
|
|
// V1: Must be in NFC form.
|
|
|
if label.nfc().ne(label.chars()) {
|
|
if label.nfc().ne(label.chars()) {
|
|
|
errors.push(Error::ValidityCriteria);
|
|
errors.push(Error::ValidityCriteria);
|
|
|
} else {
|
|
} else {
|
|
|
- validate(label, is_bidi_domain, flags, errors);
|
|
|
|
|
|
|
+ validate(label, is_bidi_domain, config, errors);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-fn validate(label: &str, is_bidi_domain: bool, flags: Flags, errors: &mut Vec<Error>) {
|
|
|
|
|
|
|
+fn validate(label: &str, is_bidi_domain: bool, config: Config, errors: &mut Vec<Error>) {
|
|
|
let first_char = label.chars().next();
|
|
let first_char = label.chars().next();
|
|
|
if first_char == None {
|
|
if first_char == None {
|
|
|
// Empty string, pass
|
|
// Empty string, pass
|
|
@@ -277,11 +277,9 @@ fn validate(label: &str, is_bidi_domain: bool, flags: Flags, errors: &mut Vec<Er
|
|
|
// NOTE: Spec says that the label must not contain a HYPHEN-MINUS character in both the
|
|
// NOTE: Spec says that the label must not contain a HYPHEN-MINUS character in both the
|
|
|
// third and fourth positions. But nobody follows this criteria. See the spec issue below:
|
|
// third and fourth positions. But nobody follows this criteria. See the spec issue below:
|
|
|
// https://github.com/whatwg/url/issues/53
|
|
// https://github.com/whatwg/url/issues/53
|
|
|
- //
|
|
|
|
|
- // TODO: Add *CheckHyphens* flag.
|
|
|
|
|
|
|
|
|
|
// V3: neither begin nor end with a U+002D HYPHEN-MINUS
|
|
// V3: neither begin nor end with a U+002D HYPHEN-MINUS
|
|
|
- else if label.starts_with("-") || label.ends_with("-") {
|
|
|
|
|
|
|
+ else if config.check_hyphens && (label.starts_with("-") || label.ends_with("-")) {
|
|
|
errors.push(Error::ValidityCriteria);
|
|
errors.push(Error::ValidityCriteria);
|
|
|
}
|
|
}
|
|
|
// V4: not contain a U+002E FULL STOP
|
|
// V4: not contain a U+002E FULL STOP
|
|
@@ -295,8 +293,8 @@ fn validate(label: &str, is_bidi_domain: bool, flags: Flags, errors: &mut Vec<Er
|
|
|
// V6: Check against Mapping Table
|
|
// V6: Check against Mapping Table
|
|
|
else if label.chars().any(|c| match *find_char(c) {
|
|
else if label.chars().any(|c| match *find_char(c) {
|
|
|
Mapping::Valid => false,
|
|
Mapping::Valid => false,
|
|
|
- Mapping::Deviation(_) => flags.transitional_processing,
|
|
|
|
|
- Mapping::DisallowedStd3Valid => flags.use_std3_ascii_rules,
|
|
|
|
|
|
|
+ Mapping::Deviation(_) => config.flags.transitional_processing,
|
|
|
|
|
+ Mapping::DisallowedStd3Valid => config.flags.use_std3_ascii_rules,
|
|
|
_ => true,
|
|
_ => true,
|
|
|
}) {
|
|
}) {
|
|
|
errors.push(Error::ValidityCriteria);
|
|
errors.push(Error::ValidityCriteria);
|
|
@@ -314,10 +312,10 @@ fn validate(label: &str, is_bidi_domain: bool, flags: Flags, errors: &mut Vec<Er
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/// http://www.unicode.org/reports/tr46/#Processing
|
|
/// http://www.unicode.org/reports/tr46/#Processing
|
|
|
-fn processing(domain: &str, flags: Flags, errors: &mut Vec<Error>) -> String {
|
|
|
|
|
|
|
+fn processing(domain: &str, config: Config, errors: &mut Vec<Error>) -> String {
|
|
|
let mut mapped = String::with_capacity(domain.len());
|
|
let mut mapped = String::with_capacity(domain.len());
|
|
|
for c in domain.chars() {
|
|
for c in domain.chars() {
|
|
|
- map_char(c, flags, &mut mapped, errors)
|
|
|
|
|
|
|
+ map_char(c, config.flags, &mut mapped, errors)
|
|
|
}
|
|
}
|
|
|
let mut normalized = String::with_capacity(mapped.len());
|
|
let mut normalized = String::with_capacity(mapped.len());
|
|
|
normalized.extend(mapped.nfc());
|
|
normalized.extend(mapped.nfc());
|
|
@@ -358,29 +356,117 @@ fn processing(domain: &str, flags: Flags, errors: &mut Vec<Error>) -> String {
|
|
|
if label.starts_with(PUNYCODE_PREFIX) {
|
|
if label.starts_with(PUNYCODE_PREFIX) {
|
|
|
match punycode::decode_to_string(&label[PUNYCODE_PREFIX.len()..]) {
|
|
match punycode::decode_to_string(&label[PUNYCODE_PREFIX.len()..]) {
|
|
|
Some(decoded_label) => {
|
|
Some(decoded_label) => {
|
|
|
- let flags = Flags {
|
|
|
|
|
- transitional_processing: false,
|
|
|
|
|
- ..flags
|
|
|
|
|
- };
|
|
|
|
|
- validate_full(&decoded_label, is_bidi_domain, flags, errors);
|
|
|
|
|
|
|
+ let config = config.transitional_processing(false);
|
|
|
|
|
+ validate_full(&decoded_label, is_bidi_domain, config, errors);
|
|
|
validated.push_str(&decoded_label)
|
|
validated.push_str(&decoded_label)
|
|
|
}
|
|
}
|
|
|
None => errors.push(Error::PunycodeError),
|
|
None => errors.push(Error::PunycodeError),
|
|
|
}
|
|
}
|
|
|
} else {
|
|
} else {
|
|
|
// `normalized` is already `NFC` so we can skip that check
|
|
// `normalized` is already `NFC` so we can skip that check
|
|
|
- validate(label, is_bidi_domain, flags, errors);
|
|
|
|
|
|
|
+ validate(label, is_bidi_domain, config, errors);
|
|
|
validated.push_str(label)
|
|
validated.push_str(label)
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
validated
|
|
validated
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+#[derive(Clone, Copy)]
|
|
|
|
|
+pub struct Config {
|
|
|
|
|
+ flags: Flags,
|
|
|
|
|
+ check_hyphens: bool,
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+impl From<Flags> for Config {
|
|
|
|
|
+ #[inline]
|
|
|
|
|
+ fn from(flags: Flags) -> Self {
|
|
|
|
|
+ Self { flags, check_hyphens: true }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+impl Config {
|
|
|
|
|
+ #[inline]
|
|
|
|
|
+ pub fn use_std3_ascii_rules(mut self, value: bool) -> Self {
|
|
|
|
|
+ self.flags.use_std3_ascii_rules = value;
|
|
|
|
|
+ self
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ #[inline]
|
|
|
|
|
+ pub fn transitional_processing(mut self, value: bool) -> Self {
|
|
|
|
|
+ self.flags.transitional_processing = value;
|
|
|
|
|
+ self
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ #[inline]
|
|
|
|
|
+ pub fn verify_dns_length(mut self, value: bool) -> Self {
|
|
|
|
|
+ self.flags.verify_dns_length = value;
|
|
|
|
|
+ self
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ #[inline]
|
|
|
|
|
+ pub fn check_hyphens(mut self, value: bool) -> Self {
|
|
|
|
|
+ self.check_hyphens = value;
|
|
|
|
|
+ self
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// http://www.unicode.org/reports/tr46/#ToASCII
|
|
|
|
|
+ pub fn to_ascii(self, domain: &str) -> Result<String, Errors> {
|
|
|
|
|
+ let mut errors = Vec::new();
|
|
|
|
|
+ let mut result = String::new();
|
|
|
|
|
+ let mut first = true;
|
|
|
|
|
+ for label in processing(domain, self, &mut errors).split('.') {
|
|
|
|
|
+ if !first {
|
|
|
|
|
+ result.push('.');
|
|
|
|
|
+ }
|
|
|
|
|
+ first = false;
|
|
|
|
|
+ if label.is_ascii() {
|
|
|
|
|
+ result.push_str(label);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ match punycode::encode_str(label) {
|
|
|
|
|
+ Some(x) => {
|
|
|
|
|
+ result.push_str(PUNYCODE_PREFIX);
|
|
|
|
|
+ result.push_str(&x);
|
|
|
|
|
+ },
|
|
|
|
|
+ None => errors.push(Error::PunycodeError)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if self.flags.verify_dns_length {
|
|
|
|
|
+ let domain = if result.ends_with(".") { &result[..result.len()-1] } else { &*result };
|
|
|
|
|
+ if domain.len() < 1 || domain.split('.').any(|label| label.len() < 1) {
|
|
|
|
|
+ errors.push(Error::TooShortForDns)
|
|
|
|
|
+ }
|
|
|
|
|
+ if domain.len() > 253 || domain.split('.').any(|label| label.len() > 63) {
|
|
|
|
|
+ errors.push(Error::TooLongForDns)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ if errors.is_empty() {
|
|
|
|
|
+ Ok(result)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ Err(Errors(errors))
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// http://www.unicode.org/reports/tr46/#ToUnicode
|
|
|
|
|
+ pub fn to_unicode(self, domain: &str) -> (String, Result<(), Errors>) {
|
|
|
|
|
+ let mut errors = Vec::new();
|
|
|
|
|
+ let domain = processing(domain, self, &mut errors);
|
|
|
|
|
+ let errors = if errors.is_empty() {
|
|
|
|
|
+ Ok(())
|
|
|
|
|
+ } else {
|
|
|
|
|
+ Err(Errors(errors))
|
|
|
|
|
+ };
|
|
|
|
|
+ (domain, errors)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
#[derive(Copy, Clone)]
|
|
#[derive(Copy, Clone)]
|
|
|
pub struct Flags {
|
|
pub struct Flags {
|
|
|
- pub use_std3_ascii_rules: bool,
|
|
|
|
|
- pub transitional_processing: bool,
|
|
|
|
|
- pub verify_dns_length: bool,
|
|
|
|
|
|
|
+ pub use_std3_ascii_rules: bool,
|
|
|
|
|
+ pub transitional_processing: bool,
|
|
|
|
|
+ pub verify_dns_length: bool,
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
|
|
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
|
|
@@ -403,45 +489,7 @@ pub struct Errors(Vec<Error>);
|
|
|
|
|
|
|
|
/// http://www.unicode.org/reports/tr46/#ToASCII
|
|
/// http://www.unicode.org/reports/tr46/#ToASCII
|
|
|
pub fn to_ascii(domain: &str, flags: Flags) -> Result<String, Errors> {
|
|
pub fn to_ascii(domain: &str, flags: Flags) -> Result<String, Errors> {
|
|
|
- let mut errors = Vec::new();
|
|
|
|
|
- let mut result = String::new();
|
|
|
|
|
- let mut first = true;
|
|
|
|
|
- for label in processing(domain, flags, &mut errors).split('.') {
|
|
|
|
|
- if !first {
|
|
|
|
|
- result.push('.');
|
|
|
|
|
- }
|
|
|
|
|
- first = false;
|
|
|
|
|
- if label.is_ascii() {
|
|
|
|
|
- result.push_str(label);
|
|
|
|
|
- } else {
|
|
|
|
|
- match punycode::encode_str(label) {
|
|
|
|
|
- Some(x) => {
|
|
|
|
|
- result.push_str(PUNYCODE_PREFIX);
|
|
|
|
|
- result.push_str(&x);
|
|
|
|
|
- }
|
|
|
|
|
- None => errors.push(Error::PunycodeError),
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if flags.verify_dns_length {
|
|
|
|
|
- let domain = if result.ends_with(".") {
|
|
|
|
|
- &result[..result.len() - 1]
|
|
|
|
|
- } else {
|
|
|
|
|
- &*result
|
|
|
|
|
- };
|
|
|
|
|
- if domain.len() < 1 || domain.split('.').any(|label| label.len() < 1) {
|
|
|
|
|
- errors.push(Error::TooShortForDns)
|
|
|
|
|
- }
|
|
|
|
|
- if domain.len() > 253 || domain.split('.').any(|label| label.len() > 63) {
|
|
|
|
|
- errors.push(Error::TooLongForDns)
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- if errors.is_empty() {
|
|
|
|
|
- Ok(result)
|
|
|
|
|
- } else {
|
|
|
|
|
- Err(Errors(errors))
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ Config::from(flags).to_ascii(domain)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/// http://www.unicode.org/reports/tr46/#ToUnicode
|
|
/// http://www.unicode.org/reports/tr46/#ToUnicode
|
|
@@ -449,12 +497,5 @@ pub fn to_ascii(domain: &str, flags: Flags) -> Result<String, Errors> {
|
|
|
/// Only `use_std3_ascii_rules` is used in `flags`.
|
|
/// Only `use_std3_ascii_rules` is used in `flags`.
|
|
|
pub fn to_unicode(domain: &str, mut flags: Flags) -> (String, Result<(), Errors>) {
|
|
pub fn to_unicode(domain: &str, mut flags: Flags) -> (String, Result<(), Errors>) {
|
|
|
flags.transitional_processing = false;
|
|
flags.transitional_processing = false;
|
|
|
- let mut errors = Vec::new();
|
|
|
|
|
- let domain = processing(domain, flags, &mut errors);
|
|
|
|
|
- let errors = if errors.is_empty() {
|
|
|
|
|
- Ok(())
|
|
|
|
|
- } else {
|
|
|
|
|
- Err(Errors(errors))
|
|
|
|
|
- };
|
|
|
|
|
- (domain, errors)
|
|
|
|
|
|
|
+ Config::from(flags).to_unicode(domain)
|
|
|
}
|
|
}
|