1use crate::db::PlacesDb;
6use crate::error::*;
7use rusqlite::{named_params, Connection};
8use serde::Serialize;
9use sql_support::ConnExt;
10use types::Timestamp;
11use url::Url;
12
13lazy_static::lazy_static! {
22 pub static ref NOW: Timestamp = Timestamp::now();
23}
24
25pub mod sql_fns {
26 use crate::import::common::NOW;
27 use crate::storage::URL_LENGTH_MAX;
28 use rusqlite::{functions::Context, types::ValueRef, Result};
29 use types::Timestamp;
30 use url::Url;
31
32 fn sanitize_timestamp(ts: i64) -> Result<Timestamp> {
33 let now = *NOW;
34 let is_sane = |ts: Timestamp| -> bool { Timestamp::EARLIEST <= ts && ts <= now };
35 let ts = Timestamp(u64::try_from(ts).unwrap_or(0));
36 if is_sane(ts) {
37 return Ok(ts);
38 }
39 let ts = Timestamp(ts.as_millis() / 1000);
41 if is_sane(ts) {
42 return Ok(ts);
43 }
44 Ok(now)
45 }
46
47 #[inline(never)]
53 pub fn sanitize_float_timestamp(ctx: &Context<'_>) -> Result<Timestamp> {
54 let ts = ctx
55 .get::<f64>(0)
56 .map(|num| {
57 if num.is_normal() && num > 0.0 {
58 num.round() as i64
59 } else {
60 0
61 }
62 })
63 .unwrap_or(0);
64 sanitize_timestamp(ts)
65 }
66
67 #[inline(never)]
68 pub fn sanitize_integer_timestamp(ctx: &Context<'_>) -> Result<Timestamp> {
69 sanitize_timestamp(ctx.get::<i64>(0).unwrap_or(0))
70 }
71
72 #[inline(never)]
75 pub fn validate_url(ctx: &Context<'_>) -> Result<Option<String>> {
76 let val = ctx.get_raw(0);
77 let href = if let ValueRef::Text(s) = val {
78 String::from_utf8_lossy(s).to_string()
79 } else {
80 return Ok(None);
81 };
82 if href.len() > URL_LENGTH_MAX {
83 return Ok(None);
84 }
85 if let Ok(url) = Url::parse(&href) {
86 Ok(Some(url.into()))
87 } else {
88 Ok(None)
89 }
90 }
91
92 #[inline(never)]
95 pub fn sanitize_utf8(ctx: &Context<'_>) -> Result<Option<String>> {
96 let val = ctx.get_raw(0);
97 Ok(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}
104
105pub 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",
112 named_params! {
113 ":path": path.as_str(),
114 ":db_alias": db_alias,
115 },
116 )?;
117 Ok(ExecuteOnDrop {
118 conn,
119 sql: format!("DETACH DATABASE {};", db_alias),
120 })
121}
122
123pub struct ExecuteOnDrop<'a> {
130 conn: &'a PlacesDb,
131 sql: String,
132}
133
134impl<'a> ExecuteOnDrop<'a> {
135 pub fn new(conn: &'a PlacesDb, sql: String) -> Self {
136 Self { conn, sql }
137 }
138
139 pub fn execute_now(self) -> Result<()> {
140 self.conn.execute_batch(&self.sql)?;
141 std::mem::forget(self);
143 Ok(())
144 }
145}
146
147impl Drop for ExecuteOnDrop<'_> {
148 fn drop(&mut self) {
149 if let Err(e) = self.conn.execute_batch(&self.sql) {
150 error_support::report_error!(
151 "places-cleanup-failure",
152 "Failed to clean up after import! {}",
153 e
154 );
155 debug!(" Failed query: {}", &self.sql);
156 }
157 }
158}
159
160pub fn select_count(conn: &PlacesDb, stmt: &str) -> Result<u32> {
161 let 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}
165
166#[derive(Serialize, PartialEq, Eq, Debug, Clone, Default)]
167pub struct HistoryMigrationResult {
168 pub num_total: u32,
169 pub num_succeeded: u32,
170 pub num_failed: u32,
171 pub total_duration: u64,
172}
173
174pub fn define_history_migration_functions(c: &Connection) -> Result<()> {
175 use rusqlite::functions::FunctionFlags;
176 c.create_scalar_function(
177 "validate_url",
178 1,
179 FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
180 crate::import::common::sql_fns::validate_url,
181 )?;
182 c.create_scalar_function(
183 "sanitize_timestamp",
184 1,
185 FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
186 crate::import::common::sql_fns::sanitize_integer_timestamp,
187 )?;
188 c.create_scalar_function(
189 "hash",
190 -1,
191 FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
192 crate::db::db::sql_fns::hash,
193 )?;
194 c.create_scalar_function(
195 "generate_guid",
196 0,
197 FunctionFlags::SQLITE_UTF8,
198 crate::db::db::sql_fns::generate_guid,
199 )?;
200 c.create_scalar_function(
201 "sanitize_utf8",
202 1,
203 FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
204 crate::import::common::sql_fns::sanitize_utf8,
205 )?;
206 c.create_scalar_function(
207 "sanitize_float_timestamp",
208 1,
209 FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
210 crate::import::common::sql_fns::sanitize_float_timestamp,
211 )?;
212 Ok(())
213}