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