Files
cdp_hub/internal/platform/files/instance_data_preparer.go
T

105 lines
3.2 KiB
Go

package files
import (
"context"
"errors"
"fmt"
"os"
"path/filepath"
)
var ErrInstanceDataDirectoryUnavailable = errors.New("instance data directory is unavailable")
// DirectoryPreparer verifies one explicitly supplied absolute directory.
// It is used for program-owned locations such as the configured log folder.
type DirectoryPreparer interface {
PrepareDirectory(context.Context, string) error
}
// InstanceDataPreparer creates and verifies only the User Data Dir belonging
// to the explicitly requested instance. It never discovers or changes other
// browser data directories.
type InstanceDataPreparer interface {
PrepareInstanceDataDir(context.Context, string) error
}
type instanceDataPreparer struct {
mkdirAll func(string, os.FileMode) error
lstat func(string) (os.FileInfo, error)
createTemp func(string, string) (*os.File, error)
remove func(string) error
}
func NewInstanceDataPreparer() InstanceDataPreparer {
return newInstanceDataPreparer(os.MkdirAll, os.Lstat, os.CreateTemp, os.Remove)
}
func NewDirectoryPreparer() DirectoryPreparer {
return newInstanceDataPreparer(os.MkdirAll, os.Lstat, os.CreateTemp, os.Remove)
}
func newInstanceDataPreparer(
mkdirAll func(string, os.FileMode) error,
lstat func(string) (os.FileInfo, error),
createTemp func(string, string) (*os.File, error),
remove func(string) error,
) instanceDataPreparer {
return instanceDataPreparer{mkdirAll: mkdirAll, lstat: lstat, createTemp: createTemp, remove: remove}
}
func (p instanceDataPreparer) PrepareInstanceDataDir(ctx context.Context, userDataDir string) error {
return p.PrepareDirectory(ctx, userDataDir)
}
func (p instanceDataPreparer) PrepareDirectory(ctx context.Context, directory string) error {
if err := ctx.Err(); err != nil {
return err
}
path, err := normalizePreparatoryInstanceDataDir(directory)
if err != nil {
return err
}
if err := p.mkdirAll(path, 0o700); err != nil {
return fmt.Errorf("%w: create", ErrInstanceDataDirectoryUnavailable)
}
if err := ctx.Err(); err != nil {
return err
}
info, err := p.lstat(path)
if err != nil {
return fmt.Errorf("%w: inspect", ErrInstanceDataDirectoryUnavailable)
}
if info.Mode()&(os.ModeSymlink|os.ModeIrregular) != 0 || isReparsePoint(info) {
return fmt.Errorf("%w: links and reparse points are not supported", ErrUnsafeInstanceDataDirectory)
}
if !info.IsDir() {
return ErrInstanceDataNotDirectory
}
probe, err := p.createTemp(path, ".chub-write-*")
if err != nil {
return fmt.Errorf("%w: write", ErrInstanceDataDirectoryUnavailable)
}
probePath := probe.Name()
if err := probe.Close(); err != nil {
_ = p.remove(probePath)
return fmt.Errorf("%w: close", ErrInstanceDataDirectoryUnavailable)
}
if err := p.remove(probePath); err != nil && !errors.Is(err, os.ErrNotExist) {
return fmt.Errorf("%w: cleanup", ErrInstanceDataDirectoryUnavailable)
}
if err := ctx.Err(); err != nil {
return err
}
return nil
}
// normalizePreparatoryInstanceDataDir is kept separate from deletion's
// helper so callers can use a stable, intention-revealing validation entry.
func normalizePreparatoryInstanceDataDir(value string) (string, error) {
path, err := normalizeRemovableInstanceDataDir(value)
if err != nil {
return "", err
}
return filepath.Clean(path), nil
}