diff --git a/dataswamp/services.go b/dataswamp/services.go index 5021830..6dcca34 100644 --- a/dataswamp/services.go +++ b/dataswamp/services.go @@ -57,6 +57,12 @@ func (s SwampFileService) getOrCreateNs(namespace_in string) *namespace.Namespac func (s SwampFileService) SaveFile(ref swampfile.FileReference, src io.Reader, size int64) error { ns := s.getOrCreateNs(ref.UserAgent) + err := ref.Clean(true) + + if err != nil { + return err + } + if !ns.FileQuota.Allows(size) { return namespace.ErrExceedQuota } @@ -78,9 +84,11 @@ func (s SwampFileService) SaveFile(ref swampfile.FileReference, src io.Reader, s func (s SwampFileService) OpenOutFile(ref swampfile.FileReference) (swampfile.SwampOutFile, error) { ns := s.getOrCreateNs(ref.UserAgent) - // if err == namespace.ErrNotExists { - // return nil, err - // } + err := ref.Clean(true) + + if err != nil { + return nil, err + } f, err := s.swamp_file_repo.Open(ref.Path, strconv.FormatInt(ns.ID, 10)) diff --git a/dataswamp/services_test.go b/dataswamp/services_test.go index 3592ad1..b63deea 100644 --- a/dataswamp/services_test.go +++ b/dataswamp/services_test.go @@ -11,9 +11,9 @@ import ( m_swampfile "caj-larsson/bog/infrastructure/memory/swampfile" ) -var file_ref1 = swampfile.FileReference{"ptah1", "ua1"} -var file_ref2 = swampfile.FileReference{"path1", "ua2"} -var file_ref3 = swampfile.FileReference{"path2", "ua1"} +var file_ref1 = swampfile.FileReference{"/ptah1", "ns1"} +var file_ref2 = swampfile.FileReference{"/path1", "ns2"} +var file_ref3 = swampfile.FileReference{"/path2", "ns1"} func NewTestSwampFileService() SwampFileService { file_repo := m_swampfile.NewRepository() @@ -67,12 +67,12 @@ func TestFileIsReadBack(t *testing.T) { is.Equal(outfile.String(), "My bog data") } -func TestUAIsolation(t *testing.T) { +func TestNSIsolation(t *testing.T) { is := is.New(t) s := NewTestSwampFileService() - ns1_file := bytes.NewBufferString("My bog data ua1") - ns2_file := bytes.NewBufferString("My bog data ua2") + ns1_file := bytes.NewBufferString("My bog data ns1") + ns2_file := bytes.NewBufferString("My bog data ns2") _ = s.SaveFile(file_ref1, ns1_file, int64(ns1_file.Len())) _ = s.SaveFile(file_ref2, ns2_file, int64(ns2_file.Len())) @@ -82,11 +82,26 @@ func TestUAIsolation(t *testing.T) { outfile := bytes.NewBufferString("") _, _ = outfile.ReadFrom(outswampfile) - is.Equal(outfile.String(), "My bog data ua1") + is.Equal(outfile.String(), "My bog data ns1") } -func TestCleanPath(t *testing.T) { + +func TestPathStrictMode(t *testing.T) { is := is.New(t) + s := NewTestSwampFileService() + + ns_file := bytes.NewBufferString("My bog data ns1") + + ref := swampfile.FileReference { + "/path/../with/../backrefs", + "ns1", + } + + outfile, err := s.OpenOutFile(ref) + + is.Equal(err, swampfile.ErrUnacceptablePath) + is.Equal(outfile, nil) - is.Equal(CleanPath("/"), "/") + err = s.SaveFile(ref, ns_file, int64(ns_file.Len())) + is.Equal(err, swampfile.ErrUnacceptablePath) } diff --git a/dataswamp/swampfile/entities.go b/dataswamp/swampfile/entities.go index 2104779..5f51a61 100644 --- a/dataswamp/swampfile/entities.go +++ b/dataswamp/swampfile/entities.go @@ -13,10 +13,11 @@ type SwampFile struct { } var ( - ErrDuplicate = errors.New("record already exists") - ErrExceedQuota = errors.New("file too large") - ErrQuotaInvalid = errors.New("quota invalid") - ErrNotExists = errors.New("row not exists") - ErrUpdateFailed = errors.New("update failed") - ErrDeleteFailed = errors.New("delete failed") + ErrDuplicate = errors.New("record already exists") + ErrExceedQuota = errors.New("file too large") + ErrQuotaInvalid = errors.New("quota invalid") + ErrNotExists = errors.New("row not exists") + ErrUpdateFailed = errors.New("update failed") + ErrDeleteFailed = errors.New("delete failed") + ErrUnacceptablePath = errors.New("unacceptable path") ) diff --git a/dataswamp/swampfile/valueobjects.go b/dataswamp/swampfile/valueobjects.go index fe8e0ff..8f74da9 100644 --- a/dataswamp/swampfile/valueobjects.go +++ b/dataswamp/swampfile/valueobjects.go @@ -2,6 +2,9 @@ package swampfile import ( "io" + "path" + "path/filepath" + "strings" "time" ) @@ -27,3 +30,15 @@ type FileReference struct { Path string UserAgent string } + +func (fr *FileReference) Clean(strict bool) error { + c := filepath.FromSlash(path.Clean("/" + strings.Trim(fr.Path, "/"))) + + if c != fr.Path && strict { + return ErrUnacceptablePath + } + + fr.Path = c + + return nil +} diff --git a/dataswamp/swampfile/valueobjects_test.go b/dataswamp/swampfile/valueobjects_test.go new file mode 100644 index 0000000..a4a5c3b --- /dev/null +++ b/dataswamp/swampfile/valueobjects_test.go @@ -0,0 +1,44 @@ +package swampfile + +import ( + "github.com/matryer/is" + "testing" +) + +func TestSwampPathNotStrict(t *testing.T) { + cases := []struct { + dirty string + clean string + }{ + {"/", "/"}, + {"..", "/"}, + {"/../a", "/a"}, + {"/../a/../a", "/a"}, + {"../b", "/b"}, + } + + is := is.New(t) + + for _, tc := range cases { + rf := FileReference{ + tc.dirty, + "ns", + } + + err := rf.Clean(false) + is.NoErr(err) + is.Equal(rf.Path, tc.clean) + } +} + +func TestSwampPathStrict(t *testing.T) { + is := is.New(t) + + rf := FileReference{ + "/a/../b", + "ns", + } + + err := rf.Clean(true) + is.Equal(err, ErrUnacceptablePath) +}