summaryrefslogtreecommitdiffstats
path: root/dependencies/pkg/mod/golang.org/x/exp@v0.0.0-20220613132600-b0d781184e0d/cmd/txtar
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--dependencies/pkg/mod/golang.org/x/exp@v0.0.0-20220613132600-b0d781184e0d/cmd/txtar/txtar.go196
-rw-r--r--dependencies/pkg/mod/golang.org/x/exp@v0.0.0-20220613132600-b0d781184e0d/cmd/txtar/txtar_test.go178
2 files changed, 374 insertions, 0 deletions
diff --git a/dependencies/pkg/mod/golang.org/x/exp@v0.0.0-20220613132600-b0d781184e0d/cmd/txtar/txtar.go b/dependencies/pkg/mod/golang.org/x/exp@v0.0.0-20220613132600-b0d781184e0d/cmd/txtar/txtar.go
new file mode 100644
index 0000000..9beb163
--- /dev/null
+++ b/dependencies/pkg/mod/golang.org/x/exp@v0.0.0-20220613132600-b0d781184e0d/cmd/txtar/txtar.go
@@ -0,0 +1,196 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// The txtar command writes or extracts a text-based file archive in the format
+// provided by the golang.org/x/tools/txtar package.
+//
+// The default behavior is to read a comment from stdin and write the archive
+// file containing the recursive contents of the named files and directories,
+// including hidden files, to stdout. Any non-flag arguments to the command name
+// the files and/or directories to include, with the contents of directories
+// included recursively. An empty argument list is equivalent to ".".
+//
+// The --extract (or -x) flag instructs txtar to instead read the archive file
+// from stdin and extract all of its files to corresponding locations relative
+// to the current, writing the archive's comment to stdout.
+//
+// Archive files are by default extracted only to the current directory or its
+// subdirectories. To allow extracting outside the current directory, use the
+// --unsafe flag.
+//
+// Shell variables in paths are expanded (using os.Expand) if the corresponding
+// variable is set in the process environment. When writing an archive, the
+// variables (before expansion) are preserved in the archived paths.
+//
+// Example usage:
+//
+// txtar *.go <README >testdata/example.txt
+//
+// txtar --extract <playground_example.txt >main.go
+package main
+
+import (
+ "bytes"
+ "flag"
+ "fmt"
+ "io/ioutil"
+ "os"
+ "path"
+ "path/filepath"
+ "regexp"
+ "strings"
+ "time"
+
+ "golang.org/x/tools/txtar"
+)
+
+var (
+ extractFlag = flag.Bool("extract", false, "if true, extract files from the archive instead of writing to it")
+ unsafeFlag = flag.Bool("unsafe", false, "allow extraction of files outside the current directory")
+)
+
+func init() {
+ flag.BoolVar(extractFlag, "x", *extractFlag, "short alias for --extract")
+}
+
+func main() {
+ flag.Parse()
+
+ var err error
+ if *extractFlag {
+ if len(flag.Args()) > 0 {
+ fmt.Fprintln(os.Stderr, "Usage: txtar --extract <archive.txt")
+ os.Exit(2)
+ }
+ err = extract()
+ } else {
+ paths := flag.Args()
+ if len(paths) == 0 {
+ paths = []string{"."}
+ }
+ err = archive(paths)
+ }
+
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Error: %v\n", err)
+ os.Exit(1)
+ }
+}
+
+func extract() (err error) {
+ b, err := ioutil.ReadAll(os.Stdin)
+ if err != nil {
+ return err
+ }
+
+ ar := txtar.Parse(b)
+
+ if !*unsafeFlag {
+ // Check that no files are extracted outside the current directory
+ wd, err := os.Getwd()
+ if err != nil {
+ return err
+ }
+ // Add trailing separator to terminate wd.
+ // This prevents extracting to outside paths which prefix wd,
+ // e.g. extracting to /home/foobar when wd is /home/foo
+ if !strings.HasSuffix(wd, string(filepath.Separator)) {
+ wd += string(filepath.Separator)
+ }
+
+ for _, f := range ar.Files {
+ fileName := filepath.Clean(expand(f.Name))
+
+ if strings.HasPrefix(fileName, "..") ||
+ (filepath.IsAbs(fileName) && !strings.HasPrefix(fileName, wd)) {
+ return fmt.Errorf("file path '%s' is outside the current directory", f.Name)
+ }
+ }
+ }
+
+ for _, f := range ar.Files {
+ fileName := filepath.FromSlash(path.Clean(expand(f.Name)))
+ if err := os.MkdirAll(filepath.Dir(fileName), 0777); err != nil {
+ return err
+ }
+ if err := ioutil.WriteFile(fileName, f.Data, 0666); err != nil {
+ return err
+ }
+ }
+
+ if len(ar.Comment) > 0 {
+ os.Stdout.Write(ar.Comment)
+ }
+ return nil
+}
+
+func archive(paths []string) (err error) {
+ txtarHeader := regexp.MustCompile(`(?m)^-- .* --$`)
+
+ ar := new(txtar.Archive)
+ for _, p := range paths {
+ root := filepath.Clean(expand(p))
+ prefix := root + string(filepath.Separator)
+ err := filepath.Walk(root, func(fileName string, info os.FileInfo, err error) error {
+ if err != nil || info.IsDir() {
+ return err
+ }
+
+ suffix := ""
+ if fileName != root {
+ suffix = strings.TrimPrefix(fileName, prefix)
+ }
+ name := filepath.ToSlash(filepath.Join(p, suffix))
+
+ data, err := ioutil.ReadFile(fileName)
+ if err != nil {
+ return err
+ }
+ if txtarHeader.Match(data) {
+ return fmt.Errorf("cannot archive %s: file contains a txtar header", name)
+ }
+
+ ar.Files = append(ar.Files, txtar.File{Name: name, Data: data})
+ return nil
+ })
+ if err != nil {
+ return err
+ }
+ }
+
+ // After we have read all of the source files, read the comment from stdin.
+ //
+ // Wait until the read has been blocked for a while before prompting the user
+ // to enter it: if they are piping the comment in from some other file, the
+ // read should complete very quickly and there is no need for a prompt.
+ // (200ms is typically long enough to read a reasonable comment from the local
+ // machine, but short enough that humans don't notice it.)
+ //
+ // Don't prompt until we have successfully read the other files:
+ // if we encountered an error, we don't need to ask for a comment.
+ timer := time.AfterFunc(200*time.Millisecond, func() {
+ fmt.Fprintln(os.Stderr, "Enter comment:")
+ })
+ comment, err := ioutil.ReadAll(os.Stdin)
+ timer.Stop()
+ if err != nil {
+ return fmt.Errorf("reading comment from %s: %v", os.Stdin.Name(), err)
+ }
+ ar.Comment = bytes.TrimSpace(comment)
+
+ _, err = os.Stdout.Write(txtar.Format(ar))
+ return err
+}
+
+// expand is like os.ExpandEnv, but preserves unescaped variables (instead
+// of escaping them to the empty string) if the variable is not set.
+func expand(p string) string {
+ return os.Expand(p, func(key string) string {
+ v, ok := os.LookupEnv(key)
+ if !ok {
+ return "$" + key
+ }
+ return v
+ })
+}
diff --git a/dependencies/pkg/mod/golang.org/x/exp@v0.0.0-20220613132600-b0d781184e0d/cmd/txtar/txtar_test.go b/dependencies/pkg/mod/golang.org/x/exp@v0.0.0-20220613132600-b0d781184e0d/cmd/txtar/txtar_test.go
new file mode 100644
index 0000000..ea9f2bf
--- /dev/null
+++ b/dependencies/pkg/mod/golang.org/x/exp@v0.0.0-20220613132600-b0d781184e0d/cmd/txtar/txtar_test.go
@@ -0,0 +1,178 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main_test
+
+import (
+ "fmt"
+ "io/ioutil"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "runtime"
+ "strings"
+ "sync"
+ "testing"
+)
+
+const comment = "This is a txtar archive.\n"
+
+const testdata = `This is a txtar archive.
+-- one.txt --
+one
+-- dir/two.txt --
+two
+-- $SPECIAL_LOCATION/three.txt --
+three
+`
+
+func TestMain(m *testing.M) {
+ code := m.Run()
+ txtarBin.once.Do(func() {})
+ if txtarBin.name != "" {
+ os.Remove(txtarBin.name)
+ }
+ os.Exit(code)
+}
+
+func TestRoundTrip(t *testing.T) {
+ os.Setenv("SPECIAL_LOCATION", "special")
+ defer os.Unsetenv("SPECIAL_LOCATION")
+
+ // Expand the testdata archive into a temporary directory.
+ parentDir, err := ioutil.TempDir("", "txtar")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(parentDir)
+ dir := filepath.Join(parentDir, "dir")
+ if err := os.Mkdir(dir, 0755); err != nil {
+ t.Fatal(err)
+ }
+ if out, err := txtar(t, dir, testdata, "--extract"); err != nil {
+ t.Fatal(err)
+ } else if out != comment {
+ t.Fatalf("txtar --extract: stdout:\n%s\nwant:\n%s", out, comment)
+ }
+
+ // Now, re-archive its contents explicitly and ensure that the result matches
+ // the original.
+ args := []string{"one.txt", "dir", "$SPECIAL_LOCATION"}
+ if out, err := txtar(t, dir, comment, args...); err != nil {
+ t.Fatal(err)
+ } else if out != testdata {
+ t.Fatalf("txtar %s: archive:\n%s\n\nwant:\n%s", strings.Join(args, " "), out, testdata)
+ }
+}
+
+func TestUnsafePaths(t *testing.T) {
+ // Set up temporary directories for test archives.
+ parentDir, err := ioutil.TempDir("", "txtar")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(parentDir)
+ dir := filepath.Join(parentDir, "dir")
+ if err := os.Mkdir(dir, 0755); err != nil {
+ t.Fatal(err)
+ }
+
+ // Test --unsafe option for both absolute and relative paths
+ testcases := []struct{ name, path string }{
+ {"Absolute", filepath.Join(parentDir, "dirSpecial")},
+ {"Relative", "../special"},
+ }
+
+ for _, tc := range testcases {
+ t.Run(tc.name, func(t *testing.T) {
+ // Set SPECIAL_LOCATION outside the current directory
+ t.Setenv("SPECIAL_LOCATION", tc.path)
+
+ // Expand the testdata archive into a temporary directory.
+
+ // Should fail without the --unsafe flag
+ if _, err := txtar(t, dir, testdata, "--extract"); err == nil {
+ t.Fatalf("txtar --extract: extracts to unsafe paths")
+ }
+
+ // Should allow paths outside the current dir with the --unsafe flags
+ out, err := txtar(t, dir, testdata, "--extract", "--unsafe")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if out != comment {
+ t.Fatalf("txtar --extract --unsafe: stdout:\n%s\nwant:\n%s", out, comment)
+ }
+
+ // Now, re-archive its contents explicitly and ensure that the result matches
+ // the original.
+ args := []string{"one.txt", "dir", "$SPECIAL_LOCATION"}
+ out, err = txtar(t, dir, comment, args...)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if out != testdata {
+ t.Fatalf("txtar %s: archive:\n%s\n\nwant:\n%s", strings.Join(args, " "), out, testdata)
+ }
+ })
+ }
+}
+
+// txtar runs the txtar command in the given directory with the given input and
+// arguments.
+func txtar(t *testing.T, dir, input string, args ...string) (string, error) {
+ t.Helper()
+ cmd := exec.Command(txtarName(t), args...)
+ cmd.Dir = dir
+ cmd.Env = append(os.Environ(), "PWD="+dir)
+ cmd.Stdin = strings.NewReader(input)
+ stderr := new(strings.Builder)
+ cmd.Stderr = stderr
+ out, err := cmd.Output()
+ if err != nil {
+ return "", fmt.Errorf("%s: %v\n%s", strings.Join(cmd.Args, " "), err, stderr)
+ }
+ if stderr.String() != "" {
+ t.Logf("OK: %s\n%s", strings.Join(cmd.Args, " "), stderr)
+ }
+ return string(out), nil
+}
+
+var txtarBin struct {
+ once sync.Once
+ name string
+ err error
+}
+
+// txtarName returns the name of the txtar executable, building it if needed.
+func txtarName(t *testing.T) string {
+ t.Helper()
+ if _, err := exec.LookPath("go"); err != nil {
+ t.Skipf("cannot build txtar binary: %v", err)
+ }
+
+ txtarBin.once.Do(func() {
+ exe, err := ioutil.TempFile("", "txtar-*.exe")
+ if err != nil {
+ txtarBin.err = err
+ return
+ }
+ exe.Close()
+ txtarBin.name = exe.Name()
+
+ cmd := exec.Command("go", "build", "-o", txtarBin.name, ".")
+ out, err := cmd.CombinedOutput()
+ if err != nil {
+ txtarBin.err = fmt.Errorf("%s: %v\n%s", strings.Join(cmd.Args, " "), err, out)
+ }
+ })
+
+ if txtarBin.err != nil {
+ if runtime.GOOS == "android" {
+ t.Skipf("skipping test after failing to build txtar binary: go_android_exec may have failed to copy needed dependencies (see https://golang.org/issue/37088)")
+ }
+ t.Fatal(txtarBin.err)
+ }
+ return txtarBin.name
+}