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/. */
45use crate::db::PlacesDb;
6use crate::error::*;
7use rusqlite::{named_params, Connection};
8use serde::Serialize;
9use sql_support::ConnExt;
10use types::Timestamp;
11use url::Url;
1213// sanitize_timestamp can't use `Timestamp::now();` directly because it needs
14// to sanitize both created and modified, plus ensure modified isn't before
15// created - which isn't possible with the non-monotonic timestamp.
16// So we have a static `NOW`, which will be initialized the first time it is
17// referenced, and that value subsequently used for every imported bookmark (and
18// note that it's only used in cases where the existing timestamps are invalid.)
19// This is fine for our use-case, where we do exactly one import as soon as the
20// process starts.
21lazy_static::lazy_static! {
22pub static ref NOW: Timestamp = Timestamp::now();
23}
2425pub mod sql_fns {
26use crate::import::common::NOW;
27use crate::storage::URL_LENGTH_MAX;
28use rusqlite::{functions::Context, types::ValueRef, Result};
29use types::Timestamp;
30use url::Url;
3132fn sanitize_timestamp(ts: i64) -> Result<Timestamp> {
33let now = *NOW;
34let is_sane = |ts: Timestamp| -> bool { Timestamp::EARLIEST <= ts && ts <= now };
35let ts = Timestamp(u64::try_from(ts).unwrap_or(0));
36if is_sane(ts) {
37return Ok(ts);
38 }
39// Maybe the timestamp was actually in μs?
40let ts = Timestamp(ts.as_millis() / 1000);
41if is_sane(ts) {
42return Ok(ts);
43 }
44Ok(now)
45 }
4647// Unfortunately dates for history visits in old iOS databases
48 // have a type of `REAL` in their schema. This means they are represented
49 // as a float value and have to be read as f64s.
50 // This is unconventional, and you probably don't need to use
51 // this function otherwise.
52#[inline(never)]
53pub fn sanitize_float_timestamp(ctx: &Context<'_>) -> Result<Timestamp> {
54let ts = ctx
55 .get::<f64>(0)
56 .map(|num| {
57if num.is_normal() && num > 0.0 {
58 num.round() as i64
59 } else {
600
61}
62 })
63 .unwrap_or(0);
64 sanitize_timestamp(ts)
65 }
6667#[inline(never)]
68pub fn sanitize_integer_timestamp(ctx: &Context<'_>) -> Result<Timestamp> {
69 sanitize_timestamp(ctx.get::<i64>(0).unwrap_or(0))
70 }
7172// Possibly better named as "normalize URL" - even in non-error cases, the
73 // result string may not be the same href used passed as input.
74#[inline(never)]
75pub fn validate_url(ctx: &Context<'_>) -> Result<Option<String>> {
76let val = ctx.get_raw(0);
77let href = if let ValueRef::Text(s) = val {
78 String::from_utf8_lossy(s).to_string()
79 } else {
80return Ok(None);
81 };
82if href.len() > URL_LENGTH_MAX {
83return Ok(None);
84 }
85if let Ok(url) = Url::parse(&href) {
86Ok(Some(url.into()))
87 } else {
88Ok(None)
89 }
90 }
9192// Sanitize a text column into valid utf-8. Leave NULLs alone, but all other
93 // types are converted to an empty string.
94#[inline(never)]
95pub fn sanitize_utf8(ctx: &Context<'_>) -> Result<Option<String>> {
96let val = ctx.get_raw(0);
97Ok(match val {
98 ValueRef::Text(s) => Some(String::from_utf8_lossy(s).to_string()),
99 ValueRef::Null => None,
100_ => Some("".to_owned()),
101 })
102 }
103}
104105pub fn attached_database<'a>(
106 conn: &'a PlacesDb,
107 path: &Url,
108 db_alias: &'static str,
109) -> Result<ExecuteOnDrop<'a>> {
110 conn.execute(
111"ATTACH DATABASE :path AS :db_alias",
112named_params! {
113":path": path.as_str(),
114":db_alias": db_alias,
115 },
116 )?;
117Ok(ExecuteOnDrop {
118 conn,
119 sql: format!("DETACH DATABASE {};", db_alias),
120 })
121}
122123/// We use/abuse the mirror to perform our import, but need to clean it up
124/// afterwards. This is an RAII helper to do so.
125///
126/// Ideally, you should call `execute_now` rather than letting this drop
127/// automatically, as we can't report errors beyond logging when running
128/// Drop.
129pub struct ExecuteOnDrop<'a> {
130 conn: &'a PlacesDb,
131 sql: String,
132}
133134impl<'a> ExecuteOnDrop<'a> {
135pub fn new(conn: &'a PlacesDb, sql: String) -> Self {
136Self { conn, sql }
137 }
138139pub fn execute_now(self) -> Result<()> {
140self.conn.execute_batch(&self.sql)?;
141// Don't run our `drop` function.
142std::mem::forget(self);
143Ok(())
144 }
145}
146147impl Drop for ExecuteOnDrop<'_> {
148fn drop(&mut self) {
149if let Err(e) = self.conn.execute_batch(&self.sql) {
150error_support::report_error!(
151"places-cleanup-failure",
152"Failed to clean up after import! {}",
153 e
154 );
155debug!(" Failed query: {}", &self.sql);
156 }
157 }
158}
159160pub fn select_count(conn: &PlacesDb, stmt: &str) -> Result<u32> {
161let count: Result<Option<u32>> =
162 conn.try_query_row(stmt, [], |row| Ok(row.get::<_, u32>(0)?), false);
163 count.map(|op| op.unwrap_or(0))
164}
165166#[derive(Serialize, PartialEq, Eq, Debug, Clone, Default)]
167pub struct HistoryMigrationResult {
168pub num_total: u32,
169pub num_succeeded: u32,
170pub num_failed: u32,
171pub total_duration: u64,
172}
173174pub fn define_history_migration_functions(c: &Connection) -> Result<()> {
175use rusqlite::functions::FunctionFlags;
176 c.create_scalar_function(
177"validate_url",
1781,
179 FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
180crate::import::common::sql_fns::validate_url,
181 )?;
182 c.create_scalar_function(
183"sanitize_timestamp",
1841,
185 FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
186crate::import::common::sql_fns::sanitize_integer_timestamp,
187 )?;
188 c.create_scalar_function(
189"hash",
190 -1,
191 FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
192crate::db::db::sql_fns::hash,
193 )?;
194 c.create_scalar_function(
195"generate_guid",
1960,
197 FunctionFlags::SQLITE_UTF8,
198crate::db::db::sql_fns::generate_guid,
199 )?;
200 c.create_scalar_function(
201"sanitize_utf8",
2021,
203 FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
204crate::import::common::sql_fns::sanitize_utf8,
205 )?;
206 c.create_scalar_function(
207"sanitize_float_timestamp",
2081,
209 FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
210crate::import::common::sql_fns::sanitize_float_timestamp,
211 )?;
212Ok(())
213}