261 lines
8.1 KiB
Rust
261 lines
8.1 KiB
Rust
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
use core::fmt::Write;
|
|
use std::borrow::Cow;
|
|
|
|
use idna::uts46::AsciiDenyList;
|
|
use idna::uts46::ErrorPolicy;
|
|
use idna::uts46::Hyphens;
|
|
use idna::uts46::ProcessingError;
|
|
use idna::uts46::ProcessingSuccess;
|
|
use idna::uts46::Uts46;
|
|
use nserror::*;
|
|
use nsstring::*;
|
|
use percent_encoding::percent_decode;
|
|
|
|
/// The URL deny list plus asterisk and double quote.
|
|
/// Using AsciiDenyList::URL is https://bugzilla.mozilla.org/show_bug.cgi?id=1815926 .
|
|
const GECKO: AsciiDenyList = AsciiDenyList::new(true, "%#/:<>?@[\\]^|*\"");
|
|
|
|
/// Deny only glyphless ASCII to accommodate legacy callers.
|
|
const GLYPHLESS: AsciiDenyList = AsciiDenyList::new(true, "");
|
|
|
|
extern "C" {
|
|
#[allow(improper_ctypes)]
|
|
// char is now actually guaranteed to have the same representation as u32
|
|
fn mozilla_net_is_label_safe(
|
|
label: *const char,
|
|
label_len: usize,
|
|
tld: *const char,
|
|
tld_len: usize,
|
|
) -> bool;
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn mozilla_net_domain_to_ascii_impl(
|
|
src: *const nsACString,
|
|
allow_any_glyphful_ascii: bool,
|
|
dst: *mut nsACString,
|
|
) -> nsresult {
|
|
debug_assert_ne!(src, dst as *const nsACString, "src and dst must not alias");
|
|
process(|_, _, _| false, allow_any_glyphful_ascii, &*src, &mut *dst)
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn mozilla_net_domain_to_unicode_impl(
|
|
src: *const nsACString,
|
|
allow_any_glyphful_ascii: bool,
|
|
dst: *mut nsACString,
|
|
) -> nsresult {
|
|
debug_assert_ne!(src, dst as *const nsACString, "src and dst must not alias");
|
|
process(|_, _, _| true, allow_any_glyphful_ascii, &*src, &mut *dst)
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn mozilla_net_domain_to_display_impl(
|
|
src: *const nsACString,
|
|
allow_any_glyphful_ascii: bool,
|
|
dst: *mut nsACString,
|
|
) -> nsresult {
|
|
debug_assert_ne!(src, dst as *const nsACString, "src and dst must not alias");
|
|
// XXX do we want to change this not to fail fast?
|
|
process(
|
|
|label, tld, _| unsafe {
|
|
debug_assert!(!label.is_empty());
|
|
mozilla_net_is_label_safe(
|
|
label.as_ptr(),
|
|
label.len(),
|
|
if tld.is_empty() {
|
|
core::ptr::null()
|
|
} else {
|
|
tld.as_ptr()
|
|
},
|
|
tld.len(),
|
|
)
|
|
},
|
|
allow_any_glyphful_ascii,
|
|
&*src,
|
|
&mut *dst,
|
|
)
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn mozilla_net_domain_to_display_and_ascii_impl(
|
|
src: *const nsACString,
|
|
dst: *mut nsACString,
|
|
ascii_dst: *mut nsACString,
|
|
) -> nsresult {
|
|
debug_assert_ne!(src, dst as *const nsACString, "src and dst must not alias");
|
|
debug_assert_ne!(
|
|
src, ascii_dst as *const nsACString,
|
|
"src and ascii_dst must not alias"
|
|
);
|
|
debug_assert_ne!(dst, ascii_dst, "dst and ascii_dst must not alias");
|
|
{
|
|
let src = &*src;
|
|
let dst: &mut nsACString = &mut *dst;
|
|
let ascii_dst: &mut nsACString = &mut *ascii_dst;
|
|
dst.truncate();
|
|
ascii_dst.truncate();
|
|
#[cfg(feature = "mailnews")]
|
|
{
|
|
if src == "Local%20Folders" || src == "smart%20mailboxes" {
|
|
dst.assign(src);
|
|
return nserror::NS_OK;
|
|
}
|
|
}
|
|
let unpercent: Cow<'_, [u8]> = percent_decode(src).into();
|
|
match Uts46::new().process(
|
|
&unpercent,
|
|
GECKO,
|
|
Hyphens::Allow,
|
|
ErrorPolicy::FailFast,
|
|
|label, tld, _| unsafe {
|
|
debug_assert!(!label.is_empty());
|
|
mozilla_net_is_label_safe(
|
|
label.as_ptr(),
|
|
label.len(),
|
|
if tld.is_empty() {
|
|
core::ptr::null()
|
|
} else {
|
|
tld.as_ptr()
|
|
},
|
|
tld.len(),
|
|
)
|
|
},
|
|
&mut IdnaWriteWrapper::new(dst),
|
|
Some(&mut IdnaWriteWrapper::new(ascii_dst)),
|
|
) {
|
|
Ok(ProcessingSuccess::Passthrough) => {
|
|
// Let the borrow the `IdnaWriteWrapper`s end and fall through.
|
|
}
|
|
Ok(ProcessingSuccess::WroteToSink) => return nserror::NS_OK,
|
|
|
|
Err(ProcessingError::ValidityError) => return nserror::NS_ERROR_MALFORMED_URI,
|
|
Err(ProcessingError::SinkError) => unreachable!(),
|
|
}
|
|
match unpercent {
|
|
Cow::Borrowed(_) => dst.assign(src),
|
|
Cow::Owned(vec) => dst.append(&vec),
|
|
}
|
|
nserror::NS_OK
|
|
}
|
|
}
|
|
|
|
type BufferString = arraystring::ArrayString<arraystring::typenum::U255>;
|
|
|
|
/// Buffering type to avoid atomic check of destination
|
|
/// `nsACString` on a per character basis.
|
|
struct IdnaWriteWrapper<'a> {
|
|
sink: &'a mut nsACString,
|
|
buffer: BufferString,
|
|
}
|
|
|
|
impl<'a> IdnaWriteWrapper<'a> {
|
|
fn new(sink: &'a mut nsACString) -> IdnaWriteWrapper<'a> {
|
|
IdnaWriteWrapper {
|
|
sink,
|
|
buffer: BufferString::new(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'a> Write for IdnaWriteWrapper<'a> {
|
|
fn write_str(&mut self, s: &str) -> std::fmt::Result {
|
|
if self.buffer.try_push_str(s).is_ok() {
|
|
return Ok(());
|
|
}
|
|
if !self.buffer.is_empty() {
|
|
self.sink.append(self.buffer.as_bytes());
|
|
self.buffer.clear();
|
|
if self.buffer.try_push_str(s).is_ok() {
|
|
return Ok(());
|
|
}
|
|
}
|
|
// Input too long to fit in the buffer.
|
|
self.sink.append(s.as_bytes());
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl<'a> Drop for IdnaWriteWrapper<'a> {
|
|
fn drop(&mut self) {
|
|
if !self.buffer.is_empty() {
|
|
self.sink.append(self.buffer.as_bytes());
|
|
}
|
|
}
|
|
}
|
|
|
|
fn process<OutputUnicode: FnMut(&[char], &[char], bool) -> bool>(
|
|
output_as_unicode: OutputUnicode,
|
|
allow_any_glyphful_ascii: bool,
|
|
src: &nsACString,
|
|
dst: &mut nsACString,
|
|
) -> nsresult {
|
|
dst.truncate();
|
|
#[cfg(feature = "mailnews")]
|
|
{
|
|
if src == "Local Folders" || src == "local folders" {
|
|
dst.assign("Local%20Folders");
|
|
return nserror::NS_OK;
|
|
} else if src == "smart mailboxes" {
|
|
dst.assign("smart%20mailboxes");
|
|
return nserror::NS_OK;
|
|
}
|
|
}
|
|
match Uts46::new().process(
|
|
&src,
|
|
if allow_any_glyphful_ascii {
|
|
GLYPHLESS
|
|
} else {
|
|
AsciiDenyList::URL
|
|
},
|
|
Hyphens::Allow,
|
|
ErrorPolicy::FailFast,
|
|
output_as_unicode,
|
|
&mut IdnaWriteWrapper::new(dst),
|
|
None,
|
|
) {
|
|
Ok(ProcessingSuccess::Passthrough) => {
|
|
// Let the borrow of `dst` inside `IdnaWriteWrapper` end and fall through.
|
|
}
|
|
Ok(ProcessingSuccess::WroteToSink) => return nserror::NS_OK,
|
|
Err(ProcessingError::ValidityError) => return nserror::NS_ERROR_MALFORMED_URI,
|
|
Err(ProcessingError::SinkError) => unreachable!(),
|
|
}
|
|
dst.assign(src);
|
|
nserror::NS_OK
|
|
}
|
|
|
|
/// Not general-purpose! Only to be used from `nsDocShell::AttemptURIFixup`.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn mozilla_net_recover_keyword_from_punycode(
|
|
src: *const nsACString,
|
|
dst: *mut nsACString,
|
|
) {
|
|
let sink = &mut (*dst);
|
|
let mut seen_label = false;
|
|
for label in (*src).split(|b| *b == b'.') {
|
|
if seen_label {
|
|
sink.append(".");
|
|
}
|
|
seen_label = true;
|
|
// We know the Punycode prefix is in lower case if we got it from
|
|
// our own IDNA conversion code.
|
|
if let Some(punycode) = label.strip_prefix(b"xn--") {
|
|
// Not bothering to optimize this.
|
|
// Just unwrap, since we know our IDNA conversion code gives
|
|
// us ASCII here.
|
|
let utf8 = std::str::from_utf8(punycode).unwrap();
|
|
if let Some(decoded) = idna::punycode::decode_to_string(utf8) {
|
|
sink.append(&decoded);
|
|
} else {
|
|
sink.append(label);
|
|
}
|
|
} else {
|
|
sink.append(label);
|
|
}
|
|
}
|
|
}
|