Files
HackBrowserData/filemanager/session.go
T
Roger 12436217ae feat: add filemanager session and crypto version detection (#516)
* feat: add filemanager session and crypto version detection

* refactor: move copy logic into filemanager, remove fileutil dependency

* fix: apply review suggestions for filemanager

* feat: add Windows locked file tests, fix readFileContent with ReadFile+FileMapping fallback

* fix: remove self-PID skip in findFileHandle to fix Windows CI test

* fix: seek to file start before reading duplicated handle

* fix: use full path matching in findFileHandle to avoid cross-app handle collision

* test: enhance Windows copyLocked tests with write-then-read, large file, and normal copy scenarios

* fix: check all errors in Windows tests, use bytes.Equal for large file comparison

* fix: use stable path suffix matching to handle Windows short path names in CI
2026-04-04 01:41:01 +08:00

76 lines
2.1 KiB
Go

package filemanager
import (
"errors"
"fmt"
"os"
"runtime"
)
// Session manages temporary files for a single browser extraction run.
// It creates an isolated temp directory and provides methods to copy
// browser files into it. Call Cleanup() when done to remove all temp files.
type Session struct {
tempDir string
}
// NewSession creates a session with a unique temporary directory.
func NewSession() (*Session, error) {
dir, err := os.MkdirTemp("", "hbd-*")
if err != nil {
return nil, fmt.Errorf("create temp dir: %w", err)
}
return &Session{tempDir: dir}, nil
}
// TempDir returns the session's temporary directory path.
func (s *Session) TempDir() string {
return s.tempDir
}
// Acquire copies a browser file (or directory) from src to dst.
// For regular files, it also copies SQLite WAL and SHM companion files
// if they exist. For directories (e.g. LevelDB), it copies the entire
// directory while skipping lock files.
//
// On Windows, if the normal copy fails (e.g. file locked by Chrome),
// it falls back to DuplicateHandle + FileMapping to bypass exclusive locks.
func (s *Session) Acquire(src, dst string, isDir bool) error {
if isDir {
return copyDir(src, dst, "lock")
}
// Try normal copy first
err := copyFile(src, dst)
if err != nil {
// Only attempt locked-file fallback on Windows where Chrome holds exclusive locks.
// On other platforms, return the original error directly.
if runtime.GOOS != "windows" {
return fmt.Errorf("copy: %w", err)
}
if err2 := copyLocked(src, dst); err2 != nil {
return errors.Join(
fmt.Errorf("copy: %w", err),
fmt.Errorf("locked copy: %w", err2),
)
}
}
// Copy SQLite WAL/SHM companion files if present
var walErrs []error
for _, suffix := range []string{"-wal", "-shm"} {
walSrc := src + suffix
if isFileExists(walSrc) {
if err := copyFile(walSrc, dst+suffix); err != nil {
walErrs = append(walErrs, fmt.Errorf("copy %s: %w", suffix, err))
}
}
}
return errors.Join(walErrs...)
}
// Cleanup removes the session's temporary directory and all its contents.
func (s *Session) Cleanup() {
os.RemoveAll(s.tempDir)
}