package bundle import ( "bytes" "context" "io" "net/http" "os" "path" "path/filepath" "strings" "github.com/pkg/errors" "gitlab.com/wpetit/goweb/logger" ) type FileSystem struct { prefix string bundle Bundle } func (fs *FileSystem) Open(name string) (http.File, error) { if filepath.Separator != '/' && strings.ContainsRune(name, filepath.Separator) || strings.Contains(name, "\x00") { return nil, errors.New("http: invalid character in file path") } p := path.Join(fs.prefix, strings.TrimPrefix(name, "/")) ctx := logger.With( context.Background(), logger.F("filename", name), ) logger.Debug(ctx, "opening file") readCloser, fileInfo, err := fs.bundle.File(p) if err != nil { if os.IsNotExist(err) { return nil, err } return nil, errors.Wrapf(err, "could not open bundle file '%s'", p) } defer readCloser.Close() file := &File{ fi: fileInfo, } if fileInfo.IsDir() { files, err := fs.bundle.Dir(p) if err != nil { return nil, errors.Wrapf(err, "could not read bundle directory '%s'", p) } file.files = files } else { data, err := io.ReadAll(readCloser) if err != nil { logger.Error(ctx, "could not read bundle file", logger.CapturedE(errors.WithStack(err))) return nil, errors.Wrapf(err, "could not read bundle file '%s'", p) } file.Reader = bytes.NewReader(data) } return file, nil } func NewFileSystem(prefix string, bundle Bundle) *FileSystem { return &FileSystem{prefix, bundle} } type File struct { *bytes.Reader fi os.FileInfo files []os.FileInfo } // A noop-closer. func (f *File) Close() error { return nil } func (f *File) Readdir(count int) ([]os.FileInfo, error) { if f.fi.IsDir() && f.files != nil { return f.files, nil } return nil, os.ErrNotExist } func (f *File) Stat() (os.FileInfo, error) { return f.fi, nil } var _ http.FileSystem = &FileSystem{}