@mherod/get-cookie 2.1.0 → 2.1.2

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.
Files changed (57) hide show
  1. package/.parcelrc +2 -7
  2. package/bun.lockb +0 -0
  3. package/dist/cli.js +18319 -2
  4. package/dist/index.js +24914 -1
  5. package/dist/types.d.ts +361 -30
  6. package/package-lock.json +3997 -6448
  7. package/package.json +8 -54
  8. package/src/CookieRow.ts +35 -7
  9. package/src/CookieSpec.ts +32 -14
  10. package/src/SpecialCases.ts +6 -5
  11. package/src/StringToRegex.ts +4 -4
  12. package/src/browsers/CompositeCookieQueryStrategy.ts +34 -28
  13. package/src/browsers/CookieQueryStrategy.ts +1 -1
  14. package/src/browsers/CookieStoreQueryStrategy.ts +71 -55
  15. package/src/browsers/QuerySqliteThenTransform.ts +27 -64
  16. package/src/browsers/chrome/ChromeApplicationSupport.ts +4 -0
  17. package/src/browsers/chrome/ChromeCookieQueryStrategy.ts +118 -91
  18. package/src/browsers/chrome/decrypt.ts +133 -97
  19. package/src/browsers/chrome/getChromePassword.ts +22 -5
  20. package/src/browsers/firefox/FirefoxCookieQueryStrategy.ts +19 -13
  21. package/src/browsers/getEncryptedChromeCookie.ts +101 -66
  22. package/src/browsers/mock/MockCookieQueryStrategy.ts +8 -4
  23. package/src/browsers/safari/SafariCookieQueryStrategy.ts +34 -3
  24. package/src/cli.ts +36 -29
  25. package/src/cliQueryCookies.ts +14 -4
  26. package/src/comboQueryCookieSpec.ts +7 -10
  27. package/src/cookieQueryOptions.ts +8 -1
  28. package/src/cookieSpecsFromUrl.ts +53 -7
  29. package/src/decodeBinaryCookies.ts +72 -0
  30. package/src/execSimple.ts +1 -2
  31. package/src/fetchWithCookies.ts +172 -144
  32. package/src/findAllFiles.ts +10 -0
  33. package/src/getChromeCookie.ts +6 -8
  34. package/src/getCookie.ts +8 -8
  35. package/src/getFirefoxCookie.ts +13 -9
  36. package/src/getGroupedRenderedCookies.ts +29 -6
  37. package/src/getMergedRenderedCookies.ts +4 -2
  38. package/src/global.ts +2 -2
  39. package/src/index.ts +16 -6
  40. package/src/isValidJwt.ts +6 -4
  41. package/src/listChromeProfiles.ts +18 -10
  42. package/src/logger.ts +0 -1
  43. package/src/processBeforeReturn.ts +15 -12
  44. package/src/queryCookies.ts +22 -17
  45. package/src/resultsRendered.ts +20 -7
  46. package/src/unpackHeaders.ts +10 -8
  47. package/src/util/flatMapAsync.ts +20 -16
  48. package/tsconfig.json +4 -4
  49. package/dist/index.js.map +0 -1
  50. package/dist/module.js +0 -1446
  51. package/dist/module.js.map +0 -1
  52. package/dist/prompt.2b8c61c0.js +0 -40
  53. package/dist/prompt.536a2c51.js +0 -40
  54. package/dist/prompt.fb2c7dad.js.map +0 -1
  55. package/dist/types.d.ts.map +0 -1
  56. package/src/CookieStore.ts +0 -27
  57. package/src/FileCookieStore.ts +0 -178
@@ -7,81 +7,116 @@ import { stringToRegex } from "../StringToRegex";
7
7
  import CookieRow from "../CookieRow";
8
8
  import consola from "../logger";
9
9
 
10
- export async function getEncryptedChromeCookie({
11
- name,
12
- domain,
13
- file = join(chromeApplicationSupport, "Default", "Cookies"),
14
- }: {
10
+ interface GetEncryptedChromeCookieParams {
15
11
  name: string;
16
12
  domain: string;
17
- file: string;
18
- }): Promise<CookieRow[]> {
19
- if (!existsSync(file)) {
20
- throw new Error(`File ${file} does not exist`);
13
+ file?: string;
14
+ }
15
+
16
+ interface SqlQueryBuilderInterface {
17
+ build(): string;
18
+ addCondition(field: string, value: string, operator?: string): void;
19
+ }
20
+
21
+ class SqlQueryBuilder implements SqlQueryBuilderInterface {
22
+ private baseQuery: string;
23
+ private conditions: string[];
24
+
25
+ constructor(
26
+ baseQuery: string = "SELECT encrypted_value, name, host_key, expires_utc FROM cookies",
27
+ ) {
28
+ this.baseQuery = baseQuery;
29
+ this.conditions = [];
21
30
  }
22
- if (parsedArgs.verbose) {
23
- const s = file.split("/").slice(-3).join("/");
24
- consola.start(
25
- `Trying Chrome (at ${s}) cookie ${name} for domain ${domain}`,
26
- );
31
+
32
+ addCondition(field: string, value: string, operator: string = "="): void {
33
+ const wildcardRegexp = /^([*%])$/i;
34
+ if (!wildcardRegexp.test(value)) {
35
+ const condition = `${field} ${operator} '${value}'`;
36
+ this.conditions.push(condition);
37
+ }
27
38
  }
28
- let sql;
29
- //language=SQL
30
- sql = "SELECT encrypted_value, name, host_key, expires_utc FROM cookies";
31
- // Define a regular expression to match wildcard characters
32
- const wildcardRegexp = /^([*%])$/i;
33
- // Check if the name does not contain wildcard characters
34
- const specifiedName = name.match(wildcardRegexp) == null;
35
- // Check if the domain does not contain wildcard characters
36
- const specifiedDomain = domain.match(wildcardRegexp) == null;
37
- // Check if the domain contains wildcard characters
38
- const wildcardDomain = domain.match(/[%*]/) != null;
39
- // Determine if we should query the domain based on the previous checks
40
- const queryDomain = specifiedDomain && !wildcardDomain;
41
- // if we have a wildcard domain, we need to use a regexp
42
- // If the name is specified or we need to query the domain, we add a WHERE clause to the SQL query
43
- if (specifiedName || queryDomain) {
44
- sql += ` WHERE `;
45
- // If the name is specified, we add a condition to the SQL query to match the name
46
- if (specifiedName) {
47
- sql += `name = '${name}'`;
48
- // If we also need to query the domain, we add an AND operator to the SQL query
49
- if (queryDomain) {
50
- sql += ` AND `;
51
- }
39
+
40
+ build(): string {
41
+ let sql = this.baseQuery;
42
+ if (this.conditions.length > 0) {
43
+ sql += ` WHERE ${this.conditions.join(" AND ")}`;
52
44
  }
53
- // If we need to query the domain, we add a condition to the SQL query to match the domain
54
- if (queryDomain) {
55
- // The leading dot is replaced with % to match subdomains
56
- const sqlEmbedDomain = domain.replace(/^[.%]?/, "%");
57
- sql += `host_key LIKE '${sqlEmbedDomain}';`;
45
+ return sql;
46
+ }
47
+ }
48
+
49
+ interface LoggerInterface {
50
+ log(file: string, name: string, domain: string, sql: string): void;
51
+ }
52
+
53
+ class VerboseLogger implements LoggerInterface {
54
+ log(file: string, name: string, domain: string, sql: string): void {
55
+ if (parsedArgs.verbose) {
56
+ const s = file.split("/").slice(-3).join("/");
57
+ consola.start(
58
+ `Trying Chrome (at ${s}) cookie ${name} for domain ${domain}`,
59
+ );
58
60
  }
59
61
  }
60
- if (parsedArgs.verbose) {
61
- consola.info("Querying:", sql);
62
+ }
63
+
64
+ interface RowTransformerInterface {
65
+ transform(row: any): CookieRow;
66
+ }
67
+
68
+ class RowTransformer implements RowTransformerInterface {
69
+ transform(row: any): CookieRow {
70
+ const cookieRow: CookieRow = {
71
+ expiry: (row["expires_utc"] / 1000000 - 11644473600) * 1000,
72
+ domain: row["host_key"],
73
+ name: row["name"],
74
+ value: row["encrypted_value"],
75
+ };
76
+ return cookieRow;
77
+ }
78
+ }
79
+
80
+ export async function getEncryptedChromeCookie({
81
+ name,
82
+ domain,
83
+ file = join(chromeApplicationSupport, "Default", "Cookies"),
84
+ }: GetEncryptedChromeCookieParams): Promise<CookieRow[]> {
85
+ if (!existsSync(file)) {
86
+ throw new Error(`File ${file} does not exist`);
87
+ }
88
+
89
+ const sqlQueryBuilder: SqlQueryBuilderInterface = new SqlQueryBuilder();
90
+ sqlQueryBuilder.addCondition("name", name);
91
+ const wildcardDomain = /[%*]/.test(domain);
92
+ if (!wildcardDomain) {
93
+ const sqlEmbedDomain = domain.replace(/^[.%]?/, "%");
94
+ sqlQueryBuilder.addCondition("host_key", sqlEmbedDomain, "LIKE");
62
95
  }
96
+ const sql: string = sqlQueryBuilder.build();
97
+
98
+ const verboseLogger: LoggerInterface = new VerboseLogger();
99
+ verboseLogger.log(file, name, domain, sql);
100
+
63
101
  const domainRegexp: RegExp = stringToRegex(domain);
64
- const sqliteQuery1: CookieRow[] = await querySqliteThenTransform({
65
- file: file,
66
- sql: sql,
67
- rowFilter: (row) => {
68
- return row["host_key"].match(domainRegexp) != null;
69
- },
70
- rowTransform: (row) => {
71
- const cookieRow = {
72
- expiry: (row["expires_utc"] / 1000000 - 11644473600) * 1000,
73
- domain: row["host_key"],
74
- name: row["name"],
75
- value: row["encrypted_value"],
76
- };
77
- if (parsedArgs.verbose) {
78
- consola.info("Found", cookieRow);
79
- }
80
- return cookieRow;
81
- },
82
- });
83
- return sqliteQuery1.filter((row) => {
84
- // TODO: is this needed?
102
+ const rowTransformer: RowTransformerInterface = new RowTransformer();
103
+ let sqliteQuery1: CookieRow[];
104
+ try {
105
+ sqliteQuery1 = await querySqliteThenTransform({
106
+ file: file,
107
+ sql: sql,
108
+ rowFilter: (row) => {
109
+ return row["host_key"].match(domainRegexp) != null;
110
+ },
111
+ rowTransform: rowTransformer.transform.bind(rowTransformer),
112
+ });
113
+ } catch (error) {
114
+ throw error;
115
+ }
116
+
117
+ const filteredResults = sqliteQuery1.filter((row) => {
85
118
  return row.domain.match(domainRegexp) != null;
86
119
  });
120
+
121
+ return filteredResults;
87
122
  }
@@ -4,15 +4,19 @@ import CookieQueryStrategy from "../CookieQueryStrategy";
4
4
  export default class MockCookieQueryStrategy implements CookieQueryStrategy {
5
5
  browserName: string = "mock";
6
6
 
7
- private cookies: ExportedCookie[] = [];
7
+ private cookies: ExportedCookie[];
8
8
 
9
9
  constructor(cookies: ExportedCookie[]) {
10
10
  this.cookies = cookies;
11
11
  }
12
12
 
13
13
  async queryCookies(name: string, domain: string): Promise<ExportedCookie[]> {
14
- return this.cookies.filter((c) => {
15
- return c.name === name && c.domain === domain;
16
- });
14
+ const filteredCookies: ExportedCookie[] = [];
15
+ for (const cookie of this.cookies) {
16
+ if (cookie.name === name && cookie.domain === domain) {
17
+ filteredCookies.push(cookie);
18
+ }
19
+ }
20
+ return filteredCookies;
17
21
  }
18
22
  }
@@ -1,11 +1,42 @@
1
1
  import CookieQueryStrategy from "../CookieQueryStrategy";
2
2
  import ExportedCookie from "../../ExportedCookie";
3
+ import { join } from "path";
4
+ import { decodeBinaryCookies } from "../../decodeBinaryCookies"; // Assuming you have a module to decode binary cookies
5
+ import CookieRow from "../../CookieRow";
3
6
 
4
7
  export default class SafariCookieQueryStrategy implements CookieQueryStrategy {
5
- browserName = "Safari";
8
+ browserName: string = "Safari";
6
9
 
7
10
  async queryCookies(name: string, domain: string): Promise<ExportedCookie[]> {
8
- // TODO: implement
9
- return [];
11
+ const homeDir: string | undefined = process.env.HOME;
12
+ if (!homeDir) {
13
+ throw new Error("HOME environment variable is not set");
14
+ }
15
+
16
+ const cookieDbPath: string = join(
17
+ homeDir,
18
+ "Library",
19
+ "Cookies",
20
+ "Cookies.binarycookies",
21
+ );
22
+
23
+ try {
24
+ const cookies: CookieRow[] = await decodeBinaryCookies(cookieDbPath);
25
+
26
+ const filteredCookies: CookieRow[] = cookies.filter(
27
+ (cookie: CookieRow) => cookie.name === name && cookie.domain.includes(domain)
28
+ );
29
+
30
+ const exportedCookies: ExportedCookie[] = filteredCookies.map((cookie: CookieRow) => ({
31
+ domain: cookie.domain,
32
+ name: cookie.name,
33
+ value: cookie.value.toString("utf8"),
34
+ }));
35
+
36
+ return exportedCookies;
37
+ } catch (e) {
38
+ console.error(`Error decoding ${cookieDbPath}`, e);
39
+ return [];
40
+ }
10
41
  }
11
42
  }
package/src/cli.ts CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env ts-node
1
+ #!/usr/bin/env bun
2
2
 
3
3
  import { argv, parsedArgs } from "./argv";
4
4
  import { fetchWithCookies } from "./fetchWithCookies";
@@ -10,13 +10,19 @@ import { cliQueryCookies } from "./cliQueryCookies";
10
10
 
11
11
  async function main() {
12
12
  if (parsedArgs["help"] || parsedArgs["h"]) {
13
- logger.log(`Usage: ${argv[1]} [name] [domain] [options] `);
13
+ logger.log(`Usage: ${argv[1]} [name] [domain] [options]`);
14
14
  logger.log(`Options:`);
15
- logger.log(` -h, --help: Show this help`);
16
- logger.log(` -v, --verbose: Show verbose output`);
15
+ logger.log(` -h, --help: Show this help message`);
16
+ logger.log(` -v, --verbose: Enable verbose output`);
17
17
  logger.log(` -d, --dump: Dump all results`);
18
18
  logger.log(` -D, --dump-grouped: Dump all results, grouped by profile`);
19
19
  logger.log(` -r, --render: Render all results`);
20
+ logger.log(` -F, --fetch <url>: Fetch data from the specified URL`);
21
+ logger.log(` -H <header>: Specify headers for the fetch request`);
22
+ logger.log(
23
+ ` --dump-response-headers: Dump response headers from fetch request`,
24
+ );
25
+ logger.log(` --dump-response-body: Dump response body from fetch request`);
20
26
  return;
21
27
  }
22
28
 
@@ -26,7 +32,7 @@ async function main() {
26
32
  if (fetchUrl) {
27
33
  let url: URL;
28
34
  try {
29
- url = new URL(<string>fetchUrl);
35
+ url = new URL(fetchUrl);
30
36
  } catch (e) {
31
37
  logger.error("Invalid URL", fetchUrl);
32
38
  return;
@@ -34,34 +40,25 @@ async function main() {
34
40
  logger.start("Fetching", url.href);
35
41
  const headerArgs: string[] | string = parsedArgs["H"];
36
42
  const headers = unpackHeaders(headerArgs);
37
- const onfulfilled = (res: Response) => {
43
+ const onfulfilled = async (res: Response) => {
38
44
  if (parsedArgs["dump-response-headers"]) {
39
- res.headers.forEach((value: string, key: string) => {
45
+ for (const [key, value] of Object.entries(res.headers)) {
40
46
  logger.log(`${key}: ${value}`);
41
- });
47
+ }
42
48
  }
43
49
  if (parsedArgs["dump-response-body"]) {
44
- res.text().then((r) => {
45
- logger.log(r);
46
- });
50
+ const responseBody = await res.text();
51
+ logger.log(responseBody);
47
52
  }
48
- return;
49
53
  };
50
- return fetchWithCookies(
51
- url,
52
- {
53
- //
54
- headers,
55
- },
56
- //
57
- ).then(
58
- (res) => {
59
- logger.debug("Response", res);
60
- onfulfilled(res);
61
- },
62
- logger.error,
63
- //
64
- );
54
+ try {
55
+ const res = await fetchWithCookies(url, { headers });
56
+ logger.debug("Response", res);
57
+ await onfulfilled(res);
58
+ } catch (error) {
59
+ logger.error(error);
60
+ }
61
+ return;
65
62
  }
66
63
 
67
64
  const cookieSpecs: CookieSpec[] = [];
@@ -81,7 +78,17 @@ async function main() {
81
78
  logger.log("cookieSpecs", cookieSpecs);
82
79
  }
83
80
 
84
- await cliQueryCookies(cookieSpecs).catch(logger.error);
81
+ try {
82
+ await cliQueryCookies(cookieSpecs);
83
+ } catch (error) {
84
+ logger.error(error);
85
+ }
85
86
  }
86
87
 
87
- main().then((r) => r, logger.error);
88
+ main().then(
89
+ () => process.exit(0),
90
+ (error) => {
91
+ logger.error(error);
92
+ process.exit(1);
93
+ },
94
+ );
@@ -10,26 +10,29 @@ export async function cliQueryCookies(
10
10
  cookieSpec: CookieSpec | CookieSpec[],
11
11
  limit?: number,
12
12
  removeExpired?: boolean,
13
- //
14
13
  ) {
15
14
  try {
16
15
  const results: ExportedCookie[] = await comboQueryCookieSpec(cookieSpec, {
17
16
  limit,
18
17
  removeExpired,
19
18
  });
20
- if (results == null || results.length == 0) {
19
+
20
+ if (!results || results.length === 0) {
21
21
  logger.error("No results");
22
22
  return;
23
23
  }
24
+
24
25
  if (parsedArgs["dump"] || parsedArgs["d"]) {
25
26
  logger.log(results);
26
27
  return;
27
28
  }
29
+
28
30
  if (parsedArgs["dump-grouped"] || parsedArgs["D"]) {
29
31
  const groupedByFile = groupBy(results, (r) => r.meta?.file);
30
32
  logger.log(JSON.stringify(groupedByFile, null, 2));
31
33
  return;
32
34
  }
35
+
33
36
  if (
34
37
  parsedArgs["render"] ||
35
38
  parsedArgs["render-merged"] ||
@@ -38,14 +41,21 @@ export async function cliQueryCookies(
38
41
  logger.log(resultsRendered(results));
39
42
  return;
40
43
  }
44
+
41
45
  if (parsedArgs["render-grouped"] || parsedArgs["R"]) {
42
46
  const groupedByFile = groupBy(results, (r) => r.meta?.file);
43
47
  for (const file of Object.keys(groupedByFile)) {
44
- let results = groupedByFile[file];
45
- logger.log(file, ": ", resultsRendered(results));
48
+ const fileResults = groupedByFile[file];
49
+ logger.log(`${file}: ${resultsRendered(fileResults)}`);
46
50
  }
47
51
  return;
48
52
  }
53
+
54
+ if (parsedArgs["output"] === "json") {
55
+ logger.log(JSON.stringify(results, null, 2));
56
+ return;
57
+ }
58
+
49
59
  for (const result of results) {
50
60
  logger.log(result.value);
51
61
  }
@@ -12,18 +12,15 @@ export async function comboQueryCookieSpec(
12
12
  ): Promise<ExportedCookie[]> {
13
13
  const optsWithDefaults: CookieQueryOptions<CookieQueryStrategy> =
14
14
  mergedWithDefaults(options);
15
- const fn = (cs: CookieSpec) => queryCookies(cs, optsWithDefaults);
15
+ const queryFn = async (cs: CookieSpec): Promise<ExportedCookie[]> =>
16
+ queryCookies(cs, optsWithDefaults);
16
17
 
18
+ let cookies: ExportedCookie[];
17
19
  if (Array.isArray(cookieSpec)) {
18
- const cookiesForMultiSpec: ExportedCookie[] = await flatMapAsync(
19
- cookieSpec,
20
- async (cs: CookieSpec) => {
21
- return await fn(cs);
22
- },
23
- );
24
- return processBeforeReturn(cookiesForMultiSpec, options);
20
+ cookies = await flatMapAsync(cookieSpec, queryFn);
25
21
  } else {
26
- const cookiesForSingleSpec: ExportedCookie[] = await fn(cookieSpec);
27
- return processBeforeReturn(cookiesForSingleSpec, options);
22
+ cookies = await queryFn(cookieSpec);
28
23
  }
24
+
25
+ return processBeforeReturn(cookies, options);
29
26
  }
@@ -11,10 +11,17 @@ export type CookieQueryOptions<T extends CookieQueryStrategy> = {
11
11
  export const defaultCookieQueryOptions: CookieQueryOptions<CookieQueryStrategy> =
12
12
  {
13
13
  strategy: new CompositeCookieQueryStrategy(),
14
+ limit: undefined,
15
+ removeExpired: undefined,
14
16
  };
15
17
 
16
18
  export function mergedWithDefaults<T extends CookieQueryStrategy>(
17
19
  options?: CookieQueryOptions<T>,
18
20
  ): CookieQueryOptions<T> {
19
- return merge(defaultCookieQueryOptions, options);
21
+ const mergedOptions: CookieQueryOptions<T> = merge(
22
+ {},
23
+ defaultCookieQueryOptions,
24
+ options,
25
+ );
26
+ return mergedOptions;
20
27
  }
@@ -13,15 +13,61 @@ export function cookieSpecsFromUrl(url: URL | string): CookieSpec[] {
13
13
  return [];
14
14
  }
15
15
 
16
- const urlObj = typeof url === "string" ? new URL(url) : url;
17
- const hostnameParts = urlObj.hostname.split(".");
18
- const topLevelDomain = hostnameParts.slice(-2).join(".");
16
+ const urlObj: URL = parseUrl(url);
17
+ const hostnameParts: string[] = splitHostname(urlObj.hostname);
18
+ const topLevelDomain: string = getTopLevelDomain(hostnameParts);
19
19
 
20
- const cookieSpecs: CookieSpec[] = [
20
+ const cookieSpecs: CookieSpec[] = createCookieSpecs(
21
+ urlObj.hostname,
22
+ topLevelDomain,
23
+ );
24
+
25
+ return uniqBy(
26
+ cookieSpecs,
27
+ (spec: CookieSpec) => `${spec.name}:${spec.domain}`,
28
+ );
29
+ }
30
+
31
+ /**
32
+ * Parses the input URL string or URL object and returns a URL object.
33
+ * @param url - The URL to parse.
34
+ * @returns A URL object.
35
+ */
36
+ function parseUrl(url: URL | string): URL {
37
+ return typeof url === "string" ? new URL(url) : url;
38
+ }
39
+
40
+ /**
41
+ * Splits the hostname into its constituent parts.
42
+ * @param hostname - The hostname to split.
43
+ * @returns An array of strings representing the parts of the hostname.
44
+ */
45
+ function splitHostname(hostname: string): string[] {
46
+ return hostname.split(".");
47
+ }
48
+
49
+ /**
50
+ * Extracts the top-level domain from the hostname parts.
51
+ * @param hostnameParts - The parts of the hostname.
52
+ * @returns A string representing the top-level domain.
53
+ */
54
+ function getTopLevelDomain(hostnameParts: string[]): string {
55
+ return hostnameParts.slice(-2).join(".");
56
+ }
57
+
58
+ /**
59
+ * Creates cookie specifications based on the hostname and top-level domain.
60
+ * @param hostname - The full hostname.
61
+ * @param topLevelDomain - The top-level domain.
62
+ * @returns An array of CookieSpec objects.
63
+ */
64
+ function createCookieSpecs(
65
+ hostname: string,
66
+ topLevelDomain: string,
67
+ ): CookieSpec[] {
68
+ return [
21
69
  { name: "%", domain: `%.${topLevelDomain}` },
22
- { name: "%", domain: urlObj.hostname },
70
+ { name: "%", domain: hostname },
23
71
  { name: "%", domain: topLevelDomain },
24
72
  ];
25
-
26
- return uniqBy(cookieSpecs, JSON.stringify);
27
73
  }
@@ -0,0 +1,72 @@
1
+ import CookieRow from "./CookieRow";
2
+ import fs from "fs/promises";
3
+
4
+ export const decodeBinaryCookies = async (cookieDbPath: string): Promise<CookieRow[]> => {
5
+ try {
6
+ // Check if file exists
7
+ await fs.access(cookieDbPath);
8
+ } catch {
9
+ // If file doesn't exist, return empty array
10
+ return [];
11
+ }
12
+
13
+ // Magic bytes: "COOK" = 0x636F6F6B
14
+ const magicBytes: Buffer = Buffer.from([0x63, 0x6F, 0x6B, 0x6B]);
15
+ const buffer: Buffer = await fs.readFile(cookieDbPath);
16
+
17
+ if (!buffer.slice(0, 4).equals(magicBytes)) {
18
+ throw new Error("Not a cookie file");
19
+ }
20
+
21
+ const count: number = buffer.readUInt32BE(4);
22
+ const cookies: CookieRow[] = [];
23
+
24
+ let offset: number = 8;
25
+ for (let i: number = 0; i < count; i++) {
26
+ const pageSize: number = buffer.readUInt32BE(offset);
27
+ offset += 4;
28
+ const page: Buffer = buffer.slice(offset, offset + pageSize);
29
+ offset += pageSize;
30
+
31
+ if (!page.slice(0, 4).equals(Buffer.from([0x00, 0x00, 0x01, 0x00]))) {
32
+ throw new Error("Bad page header");
33
+ }
34
+
35
+ const cookieCount: number = page.readUInt32LE(4);
36
+ let pageOffset: number = 8;
37
+ for (let j: number = 0; j < cookieCount; j++) {
38
+ const cookieOffset: number = page.readUInt32LE(pageOffset);
39
+ pageOffset += 4;
40
+ const cookieLength: number = page.readUInt32LE(cookieOffset);
41
+ const cookie: Buffer = page.slice(cookieOffset, cookieOffset + cookieLength);
42
+
43
+ const flags: number = cookie.readUInt32LE(8);
44
+ const urlOffset: number = cookie.readUInt32LE(16);
45
+ const nameOffset: number = cookie.readUInt32LE(20);
46
+ const valueOffset: number = cookie.readUInt32LE(28);
47
+ const expiry: number = cookie.readDoubleLE(40) + 978307200;
48
+
49
+ const url: string = cookie.slice(urlOffset, nameOffset).toString('utf8').replace(/\0/g, '');
50
+ const name: string = cookie.slice(nameOffset, valueOffset).toString('utf8').replace(/\0/g, '');
51
+ const value: string = cookie.slice(valueOffset, cookie.length).toString('utf8').replace(/\0/g, '');
52
+
53
+ cookies.push({
54
+ domain: url,
55
+ name: name,
56
+ value: Buffer.from(value, 'utf8'),
57
+ expiry: new Date(expiry * 1000).getTime(),
58
+ meta: {
59
+ path: cookie.slice(cookie.readUInt32LE(24), valueOffset).toString('utf8').replace(/\0/g, ''),
60
+ httpOnly: (flags & 0x04) === 0x04,
61
+ secure: (flags & 0x01) === 0x01
62
+ }
63
+ });
64
+ }
65
+
66
+ if (!page.slice(cookieCount * 4 + 8, cookieCount * 4 + 12).equals(Buffer.from([0x00, 0x00, 0x00, 0x00]))) {
67
+ throw new Error("Bad page trailer");
68
+ }
69
+ }
70
+
71
+ return cookies;
72
+ };
package/src/execSimple.ts CHANGED
@@ -1,7 +1,6 @@
1
- import { execSync } from "child_process";
2
-
3
1
  export async function execSimple(command: string): Promise<string> {
4
2
  try {
3
+ const { execSync } = await import("child_process");
5
4
  const stdout = execSync(command, {
6
5
  encoding: "binary",
7
6
  maxBuffer: 5 * 1024,