149 lines
3.8 KiB
Go
149 lines
3.8 KiB
Go
package workspace
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestSafePathAcceptsCleanRelativePaths(t *testing.T) {
|
|
root := t.TempDir()
|
|
|
|
got, err := SafePath(root, "source/manifest.json")
|
|
if err != nil {
|
|
t.Fatalf("SafePath: %v", err)
|
|
}
|
|
|
|
want := filepath.Join(root, "source", "manifest.json")
|
|
if got != want {
|
|
t.Fatalf("SafePath = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestSafePathRejectsUnsafeNames(t *testing.T) {
|
|
root := t.TempDir()
|
|
tests := []struct {
|
|
name string
|
|
path string
|
|
want string
|
|
}{
|
|
{name: "empty", path: " ", want: "empty"},
|
|
{name: "absolute", path: filepath.Join(root, "artifact.json"), want: "relative"},
|
|
{name: "parent segment", path: "../artifact.json", want: ".."},
|
|
{name: "embedded parent", path: "source/../artifact.json", want: ".."},
|
|
{name: "backslash", path: `source\artifact.json`, want: "slash-separated"},
|
|
{name: "unclean", path: "source//artifact.json", want: "clean"},
|
|
{name: "dot", path: ".", want: ".."},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
got, err := SafePath(root, tc.path)
|
|
if err == nil {
|
|
t.Fatalf("SafePath returned %q, want error", got)
|
|
}
|
|
if !strings.Contains(err.Error(), tc.want) {
|
|
t.Fatalf("SafePath error = %v, want containing %q", err, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSafePathRejectsEmptyRoot(t *testing.T) {
|
|
got, err := SafePath(" ", "artifact.json")
|
|
if err == nil {
|
|
t.Fatalf("SafePath returned %q, want error", got)
|
|
}
|
|
if !strings.Contains(err.Error(), "root") {
|
|
t.Fatalf("SafePath error = %v, want root error", err)
|
|
}
|
|
}
|
|
|
|
func TestSafePathDoesNotPermitEscapingRoot(t *testing.T) {
|
|
root := t.TempDir()
|
|
for _, name := range []string{
|
|
"..",
|
|
"../outside.json",
|
|
"nested/../../outside.json",
|
|
} {
|
|
t.Run(name, func(t *testing.T) {
|
|
got, err := SafePath(root, name)
|
|
if err == nil {
|
|
t.Fatalf("SafePath returned %q, want error", got)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestWriteJSONWritesIndentedAtomicArtifact(t *testing.T) {
|
|
root := t.TempDir()
|
|
|
|
err := WriteJSON(root, "source/manifest.json", map[string]any{
|
|
"status": "succeeded",
|
|
"count": 2,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("WriteJSON: %v", err)
|
|
}
|
|
|
|
got := string(readFile(t, filepath.Join(root, "source", "manifest.json")))
|
|
if !strings.HasSuffix(got, "\n") {
|
|
t.Fatalf("expected trailing newline, got %q", got)
|
|
}
|
|
if !strings.Contains(got, `"status": "succeeded"`) || !strings.Contains(got, `"count": 2`) {
|
|
t.Fatalf("unexpected JSON: %s", got)
|
|
}
|
|
assertNoTempFiles(t, filepath.Join(root, "source"))
|
|
}
|
|
|
|
func TestWriteBytesWritesNestedArtifact(t *testing.T) {
|
|
root := t.TempDir()
|
|
|
|
if err := WriteBytes(root, "chunk/chunks.json", []byte("payload")); err != nil {
|
|
t.Fatalf("WriteBytes: %v", err)
|
|
}
|
|
|
|
got := string(readFile(t, filepath.Join(root, "chunk", "chunks.json")))
|
|
if got != "payload" {
|
|
t.Fatalf("bytes = %q, want payload", got)
|
|
}
|
|
assertNoTempFiles(t, filepath.Join(root, "chunk"))
|
|
}
|
|
|
|
func TestWritersRejectUnsafePaths(t *testing.T) {
|
|
root := t.TempDir()
|
|
|
|
if err := WriteBytes(root, "../outside.json", []byte("payload")); err == nil {
|
|
t.Fatalf("WriteBytes accepted unsafe path")
|
|
}
|
|
if err := WriteJSON(root, `debug\trace.json`, map[string]string{"x": "y"}); err == nil {
|
|
t.Fatalf("WriteJSON accepted unsafe path")
|
|
}
|
|
if _, err := os.Stat(filepath.Join(root, "..", "outside.json")); !os.IsNotExist(err) {
|
|
t.Fatalf("outside path stat err = %v, want not exist", err)
|
|
}
|
|
}
|
|
|
|
func readFile(t *testing.T, path string) []byte {
|
|
t.Helper()
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
t.Fatalf("read %q: %v", path, err)
|
|
}
|
|
return data
|
|
}
|
|
|
|
func assertNoTempFiles(t *testing.T, dir string) {
|
|
t.Helper()
|
|
entries, err := os.ReadDir(dir)
|
|
if err != nil {
|
|
t.Fatalf("read dir %q: %v", dir, err)
|
|
}
|
|
for _, entry := range entries {
|
|
if strings.Contains(entry.Name(), ".tmp-") {
|
|
t.Fatalf("temporary file was not cleaned up: %s", entry.Name())
|
|
}
|
|
}
|
|
}
|