Skip to content
This repository was archived by the owner on Mar 29, 2023. It is now read-only.

Commit 447f558

Browse files
authored
chore(filewriter): cleanup writes (#43)
* chore(filewriter): cleanup writes
1 parent 929a486 commit 447f558

8 files changed

+182
-3
lines changed

filewriter.go

+18-2
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,28 @@
11
package files
22

33
import (
4+
"errors"
45
"fmt"
56
"io"
67
"os"
78
"path/filepath"
89
)
910

11+
var ErrInvalidDirectoryEntry = errors.New("invalid directory entry name")
12+
var ErrPathExistsOverwrite = errors.New("path already exists and overwriting is not allowed")
13+
1014
// WriteTo writes the given node to the local filesystem at fpath.
1115
func WriteTo(nd Node, fpath string) error {
16+
if _, err := os.Lstat(fpath); err == nil {
17+
return ErrPathExistsOverwrite
18+
} else if !os.IsNotExist(err) {
19+
return err
20+
}
1221
switch nd := nd.(type) {
1322
case *Symlink:
1423
return os.Symlink(nd.Target, fpath)
1524
case File:
16-
f, err := os.Create(fpath)
25+
f, err := createNewFile(fpath)
1726
defer f.Close()
1827
if err != nil {
1928
return err
@@ -31,7 +40,14 @@ func WriteTo(nd Node, fpath string) error {
3140

3241
entries := nd.Entries()
3342
for entries.Next() {
34-
child := filepath.Join(fpath, entries.Name())
43+
entryName := entries.Name()
44+
if entryName == "" ||
45+
entryName == "." ||
46+
entryName == ".." ||
47+
!isValidFilename(entryName) {
48+
return ErrInvalidDirectoryEntry
49+
}
50+
child := filepath.Join(fpath, entryName)
3551
if err := WriteTo(entries.Node(), child); err != nil {
3652
return err
3753
}

filewriter_test.go

+24
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import (
66
"os"
77
"path/filepath"
88
"testing"
9+
10+
"github.com/stretchr/testify/assert"
911
)
1012

1113
func TestWriteTo(t *testing.T) {
@@ -75,3 +77,25 @@ func TestWriteTo(t *testing.T) {
7577
t.Fatalf("failed to find: %#v", expected)
7678
}
7779
}
80+
81+
func TestDontAllowOverwrite(t *testing.T) {
82+
tmppath, err := ioutil.TempDir("", "files-test")
83+
assert.NoError(t, err)
84+
defer os.RemoveAll(tmppath)
85+
86+
path := filepath.Join(tmppath, "output")
87+
88+
// Check we can actually write to the output path before trying invalid entries
89+
// and leave an existing entry to test overwrite protection.
90+
assert.NoError(t, WriteTo(NewMapDirectory(map[string]Node{
91+
"exisiting-entry": NewBytesFile(nil),
92+
}), path))
93+
94+
assert.Equal(t, ErrPathExistsOverwrite, WriteTo(NewBytesFile(nil), filepath.Join(path)))
95+
assert.Equal(t, ErrPathExistsOverwrite, WriteTo(NewBytesFile(nil), filepath.Join(path, "exisiting-entry")))
96+
// The directory in `path` has already been created so this should fail too:
97+
assert.Equal(t, ErrPathExistsOverwrite, WriteTo(NewMapDirectory(map[string]Node{
98+
"any-name": NewBytesFile(nil),
99+
}), filepath.Join(path)))
100+
os.RemoveAll(path)
101+
}

filewriter_unix.go

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
//go:build darwin || linux || netbsd || openbsd
2+
// +build darwin linux netbsd openbsd
3+
4+
package files
5+
6+
import (
7+
"os"
8+
"strings"
9+
"syscall"
10+
)
11+
12+
var invalidChars = `/` + "\x00"
13+
14+
func isValidFilename(filename string) bool {
15+
return !strings.ContainsAny(filename, invalidChars)
16+
}
17+
18+
func createNewFile(path string) (*os.File, error) {
19+
return os.OpenFile(path, os.O_EXCL|os.O_CREATE|os.O_WRONLY|syscall.O_NOFOLLOW, 0666)
20+
}

filewriter_unix_test.go

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
//go:build darwin || linux || netbsd || openbsd
2+
// +build darwin linux netbsd openbsd
3+
4+
package files
5+
6+
import (
7+
"io/ioutil"
8+
"os"
9+
"path/filepath"
10+
"testing"
11+
12+
"github.com/stretchr/testify/assert"
13+
)
14+
15+
func TestWriteToInvalidPaths(t *testing.T) {
16+
tmppath, err := ioutil.TempDir("", "files-test")
17+
assert.NoError(t, err)
18+
defer os.RemoveAll(tmppath)
19+
20+
path := filepath.Join(tmppath, "output")
21+
22+
// Check we can actually write to the output path before trying invalid entries.
23+
assert.NoError(t, WriteTo(NewMapDirectory(map[string]Node{
24+
"valid-entry": NewBytesFile(nil),
25+
}), path))
26+
os.RemoveAll(path)
27+
28+
// Now try all invalid entry names
29+
for _, entryName := range []string{"", ".", "..", "/", "", "not/a/base/path"} {
30+
assert.Equal(t, ErrInvalidDirectoryEntry, WriteTo(NewMapDirectory(map[string]Node{
31+
entryName: NewBytesFile(nil),
32+
}), filepath.Join(path)))
33+
os.RemoveAll(path)
34+
}
35+
}

filewriter_windows.go

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
//go:build windows
2+
// +build windows
3+
4+
package files
5+
6+
import (
7+
"os"
8+
"strings"
9+
)
10+
11+
var invalidChars = `<>:"/\|?*` + "\x00"
12+
13+
var reservedNames = map[string]struct{}{
14+
"CON": {},
15+
"PRN": {},
16+
"AUX": {},
17+
"NUL": {},
18+
"COM1": {},
19+
"COM2": {},
20+
"COM3": {},
21+
"COM4": {},
22+
"COM5": {},
23+
"COM6": {},
24+
"COM7": {},
25+
"COM8": {},
26+
"COM9": {},
27+
"LPT1": {},
28+
"LPT2": {},
29+
"LPT3": {},
30+
"LPT4": {},
31+
"LPT5": {},
32+
"LPT6": {},
33+
"LPT7": {},
34+
"LPT8": {},
35+
"LPT9": {},
36+
}
37+
38+
func isValidFilename(filename string) bool {
39+
_, isReservedName := reservedNames[filename]
40+
return !strings.ContainsAny(filename, invalidChars) &&
41+
!isReservedName
42+
}
43+
44+
func createNewFile(path string) (*os.File, error) {
45+
return os.OpenFile(path, os.O_EXCL|os.O_CREATE|os.O_WRONLY, 0666)
46+
}

filewriter_windows_test.go

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
//go:build windows
2+
// +build windows
3+
4+
package files
5+
6+
import (
7+
"io/ioutil"
8+
"os"
9+
"path/filepath"
10+
"testing"
11+
12+
"github.com/stretchr/testify/assert"
13+
)
14+
15+
func TestWriteToInvalidPaths(t *testing.T) {
16+
tmppath, err := ioutil.TempDir("", "files-test")
17+
assert.NoError(t, err)
18+
defer os.RemoveAll(tmppath)
19+
20+
path := filepath.Join(tmppath, "output")
21+
22+
// Check we can actually write to the output path before trying invalid entries.
23+
assert.NoError(t, WriteTo(NewMapDirectory(map[string]Node{
24+
"valid-entry": NewBytesFile(nil),
25+
}), path))
26+
os.RemoveAll(path)
27+
28+
// Now try all invalid entry names
29+
for _, entryName := range []string{"", ".", "..", "/", "", "not/a/base/path",
30+
"<", ">", ":", "\"", "\\", "|", "?", "*", "\x00",
31+
"CON", "PRN", "AUX", "NUL", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9"} {
32+
assert.Equal(t, ErrInvalidDirectoryEntry, WriteTo(NewMapDirectory(map[string]Node{
33+
entryName: NewBytesFile(nil),
34+
}), filepath.Join(path)))
35+
os.RemoveAll(path)
36+
}
37+
}

go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ module github.com/ipfs/go-ipfs-files
22

33
require (
44
github.com/crackcomm/go-gitignore v0.0.0-20170627025303-887ab5e44cc3
5-
github.com/stretchr/testify v1.7.0 // indirect
5+
github.com/stretchr/testify v1.7.0
66
golang.org/x/sys v0.0.0-20190302025703-b6889370fb10
77
)
88

go.sum

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5Cc
99
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
1010
golang.org/x/sys v0.0.0-20190302025703-b6889370fb10 h1:xQJI9OEiErEQ++DoXOHqEpzsGMrAv2Q2jyCpi7DmfpQ=
1111
golang.org/x/sys v0.0.0-20190302025703-b6889370fb10/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
12+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
1213
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
1314
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
1415
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

0 commit comments

Comments
 (0)