Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add new profile finder method for chromium and firefox #456

Open
wants to merge 4 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -207,4 +207,5 @@ hack-browser-data
!.typos.toml
!.github/*.yml
!log/
examples/*.go
examples/*.go
profile/**/*.go
143 changes: 143 additions & 0 deletions filemanager/filemanager.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
package filemanager

import (
"fmt"
"io"
"os"
"path/filepath"
"strings"
"time"

cp "github.com/otiai10/copy"

"github.com/moond4rk/hackbrowserdata/log"
"github.com/moond4rk/hackbrowserdata/profile"
)

// FileManager manages temporary file operations for data extraction.
type FileManager struct {
// TempDir is the path to the temporary directory.
TempDir string
}

// defaultDirPattern is the default pattern used for generating temporary directory names.
const defaultDirPattern = "hackbrowserdata"

// NewFileManager creates a new instance of FileManager with a unique temporary directory.
func NewFileManager() (*FileManager, error) {
baseTempDir := os.TempDir()
tempDir, err := os.MkdirTemp(baseTempDir, defaultDirPattern)
if err != nil {
return nil, fmt.Errorf("failed to create temporary directory: %w", err)
}
return &FileManager{TempDir: tempDir}, nil
}

func (fm *FileManager) CopyProfiles(originalProfiles profile.Profiles) (profile.Profiles, error) {
newProfiles := profile.NewProfiles()
for profileName, originalProfile := range originalProfiles {
newProfile, err := fm.CopyProfile(originalProfile)
// TODO: Handle multi error
if err != nil {
return nil, fmt.Errorf("failed to copy profile %s: %w", profileName, err)
}
newProfiles[profileName] = newProfile
}
return newProfiles, nil
}

// CopyProfile copies the profile to a temporary directory and returns the new profile.
// TODO: Handle multi error
func (fm *FileManager) CopyProfile(originalProfile *profile.Profile) (*profile.Profile, error) {
newProfile := profile.NewProfile(originalProfile.Name)
newProfile.BrowserType = originalProfile.BrowserType

if originalProfile.MasterKeyPath != "" {
copiedMasterKeyPath, err := fm.CopyToTemp(originalProfile.Name, originalProfile.MasterKeyPath)
if err != nil {
return nil, fmt.Errorf("failed to copy master key: %w", err)
}
newProfile.MasterKeyPath = copiedMasterKeyPath
}

for dataType, paths := range originalProfile.DataFilePath {
for _, originalPath := range paths {
copiedPath, err := fm.CopyToTemp(originalProfile.Name, originalPath)
if err != nil {
log.Errorf("failed to copy data file %s: %v", originalPath, err)
continue
// return nil, fmt.Errorf("failed to copy data file %s: %w", originalPath, err)

Check failure on line 69 in filemanager/filemanager.go

View workflow job for this annotation

GitHub Actions / Lint

commentedOutCode: may want to remove commented-out code (gocritic)
}
newProfile.DataFilePath[dataType] = append(newProfile.DataFilePath[dataType], copiedPath)
}
}

return newProfile, nil
}

// CopyToTemp copies the specified file or directory to temporary directory and returns its new path.
func (fm *FileManager) CopyToTemp(baseName, originalPath string) (string, error) {
stat, err := os.Stat(originalPath)
if err != nil {
return "", fmt.Errorf("error accessing path %s: %w", originalPath, err)
}

// unique target filepath is generated by appending the current time in nanoseconds to the original filename
// such is profileName-<originalFilename>-<currentUnixNano>.<ext>
timestampSuffix := fmt.Sprintf("%d", time.Now().UnixNano())
targetFilename := fmt.Sprintf("%s-%s-%s", baseName, filepath.Base(originalPath), timestampSuffix)
targetPath := filepath.Join(fm.TempDir, targetFilename)

if stat.IsDir() {
// skip copying the directory if it is a lock file, mostly used in leveldb
err = fm.CopyDir(originalPath, targetPath, "lock")
} else {
err = fm.CopyFile(originalPath, targetPath)
}

if err != nil {
return "", fmt.Errorf("failed to copy %s to %s: %w", originalPath, targetPath, err)
}

return targetPath, nil
}

// Cleanup removes the temporary directory and all its contents.
func (fm *FileManager) Cleanup() error {
return os.RemoveAll(fm.TempDir)
}

// CopyDir copies the directory from the source to the destination
// skip the file if you don't want to copy
func (fm *FileManager) CopyDir(src, dst, skip string) error {
s := cp.Options{Skip: func(info os.FileInfo, src, dst string) (bool, error) {
return strings.HasSuffix(strings.ToLower(src), skip), nil
}}
return cp.Copy(src, dst, s)
}

// CopyFile copies the file from the source to the destination.
// Here `dst` is expected to be the complete path including the filename where the content is to be written.
func (fm *FileManager) CopyFile(src, dst string) error {
srcFile, err := os.Open(src)
if err != nil {
return err
}
defer srcFile.Close()

Check failure on line 126 in filemanager/filemanager.go

View workflow job for this annotation

GitHub Actions / Lint

Error return value of `srcFile.Close` is not checked (errcheck)

srcInfo, err := srcFile.Stat()
if err != nil {
return err
}

dstFile, err := os.OpenFile(dst, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, srcInfo.Mode())
if err != nil {
return err
}
defer dstFile.Close()

Check failure on line 137 in filemanager/filemanager.go

View workflow job for this annotation

GitHub Actions / Lint

Error return value of `dstFile.Close` is not checked (errcheck)

if _, err := io.Copy(dstFile, srcFile); err != nil {
return err
}
return nil
}
46 changes: 46 additions & 0 deletions filemanager/filemanager_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package filemanager

import (
"fmt"
"path/filepath"
"runtime"
"testing"

"github.com/stretchr/testify/assert"

"github.com/moond4rk/hackbrowserdata/profile"
"github.com/moond4rk/hackbrowserdata/types2"
)

func TestNewFileManager(t *testing.T) {
fm, err := NewFileManager()
assert.NoError(t, err)
defer fm.Cleanup()

Check failure on line 18 in filemanager/filemanager_test.go

View workflow job for this annotation

GitHub Actions / Lint

Error return value of `fm.Cleanup` is not checked (errcheck)
fmt.Println(fm.TempDir)

Check failure on line 19 in filemanager/filemanager_test.go

View workflow job for this annotation

GitHub Actions / Lint

use of `fmt.Println` forbidden because "Do not commit print statements." (forbidigo)
}

func TestFileManager_CopyProfile(t *testing.T) {
if runtime.GOOS != "darwin" {
t.Skip("skipping test on non-darwin system")
}
paths, err := filepath.Glob(`/Users/*/Library/Application Support/Firefox/Profiles`)
assert.NoError(t, err)
if len(paths) == 0 {
t.Skip("no chrome profile found")
}
rootPath := paths[0]
browserType := types2.FirefoxType
dataTypes := types2.AllDataTypes
finder := profile.NewFinder()
profiles, err := finder.FindProfiles(rootPath, browserType, dataTypes)
assert.NoError(t, err)
assert.NotNil(t, profiles)
fmt.Println(profiles)

Check failure on line 38 in filemanager/filemanager_test.go

View workflow job for this annotation

GitHub Actions / Lint

use of `fmt.Println` forbidden because "Do not commit print statements." (forbidigo)
fm, err := NewFileManager()
assert.NoError(t, err)
fmt.Println(fm.TempDir)

Check failure on line 41 in filemanager/filemanager_test.go

View workflow job for this annotation

GitHub Actions / Lint

use of `fmt.Println` forbidden because "Do not commit print statements." (forbidigo)
defer fm.Cleanup()

Check failure on line 42 in filemanager/filemanager_test.go

View workflow job for this annotation

GitHub Actions / Lint

Error return value of `fm.Cleanup` is not checked (errcheck)
newProfiles, err := fm.CopyProfiles(profiles)
assert.NoError(t, err)
_ = newProfiles
}
6 changes: 2 additions & 4 deletions log/log.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,8 @@ import (
"github.com/moond4rk/hackbrowserdata/log/level"
)

var (
// defaultLogger is the default logger used by the package-level functions.
defaultLogger = NewLogger(nil)
)
// defaultLogger is the default logger used by the package-level functions.
var defaultLogger = NewLogger(nil)

func SetVerbose() {
defaultLogger.SetLevel(level.DebugLevel)
Expand Down
2 changes: 1 addition & 1 deletion log/logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ func (l *baseLogger) prefixPrint(prefix string, args ...any) {
}

func (l *baseLogger) getCallDepth() int {
var defaultCallDepth = 2
defaultCallDepth := 2
pcs := make([]uintptr, 10)
n := runtime.Callers(defaultCallDepth, pcs)
frames := runtime.CallersFrames(pcs[:n])
Expand Down
62 changes: 29 additions & 33 deletions log/logger_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,18 @@ type baseTestCase struct {
wantedPattern string
}

var (
baseTestCases = []baseTestCase{
{
description: "without trailing newline, logger adds newline",
message: "hello, hacker!",
suffix: "",
},
{
description: "with trailing newline, logger preserves newline",
message: "hello, hacker!",
suffix: "\n",
},
}
)
var baseTestCases = []baseTestCase{
{
description: "without trailing newline, logger adds newline",
message: "hello, hacker!",
suffix: "",
},
{
description: "with trailing newline, logger preserves newline",
message: "hello, hacker!",
suffix: "\n",
},
}

func TestLoggerDebug(t *testing.T) {
for _, tc := range baseTestCases {
Expand Down Expand Up @@ -121,25 +119,23 @@ type formatTestCase struct {
wantedPattern string
}

var (
formatTestCases = []formatTestCase{
{
description: "message with format prefix",
format: "hello, %s!",
args: []any{"Hacker"},
},
{
description: "message with format prefix",
format: "hello, %d,%d,%d!",
args: []any{1, 2, 3},
},
{
description: "message with format prefix",
format: "hello, %s,%d,%d!",
args: []any{"Hacker", 2, 3},
},
}
)
var formatTestCases = []formatTestCase{
{
description: "message with format prefix",
format: "hello, %s!",
args: []any{"Hacker"},
},
{
description: "message with format prefix",
format: "hello, %d,%d,%d!",
args: []any{1, 2, 3},
},
{
description: "message with format prefix",
format: "hello, %s,%d,%d!",
args: []any{"Hacker", 2, 3},
},
}

func TestLoggerDebugf(t *testing.T) {
for _, tc := range formatTestCases {
Expand Down
16 changes: 16 additions & 0 deletions profile/filesystem.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package profile

import (
"io/fs"
"path/filepath"
)

type FileSystem interface {
WalkDir(root string, fn fs.WalkDirFunc) error
}

type osFS struct{}

func (o osFS) WalkDir(root string, fn fs.WalkDirFunc) error {
return filepath.WalkDir(root, fn)
}
Loading
Loading