code.gitea.io/gitea@v1.22.3/modules/lfs/pointer.go (about)

     1  // Copyright 2021 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package lfs
     5  
     6  import (
     7  	"crypto/sha256"
     8  	"encoding/hex"
     9  	"errors"
    10  	"fmt"
    11  	"io"
    12  	"path"
    13  	"regexp"
    14  	"strconv"
    15  	"strings"
    16  )
    17  
    18  const (
    19  	blobSizeCutoff = 1024
    20  
    21  	// MetaFileIdentifier is the string appearing at the first line of LFS pointer files.
    22  	// https://github.com/git-lfs/git-lfs/blob/master/docs/spec.md
    23  	MetaFileIdentifier = "version https://git-lfs.github.com/spec/v1"
    24  
    25  	// MetaFileOidPrefix appears in LFS pointer files on a line before the sha256 hash.
    26  	MetaFileOidPrefix = "oid sha256:"
    27  )
    28  
    29  var (
    30  	// ErrMissingPrefix occurs if the content lacks the LFS prefix
    31  	ErrMissingPrefix = errors.New("content lacks the LFS prefix")
    32  
    33  	// ErrInvalidStructure occurs if the content has an invalid structure
    34  	ErrInvalidStructure = errors.New("content has an invalid structure")
    35  
    36  	// ErrInvalidOIDFormat occurs if the oid has an invalid format
    37  	ErrInvalidOIDFormat = errors.New("OID has an invalid format")
    38  )
    39  
    40  // ReadPointer tries to read LFS pointer data from the reader
    41  func ReadPointer(reader io.Reader) (Pointer, error) {
    42  	buf := make([]byte, blobSizeCutoff)
    43  	n, err := io.ReadFull(reader, buf)
    44  	if err != nil && err != io.ErrUnexpectedEOF {
    45  		return Pointer{}, err
    46  	}
    47  	buf = buf[:n]
    48  
    49  	return ReadPointerFromBuffer(buf)
    50  }
    51  
    52  var oidPattern = regexp.MustCompile(`^[a-f\d]{64}$`)
    53  
    54  // ReadPointerFromBuffer will return a pointer if the provided byte slice is a pointer file or an error otherwise.
    55  func ReadPointerFromBuffer(buf []byte) (Pointer, error) {
    56  	var p Pointer
    57  
    58  	headString := string(buf)
    59  	if !strings.HasPrefix(headString, MetaFileIdentifier) {
    60  		return p, ErrMissingPrefix
    61  	}
    62  
    63  	splitLines := strings.Split(headString, "\n")
    64  	if len(splitLines) < 3 {
    65  		return p, ErrInvalidStructure
    66  	}
    67  
    68  	oid := strings.TrimPrefix(splitLines[1], MetaFileOidPrefix)
    69  	if len(oid) != 64 || !oidPattern.MatchString(oid) {
    70  		return p, ErrInvalidOIDFormat
    71  	}
    72  	size, err := strconv.ParseInt(strings.TrimPrefix(splitLines[2], "size "), 10, 64)
    73  	if err != nil {
    74  		return p, err
    75  	}
    76  
    77  	p.Oid = oid
    78  	p.Size = size
    79  
    80  	return p, nil
    81  }
    82  
    83  // IsValid checks if the pointer has a valid structure.
    84  // It doesn't check if the pointed-to-content exists.
    85  func (p Pointer) IsValid() bool {
    86  	if len(p.Oid) != 64 {
    87  		return false
    88  	}
    89  	if !oidPattern.MatchString(p.Oid) {
    90  		return false
    91  	}
    92  	if p.Size < 0 {
    93  		return false
    94  	}
    95  	return true
    96  }
    97  
    98  // StringContent returns the string representation of the pointer
    99  // https://github.com/git-lfs/git-lfs/blob/main/docs/spec.md#the-pointer
   100  func (p Pointer) StringContent() string {
   101  	return fmt.Sprintf("%s\n%s%s\nsize %d\n", MetaFileIdentifier, MetaFileOidPrefix, p.Oid, p.Size)
   102  }
   103  
   104  // RelativePath returns the relative storage path of the pointer
   105  func (p Pointer) RelativePath() string {
   106  	if len(p.Oid) < 5 {
   107  		return p.Oid
   108  	}
   109  
   110  	return path.Join(p.Oid[0:2], p.Oid[2:4], p.Oid[4:])
   111  }
   112  
   113  func (p Pointer) LogString() string {
   114  	if p.Oid == "" && p.Size == 0 {
   115  		return "<LFSPointer empty>"
   116  	}
   117  	return fmt.Sprintf("<LFSPointer %s:%d>", p.Oid, p.Size)
   118  }
   119  
   120  // GeneratePointer generates a pointer for arbitrary content
   121  func GeneratePointer(content io.Reader) (Pointer, error) {
   122  	h := sha256.New()
   123  	c, err := io.Copy(h, content)
   124  	if err != nil {
   125  		return Pointer{}, err
   126  	}
   127  	sum := h.Sum(nil)
   128  	return Pointer{Oid: hex.EncodeToString(sum), Size: c}, nil
   129  }