server(filesystem): allow decompressing individual files
Implements logic for handling a file that is compressed but isn't an archive. Fixes https://github.com/pterodactyl/panel/issues/5034 Signed-off-by: Matthew Penner <me@matthewp.io>
This commit is contained in:
parent
1c5ddcd20c
commit
f1c5bbd42d
100
server/filesystem/archiverext/compressed.go
Normal file
100
server/filesystem/archiverext/compressed.go
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
// SPDX-FileCopyrightText: Copyright (c) 2016 Matthew Holt
|
||||||
|
|
||||||
|
// Code in this file was derived from
|
||||||
|
// https://github.com/mholt/archiver/blob/v4.0.0-alpha.8/fs.go
|
||||||
|
//
|
||||||
|
// These modifications were necessary to allow us to use an already open file
|
||||||
|
// with archiver.FileFS.
|
||||||
|
|
||||||
|
package archiverext
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"io/fs"
|
||||||
|
|
||||||
|
"github.com/mholt/archiver/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FileFS allows accessing a file on disk using a consistent file system interface.
|
||||||
|
// The value should be the path to a regular file, not a directory. This file will
|
||||||
|
// be the only entry in the file system and will be at its root. It can be accessed
|
||||||
|
// within the file system by the name of "." or the filename.
|
||||||
|
//
|
||||||
|
// If the file is compressed, set the Compression field so that reads from the
|
||||||
|
// file will be transparently decompressed.
|
||||||
|
type FileFS struct {
|
||||||
|
// File is the compressed file backing the FileFS.
|
||||||
|
File fs.File
|
||||||
|
|
||||||
|
// If file is compressed, setting this field will
|
||||||
|
// transparently decompress reads.
|
||||||
|
Compression archiver.Decompressor
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open opens the named file, which must be the file used to create the file system.
|
||||||
|
func (f FileFS) Open(name string) (fs.File, error) {
|
||||||
|
if err := f.checkName(name, "open"); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if f.Compression == nil {
|
||||||
|
return f.File, nil
|
||||||
|
}
|
||||||
|
r, err := f.Compression.OpenReader(f.File)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return compressedFile{f.File, r}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadDir returns a directory listing with the file as the singular entry.
|
||||||
|
func (f FileFS) ReadDir(name string) ([]fs.DirEntry, error) {
|
||||||
|
if err := f.checkName(name, "stat"); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
info, err := f.Stat(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return []fs.DirEntry{fs.FileInfoToDirEntry(info)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stat stats the named file, which must be the file used to create the file system.
|
||||||
|
func (f FileFS) Stat(name string) (fs.FileInfo, error) {
|
||||||
|
if err := f.checkName(name, "stat"); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return f.File.Stat()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f FileFS) checkName(name, op string) error {
|
||||||
|
if !fs.ValidPath(name) {
|
||||||
|
return &fs.PathError{Op: "open", Path: name, Err: fs.ErrInvalid}
|
||||||
|
}
|
||||||
|
// TODO: we may need better name validation.
|
||||||
|
if name != "." {
|
||||||
|
return &fs.PathError{Op: op, Path: name, Err: fs.ErrNotExist}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// compressedFile is an fs.File that specially reads
|
||||||
|
// from a decompression reader, and which closes both
|
||||||
|
// that reader and the underlying file.
|
||||||
|
type compressedFile struct {
|
||||||
|
fs.File
|
||||||
|
decomp io.ReadCloser
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cf compressedFile) Read(p []byte) (int, error) {
|
||||||
|
return cf.decomp.Read(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cf compressedFile) Close() error {
|
||||||
|
err := cf.File.Close()
|
||||||
|
err2 := cf.decomp.Close()
|
||||||
|
if err2 != nil && err == nil {
|
||||||
|
err = err2
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
|
@ -16,6 +16,7 @@ import (
|
||||||
"github.com/mholt/archiver/v4"
|
"github.com/mholt/archiver/v4"
|
||||||
|
|
||||||
"github.com/pterodactyl/wings/internal/ufs"
|
"github.com/pterodactyl/wings/internal/ufs"
|
||||||
|
"github.com/pterodactyl/wings/server/filesystem/archiverext"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CompressFiles compresses all the files matching the given paths in the
|
// CompressFiles compresses all the files matching the given paths in the
|
||||||
|
@ -85,11 +86,12 @@ func (fs *Filesystem) archiverFileSystem(ctx context.Context, p string) (iofs.FS
|
||||||
return zip.NewReader(f, info.Size())
|
return zip.NewReader(f, info.Size())
|
||||||
case archiver.Archival:
|
case archiver.Archival:
|
||||||
return archiver.ArchiveFS{Stream: io.NewSectionReader(f, 0, info.Size()), Format: ff, Context: ctx}, nil
|
return archiver.ArchiveFS{Stream: io.NewSectionReader(f, 0, info.Size()), Format: ff, Context: ctx}, nil
|
||||||
|
case archiver.Compression:
|
||||||
|
return archiverext.FileFS{File: f, Compression: ff}, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = f.Close()
|
_ = f.Close()
|
||||||
return nil, nil
|
return nil, archiver.ErrNoMatch
|
||||||
}
|
}
|
||||||
|
|
||||||
// SpaceAvailableForDecompression looks through a given archive and determines
|
// SpaceAvailableForDecompression looks through a given archive and determines
|
||||||
|
|
Loading…
Reference in New Issue
Block a user