You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

pointer.go 3.3KB

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