tor-browser

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

lib.rs (14215B)


      1 /* -*- Mode: rust; rust-indent-offset: 2 -*- */
      2 /* This Source Code Form is subject to the terms of the Mozilla Public
      3 * License, v. 2.0. If a copy of the MPL was not distributed with this
      4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      5 
      6 #![expect(
      7    clippy::missing_safety_doc,
      8    clippy::missing_panics_doc,
      9    reason = "OK here"
     10 )]
     11 #![allow(unknown_lints, mismatched_lifetime_syntaxes)]
     12 
     13 extern crate url;
     14 use url::{quirks, ParseOptions, Position, Url};
     15 
     16 extern crate nsstring;
     17 use nsstring::{nsACString, nsCString};
     18 
     19 extern crate nserror;
     20 use nserror::{nsresult, NS_ERROR_MALFORMED_URI, NS_ERROR_UNEXPECTED, NS_OK};
     21 
     22 extern crate xpcom;
     23 use xpcom::{AtomicRefcnt, RefCounted, RefPtr};
     24 
     25 extern crate uuid;
     26 use std::{fmt::Write as _, marker::PhantomData, ops, ptr, str};
     27 
     28 use uuid::Uuid;
     29 
     30 extern "C" {
     31    fn Gecko_StrictFileOriginPolicy() -> bool;
     32 }
     33 
     34 /// Helper macro. If the expression $e is Ok(t) evaluates to t, otherwise,
     35 /// returns `NS_ERROR_MALFORMED_URI`.
     36 macro_rules! try_or_malformed {
     37    ($e:expr) => {
     38        match $e {
     39            Ok(v) => v,
     40            Err(_) => return NS_ERROR_MALFORMED_URI,
     41        }
     42    };
     43 }
     44 
     45 fn parser<'a>() -> ParseOptions<'a> {
     46    Url::options()
     47 }
     48 
     49 fn default_port(scheme: &str) -> Option<u16> {
     50    match scheme {
     51        "ftp" => Some(21),
     52        "gopher" => Some(70),
     53        "http" | "ws" => Some(80),
     54        "https" | "wss" | "rtsp" | "android" => Some(443),
     55        _ => None,
     56    }
     57 }
     58 
     59 /// A slice into the backing string.
     60 ///
     61 /// This type is only valid as long as the [`MozURL`] which it was pulled from is
     62 /// valid. In C++, this type implicitly converts to a `nsDependentCString`, and is
     63 /// an implementation detail.
     64 ///
     65 /// This type exists because, unlike `&str`, this type is safe to return over FFI.
     66 #[repr(C)]
     67 pub struct SpecSlice<'a> {
     68    data: *const u8,
     69    len: u32,
     70    _marker: PhantomData<&'a [u8]>,
     71 }
     72 
     73 impl<'a> From<&'a str> for SpecSlice<'a> {
     74    fn from(s: &'a str) -> Self {
     75        SpecSlice {
     76            data: s.as_ptr(),
     77            len: u32::try_from(s.len()).expect("string length not representable in u32"),
     78            _marker: PhantomData,
     79        }
     80    }
     81 }
     82 
     83 /// The [`MozURL`] reference-counted threadsafe URL type. This type intentionally
     84 /// implements no XPCOM interfaces, and all method calls are non-virtual.
     85 #[repr(C)]
     86 pub struct MozURL {
     87    pub url: Url,
     88    refcnt: AtomicRefcnt,
     89 }
     90 
     91 impl MozURL {
     92    #[must_use]
     93    pub fn from_url(url: Url) -> RefPtr<Self> {
     94        // Actually allocate the URL on the heap. This is the only place we actually
     95        // create a [`MozURL`], other than in `clone()`.
     96        unsafe {
     97            RefPtr::from_raw(Box::into_raw(Box::new(Self {
     98                url,
     99                refcnt: AtomicRefcnt::new(),
    100            })))
    101            .expect("MozURL created OK")
    102        }
    103    }
    104 }
    105 
    106 impl ops::Deref for MozURL {
    107    type Target = Url;
    108    fn deref(&self) -> &Url {
    109        &self.url
    110    }
    111 }
    112 impl ops::DerefMut for MozURL {
    113    fn deref_mut(&mut self) -> &mut Url {
    114        &mut self.url
    115    }
    116 }
    117 
    118 // Memory Management for MozURL
    119 #[no_mangle]
    120 pub unsafe extern "C" fn mozurl_addref(url: &MozURL) {
    121    url.refcnt.inc();
    122 }
    123 
    124 #[no_mangle]
    125 pub unsafe extern "C" fn mozurl_release(url: &MozURL) {
    126    let rc = url.refcnt.dec();
    127    if rc == 0 {
    128        drop(Box::from_raw(ptr::from_ref::<MozURL>(url).cast_mut()));
    129    }
    130 }
    131 
    132 // xpcom::RefPtr support
    133 unsafe impl RefCounted for MozURL {
    134    unsafe fn addref(&self) {
    135        mozurl_addref(self);
    136    }
    137    unsafe fn release(&self) {
    138        mozurl_release(self);
    139    }
    140 }
    141 
    142 // Allocate a new MozURL object with a RefCnt of 1, and store a pointer to it
    143 // into url.
    144 #[no_mangle]
    145 pub extern "C" fn mozurl_new(
    146    result: &mut *const MozURL,
    147    spec: &nsACString,
    148    base: Option<&MozURL>,
    149 ) -> nsresult {
    150    *result = ptr::null_mut();
    151 
    152    let spec = try_or_malformed!(str::from_utf8(spec));
    153    let url = if let Some(base) = base {
    154        try_or_malformed!(base.url.join(spec))
    155    } else {
    156        try_or_malformed!(parser().parse(spec))
    157    };
    158 
    159    MozURL::from_url(url).forget(result);
    160    NS_OK
    161 }
    162 
    163 /// Allocate a new [`MozURL`] object which is a clone of the original, and store a
    164 /// pointer to it into newurl.
    165 #[no_mangle]
    166 pub extern "C" fn mozurl_clone(url: &MozURL, newurl: &mut *const MozURL) {
    167    MozURL::from_url(url.url.clone()).forget(newurl);
    168 }
    169 
    170 #[no_mangle]
    171 pub extern "C" fn mozurl_spec(url: &MozURL) -> SpecSlice {
    172    url.as_ref().into()
    173 }
    174 
    175 #[no_mangle]
    176 pub extern "C" fn mozurl_scheme(url: &MozURL) -> SpecSlice {
    177    url.scheme().into()
    178 }
    179 
    180 #[no_mangle]
    181 pub extern "C" fn mozurl_username(url: &MozURL) -> SpecSlice {
    182    if url.cannot_be_a_base() {
    183        "".into()
    184    } else {
    185        url.username().into()
    186    }
    187 }
    188 
    189 #[no_mangle]
    190 pub extern "C" fn mozurl_password(url: &MozURL) -> SpecSlice {
    191    url.password().unwrap_or("").into()
    192 }
    193 
    194 #[no_mangle]
    195 pub extern "C" fn mozurl_host(url: &MozURL) -> SpecSlice {
    196    url.host_str().unwrap_or("").into()
    197 }
    198 
    199 #[no_mangle]
    200 pub extern "C" fn mozurl_port(url: &MozURL) -> i32 {
    201    // NOTE: Gecko uses -1 to represent the default port.
    202    url.port().map_or(-1, i32::from)
    203 }
    204 
    205 #[no_mangle]
    206 pub extern "C" fn mozurl_real_port(url: &MozURL) -> i32 {
    207    url.port()
    208        .or_else(|| default_port(url.scheme()))
    209        .map_or(-1, i32::from)
    210 }
    211 
    212 #[no_mangle]
    213 pub extern "C" fn mozurl_host_port(url: &MozURL) -> SpecSlice {
    214    if url.port().is_some() {
    215        return (&url[Position::BeforeHost..Position::BeforePath]).into();
    216    }
    217    url.host_str().unwrap_or("").into()
    218 }
    219 
    220 #[no_mangle]
    221 pub extern "C" fn mozurl_filepath(url: &MozURL) -> SpecSlice {
    222    url.path().into()
    223 }
    224 
    225 #[no_mangle]
    226 pub extern "C" fn mozurl_path(url: &MozURL) -> SpecSlice {
    227    (&url[Position::BeforePath..]).into()
    228 }
    229 
    230 #[no_mangle]
    231 pub extern "C" fn mozurl_query(url: &MozURL) -> SpecSlice {
    232    url.query().unwrap_or("").into()
    233 }
    234 
    235 #[no_mangle]
    236 pub extern "C" fn mozurl_fragment(url: &MozURL) -> SpecSlice {
    237    url.fragment().unwrap_or("").into()
    238 }
    239 
    240 #[no_mangle]
    241 pub extern "C" fn mozurl_spec_no_ref(url: &MozURL) -> SpecSlice {
    242    (&url[..Position::AfterQuery]).into()
    243 }
    244 
    245 #[no_mangle]
    246 pub extern "C" fn mozurl_has_fragment(url: &MozURL) -> bool {
    247    url.fragment().is_some()
    248 }
    249 
    250 #[no_mangle]
    251 pub extern "C" fn mozurl_has_query(url: &MozURL) -> bool {
    252    url.query().is_some()
    253 }
    254 
    255 #[no_mangle]
    256 pub extern "C" fn mozurl_directory(url: &MozURL) -> SpecSlice {
    257    url.path().rfind('/').map_or_else(
    258        || url.path().into(),
    259        |position| url.path()[..=position].into(),
    260    )
    261 }
    262 
    263 #[no_mangle]
    264 pub extern "C" fn mozurl_prepath(url: &MozURL) -> SpecSlice {
    265    (&url[..Position::BeforePath]).into()
    266 }
    267 
    268 fn get_origin(url: &MozURL) -> Option<String> {
    269    match url.scheme() {
    270        "blob" | "ftp" | "http" | "https" | "ws" | "wss" => {
    271            Some(url.origin().ascii_serialization())
    272        }
    273        "indexeddb" | "moz-extension" | "resource" => {
    274            let host = url.host_str().unwrap_or("");
    275 
    276            let port = url.port().or_else(|| default_port(url.scheme()));
    277 
    278            if port == default_port(url.scheme()) {
    279                Some(format!("{}://{}", url.scheme(), host))
    280            } else {
    281                Some(format!(
    282                    "{}://{}:{}",
    283                    url.scheme(),
    284                    host,
    285                    port.expect("got a port")
    286                ))
    287            }
    288        }
    289        "file" => {
    290            if unsafe { Gecko_StrictFileOriginPolicy() } {
    291                Some(url[..Position::AfterPath].to_owned())
    292            } else {
    293                Some("file://UNIVERSAL_FILE_URI_ORIGIN".to_owned())
    294            }
    295        }
    296        "about" | "moz-safe-about" => Some(url[..Position::AfterPath].to_owned()),
    297        _ => None,
    298    }
    299 }
    300 
    301 #[no_mangle]
    302 pub extern "C" fn mozurl_origin(url: &MozURL, origin: &mut nsACString) {
    303    let origin_str = if url.as_ref().starts_with("about:blank") {
    304        None
    305    } else {
    306        get_origin(url)
    307    };
    308 
    309    let origin_str = origin_str.unwrap_or_else(|| {
    310        // nsIPrincipal stores the uuid, so the same uuid is returned everytime.
    311        // We can't do that for MozURL because it can be used across threads.
    312        // Storing uuid would mutate the object which would cause races between
    313        // threads.
    314        format!("moz-nullprincipal:{{{}}}", Uuid::new_v4())
    315    });
    316 
    317    // NOTE: Try to re-use the allocation we got from rust-url, and transfer
    318    // ownership of the buffer to C++.
    319    let mut o = nsCString::from(origin_str);
    320    origin.take_from(&mut o);
    321 }
    322 
    323 // Helper macro for debug asserting that we're the only reference to MozURL.
    324 macro_rules! debug_assert_mut {
    325    ($e:expr) => {
    326        debug_assert_eq!($e.refcnt.get(), 1, "Cannot mutate an aliased MozURL!");
    327    };
    328 }
    329 
    330 #[no_mangle]
    331 pub extern "C" fn mozurl_cannot_be_a_base(url: &mut MozURL) -> bool {
    332    debug_assert_mut!(url);
    333    url.cannot_be_a_base()
    334 }
    335 
    336 #[no_mangle]
    337 pub extern "C" fn mozurl_set_scheme(url: &mut MozURL, scheme: &nsACString) -> nsresult {
    338    debug_assert_mut!(url);
    339    let scheme = try_or_malformed!(str::from_utf8(scheme));
    340    try_or_malformed!(quirks::set_protocol(url, scheme));
    341    NS_OK
    342 }
    343 
    344 #[no_mangle]
    345 pub extern "C" fn mozurl_set_username(url: &mut MozURL, username: &nsACString) -> nsresult {
    346    debug_assert_mut!(url);
    347    let username = try_or_malformed!(str::from_utf8(username));
    348    try_or_malformed!(quirks::set_username(url, username));
    349    NS_OK
    350 }
    351 
    352 #[no_mangle]
    353 pub extern "C" fn mozurl_set_password(url: &mut MozURL, password: &nsACString) -> nsresult {
    354    debug_assert_mut!(url);
    355    let password = try_or_malformed!(str::from_utf8(password));
    356    try_or_malformed!(quirks::set_password(url, password));
    357    NS_OK
    358 }
    359 
    360 #[no_mangle]
    361 pub extern "C" fn mozurl_set_host_port(url: &mut MozURL, hostport: &nsACString) -> nsresult {
    362    debug_assert_mut!(url);
    363    let hostport = try_or_malformed!(str::from_utf8(hostport));
    364    try_or_malformed!(quirks::set_host(url, hostport));
    365    NS_OK
    366 }
    367 
    368 #[no_mangle]
    369 pub extern "C" fn mozurl_set_hostname(url: &mut MozURL, host: &nsACString) -> nsresult {
    370    debug_assert_mut!(url);
    371    let host = try_or_malformed!(str::from_utf8(host));
    372    try_or_malformed!(quirks::set_hostname(url, host));
    373    NS_OK
    374 }
    375 
    376 #[no_mangle]
    377 pub extern "C" fn mozurl_set_port_no(url: &mut MozURL, new_port: i32) -> nsresult {
    378    debug_assert_mut!(url);
    379 
    380    if new_port > i32::from(u16::MAX) {
    381        return NS_ERROR_UNEXPECTED;
    382    }
    383 
    384    if url.cannot_be_a_base() {
    385        return NS_ERROR_MALFORMED_URI;
    386    }
    387 
    388    #[expect(clippy::cast_sign_loss, reason = "not possible due to first match arm")]
    389    let port = match new_port {
    390        new if new < 0 || i32::from(u16::MAX) <= new => None,
    391        new if Some(new as u16) == default_port(url.scheme()) => None,
    392        new => Some(new as u16),
    393    };
    394    try_or_malformed!(url.set_port(port));
    395    NS_OK
    396 }
    397 
    398 #[no_mangle]
    399 pub extern "C" fn mozurl_set_pathname(url: &mut MozURL, path: &nsACString) -> nsresult {
    400    debug_assert_mut!(url);
    401    let path = try_or_malformed!(str::from_utf8(path));
    402    quirks::set_pathname(url, path);
    403    NS_OK
    404 }
    405 
    406 #[no_mangle]
    407 pub extern "C" fn mozurl_set_query(url: &mut MozURL, query: &nsACString) -> nsresult {
    408    debug_assert_mut!(url);
    409    let query = try_or_malformed!(str::from_utf8(query));
    410    quirks::set_search(url, query);
    411    NS_OK
    412 }
    413 
    414 #[no_mangle]
    415 pub extern "C" fn mozurl_set_fragment(url: &mut MozURL, fragment: &nsACString) -> nsresult {
    416    debug_assert_mut!(url);
    417    let fragment = try_or_malformed!(str::from_utf8(fragment));
    418    quirks::set_hash(url, fragment);
    419    NS_OK
    420 }
    421 
    422 #[no_mangle]
    423 pub extern "C" fn mozurl_sizeof(url: &MozURL) -> usize {
    424    debug_assert_mut!(url);
    425    size_of::<MozURL>() + url.as_str().len()
    426 }
    427 
    428 #[no_mangle]
    429 pub extern "C" fn mozurl_common_base(
    430    url1: &MozURL,
    431    url2: &MozURL,
    432    result: &mut *const MozURL,
    433 ) -> nsresult {
    434    *result = ptr::null();
    435    if url1.url == url2.url {
    436        RefPtr::new(url1).forget(result);
    437        return NS_OK;
    438    }
    439 
    440    if url1.scheme() != url2.scheme()
    441        || url1.host() != url2.host()
    442        || url1.username() != url2.username()
    443        || url1.password() != url2.password()
    444        || url1.port() != url2.port()
    445    {
    446        return NS_OK;
    447    }
    448 
    449    match (url1.path_segments(), url2.path_segments()) {
    450        (Some(path1), Some(path2)) => {
    451            // Take the shared prefix of path segments
    452            let mut url = url1.url.clone();
    453            if let Ok(mut segs) = url.path_segments_mut() {
    454                segs.clear();
    455                segs.extend(path1.zip(path2).take_while(|&(a, b)| a == b).map(|p| p.0));
    456            } else {
    457                return NS_OK;
    458            }
    459 
    460            MozURL::from_url(url).forget(result);
    461            NS_OK
    462        }
    463        _ => NS_OK,
    464    }
    465 }
    466 
    467 #[no_mangle]
    468 pub extern "C" fn mozurl_relative(
    469    url1: &MozURL,
    470    url2: &MozURL,
    471    result: &mut nsACString,
    472 ) -> nsresult {
    473    if url1.url == url2.url {
    474        result.truncate();
    475        return NS_OK;
    476    }
    477 
    478    if url1.scheme() != url2.scheme()
    479        || url1.host() != url2.host()
    480        || url1.username() != url2.username()
    481        || url1.password() != url2.password()
    482        || url1.port() != url2.port()
    483    {
    484        result.assign(url2.as_ref());
    485        return NS_OK;
    486    }
    487 
    488    match (url1.path_segments(), url2.path_segments()) {
    489        (Some(mut path1), Some(mut path2)) => {
    490            // Exhaust the part of the iterators that match
    491            while let (Some(p1), Some(p2)) = (&path1.next(), &path2.next()) {
    492                if p1 != p2 {
    493                    break;
    494                }
    495            }
    496 
    497            result.truncate();
    498            for _ in path1 {
    499                result.append("../");
    500            }
    501            for p2 in path2 {
    502                result.append(p2);
    503                result.append("/");
    504            }
    505        }
    506        _ => {
    507            result.assign(url2.as_ref());
    508        }
    509    }
    510    NS_OK
    511 }
    512 
    513 /// This type is used by nsStandardURL
    514 #[no_mangle]
    515 pub extern "C" fn rusturl_parse_ipv6addr(input: &nsACString, addr: &mut nsACString) -> nsresult {
    516    let ip6 = try_or_malformed!(str::from_utf8(input));
    517    let host = try_or_malformed!(url::Host::parse(ip6));
    518    _ = write!(addr, "{host}");
    519    NS_OK
    520 }