diff options
Diffstat (limited to 'internal/iohelpers/writers.go')
| -rw-r--r-- | internal/iohelpers/writers.go | 55 |
1 files changed, 55 insertions, 0 deletions
diff --git a/internal/iohelpers/writers.go b/internal/iohelpers/writers.go index 5030d275..870fa4cb 100644 --- a/internal/iohelpers/writers.go +++ b/internal/iohelpers/writers.go @@ -6,7 +6,13 @@ import ( "errors" "fmt" "io" + "io/fs" + "os" + "path/filepath" + "strings" "sync" + + "github.com/hack-pad/hackpadfs" ) type emptySkipper struct { @@ -212,3 +218,52 @@ func (l *lazyWriteCloser) Write(p []byte) (n int, err error) { } return w.Write(p) } + +// WriteFile writes the given content to the file, truncating any existing file, +// and creating the directory structure leading up to it if necessary. +func WriteFile(fsys fs.FS, filename string, content []byte) error { + err := assertPathInWD(filename) + if err != nil { + return fmt.Errorf("failed to open %s: %w", filename, err) + } + + fi, err := fs.Stat(fsys, filename) + if err != nil && !errors.Is(err, fs.ErrNotExist) { + return fmt.Errorf("failed to stat %s: %w", filename, err) + } + mode := NormalizeFileMode(0o644) + if fi != nil { + mode = fi.Mode() + } + + err = hackpadfs.MkdirAll(fsys, filepath.Dir(filename), 0o755) + if err != nil { + return fmt.Errorf("failed to make dirs for %s: %w", filename, err) + } + + err = hackpadfs.WriteFullFile(fsys, filename, content, mode) + if err != nil { + return fmt.Errorf("failed to write %s: %w", filename, err) + } + + return nil +} + +func assertPathInWD(filename string) error { + wd, err := os.Getwd() + if err != nil { + return err + } + f, err := filepath.Abs(filename) + if err != nil { + return err + } + r, err := filepath.Rel(wd, f) + if err != nil { + return err + } + if strings.HasPrefix(r, "..") { + return fmt.Errorf("path %s not contained by working directory %s (rel: %s)", filename, wd, r) + } + return nil +} |
