autofill/db/
mod.rs

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
5pub mod addresses;
6pub mod credit_cards;
7pub mod models;
8pub mod passports;
9pub mod schema;
10pub mod store;
11
12use crate::error::*;
13
14use error_support::error;
15use interrupt_support::{SqlInterruptHandle, SqlInterruptScope};
16use rusqlite::{Connection, OpenFlags};
17use sql_support::open_database;
18use std::sync::Arc;
19use std::{
20    ops::{Deref, DerefMut},
21    path::{Path, PathBuf},
22};
23use url::Url;
24
25pub struct AutofillDb {
26    pub writer: Connection,
27    interrupt_handle: Arc<SqlInterruptHandle>,
28}
29
30impl AutofillDb {
31    pub fn new(db_path: impl AsRef<Path>) -> Result<Self> {
32        let db_path = normalize_path(db_path)?;
33        Self::new_named(db_path)
34    }
35
36    pub fn new_memory(db_path: &str) -> Result<Self> {
37        let name = PathBuf::from(format!("file:{}?mode=memory&cache=shared", db_path));
38        Self::new_named(name)
39    }
40
41    fn new_named(db_path: PathBuf) -> Result<Self> {
42        // We always create the read-write connection for an initial open so
43        // we can create the schema and/or do version upgrades.
44        let flags = OpenFlags::SQLITE_OPEN_NO_MUTEX
45            | OpenFlags::SQLITE_OPEN_URI
46            | OpenFlags::SQLITE_OPEN_CREATE
47            | OpenFlags::SQLITE_OPEN_READ_WRITE;
48
49        let conn = open_database::open_database_with_flags(
50            db_path,
51            flags,
52            &schema::AutofillConnectionInitializer,
53        )?;
54
55        Ok(Self {
56            interrupt_handle: Arc::new(SqlInterruptHandle::new(&conn)),
57            writer: conn,
58        })
59    }
60
61    #[inline]
62    pub fn begin_interrupt_scope(&self) -> Result<SqlInterruptScope> {
63        Ok(self.interrupt_handle.begin_interrupt_scope()?)
64    }
65
66    pub fn close(self) {
67        if let Err((_, err)) = self.writer.close() {
68            // Log the error, but continue with shutdown.
69            error!("Failed to close the connection: {:?}", err);
70        }
71    }
72}
73
74impl Deref for AutofillDb {
75    type Target = Connection;
76
77    fn deref(&self) -> &Self::Target {
78        &self.writer
79    }
80}
81
82impl DerefMut for AutofillDb {
83    fn deref_mut(&mut self) -> &mut Self::Target {
84        &mut self.writer
85    }
86}
87
88fn unurl_path(p: impl AsRef<Path>) -> PathBuf {
89    p.as_ref()
90        .to_str()
91        .and_then(|s| Url::parse(s).ok())
92        .and_then(|u| {
93            if u.scheme() == "file" {
94                u.to_file_path().ok()
95            } else {
96                None
97            }
98        })
99        .unwrap_or_else(|| p.as_ref().to_owned())
100}
101
102fn normalize_path(p: impl AsRef<Path>) -> Result<PathBuf> {
103    let path = unurl_path(p);
104    if let Ok(canonical) = path.canonicalize() {
105        return Ok(canonical);
106    }
107    // It probably doesn't exist yet. This is an error, although it seems to
108    // work on some systems.
109    //
110    // We resolve this by trying to canonicalize the parent directory, and
111    // appending the requested file name onto that. If we can't canonicalize
112    // the parent, we return an error.
113    //
114    // Also, we return errors if the path ends in "..", if there is no
115    // parent directory, etc.
116    let file_name = path
117        .file_name()
118        .ok_or_else(|| Error::IllegalDatabasePath(path.clone()))?;
119
120    let parent = path
121        .parent()
122        .ok_or_else(|| Error::IllegalDatabasePath(path.clone()))?;
123
124    let mut canonical = parent.canonicalize()?;
125    canonical.push(file_name);
126    Ok(canonical)
127}
128
129pub(crate) mod sql_fns {
130    use rusqlite::{functions::Context, Result};
131    use sync_guid::Guid as SyncGuid;
132    use types::Timestamp;
133
134    #[inline(never)]
135    #[allow(dead_code)]
136    pub fn generate_guid(_ctx: &Context<'_>) -> Result<SyncGuid> {
137        Ok(SyncGuid::random())
138    }
139
140    #[inline(never)]
141    pub fn now(_ctx: &Context<'_>) -> Result<Timestamp> {
142        Ok(Timestamp::now())
143    }
144}
145
146// Helpers for tests
147#[cfg(test)]
148pub mod test {
149    use super::*;
150    use std::sync::atomic::{AtomicUsize, Ordering};
151
152    // A helper for our tests to get their own memory Api.
153    static ATOMIC_COUNTER: AtomicUsize = AtomicUsize::new(0);
154
155    pub fn new_mem_db() -> AutofillDb {
156        error_support::init_for_tests();
157        let counter = ATOMIC_COUNTER.fetch_add(1, Ordering::Relaxed);
158        AutofillDb::new_memory(&format!("test_autofill-api-{}", counter))
159            .expect("should get an API")
160    }
161}