mirror of
https://github.com/moonD4rk/HackBrowserData.git
synced 2026-05-19 18:58:03 +02:00
00ad0e0bd4
* docs: add RFC-004 for CLI (cobra) and output design * feat: add output package with Formatter interface and BrowserData.Each * fix: golangci config array syntax + add output package tests * refactor: encapsulated Output as Writer, collect-then-write pattern * refactor: unified row type with reflection-based CSV/JSON output * fix: ProfileName empty guard, writeFile close error check, sync RFC-004
305 lines
9.4 KiB
Markdown
305 lines
9.4 KiB
Markdown
# RFC-004: CLI (Cobra) and Output Design
|
|
|
|
**Author**: moonD4rk
|
|
**Status**: Proposed
|
|
**Created**: 2026-04-03
|
|
**Updated**: 2026-04-03
|
|
|
|
## Context
|
|
|
|
v2 architecture delivers `Extract() → *types.BrowserData`. The remaining
|
|
pieces are: CLI for user interaction and output for writing results to files.
|
|
Current CLI uses `urfave/cli` with flat flags; migrating to `cobra` with
|
|
subcommands for better extensibility.
|
|
|
|
## 1. CLI Design
|
|
|
|
### Subcommands
|
|
|
|
```
|
|
hack-browser-data
|
|
├── dump # extract browser data (default when no subcommand)
|
|
│ ├── -b, --browser all|chrome|firefox|... (default: all)
|
|
│ ├── -c, --category all|password,cookie,... (default: all)
|
|
│ ├── -f, --format csv|json|cookie-editor (default: csv)
|
|
│ ├── -d, --dir output directory (default: results)
|
|
│ ├── -p, --profile-path custom profile path
|
|
│ ├── --keychain-pw macOS keychain password
|
|
│ └── --zip compress output
|
|
│
|
|
├── list # show detected browsers and profile paths
|
|
│ └── --detail show per-category entry counts (no decryption)
|
|
│
|
|
└── global flags
|
|
├── -v, --verbose
|
|
└── --version
|
|
```
|
|
|
|
Running `hack-browser-data` with no subcommand defaults to `dump`.
|
|
|
|
### Examples
|
|
|
|
```bash
|
|
hack-browser-data # dump all
|
|
hack-browser-data dump -b chrome -c password,cookie # specific
|
|
hack-browser-data dump -b chrome -f json # JSON output
|
|
hack-browser-data dump -f cookie-editor # CookieEditor format
|
|
hack-browser-data list # show browsers
|
|
hack-browser-data list --detail # show counts
|
|
```
|
|
|
|
### Removed/changed flags vs current CLI
|
|
|
|
| Current flag | Action | Reason |
|
|
|-------------|--------|--------|
|
|
| `--full-export` | Removed | Replaced by `--category all` (default) |
|
|
| `--results-dir` | Renamed `--dir` | Shorter |
|
|
| — | New `--category` | Fine-grained control |
|
|
| — | New `--keychain-pw` | macOS keychain password |
|
|
| — | New `--format cookie-editor` | CookieEditor compatibility |
|
|
|
|
### Code structure
|
|
|
|
```
|
|
cmd/hack-browser-data/
|
|
├── main.go # cobra root command setup
|
|
├── dump.go # dump subcommand
|
|
└── list.go # list subcommand
|
|
```
|
|
|
|
## 2. Output Design
|
|
|
|
### File organization
|
|
|
|
One file per category. Browser and profile are columns, not filenames:
|
|
|
|
```
|
|
results/
|
|
├── password.csv
|
|
├── cookie.csv
|
|
├── history.csv
|
|
├── bookmark.csv
|
|
├── download.csv
|
|
├── extension.csv
|
|
├── creditcard.csv
|
|
├── localstorage.csv
|
|
└── sessionstorage.csv
|
|
```
|
|
|
|
At most 9 files, regardless of how many browsers/profiles.
|
|
|
|
Example `password.csv`:
|
|
```
|
|
browser,profile,url,username,password,created_at
|
|
Chrome,Default,https://example.com,alice,xxx,2026-01-01
|
|
Chrome,Profile 1,https://github.com,bob,yyy,2026-02-01
|
|
Firefox,abc123.default,https://reddit.com,charlie,zzz,2026-03-01
|
|
```
|
|
|
|
Example `password.json`:
|
|
```json
|
|
[
|
|
{"browser":"Chrome","profile":"Default","url":"https://example.com","username":"alice","password":"xxx","created_at":"2026-01-01T00:00:00Z"},
|
|
{"browser":"Firefox","profile":"abc123.default","url":"https://reddit.com","username":"charlie","password":"zzz","created_at":"2026-03-01T00:00:00Z"}
|
|
]
|
|
```
|
|
|
|
### Architecture: encapsulated Writer struct
|
|
|
|
The `Writer` struct is the only exported type. All internals (formatter,
|
|
row types, file management) are unexported. Caller sees 3 methods only.
|
|
|
|
```go
|
|
// output/output.go — the only exported type
|
|
|
|
type Writer struct {
|
|
dir string
|
|
formatter formatter // unexported
|
|
results []result // unexported
|
|
}
|
|
|
|
func NewWriter(dir, format string) (*Writer, error) {
|
|
f, err := newFormatter(format)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &Writer{dir: dir, formatter: f}, nil
|
|
}
|
|
|
|
func (w *Writer) Add(browser, profile string, data *types.BrowserData) {
|
|
w.results = append(w.results, result{browser, profile, data})
|
|
}
|
|
|
|
func (w *Writer) Write() error {
|
|
// 1. aggregate all results by category into row slices
|
|
// 2. for each non-empty category, format to buffer, write file
|
|
}
|
|
```
|
|
|
|
Caller code (3 lines):
|
|
|
|
```go
|
|
w, _ := output.NewWriter(dir, "csv")
|
|
for _, b := range browsers {
|
|
data, _ := b.Extract(categories)
|
|
w.Add(b.BrowserName(), b.ProfileName(), data)
|
|
}
|
|
w.Write()
|
|
```
|
|
|
|
### Data layer stays pure
|
|
|
|
Entry structs do NOT contain browser/profile. Each field carries both
|
|
`json` and `csv` struct tags — JSON output reads `json` tags, CSV output
|
|
reads `csv` tags via reflection. No methods on entry types.
|
|
|
|
```go
|
|
// types/models.go — pure data, no methods
|
|
type LoginEntry struct {
|
|
URL string `json:"url" csv:"url"`
|
|
Username string `json:"username" csv:"username"`
|
|
Password string `json:"password" csv:"password"`
|
|
CreatedAt time.Time `json:"created_at" csv:"created_at"`
|
|
}
|
|
```
|
|
|
|
### Internal row type (unexported)
|
|
|
|
A single `row` type wraps any entry with browser/profile context:
|
|
|
|
```go
|
|
// output/row.go — unexported
|
|
|
|
type row struct {
|
|
Browser string
|
|
Profile string
|
|
entry any
|
|
}
|
|
```
|
|
|
|
- **CSV**: `row.csvHeader()` / `row.csvRow()` use reflection to read `csv`
|
|
struct tags and convert field values to strings (handles string, bool,
|
|
int, int64, time.Time).
|
|
- **JSON**: `row.MarshalJSON()` uses `reflect.StructOf` to dynamically
|
|
build a flat struct with browser/profile fields followed by entry fields,
|
|
then delegates to `json.Marshal`. No manual string concatenation.
|
|
|
|
### Internal formatter interface (unexported)
|
|
|
|
```go
|
|
// output/formatter.go — unexported
|
|
|
|
type formatter interface {
|
|
format(w io.Writer, rows []row) error
|
|
ext() string
|
|
}
|
|
|
|
func newFormatter(name string) (formatter, error) {
|
|
switch name {
|
|
case "csv": return &csvFormatter{}, nil
|
|
case "json": return &jsonFormatter{}, nil
|
|
case "cookie-editor": return &cookieEditorFormatter{}, nil
|
|
default: return nil, fmt.Errorf("unsupported format: %s", name)
|
|
}
|
|
}
|
|
```
|
|
|
|
### Format support
|
|
|
|
**CSV** (default):
|
|
- Standard `encoding/csv` — **no gocsv dependency**
|
|
- UTF-8 BOM for Excel compatibility
|
|
- Headers and values derived from `csv` struct tags via reflection
|
|
|
|
**JSON**:
|
|
- Valid JSON Array per file (not JSON Lines)
|
|
- Pretty-printed with `json.Encoder`, no HTML escape
|
|
- `reflect.StructOf` dynamically flattens browser/profile + entry fields
|
|
|
|
**CookieEditor** (`--format cookie-editor`):
|
|
- Only exports cookies, other categories skipped
|
|
- Field mapping: host→domain, IsSecure→secure, ExpireAt→expirationDate (unix)
|
|
|
|
### Dependency changes
|
|
|
|
- **Remove**: `github.com/gocarina/gocsv`
|
|
- **Remove**: `golang.org/x/text` (UTF-8 BOM = 3 bytes directly)
|
|
- **Add**: `github.com/spf13/cobra`
|
|
|
|
### Output package structure
|
|
|
|
```
|
|
output/
|
|
├── output.go # Writer struct (exported): NewWriter(), Add(), Write()
|
|
├── row.go # Unified row type (unexported) + MarshalJSON
|
|
├── reflect.go # Reflection helpers: csv tag parsing, field formatting
|
|
├── formatter.go # formatter interface (unexported) + newFormatter()
|
|
├── csv.go # csvFormatter (unexported)
|
|
├── json.go # jsonFormatter (unexported)
|
|
└── cookie_editor.go # cookieEditorFormatter (unexported)
|
|
```
|
|
|
|
## 3. `list` Command
|
|
|
|
### Basic mode
|
|
|
|
Shows real filesystem paths detected by `NewBrowsers`. No database access.
|
|
|
|
```
|
|
$ hack-browser-data list
|
|
|
|
Browser Profile Path
|
|
Chrome Default /Users/x/Library/.../Google/Chrome/Default
|
|
Chrome Profile 1 /Users/x/Library/.../Google/Chrome/Profile 1
|
|
Firefox abc123.default-release /Users/x/Library/.../Firefox/Profiles/abc123...
|
|
```
|
|
|
|
### Detail mode (`--detail`)
|
|
|
|
Counts entries per category without decryption:
|
|
|
|
```
|
|
$ hack-browser-data list --detail
|
|
|
|
Browser Profile Password Cookie History Bookmark Extension
|
|
Chrome Default 1 3544 66 852 39
|
|
Chrome Profile 1 2 802 32 0 3
|
|
Firefox abc123.default-release 3 48 53 7 0
|
|
```
|
|
|
|
## 4. Data flow
|
|
|
|
```
|
|
CLI (cobra dump)
|
|
→ Parse flags: browser, category, format, dir, keychain-pw
|
|
→ browser.Pick(browserName, keychainPwd) → []Browser
|
|
→ w, _ := output.NewWriter(dir, format)
|
|
→ For each browser:
|
|
→ data, _ := b.Extract(categories)
|
|
→ w.Add(b.BrowserName(), b.ProfileName(), data)
|
|
→ w.Write()
|
|
→ Optional: compress dir to zip
|
|
|
|
CLI (cobra list)
|
|
→ browser.Pick("all", "") → []Browser
|
|
→ For each browser:
|
|
→ Print BrowserName() + ProfileName() + profileDir
|
|
→ If --detail: Extract + count entries
|
|
```
|
|
|
|
## 5. Implementation status
|
|
|
|
- [x] `output/` package: Writer struct + unified row type + reflection-based CSV/JSON + formatters
|
|
- [x] `types/category.go`: removed Each() and CategoryData
|
|
- [x] `types/models.go`: pure data structs with `json` + `csv` tags, no methods
|
|
- [x] Tests: 27 tests covering CSV/JSON/CookieEditor output, reflection helpers, MarshalJSON, csv tag coverage
|
|
- [ ] (PR 2) Rewrite browser dispatch + cobra CLI
|
|
- [ ] (PR 3) Delete old code + rename files
|
|
|
|
## 6. Future extensions
|
|
|
|
- `--group-by browser` — one file per browser+category (group by browser)
|
|
- `--group-by profile` — one file per browser+profile+category (group by profile)
|
|
- `--format netscape` — Netscape cookie.txt format (curl/wget compatible)
|
|
- `--format har` — HAR (HTTP Archive) format
|