Skip to content

Commit

Permalink
Merge pull request #40 from jfontan/posix-index
Browse files Browse the repository at this point in the history
Store file names in POSIX format
  • Loading branch information
mcuadros authored Jan 28, 2019
2 parents 7da16ad + 4670656 commit 36b4509
Show file tree
Hide file tree
Showing 7 changed files with 100 additions and 41 deletions.
2 changes: 1 addition & 1 deletion SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ at all.
Each index entry has the following fields:

* Byte length of the entry name (uint32).
* Entry name (UTF-8 string).
* Entry name (UTF-8 string in UNIX format).
* UNIX mode (uint32) (see Go implementation [issue](https://github.com/src-d/go-siva/issues/11)).
* Modification time as UNIX time in nanoseconds (int64).
* Offset of the file content, relative to the beginning of the block (uint64).
Expand Down
Binary file modified fixtures/dirs.siva
Binary file not shown.
82 changes: 73 additions & 9 deletions index.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
"errors"
"fmt"
"io"
"path/filepath"
"path"
"sort"
"strings"
"time"
Expand All @@ -19,7 +19,7 @@ var (
ErrInvalidSignature = errors.New("invalid signature")
ErrEmptyIndex = errors.New("empty index")
ErrUnsupportedIndexVersion = errors.New("unsupported index version")
ErrCRC32Missmatch = errors.New("crc32 missmatch")
ErrCRC32Missmatch = errors.New("crc32 mismatch")
)

const (
Expand Down Expand Up @@ -219,6 +219,7 @@ func (i *Index) ToSafePaths() Index {

// Find returns the first IndexEntry with the given name, if any
func (i Index) Find(name string) *IndexEntry {
name = ToSafePath(name)
for _, e := range i {
if e.Name == name {
return e
Expand All @@ -231,9 +232,10 @@ func (i Index) Find(name string) *IndexEntry {
// Glob returns all index entries whose name matches pattern or nil if there is
// no matching entry. The syntax of patterns is the same as in filepath.Match.
func (i Index) Glob(pattern string) ([]*IndexEntry, error) {
pattern = ToSafePath(pattern)
matches := []*IndexEntry{}
for _, e := range i {
m, err := filepath.Match(pattern, e.Name)
m, err := path.Match(pattern, e.Name)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -529,13 +531,75 @@ func (e *IndexWriteError) Error() string {
//
// If your application relies on using absolute paths, you should not use this
// and you are encouraged to do your own validation and normalization.
func ToSafePath(path string) string {
volume := filepath.VolumeName(path)
func ToSafePath(p string) string {
volume := volumeName(p)
if volume != "" {
path = strings.Replace(path, volume, "", 1)
p = strings.Replace(p, volume, "", 1)
}

path = filepath.Join(string(filepath.Separator), path)
path = filepath.ToSlash(path)
return path[1:]
p = toSlash(p)
p = path.Join(posixSeparator, p)
return p[1:]
}

// These functions are copied from golang library path/filepath/path_windows.go
// as we need them also in posix systems.

const (
posixSeparator = "/"
windowsSeparator = "\\"
)

func toSlash(path string) string {
return strings.Replace(path, windowsSeparator, posixSeparator, -1)
}

func fromSlash(path string) string {
return strings.Replace(path, posixSeparator, windowsSeparator, -1)
}

func volumeName(path string) string {
return path[:volumeNameLen(path)]
}

func isSlash(c uint8) bool {
return c == '\\' || c == '/'
}

// volumeNameLen returns length of the leading volume name on Windows.
// It returns 0 elsewhere.
func volumeNameLen(path string) int {
if len(path) < 2 {
return 0
}
// with drive letter
c := path[0]
if path[1] == ':' && ('a' <= c && c <= 'z' || 'A' <= c && c <= 'Z') {
return 2
}
// is it UNC? https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx
if l := len(path); l >= 5 && isSlash(path[0]) && isSlash(path[1]) &&
!isSlash(path[2]) && path[2] != '.' {
// first, leading `\\` and next shouldn't be `\`. its server name.
for n := 3; n < l-1; n++ {
// second, next '\' shouldn't be repeated.
if isSlash(path[n]) {
n++
// third, following something characters. its share name.
if !isSlash(path[n]) {
if path[n] == '.' {
break
}
for ; n < l; n++ {
if isSlash(path[n]) {
break
}
}
return n
}
break
}
}
}
return 0
}
37 changes: 7 additions & 30 deletions index_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package siva

import (
"bytes"
"runtime"
"sort"
"time"

Expand Down Expand Up @@ -98,19 +97,21 @@ func (s *IndexSuite) TestFind(c *C) {
i := Index{
{Header: Header{Name: "foo"}, Start: 1},
{Header: Header{Name: "bar"}, Start: 2},
{Header: Header{Name: "dir/file.txt"}, Start: 3},
}

sort.Sort(i)

e := i.Find("bar")
c.Assert(e, NotNil)
c.Assert(e.Start, Equals, uint64(2))
}

func (s *IndexSuite) TestToSafePathsWindows(c *C) {
if runtime.GOOS != "windows" {
c.Skip("windows only")
}
e = i.Find("dir\\file.txt")
c.Assert(e, NotNil)
c.Assert(e.Start, Equals, uint64(3))
}

func (s *IndexSuite) TestToSafePaths(c *C) {
i := Index{
{Header: Header{Name: `C:\foo\bar`}, Start: 1},
{Header: Header{Name: `\\network\share\foo\bar`}, Start: 2},
Expand All @@ -129,27 +130,3 @@ func (s *IndexSuite) TestToSafePathsWindows(c *C) {
}
c.Assert(f, DeepEquals, expected)
}

func (s *IndexSuite) TestToSafePathsNotWindows(c *C) {
if runtime.GOOS == "windows" {
c.Skip("posix only")
}

i := Index{
{Header: Header{Name: `C:\foo\bar`}, Start: 1},
{Header: Header{Name: `\\network\share\foo\bar`}, Start: 2},
{Header: Header{Name: `/foo/bar`}, Start: 3},
{Header: Header{Name: `../bar`}, Start: 4},
{Header: Header{Name: `foo/bar/../../baz`}, Start: 5},
}

f := i.ToSafePaths()
expected := Index{
{Header: Header{Name: `C:\foo\bar`}, Start: 1},
{Header: Header{Name: `\\network\share\foo\bar`}, Start: 2},
{Header: Header{Name: `foo/bar`}, Start: 3},
{Header: Header{Name: `bar`}, Start: 4},
{Header: Header{Name: `baz`}, Start: 5},
}
c.Assert(f, DeepEquals, expected)
}
10 changes: 9 additions & 1 deletion reader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ func (s *ReaderSuite) TestSeekAndRead(c *C) {
}

func (s *ReaderSuite) TestIndexGlob(c *C) {
s.testIndexGlob(c, "*", []string{
"file.txt",
})
s.testIndexGlob(c, "*/*", []string{
"letters/a",
"letters/b",
Expand All @@ -94,6 +97,11 @@ func (s *ReaderSuite) TestIndexGlob(c *C) {
"letters/b",
"letters/c",
})
s.testIndexGlob(c, "numbers\\*", []string{
"numbers/1",
"numbers/2",
"numbers/3",
})
s.testIndexGlob(c, "nonexistent/*", []string{})
}

Expand All @@ -104,7 +112,7 @@ func (s *ReaderSuite) testIndexGlob(c *C, pattern string, expected []string) {
r := NewReader(f)
i, err := r.Index()
c.Assert(err, IsNil)
c.Assert(i, HasLen, 6)
c.Assert(i, HasLen, 7)

matches, err := i.Glob(pattern)
c.Assert(err, IsNil)
Expand Down
2 changes: 2 additions & 0 deletions writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ func (w *writer) WriteHeader(h *Header) error {
Start: w.position,
}

w.current.Name = ToSafePath(h.Name)

w.index = append(w.index, w.current)
w.oIndex = w.oIndex.Update(w.current)

Expand Down
8 changes: 8 additions & 0 deletions writer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,13 @@ func (s *WriterSuite) TestWriterZeroLength(c *C) {
c.Assert(err, IsNil)
s.writeFixture(c, w, files[1])

err = w.WriteHeader(&Header{
Name: "C:\\some\\path\\file.txt",
Mode: 0600,
ModTime: time.Now(),
})
c.Assert(err, IsNil)

err = w.Close()
c.Assert(err, IsNil)

Expand All @@ -141,4 +148,5 @@ func (s *WriterSuite) TestWriterZeroLength(c *C) {
c.Assert(index[1].Name, Equals, "empty-file")
c.Assert(index[1].Size, Equals, uint64(0))
c.Assert(index[2].Name, Equals, "readme.txt")
c.Assert(index[3].Name, Equals, "some/path/file.txt")
}

0 comments on commit 36b4509

Please sign in to comment.