81 lines
2.7 KiB
Go
81 lines
2.7 KiB
Go
package archive
|
|
|
|
import (
|
|
"archive/tar"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
type TarReader interface {
|
|
Next() (*tar.Header, error)
|
|
Read(b []byte) (int, error)
|
|
}
|
|
|
|
// NormalizingTarReader read from the wrapped TarReader normalizes header before passing them through to the caller
|
|
// NormalizingTarReader always normalizes header.Name so that path separators match the runtime OS
|
|
// Other modifications can be enabled by invoking options on the NormalizingTarReader
|
|
type NormalizingTarReader struct {
|
|
TarReader
|
|
headerOpts []HeaderOpt
|
|
excludedPaths []string
|
|
}
|
|
|
|
// Strip removes leading directories for any subsequently read *tar.Header
|
|
func (tr *NormalizingTarReader) Strip(prefix string) {
|
|
tr.headerOpts = append(tr.headerOpts, func(header *tar.Header) *tar.Header {
|
|
header.Name = strings.TrimPrefix(header.Name, prefix)
|
|
return header
|
|
})
|
|
}
|
|
|
|
// ExcludedPaths configures an array of paths to be skipped on subsequent calls to Next
|
|
// Children of the configured paths will also be skipped
|
|
// paths should match the unmodified Name of the *tar.Header returned by the wrapped TarReader, not the normalized headers
|
|
func (tr *NormalizingTarReader) ExcludePaths(paths []string) {
|
|
tr.excludedPaths = paths
|
|
}
|
|
|
|
// PrependDir will set the Name of any subsequently read *tar.Header the result of filepath.Join of dir and the
|
|
//
|
|
// original Name
|
|
func (tr *NormalizingTarReader) PrependDir(dir string) {
|
|
tr.headerOpts = append(tr.headerOpts, func(hdr *tar.Header) *tar.Header {
|
|
// Suppress gosec check for zip slip vulnerability, as we set dir in our code.
|
|
// #nosec G305
|
|
hdr.Name = filepath.Join(dir, hdr.Name)
|
|
return hdr
|
|
})
|
|
}
|
|
|
|
// NewNormalizingTarReader creates a NormalizingTarReaders that wraps the provided TarReader
|
|
func NewNormalizingTarReader(tr TarReader) *NormalizingTarReader {
|
|
return &NormalizingTarReader{TarReader: tr}
|
|
}
|
|
|
|
// Next calls Next on the wrapped TarReader and applies modifications before returning the *tar.Header
|
|
// If the wrapped TarReader returns a *tar.Header matching an excluded path Next will proceed to the next entry,
|
|
//
|
|
// returning the first non-excluded entry
|
|
//
|
|
// Modification options will be apply in the order the options were invoked.
|
|
// Standard modifications (path separators normalization) are applied last.
|
|
func (tr *NormalizingTarReader) Next() (*tar.Header, error) {
|
|
hdr, err := tr.TarReader.Next()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, excluded := range tr.excludedPaths {
|
|
if strings.HasPrefix(hdr.Name, excluded) {
|
|
return tr.Next() // If path is excluded move on to the next entry
|
|
}
|
|
}
|
|
for _, opt := range tr.headerOpts {
|
|
hdr = opt(hdr)
|
|
}
|
|
if hdr.Name == "" {
|
|
return tr.Next() // If entire path is stripped move on to the next entry
|
|
}
|
|
hdr.Name = filepath.FromSlash(hdr.Name)
|
|
return hdr, nil
|
|
}
|