From 1d661548c68e1ddc7d32c471064cfe906f8465dc Mon Sep 17 00:00:00 2001 From: Dominic Della Valle Date: Mon, 12 Mar 2018 10:22:26 -0400 Subject: [PATCH] Sanitize `get` output paths for external file systems License: MIT Signed-off-by: Dominic Della Valle --- core/commands/get.go | 6 ++-- thirdparty/tar/extractor.go | 26 ++++++++--------- thirdparty/tar/sanitize.go | 9 ++++++ thirdparty/tar/sanitize_darwin.go | 11 +++++++ thirdparty/tar/sanitize_windows.go | 47 ++++++++++++++++++++++++++++++ 5 files changed, 83 insertions(+), 16 deletions(-) create mode 100644 thirdparty/tar/sanitize.go create mode 100644 thirdparty/tar/sanitize_darwin.go create mode 100644 thirdparty/tar/sanitize_windows.go diff --git a/core/commands/get.go b/core/commands/get.go index 3393007331c..e4981449151 100644 --- a/core/commands/get.go +++ b/core/commands/get.go @@ -6,7 +6,7 @@ import ( "fmt" "io" "os" - gopath "path" + "path/filepath" "strings" core "github.com/ipfs/go-ipfs/core" @@ -183,8 +183,8 @@ func getOutPath(req *cmds.Request) string { outPath, _ := req.Options["output"].(string) if outPath == "" { trimmed := strings.TrimRight(req.Arguments[0], "/") - _, outPath = gopath.Split(trimmed) - outPath = gopath.Clean(outPath) + _, outPath = filepath.Split(trimmed) + outPath = filepath.Clean(outPath) } return outPath } diff --git a/thirdparty/tar/extractor.go b/thirdparty/tar/extractor.go index fddf8082eea..182e767c678 100644 --- a/thirdparty/tar/extractor.go +++ b/thirdparty/tar/extractor.go @@ -61,33 +61,33 @@ func (te *Extractor) Extract(reader io.Reader) error { return nil } -// outputPath returns the path at whicht o place tarPath -func (te *Extractor) outputPath(tarPath string) string { - elems := strings.Split(tarPath, "/") // break into elems - elems = elems[1:] // remove original root - - path := fp.Join(elems...) // join elems - path = fp.Join(te.Path, path) // rebase on extractor root - return path +// outputPath returns the path at which to place tarPath +func (te *Extractor) outputPath(header *tar.Header) string { + elems := strings.Split(header.Name, "/") // break into elems + elems = elems[1:] // remove IPFS root + safePath := platformSanitize(elems) // assure that our output path is platform legal + fmt.Printf("DBG: %q -> %q\n", header.Name, safePath) + safePath = fp.Join(te.Path, safePath) // rebase on to extraction target root + return safePath } func (te *Extractor) extractDir(h *tar.Header, depth int) error { - path := te.outputPath(h.Name) - + path := te.outputPath(h) if depth == 0 { - // if this is the root root directory, use it as the output path for remaining files + // if this is the root directory, use it as the output path for remaining files te.Path = path } + //path := platformSanitizeDir(h.Name) return os.MkdirAll(path, 0755) } func (te *Extractor) extractSymlink(h *tar.Header) error { - return os.Symlink(h.Linkname, te.outputPath(h.Name)) + return os.Symlink(h.Linkname, te.outputPath(h)) } func (te *Extractor) extractFile(h *tar.Header, r *tar.Reader, depth int, rootExists bool, rootIsDir bool) error { - path := te.outputPath(h.Name) + path := te.outputPath(h) if depth == 0 { // if depth is 0, this is the only file (we aren't 'ipfs get'ing a directory) if rootExists && rootIsDir { diff --git a/thirdparty/tar/sanitize.go b/thirdparty/tar/sanitize.go new file mode 100644 index 00000000000..22dff95fe91 --- /dev/null +++ b/thirdparty/tar/sanitize.go @@ -0,0 +1,9 @@ +// +build !windows,!darwin + +package tar + +import "path/filepath" + +func platformSanitize(pathElements []string) string { + return filepath.Join(pathElements...) +} diff --git a/thirdparty/tar/sanitize_darwin.go b/thirdparty/tar/sanitize_darwin.go new file mode 100644 index 00000000000..30c9d648c34 --- /dev/null +++ b/thirdparty/tar/sanitize_darwin.go @@ -0,0 +1,11 @@ +package tar + +import ( + "path/filepath" + "strings" +) + +func platformSanitize(pathElements []string) string { + res := filepath.Join(pathElements...) + return strings.Replace(res, ":", "-", -1) +} diff --git a/thirdparty/tar/sanitize_windows.go b/thirdparty/tar/sanitize_windows.go new file mode 100644 index 00000000000..c96923a3abc --- /dev/null +++ b/thirdparty/tar/sanitize_windows.go @@ -0,0 +1,47 @@ +package tar + +import ( + "net/url" + "path/filepath" + "regexp" + "strings" +) + +//https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx +var reservedNames = [...]string{"CON", "PRN", "AUX", "NUL", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9"} + +const reservedCharsRegex = `[<>:"\\|?*]` //NOTE: `/` is not included, files with this in the name will cause problems + +func platformSanitize(pathElements []string) string { + // first pass: scan and prefix reserved names CON -> _CON + for i, pe := range pathElements { + for _, rn := range reservedNames { + if pe == rn { + pathElements[i] = "_" + rn + break + } + } + pathElements[i] = strings.TrimRight(pe, ". ") //MSDN: Do not end a file or directory name with a space or a period + } + //second pass: scan and encode reserved characters ? -> %3F + res := strings.Join(pathElements, `/`) // intentionally avoiding [file]path.Clean(), we want `\`'s intact + re := regexp.MustCompile(reservedCharsRegex) + illegalIndices := re.FindAllStringIndex(res, -1) + + if illegalIndices != nil { + var lastIndex int + var builder strings.Builder + allocAssist := (len(res) - len(illegalIndices)) + (len(illegalIndices) * 3) // 3 = encoded length + builder.Grow(allocAssist) + + for _, si := range illegalIndices { + builder.WriteString(res[lastIndex:si[0]]) // append up to problem char + builder.WriteString(url.QueryEscape(res[si[0]:si[1]])) // escape and append problem char + lastIndex = si[1] + } + builder.WriteString(res[lastIndex:]) // append remainder + res = builder.String() + } + + return filepath.FromSlash(res) +}