mirror of
https://github.com/moonD4rk/HackBrowserData.git
synced 2026-05-19 18:58:03 +02:00
92053b85b0
* chore: update golangci-lint config and fix lint issues
317 lines
8.8 KiB
Go
317 lines
8.8 KiB
Go
package output
|
|
|
|
import (
|
|
"encoding/csv"
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/moond4rk/hackbrowserdata/types"
|
|
)
|
|
|
|
var testTime = time.Date(2026, 1, 15, 10, 30, 0, 0, time.UTC)
|
|
|
|
func chromeData() *types.BrowserData {
|
|
return &types.BrowserData{
|
|
Passwords: []types.LoginEntry{
|
|
{URL: "https://example.com", Username: "alice", Password: "secret", CreatedAt: testTime},
|
|
},
|
|
Cookies: []types.CookieEntry{
|
|
{
|
|
Host: ".example.com", Path: "/", Name: "session", Value: "abc123",
|
|
IsSecure: true, IsHTTPOnly: true, HasExpire: true, IsPersistent: true,
|
|
ExpireAt: testTime, CreatedAt: testTime,
|
|
},
|
|
},
|
|
Histories: []types.HistoryEntry{
|
|
{URL: "https://example.com", Title: "Example", VisitCount: 5, LastVisit: testTime},
|
|
},
|
|
}
|
|
}
|
|
|
|
func firefoxData() *types.BrowserData {
|
|
return &types.BrowserData{
|
|
Passwords: []types.LoginEntry{
|
|
{URL: "https://reddit.com", Username: "bob", Password: "hunter2", CreatedAt: testTime},
|
|
},
|
|
Cookies: []types.CookieEntry{
|
|
{
|
|
Host: ".reddit.com", Path: "/", Name: "token", Value: "xyz789",
|
|
IsSecure: true, IsHTTPOnly: false, ExpireAt: testTime, CreatedAt: testTime,
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
// --- New ---
|
|
|
|
func TestNew(t *testing.T) {
|
|
tests := []struct {
|
|
format string
|
|
wantErr bool
|
|
}{
|
|
{"csv", false},
|
|
{"json", false},
|
|
{"cookie-editor", false},
|
|
{"unknown", true},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.format, func(t *testing.T) {
|
|
out, err := NewWriter(t.TempDir(), tt.format)
|
|
if tt.wantErr {
|
|
require.Error(t, err)
|
|
assert.Nil(t, out)
|
|
} else {
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, out)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// --- CSV output ---
|
|
|
|
func TestWrite_CSV_Password(t *testing.T) {
|
|
dir := t.TempDir()
|
|
out, err := NewWriter(dir, "csv")
|
|
require.NoError(t, err)
|
|
out.Add("Chrome", "Default", chromeData())
|
|
out.Add("Firefox", "abc123", firefoxData())
|
|
require.NoError(t, out.Write())
|
|
|
|
records := readCSV(t, filepath.Join(dir, "password.csv"))
|
|
require.Len(t, records, 3) // header + 2 rows
|
|
|
|
assert.Equal(t, []string{"browser", "profile", "url", "username", "password", "created_at"}, records[0])
|
|
assert.Equal(t, []string{"Chrome", "Default", "https://example.com", "alice", "secret", "2026-01-15T10:30:00Z"}, records[1])
|
|
assert.Equal(t, []string{"Firefox", "abc123", "https://reddit.com", "bob", "hunter2", "2026-01-15T10:30:00Z"}, records[2])
|
|
}
|
|
|
|
func TestWrite_CSV_Cookie(t *testing.T) {
|
|
dir := t.TempDir()
|
|
out, err := NewWriter(dir, "csv")
|
|
require.NoError(t, err)
|
|
out.Add("Chrome", "Default", chromeData())
|
|
require.NoError(t, out.Write())
|
|
|
|
records := readCSV(t, filepath.Join(dir, "cookie.csv"))
|
|
require.Len(t, records, 2)
|
|
|
|
assert.Equal(t,
|
|
[]string{
|
|
"browser", "profile", "host", "path", "name", "value",
|
|
"is_secure", "is_http_only", "has_expire", "is_persistent", "expire_at", "created_at",
|
|
},
|
|
records[0],
|
|
)
|
|
assert.Equal(t,
|
|
[]string{
|
|
"Chrome", "Default", ".example.com", "/", "session", "abc123",
|
|
"true", "true", "true", "true", "2026-01-15T10:30:00Z", "2026-01-15T10:30:00Z",
|
|
},
|
|
records[1],
|
|
)
|
|
}
|
|
|
|
func TestWrite_CSV_History(t *testing.T) {
|
|
dir := t.TempDir()
|
|
out, err := NewWriter(dir, "csv")
|
|
require.NoError(t, err)
|
|
out.Add("Chrome", "Profile 1", chromeData())
|
|
require.NoError(t, out.Write())
|
|
|
|
records := readCSV(t, filepath.Join(dir, "history.csv"))
|
|
require.Len(t, records, 2)
|
|
|
|
assert.Equal(t, []string{"browser", "profile", "url", "title", "visit_count", "last_visit"}, records[0])
|
|
assert.Equal(t, []string{"Chrome", "Profile 1", "https://example.com", "Example", "5", "2026-01-15T10:30:00Z"}, records[1])
|
|
}
|
|
|
|
func TestWrite_CSV_UTF8BOM(t *testing.T) {
|
|
dir := t.TempDir()
|
|
out, err := NewWriter(dir, "csv")
|
|
require.NoError(t, err)
|
|
out.Add("Chrome", "Default", chromeData())
|
|
require.NoError(t, out.Write())
|
|
|
|
raw, err := os.ReadFile(filepath.Join(dir, "password.csv"))
|
|
require.NoError(t, err)
|
|
require.GreaterOrEqual(t, len(raw), 3)
|
|
assert.Equal(t, utf8BOM, raw[:3], "CSV should start with UTF-8 BOM")
|
|
}
|
|
|
|
// --- JSON output ---
|
|
|
|
func TestWrite_JSON_Password(t *testing.T) {
|
|
dir := t.TempDir()
|
|
out, err := NewWriter(dir, "json")
|
|
require.NoError(t, err)
|
|
out.Add("Chrome", "Default", chromeData())
|
|
out.Add("Firefox", "abc123", firefoxData())
|
|
require.NoError(t, out.Write())
|
|
|
|
type pwJSON struct {
|
|
Browser string `json:"browser"`
|
|
Profile string `json:"profile"`
|
|
URL string `json:"url"`
|
|
Username string `json:"username"`
|
|
Password string `json:"password"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
}
|
|
var rows []pwJSON
|
|
readJSON(t, filepath.Join(dir, "password.json"), &rows)
|
|
require.Len(t, rows, 2)
|
|
|
|
assert.Equal(t, pwJSON{
|
|
Browser: "Chrome", Profile: "Default",
|
|
URL: "https://example.com", Username: "alice", Password: "secret", CreatedAt: testTime,
|
|
}, rows[0])
|
|
assert.Equal(t, pwJSON{
|
|
Browser: "Firefox", Profile: "abc123",
|
|
URL: "https://reddit.com", Username: "bob", Password: "hunter2", CreatedAt: testTime,
|
|
}, rows[1])
|
|
}
|
|
|
|
func TestWrite_JSON_Cookie(t *testing.T) {
|
|
dir := t.TempDir()
|
|
out, err := NewWriter(dir, "json")
|
|
require.NoError(t, err)
|
|
out.Add("Chrome", "Default", chromeData())
|
|
require.NoError(t, out.Write())
|
|
|
|
type ckJSON struct {
|
|
Browser string `json:"browser"`
|
|
Profile string `json:"profile"`
|
|
Host string `json:"host"`
|
|
Path string `json:"path"`
|
|
Name string `json:"name"`
|
|
Value string `json:"value"`
|
|
IsSecure bool `json:"is_secure"`
|
|
IsHTTPOnly bool `json:"is_http_only"`
|
|
HasExpire bool `json:"has_expire"`
|
|
IsPersistent bool `json:"is_persistent"`
|
|
ExpireAt time.Time `json:"expire_at"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
}
|
|
var rows []ckJSON
|
|
readJSON(t, filepath.Join(dir, "cookie.json"), &rows)
|
|
require.Len(t, rows, 1)
|
|
|
|
assert.Equal(t, ckJSON{
|
|
Browser: "Chrome", Profile: "Default",
|
|
Host: ".example.com", Path: "/", Name: "session", Value: "abc123",
|
|
IsSecure: true, IsHTTPOnly: true, HasExpire: true, IsPersistent: true,
|
|
ExpireAt: testTime, CreatedAt: testTime,
|
|
}, rows[0])
|
|
}
|
|
|
|
func TestWrite_JSON_NoBOM(t *testing.T) {
|
|
dir := t.TempDir()
|
|
out, err := NewWriter(dir, "json")
|
|
require.NoError(t, err)
|
|
out.Add("Chrome", "Default", chromeData())
|
|
require.NoError(t, out.Write())
|
|
|
|
raw, err := os.ReadFile(filepath.Join(dir, "password.json"))
|
|
require.NoError(t, err)
|
|
if len(raw) >= 3 {
|
|
assert.NotEqual(t, utf8BOM, raw[:3], "JSON should NOT have BOM")
|
|
}
|
|
}
|
|
|
|
// --- CookieEditor output ---
|
|
|
|
func TestWrite_CookieEditor(t *testing.T) {
|
|
dir := t.TempDir()
|
|
out, err := NewWriter(dir, "cookie-editor")
|
|
require.NoError(t, err)
|
|
out.Add("Chrome", "Default", chromeData())
|
|
require.NoError(t, out.Write())
|
|
|
|
var entries []cookieEditorEntry
|
|
readJSON(t, filepath.Join(dir, "cookie.json"), &entries)
|
|
require.Len(t, entries, 1)
|
|
|
|
assert.Equal(t, cookieEditorEntry{
|
|
Domain: ".example.com",
|
|
Name: "session",
|
|
Value: "abc123",
|
|
Path: "/",
|
|
Secure: true,
|
|
HTTPOnly: true,
|
|
ExpirationDate: float64(testTime.Unix()),
|
|
}, entries[0])
|
|
}
|
|
|
|
func TestWrite_CookieEditor_FallbackJSON(t *testing.T) {
|
|
dir := t.TempDir()
|
|
out, err := NewWriter(dir, "cookie-editor")
|
|
require.NoError(t, err)
|
|
out.Add("Chrome", "Default", &types.BrowserData{
|
|
Passwords: []types.LoginEntry{{URL: "https://a.com"}},
|
|
})
|
|
require.NoError(t, out.Write())
|
|
|
|
// non-cookie categories fall back to standard JSON format
|
|
_, err = os.Stat(filepath.Join(dir, "password.json"))
|
|
assert.False(t, os.IsNotExist(err), "password.json should be created via JSON fallback")
|
|
}
|
|
|
|
// --- File creation ---
|
|
|
|
func TestWrite_EmptyCategoryNoFile(t *testing.T) {
|
|
dir := t.TempDir()
|
|
out, err := NewWriter(dir, "csv")
|
|
require.NoError(t, err)
|
|
out.Add("Chrome", "Default", &types.BrowserData{
|
|
Passwords: []types.LoginEntry{{URL: "https://a.com"}},
|
|
})
|
|
require.NoError(t, out.Write())
|
|
|
|
assert.FileExists(t, filepath.Join(dir, "password.csv"))
|
|
_, err = os.Stat(filepath.Join(dir, "cookie.csv"))
|
|
assert.True(t, os.IsNotExist(err))
|
|
_, err = os.Stat(filepath.Join(dir, "history.csv"))
|
|
assert.True(t, os.IsNotExist(err))
|
|
}
|
|
|
|
func TestWrite_NoData(t *testing.T) {
|
|
dir := t.TempDir()
|
|
out, err := NewWriter(dir, "csv")
|
|
require.NoError(t, err)
|
|
require.NoError(t, out.Write())
|
|
|
|
entries, _ := os.ReadDir(dir)
|
|
assert.Empty(t, entries, "no files should be created when no data added")
|
|
}
|
|
|
|
// --- helpers ---
|
|
|
|
func readCSV(t *testing.T, path string) [][]string {
|
|
t.Helper()
|
|
raw, err := os.ReadFile(path)
|
|
require.NoError(t, err)
|
|
// Skip UTF-8 BOM if present
|
|
content := string(raw)
|
|
if strings.HasPrefix(content, string(utf8BOM)) {
|
|
content = content[len(utf8BOM):]
|
|
}
|
|
reader := csv.NewReader(strings.NewReader(content))
|
|
records, err := reader.ReadAll()
|
|
require.NoError(t, err)
|
|
return records
|
|
}
|
|
|
|
func readJSON(t *testing.T, path string, v any) {
|
|
t.Helper()
|
|
raw, err := os.ReadFile(path)
|
|
require.NoError(t, err)
|
|
require.NoError(t, json.Unmarshal(raw, v))
|
|
}
|