fileutil

package
v0.8.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 9, 2026 License: MIT Imports: 24 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrInvalidPath      = errors.New("invalid path")
	ErrUnknownExtension = errors.New("unknown extension")
)
View Source
var (
	// ErrNotUTF8Text indicates a file could be read but is not valid UTF‑8.
	ErrNotUTF8Text        = errors.New("file is not valid UTF-8 text")
	ErrFileExceedsMaxSize = errors.New("file exceeds maximum allowed size")
)
View Source
var BaseMIMEToMode = map[string]ExtensionMode{
	"":                         ExtensionModeDefault,
	"application/octet-stream": ExtensionModeDefault,

	"text/plain":             ExtensionModeText,
	"text/markdown":          ExtensionModeText,
	"text/html":              ExtensionModeText,
	"text/css":               ExtensionModeText,
	"application/json":       ExtensionModeText,
	"application/xml":        ExtensionModeText,
	"application/x-yaml":     ExtensionModeText,
	"application/yaml":       ExtensionModeText,
	"application/toml":       ExtensionModeText,
	"application/sql":        ExtensionModeText,
	"application/javascript": ExtensionModeText,

	"image/jpeg":    ExtensionModeImage,
	"image/png":     ExtensionModeImage,
	"image/gif":     ExtensionModeImage,
	"image/webp":    ExtensionModeImage,
	"image/bmp":     ExtensionModeImage,
	"image/svg+xml": ExtensionModeImage,

	"application/pdf":               ExtensionModeDocument,
	"application/msword":            ExtensionModeDocument,
	"application/vnd.ms-powerpoint": ExtensionModeDocument,
	"application/vnd.ms-excel":      ExtensionModeDocument,
	"application/vnd.openxmlformats-officedocument.wordprocessingml.document":   ExtensionModeDocument,
	"application/vnd.openxmlformats-officedocument.presentationml.presentation": ExtensionModeDocument,
	"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet":         ExtensionModeDocument,
	"application/vnd.oasis.opendocument.text":                                   ExtensionModeDocument,
	"application/vnd.oasis.opendocument.spreadsheet":                            ExtensionModeDocument,
}

BaseMIMEToMode maps *base mime types* (no parameters) to a coarse mode.

View Source
var ExtensionToMIMEType = map[FileExt]MIMEType{
	ExtTxt:      MIMETextPlain,
	ExtMd:       MIMETextMarkdown,
	ExtMarkdown: MIMETextMarkdown,
	ExtLog:      MIMETextPlain,
	ExtJSON:     MIMEApplicationJSON,
	ExtYAML:     MIMEApplicationYAML,
	ExtYML:      MIMEApplicationYAML,
	ExtTOML:     MIMEApplicationTOML,
	ExtJS:       MIMEApplicationJS,
	ExtTS:       MIMETextPlain,
	ExtTSX:      MIMETextPlain,
	ExtJSX:      MIMETextPlain,
	ExtPY:       MIMETextPlain,
	ExtGO:       MIMETextPlain,
	ExtRS:       MIMETextPlain,
	ExtJAVA:     MIMETextPlain,
	ExtC:        MIMETextPlain,
	ExtCPP:      MIMETextPlain,
	ExtH:        MIMETextPlain,
	ExtHPP:      MIMETextPlain,
	ExtCS:       MIMETextPlain,
	ExtRB:       MIMETextPlain,
	ExtPHP:      MIMETextPlain,
	ExtHTML:     MIMETextHTML,
	ExtHTM:      MIMETextHTML,
	ExtCSS:      MIMETextCSS,
	ExtSCSS:     MIMETextPlain,
	ExtLESS:     MIMETextPlain,
	ExtSQL:      MIMEApplicationSQL,
	ExtMod:      MIMETextPlain,
	ExtSum:      MIMETextPlain,
	ExtJSONL:    MIMETextPlain,
	ExtShell:    MIMETextPlain,
	ExtSWIFT:    MIMETextPlain,
	ExtM:        MIMETextPlain,
	ExtKT:       MIMETextPlain,
	ExtPL:       MIMETextPlain,
	ExtSCALA:    MIMETextPlain,
	ExtHS:       MIMETextPlain,
	ExtLUA:      MIMETextPlain,
	ExtDART:     MIMETextPlain,
	ExtCmake:    MIMETextPlain,
	ExtBazel:    MIMETextPlain,
	ExtXML:      MIMEApplicationXML,

	ExtJPG:  MIMEImageJPEG,
	ExtJPEG: MIMEImageJPEG,
	ExtPNG:  MIMEImagePNG,
	ExtGIF:  MIMEImageGIF,
	ExtWEBP: MIMEImageWEBP,
	ExtBMP:  MIMEImageBMP,
	ExtSVG:  MIMEImageSVG,

	ExtPDF:  MIMEApplicationPDF,
	ExtDOC:  MIMEApplicationMSWord,
	ExtDOCX: MIMEApplicationOpenXMLDoc,
	ExtPPT:  MIMEApplicationMSPowerPt,
	ExtPPTX: MIMEApplicationOpenXMLPPT,
	ExtXLS:  MIMEApplicationMSExcel,
	ExtXLSX: MIMEApplicationOpenXMLXLS,
	ExtODT:  MIMEApplicationODT,
	ExtODS:  MIMEApplicationODS,
}

ExtensionToMIMEType is an internal registry of common/explicitly-supported extensions. This is used before falling back to mime.TypeByExtension.

View Source
var ModeToExtensions = func() map[ExtensionMode][]FileExt {
	m := make(map[ExtensionMode][]FileExt, len(AllExtensionModes))
	for ext, mt := range ExtensionToMIMEType {
		mode := GetModeForMIME(mt)
		m[mode] = append(m[mode], ext)
	}
	return m
}()

ModeToExtensions is a convenience reverse index built from ExtensionToMIMEType + BaseMIMEToMode.

Functions

func ApplyDarwinSystemRootAliases added in v0.7.0

func ApplyDarwinSystemRootAliases(p string) string

ApplyDarwinSystemRootAliases rewrites known macOS root-level compatibility symlink prefixes to their canonical target paths (e.g. /var/... -> /private/var/...).

It does not access the filesystem and does not resolve arbitrary symlinks.

func CanonicalizeAllowedRoots added in v0.7.0

func CanonicalizeAllowedRoots(roots []string) ([]string, error)

func CopyFileCtx added in v0.6.0

func CopyFileCtx(ctx context.Context, src, dst string, perm os.FileMode) (written int64, err error)

CopyFileCtx copies src->dst, creating dst with O_EXCL. It checks ctx between read iterations.

func CopyFileToExistingCtx added in v0.6.0

func CopyFileToExistingCtx(ctx context.Context, src, dst string) (int64, error)

CopyFileToExistingCtx copies src -> dst where dst is expected to already exist (typically a placeholder reserved with O_EXCL). "dst" is truncated and overwritten.

func EnsureDirNoSymlink(dir string, maxNewDirs int) (created int, err error)

EnsureDirNoSymlink creates missing directories one component at a time, refusing to traverse symlink components. "maxNewDirs: 0 => unlimited"; otherwise limits how many missing dirs it will create.

func EnsureNonOverlappingFixedWidth added in v0.6.0

func EnsureNonOverlappingFixedWidth(matchIdxs []int, width int) error

EnsureNonOverlappingFixedWidth ensures matches do not overlap. Matches must be sorted ascending (as produced by Find* helpers).

func EnsurePathWithinAllowedRoots added in v0.7.0

func EnsurePathWithinAllowedRoots(p string, roots []string) error

EnsurePathWithinAllowedRoots enforces that p is within at least one root. If roots is empty, it allows everything.

func FindTrimmedAdjacentBlockMatches added in v0.6.0

func FindTrimmedAdjacentBlockMatches(lines, before, match, after []string) []int

FindTrimmedAdjacentBlockMatches finds indices i where:

(before matches immediately before i, if provided) AND
(match matches at i) AND
(after matches immediately after the match block, if provided)

Comparison is done on trimmed lines.

func FindTrimmedBlockMatches added in v0.6.0

func FindTrimmedBlockMatches(lines, block []string) []int

FindTrimmedBlockMatches returns all start indices i where `block` matches `lines` when comparing strings.TrimSpace(line) line-by-line.

Returns indices in ascending order.

func GetBaseMIME added in v0.4.0

func GetBaseMIME(mt MIMEType) string

func GetEffectiveWorkDir added in v0.7.0

func GetEffectiveWorkDir(inputWorkDir string, allowedRoots []string) (string, error)

func GetTrimmedLines added in v0.6.0

func GetTrimmedLines(lines []string) []string

func InitPathPolicy added in v0.7.0

func InitPathPolicy(workBaseDir string, allowedRoots []string) (effectiveBase string, canonRoots []string, err error)

InitPathPolicy canonicalizes allowedRoots and computes an effective workBaseDir.

Behavior:

  • allowedRoots == nil/empty => allow all (canonRoots will be nil/empty)
  • workBaseDir blank:
  • if allowedRoots is set => defaults to the first allowed root (more deterministic/sandbox-friendly)
  • else => defaults to current process working directory
  • returned effectiveBase is canonicalized and guaranteed to exist and (if roots set) be within roots

func IsBlockEqualsAt added in v0.6.0

func IsBlockEqualsAt(haystack, needle []string, start int) bool

func IsPathWithinRoot added in v0.7.0

func IsPathWithinRoot(root, p string) (bool, error)

func ListDirectory

func ListDirectory(path, pattern string) ([]string, error)

ListDirectory lists files/dirs in path (default "."), pattern is an optional glob filter (filepath.Match).

func ListDirectoryNormalized added in v0.7.0

func ListDirectoryNormalized(dir, pattern string) ([]string, error)

ListDirectoryNormalized lists entries in a directory that is assumed to be already normalized. It does not normalize or resolve relative paths; callers must do that.

func MIMEForLocalFile

func MIMEForLocalFile(
	path string,
) (mimeType MIMEType, mode ExtensionMode, method MIMEDetectMethod, err error)

MIMEForLocalFile returns a best-effort MIME type, "file mode" (text/image/document/default) and detection method.

Behavior:

  • First try extension-based detection (internal registry + stdlib).
  • If extension detection is unknown or generic, sniff the file bytes.
  • Sniffing uses DetectContentType + a small "isProbablyTextSample" heuristic.

Detection method can be:

  • extension: a non-generic MIME type was derived from the file extension (no file IO required)
  • sniff: content sniffing was used (requires opening/reading the file)

func NormalizeAbsPath added in v0.6.0

func NormalizeAbsPath(p string) (string, error)

NormalizeAbsPath normalizes the input path (trim, NUL reject, Clean) and requires it to be absolute.

Tools that require absolute paths should use this helper to keep behavior consistent across the toolset.

func NormalizeLineBlockInput added in v0.6.0

func NormalizeLineBlockInput(in []string) []string

NormalizeLineBlockInput makes tool line-block arguments more forgiving.

Behavior:

  • Treats embedded CRLF/CR/LF in items as line breaks (splits into multiple lines).
  • Trims trailing newline characters from each item to avoid accidental extra empty lines.
  • Preserves intentional empty lines ("" remains a single empty line).

This helps when callers (especially LLMs) accidentally include newline characters in JSON strings.

func NormalizePath added in v0.6.0

func NormalizePath(p string) (string, error)

NormalizePath: - trims - rejects empty and NUL byte - filepath.Clean.

func ReadFile

func ReadFile(path string, encoding ReadEncoding, maxBytes int64) (string, error)

ReadFile reads a file and returns its contents. If maxBytes > 0, it enforces a hard cap during reading.

func RequireExistingRegularFileNoSymlink(path string) (fs.FileInfo, error)

RequireExistingRegularFileNoSymlink validates that path exists, is a regular file, and is NOT a symlink (Lstat-based). It also verifies the parent directory contains no symlink components (best-effort hardening).

func RequireSingleMatch added in v0.6.0

func RequireSingleMatch(idxs []int, name string) (int, error)

RequireSingleMatch enforces that idxs contains exactly one match index. This is useful for “anchor must be unique” tool semantics.

func RequireSingleTrimmedBlockMatch added in v0.6.0

func RequireSingleTrimmedBlockMatch(lines, block []string, name string) (int, error)

RequireSingleTrimmedBlockMatch finds trimmed-equal block matches and requires exactly one.

func ResolvePath added in v0.7.0

func ResolvePath(workBaseDir string, allowedRoots []string, inputPath, defaultIfEmpty string) (string, error)

ResolvePath resolves an input path (absolute or relative) to an absolute path:

  • relative paths resolve against workBaseDir
  • enforces allowedRoots (if set)
  • normalizes OS-specific separators and cleans path
  • applies macOS root-level compatibility symlink aliases (e.g. /var -> /private/var)

func SearchFiles

func SearchFiles(
	ctx context.Context,
	root, pattern string,
	maxResults int,
) (matchedFiles []string, reachedLimit bool, err error)

SearchFiles walks root (default ".") recursively and returns up to maxResults files whose *path* or UTF-8 text content* match the regexp pattern. If maxResults <= 0, it is treated as "no limit".

func SniffFileMIME

func SniffFileMIME(path string) (mimeType MIMEType, mode ExtensionMode, err error)

SniffFileMIME inspects initial bytes of a file and returns a best-effort MIME type and mode. It will return an error if the file can't be opened/read.

func UniquePathInDir added in v0.6.0

func UniquePathInDir(dir, base string) (string, error)
func VerifyDirNoSymlink(dir string) error

VerifyDirNoSymlink ensures dir exists and is a directory, and none of its components are symlinks.

func WriteFileAtomicBytes added in v0.6.0

func WriteFileAtomicBytes(path string, data []byte, perm fs.FileMode, overwrite bool) error

WriteFileAtomicBytes writes data to path using an atomic commit strategy: temp file in same directory -> fsync -> commit (rename/link) -> best-effort dir sync.

"overwrite=false" guarantees the destination won't be replaced; if it exists, returns an error wrapping os.ErrExist. Notes:

  • On Windows, directory fsync is skipped (it often errors).
  • If another process holds the destination open on Windows, rename may fail.

Types

type ExtensionMode

type ExtensionMode string
const (
	ExtensionModeText     ExtensionMode = "text"
	ExtensionModeImage    ExtensionMode = "image"
	ExtensionModeDocument ExtensionMode = "document"
	ExtensionModeDefault  ExtensionMode = "default"
)

func GetModeForMIME added in v0.4.0

func GetModeForMIME(mt MIMEType) ExtensionMode

type FileExt

type FileExt string
const (
	ExtTxt      FileExt = ".txt"
	ExtMd       FileExt = ".md"
	ExtMarkdown FileExt = ".markdown"
	ExtLog      FileExt = ".log"
	ExtJSON     FileExt = ".json"
	ExtYAML     FileExt = ".yaml"
	ExtYML      FileExt = ".yml"
	ExtTOML     FileExt = ".toml"
	ExtJS       FileExt = ".js"
	ExtTS       FileExt = ".ts"
	ExtTSX      FileExt = ".tsx"
	ExtJSX      FileExt = ".jsx"
	ExtPY       FileExt = ".py"
	ExtGO       FileExt = ".go"
	ExtRS       FileExt = ".rs"
	ExtJAVA     FileExt = ".java"
	ExtC        FileExt = ".c"
	ExtCPP      FileExt = ".cpp"
	ExtH        FileExt = ".h"
	ExtHPP      FileExt = ".hpp"
	ExtCS       FileExt = ".cs"
	ExtRB       FileExt = ".rb"
	ExtPHP      FileExt = ".php"
	ExtHTML     FileExt = ".html"
	ExtHTM      FileExt = ".htm"
	ExtCSS      FileExt = ".css"
	ExtSCSS     FileExt = ".scss"
	ExtLESS     FileExt = ".less"
	ExtSQL      FileExt = ".sql"
	ExtMod      FileExt = ".mod"
	ExtSum      FileExt = ".sum"
	ExtJSONL    FileExt = ".jsonl"
	ExtShell    FileExt = ".sh"
	ExtSWIFT    FileExt = ".swift"
	ExtM        FileExt = ".m"
	ExtKT       FileExt = ".kt"
	ExtPL       FileExt = ".pl"
	ExtSCALA    FileExt = ".scala"
	ExtHS       FileExt = ".hs"
	ExtLUA      FileExt = ".lua"
	ExtDART     FileExt = ".dart"
	ExtCmake    FileExt = ".cmake"
	ExtBazel    FileExt = ".bazel"
	ExtXML      FileExt = ".xml"

	ExtJPG  FileExt = ".jpg"
	ExtJPEG FileExt = ".jpeg"
	ExtPNG  FileExt = ".png"
	ExtGIF  FileExt = ".gif"
	ExtWEBP FileExt = ".webp"
	ExtBMP  FileExt = ".bmp"
	ExtSVG  FileExt = ".svg"

	ExtPDF  FileExt = ".pdf"
	ExtDOC  FileExt = ".doc"
	ExtDOCX FileExt = ".docx"
	ExtPPT  FileExt = ".ppt"
	ExtPPTX FileExt = ".pptx"
	ExtXLS  FileExt = ".xls"
	ExtXLSX FileExt = ".xlsx"
	ExtODT  FileExt = ".odt"
	ExtODS  FileExt = ".ods"
)

func GetNormalizedExt added in v0.4.0

func GetNormalizedExt(ext string) FileExt

GetNormalizedExt lowercases and ensures a leading '.' for an extension.

type ImageData

type ImageData struct {
	ImageInfo

	Base64Data string `json:"base64Data,omitempty"` // optional, if requested
}

ImageData holds metadata (and optionally content) for an image file.

func ReadImage

func ReadImage(
	path string,
	includeBase64Data bool,
	maxBytes int64,
) (*ImageData, error)

ReadImage inspects an image file and returns its intrinsic metadata. If includeBase64 is true, Base64Data will contain the base64-encoded file contents. If the file does not exist, Exists == false and err == nil. Returns an error if the path is empty, a directory, or not a supported image.

type ImageInfo

type ImageInfo struct {
	PathInfo

	Width    int      `json:"width,omitempty"`
	Height   int      `json:"height,omitempty"`
	Format   string   `json:"format,omitempty"`   // e.g. "jpeg", "png"
	MIMEType MIMEType `json:"mimeType,omitempty"` // e.g. "image/jpeg"
}

type MIMEDetectMethod added in v0.4.0

type MIMEDetectMethod string

MIMEDetectMethod describes how MIME detection was performed. It is intentionally coarse (extension vs sniff).

const (
	MIMEDetectMethodExtension MIMEDetectMethod = "extension"
	MIMEDetectMethodSniff     MIMEDetectMethod = "sniff"
)

type MIMEType

type MIMEType string
const (
	MIMEEmpty                  MIMEType = ""
	MIMEApplicationOctetStream MIMEType = "application/octet-stream"

	MIMETextPlain    MIMEType = "text/plain; charset=utf-8"
	MIMETextMarkdown MIMEType = "text/markdown; charset=utf-8"
	MIMETextHTML     MIMEType = "text/html; charset=utf-8"
	MIMETextCSS      MIMEType = "text/css; charset=utf-8"

	MIMEApplicationJSON MIMEType = "application/json"
	MIMEApplicationXML  MIMEType = "application/xml"
	MIMEApplicationYAML MIMEType = "application/x-yaml"
	MIMEApplicationTOML MIMEType = "application/toml"
	MIMEApplicationSQL  MIMEType = "application/sql"
	MIMEApplicationJS   MIMEType = "application/javascript"

	MIMEImageJPEG MIMEType = "image/jpeg"
	MIMEImagePNG  MIMEType = "image/png"
	MIMEImageGIF  MIMEType = "image/gif"
	MIMEImageWEBP MIMEType = "image/webp"
	MIMEImageBMP  MIMEType = "image/bmp"
	MIMEImageSVG  MIMEType = "image/svg+xml"

	MIMEApplicationPDF        MIMEType = "application/pdf"
	MIMEApplicationMSWord     MIMEType = "application/msword"
	MIMEApplicationMSPowerPt  MIMEType = "application/vnd.ms-powerpoint"
	MIMEApplicationMSExcel    MIMEType = "application/vnd.ms-excel"
	MIMEApplicationOpenXMLDoc MIMEType = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
	MIMEApplicationOpenXMLPPT MIMEType = "application/vnd.openxmlformats-officedocument.presentationml.presentation"
	MIMEApplicationOpenXMLXLS MIMEType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
	MIMEApplicationODT        MIMEType = "application/vnd.oasis.opendocument.text"
	MIMEApplicationODS        MIMEType = "application/vnd.oasis.opendocument.spreadsheet"
)

func MIMEFromExtensionString

func MIMEFromExtensionString(ext string) (MIMEType, error)

MIMEFromExtensionString returns a best-known MIME for the given extension string. Accepts "png" as well as ".png" (useful because image.DecodeConfig returns "png").

Lookup order: internal registry -> stdlib mime.TypeByExtension. If the extension cannot be resolved, returns application/octet-stream and ErrUnknownExtension.

type NewlineKind added in v0.6.0

type NewlineKind string

NewlineKind describes the newline convention detected in a file.

const (
	NewlineLF   NewlineKind = "lf"
	NewlineCRLF NewlineKind = "crlf"
)

type PathInfo

type PathInfo struct {
	Path    string     `json:"path"`
	Name    string     `json:"name"`
	Exists  bool       `json:"exists"`
	IsDir   bool       `json:"isDir"`
	Size    int64      `json:"size,omitempty"`
	ModTime *time.Time `json:"modTime,omitempty"`
}

func StatPath

func StatPath(path string) (pathInfo *PathInfo, err error)

StatPath returns basic metadata for the supplied path without mutating the filesystem. If the path does not exist, exists == false and err == nil.

type ReadEncoding

type ReadEncoding string
const (
	ReadEncodingText   ReadEncoding = "text"
	ReadEncodingBinary ReadEncoding = "binary"
)

type TextFile added in v0.6.0

type TextFile struct {
	Path            string
	Perm            fs.FileMode
	Newline         NewlineKind
	HasFinalNewline bool
	Lines           []string
	SizeBytes       int64
	ModTimeUTC      *time.Time
}

TextFile is a normalized in-memory view of a UTF‑8 text file. Lines never include trailing newline characters.

func ReadTextFileUTF8 added in v0.6.0

func ReadTextFileUTF8(path string, maxBytes int64) (*TextFile, error)

ReadTextFileUTF8 reads a file as UTF‑8 text and returns a normalized TextFile view. It preserves newline kind (LF vs CRLF) and whether the file ended with a final newline.

Safety behavior:

  • Enforces maxBytes if > 0.
  • Refuses symlink file and symlink parent directories (best effort).
  • Validates UTF‑8.

func (*TextFile) Render added in v0.6.0

func (t *TextFile) Render() string

Render converts Lines back into a file string preserving newline style and final newline presence.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL