github.com/psexton/git-lfs@v2.1.1-0.20170517224304-289a18b2bc53+incompatible/lfs/pointer.go (about)

     1  package lfs
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"fmt"
     7  	"io"
     8  	"os"
     9  	"regexp"
    10  	"sort"
    11  	"strconv"
    12  	"strings"
    13  
    14  	"github.com/git-lfs/git-lfs/errors"
    15  	"github.com/git-lfs/git-lfs/progress"
    16  	"github.com/git-lfs/git-lfs/tq"
    17  )
    18  
    19  var (
    20  	v1Aliases = []string{
    21  		"http://git-media.io/v/2",            // alpha
    22  		"https://hawser.github.com/spec/v1",  // pre-release
    23  		"https://git-lfs.github.com/spec/v1", // public launch
    24  	}
    25  	latest      = "https://git-lfs.github.com/spec/v1"
    26  	oidType     = "sha256"
    27  	oidRE       = regexp.MustCompile(`\A[[:alnum:]]{64}`)
    28  	matcherRE   = regexp.MustCompile("git-media|hawser|git-lfs")
    29  	extRE       = regexp.MustCompile(`\Aext-\d{1}-\w+`)
    30  	pointerKeys = []string{"version", "oid", "size"}
    31  )
    32  
    33  type Pointer struct {
    34  	Version    string
    35  	Oid        string
    36  	Size       int64
    37  	OidType    string
    38  	Extensions []*PointerExtension
    39  }
    40  
    41  // A PointerExtension is parsed from the Git LFS Pointer file.
    42  type PointerExtension struct {
    43  	Name     string
    44  	Priority int
    45  	Oid      string
    46  	OidType  string
    47  }
    48  
    49  type ByPriority []*PointerExtension
    50  
    51  func (p ByPriority) Len() int           { return len(p) }
    52  func (p ByPriority) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }
    53  func (p ByPriority) Less(i, j int) bool { return p[i].Priority < p[j].Priority }
    54  
    55  func NewPointer(oid string, size int64, exts []*PointerExtension) *Pointer {
    56  	return &Pointer{latest, oid, size, oidType, exts}
    57  }
    58  
    59  func NewPointerExtension(name string, priority int, oid string) *PointerExtension {
    60  	return &PointerExtension{name, priority, oid, oidType}
    61  }
    62  
    63  func (p *Pointer) Smudge(writer io.Writer, workingfile string, download bool, manifest *tq.Manifest, cb progress.CopyCallback) error {
    64  	return PointerSmudge(writer, p, workingfile, download, manifest, cb)
    65  }
    66  
    67  func (p *Pointer) Encode(writer io.Writer) (int, error) {
    68  	return EncodePointer(writer, p)
    69  }
    70  
    71  func (p *Pointer) Encoded() string {
    72  	if p.Size == 0 {
    73  		return ""
    74  	}
    75  
    76  	var buffer bytes.Buffer
    77  	buffer.WriteString(fmt.Sprintf("version %s\n", latest))
    78  	for _, ext := range p.Extensions {
    79  		buffer.WriteString(fmt.Sprintf("ext-%d-%s %s:%s\n", ext.Priority, ext.Name, ext.OidType, ext.Oid))
    80  	}
    81  	buffer.WriteString(fmt.Sprintf("oid %s:%s\n", p.OidType, p.Oid))
    82  	buffer.WriteString(fmt.Sprintf("size %d\n", p.Size))
    83  	return buffer.String()
    84  }
    85  
    86  func EncodePointer(writer io.Writer, pointer *Pointer) (int, error) {
    87  	return writer.Write([]byte(pointer.Encoded()))
    88  }
    89  
    90  func DecodePointerFromFile(file string) (*Pointer, error) {
    91  	// Check size before reading
    92  	stat, err := os.Stat(file)
    93  	if err != nil {
    94  		return nil, err
    95  	}
    96  	if stat.Size() > blobSizeCutoff {
    97  		return nil, errors.NewNotAPointerError(errors.New("file size exceeds lfs pointer size cutoff"))
    98  	}
    99  	f, err := os.OpenFile(file, os.O_RDONLY, 0644)
   100  	if err != nil {
   101  		return nil, err
   102  	}
   103  	defer f.Close()
   104  	return DecodePointer(f)
   105  }
   106  func DecodePointer(reader io.Reader) (*Pointer, error) {
   107  	p, _, err := DecodeFrom(reader)
   108  	return p, err
   109  }
   110  
   111  // DecodeFrom decodes an *lfs.Pointer from the given io.Reader, "reader".
   112  // If the pointer encoded in the reader could successfully be read and decoded,
   113  // it will be returned with a nil error.
   114  //
   115  // If the pointer could not be decoded, an io.Reader containing the entire
   116  // blob's data will be returned, along with a parse error.
   117  func DecodeFrom(reader io.Reader) (*Pointer, io.Reader, error) {
   118  	buf := make([]byte, blobSizeCutoff)
   119  	n, err := reader.Read(buf)
   120  	buf = buf[:n]
   121  
   122  	var contents io.Reader = bytes.NewReader(buf)
   123  	if err != io.EOF {
   124  		contents = io.MultiReader(contents, reader)
   125  	}
   126  
   127  	if err != nil && err != io.EOF {
   128  		return nil, contents, err
   129  	}
   130  
   131  	p, err := decodeKV(bytes.TrimSpace(buf))
   132  	return p, contents, err
   133  }
   134  
   135  func verifyVersion(version string) error {
   136  	if len(version) == 0 {
   137  		return errors.NewNotAPointerError(errors.New("Missing version"))
   138  	}
   139  
   140  	for _, v := range v1Aliases {
   141  		if v == version {
   142  			return nil
   143  		}
   144  	}
   145  
   146  	return errors.New("Invalid version: " + version)
   147  }
   148  
   149  func decodeKV(data []byte) (*Pointer, error) {
   150  	kvps, exts, err := decodeKVData(data)
   151  	if err != nil {
   152  		if errors.IsBadPointerKeyError(err) {
   153  			return nil, errors.StandardizeBadPointerError(err)
   154  		}
   155  		return nil, err
   156  	}
   157  
   158  	if err := verifyVersion(kvps["version"]); err != nil {
   159  		return nil, err
   160  	}
   161  
   162  	value, ok := kvps["oid"]
   163  	if !ok {
   164  		return nil, errors.New("Invalid Oid")
   165  	}
   166  
   167  	oid, err := parseOid(value)
   168  	if err != nil {
   169  		return nil, err
   170  	}
   171  
   172  	value, ok = kvps["size"]
   173  	size, err := strconv.ParseInt(value, 10, 0)
   174  	if err != nil || size < 0 {
   175  		return nil, fmt.Errorf("Invalid size: %q", value)
   176  	}
   177  
   178  	var extensions []*PointerExtension
   179  	if exts != nil {
   180  		for key, value := range exts {
   181  			ext, err := parsePointerExtension(key, value)
   182  			if err != nil {
   183  				return nil, err
   184  			}
   185  			extensions = append(extensions, ext)
   186  		}
   187  		if err = validatePointerExtensions(extensions); err != nil {
   188  			return nil, err
   189  		}
   190  		sort.Sort(ByPriority(extensions))
   191  	}
   192  
   193  	return NewPointer(oid, size, extensions), nil
   194  }
   195  
   196  func parseOid(value string) (string, error) {
   197  	parts := strings.SplitN(value, ":", 2)
   198  	if len(parts) != 2 {
   199  		return "", errors.New("Invalid Oid value: " + value)
   200  	}
   201  	if parts[0] != oidType {
   202  		return "", errors.New("Invalid Oid type: " + parts[0])
   203  	}
   204  	oid := parts[1]
   205  	if !oidRE.Match([]byte(oid)) {
   206  		return "", errors.New("Invalid Oid: " + oid)
   207  	}
   208  	return oid, nil
   209  }
   210  
   211  func parsePointerExtension(key string, value string) (*PointerExtension, error) {
   212  	keyParts := strings.SplitN(key, "-", 3)
   213  	if len(keyParts) != 3 || keyParts[0] != "ext" {
   214  		return nil, errors.New("Invalid extension value: " + value)
   215  	}
   216  
   217  	p, err := strconv.Atoi(keyParts[1])
   218  	if err != nil || p < 0 {
   219  		return nil, errors.New("Invalid priority: " + keyParts[1])
   220  	}
   221  
   222  	name := keyParts[2]
   223  
   224  	oid, err := parseOid(value)
   225  	if err != nil {
   226  		return nil, err
   227  	}
   228  
   229  	return NewPointerExtension(name, p, oid), nil
   230  }
   231  
   232  func validatePointerExtensions(exts []*PointerExtension) error {
   233  	m := make(map[int]struct{})
   234  	for _, ext := range exts {
   235  		if _, exist := m[ext.Priority]; exist {
   236  			return fmt.Errorf("Duplicate priority found: %d", ext.Priority)
   237  		}
   238  		m[ext.Priority] = struct{}{}
   239  	}
   240  	return nil
   241  }
   242  
   243  func decodeKVData(data []byte) (kvps map[string]string, exts map[string]string, err error) {
   244  	kvps = make(map[string]string)
   245  
   246  	if !matcherRE.Match(data) {
   247  		err = errors.NewNotAPointerError(errors.New("invalid header"))
   248  		return
   249  	}
   250  
   251  	scanner := bufio.NewScanner(bytes.NewBuffer(data))
   252  	line := 0
   253  	numKeys := len(pointerKeys)
   254  	for scanner.Scan() {
   255  		text := scanner.Text()
   256  		if len(text) == 0 {
   257  			continue
   258  		}
   259  
   260  		parts := strings.SplitN(text, " ", 2)
   261  		if len(parts) < 2 {
   262  			err = fmt.Errorf("Error reading line %d: %s", line, text)
   263  			return
   264  		}
   265  
   266  		key := parts[0]
   267  		value := parts[1]
   268  
   269  		if numKeys <= line {
   270  			err = fmt.Errorf("Extra line: %s", text)
   271  			return
   272  		}
   273  
   274  		if expected := pointerKeys[line]; key != expected {
   275  			if !extRE.Match([]byte(key)) {
   276  				err = errors.NewBadPointerKeyError(expected, key)
   277  				return
   278  			}
   279  			if exts == nil {
   280  				exts = make(map[string]string)
   281  			}
   282  			exts[key] = value
   283  			continue
   284  		}
   285  
   286  		line += 1
   287  		kvps[key] = value
   288  	}
   289  
   290  	err = scanner.Err()
   291  	return
   292  }