1
0
Fork 0
mirror of https://github.com/vbatts/go-mtree.git synced 2024-11-14 20:58:41 +00:00
go-mtree/parse.go
Aleksa Sarai 63dc31a80a
parse: do not allow FullType entries to affect the current directory
As per the spec[1], Full entries must not affect the current directory.
Handling this incorrectly caused us issues with certain manifests (ones
with mixed Relative and Full entries, which is something casync does by
accident).

This is a partial fix for the issues with verifying casync-mtree's
output but there are a few other issues to iron out (including one
within casync).

[1]: https://man.netbsd.org/mtree.5

Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>
2023-06-04 20:13:30 +10:00

108 lines
2.4 KiB
Go

package mtree
import (
"bufio"
"io"
"path/filepath"
"strings"
)
// ParseSpec reads a stream of an mtree specification, and returns the DirectoryHierarchy
func ParseSpec(r io.Reader) (*DirectoryHierarchy, error) {
s := bufio.NewScanner(r)
i := int(0)
creator := dhCreator{
DH: &DirectoryHierarchy{},
}
for s.Scan() {
str := s.Text()
trimmedStr := strings.TrimLeftFunc(str, func(c rune) bool {
return c == ' ' || c == '\t'
})
e := Entry{Pos: i}
switch {
case strings.HasPrefix(trimmedStr, "#"):
e.Raw = str
if strings.HasPrefix(trimmedStr, "#mtree") {
e.Type = SignatureType
} else {
e.Type = CommentType
// from here, the comment could be "# key: value" metadata
// or a relative path hint
}
case str == "":
e.Type = BlankType
// nothing else to do here
case strings.HasPrefix(str, "/"):
e.Type = SpecialType
// collapse any escaped newlines
for {
if strings.HasSuffix(str, `\`) {
str = str[:len(str)-1]
s.Scan()
str += s.Text()
} else {
break
}
}
// parse the options
f := strings.Fields(str)
e.Name = f[0]
e.Keywords = StringToKeyVals(f[1:])
if e.Name == "/set" {
creator.curSet = &e
} else if e.Name == "/unset" {
creator.curSet = nil
}
case len(strings.Fields(str)) > 0 && strings.Fields(str)[0] == "..":
e.Type = DotDotType
e.Raw = str
if creator.curDir != nil {
creator.curDir = creator.curDir.Parent
}
// nothing else to do here
case len(strings.Fields(str)) > 0:
// collapse any escaped newlines
for {
if strings.HasSuffix(str, `\`) {
str = str[:len(str)-1]
s.Scan()
str += s.Text()
} else {
break
}
}
// parse the options
f := strings.Fields(str)
e.Name = filepath.Clean(f[0])
e.Keywords = StringToKeyVals(f[1:])
// TODO: gather keywords if using tar stream
var isDir bool
for _, kv := range e.Keywords {
if kv.Keyword() == "type" {
isDir = kv.Value() == "dir"
}
}
if strings.Contains(e.Name, "/") {
e.Type = FullType
} else {
e.Type = RelativeType
e.Parent = creator.curDir
if isDir {
creator.curDir = &e
}
}
if !isDir {
creator.curEnt = &e
}
e.Set = creator.curSet
default:
// TODO(vbatts) log a warning?
continue
}
creator.DH.Entries = append(creator.DH.Entries, e)
i++
}
return creator.DH, s.Err()
}