tor-browser

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

lib.rs (18107B)


      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 //! A Rust wrapper for mozStorage.
      6 //!
      7 //! mozStorage wraps the SQLite C API with support for XPCOM data structures,
      8 //! asynchronous statement execution, cleanup on shutdown, and connection
      9 //! cloning that propagates attached databases, pragmas, functions, and
     10 //! temporary entities. It also collects timing and memory usage stats for
     11 //! telemetry, and supports detailed statement logging. Additionally, mozStorage
     12 //! makes it possible to use the same connection handle from JS and native
     13 //! (C++ and Rust) code.
     14 //!
     15 //! Most mozStorage objects, like connections, statements, result rows,
     16 //! and variants, are thread-safe. Each connection manages a background
     17 //! thread that can be used to execute statements asynchronously, without
     18 //! blocking the main thread.
     19 //!
     20 //! This crate provides a thin wrapper to make mozStorage easier to use
     21 //! from Rust. It only wraps the synchronous API, so you can either manage
     22 //! the entire connection from a background thread, or use the `moz_task`
     23 //! crate to dispatch tasks to the connection's async thread. Executing
     24 //! synchronous statements on the main thread is not supported, and will
     25 //! assert in debug builds.
     26 
     27 #![allow(non_snake_case)]
     28 #![allow(unknown_lints, mismatched_lifetime_syntaxes)]
     29 
     30 use std::{borrow::Cow, convert::TryFrom, error, fmt, ops::Deref, result};
     31 
     32 use nserror::{nsresult, NS_ERROR_NO_INTERFACE, NS_ERROR_UNEXPECTED};
     33 use nsstring::nsCString;
     34 use storage_variant::VariantType;
     35 use xpcom::{
     36    getter_addrefs,
     37    interfaces::{
     38        mozIStorageAsyncConnection, mozIStorageConnection, mozIStorageStatement, nsIEventTarget,
     39        nsIThread,
     40    },
     41    RefPtr, XpCom,
     42 };
     43 
     44 const SQLITE_OK: i32 = 0;
     45 
     46 pub type Result<T> = result::Result<T, Error>;
     47 
     48 /// `Conn` wraps a `mozIStorageConnection`.
     49 #[derive(Clone)]
     50 pub struct Conn {
     51    handle: RefPtr<mozIStorageConnection>,
     52 }
     53 
     54 // This is safe as long as our `mozIStorageConnection` is an instance of
     55 // `mozilla::storage::Connection`, which is atomically reference counted.
     56 unsafe impl Send for Conn {}
     57 unsafe impl Sync for Conn {}
     58 
     59 impl Conn {
     60    /// Wraps a `mozIStorageConnection` in a `Conn`.
     61    #[inline]
     62    pub fn wrap(connection: RefPtr<mozIStorageConnection>) -> Conn {
     63        Conn { handle: connection }
     64    }
     65 
     66    /// Returns the wrapped `mozIStorageConnection`.
     67    #[inline]
     68    pub fn connection(&self) -> &mozIStorageConnection {
     69        &self.handle
     70    }
     71 
     72    /// Returns the maximum number of bound parameters for statements executed
     73    /// on this connection.
     74    pub fn variable_limit(&self) -> Result<usize> {
     75        let mut limit = 0i32;
     76        let rv = unsafe { self.handle.GetVariableLimit(&mut limit) };
     77        if rv.failed() {
     78            return Err(Error::Limit);
     79        }
     80        usize::try_from(limit).map_err(|_| Error::Limit)
     81    }
     82 
     83    /// Returns the async thread for this connection. This can be used
     84    /// with `moz_task` to run synchronous statements on the storage
     85    /// thread, without blocking the main thread.
     86    pub fn thread(&self) -> Result<RefPtr<nsIThread>> {
     87        let target = self.handle.get_interface::<nsIEventTarget>();
     88        target
     89            .and_then(|t| t.query_interface::<nsIThread>())
     90            .ok_or(Error::NoThread)
     91    }
     92 
     93    /// Prepares a SQL statement. `query` should only contain one SQL statement.
     94    /// If `query` contains multiple statements, only the first will be prepared,
     95    /// and the rest will be ignored.
     96    pub fn prepare<Q: AsRef<str>>(&self, query: Q) -> Result<Statement> {
     97        let statement = self.call_and_wrap_error(DatabaseOp::Prepare, || {
     98            getter_addrefs(|p| unsafe {
     99                self.handle
    100                    .CreateStatement(&*nsCString::from(query.as_ref()), p)
    101            })
    102        })?;
    103        Ok(Statement {
    104            conn: self,
    105            handle: statement,
    106        })
    107    }
    108 
    109    /// Executes a SQL statement. `query` may contain one or more
    110    /// semicolon-separated SQL statements.
    111    pub fn exec<Q: AsRef<str>>(&self, query: Q) -> Result<()> {
    112        self.call_and_wrap_error(DatabaseOp::Exec, || {
    113            unsafe {
    114                self.handle
    115                    .ExecuteSimpleSQL(&*nsCString::from(query.as_ref()))
    116            }
    117            .to_result()
    118        })
    119    }
    120 
    121    /// Opens a transaction with the default transaction behavior for this
    122    /// connection. The transaction should be committed when done. Uncommitted
    123    /// `Transaction`s will automatically roll back when they go out of scope.
    124    pub fn transaction(&mut self) -> Result<Transaction> {
    125        let behavior = self.get_default_transaction_behavior();
    126        Transaction::new(self, behavior)
    127    }
    128 
    129    /// Indicates if a transaction is currently open on this connection.
    130    /// Attempting to open a new transaction when one is already in progress
    131    /// will fail with a "cannot start a transaction within a transaction"
    132    /// error.
    133    ///
    134    /// Note that this is `true` even if the transaction was started by another
    135    /// caller, like `Sqlite.sys.mjs` or `mozStorageTransaction` from C++. See the
    136    /// explanation above `mozIStorageConnection.transactionInProgress` for why
    137    /// this matters.
    138    pub fn transaction_in_progress(&self) -> Result<bool> {
    139        let mut in_progress = false;
    140        unsafe { self.handle.GetTransactionInProgress(&mut in_progress) }.to_result()?;
    141        Ok(in_progress)
    142    }
    143 
    144    /// Opens a transaction with the requested behavior.
    145    pub fn transaction_with_behavior(
    146        &mut self,
    147        behavior: TransactionBehavior,
    148    ) -> Result<Transaction> {
    149        Transaction::new(self, behavior)
    150    }
    151 
    152    fn get_default_transaction_behavior(&self) -> TransactionBehavior {
    153        let mut typ = 0i32;
    154        let rv = unsafe { self.handle.GetDefaultTransactionType(&mut typ) };
    155        if rv.failed() {
    156            return TransactionBehavior::Deferred;
    157        }
    158        match typ {
    159            mozIStorageAsyncConnection::TRANSACTION_IMMEDIATE => TransactionBehavior::Immediate,
    160            mozIStorageAsyncConnection::TRANSACTION_EXCLUSIVE => TransactionBehavior::Exclusive,
    161            _ => TransactionBehavior::Deferred,
    162        }
    163    }
    164 
    165    /// Invokes a storage operation and returns the last SQLite error if the
    166    /// operation fails. This lets `Conn::{prepare, exec}` and
    167    /// `Statement::{step, execute}` return more detailed errors, as the
    168    /// `nsresult` codes that mozStorage uses are often too generic. For
    169    /// example, `NS_ERROR_FAILURE` might be anything from a SQL syntax error
    170    /// to an invalid column name in a trigger.
    171    ///
    172    /// Note that the last error may not be accurate if the underlying
    173    /// `mozIStorageConnection` is used concurrently from multiple threads.
    174    /// Multithreaded callers that share a connection should serialize their
    175    /// uses.
    176    fn call_and_wrap_error<T>(
    177        &self,
    178        op: DatabaseOp,
    179        func: impl FnOnce() -> result::Result<T, nsresult>,
    180    ) -> Result<T> {
    181        func().or_else(|rv| -> Result<T> {
    182            let mut code = 0i32;
    183            unsafe { self.handle.GetLastError(&mut code) }.to_result()?;
    184            Err(if code != SQLITE_OK {
    185                let mut message = nsCString::new();
    186                unsafe { self.handle.GetLastErrorString(&mut *message) }.to_result()?;
    187                Error::Database {
    188                    rv,
    189                    op,
    190                    code,
    191                    message,
    192                }
    193            } else {
    194                rv.into()
    195            })
    196        })
    197    }
    198 }
    199 
    200 pub enum TransactionBehavior {
    201    Deferred,
    202    Immediate,
    203    Exclusive,
    204 }
    205 
    206 pub struct Transaction<'c> {
    207    conn: &'c mut Conn,
    208    active: bool,
    209 }
    210 
    211 impl<'c> Transaction<'c> {
    212    /// Opens a transaction on `conn` with the given `behavior`.
    213    fn new(conn: &'c mut Conn, behavior: TransactionBehavior) -> Result<Transaction<'c>> {
    214        conn.exec(match behavior {
    215            TransactionBehavior::Deferred => "BEGIN DEFERRED",
    216            TransactionBehavior::Immediate => "BEGIN IMMEDIATE",
    217            TransactionBehavior::Exclusive => "BEGIN EXCLUSIVE",
    218        })?;
    219        Ok(Transaction { conn, active: true })
    220    }
    221 
    222    /// Commits the transaction.
    223    pub fn commit(mut self) -> Result<()> {
    224        if self.active {
    225            self.conn.exec("COMMIT")?;
    226            self.active = false;
    227        }
    228        Ok(())
    229    }
    230 
    231    /// Rolls the transaction back.
    232    pub fn rollback(mut self) -> Result<()> {
    233        self.abort()
    234    }
    235 
    236    fn abort(&mut self) -> Result<()> {
    237        if self.active {
    238            self.conn.exec("ROLLBACK")?;
    239            self.active = false;
    240        }
    241        Ok(())
    242    }
    243 }
    244 
    245 impl<'c> Deref for Transaction<'c> {
    246    type Target = Conn;
    247 
    248    fn deref(&self) -> &Conn {
    249        self.conn
    250    }
    251 }
    252 
    253 impl<'c> Drop for Transaction<'c> {
    254    fn drop(&mut self) {
    255        let _ = self.abort();
    256    }
    257 }
    258 
    259 pub struct Statement<'c> {
    260    conn: &'c Conn,
    261    handle: RefPtr<mozIStorageStatement>,
    262 }
    263 
    264 impl<'c> Statement<'c> {
    265    /// Binds a parameter at the given `index` to the prepared statement.
    266    /// `value` is any type that can be converted into a `Variant`.
    267    pub fn bind_by_index<V: VariantType>(&mut self, index: u32, value: V) -> Result<()> {
    268        let variant = value.into_variant();
    269        unsafe { self.handle.BindByIndex(index as u32, variant.coerce()) }
    270            .to_result()
    271            .map_err(|rv| Error::BindByIndex {
    272                rv,
    273                data_type: V::type_name(),
    274                index,
    275            })
    276    }
    277 
    278    /// Binds a parameter with the given `name` to the prepared statement.
    279    pub fn bind_by_name<N: AsRef<str>, V: VariantType>(&mut self, name: N, value: V) -> Result<()> {
    280        let name = name.as_ref();
    281        let variant = value.into_variant();
    282        unsafe {
    283            self.handle
    284                .BindByName(&*nsCString::from(name), variant.coerce())
    285        }
    286        .to_result()
    287        .map_err(|rv| Error::BindByName {
    288            rv,
    289            data_type: V::type_name(),
    290            name: name.into(),
    291        })
    292    }
    293 
    294    /// Executes the statement and returns the next row of data.
    295    pub fn step<'s>(&'s mut self) -> Result<Option<Step<'c, 's>>> {
    296        let has_more = self.conn.call_and_wrap_error(DatabaseOp::Step, || {
    297            let mut has_more = false;
    298            unsafe { self.handle.ExecuteStep(&mut has_more) }.to_result()?;
    299            Ok(has_more)
    300        })?;
    301        Ok(if has_more { Some(Step(self)) } else { None })
    302    }
    303 
    304    /// Executes the statement once, discards any data, and resets the
    305    /// statement.
    306    pub fn execute(&mut self) -> Result<()> {
    307        self.conn.call_and_wrap_error(DatabaseOp::Execute, || {
    308            unsafe { self.handle.Execute() }.to_result()
    309        })
    310    }
    311 
    312    /// Resets the prepared statement so that it's ready to be executed
    313    /// again, and clears any bound parameters.
    314    pub fn reset(&mut self) -> Result<()> {
    315        unsafe { self.handle.Reset() }.to_result()?;
    316        Ok(())
    317    }
    318 
    319    fn get_column_index(&self, name: &str) -> Result<u32> {
    320        let mut index = 0u32;
    321        let rv = unsafe {
    322            self.handle
    323                .GetColumnIndex(&*nsCString::from(name), &mut index)
    324        };
    325        if rv.succeeded() {
    326            Ok(index)
    327        } else {
    328            Err(Error::InvalidColumn {
    329                rv,
    330                name: name.into(),
    331            })
    332        }
    333    }
    334 
    335    fn get_column_value<T: VariantType>(&self, index: u32) -> result::Result<T, nsresult> {
    336        let variant = getter_addrefs(|p| unsafe { self.handle.GetVariant(index, p) })?;
    337        let value = T::from_variant(variant.coerce())?;
    338        Ok(value)
    339    }
    340 }
    341 
    342 impl<'c> Drop for Statement<'c> {
    343    fn drop(&mut self) {
    344        unsafe { self.handle.Finalize() };
    345    }
    346 }
    347 
    348 /// A step is the next row in the result set for a statement.
    349 pub struct Step<'c, 's>(&'s mut Statement<'c>);
    350 
    351 impl<'c, 's> Step<'c, 's> {
    352    /// Returns the value of the column at `index` for the current row.
    353    pub fn get_by_index<T: VariantType>(&self, index: u32) -> Result<T> {
    354        self.0
    355            .get_column_value(index)
    356            .map_err(|rv| Error::GetByIndex {
    357                rv,
    358                data_type: T::type_name(),
    359                index,
    360            })
    361    }
    362 
    363    /// A convenience wrapper that returns the default value for the column
    364    /// at `index` if `NULL`.
    365    pub fn get_by_index_or_default<T: VariantType + Default>(&self, index: u32) -> T {
    366        self.get_by_index(index).unwrap_or_default()
    367    }
    368 
    369    /// Returns the value of the column specified by `name` for the current row.
    370    pub fn get_by_name<N: AsRef<str>, T: VariantType>(&self, name: N) -> Result<T> {
    371        let name = name.as_ref();
    372        let index = self.0.get_column_index(name)?;
    373        self.0
    374            .get_column_value(index)
    375            .map_err(|rv| Error::GetByName {
    376                rv,
    377                data_type: T::type_name(),
    378                name: name.into(),
    379            })
    380    }
    381 
    382    /// Returns the default value for the column with the given `name`, or the
    383    /// default if the column is `NULL`.
    384    pub fn get_by_name_or_default<N: AsRef<str>, T: VariantType + Default>(&self, name: N) -> T {
    385        self.get_by_name(name).unwrap_or_default()
    386    }
    387 }
    388 
    389 /// A database operation, included for better context in error messages.
    390 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
    391 pub enum DatabaseOp {
    392    Exec,
    393    Prepare,
    394    Step,
    395    Execute,
    396 }
    397 
    398 impl DatabaseOp {
    399    /// Returns a description of the operation to include in an error message.
    400    pub fn what(&self) -> &'static str {
    401        match self {
    402            DatabaseOp::Exec => "execute SQL string",
    403            DatabaseOp::Prepare => "prepare statement",
    404            DatabaseOp::Step => "step statement",
    405            DatabaseOp::Execute => "execute statement",
    406        }
    407    }
    408 }
    409 
    410 /// Storage errors.
    411 #[derive(Debug)]
    412 pub enum Error {
    413    /// A connection doesn't have a usable async thread. The connection might be
    414    /// closed, or the thread manager may have shut down.
    415    NoThread,
    416 
    417    /// Failed to get a limit for a database connection.
    418    Limit,
    419 
    420    /// A database operation failed. The error includes a SQLite result code,
    421    /// and an explanation string.
    422    Database {
    423        rv: nsresult,
    424        op: DatabaseOp,
    425        code: i32,
    426        message: nsCString,
    427    },
    428 
    429    /// A parameter with the given data type couldn't be bound at this index,
    430    /// likely because the index is out of range.
    431    BindByIndex {
    432        rv: nsresult,
    433        data_type: Cow<'static, str>,
    434        index: u32,
    435    },
    436 
    437    /// A parameter with the given type couldn't be bound to this name, likely
    438    /// because the statement doesn't have a matching `:`-prefixed parameter
    439    /// with the name.
    440    BindByName {
    441        rv: nsresult,
    442        data_type: Cow<'static, str>,
    443        name: String,
    444    },
    445 
    446    /// A column with this name doesn't exist.
    447    InvalidColumn { rv: nsresult, name: String },
    448 
    449    /// A value of the given type couldn't be accessed at this index. This is
    450    /// the error returned when a type conversion fails; for example, requesting
    451    /// an `nsString` instead of an `Option<nsString>` when the column is `NULL`.
    452    GetByIndex {
    453        rv: nsresult,
    454        data_type: Cow<'static, str>,
    455        index: u32,
    456    },
    457 
    458    /// A value of the given type couldn't be accessed for the column with
    459    /// this name.
    460    GetByName {
    461        rv: nsresult,
    462        data_type: Cow<'static, str>,
    463        name: String,
    464    },
    465 
    466    /// A storage operation failed for other reasons.
    467    Other(nsresult),
    468 }
    469 
    470 impl error::Error for Error {
    471    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
    472        None
    473    }
    474 }
    475 
    476 impl From<nsresult> for Error {
    477    fn from(rv: nsresult) -> Error {
    478        Error::Other(rv)
    479    }
    480 }
    481 
    482 impl From<Error> for nsresult {
    483    fn from(err: Error) -> nsresult {
    484        match err {
    485            Error::NoThread => NS_ERROR_NO_INTERFACE,
    486            Error::Limit => NS_ERROR_UNEXPECTED,
    487            Error::Database { rv, .. }
    488            | Error::BindByIndex { rv, .. }
    489            | Error::BindByName { rv, .. }
    490            | Error::InvalidColumn { rv, .. }
    491            | Error::GetByIndex { rv, .. }
    492            | Error::GetByName { rv, .. }
    493            | Error::Other(rv) => rv,
    494        }
    495    }
    496 }
    497 
    498 impl fmt::Display for Error {
    499    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    500        match self {
    501            Error::NoThread => f.write_str("Async thread unavailable for storage connection"),
    502            Error::Limit => f.write_str("Failed to get limit for storage connection"),
    503            Error::Database {
    504                op, code, message, ..
    505            } => {
    506                if message.is_empty() {
    507                    write!(f, "Failed to {} with code {}", op.what(), code)
    508                } else {
    509                    write!(
    510                        f,
    511                        "Failed to {} with code {} ({})",
    512                        op.what(),
    513                        code,
    514                        message
    515                    )
    516                }
    517            }
    518            Error::BindByIndex {
    519                data_type, index, ..
    520            } => write!(f, "Can't bind {} at {}", data_type, index),
    521            Error::BindByName {
    522                data_type, name, ..
    523            } => write!(f, "Can't bind {} to named parameter {}", data_type, name),
    524            Error::InvalidColumn { name, .. } => write!(f, "Column {} doesn't exist", name),
    525            Error::GetByIndex {
    526                data_type, index, ..
    527            } => write!(f, "Can't get {} at {}", data_type, index),
    528            Error::GetByName {
    529                data_type, name, ..
    530            } => write!(f, "Can't get {} for column {}", data_type, name),
    531            Error::Other(rv) => write!(f, "Storage operation failed with {}", rv.error_name()),
    532        }
    533    }
    534 }