tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

lib.rs (8252B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 use core::fmt::Write;
      6 use std::borrow::Cow;
      7 
      8 use idna::uts46::AsciiDenyList;
      9 use idna::uts46::ErrorPolicy;
     10 use idna::uts46::Hyphens;
     11 use idna::uts46::ProcessingError;
     12 use idna::uts46::ProcessingSuccess;
     13 use idna::uts46::Uts46;
     14 use nserror::*;
     15 use nsstring::*;
     16 use percent_encoding::percent_decode;
     17 
     18 /// The URL deny list plus asterisk and double quote.
     19 /// Using AsciiDenyList::URL is https://bugzilla.mozilla.org/show_bug.cgi?id=1815926 .
     20 const GECKO: AsciiDenyList = AsciiDenyList::new(true, "%#/:<>?@[\\]^|*\"");
     21 
     22 /// Deny only glyphless ASCII to accommodate legacy callers.
     23 const GLYPHLESS: AsciiDenyList = AsciiDenyList::new(true, "");
     24 
     25 extern "C" {
     26    #[allow(improper_ctypes)]
     27    // char is now actually guaranteed to have the same representation as u32
     28    fn mozilla_net_is_label_safe(
     29        label: *const char,
     30        label_len: usize,
     31        tld: *const char,
     32        tld_len: usize,
     33    ) -> bool;
     34 }
     35 
     36 #[no_mangle]
     37 pub unsafe extern "C" fn mozilla_net_domain_to_ascii_impl(
     38    src: *const nsACString,
     39    allow_any_glyphful_ascii: bool,
     40    dst: *mut nsACString,
     41 ) -> nsresult {
     42    debug_assert_ne!(src, dst as *const nsACString, "src and dst must not alias");
     43    process(|_, _, _| false, allow_any_glyphful_ascii, &*src, &mut *dst)
     44 }
     45 
     46 #[no_mangle]
     47 pub unsafe extern "C" fn mozilla_net_domain_to_unicode_impl(
     48    src: *const nsACString,
     49    allow_any_glyphful_ascii: bool,
     50    dst: *mut nsACString,
     51 ) -> nsresult {
     52    debug_assert_ne!(src, dst as *const nsACString, "src and dst must not alias");
     53    process(|_, _, _| true, allow_any_glyphful_ascii, &*src, &mut *dst)
     54 }
     55 
     56 #[no_mangle]
     57 pub unsafe extern "C" fn mozilla_net_domain_to_display_impl(
     58    src: *const nsACString,
     59    allow_any_glyphful_ascii: bool,
     60    dst: *mut nsACString,
     61 ) -> nsresult {
     62    debug_assert_ne!(src, dst as *const nsACString, "src and dst must not alias");
     63    // XXX do we want to change this not to fail fast?
     64    process(
     65        |label, tld, _| unsafe {
     66            debug_assert!(!label.is_empty());
     67            mozilla_net_is_label_safe(
     68                label.as_ptr(),
     69                label.len(),
     70                if tld.is_empty() {
     71                    core::ptr::null()
     72                } else {
     73                    tld.as_ptr()
     74                },
     75                tld.len(),
     76            )
     77        },
     78        allow_any_glyphful_ascii,
     79        &*src,
     80        &mut *dst,
     81    )
     82 }
     83 
     84 #[no_mangle]
     85 pub unsafe extern "C" fn mozilla_net_domain_to_display_and_ascii_impl(
     86    src: *const nsACString,
     87    dst: *mut nsACString,
     88    ascii_dst: *mut nsACString,
     89 ) -> nsresult {
     90    debug_assert_ne!(src, dst as *const nsACString, "src and dst must not alias");
     91    debug_assert_ne!(
     92        src, ascii_dst as *const nsACString,
     93        "src and ascii_dst must not alias"
     94    );
     95    debug_assert_ne!(dst, ascii_dst, "dst and ascii_dst must not alias");
     96    {
     97        let src = &*src;
     98        let dst: &mut nsACString = &mut *dst;
     99        let ascii_dst: &mut nsACString = &mut *ascii_dst;
    100        dst.truncate();
    101        ascii_dst.truncate();
    102        #[cfg(feature = "mailnews")]
    103        {
    104            if src == "Local%20Folders" || src == "smart%20mailboxes" {
    105                dst.assign(src);
    106                return nserror::NS_OK;
    107            }
    108        }
    109        let unpercent: Cow<'_, [u8]> = percent_decode(src).into();
    110        match Uts46::new().process(
    111            &unpercent,
    112            GECKO,
    113            Hyphens::Allow,
    114            ErrorPolicy::FailFast,
    115            |label, tld, _| unsafe {
    116                debug_assert!(!label.is_empty());
    117                mozilla_net_is_label_safe(
    118                    label.as_ptr(),
    119                    label.len(),
    120                    if tld.is_empty() {
    121                        core::ptr::null()
    122                    } else {
    123                        tld.as_ptr()
    124                    },
    125                    tld.len(),
    126                )
    127            },
    128            &mut IdnaWriteWrapper::new(dst),
    129            Some(&mut IdnaWriteWrapper::new(ascii_dst)),
    130        ) {
    131            Ok(ProcessingSuccess::Passthrough) => {
    132                // Let the borrow the `IdnaWriteWrapper`s end and fall through.
    133            }
    134            Ok(ProcessingSuccess::WroteToSink) => return nserror::NS_OK,
    135 
    136            Err(ProcessingError::ValidityError) => return nserror::NS_ERROR_MALFORMED_URI,
    137            Err(ProcessingError::SinkError) => unreachable!(),
    138        }
    139        match unpercent {
    140            Cow::Borrowed(_) => dst.assign(src),
    141            Cow::Owned(vec) => dst.append(&vec),
    142        }
    143        nserror::NS_OK
    144    }
    145 }
    146 
    147 type BufferString = arraystring::ArrayString<arraystring::typenum::U255>;
    148 
    149 /// Buffering type to avoid atomic check of destination
    150 /// `nsACString` on a per character basis.
    151 struct IdnaWriteWrapper<'a> {
    152    sink: &'a mut nsACString,
    153    buffer: BufferString,
    154 }
    155 
    156 impl<'a> IdnaWriteWrapper<'a> {
    157    fn new(sink: &'a mut nsACString) -> IdnaWriteWrapper<'a> {
    158        IdnaWriteWrapper {
    159            sink,
    160            buffer: BufferString::new(),
    161        }
    162    }
    163 }
    164 
    165 impl<'a> Write for IdnaWriteWrapper<'a> {
    166    fn write_str(&mut self, s: &str) -> std::fmt::Result {
    167        if self.buffer.try_push_str(s).is_ok() {
    168            return Ok(());
    169        }
    170        if !self.buffer.is_empty() {
    171            self.sink.append(self.buffer.as_bytes());
    172            self.buffer.clear();
    173            if self.buffer.try_push_str(s).is_ok() {
    174                return Ok(());
    175            }
    176        }
    177        // Input too long to fit in the buffer.
    178        self.sink.append(s.as_bytes());
    179        Ok(())
    180    }
    181 }
    182 
    183 impl<'a> Drop for IdnaWriteWrapper<'a> {
    184    fn drop(&mut self) {
    185        if !self.buffer.is_empty() {
    186            self.sink.append(self.buffer.as_bytes());
    187        }
    188    }
    189 }
    190 
    191 fn process<OutputUnicode: FnMut(&[char], &[char], bool) -> bool>(
    192    output_as_unicode: OutputUnicode,
    193    allow_any_glyphful_ascii: bool,
    194    src: &nsACString,
    195    dst: &mut nsACString,
    196 ) -> nsresult {
    197    dst.truncate();
    198    #[cfg(feature = "mailnews")]
    199    {
    200        if src == "Local Folders" || src == "local folders" {
    201            dst.assign("Local%20Folders");
    202            return nserror::NS_OK;
    203        } else if src == "smart mailboxes" {
    204            dst.assign("smart%20mailboxes");
    205            return nserror::NS_OK;
    206        }
    207    }
    208    match Uts46::new().process(
    209        &src,
    210        if allow_any_glyphful_ascii {
    211            GLYPHLESS
    212        } else {
    213            AsciiDenyList::URL
    214        },
    215        Hyphens::Allow,
    216        ErrorPolicy::FailFast,
    217        output_as_unicode,
    218        &mut IdnaWriteWrapper::new(dst),
    219        None,
    220    ) {
    221        Ok(ProcessingSuccess::Passthrough) => {
    222            // Let the borrow of `dst` inside `IdnaWriteWrapper` end and fall through.
    223        }
    224        Ok(ProcessingSuccess::WroteToSink) => return nserror::NS_OK,
    225        Err(ProcessingError::ValidityError) => return nserror::NS_ERROR_MALFORMED_URI,
    226        Err(ProcessingError::SinkError) => unreachable!(),
    227    }
    228    dst.assign(src);
    229    nserror::NS_OK
    230 }
    231 
    232 /// Not general-purpose! Only to be used from `nsDocShell::AttemptURIFixup`.
    233 #[no_mangle]
    234 pub unsafe extern "C" fn mozilla_net_recover_keyword_from_punycode(
    235    src: *const nsACString,
    236    dst: *mut nsACString,
    237 ) {
    238    let sink = &mut (*dst);
    239    let mut seen_label = false;
    240    for label in (*src).split(|b| *b == b'.') {
    241        if seen_label {
    242            sink.append(".");
    243        }
    244        seen_label = true;
    245        // We know the Punycode prefix is in lower case if we got it from
    246        // our own IDNA conversion code.
    247        if let Some(punycode) = label.strip_prefix(b"xn--") {
    248            // Not bothering to optimize this.
    249            // Just unwrap, since we know our IDNA conversion code gives
    250            // us ASCII here.
    251            let utf8 = std::str::from_utf8(punycode).unwrap();
    252            if let Some(decoded) = idna::punycode::decode_to_string(utf8) {
    253                sink.append(&decoded);
    254            } else {
    255                sink.append(label);
    256            }
    257        } else {
    258            sink.append(label);
    259        }
    260    }
    261 }