Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion 7z.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,14 +92,17 @@ func (x *XFile) un7zip(zipFile *sevenzip.File) (int64, string, error) {
defer zFile.Close()

file := &file{
Path: x.clean(zipFile.Name),
Data: zFile,
FileMode: zipFile.Mode(),
DirMode: x.DirMode,
Mtime: zipFile.Modified,
Atime: zipFile.Accessed,
}

if file.Path, err = x.clean(zipFile.Name); err != nil {
return 0, file.Path, err
}

if !strings.HasPrefix(file.Path, x.OutputDir) {
// The file being written is trying to write outside of our base path. Malicious archive?
err := fmt.Errorf("%s: %w: %s (from: %s)", zipFile.FileInfo().Name(), ErrInvalidPath, file.Path, zipFile.Name)
Expand Down
5 changes: 4 additions & 1 deletion ar.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,16 @@ func (x *XFile) unAr(reader io.Reader) (int64, []string, error) {
}

file := &file{
Path: x.clean(header.Name),
Data: arReader,
FileMode: os.FileMode(header.Mode), //nolint:gosec // what else ya gonna do with this?
DirMode: x.DirMode,
Mtime: header.ModTime,
}

if file.Path, err = x.clean(header.Name); err != nil {
return 0, files, err
}

if !strings.HasPrefix(file.Path, x.OutputDir) {
// The file being written is trying to write outside of our base path. Malicious archive?
return size, files, fmt.Errorf("%s: %w: %s (from: %s)", x.FilePath, ErrInvalidPath, file.Path, header.Name)
Expand Down
6 changes: 5 additions & 1 deletion cpio.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,17 @@ func (x *XFile) uncpio(reader io.Reader) (int64, []string, error) {

func (x *XFile) uncpioFile(cpioFile *cpio.Header, cpioReader *cpio.Reader) (int64, error) {
file := &file{
Path: x.clean(cpioFile.Name),
Data: cpioReader,
FileMode: cpioFile.FileInfo().Mode(),
DirMode: x.DirMode,
Mtime: cpioFile.ModTime,
}

var err error
if file.Path, err = x.clean(cpioFile.Name); err != nil {
return 0, err
}

if !strings.HasPrefix(file.Path, x.OutputDir) {
// The file being written is trying to write outside of the base path. Malicious archive?
return 0, fmt.Errorf("%s: %w: %s (from: %s)", cpioFile.FileInfo().Name(), ErrInvalidPath, file.Path, cpioFile.Name)
Expand Down
96 changes: 72 additions & 24 deletions decompress.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,18 @@ func ExtractXZ(xFile *XFile) (size int64, filesList []string, err error) {
return 0, nil, fmt.Errorf("xz.NewReader: %w", err)
}

// Get the absolute path of the file being written.
file := &file{
Path: xFile.clean(xFile.FilePath, ".xz"),
Data: zipReader,
FileMode: xFile.FileMode,
DirMode: xFile.DirMode,
}

// Get the absolute path of the file being written.
file.Path, err = xFile.clean(xFile.FilePath, ".xz")
if err != nil {
return 0, nil, err
}

size, err = xFile.write(file)
if err != nil {
return size, nil, err
Expand All @@ -60,14 +64,18 @@ func ExtractZlib(xFile *XFile) (size int64, filesList []string, err error) {
}
defer zipReader.Close()

// Get the absolute path of the file being written.
file := &file{
Path: xFile.clean(xFile.FilePath, ".zz", ".zlib"),
Data: zipReader,
FileMode: xFile.FileMode,
DirMode: xFile.DirMode,
}

// Get the absolute path of the file being written.
file.Path, err = xFile.clean(xFile.FilePath, ".zz", ".zlib")
if err != nil {
return 0, nil, err
}

size, err = xFile.write(file)
if err != nil {
return size, nil, err
Expand All @@ -89,14 +97,18 @@ func ExtractLZMA(xFile *XFile) (size int64, filesList []string, err error) {
return 0, nil, fmt.Errorf("lzma.NewReader: %w", err)
}

// Get the absolute path of the file being written.
file := &file{
Path: xFile.clean(xFile.FilePath, ".lzma", ".lz", ".lzip"),
Data: zipReader,
FileMode: xFile.FileMode,
DirMode: xFile.DirMode,
}

// Get the absolute path of the file being written.
file.Path, err = xFile.clean(xFile.FilePath, ".lzma", ".lz", ".lzip")
if err != nil {
return 0, nil, err
}

size, err = xFile.write(file)
if err != nil {
return size, nil, err
Expand All @@ -118,14 +130,18 @@ func ExtractLZMA2(xFile *XFile) (size int64, filesList []string, err error) {
return 0, nil, fmt.Errorf("lzma.NewReader2: %w", err)
}

// Get the absolute path of the file being written.
file := &file{
Path: xFile.clean(xFile.FilePath, ".lzma", ".lzma2"),
Data: zipReader,
FileMode: xFile.FileMode,
DirMode: xFile.DirMode,
}

// Get the absolute path of the file being written.
file.Path, err = xFile.clean(xFile.FilePath, ".lzma", ".lzma2")
if err != nil {
return 0, nil, err
}

size, err = xFile.write(file)
if err != nil {
return size, nil, err
Expand All @@ -148,14 +164,18 @@ func ExtractZstandard(xFile *XFile) (size int64, filesList []string, err error)
}
defer zipReader.Close()

// Get the absolute path of the file being written.
file := &file{
Path: xFile.clean(xFile.FilePath, ".zstd", ".zst"),
Data: zipReader,
FileMode: xFile.FileMode,
DirMode: xFile.DirMode,
}

// Get the absolute path of the file being written.
file.Path, err = xFile.clean(xFile.FilePath, ".zstd", ".zst")
if err != nil {
return 0, nil, err
}

size, err = xFile.write(file)
if err != nil {
return size, nil, err
Expand All @@ -177,14 +197,18 @@ func ExtractLZW(xFile *XFile) (size int64, filesList []string, err error) {
return 0, nil, fmt.Errorf("lzw.NewReader: %w", err)
}

// Get the absolute path of the file being written.
file := &file{
Path: xFile.clean(xFile.FilePath, ".Z"),
Data: zipReader,
FileMode: xFile.FileMode,
DirMode: xFile.DirMode,
}

// Get the absolute path of the file being written.
file.Path, err = xFile.clean(xFile.FilePath, ".Z")
if err != nil {
return 0, nil, err
}

size, err = xFile.write(file)
if err != nil {
return size, nil, err
Expand All @@ -201,14 +225,18 @@ func ExtractLZ4(xFile *XFile) (size int64, filesList []string, err error) {
}
defer compressedFile.Close()

// Get the absolute path of the file being written.
file := &file{
Path: xFile.clean(xFile.FilePath, ".lz4"),
Data: lz4.NewReader(compressedFile),
FileMode: xFile.FileMode,
DirMode: xFile.DirMode,
}

// Get the absolute path of the file being written.
file.Path, err = xFile.clean(xFile.FilePath, ".lz4")
if err != nil {
return 0, nil, err
}

size, err = xFile.write(file)
if err != nil {
return size, nil, err
Expand All @@ -225,14 +253,18 @@ func ExtractSnappy(xFile *XFile) (size int64, filesList []string, err error) {
}
defer compressedFile.Close()

// Get the absolute path of the file being written.
file := &file{
Path: xFile.clean(xFile.FilePath, ".snappy", ".sz"),
Data: snappy.NewReader(compressedFile),
FileMode: xFile.FileMode,
DirMode: xFile.DirMode,
}

// Get the absolute path of the file being written.
file.Path, err = xFile.clean(xFile.FilePath, ".snappy", ".sz")
if err != nil {
return 0, nil, err
}

size, err = xFile.write(file)
if err != nil {
return size, nil, err
Expand All @@ -249,14 +281,18 @@ func ExtractS2(xFile *XFile) (size int64, filesList []string, err error) {
}
defer compressedFile.Close()

// Get the absolute path of the file being written.
file := &file{
Path: xFile.clean(xFile.FilePath, ".s2"),
Data: s2.NewReader(compressedFile),
FileMode: xFile.FileMode,
DirMode: xFile.DirMode,
}

// Get the absolute path of the file being written.
file.Path, err = xFile.clean(xFile.FilePath, ".s2")
if err != nil {
return 0, nil, err
}

size, err = xFile.write(file)
if err != nil {
return size, nil, err
Expand All @@ -273,14 +309,18 @@ func ExtractBrotli(xFile *XFile) (size int64, filesList []string, err error) {
}
defer compressedFile.Close()

// Get the absolute path of the file being written.
file := &file{
Path: xFile.clean(xFile.FilePath, ".brotli", ".br"),
Data: brotli.NewReader(compressedFile),
FileMode: xFile.FileMode,
DirMode: xFile.DirMode,
}

// Get the absolute path of the file being written.
file.Path, err = xFile.clean(xFile.FilePath, ".brotli", ".br")
if err != nil {
return 0, nil, err
}

size, err = xFile.write(file)
if err != nil {
return size, nil, err
Expand All @@ -297,14 +337,18 @@ func ExtractBzip(xFile *XFile) (size int64, filesList []string, err error) {
}
defer compressedFile.Close()

// Get the absolute path of the file being written.
file := &file{
Path: xFile.clean(xFile.FilePath, ".bz", ".bz2"),
Data: bzip2.NewReader(compressedFile),
FileMode: xFile.FileMode,
DirMode: xFile.DirMode,
}

// Get the absolute path of the file being written.
file.Path, err = xFile.clean(xFile.FilePath, ".bz", ".bz2")
if err != nil {
return 0, nil, err
}

size, err = xFile.write(file)
if err != nil {
return size, nil, err
Expand All @@ -327,15 +371,19 @@ func ExtractGzip(xFile *XFile) (size int64, filesList []string, err error) {
}
defer zipReader.Close()

// Get the absolute path of the file being written.
file := &file{
Path: xFile.clean(xFile.FilePath, ".gz"),
Data: zipReader,
FileMode: xFile.FileMode,
DirMode: xFile.DirMode,
Mtime: zipReader.ModTime,
}

// Get the absolute path of the file being written.
file.Path, err = xFile.clean(xFile.FilePath, ".gz")
if err != nil {
return 0, nil, err
}

size, err = xFile.write(file)
if err != nil {
return size, nil, err
Expand Down
30 changes: 30 additions & 0 deletions encoding.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package xtractr

import "fmt"

/* This file will surely grow when someone writes a proper character encoding detector. */

// EncoderInput is used as input for a custom encoder procedure.
type EncoderInput struct {
FileName string
XFile *XFile
}

// decode a string using the provided decoder.
func (x *XFile) decode(input string) (string, error) {
if x.Encoder == nil {
return input, nil
}

encoding := x.Encoder(&EncoderInput{FileName: input, XFile: x})
if encoding == nil {
return input, nil
}

output, err := encoding.String(input)
if err != nil {
return "", fmt.Errorf("decoding file name: %w", err)
}

return output, nil
}
15 changes: 13 additions & 2 deletions files.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import (
"regexp"
"strings"
"time"

"golang.org/x/text/encoding"
)

// ArchiveList is the value returned when searching for compressed files.
Expand Down Expand Up @@ -112,6 +114,9 @@ type XFile struct {
Password string
// (RAR/7z) Archive passwords (to try multiple).
Passwords []string
// If file names are not UTF8 encoded, pass your own encoder here.
// Provide a function that takes in a file name and returns an encoder for it.
Encoder func(*EncoderInput) *encoding.Decoder
// If the archive only has one directory in the root, then setting
// this true will cause the extracted content to be moved into the
// output folder, and the root folder in the archive to be removed.
Expand Down Expand Up @@ -473,16 +478,22 @@ func (x *Xtractr) Rename(oldpath, newpath string) error {
}

// clean returns an absolute path for a file inside the OutputDir.
// clean also decodes the file name using a provided decoder.
// If trim length is > 0, then the suffixes are trimmed, and filepath removed.
func (x *XFile) clean(filePath string, trim ...string) string {
func (x *XFile) clean(filePath string, trim ...string) (string, error) {
filePath, err := x.decode(filePath)
if err != nil {
return "", err
}

if len(trim) != 0 {
filePath = filepath.Base(filePath)
for _, suffix := range trim {
filePath = strings.TrimSuffix(filePath, suffix)
}
}

return filepath.Clean(filepath.Join(x.OutputDir, filePath))
return filepath.Clean(filepath.Join(x.OutputDir, filePath)), nil
}

// AllExcept can be used as an input to ExcludeSuffix in a Filter.
Expand Down
Loading
Loading