cloak22 2.2.0

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.
@@ -0,0 +1,274 @@
1
+ import fs from "node:fs";
2
+ import net from "node:net";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import {
6
+ defaultChromeUserDataDir,
7
+ listChromeProfiles,
8
+ type ChromeProfile,
9
+ } from "./chrome-profiles.js";
10
+
11
+ type QueryRow = {
12
+ host_key: string | null;
13
+ };
14
+
15
+ type StatementLike = {
16
+ all(...parameters: unknown[]): Array<Record<string, unknown>>;
17
+ };
18
+
19
+ type DatabaseLike = {
20
+ prepare(sql: string): StatementLike;
21
+ close(): void;
22
+ };
23
+
24
+ type DatabaseConstructor = new (path: string) => DatabaseLike;
25
+
26
+ type ReadCookieHostsDependencies = {
27
+ chromeUserDataDir?: string;
28
+ pathExists?: (targetPath: string) => boolean;
29
+ makeTempDir?: (prefix: string) => string;
30
+ copyFile?: (sourcePath: string, targetPath: string) => void;
31
+ removeDir?: (targetPath: string, options: { recursive: true; force: true }) => void;
32
+ queryHosts?: (databasePath: string) => Promise<string[]>;
33
+ };
34
+
35
+ type ListChromeProfileSitesDependencies = {
36
+ chromeUserDataDir?: string;
37
+ listProfiles?: typeof listChromeProfiles;
38
+ readCookieHosts?: (
39
+ options: { chromeUserDataDir: string; profileDirectory: string },
40
+ dependencies?: ReadCookieHostsDependencies
41
+ ) => Promise<string[]>;
42
+ };
43
+
44
+ type ListChromeProfileCookieUrlsDependencies = ReadCookieHostsDependencies & {
45
+ readCookieHosts?: (
46
+ options: {
47
+ chromeUserDataDir: string;
48
+ profileDirectory: string;
49
+ },
50
+ dependencies?: ReadCookieHostsDependencies
51
+ ) => Promise<string[]>;
52
+ };
53
+
54
+ export type ChromeSite = {
55
+ host: string;
56
+ url: string;
57
+ };
58
+
59
+ export type ChromeProfileSites = ChromeProfile & {
60
+ sites: ChromeSite[];
61
+ };
62
+
63
+ function loadDatabaseConstructor(): DatabaseConstructor {
64
+ const sqlite = require("node:sqlite") as {
65
+ DatabaseSync: DatabaseConstructor;
66
+ };
67
+
68
+ return sqlite.DatabaseSync;
69
+ }
70
+
71
+ export function normalizeCookieHosts(hosts: string[]): string[] {
72
+ const seen = new Set<string>();
73
+ const normalized: string[] = [];
74
+
75
+ for (const host of hosts) {
76
+ const candidate = host.trim().toLowerCase().replace(/^\.+/, "");
77
+
78
+ if (!candidate || seen.has(candidate)) {
79
+ continue;
80
+ }
81
+
82
+ seen.add(candidate);
83
+ normalized.push(candidate);
84
+ }
85
+
86
+ return normalized.sort((left, right) => left.localeCompare(right));
87
+ }
88
+
89
+ export function siteHostToUrl(host: string): string {
90
+ const ipCandidate =
91
+ host.startsWith("[") && host.endsWith("]") ? host.slice(1, -1) : host;
92
+ const protocol =
93
+ host === "localhost" || net.isIP(ipCandidate) !== 0 ? "http" : "https";
94
+
95
+ return `${protocol}://${host}`;
96
+ }
97
+
98
+ export async function listChromeProfileCookieUrls(
99
+ options: {
100
+ profileDirectory: string;
101
+ },
102
+ dependencies: ListChromeProfileCookieUrlsDependencies = {}
103
+ ): Promise<string[]> {
104
+ const chromeUserDataDir =
105
+ dependencies.chromeUserDataDir ?? defaultChromeUserDataDir();
106
+ const readCookieHosts =
107
+ dependencies.readCookieHosts ?? readCookieHostsForChromeProfile;
108
+ const hosts = await readCookieHosts(
109
+ {
110
+ chromeUserDataDir,
111
+ profileDirectory: options.profileDirectory,
112
+ },
113
+ {
114
+ ...dependencies,
115
+ chromeUserDataDir,
116
+ }
117
+ );
118
+
119
+ return hosts.map(siteHostToUrl);
120
+ }
121
+
122
+ export function resolveChromeCookiesDatabasePath(
123
+ options: {
124
+ chromeUserDataDir: string;
125
+ profileDirectory: string;
126
+ },
127
+ dependencies: {
128
+ pathExists?: (targetPath: string) => boolean;
129
+ } = {}
130
+ ): string | undefined {
131
+ const pathExists = dependencies.pathExists ?? fs.existsSync;
132
+ const profileDir = path.join(options.chromeUserDataDir, options.profileDirectory);
133
+ const candidates = [
134
+ path.join(profileDir, "Network", "Cookies"),
135
+ path.join(profileDir, "Cookies"),
136
+ ];
137
+
138
+ return candidates.find((candidate) => pathExists(candidate));
139
+ }
140
+
141
+ async function readCookieHostsForChromeProfile(
142
+ options: {
143
+ chromeUserDataDir: string;
144
+ profileDirectory: string;
145
+ },
146
+ dependencies: ReadCookieHostsDependencies = {}
147
+ ): Promise<string[]> {
148
+ const pathExists = dependencies.pathExists ?? fs.existsSync;
149
+ const makeTempDir = dependencies.makeTempDir ?? fs.mkdtempSync;
150
+ const copyFile = dependencies.copyFile ?? fs.copyFileSync;
151
+ const removeDir = dependencies.removeDir ?? fs.rmSync;
152
+ const queryHosts = dependencies.queryHosts ?? queryDistinctCookieHosts;
153
+ const sourcePath = resolveChromeCookiesDatabasePath(options, {
154
+ pathExists,
155
+ });
156
+
157
+ if (!sourcePath) {
158
+ return [];
159
+ }
160
+
161
+ const tempRoot = makeTempDir(path.join(os.tmpdir(), "cloak-cookie-db-"));
162
+ const stagedPath = path.join(tempRoot, path.basename(sourcePath));
163
+
164
+ try {
165
+ copyFile(sourcePath, stagedPath);
166
+ copyOptionalSidecar(`${sourcePath}-wal`, `${stagedPath}-wal`, pathExists, copyFile);
167
+ copyOptionalSidecar(`${sourcePath}-shm`, `${stagedPath}-shm`, pathExists, copyFile);
168
+
169
+ const hosts = await queryHosts(stagedPath);
170
+ return normalizeCookieHosts(hosts);
171
+ } catch {
172
+ return [];
173
+ } finally {
174
+ removeDir(tempRoot, { recursive: true, force: true });
175
+ }
176
+ }
177
+
178
+ export async function listChromeProfileSites(
179
+ dependencies: ListChromeProfileSitesDependencies = {}
180
+ ): Promise<ChromeProfileSites[]> {
181
+ const chromeUserDataDir =
182
+ dependencies.chromeUserDataDir ?? defaultChromeUserDataDir();
183
+ const listProfiles = dependencies.listProfiles ?? listChromeProfiles;
184
+ const readCookieHosts =
185
+ dependencies.readCookieHosts ?? readCookieHostsForChromeProfile;
186
+ const profiles = listProfiles({ chromeUserDataDir });
187
+
188
+ return Promise.all(
189
+ profiles.map(async (profile) => {
190
+ const hosts = await readCookieHosts(
191
+ {
192
+ chromeUserDataDir,
193
+ profileDirectory: profile.directory,
194
+ },
195
+ {
196
+ chromeUserDataDir,
197
+ }
198
+ );
199
+
200
+ return {
201
+ ...profile,
202
+ sites: hosts.map((host) => ({
203
+ host,
204
+ url: siteHostToUrl(host),
205
+ })),
206
+ };
207
+ })
208
+ );
209
+ }
210
+
211
+ export function formatChromeProfileSitesReport(
212
+ profiles: ChromeProfileSites[]
213
+ ): string {
214
+ if (profiles.length === 0) {
215
+ return "No Chrome profiles found.\n";
216
+ }
217
+
218
+ const lines: string[] = [];
219
+
220
+ for (const profile of profiles) {
221
+ const label = profile.accountName ?? profile.name;
222
+ const countLabel = `${profile.sites.length} ${profile.sites.length === 1 ? "site" : "sites"}`;
223
+ lines.push(`${profile.directory}: ${label} (${countLabel})`);
224
+
225
+ if (profile.sites.length === 0) {
226
+ lines.push(" (no cookie-bearing sites found)");
227
+ } else {
228
+ for (const site of profile.sites) {
229
+ lines.push(` ${site.host}`);
230
+ }
231
+ }
232
+
233
+ lines.push("");
234
+ }
235
+
236
+ return `${lines.join("\n").trimEnd()}\n`;
237
+ }
238
+
239
+ function copyOptionalSidecar(
240
+ sourcePath: string,
241
+ targetPath: string,
242
+ pathExists: (targetPath: string) => boolean,
243
+ copyFile: (sourcePath: string, targetPath: string) => void
244
+ ) {
245
+ if (!pathExists(sourcePath)) {
246
+ return;
247
+ }
248
+
249
+ copyFile(sourcePath, targetPath);
250
+ }
251
+
252
+ async function queryDistinctCookieHosts(databasePath: string): Promise<string[]> {
253
+ const DatabaseSync = loadDatabaseConstructor();
254
+ const database = new DatabaseSync(databasePath);
255
+
256
+ try {
257
+ const rows = database
258
+ .prepare(
259
+ [
260
+ "SELECT DISTINCT host_key",
261
+ "FROM cookies",
262
+ "WHERE host_key IS NOT NULL AND host_key != ''",
263
+ "ORDER BY host_key COLLATE NOCASE",
264
+ ].join(" ")
265
+ )
266
+ .all() as QueryRow[];
267
+
268
+ return rows
269
+ .map((row) => row.host_key ?? "")
270
+ .filter((host) => host.length > 0);
271
+ } finally {
272
+ database.close();
273
+ }
274
+ }
@@ -0,0 +1,92 @@
1
+ import path from "node:path";
2
+ import fs from "node:fs";
3
+ import os from "node:os";
4
+
5
+ export type ChromeProfile = {
6
+ directory: string;
7
+ name: string;
8
+ accountName?: string;
9
+ };
10
+
11
+ type Dependencies = {
12
+ chromeUserDataDir?: string;
13
+ pathExists?: (path: string) => boolean;
14
+ readdir?: (dir: string) => string[];
15
+ readFile?: (path: string) => string;
16
+ };
17
+
18
+ export function defaultChromeUserDataDir(): string {
19
+ const platform = os.platform();
20
+ const home = os.homedir();
21
+
22
+ if (platform === "darwin") {
23
+ return path.join(home, "Library", "Application Support", "Google", "Chrome");
24
+ }
25
+
26
+ if (platform === "win32") {
27
+ return path.join(home, "AppData", "Local", "Google", "Chrome", "User Data");
28
+ }
29
+
30
+ // Linux
31
+ return path.join(home, ".config", "google-chrome");
32
+ }
33
+
34
+ function readProfileInfo(
35
+ prefsPath: string,
36
+ readFile: (p: string) => string
37
+ ): { name: string; accountName?: string } | undefined {
38
+ try {
39
+ const raw = readFile(prefsPath);
40
+ const prefs = JSON.parse(raw);
41
+ const name = prefs?.profile?.name;
42
+ if (name === undefined) return undefined;
43
+ const accountName = prefs?.account_info?.[0]?.full_name || undefined;
44
+ return { name, accountName };
45
+ } catch {
46
+ return undefined;
47
+ }
48
+ }
49
+
50
+ export function listChromeProfiles(dependencies: Dependencies = {}): ChromeProfile[] {
51
+ const userDataDir = dependencies.chromeUserDataDir ?? defaultChromeUserDataDir();
52
+ const readdir = dependencies.readdir ?? ((dir: string) => fs.readdirSync(dir));
53
+ const readFile = dependencies.readFile ?? ((p: string) => fs.readFileSync(p, "utf8"));
54
+
55
+ let entries: string[];
56
+ try {
57
+ entries = readdir(userDataDir);
58
+ } catch {
59
+ return [];
60
+ }
61
+
62
+ const profiles: ChromeProfile[] = [];
63
+
64
+ for (const entry of entries) {
65
+ if (entry !== "Default" && !entry.startsWith("Profile ")) {
66
+ continue;
67
+ }
68
+
69
+ const prefsPath = path.join(userDataDir, entry, "Preferences");
70
+ const info = readProfileInfo(prefsPath, readFile);
71
+
72
+ if (info !== undefined) {
73
+ profiles.push({
74
+ directory: entry,
75
+ name: info.name,
76
+ ...(info.accountName ? { accountName: info.accountName } : {}),
77
+ });
78
+ }
79
+ }
80
+
81
+ profiles.sort((a, b) => a.directory.localeCompare(b.directory));
82
+ return profiles;
83
+ }
84
+
85
+ export function hasChromeUserDataDir(
86
+ dependencies: Pick<Dependencies, "chromeUserDataDir" | "pathExists"> = {}
87
+ ): boolean {
88
+ const userDataDir = dependencies.chromeUserDataDir ?? defaultChromeUserDataDir();
89
+ const pathExists = dependencies.pathExists ?? fs.existsSync;
90
+
91
+ return pathExists(userDataDir);
92
+ }