feat: add types.Category, data models, and browserdata.Data (#512)

* feat: add new types.Category, data models, and browserdata.Data

Phase 1 of architecture refactoring (RFC-001/RFC-002):

- types/category.go: Category enum (9 values) replacing DataType (22 values)
  with String(), IsSensitive(), AllCategories, NonSensitiveCategories()
- types/models.go: browser-agnostic data models (LoginEntry, CookieEntry,
  BookmarkEntry, HistoryEntry, DownloadEntry, CreditCardEntry, StorageEntry,
  ExtensionEntry) — no encrypted fields, no browser prefixes
- types/category_test.go: tests for Category methods
- browserdata/browser_data.go: new Data struct with typed slices,
  coexists with old BrowserData during migration

* docs: replace Coveralls badge with Codecov in README

* fix: apply review suggestions (is_http_only tag, json tags on Data)
This commit is contained in:
Roger
2026-03-23 02:30:42 +08:00
committed by moonD4rk
parent c493804ede
commit b680d43caa
5 changed files with 213 additions and 1 deletions
+71
View File
@@ -0,0 +1,71 @@
package types
// Category represents a kind of browser data.
// It is browser-agnostic — a password is a password regardless of which browser it came from.
type Category int
const (
Password Category = iota
Cookie
Bookmark
History
Download
CreditCard
Extension
LocalStorage
SessionStorage
)
// AllCategories returns all supported data categories.
var AllCategories = []Category{
Password, Cookie, Bookmark, History, Download,
CreditCard, Extension, LocalStorage, SessionStorage,
}
// String returns the human-readable name of the category.
func (c Category) String() string {
switch c {
case Password:
return "password"
case Cookie:
return "cookie"
case Bookmark:
return "bookmark"
case History:
return "history"
case Download:
return "download"
case CreditCard:
return "creditcard"
case Extension:
return "extension"
case LocalStorage:
return "localstorage"
case SessionStorage:
return "sessionstorage"
default:
return "unknown"
}
}
// IsSensitive returns whether the category contains sensitive data
// that requires explicit opt-in to export.
func (c Category) IsSensitive() bool {
switch c {
case Password, Cookie, CreditCard:
return true
default:
return false
}
}
// NonSensitiveCategories returns categories that are safe to export by default.
func NonSensitiveCategories() []Category {
var cats []Category
for _, c := range AllCategories {
if !c.IsSensitive() {
cats = append(cats, c)
}
}
return cats
}
+52
View File
@@ -0,0 +1,52 @@
package types
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestCategory_String(t *testing.T) {
tests := []struct {
cat Category
want string
}{
{Password, "password"},
{Cookie, "cookie"},
{Bookmark, "bookmark"},
{History, "history"},
{Download, "download"},
{CreditCard, "creditcard"},
{Extension, "extension"},
{LocalStorage, "localstorage"},
{SessionStorage, "sessionstorage"},
{Category(999), "unknown"},
}
for _, tt := range tests {
assert.Equal(t, tt.want, tt.cat.String())
}
}
func TestCategory_IsSensitive(t *testing.T) {
sensitive := []Category{Password, Cookie, CreditCard}
for _, c := range sensitive {
assert.True(t, c.IsSensitive(), "%s should be sensitive", c)
}
notSensitive := []Category{Bookmark, History, Download, Extension, LocalStorage, SessionStorage}
for _, c := range notSensitive {
assert.False(t, c.IsSensitive(), "%s should not be sensitive", c)
}
}
func TestAllCategories(t *testing.T) {
assert.Len(t, AllCategories, 9)
}
func TestNonSensitiveCategories(t *testing.T) {
cats := NonSensitiveCategories()
assert.Len(t, cats, 6)
for _, c := range cats {
assert.False(t, c.IsSensitive())
}
}
+71
View File
@@ -0,0 +1,71 @@
package types
import "time"
// LoginEntry represents a single saved login credential.
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"`
}
// CookieEntry represents a single browser cookie.
type CookieEntry struct {
Host string `json:"host" csv:"host"`
Path string `json:"path" csv:"path"`
Name string `json:"name" csv:"name"`
Value string `json:"value" csv:"value"`
IsSecure bool `json:"is_secure" csv:"is_secure"`
IsHTTPOnly bool `json:"is_http_only" csv:"is_http_only"`
ExpireAt time.Time `json:"expire_at" csv:"expire_at"`
CreatedAt time.Time `json:"created_at" csv:"created_at"`
}
// BookmarkEntry represents a single browser bookmark.
type BookmarkEntry struct {
Name string `json:"name" csv:"name"`
URL string `json:"url" csv:"url"`
Folder string `json:"folder" csv:"folder"`
CreatedAt time.Time `json:"created_at" csv:"created_at"`
}
// HistoryEntry represents a single browser history record.
type HistoryEntry struct {
URL string `json:"url" csv:"url"`
Title string `json:"title" csv:"title"`
VisitCount int `json:"visit_count" csv:"visit_count"`
LastVisit time.Time `json:"last_visit" csv:"last_visit"`
}
// DownloadEntry represents a single browser download record.
type DownloadEntry struct {
URL string `json:"url" csv:"url"`
TargetPath string `json:"target_path" csv:"target_path"`
TotalBytes int64 `json:"total_bytes" csv:"total_bytes"`
StartTime time.Time `json:"start_time" csv:"start_time"`
EndTime time.Time `json:"end_time" csv:"end_time"`
}
// CreditCardEntry represents a single saved credit card.
type CreditCardEntry struct {
Name string `json:"name" csv:"name"`
Number string `json:"number" csv:"number"`
ExpMonth string `json:"exp_month" csv:"exp_month"`
ExpYear string `json:"exp_year" csv:"exp_year"`
}
// StorageEntry represents a single key-value pair from local or session storage.
type StorageEntry struct {
URL string `json:"url" csv:"url"`
Key string `json:"key" csv:"key"`
Value string `json:"value" csv:"value"`
}
// ExtensionEntry represents a single browser extension.
type ExtensionEntry struct {
Name string `json:"name" csv:"name"`
ID string `json:"id" csv:"id"`
Description string `json:"description" csv:"description"`
Version string `json:"version" csv:"version"`
}