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