84 lines
2.1 KiB
Go
84 lines
2.1 KiB
Go
|
|
package local
|
||
|
|
|
||
|
|
import (
|
||
|
|
"fmt"
|
||
|
|
"os"
|
||
|
|
"path/filepath"
|
||
|
|
"strings"
|
||
|
|
)
|
||
|
|
|
||
|
|
// New creates a new instance of the local storage medium.
|
||
|
|
// It requires a root path to sandbox all file operations.
|
||
|
|
func New(rootPath string) (*Medium, error) {
|
||
|
|
if err := os.MkdirAll(rootPath, os.ModePerm); err != nil {
|
||
|
|
return nil, fmt.Errorf("could not create root directory at %s: %w", rootPath, err)
|
||
|
|
}
|
||
|
|
return &Medium{root: rootPath}, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// path returns a full, safe path within the medium's root.
|
||
|
|
func (m *Medium) path(subpath string) (string, error) {
|
||
|
|
if strings.Contains(subpath, "..") {
|
||
|
|
return "", fmt.Errorf("path traversal attempt detected")
|
||
|
|
}
|
||
|
|
return filepath.Join(m.root, subpath), nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// Read retrieves the content of a file from the local disk.
|
||
|
|
func (m *Medium) Read(path string) (string, error) {
|
||
|
|
safePath, err := m.path(path)
|
||
|
|
if err != nil {
|
||
|
|
return "", err
|
||
|
|
}
|
||
|
|
data, err := os.ReadFile(safePath)
|
||
|
|
if err != nil {
|
||
|
|
return "", err
|
||
|
|
}
|
||
|
|
return string(data), nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// Write saves the given content to a file on the local disk.
|
||
|
|
func (m *Medium) Write(path, content string) error {
|
||
|
|
safePath, err := m.path(path)
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
dir := filepath.Dir(safePath)
|
||
|
|
if err := os.MkdirAll(dir, os.ModePerm); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
return os.WriteFile(safePath, []byte(content), 0644)
|
||
|
|
}
|
||
|
|
|
||
|
|
// EnsureDir makes sure a directory exists on the local disk.
|
||
|
|
func (m *Medium) EnsureDir(path string) error {
|
||
|
|
safePath, err := m.path(path)
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
return os.MkdirAll(safePath, os.ModePerm)
|
||
|
|
}
|
||
|
|
|
||
|
|
// IsFile checks if a path exists and is a regular file on the local disk.
|
||
|
|
func (m *Medium) IsFile(path string) bool {
|
||
|
|
safePath, err := m.path(path)
|
||
|
|
if err != nil {
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
info, err := os.Stat(safePath)
|
||
|
|
if os.IsNotExist(err) {
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
return !info.IsDir()
|
||
|
|
}
|
||
|
|
|
||
|
|
// FileGet is a convenience function that reads a file from the medium.
|
||
|
|
func (m *Medium) FileGet(path string) (string, error) {
|
||
|
|
return m.Read(path)
|
||
|
|
}
|
||
|
|
|
||
|
|
// FileSet is a convenience function that writes a file to the medium.
|
||
|
|
func (m *Medium) FileSet(path, content string) error {
|
||
|
|
return m.Write(path, content)
|
||
|
|
}
|