Files
HackBrowserData/browser/safari/source.go
T
Roger d75738b90f feat(safari): multi-profile support (#581)
* feat(safari): multi-profile support
2026-04-21 15:50:36 +08:00

61 lines
2.3 KiB
Go

package safari
import (
"path/filepath"
"github.com/moond4rk/hackbrowserdata/types"
)
type sourcePath struct {
abs string
isDir bool
}
func file(abs string) sourcePath { return sourcePath{abs: abs} }
// buildSources dispatches between the default and named-profile path layouts.
//
// macOS 14+ layout:
// - History, Cookie: per-profile (separate files per profile UUID)
// - Download: shared plist, filtered by DownloadEntryProfileUUIDStringKey at extract time
// - Bookmark: shared plist, attributed to default only (no per-entry UUID available)
// - Password: macOS Keychain (shared, not listed)
func buildSources(p profileContext) map[types.Category][]sourcePath {
if p.isDefault() {
return defaultSources(p)
}
return namedSources(p)
}
// defaultSources: cookies try macOS 14+ container first, then the ≤13 legacy path.
func defaultSources(p profileContext) map[types.Category][]sourcePath {
home := p.legacyHome
containerCookies := filepath.Join(p.container, "Cookies", "Cookies.binarycookies")
legacyCookies := filepath.Join(filepath.Dir(home), "Cookies", "Cookies.binarycookies")
return map[types.Category][]sourcePath{
types.History: {file(filepath.Join(home, "History.db"))},
types.Cookie: {file(containerCookies), file(legacyCookies)},
types.Bookmark: {file(filepath.Join(home, "Bookmarks.plist"))},
types.Download: {file(filepath.Join(home, "Downloads.plist"))},
}
}
// namedSources omits Bookmark (shared plist with no per-entry profile tag, so attributed to default).
// Download is included because Downloads.plist carries DownloadEntryProfileUUIDStringKey per entry;
// extractDownloads filters by owner UUID so default and named profiles each see their own downloads.
//
// LocalStorage slot for a follow-up PR:
//
// file(filepath.Join(p.container, "WebKit/WebsiteDataStore", p.uuidLower, "LocalStorage"))
func namedSources(p profileContext) map[types.Category][]sourcePath {
profileDir := filepath.Join(p.container, "Safari", "Profiles", p.uuidUpper)
webkitStore := filepath.Join(p.container, "WebKit", "WebsiteDataStore", p.uuidLower)
return map[types.Category][]sourcePath{
types.History: {file(filepath.Join(profileDir, "History.db"))},
types.Cookie: {file(filepath.Join(webkitStore, "Cookies", "Cookies.binarycookies"))},
types.Download: {file(filepath.Join(p.legacyHome, "Downloads.plist"))},
}
}