tor-browser

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

lib.rs (11674B)


      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 error_support::{info, warn};
      6 use futures_channel::oneshot;
      7 use std::{ffi::{CStr, c_char}, ptr, slice, sync::Arc};
      8 use url::Url;
      9 use viaduct::{
     10    init_backend, Backend, ClientSettings, Method, Request, Response, Result, ViaductError,
     11 };
     12 
     13 const NULL: char = '\0';
     14 
     15 /// Request for the C++ backend
     16 #[repr(C)]
     17 pub struct FfiRequest {
     18    pub timeout: u32,
     19    pub redirect_limit: u32,
     20    pub method: Method,
     21    pub url: *mut u8,
     22    pub headers: *mut FfiHeader,
     23    pub header_count: usize,
     24    pub body: *mut u8,
     25    pub body_len: usize,
     26 }
     27 
     28 #[repr(C)]
     29 pub struct FfiHeader {
     30    pub key: *mut u8,
     31    pub value: *mut u8,
     32 }
     33 
     34 /// Result from the backend
     35 ///
     36 /// This is built-up piece by piece using the extern "C" API.
     37 pub struct FfiResult {
     38    // oneshot sender that the Rust code is awaiting.  If `Ok(())` is sent, then the Rust code
     39    // should return the response.  If an error is sent, then that should be returned instead.
     40    sender: Option<oneshot::Sender<Result<Response>>>,
     41    response: Response,
     42    // Owned values stored in the [FfiRequest].  These are copied from the request.  By storing
     43    // them in the result, we ensure they stay alive while the C code may access them.
     44    pub url: String,
     45    pub headers: Vec<(String, String)>,
     46    pub body: Option<Vec<u8>>,
     47    // The request struct that we pass to C++.  This must be kept alive as long as the C++ code is
     48    // using it.
     49    pub request: FfiRequest,
     50    pub ffi_headers: Vec<FfiHeader>,
     51 }
     52 
     53 // Functions that the C++ library exports for us
     54 extern "C" {
     55    fn viaduct_necko_backend_init();
     56 
     57    #[allow(improper_ctypes)]
     58    fn viaduct_necko_backend_send_request(request: *const FfiRequest, result: *mut FfiResult);
     59 }
     60 
     61 // Functions that we provide to the C++ library
     62 
     63 /// Set the URL for a result
     64 ///
     65 /// # Safety
     66 ///
     67 /// - `result` must be valid.
     68 /// - `url` and `length` must refer to a valid byte string.
     69 ///
     70 /// Note: URLs are expected to be ASCII. Non-ASCII URLs will be logged and skipped.
     71 #[no_mangle]
     72 pub unsafe extern "C" fn viaduct_necko_result_set_url(
     73    result: *mut FfiResult,
     74    url: *const u8,
     75    length: usize,
     76 ) {
     77    let result = unsafe { &mut *result };
     78 
     79    // Safety: Creating a slice from raw parts is safe if the backend passes valid pointers and lengths
     80    let url_bytes = unsafe { slice::from_raw_parts(url, length) };
     81 
     82    // Validate that the URL is ASCII before converting to String
     83    if !url_bytes.is_ascii() {
     84        warn!(
     85            "Non-ASCII URL received - length: {} - skipping URL update",
     86            length
     87        );
     88        return;
     89    }
     90 
     91    // Safety: We just verified the bytes are ASCII, which is valid UTF-8
     92    let url_str = unsafe { std::str::from_utf8_unchecked(url_bytes) };
     93 
     94    match Url::parse(url_str) {
     95        Ok(url) => {
     96            result.response.url = url;
     97        }
     98        Err(e) => {
     99            warn!("Error parsing URL from C backend: {e}")
    100        }
    101    }
    102 }
    103 
    104 /// Set the status code for a result
    105 ///
    106 /// # Safety
    107 ///
    108 /// `result` must be valid.
    109 #[no_mangle]
    110 pub unsafe extern "C" fn viaduct_necko_result_set_status_code(result: *mut FfiResult, code: u16) {
    111    let result = unsafe { &mut *result };
    112    result.response.status = code;
    113 }
    114 
    115 /// Set a header for a result
    116 ///
    117 /// # Safety
    118 ///
    119 /// - `result` must be valid.
    120 /// - `key` and `key_length` must refer to a valid byte string.
    121 /// - `value` and `value_length` must refer to a valid byte string.
    122 ///
    123 /// Note: HTTP headers are expected to be ASCII. Non-ASCII headers will be logged and skipped.
    124 #[no_mangle]
    125 pub unsafe extern "C" fn viaduct_necko_result_add_header(
    126    result: *mut FfiResult,
    127    key: *const u8,
    128    key_length: usize,
    129    value: *const u8,
    130    value_length: usize,
    131 ) {
    132    let result = unsafe { &mut *result };
    133 
    134    // Safety: Creating slices from raw parts is safe if the backend passes valid pointers and lengths
    135    let key_bytes = unsafe { slice::from_raw_parts(key, key_length) };
    136    let value_bytes = unsafe { slice::from_raw_parts(value, value_length) };
    137 
    138    // Validate that headers are ASCII before converting to String
    139    // HTTP headers should be ASCII per best practices, though the spec technically allows other encodings
    140    if !key_bytes.is_ascii() || !value_bytes.is_ascii() {
    141        warn!(
    142            "Non-ASCII HTTP header received - key_len: {}, value_len: {} - skipping header",
    143            key_length, value_length
    144        );
    145        return;
    146    }
    147 
    148    // Safety: We just verified the bytes are ASCII, which is valid UTF-8
    149    let (key, value) = unsafe {
    150        (
    151            String::from_utf8_unchecked(key_bytes.to_vec()),
    152            String::from_utf8_unchecked(value_bytes.to_vec()),
    153        )
    154    };
    155 
    156    let _ = result.response.headers.insert(key, value);
    157 }
    158 
    159 /// Append data to a result body
    160 ///
    161 /// This method can be called multiple times to build up the body in chunks.
    162 ///
    163 /// # Safety
    164 ///
    165 /// - `result` must be valid.
    166 /// - `data` and `length` must refer to a binary string.
    167 #[no_mangle]
    168 pub unsafe extern "C" fn viaduct_necko_result_extend_body(
    169    result: *mut FfiResult,
    170    data: *const u8,
    171    length: usize,
    172 ) {
    173    let result = unsafe { &mut *result };
    174    // Safety: this is safe as long as the backend passes us valid data
    175    result
    176        .response
    177        .body
    178        .extend_from_slice(unsafe { slice::from_raw_parts(data, length) });
    179 }
    180 
    181 /// Complete a result
    182 ///
    183 /// # Safety
    184 ///
    185 /// `result` must be valid.  After calling this function it must not be used again.
    186 #[no_mangle]
    187 pub unsafe extern "C" fn viaduct_necko_result_complete(result: *mut FfiResult) {
    188    let mut result = unsafe { Box::from_raw(result) };
    189    match result.sender.take() {
    190        Some(sender) => {
    191            // Ignore any errors when sending the result.  This happens when the receiver is
    192            // closed, which happens when a future is cancelled.
    193            let _ = sender.send(Ok(result.response));
    194        }
    195        None => warn!("viaduct-necko: result completed twice"),
    196    }
    197 }
    198 
    199 /// Complete a result with an error message
    200 ///
    201 /// # Safety
    202 ///
    203 /// - `result` must be valid.  After calling this function it must not be used again.
    204 /// - `message` and `length` must refer to a valid UTF-8 string.
    205 #[no_mangle]
    206 pub unsafe extern "C" fn viaduct_necko_result_complete_error(
    207    result: *mut FfiResult,
    208    error_code: u32,
    209    message: *const u8,
    210 ) {
    211    let mut result = unsafe { Box::from_raw(result) };
    212    // Safety: this is safe as long as the backend passes us valid data
    213    let msg_str = unsafe {
    214        CStr::from_ptr(message as *const c_char)
    215            .to_string_lossy()
    216            .into_owned()
    217    };
    218    let msg = format!("{} (0x{:08x})", msg_str, error_code);
    219    match result.sender.take() {
    220        Some(sender) => {
    221            // Ignore any errors when sending the result.  This happens when the receiver is
    222            // closed, which happens when a future is cancelled.
    223            let _ = sender.send(Err(ViaductError::BackendError(msg)));
    224        }
    225        None => warn!("viaduct-necko: result completed twice"),
    226    }
    227 }
    228 
    229 // The Necko backend is a zero-sized type, since all the backend functionality is statically linked
    230 struct NeckoBackend;
    231 
    232 /// Initialize the Necko backend
    233 ///
    234 /// This should be called once at startup before any HTTP requests are made.
    235 pub fn init_necko_backend() -> Result<()> {
    236    info!("Initializing viaduct Necko backend");
    237    // Safety: this is safe as long as the C++ code is correct.
    238    unsafe { viaduct_necko_backend_init() };
    239    init_backend(Arc::new(NeckoBackend))
    240 }
    241 
    242 #[async_trait::async_trait]
    243 impl Backend for NeckoBackend {
    244    async fn send_request(&self, request: Request, settings: ClientSettings) -> Result<Response> {
    245        // Convert the request for the backend
    246        let mut url = request.url.to_string();
    247        url.push(NULL);
    248 
    249        // Convert headers to null-terminated strings for C++
    250        // Note: Headers iterates over Header objects, not tuples
    251        let header_strings: Vec<(String, String)> = request
    252            .headers
    253            .iter()
    254            .map(|h| {
    255                let mut key_str = h.name().to_string();
    256                key_str.push(NULL);
    257                let mut value_str = h.value().to_string();
    258                value_str.push(NULL);
    259                (key_str, value_str)
    260            })
    261            .collect();
    262 
    263        // Prepare an FfiResult with an empty response
    264        let (sender, receiver) = oneshot::channel();
    265        let mut result = Box::new(FfiResult {
    266            sender: Some(sender),
    267            response: Response {
    268                request_method: request.method,
    269                url: request.url.clone(),
    270                status: 0,
    271                headers: viaduct::Headers::new(),
    272                body: Vec::default(),
    273            },
    274            url,
    275            headers: header_strings,
    276            body: request.body,
    277            request: FfiRequest {
    278                timeout: settings.timeout,
    279                redirect_limit: settings.redirect_limit,
    280                method: request.method,
    281                url: ptr::null_mut(),
    282                headers: ptr::null_mut(),
    283                header_count: 0,
    284                body: ptr::null_mut(),
    285                body_len: 0,
    286            },
    287            ffi_headers: Vec::new(),
    288        });
    289 
    290        // Now that we have the result box, we can set up the pointers in the request.
    291        // By doing this after creating the box, we minimize the chance that a value moves after a pointer is created.
    292        result.ffi_headers = result
    293            .headers
    294            .iter_mut()
    295            .map(|(key, value)| FfiHeader {
    296                key: key.as_mut_ptr(),
    297                value: value.as_mut_ptr(),
    298            })
    299            .collect();
    300 
    301        let (body_ptr, body_len) = match &result.body {
    302            Some(body) => (body.as_ptr() as *mut u8, body.len()),
    303            None => (ptr::null_mut(), 0),
    304        };
    305 
    306        result.request.url = result.url.as_mut_ptr();
    307        result.request.headers = result.ffi_headers.as_mut_ptr();
    308        result.request.header_count = result.ffi_headers.len();
    309        result.request.body = body_ptr;
    310        result.request.body_len = body_len;
    311 
    312        let request_ptr = &result.request as *const FfiRequest;
    313 
    314        // Safety: this is safe if the C backend implements the API correctly.
    315        unsafe {
    316            viaduct_necko_backend_send_request(request_ptr, Box::into_raw(result));
    317        };
    318 
    319        receiver.await.unwrap_or_else(|_| {
    320            Err(ViaductError::BackendError(
    321                "Error receiving result from C++ backend".to_string(),
    322            ))
    323        })
    324    }
    325 }
    326 
    327 // Mark FFI types as Send to allow them to be used across an await point.  This is safe as long as
    328 // the backend code uses them correctly.
    329 unsafe impl Send for FfiRequest {}
    330 unsafe impl Send for FfiResult {}
    331 unsafe impl Send for FfiHeader {}
    332 
    333 #[cfg(test)]
    334 mod tests {
    335    use super::*;
    336 
    337    #[test]
    338    fn test_method_layout() {
    339        // Assert that the viaduct::Method enum matches the layout expected by the C++ backend.
    340        // See ViaductMethod in backend.h
    341        assert_eq!(Method::Get as u8, 0);
    342        assert_eq!(Method::Head as u8, 1);
    343        assert_eq!(Method::Post as u8, 2);
    344        assert_eq!(Method::Put as u8, 3);
    345        assert_eq!(Method::Delete as u8, 4);
    346        assert_eq!(Method::Connect as u8, 5);
    347        assert_eq!(Method::Options as u8, 6);
    348        assert_eq!(Method::Trace as u8, 7);
    349        assert_eq!(Method::Patch as u8, 8);
    350    }
    351 }