chrome-tools 1.1.5 → 1.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/cli.js +556 -1
- package/dist/src/core/browsers.d.ts +7 -3
- package/dist/src/core/browsers.js +186 -1
- package/dist/src/core/chrome.d.ts +6 -4
- package/dist/src/core/chrome.js +240 -1
- package/dist/src/core/crypto.d.ts +1 -1
- package/dist/src/core/crypto.js +113 -1
- package/dist/src/core/db.d.ts +4 -2
- package/dist/src/core/db.js +90 -1
- package/dist/src/core/keychain.d.ts +10 -0
- package/dist/src/core/keychain.js +191 -1
- package/dist/src/core/limit.d.ts +4 -0
- package/dist/src/core/limit.js +12 -0
- package/dist/src/core/platform/darwin.js +31 -1
- package/dist/src/core/platform/index.d.ts +4 -3
- package/dist/src/core/platform/index.js +69 -1
- package/dist/src/core/platform/linux.js +64 -1
- package/dist/src/core/platform/types.d.ts +3 -2
- package/dist/src/core/platform/types.js +1 -1
- package/dist/src/core/platform/win32.js +114 -1
- package/dist/src/core/sql.d.ts +2 -0
- package/dist/src/core/sql.js +4 -0
- package/dist/src/index.d.ts +2 -2
- package/dist/src/index.js +34 -1
- package/dist/src/modules/autofill.d.ts +8 -15
- package/dist/src/modules/autofill.js +170 -1
- package/dist/src/modules/bookmarks.d.ts +3 -5
- package/dist/src/modules/bookmarks.js +83 -1
- package/dist/src/modules/cookies.d.ts +4 -5
- package/dist/src/modules/cookies.js +157 -1
- package/dist/src/modules/downloads.d.ts +3 -5
- package/dist/src/modules/downloads.js +80 -1
- package/dist/src/modules/extensions.d.ts +3 -5
- package/dist/src/modules/extensions.js +71 -1
- package/dist/src/modules/history.d.ts +5 -10
- package/dist/src/modules/history.js +83 -1
- package/dist/src/modules/local-storage.d.ts +3 -5
- package/dist/src/modules/local-storage.js +455 -1
- package/dist/src/modules/passwords.d.ts +4 -5
- package/dist/src/modules/passwords.js +64 -1
- package/dist/src/modules/search-engines.d.ts +3 -5
- package/dist/src/modules/search-engines.js +38 -1
- package/dist/src/modules/top-sites.d.ts +3 -5
- package/dist/src/modules/top-sites.js +33 -1
- package/package.json +14 -12
|
@@ -1 +1,114 @@
|
|
|
1
|
-
import{execFileSync
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { decryptValueBufferGcm } from "../crypto.js";
|
|
5
|
+
import { getActiveBrowser } from "../browsers.js";
|
|
6
|
+
// Chrome base64-encodes the DPAPI-protected AES key in Local State, prefixed
|
|
7
|
+
// with the ASCII bytes "DPAPI".
|
|
8
|
+
const DPAPI_PREFIX = Buffer.from("DPAPI", "ascii");
|
|
9
|
+
// Keyed by User Data dir so different browsers (each with its own Local State)
|
|
10
|
+
// cache independently (e.g. under `--all-browsers`).
|
|
11
|
+
const _cachedKeys = new Map();
|
|
12
|
+
/**
|
|
13
|
+
* Unprotects a DPAPI blob for the current user.
|
|
14
|
+
*
|
|
15
|
+
* Node has no binding for `CryptUnprotectData`, so we shell out to PowerShell's
|
|
16
|
+
* `ProtectedData.Unprotect`. This needs no native build step. For a faster,
|
|
17
|
+
* dependency-light alternative, swap this for the optional `win-dpapi` addon.
|
|
18
|
+
*/
|
|
19
|
+
function dpapiDecrypt(encrypted) {
|
|
20
|
+
const script = [
|
|
21
|
+
"$ErrorActionPreference='Stop';",
|
|
22
|
+
"Add-Type -AssemblyName System.Security;",
|
|
23
|
+
"$b=[Console]::In.ReadToEnd().Trim();",
|
|
24
|
+
"$enc=[Convert]::FromBase64String($b);",
|
|
25
|
+
"$dec=[System.Security.Cryptography.ProtectedData]::Unprotect($enc,$null,[System.Security.Cryptography.DataProtectionScope]::CurrentUser);",
|
|
26
|
+
"[Console]::Out.Write([Convert]::ToBase64String($dec));",
|
|
27
|
+
].join(" ");
|
|
28
|
+
let out;
|
|
29
|
+
try {
|
|
30
|
+
out = execFileSync("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", script], { input: encrypted.toString("base64"), encoding: "utf-8" });
|
|
31
|
+
}
|
|
32
|
+
catch (err) {
|
|
33
|
+
throw new Error("DPAPI decryption via PowerShell failed.\n" +
|
|
34
|
+
"The encrypted_key can only be unprotected by the same Windows user that " +
|
|
35
|
+
"created it. Run as that user on the same machine.\n" +
|
|
36
|
+
`(powershell: ${err?.message ?? err})`);
|
|
37
|
+
}
|
|
38
|
+
return Buffer.from(out.trim(), "base64");
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Windows backend.
|
|
42
|
+
*
|
|
43
|
+
* Key source: `os_crypt.encrypted_key` in Local State — base64, "DPAPI"-prefixed,
|
|
44
|
+
* unprotected via DPAPI into a raw 32-byte AES-256 key (no PBKDF2 on Windows).
|
|
45
|
+
* Cipher: AES-256-GCM (nonce + tag carried in each blob).
|
|
46
|
+
*
|
|
47
|
+
* Note: Chrome 127+ adds app-bound ("v20") keys requiring elevation; those are
|
|
48
|
+
* not handled here. Standard v10/v11 cookies and passwords decrypt fine.
|
|
49
|
+
*/
|
|
50
|
+
export const win32 = {
|
|
51
|
+
name: "win32",
|
|
52
|
+
getDefaultChromeDir(browser = getActiveBrowser()) {
|
|
53
|
+
const roaming = browser.dirs.win32Base === "roaming";
|
|
54
|
+
const appData = roaming
|
|
55
|
+
? process.env.APPDATA ||
|
|
56
|
+
(process.env.USERPROFILE
|
|
57
|
+
? join(process.env.USERPROFILE, "AppData", "Roaming")
|
|
58
|
+
: "")
|
|
59
|
+
: process.env.LOCALAPPDATA ||
|
|
60
|
+
(process.env.USERPROFILE
|
|
61
|
+
? join(process.env.USERPROFILE, "AppData", "Local")
|
|
62
|
+
: "");
|
|
63
|
+
if (!appData) {
|
|
64
|
+
throw new Error("Could not resolve the Windows application-data directory.");
|
|
65
|
+
}
|
|
66
|
+
return join(appData, ...browser.dirs.win32.split("/"));
|
|
67
|
+
},
|
|
68
|
+
getDecryptionKey(chromeDir, browser = getActiveBrowser()) {
|
|
69
|
+
const requestedDir = chromeDir || this.getDefaultChromeDir(browser);
|
|
70
|
+
// `profilePath` may point directly at .../Default. Local State belongs to
|
|
71
|
+
// the parent User Data directory, so use it when the supplied path does not
|
|
72
|
+
// contain its own Local State file.
|
|
73
|
+
const parentDir = dirname(requestedDir);
|
|
74
|
+
const dir = existsSync(join(requestedDir, "Local State"))
|
|
75
|
+
? requestedDir
|
|
76
|
+
: parentDir !== requestedDir && existsSync(join(parentDir, "Local State"))
|
|
77
|
+
? parentDir
|
|
78
|
+
: requestedDir;
|
|
79
|
+
const cached = _cachedKeys.get(dir);
|
|
80
|
+
if (cached !== undefined) {
|
|
81
|
+
return cached;
|
|
82
|
+
}
|
|
83
|
+
const localStatePath = join(dir, "Local State");
|
|
84
|
+
if (!existsSync(localStatePath)) {
|
|
85
|
+
throw new Error(`Could not find Chrome's "Local State" at ${localStatePath}.\n` +
|
|
86
|
+
"Run Google Chrome at least once so it creates the encryption key.");
|
|
87
|
+
}
|
|
88
|
+
let encryptedKeyB64;
|
|
89
|
+
try {
|
|
90
|
+
const raw = readFileSync(localStatePath, "utf-8");
|
|
91
|
+
encryptedKeyB64 = JSON.parse(raw)?.os_crypt?.encrypted_key;
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
throw new Error(`Failed to read os_crypt.encrypted_key from Local State: ${err.message}`);
|
|
95
|
+
}
|
|
96
|
+
if (!encryptedKeyB64) {
|
|
97
|
+
throw new Error("No os_crypt.encrypted_key found in Local State.\n" +
|
|
98
|
+
"This profile may predate Chrome's encrypted-key scheme.");
|
|
99
|
+
}
|
|
100
|
+
const encryptedKey = Buffer.from(encryptedKeyB64, "base64");
|
|
101
|
+
if (!encryptedKey.subarray(0, DPAPI_PREFIX.length).equals(DPAPI_PREFIX)) {
|
|
102
|
+
throw new Error('Unexpected encrypted_key format (missing "DPAPI" prefix).');
|
|
103
|
+
}
|
|
104
|
+
const key = dpapiDecrypt(encryptedKey.subarray(DPAPI_PREFIX.length));
|
|
105
|
+
_cachedKeys.set(dir, key);
|
|
106
|
+
return key;
|
|
107
|
+
},
|
|
108
|
+
decrypt(blob, key) {
|
|
109
|
+
return decryptValueBufferGcm(blob, key);
|
|
110
|
+
},
|
|
111
|
+
clearCache() {
|
|
112
|
+
_cachedKeys.clear();
|
|
113
|
+
},
|
|
114
|
+
};
|
package/dist/src/index.d.ts
CHANGED
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
* const profiles = discoverProfiles();
|
|
12
12
|
*/
|
|
13
13
|
export { getDefaultChromeDir, discoverProfiles, getProfileMetadata, getProfileStates, resolveProfilePath, resolveTargetProfiles, detectInstalledBrowsers, type DiscoveredProfile, type ProfileMetadata, type ProfileState, type ResolveProfilesOptions, type InstalledBrowser, } from "./core/chrome.js";
|
|
14
|
-
export { BROWSERS, listBrowsers, resolveBrowser, getActiveBrowser, setActiveBrowser, type BrowserId, type BrowserDescriptor, } from "./core/browsers.js";
|
|
14
|
+
export { BROWSERS, listBrowsers, resolveBrowser, requireBrowser, getActiveBrowser, setActiveBrowser, type BrowserId, type BrowserDescriptor, } from "./core/browsers.js";
|
|
15
15
|
export { selectPlatform, getPlatformChromeDir, deriveEncryptionKey, decryptValue, decryptValueBuffer, clearKeyCache, type ChromePlatform, } from "./core/platform/index.js";
|
|
16
|
-
export { getKeychainPassword, getChromeKeychainPassword, clearKeychainCache, } from "./core/keychain.js";
|
|
16
|
+
export { getKeychainPassword, getChromeKeychainPassword, clearKeychainCache, setKeychainPasswordProvider, getKeychainPasswordProvider, type KeychainPasswordProvider, } from "./core/keychain.js";
|
|
17
17
|
export { chromeTimeToDate, } from "./core/crypto.js";
|
|
18
18
|
export { queryDatabase, tableExists, } from "./core/db.js";
|
|
19
19
|
export { getPasswords } from "./modules/passwords.js";
|
package/dist/src/index.js
CHANGED
|
@@ -1 +1,34 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* chrome-tools — Public API
|
|
3
|
+
*
|
|
4
|
+
* This module re-exports all feature modules for use as a Node.js library.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* import { getPasswords, getBookmarks, discoverProfiles } from 'chrome-tools';
|
|
8
|
+
*
|
|
9
|
+
* const passwords = getPasswords({ profile: 'Default' });
|
|
10
|
+
* const bookmarks = getBookmarks({ search: 'github' });
|
|
11
|
+
* const profiles = discoverProfiles();
|
|
12
|
+
*/
|
|
13
|
+
// Core utilities
|
|
14
|
+
export { getDefaultChromeDir, discoverProfiles, getProfileMetadata, getProfileStates, resolveProfilePath, resolveTargetProfiles, detectInstalledBrowsers, } from "./core/chrome.js";
|
|
15
|
+
// Browser registry & selection (all Chromium-based browsers)
|
|
16
|
+
export { BROWSERS, listBrowsers, resolveBrowser, requireBrowser, getActiveBrowser, setActiveBrowser, } from "./core/browsers.js";
|
|
17
|
+
// Platform-aware decryption (macOS / Windows / Linux)
|
|
18
|
+
export { selectPlatform, getPlatformChromeDir, deriveEncryptionKey, decryptValue, decryptValueBuffer, clearKeyCache, } from "./core/platform/index.js";
|
|
19
|
+
// macOS Keychain helpers (macOS-only). `getKeychainPassword` takes a Safe
|
|
20
|
+
// Storage service name; `getChromeKeychainPassword` is the Chrome-specific alias.
|
|
21
|
+
export { getKeychainPassword, getChromeKeychainPassword, clearKeychainCache, setKeychainPasswordProvider, getKeychainPasswordProvider, } from "./core/keychain.js";
|
|
22
|
+
export { chromeTimeToDate, } from "./core/crypto.js";
|
|
23
|
+
export { queryDatabase, tableExists, } from "./core/db.js";
|
|
24
|
+
// Feature modules
|
|
25
|
+
export { getPasswords } from "./modules/passwords.js";
|
|
26
|
+
export { getBookmarks } from "./modules/bookmarks.js";
|
|
27
|
+
export { getHistory, getSearchTerms } from "./modules/history.js";
|
|
28
|
+
export { getCookies } from "./modules/cookies.js";
|
|
29
|
+
export { getLocalStorage } from "./modules/local-storage.js";
|
|
30
|
+
export { getAutofillEntries, getCreditCards, getAddresses } from "./modules/autofill.js";
|
|
31
|
+
export { getDownloads } from "./modules/downloads.js";
|
|
32
|
+
export { getExtensions } from "./modules/extensions.js";
|
|
33
|
+
export { getSearchEngines } from "./modules/search-engines.js";
|
|
34
|
+
export { getTopSites } from "./modules/top-sites.js";
|
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
profile?: string;
|
|
4
|
-
browser?: string;
|
|
1
|
+
import { type ResolveProfilesOptions } from "../core/chrome.js";
|
|
2
|
+
export interface AutofillOptions extends ResolveProfilesOptions {
|
|
5
3
|
search?: string;
|
|
6
4
|
limit?: number;
|
|
7
5
|
}
|
|
@@ -13,10 +11,8 @@ export interface AutofillEntry {
|
|
|
13
11
|
dateLastUsed: string;
|
|
14
12
|
count: number;
|
|
15
13
|
}
|
|
16
|
-
export interface CreditCardOptions {
|
|
17
|
-
|
|
18
|
-
profile?: string;
|
|
19
|
-
browser?: string;
|
|
14
|
+
export interface CreditCardOptions extends ResolveProfilesOptions {
|
|
15
|
+
decryptionKey?: Buffer;
|
|
20
16
|
}
|
|
21
17
|
export interface CreditCardEntry {
|
|
22
18
|
profile: string;
|
|
@@ -30,10 +26,7 @@ export interface CreditCardEntry {
|
|
|
30
26
|
useCount: number;
|
|
31
27
|
dateModified: string;
|
|
32
28
|
}
|
|
33
|
-
export interface AddressOptions {
|
|
34
|
-
chromeDir?: string;
|
|
35
|
-
profile?: string;
|
|
36
|
-
browser?: string;
|
|
29
|
+
export interface AddressOptions extends ResolveProfilesOptions {
|
|
37
30
|
}
|
|
38
31
|
export interface AddressEntry {
|
|
39
32
|
profile: string;
|
|
@@ -57,7 +50,7 @@ export interface AddressEntry {
|
|
|
57
50
|
* @param options - Filtering and targeting options.
|
|
58
51
|
* @returns Array of autofill entries.
|
|
59
52
|
*/
|
|
60
|
-
export declare function getAutofillEntries({ chromeDir, profile, browser, search, limit, }?: AutofillOptions): AutofillEntry[];
|
|
53
|
+
export declare function getAutofillEntries({ chromeDir, profilePath, profile, browser, search, limit, }?: AutofillOptions): AutofillEntry[];
|
|
61
54
|
/**
|
|
62
55
|
* Retrieves saved credit cards from Chrome's Web Data database.
|
|
63
56
|
* Card numbers are encrypted and will be decrypted.
|
|
@@ -65,11 +58,11 @@ export declare function getAutofillEntries({ chromeDir, profile, browser, search
|
|
|
65
58
|
* @param options - Targeting options.
|
|
66
59
|
* @returns Array of credit card entries.
|
|
67
60
|
*/
|
|
68
|
-
export declare function getCreditCards({ chromeDir, profile, browser }?: CreditCardOptions): CreditCardEntry[];
|
|
61
|
+
export declare function getCreditCards({ chromeDir, profilePath, profile, browser, decryptionKey, }?: CreditCardOptions): CreditCardEntry[];
|
|
69
62
|
/**
|
|
70
63
|
* Retrieves saved addresses from Chrome's Web Data database.
|
|
71
64
|
*
|
|
72
65
|
* @param options - Targeting options.
|
|
73
66
|
* @returns Array of address entries.
|
|
74
67
|
*/
|
|
75
|
-
export declare function getAddresses({ chromeDir, profile, browser }?: AddressOptions): AddressEntry[];
|
|
68
|
+
export declare function getAddresses({ chromeDir, profilePath, profile, browser }?: AddressOptions): AddressEntry[];
|
|
@@ -1 +1,170 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { resolveTargetProfiles } from "../core/chrome.js";
|
|
3
|
+
import { deriveEncryptionKey, decryptValue } from "../core/platform/index.js";
|
|
4
|
+
import { chromeTimeToDate } from "../core/crypto.js";
|
|
5
|
+
import { queryDatabase } from "../core/db.js";
|
|
6
|
+
import { limitResults, normalizeLimit } from "../core/limit.js";
|
|
7
|
+
import { sqlLikePattern } from "../core/sql.js";
|
|
8
|
+
/**
|
|
9
|
+
* Retrieves autofill entries from Chrome's Web Data database.
|
|
10
|
+
*
|
|
11
|
+
* @param options - Filtering and targeting options.
|
|
12
|
+
* @returns Array of autofill entries.
|
|
13
|
+
*/
|
|
14
|
+
export function getAutofillEntries({ chromeDir, profilePath, profile, browser, search, limit = 100, } = {}) {
|
|
15
|
+
const profiles = resolveTargetProfiles({ chromeDir, profilePath, profile, browser });
|
|
16
|
+
const results = [];
|
|
17
|
+
const rowLimit = normalizeLimit(limit);
|
|
18
|
+
for (const p of profiles) {
|
|
19
|
+
const dbPath = join(p.path, "Web Data");
|
|
20
|
+
try {
|
|
21
|
+
// Apply search in SQL before LIMIT so older matches are not truncated away.
|
|
22
|
+
let sql = `SELECT name, value, date_created, date_last_used, count FROM autofill`;
|
|
23
|
+
const params = [];
|
|
24
|
+
if (search) {
|
|
25
|
+
sql += ` WHERE IFNULL(name, '') LIKE ? ESCAPE '\\' OR IFNULL(value, '') LIKE ? ESCAPE '\\'`;
|
|
26
|
+
const like = sqlLikePattern(search);
|
|
27
|
+
params.push(like, like);
|
|
28
|
+
}
|
|
29
|
+
sql += ` ORDER BY date_last_used DESC`;
|
|
30
|
+
if (rowLimit) {
|
|
31
|
+
sql += ` LIMIT ${rowLimit}`;
|
|
32
|
+
}
|
|
33
|
+
const rows = queryDatabase(dbPath, sql, params);
|
|
34
|
+
for (const row of rows) {
|
|
35
|
+
results.push({
|
|
36
|
+
profile: p.name,
|
|
37
|
+
name: row.name || "",
|
|
38
|
+
value: row.value || "",
|
|
39
|
+
dateCreated: chromeTimeToDate(row.date_created ? BigInt(row.date_created) * 1000000n + 11644473600000000n : 0),
|
|
40
|
+
dateLastUsed: chromeTimeToDate(row.date_last_used ? BigInt(row.date_last_used) * 1000000n + 11644473600000000n : 0),
|
|
41
|
+
count: row.count || 0,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
// Skip profiles with errors
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return limitResults(results, limit);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Retrieves saved credit cards from Chrome's Web Data database.
|
|
53
|
+
* Card numbers are encrypted and will be decrypted.
|
|
54
|
+
*
|
|
55
|
+
* @param options - Targeting options.
|
|
56
|
+
* @returns Array of credit card entries.
|
|
57
|
+
*/
|
|
58
|
+
export function getCreditCards({ chromeDir, profilePath, profile, browser, decryptionKey, } = {}) {
|
|
59
|
+
const userDataDir = profilePath || chromeDir;
|
|
60
|
+
const profiles = resolveTargetProfiles({ chromeDir, profilePath, profile, browser });
|
|
61
|
+
// Pass user-data dir so Windows reads Local State from the same tree as the profiles.
|
|
62
|
+
let derivedKey = decryptionKey;
|
|
63
|
+
const results = [];
|
|
64
|
+
for (const p of profiles) {
|
|
65
|
+
const dbPath = join(p.path, "Web Data");
|
|
66
|
+
try {
|
|
67
|
+
const rows = queryDatabase(dbPath, `SELECT guid, name_on_card, expiration_month, expiration_year,
|
|
68
|
+
card_number_encrypted, date_modified, origin, use_count,
|
|
69
|
+
nickname
|
|
70
|
+
FROM credit_cards ORDER BY use_date DESC`);
|
|
71
|
+
for (const row of rows) {
|
|
72
|
+
let cardNumber = "";
|
|
73
|
+
if (row.card_number_encrypted?.length > 0) {
|
|
74
|
+
cardNumber = decryptValue(row.card_number_encrypted, derivedKey ??= deriveEncryptionKey(userDataDir, browser));
|
|
75
|
+
}
|
|
76
|
+
results.push({
|
|
77
|
+
profile: p.name,
|
|
78
|
+
guid: row.guid || "",
|
|
79
|
+
nameOnCard: row.name_on_card || "",
|
|
80
|
+
expirationMonth: row.expiration_month || 0,
|
|
81
|
+
expirationYear: row.expiration_year || 0,
|
|
82
|
+
cardNumber,
|
|
83
|
+
nickname: row.nickname || "",
|
|
84
|
+
origin: row.origin || "",
|
|
85
|
+
useCount: row.use_count || 0,
|
|
86
|
+
dateModified: chromeTimeToDate(row.date_modified ? BigInt(row.date_modified) * 1000000n + 11644473600000000n : 0),
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
// Skip profiles with errors
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return results;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Retrieves saved addresses from Chrome's Web Data database.
|
|
98
|
+
*
|
|
99
|
+
* @param options - Targeting options.
|
|
100
|
+
* @returns Array of address entries.
|
|
101
|
+
*/
|
|
102
|
+
export function getAddresses({ chromeDir, profilePath, profile, browser } = {}) {
|
|
103
|
+
const profiles = resolveTargetProfiles({ chromeDir, profilePath, profile, browser });
|
|
104
|
+
const results = [];
|
|
105
|
+
for (const p of profiles) {
|
|
106
|
+
const dbPath = join(p.path, "Web Data");
|
|
107
|
+
try {
|
|
108
|
+
const profileRows = queryDatabase(dbPath, `SELECT guid, company_name, street_address,
|
|
109
|
+
city, state, zipcode, country_code, date_modified,
|
|
110
|
+
origin, use_count
|
|
111
|
+
FROM autofill_profiles ORDER BY use_date DESC`);
|
|
112
|
+
// Try to join with names, emails, and phones
|
|
113
|
+
let nameRows = [];
|
|
114
|
+
let emailRows = [];
|
|
115
|
+
let phoneRows = [];
|
|
116
|
+
try {
|
|
117
|
+
nameRows = queryDatabase(dbPath, `SELECT guid, first_name, middle_name, last_name, full_name FROM autofill_profile_names`);
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
/* ignore */
|
|
121
|
+
}
|
|
122
|
+
try {
|
|
123
|
+
emailRows = queryDatabase(dbPath, `SELECT guid, email FROM autofill_profile_emails`);
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
/* ignore */
|
|
127
|
+
}
|
|
128
|
+
try {
|
|
129
|
+
phoneRows = queryDatabase(dbPath, `SELECT guid, number FROM autofill_profile_phones`);
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
/* ignore */
|
|
133
|
+
}
|
|
134
|
+
const nameMap = new Map();
|
|
135
|
+
for (const n of nameRows) {
|
|
136
|
+
nameMap.set(n.guid, n.full_name || [n.first_name, n.middle_name, n.last_name].filter(Boolean).join(" "));
|
|
137
|
+
}
|
|
138
|
+
const emailMap = new Map();
|
|
139
|
+
for (const e of emailRows) {
|
|
140
|
+
emailMap.set(e.guid, e.email || "");
|
|
141
|
+
}
|
|
142
|
+
const phoneMap = new Map();
|
|
143
|
+
for (const ph of phoneRows) {
|
|
144
|
+
phoneMap.set(ph.guid, ph.number || "");
|
|
145
|
+
}
|
|
146
|
+
for (const row of profileRows) {
|
|
147
|
+
results.push({
|
|
148
|
+
profile: p.name,
|
|
149
|
+
guid: row.guid || "",
|
|
150
|
+
fullName: nameMap.get(row.guid) || "",
|
|
151
|
+
email: emailMap.get(row.guid) || "",
|
|
152
|
+
phone: phoneMap.get(row.guid) || "",
|
|
153
|
+
companyName: row.company_name || "",
|
|
154
|
+
streetAddress: row.street_address || "",
|
|
155
|
+
city: row.city || "",
|
|
156
|
+
state: row.state || "",
|
|
157
|
+
zipcode: row.zipcode || "",
|
|
158
|
+
countryCode: row.country_code || "",
|
|
159
|
+
origin: row.origin || "",
|
|
160
|
+
useCount: row.use_count || 0,
|
|
161
|
+
dateModified: chromeTimeToDate(row.date_modified ? BigInt(row.date_modified) * 1000000n + 11644473600000000n : 0),
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
// Skip profiles with errors
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return results;
|
|
170
|
+
}
|
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
profile?: string;
|
|
4
|
-
browser?: string;
|
|
1
|
+
import { type ResolveProfilesOptions } from "../core/chrome.js";
|
|
2
|
+
export interface BookmarkOptions extends ResolveProfilesOptions {
|
|
5
3
|
folder?: string;
|
|
6
4
|
search?: string;
|
|
7
5
|
}
|
|
@@ -21,4 +19,4 @@ export interface BookmarkEntry {
|
|
|
21
19
|
* @param options - Options for filtering and targeting.
|
|
22
20
|
* @returns Filtered bookmark entries.
|
|
23
21
|
*/
|
|
24
|
-
export declare function getBookmarks({ chromeDir, profile, folder, search, browser }?: BookmarkOptions): BookmarkEntry[];
|
|
22
|
+
export declare function getBookmarks({ chromeDir, profilePath, profile, folder, search, browser, }?: BookmarkOptions): BookmarkEntry[];
|
|
@@ -1 +1,83 @@
|
|
|
1
|
-
import{existsSync
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { resolveTargetProfiles } from "../core/chrome.js";
|
|
4
|
+
import { chromeTimeToDate } from "../core/crypto.js";
|
|
5
|
+
/**
|
|
6
|
+
* Recursively flattens the bookmark tree into a flat array.
|
|
7
|
+
*
|
|
8
|
+
* @param node - Bookmark tree node.
|
|
9
|
+
* @param folderPath - Current folder path for context.
|
|
10
|
+
* @returns Array of flattened bookmark entries.
|
|
11
|
+
*/
|
|
12
|
+
function flattenBookmarks(node, folderPath = "") {
|
|
13
|
+
const results = [];
|
|
14
|
+
if (!node)
|
|
15
|
+
return results;
|
|
16
|
+
const currentPath = folderPath
|
|
17
|
+
? `${folderPath}/${node.name || ""}`
|
|
18
|
+
: node.name || "";
|
|
19
|
+
if (node.type === "url") {
|
|
20
|
+
results.push({
|
|
21
|
+
title: node.name || "",
|
|
22
|
+
url: node.url || "",
|
|
23
|
+
folder: folderPath,
|
|
24
|
+
dateAdded: chromeTimeToDate(node.date_added),
|
|
25
|
+
dateLastUsed: chromeTimeToDate(node.date_last_used),
|
|
26
|
+
id: node.id,
|
|
27
|
+
guid: node.guid || "",
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
if (node.children && Array.isArray(node.children)) {
|
|
31
|
+
for (const child of node.children) {
|
|
32
|
+
results.push(...flattenBookmarks(child, currentPath));
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return results;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Reads and parses bookmarks from Chrome's Bookmarks JSON file.
|
|
39
|
+
*
|
|
40
|
+
* @param options - Options for filtering and targeting.
|
|
41
|
+
* @returns Filtered bookmark entries.
|
|
42
|
+
*/
|
|
43
|
+
export function getBookmarks({ chromeDir, profilePath, profile, folder, search, browser, } = {}) {
|
|
44
|
+
const profiles = resolveTargetProfiles({ chromeDir, profilePath, profile, browser });
|
|
45
|
+
const results = [];
|
|
46
|
+
for (const p of profiles) {
|
|
47
|
+
const bookmarksPath = join(p.path, "Bookmarks");
|
|
48
|
+
if (!existsSync(bookmarksPath))
|
|
49
|
+
continue;
|
|
50
|
+
try {
|
|
51
|
+
const raw = readFileSync(bookmarksPath, "utf-8");
|
|
52
|
+
const data = JSON.parse(raw);
|
|
53
|
+
const roots = data.roots;
|
|
54
|
+
if (!roots)
|
|
55
|
+
continue;
|
|
56
|
+
// Flatten all root bookmark folders
|
|
57
|
+
const rootNames = ["bookmark_bar", "other", "synced"];
|
|
58
|
+
for (const rootName of rootNames) {
|
|
59
|
+
if (roots[rootName]) {
|
|
60
|
+
const bookmarks = flattenBookmarks(roots[rootName], "");
|
|
61
|
+
for (const bm of bookmarks) {
|
|
62
|
+
bm.profile = p.name;
|
|
63
|
+
results.push(bm);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
// Skip profiles with parse errors
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
let filtered = results;
|
|
73
|
+
if (folder) {
|
|
74
|
+
const term = folder.toLowerCase();
|
|
75
|
+
filtered = filtered.filter((b) => b.folder.toLowerCase().includes(term));
|
|
76
|
+
}
|
|
77
|
+
if (search) {
|
|
78
|
+
const term = search.toLowerCase();
|
|
79
|
+
filtered = filtered.filter((b) => b.title.toLowerCase().includes(term) ||
|
|
80
|
+
b.url.toLowerCase().includes(term));
|
|
81
|
+
}
|
|
82
|
+
return filtered;
|
|
83
|
+
}
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
profile?: string;
|
|
4
|
-
browser?: string;
|
|
1
|
+
import { type ResolveProfilesOptions } from "../core/chrome.js";
|
|
2
|
+
export interface CookieOptions extends ResolveProfilesOptions {
|
|
5
3
|
domain?: string;
|
|
6
4
|
url?: string;
|
|
7
5
|
decrypt?: boolean;
|
|
8
6
|
limit?: number;
|
|
7
|
+
decryptionKey?: Buffer;
|
|
9
8
|
}
|
|
10
9
|
export interface CookieEntry {
|
|
11
10
|
profile: string;
|
|
@@ -31,7 +30,7 @@ declare function pathMatch(urlPath: string, cookiePath: string): boolean;
|
|
|
31
30
|
* @param options - Targeting and filtering options.
|
|
32
31
|
* @returns Array of cookie entries.
|
|
33
32
|
*/
|
|
34
|
-
export declare function getCookies({ chromeDir, profile, browser, domain, url, decrypt, limit, }?: CookieOptions): CookieEntry[];
|
|
33
|
+
export declare function getCookies({ chromeDir, profilePath, profile, browser, domain, url, decrypt, limit, decryptionKey, }?: CookieOptions): CookieEntry[];
|
|
35
34
|
export declare const _test: {
|
|
36
35
|
parseURL: typeof parseURL;
|
|
37
36
|
getPossibleHostKeys: typeof getPossibleHostKeys;
|