Skip to content
API Documentation
BastionGuard™
PasswordImporter.cpp
Go to the documentation of this file.
1/*
2 * BastionGuard™
3 * Copyright (C) 2025–2026 Calogero Scarnà
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, version 3.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 *
17 * BastionGuard™ is a trademark of Calogero Scarnà.
18 * The BastionGuard™ name and branding are not licensed under the GPL.
19 */
20
21#include "PasswordImporter.hpp"
22
23#include <fstream>
24#include <sstream>
25#include <algorithm>
26#include <regex>
27
28
29std::vector<ImportedCredential> PasswordImporter::importFromFile(
30 const std::string& path,
31 std::string& errorMsg)
32{
33 std::ifstream in(path);
34 if (!in.is_open()) {
35 errorMsg = "Impossibile aprire il file: " + path;
36 return {};
37 }
38
39 std::vector<std::string> lines;
40 std::string line;
41 while (std::getline(in, line)) {
42 if (!lines.empty() == false && line.size() >= 3 &&
43 static_cast<unsigned char>(line[0]) == 0xEF &&
44 static_cast<unsigned char>(line[1]) == 0xBB &&
45 static_cast<unsigned char>(line[2]) == 0xBF) {
46 line = line.substr(3);
47 }
48 if (!line.empty() && line.back() == '\r')
49 line.pop_back();
50 if (!line.empty())
51 lines.push_back(line);
52 }
53
54 if (lines.empty()) {
55 errorMsg = "Il file è vuoto.";
56 return {};
57 }
58
59 const BrowserFormat fmt = detectFormat(lines[0]);
60
61 switch (fmt) {
64 return parseChrome(lines);
66 return parseFirefox(lines);
68 return parseSafari(lines);
69 default:
70 errorMsg = "Formato CSV non riconosciuto.\n"
71 "Header trovato: " + lines[0] + "\n"
72 "Formati supportati: Chrome, Edge, Firefox, Safari.";
73 return {};
74 }
75}
76
78 std::string h = header;
79 std::transform(h.begin(), h.end(), h.begin(), ::tolower);
80 if (h.find("httprealm") != std::string::npos ||
81 h.find("formactionorigin") != std::string::npos)
83 if (h.find("otpauth") != std::string::npos ||
84 (h.find("title") != std::string::npos && h.find("notes") != std::string::npos))
86 if (h.find("name") != std::string::npos &&
87 h.find("url") != std::string::npos &&
88 h.find("username") != std::string::npos &&
89 h.find("password") != std::string::npos)
91
93}
94
95
98 e.name = cred.name.empty() ? domainFromUrl(cred.url) : cred.name;
99 e.url = cred.url;
100 e.username = cred.username;
101 e.notes = cred.notes;
102 return e;
103}
104
105
106std::vector<ImportedCredential> PasswordImporter::parseChrome(
107 const std::vector<std::string>& lines)
108{
109 std::vector<ImportedCredential> result;
110
111 auto header = splitCsvLine(lines[0]);
112 int iName = -1, iUrl = -1, iUser = -1, iPass = -1;
113
114 for (int i = 0; i < static_cast<int>(header.size()); ++i) {
115 std::string col = header[i];
116 std::transform(col.begin(), col.end(), col.begin(), ::tolower);
117 if (col == "name") iName = i;
118 else if (col == "url") iUrl = i;
119 else if (col == "username") iUser = i;
120 else if (col == "password") iPass = i;
121 }
122
123 if (iUrl < 0 || iUser < 0 || iPass < 0) return result;
124
125 for (size_t r = 1; r < lines.size(); ++r) {
126 auto fields = splitCsvLine(lines[r]);
127 if (fields.size() < 3) continue;
128
129 auto get = [&](int idx) -> std::string {
130 if (idx < 0 || idx >= static_cast<int>(fields.size())) return "";
131 return unquoteCsv(fields[idx]);
132 };
133
135 c.name = get(iName);
136 c.url = get(iUrl);
137 c.username = get(iUser);
138 c.password = get(iPass);
139
140 if (c.url.empty() && c.username.empty()) continue;
141 result.push_back(std::move(c));
142 }
143
144 return result;
145}
146
147
148std::vector<ImportedCredential> PasswordImporter::parseFirefox(
149 const std::vector<std::string>& lines)
150{
151 std::vector<ImportedCredential> result;
152
153 auto header = splitCsvLine(lines[0]);
154 int iUrl = -1, iUser = -1, iPass = -1;
155
156 for (int i = 0; i < static_cast<int>(header.size()); ++i) {
157 std::string col = header[i];
158 std::transform(col.begin(), col.end(), col.begin(), ::tolower);
159 if (col == "url") iUrl = i;
160 else if (col == "username") iUser = i;
161 else if (col == "password") iPass = i;
162 }
163
164 if (iUrl < 0 || iUser < 0 || iPass < 0) return result;
165
166 for (size_t r = 1; r < lines.size(); ++r) {
167 auto fields = splitCsvLine(lines[r]);
168 if (fields.size() < 3) continue;
169
170 auto get = [&](int idx) -> std::string {
171 if (idx < 0 || idx >= static_cast<int>(fields.size())) return "";
172 return unquoteCsv(fields[idx]);
173 };
174
176 c.url = get(iUrl);
177 c.username = get(iUser);
178 c.password = get(iPass);
179 c.name = domainFromUrl(c.url);
180
181 if (c.url.empty() && c.username.empty()) continue;
182 result.push_back(std::move(c));
183 }
184
185 return result;
186}
187
188
189std::vector<ImportedCredential> PasswordImporter::parseSafari(
190 const std::vector<std::string>& lines)
191{
192 std::vector<ImportedCredential> result;
193
194 auto header = splitCsvLine(lines[0]);
195 int iTitle = -1, iUrl = -1, iUser = -1, iPass = -1, iNotes = -1;
196
197 for (int i = 0; i < static_cast<int>(header.size()); ++i) {
198 std::string col = header[i];
199 std::transform(col.begin(), col.end(), col.begin(), ::tolower);
200 if (col == "title") iTitle = i;
201 else if (col == "url") iUrl = i;
202 else if (col == "username") iUser = i;
203 else if (col == "password") iPass = i;
204 else if (col == "notes") iNotes = i;
205 }
206
207 if (iUrl < 0 || iUser < 0 || iPass < 0) return result;
208
209 for (size_t r = 1; r < lines.size(); ++r) {
210 auto fields = splitCsvLine(lines[r]);
211 if (fields.size() < 3) continue;
212
213 auto get = [&](int idx) -> std::string {
214 if (idx < 0 || idx >= static_cast<int>(fields.size())) return "";
215 return unquoteCsv(fields[idx]);
216 };
217
219 c.name = get(iTitle);
220 c.url = get(iUrl);
221 c.username = get(iUser);
222 c.password = get(iPass);
223 c.notes = get(iNotes);
224
225 if (c.url.empty() && c.username.empty()) continue;
226 result.push_back(std::move(c));
227 }
228
229 return result;
230}
231
232
233std::vector<std::string> PasswordImporter::splitCsvLine(const std::string& line) {
234 std::vector<std::string> fields;
235 std::string field;
236 bool inQuotes = false;
237
238 for (size_t i = 0; i < line.size(); ++i) {
239 const char c = line[i];
240
241 if (inQuotes) {
242 if (c == '"') {
243 if (i + 1 < line.size() && line[i + 1] == '"') {
244 field += '"';
245 ++i;
246 } else {
247 inQuotes = false;
248 }
249 } else {
250 field += c;
251 }
252 } else {
253 if (c == '"') {
254 inQuotes = true;
255 } else if (c == ',') {
256 fields.push_back(field);
257 field.clear();
258 } else {
259 field += c;
260 }
261 }
262 }
263 fields.push_back(field);
264 return fields;
265}
266
267
268std::string PasswordImporter::unquoteCsv(const std::string& f) {
269 if (f.size() >= 2 && f.front() == '"' && f.back() == '"') {
270 std::string inner = f.substr(1, f.size() - 2);
271 // Unescapa "" → "
272 std::string out;
273 out.reserve(inner.size());
274 for (size_t i = 0; i < inner.size(); ++i) {
275 if (inner[i] == '"' && i + 1 < inner.size() && inner[i + 1] == '"') {
276 out += '"';
277 ++i;
278 } else {
279 out += inner[i];
280 }
281 }
282 return out;
283 }
284 return f;
285}
286
287
288std::string PasswordImporter::domainFromUrl(const std::string& url) {
289 if (url.empty()) return "";
290 std::string s = url;
291 const auto schema_end = s.find("://");
292 if (schema_end != std::string::npos)
293 s = s.substr(schema_end + 3);
294 const auto slash = s.find('/');
295 if (slash != std::string::npos)
296 s = s.substr(0, slash);
297 const auto colon = s.find(':');
298 if (colon != std::string::npos)
299 s = s.substr(0, colon);
300 if (s.substr(0, 4) == "www.")
301 s = s.substr(4);
302
303 return s;
304}
BrowserFormat
static std::string unquoteCsv(const std::string &field)
static std::string domainFromUrl(const std::string &url)
static BrowserFormat detectFormat(const std::string &headerLine)
static std::vector< ImportedCredential > importFromFile(const std::string &path, std::string &errorMsg)
static std::vector< ImportedCredential > parseChrome(const std::vector< std::string > &lines)
static std::vector< ImportedCredential > parseFirefox(const std::vector< std::string > &lines)
static std::vector< std::string > splitCsvLine(const std::string &line)
static PasswordEntry toPasswordEntry(const ImportedCredential &cred)
static std::vector< ImportedCredential > parseSafari(const std::vector< std::string > &lines)
std::string url
std::string username
std::string notes
std::string name