sos_migrate/import/csv/
firefox.rs

1//! Parser for the Firefox passwords CSV export.
2
3use async_trait::async_trait;
4use serde::Deserialize;
5use sos_core::crypto::AccessKey;
6use sos_vault::Vault;
7use sos_vfs as vfs;
8use std::path::{Path, PathBuf};
9use tokio::io::AsyncRead;
10use url::Url;
11
12use super::{GenericCsvConvert, GenericCsvEntry, GenericPasswordRecord};
13use crate::{import::read_csv_records, Convert, Result};
14
15/// Record for an entry in a Firefox passwords CSV export.
16#[derive(Deserialize)]
17pub struct FirefoxPasswordRecord {
18    /// The URL of the entry.
19    pub url: Url,
20    /// The username for the entry.
21    pub username: String,
22    /// The password for the entry.
23    pub password: String,
24    /// The HTTP realm for the entry.
25    #[serde(rename = "httpRealm")]
26    pub http_realm: String,
27    /// The form action origin for the entry.
28    #[serde(rename = "formActionOrigin")]
29    pub form_action_origin: String,
30    /// The guid for the entry.
31    pub guid: String,
32    /// The time created for the entry.
33    #[serde(rename = "timeCreated")]
34    pub time_created: String,
35    /// The time last used for the entry.
36    #[serde(rename = "timeLastUsed")]
37    pub time_last_used: String,
38    /// The time password was changed for the entry.
39    #[serde(rename = "timePasswordChanged")]
40    pub time_password_changed: String,
41}
42
43impl From<FirefoxPasswordRecord> for GenericPasswordRecord {
44    fn from(value: FirefoxPasswordRecord) -> Self {
45        Self {
46            label: value.url.to_string(),
47            url: vec![value.url],
48            username: value.username,
49            password: value.password,
50            otp_auth: None,
51            tags: None,
52            note: None,
53        }
54    }
55}
56
57impl From<FirefoxPasswordRecord> for GenericCsvEntry {
58    fn from(value: FirefoxPasswordRecord) -> Self {
59        Self::Password(value.into())
60    }
61}
62
63/// Parse records from a reader.
64pub async fn parse_reader<R: AsyncRead + Unpin + Send>(
65    reader: R,
66) -> Result<Vec<FirefoxPasswordRecord>> {
67    read_csv_records::<FirefoxPasswordRecord, _>(reader).await
68}
69
70/// Parse records from a path.
71pub async fn parse_path<P: AsRef<Path>>(
72    path: P,
73) -> Result<Vec<FirefoxPasswordRecord>> {
74    parse_reader(vfs::File::open(path).await?).await
75}
76
77/// Import a Firefox passwords CSV export into a vault.
78pub struct FirefoxPasswordCsv;
79
80#[async_trait]
81impl Convert for FirefoxPasswordCsv {
82    type Input = PathBuf;
83
84    async fn convert(
85        &self,
86        source: Self::Input,
87        vault: Vault,
88        key: &AccessKey,
89    ) -> crate::Result<Vault> {
90        let records: Vec<GenericCsvEntry> = parse_path(source)
91            .await?
92            .into_iter()
93            .map(|r| r.into())
94            .collect();
95        GenericCsvConvert.convert(records, vault, key).await
96    }
97}