Skip to content

Commit

Permalink
Refactor WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
djdv committed Apr 12, 2018
1 parent f41db12 commit ff28315
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 82 deletions.
115 changes: 85 additions & 30 deletions extractor.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,28 @@ import (
)

type Extractor struct {
Path string
Progress func(int64) int64
sanitizePaths bool
sanitizeLinks bool
stubLinks bool
Path string
Progress func(int64) int64

sanitizePathFunc SanitizePathFunc
sanitizePathCallback SanitizePathCallback

// A LinkFunc can be provided for user specified handling of filesystem links
LinkFunc func(Link) error
}

type Link struct {
Root, Name, Target string
}

type SanitizePathFunc func(pathComponents []string) (joinedComponents string)
type SanitizePathCallback func(input, output string) (userDefined error)

func (te *Extractor) Extract(reader io.Reader) error {
if isNullDevice(te.Path) {
return nil
}

tarReader := tar.NewReader(reader)

// Check if the output path already exists, so we know whether we should
Expand Down Expand Up @@ -64,35 +78,40 @@ func (te *Extractor) Extract(reader io.Reader) error {
return nil
}

// Sanitize toggles all output sanitation rules
// Sanitize toggles output sanitation, using default sanitation functions
func (te *Extractor) Sanitize(toggle bool) {
te.sanitizePaths = toggle
te.sanitizeLinks = toggle
}

// SanitizePaths toggles converting illegal paths to valid paths on Extract
func (te *Extractor) SanitizePaths(toggle bool) {
te.sanitizePaths = toggle

te.sanitizePathFunc = sanitizePath
te.LinkFunc = func(inLink Link) error {
if err := childrenOnly(inLink); err != nil {
return err
}
if err := platformAnalyzeLink(inLink); err != nil {
return err
}
return os.Symlink(inLink.Target, inLink.Name)
}
}

// SanitizeLinks toggles failure for links that are absolute or escape the output root on Extract
func (te *Extractor) SanitizeLinks(toggle bool) {
te.sanitizeLinks = toggle
// A SanitizePathFunc can be provided if you wish to filter individual path components
func (te *Extractor) SetPathSanitizer(f SanitizePathFunc) {
te.sanitizePathFunc = f
}

// StubLinks toggles link stubbing. When enabled, all links are created as empty files instead of links
func (te *Extractor) StubLinks(toggle bool) {
te.stubLinks = toggle
// A SanitizePathCallback can be provided if you wish to be called after the SanitizePathFunc
func (te *Extractor) SetPathSanitizerCallback(cb SanitizePathCallback) {
if te.sanitizePathFunc == nil {
te.sanitizePathFunc = sanitizePath
}
te.sanitizePathCallback = cb
}

// outputPath returns the path at which to place tarPath
func (te *Extractor) outputPath(tarPath string) string {
var outPath string
elems := strings.Split(tarPath, "/") // break into elems
elems = elems[1:] // remove original root
if te.sanitizePaths {
outPath = platformSanitize(elems) // sanitize base path elements to be platform legal
if te.sanitizePathFunc != nil {
outPath = te.sanitizePathFunc(elems) // sanitize base path elements to be platform legal
} else {
outPath = fp.Join(elems...) // join elems
}
Expand All @@ -102,6 +121,11 @@ func (te *Extractor) outputPath(tarPath string) string {

func (te *Extractor) extractDir(h *tar.Header, depth int) error {
path := te.outputPath(h.Name)
if te.sanitizePathCallback != nil {
if err := te.sanitizePathCallback(h.Name, path); err != nil {
return err
}
}

if depth == 0 {
// if this is the root directory, use it as the output path for remaining files
Expand All @@ -112,21 +136,29 @@ func (te *Extractor) extractDir(h *tar.Header, depth int) error {
}

func (te *Extractor) extractSymlink(h *tar.Header) error {
if te.stubLinks {
f, err := os.Create(te.outputPath(h.Name))
f.Close()
return err
path := te.outputPath(h.Name)
if te.sanitizePathCallback != nil {
if err := te.sanitizePathCallback(h.Name, path); err != nil {
return err
}
}
if te.sanitizeLinks {
return platformLink(te.Path, h.Linkname, te.outputPath(h.Name))

if te.LinkFunc != nil {
return te.LinkFunc(Link{Root: te.Path, Name: h.Name, Target: h.Linkname})
}
return os.Symlink(h.Linkname, te.outputPath(h.Name))

return os.Symlink(h.Linkname, path)
}

func (te *Extractor) extractFile(h *tar.Header, r *tar.Reader, depth int, rootExists bool, rootIsDir bool) error {
path := te.outputPath(h.Name)
if te.sanitizePathCallback != nil {
if err := te.sanitizePathCallback(h.Name, path); err != nil {
return err
}
}

if depth == 0 { // if depth is 0, this is the only file (we aren't 'ipfs get'ing a directory)
if depth == 0 { // if depth is 0, this is the only file (we aren't extracting a directory)
if rootExists && rootIsDir {
// putting file inside of a root dir.
fnameo := gopath.Base(h.Name)
Expand Down Expand Up @@ -167,5 +199,28 @@ func copyWithProgress(to io.Writer, from io.Reader, cb func(int64) int64) error
return err
}
}
}

// childrenOnly is a link-filter base, called before platform specific filters
func childrenOnly(inLink Link) error {
if fp.IsAbs(inLink.Target) {
return fmt.Errorf("Link target %q is an absolute path (forbidden)", inLink.Target) //TODO: discuss
}

resolvedTarget := fp.Join(inLink.Name, inLink.Target)
rel, err := fp.Rel(inLink.Root, resolvedTarget)
if err != nil {
return err
}

//disallow symlinks from climbing out of the target root
if strings.HasPrefix(rel, "..") {
return fmt.Errorf("Symlink target %q escapes target root %q", inLink.Target, inLink.Root)
}
//disallow pointing to self from parent ("../self" resolves to "self")
if resolvedTarget == inLink.Root {
return fmt.Errorf("Symlink target %q points to itself %q", inLink.Target, inLink.Root)
}

return nil
}
27 changes: 9 additions & 18 deletions sanitize.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,19 @@
package tar

import (
"fmt"
"os"
"path/filepath"
"strings"
)

func platformSanitize(pathElements []string) string {
return filepath.Join(pathElements...)
func isNullDevice(path string) bool {
return path == os.DevNull
}

//func platformLink(target, link string) error {
//func platformLink(linkBase string, linkHeader *tar.Header) error {
func platformLink(targetRoot, target, link string) error {
//prevent symlinks from accessing outside of the output root
resolvedTarget := filepath.Join(target, link)
rel, err := filepath.Rel(targetRoot, resolvedTarget)
if err != nil {
return err
}
if strings.HasPrefix(rel, "..") {
return fmt.Errorf("Symlink target %q escapes target root %q", target, targetRoot)
}
return os.Symlink(target, link)
//return os.Symlink(h.Linkname, linkBase te.outputPath(h.Name))
func sanitizePath(pathElements []string) (string, error) {
return filepath.Join(pathElements...), nil
}

//func sanitizeLink(targetRoot, link Link) (Link, error) {
func platformAnalyzeLink(inLink Link) error {
return nil
}
11 changes: 10 additions & 1 deletion sanitize_darwin.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
package tar

import (
"os"
"path/filepath"
"strings"
)

func platformSanitize(pathElements []string) string {
func isNullDevice(path string) bool {
return path == os.DevNull
}

func sanitizePath(pathElements []string) string {
res := filepath.Join(pathElements...)
return strings.Replace(res, ":", "-", -1)
}

func platformAnalyzeLink(inLink Link) error {
return nil
}
44 changes: 11 additions & 33 deletions sanitize_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@ func init() {
reservedCharsRegex = regexp.MustCompile(reservedCharsStr)
}

func platformSanitize(pathElements []string) string {
func isNullDevice(path string) bool {
return strings.ToUpper(path) == os.DevNull
}

func sanitizePath(pathElements []string) string {
//first pass: strip illegal tail & prefix reserved names `CON .` -> `_CON`
for pi := range pathElements {
pathElements[pi] = strings.TrimRight(pathElements[pi], ". ") //MSDN: Do not end a file or directory name with a space or a period
Expand Down Expand Up @@ -53,41 +57,15 @@ func platformSanitize(pathElements []string) string {
res = builder.String()
}

return filepath.FromSlash(res)
}

func platformLink(targetRoot, target, link string) error {
if filepath.IsAbs(target) {
return fmt.Errorf("Link target %q is an absolute path (forbidden)", target) //TODO: discuss
}

if strings.HasPrefix(target, string(os.PathSeparator)) || strings.HasPrefix(target, "/") {
return fmt.Errorf("Link target %q is relative to drive root (forbidden)", target) //TODO: discuss
}
res = filepath.FromSlash(res)

resolvedTarget := filepath.Join(link, target)
rel, err := filepath.Rel(targetRoot, resolvedTarget)
if err != nil {
return err
}
return res
}

//disallow symlinks from climbing out of the target root
if strings.HasPrefix(rel, "..") {
return fmt.Errorf("Symlink target %q escapes target root %q", target, targetRoot)
}
//disallow pointing to self from parent ("../self" resolves to "self")
if resolvedTarget == targetRoot {
return fmt.Errorf("Symlink target %q points to itself %q", target, targetRoot)
func platformAnalyzeLink(inLink Link) error {
if strings.HasPrefix(inLink.Target, string(os.PathSeparator)) || strings.HasPrefix(inLink.Target, "/") {
return fmt.Errorf("Link target %q is relative to drive root (forbidden)", inLink.Target) //TODO: discuss
}

return os.Symlink(target, link)
/* TODO: placeholder url; a modified version of this should be in client code, not this lib
if err != nil {
if lErr, ok := err.(*os.LinkError); ok && lErr.Err == windows.ERROR_PRIVILEGE_NOT_HELD {
return fmt.Errorf("Symlink creation privileges not held, see: https://github.com/ipfs/go-ipfs/blob/master/docs/windows.md#troubleshooting")
}
return err
}
return nil
*/
}

0 comments on commit ff28315

Please sign in to comment.