git-lfs/lfs/pointer.go

287 lines
6.6 KiB
Go
Raw Normal View History

package lfs
2013-09-27 17:08:19 +00:00
import (
2014-07-24 21:23:02 +00:00
"bufio"
2013-09-27 17:08:19 +00:00
"bytes"
2014-07-24 22:02:27 +00:00
"fmt"
2013-09-27 17:08:19 +00:00
"io"
"os"
"regexp"
"sort"
2014-07-24 21:23:02 +00:00
"strconv"
"strings"
2016-11-15 17:01:18 +00:00
"github.com/git-lfs/git-lfs/errors"
2013-09-27 17:08:19 +00:00
)
var (
v1Aliases = []string{
2015-04-28 19:29:45 +00:00
"http://git-media.io/v/2", // alpha
"https://hawser.github.com/spec/v1", // pre-release
"https://git-lfs.github.com/spec/v1", // public launch
}
latest = "https://git-lfs.github.com/spec/v1"
oidType = "sha256"
oidRE = regexp.MustCompile(`\A[[:alnum:]]{64}`)
2015-03-19 19:30:55 +00:00
matcherRE = regexp.MustCompile("git-media|hawser|git-lfs")
extRE = regexp.MustCompile(`\Aext-\d{1}-\w+`)
pointerKeys = []string{"version", "oid", "size"}
)
type Pointer struct {
Version string
Oid string
Size int64
OidType string
2015-07-24 04:53:36 +00:00
Extensions []*PointerExtension
}
2015-07-24 04:53:36 +00:00
// A PointerExtension is parsed from the Git LFS Pointer file.
type PointerExtension struct {
Name string
Priority int
Oid string
OidType string
}
2013-09-27 17:08:53 +00:00
2015-07-24 04:53:36 +00:00
type ByPriority []*PointerExtension
func (p ByPriority) Len() int { return len(p) }
func (p ByPriority) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
func (p ByPriority) Less(i, j int) bool { return p[i].Priority < p[j].Priority }
2015-07-24 04:53:36 +00:00
func NewPointer(oid string, size int64, exts []*PointerExtension) *Pointer {
return &Pointer{latest, oid, size, oidType, exts}
}
func NewPointerExtension(name string, priority int, oid string) *PointerExtension {
return &PointerExtension{name, priority, oid, oidType}
}
func (p *Pointer) Encode(writer io.Writer) (int, error) {
return EncodePointer(writer, p)
}
2014-09-18 18:38:10 +00:00
func (p *Pointer) Encoded() string {
if p.Size == 0 {
2016-02-02 17:14:58 +00:00
return ""
}
2016-02-02 17:14:58 +00:00
var buffer bytes.Buffer
buffer.WriteString(fmt.Sprintf("version %s\n", latest))
for _, ext := range p.Extensions {
buffer.WriteString(fmt.Sprintf("ext-%d-%s %s:%s\n", ext.Priority, ext.Name, ext.OidType, ext.Oid))
}
buffer.WriteString(fmt.Sprintf("oid %s:%s\n", p.OidType, p.Oid))
buffer.WriteString(fmt.Sprintf("size %d\n", p.Size))
return buffer.String()
2014-09-18 18:38:10 +00:00
}
func EncodePointer(writer io.Writer, pointer *Pointer) (int, error) {
2014-09-18 18:38:10 +00:00
return writer.Write([]byte(pointer.Encoded()))
2013-09-27 17:08:19 +00:00
}
func DecodePointerFromFile(file string) (*Pointer, error) {
// Check size before reading
stat, err := os.Stat(file)
if err != nil {
return nil, err
}
if stat.Size() > blobSizeCutoff {
2016-08-18 20:20:33 +00:00
return nil, errors.NewNotAPointerError(errors.New("file size exceeds lfs pointer size cutoff"))
}
f, err := os.OpenFile(file, os.O_RDONLY, 0644)
if err != nil {
return nil, err
}
defer f.Close()
return DecodePointer(f)
}
func DecodePointer(reader io.Reader) (*Pointer, error) {
p, _, err := DecodeFrom(reader)
return p, err
}
// DecodeFrom decodes an *lfs.Pointer from the given io.Reader, "reader".
// If the pointer encoded in the reader could successfully be read and decoded,
// it will be returned with a nil error.
//
// If the pointer could not be decoded, an io.Reader containing the entire
// blob's data will be returned, along with a parse error.
func DecodeFrom(reader io.Reader) (*Pointer, io.Reader, error) {
buf := make([]byte, blobSizeCutoff)
2016-12-23 19:21:28 +00:00
n, err := reader.Read(buf)
buf = buf[:n]
2016-12-23 19:21:28 +00:00
var contents io.Reader = bytes.NewReader(buf)
if err != io.EOF {
contents = io.MultiReader(contents, reader)
}
2016-12-23 19:21:28 +00:00
if err != nil && err != io.EOF {
return nil, contents, err
2013-10-04 13:48:02 +00:00
}
2013-09-27 17:08:19 +00:00
2016-12-23 19:21:28 +00:00
p, err := decodeKV(bytes.TrimSpace(buf))
return p, contents, err
}
2013-09-27 17:08:19 +00:00
func verifyVersion(version string) error {
if len(version) == 0 {
2016-08-18 20:20:33 +00:00
return errors.NewNotAPointerError(errors.New("Missing version"))
}
for _, v := range v1Aliases {
if v == version {
return nil
}
}
return errors.New("Invalid version: " + version)
2014-07-24 20:49:47 +00:00
}
2014-08-14 17:15:22 +00:00
func decodeKV(data []byte) (*Pointer, error) {
kvps, exts, err := decodeKVData(data)
2014-07-24 21:23:02 +00:00
if err != nil {
2016-08-18 20:20:33 +00:00
if errors.IsBadPointerKeyError(err) {
return nil, errors.StandardizeBadPointerError(err)
}
2014-07-24 21:23:02 +00:00
return nil, err
}
if err := verifyVersion(kvps["version"]); err != nil {
return nil, err
2014-07-24 21:23:02 +00:00
}
value, ok := kvps["oid"]
2014-07-24 21:23:02 +00:00
if !ok {
return nil, errors.New("Invalid Oid")
}
oid, err := parseOid(value)
if err != nil {
return nil, err
2014-07-24 21:23:02 +00:00
}
value, ok = kvps["size"]
size, err := strconv.ParseInt(value, 10, 64)
if err != nil || size < 0 {
2015-07-24 04:53:36 +00:00
return nil, fmt.Errorf("Invalid size: %q", value)
}
var extensions []*PointerExtension
if exts != nil {
for key, value := range exts {
ext, err := parsePointerExtension(key, value)
if err != nil {
return nil, err
}
2015-07-24 04:53:36 +00:00
extensions = append(extensions, ext)
}
if err = validatePointerExtensions(extensions); err != nil {
return nil, err
}
sort.Sort(ByPriority(extensions))
}
return NewPointer(oid, size, extensions), nil
}
func parseOid(value string) (string, error) {
parts := strings.SplitN(value, ":", 2)
if len(parts) != 2 {
return "", errors.New("Invalid Oid value: " + value)
}
if parts[0] != oidType {
return "", errors.New("Invalid Oid type: " + parts[0])
}
oid := parts[1]
if !oidRE.Match([]byte(oid)) {
return "", errors.New("Invalid Oid: " + oid)
}
return oid, nil
}
func parsePointerExtension(key string, value string) (*PointerExtension, error) {
keyParts := strings.SplitN(key, "-", 3)
if len(keyParts) != 3 || keyParts[0] != "ext" {
return nil, errors.New("Invalid extension value: " + value)
2015-05-12 08:45:06 +00:00
}
p, err := strconv.Atoi(keyParts[1])
if err != nil || p < 0 {
return nil, errors.New("Invalid priority: " + keyParts[1])
}
name := keyParts[2]
oid, err := parseOid(value)
2015-05-12 08:45:06 +00:00
if err != nil {
return nil, err
2014-07-24 21:23:02 +00:00
}
return NewPointerExtension(name, p, oid), nil
2014-07-24 21:23:02 +00:00
}
2015-07-24 04:53:36 +00:00
func validatePointerExtensions(exts []*PointerExtension) error {
m := make(map[int]struct{})
for _, ext := range exts {
if _, exist := m[ext.Priority]; exist {
return fmt.Errorf("Duplicate priority found: %d", ext.Priority)
}
m[ext.Priority] = struct{}{}
}
return nil
}
func decodeKVData(data []byte) (kvps map[string]string, exts map[string]string, err error) {
kvps = make(map[string]string)
2014-08-14 20:23:19 +00:00
2015-01-30 16:56:12 +00:00
if !matcherRE.Match(data) {
2016-08-18 20:20:33 +00:00
err = errors.NewNotAPointerError(errors.New("invalid header"))
return
2014-08-14 20:23:19 +00:00
}
2014-08-14 17:15:22 +00:00
scanner := bufio.NewScanner(bytes.NewBuffer(data))
2014-08-14 20:23:19 +00:00
line := 0
2014-08-14 20:30:33 +00:00
numKeys := len(pointerKeys)
2014-08-14 17:15:22 +00:00
for scanner.Scan() {
2014-08-14 20:30:33 +00:00
text := scanner.Text()
if len(text) == 0 {
continue
}
parts := strings.SplitN(text, " ", 2)
if len(parts) < 2 {
err = fmt.Errorf("Error reading line %d: %s", line, text)
return
}
2014-08-14 20:30:33 +00:00
key := parts[0]
value := parts[1]
2014-08-14 20:30:33 +00:00
if numKeys <= line {
err = fmt.Errorf("Extra line: %s", text)
return
2014-08-14 20:30:33 +00:00
}
if expected := pointerKeys[line]; key != expected {
if !extRE.Match([]byte(key)) {
2016-08-18 20:20:33 +00:00
err = errors.NewBadPointerKeyError(expected, key)
return
}
if exts == nil {
exts = make(map[string]string)
}
exts[key] = value
continue
2014-08-14 20:30:33 +00:00
}
2014-08-14 20:23:19 +00:00
line += 1
kvps[key] = value
2014-08-14 17:15:22 +00:00
}
err = scanner.Err()
return
2014-08-14 17:15:22 +00:00
}