Some checks are pending
Docs Deploy / build_and_deploy (push) Waiting to run
Generate Docs / cli (push) Waiting to run
Generate Config Doc / cli (push) Waiting to run
Go formatting / go-formatting (push) Waiting to run
Check links / markdown-link-check (push) Waiting to run
Integration / pre-test (push) Waiting to run
Integration / test on (push) Blocked by required conditions
Integration / status (push) Blocked by required conditions
Lint / Lint Go code (push) Waiting to run
Test / test (ubuntu-latest) (push) Waiting to run
79 lines
2.1 KiB
Go
79 lines
2.1 KiB
Go
package tarball
|
|
|
|
import (
|
|
"archive/tar"
|
|
"compress/gzip"
|
|
"io"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/ignite/cli/v29/ignite/pkg/errors"
|
|
)
|
|
|
|
var (
|
|
// ErrGzipFileNotFound the file not found in the gzip.
|
|
ErrGzipFileNotFound = errors.New("file not found in the gzip")
|
|
// ErrNotGzipType the file is not a gzip.
|
|
ErrNotGzipType = errors.New("file is not a gzip type")
|
|
// ErrInvalidFileName the file name is invalid.
|
|
ErrInvalidFileName = errors.New("invalid file name")
|
|
// ErrInvalidFilePath the file path is invalid.
|
|
ErrInvalidFilePath = errors.New("invalid file path")
|
|
// ErrFileTooLarge the file is too large to extract.
|
|
ErrFileTooLarge = errors.New("file too large to extract")
|
|
)
|
|
|
|
// ExtractFile founds and reads a specific file into a gzip file and folders recursively.
|
|
func ExtractFile(reader io.Reader, out io.Writer, fileName string) (string, error) {
|
|
if fileName == "" {
|
|
return "", ErrInvalidFileName
|
|
}
|
|
archive, err := gzip.NewReader(reader)
|
|
// Verify if is a GZIP file
|
|
if errors.Is(err, io.EOF) || errors.Is(err, gzip.ErrHeader) {
|
|
return "", ErrNotGzipType
|
|
} else if err != nil {
|
|
return "", err
|
|
}
|
|
defer archive.Close()
|
|
|
|
tarReader := tar.NewReader(archive)
|
|
// Read the tarball files and find only the necessary file
|
|
for {
|
|
header, err := tarReader.Next()
|
|
if errors.Is(err, io.EOF) {
|
|
return "", ErrGzipFileNotFound
|
|
} else if err != nil {
|
|
return header.Name, err
|
|
}
|
|
|
|
// Validate the file path
|
|
if !isValidPath(header.Name) {
|
|
return "", ErrInvalidFilePath
|
|
}
|
|
|
|
switch header.Typeflag {
|
|
case tar.TypeDir:
|
|
continue
|
|
case tar.TypeReg:
|
|
name := filepath.Base(header.Name)
|
|
if fileName == name {
|
|
// Limit the size of the file to extract
|
|
if header.Size > 100<<20 { // 100 MB limit
|
|
return "", ErrFileTooLarge
|
|
}
|
|
limitedReader := io.LimitReader(tarReader, 1000<<20) // 1000 MB limit
|
|
_, err := io.Copy(out, limitedReader)
|
|
return header.Name, err
|
|
}
|
|
default:
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
|
|
// isValidPath checks for directory traversal attacks.
|
|
func isValidPath(filePath string) bool {
|
|
cleanPath := filepath.Clean(filePath)
|
|
return !strings.Contains(cleanPath, "..")
|
|
}
|