feat: add Firefox extract methods and complete data model fields (#527)

* feat: add Firefox extract methods and complete data model fields

Firefox extract methods:
- extractPasswords: JSON + ASN1PBE decryption via decryptPBE helper
- extractCookies: SQLite, plaintext (no encryption), journalOff
- extractHistories: SQLite, visit count ASC sort (matches old behavior)
- extractDownloads: SQLite, moz_annos JOIN with JSON content parsing
- extractBookmarks: SQLite, moz_bookmarks JOIN moz_places
- extractExtensions: JSON, filter by location=app-profile
- extractLocalStorage: SQLite webappsstore2, reversed originKey parsing

Complete data model fields (union of Chromium and Firefox):
- CookieEntry: add HasExpire, IsPersistent
- DownloadEntry: add MimeType
- CreditCardEntry: add NickName, Address
- ExtensionEntry: add HomepageURL, Enabled

Update Chromium extractors to populate new fields:
- extract_cookie.go: fill HasExpire, IsPersistent
- extract_download.go: SELECT and fill mime_type
- extract_creditcard.go: SELECT nickname, billing_address_id
- extract_extension.go: fill HomepageURL, Enabled (state==1)

Tests:
- Full test coverage for all 7 Firefox extract functions
- Password test uses known ASN1PBE test vectors from crypto package
- Table-driven tests for parseOriginKey
- Updated Chromium tests for new fields

* fix: add COALESCE for nullable bookmark title in Firefox query

Firefox moz_bookmarks.title can be NULL (PR #500 fixed this in old code).
Add COALESCE to handle NULL gracefully in SQL instead of relying on
driver-specific NULL→string conversion behavior.

* fix: enable journalOff for all Firefox SQLite extractors and populate cookie flags

- Set journalOff=true for extract_history, extract_download, extract_bookmark
  (Firefox databases require PRAGMA journal_mode=off to avoid lock errors)
- Populate HasExpire and IsPersistent for Firefox cookies (derived from expiry>0)
- Add test assertions for HasExpire/IsPersistent in both Chromium and Firefox
This commit is contained in:
Roger
2026-03-30 20:52:11 +08:00
committed by moonD4rk
parent 2c4e871e59
commit 1ec2781131
25 changed files with 937 additions and 38 deletions
+10 -8
View File
@@ -34,14 +34,16 @@ func extractCookies(masterKey []byte, path string) ([]types.CookieEntry, error)
value, _ := decryptValue(masterKey, encryptedValue)
value = stripCookieHash(value, host)
return types.CookieEntry{
Name: name,
Host: host,
Path: cookiePath,
Value: string(value),
IsSecure: isSecure != 0,
IsHTTPOnly: isHTTPOnly != 0,
ExpireAt: typeutil.TimeEpoch(expireAt),
CreatedAt: typeutil.TimeEpoch(createdAt),
Name: name,
Host: host,
Path: cookiePath,
Value: string(value),
IsSecure: isSecure != 0,
IsHTTPOnly: isHTTPOnly != 0,
HasExpire: hasExpire != 0,
IsPersistent: isPersistent != 0,
ExpireAt: typeutil.TimeEpoch(expireAt),
CreatedAt: typeutil.TimeEpoch(createdAt),
}, nil
})
if err != nil {
+2
View File
@@ -27,6 +27,8 @@ func TestExtractCookies(t *testing.T) {
assert.Equal(t, "/api", got[0].Path)
assert.True(t, got[0].IsSecure)
assert.False(t, got[0].IsHTTPOnly)
assert.True(t, got[0].HasExpire)
assert.True(t, got[0].IsPersistent)
assert.False(t, got[0].CreatedAt.IsZero())
assert.True(t, got[0].ExpireAt.After(got[0].CreatedAt))
assert.True(t, got[1].IsHTTPOnly)
+5 -3
View File
@@ -8,14 +8,14 @@ import (
)
const defaultCreditCardQuery = `SELECT name_on_card, expiration_month, expiration_year,
card_number_encrypted FROM credit_cards`
card_number_encrypted, COALESCE(nickname, ''), COALESCE(billing_address_id, '') FROM credit_cards`
func extractCreditCards(masterKey []byte, path string) ([]types.CreditCardEntry, error) {
return sqliteutil.QueryRows(path, false, defaultCreditCardQuery,
func(rows *sql.Rows) (types.CreditCardEntry, error) {
var name, month, year string
var name, month, year, nickName, address string
var encNumber []byte
if err := rows.Scan(&name, &month, &year, &encNumber); err != nil {
if err := rows.Scan(&name, &month, &year, &encNumber, &nickName, &address); err != nil {
return types.CreditCardEntry{}, err
}
number, _ := decryptValue(masterKey, encNumber)
@@ -24,6 +24,8 @@ func extractCreditCards(masterKey []byte, path string) ([]types.CreditCardEntry,
Number: string(number),
ExpMonth: month,
ExpYear: year,
NickName: nickName,
Address: address,
}, nil
})
}
+2 -2
View File
@@ -9,8 +9,8 @@ import (
func TestExtractCreditCards(t *testing.T) {
path := createTestDB(t, "Web Data", creditCardsSchema,
insertCreditCard("John Doe", 12, 2025, ""),
insertCreditCard("Jane Smith", 6, 2027, ""),
insertCreditCard("John Doe", 12, 2025, "", "Johnny", "addr-1"),
insertCreditCard("Jane Smith", 6, 2027, "", "", ""),
)
got, err := extractCreditCards(nil, path)
+5 -3
View File
@@ -9,19 +9,21 @@ import (
"github.com/moond4rk/hackbrowserdata/utils/typeutil"
)
const defaultDownloadQuery = `SELECT target_path, tab_url, total_bytes, start_time, end_time FROM downloads`
const defaultDownloadQuery = `SELECT target_path, tab_url, total_bytes, start_time, end_time,
mime_type FROM downloads`
func extractDownloads(path string) ([]types.DownloadEntry, error) {
downloads, err := sqliteutil.QueryRows(path, false, defaultDownloadQuery,
func(rows *sql.Rows) (types.DownloadEntry, error) {
var targetPath, url string
var targetPath, url, mimeType string
var totalBytes, startTime, endTime int64
if err := rows.Scan(&targetPath, &url, &totalBytes, &startTime, &endTime); err != nil {
if err := rows.Scan(&targetPath, &url, &totalBytes, &startTime, &endTime, &mimeType); err != nil {
return types.DownloadEntry{}, err
}
return types.DownloadEntry{
URL: url,
TargetPath: targetPath,
MimeType: mimeType,
TotalBytes: totalBytes,
StartTime: typeutil.TimeEpoch(startTime),
EndTime: typeutil.TimeEpoch(endTime),
+3 -2
View File
@@ -9,8 +9,8 @@ import (
func TestExtractDownloads(t *testing.T) {
path := createTestDB(t, "History", downloadsSchema,
insertDownload("/tmp/old.zip", "https://old.com/file.zip", 1024, 13340000000000000, 13340000100000000),
insertDownload("/tmp/new.pdf", "https://new.com/doc.pdf", 2048, 13360000000000000, 13360000200000000),
insertDownload("/tmp/old.zip", "https://old.com/file.zip", "application/zip", 1024, 13340000000000000, 13340000100000000),
insertDownload("/tmp/new.pdf", "https://new.com/doc.pdf", "application/pdf", 2048, 13360000000000000, 13360000200000000),
)
got, err := extractDownloads(path)
@@ -23,6 +23,7 @@ func TestExtractDownloads(t *testing.T) {
// Verify field mapping
assert.Equal(t, "/tmp/new.pdf", got[0].TargetPath)
assert.Equal(t, "application/pdf", got[0].MimeType)
assert.Equal(t, int64(2048), got[0].TotalBytes)
assert.False(t, got[0].StartTime.IsZero())
assert.False(t, got[0].EndTime.IsZero())
+2
View File
@@ -51,6 +51,8 @@ func extractExtensions(path string) ([]types.ExtensionEntry, error) {
ID: id.String(),
Description: manifest.Get("description").String(),
Version: manifest.Get("version").String(),
HomepageURL: manifest.Get("homepage_url").String(),
Enabled: ext.Get("state").Int() == 1,
})
return true
})
+7 -7
View File
@@ -173,22 +173,22 @@ func insertURL(url, title string, visitCount int, lastVisitTime int64) string {
)
}
func insertDownload(targetPath, tabURL string, totalBytes, startTime, endTime int64) string {
func insertDownload(targetPath, tabURL, mimeType string, totalBytes, startTime, endTime int64) string {
return fmt.Sprintf(
`INSERT INTO downloads (id, guid, current_path, target_path, start_time, received_bytes,
total_bytes, state, danger_type, interrupt_reason, hash, end_time, opened, last_access_time,
transient, referrer, site_url, embedder_download_data, tab_url, tab_referrer_url,
http_method, by_ext_id, by_ext_name, by_web_app_id, etag, last_modified, mime_type, original_mime_type)
VALUES (NULL, '', '', '%s', %d, %d, %d, 1, 0, 0, x'', %d, 0, 0, 0, '', '', '', '%s', '', 'GET', '', '', '', '', '', '', '')`,
targetPath, startTime, totalBytes, totalBytes, endTime, tabURL,
VALUES (NULL, '', '', '%s', %d, %d, %d, 1, 0, 0, x'', %d, 0, 0, 0, '', '', '', '%s', '', 'GET', '', '', '', '', '', '%s', '')`,
targetPath, startTime, totalBytes, totalBytes, endTime, tabURL, mimeType,
)
}
func insertCreditCard(name string, month, year int, encNumberHex string) string {
func insertCreditCard(name string, month, year int, encNumberHex, nickName, address string) string {
return fmt.Sprintf(
`INSERT INTO credit_cards (guid, name_on_card, expiration_month, expiration_year, card_number_encrypted)
VALUES ('%s-%d-%d', '%s', %d, %d, x'%s')`,
name, month, year, name, month, year, encNumberHex,
`INSERT INTO credit_cards (guid, name_on_card, expiration_month, expiration_year, card_number_encrypted, nickname, billing_address_id)
VALUES ('%s-%d-%d', '%s', %d, %d, x'%s', '%s', '%s')`,
name, month, year, name, month, year, encNumberHex, nickName, address,
)
}