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.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. // Copyright 2020 The Gitea Authors. All rights reserved.
  2. // Use of this source code is governed by a MIT-style
  3. // license that can be found in the LICENSE file.
  4. package lfs
  5. import (
  6. "crypto/sha256"
  7. "encoding/hex"
  8. "errors"
  9. "fmt"
  10. "hash"
  11. "io"
  12. "os"
  13. "code.gitea.io/gitea/modules/log"
  14. "code.gitea.io/gitea/modules/storage"
  15. )
  16. var (
  17. // ErrHashMismatch occurs if the content has does not match OID
  18. ErrHashMismatch = errors.New("Content hash does not match OID")
  19. // ErrSizeMismatch occurs if the content size does not match
  20. ErrSizeMismatch = errors.New("Content size does not match")
  21. )
  22. // ErrRangeNotSatisfiable represents an error which request range is not satisfiable.
  23. type ErrRangeNotSatisfiable struct {
  24. FromByte int64
  25. }
  26. // IsErrRangeNotSatisfiable returns true if the error is an ErrRangeNotSatisfiable
  27. func IsErrRangeNotSatisfiable(err error) bool {
  28. _, ok := err.(ErrRangeNotSatisfiable)
  29. return ok
  30. }
  31. func (err ErrRangeNotSatisfiable) Error() string {
  32. return fmt.Sprintf("Requested range %d is not satisfiable", err.FromByte)
  33. }
  34. // ContentStore provides a simple file system based storage.
  35. type ContentStore struct {
  36. storage.ObjectStorage
  37. }
  38. // NewContentStore creates the default ContentStore
  39. func NewContentStore() *ContentStore {
  40. contentStore := &ContentStore{ObjectStorage: storage.LFS}
  41. return contentStore
  42. }
  43. // Get takes a Meta object and retrieves the content from the store, returning
  44. // it as an io.ReadSeekCloser.
  45. func (s *ContentStore) Get(pointer Pointer) (storage.Object, error) {
  46. f, err := s.Open(pointer.RelativePath())
  47. if err != nil {
  48. log.Error("Whilst trying to read LFS OID[%s]: Unable to open Error: %v", pointer.Oid, err)
  49. return nil, err
  50. }
  51. return f, err
  52. }
  53. // Put takes a Meta object and an io.Reader and writes the content to the store.
  54. func (s *ContentStore) Put(pointer Pointer, r io.Reader) error {
  55. p := pointer.RelativePath()
  56. // Wrap the provided reader with an inline hashing and size checker
  57. wrappedRd := newHashingReader(pointer.Size, pointer.Oid, r)
  58. // now pass the wrapped reader to Save - if there is a size mismatch or hash mismatch then
  59. // the errors returned by the newHashingReader should percolate up to here
  60. written, err := s.Save(p, wrappedRd, pointer.Size)
  61. if err != nil {
  62. log.Error("Whilst putting LFS OID[%s]: Failed to copy to tmpPath: %s Error: %v", pointer.Oid, p, err)
  63. return err
  64. }
  65. // This shouldn't happen but it is sensible to test
  66. if written != pointer.Size {
  67. if err := s.Delete(p); err != nil {
  68. log.Error("Cleaning the LFS OID[%s] failed: %v", pointer.Oid, err)
  69. }
  70. return ErrSizeMismatch
  71. }
  72. return nil
  73. }
  74. // Exists returns true if the object exists in the content store.
  75. func (s *ContentStore) Exists(pointer Pointer) (bool, error) {
  76. _, err := s.ObjectStorage.Stat(pointer.RelativePath())
  77. if err != nil {
  78. if os.IsNotExist(err) {
  79. return false, nil
  80. }
  81. return false, err
  82. }
  83. return true, nil
  84. }
  85. // Verify returns true if the object exists in the content store and size is correct.
  86. func (s *ContentStore) Verify(pointer Pointer) (bool, error) {
  87. p := pointer.RelativePath()
  88. fi, err := s.ObjectStorage.Stat(p)
  89. if os.IsNotExist(err) || (err == nil && fi.Size() != pointer.Size) {
  90. return false, nil
  91. } else if err != nil {
  92. log.Error("Unable stat file: %s for LFS OID[%s] Error: %v", p, pointer.Oid, err)
  93. return false, err
  94. }
  95. return true, nil
  96. }
  97. // ReadMetaObject will read a models.LFSMetaObject and return a reader
  98. func ReadMetaObject(pointer Pointer) (io.ReadCloser, error) {
  99. contentStore := NewContentStore()
  100. return contentStore.Get(pointer)
  101. }
  102. type hashingReader struct {
  103. internal io.Reader
  104. currentSize int64
  105. expectedSize int64
  106. hash hash.Hash
  107. expectedHash string
  108. }
  109. func (r *hashingReader) Read(b []byte) (int, error) {
  110. n, err := r.internal.Read(b)
  111. if n > 0 {
  112. r.currentSize += int64(n)
  113. wn, werr := r.hash.Write(b[:n])
  114. if wn != n || werr != nil {
  115. return n, werr
  116. }
  117. }
  118. if err != nil && err == io.EOF {
  119. if r.currentSize != r.expectedSize {
  120. return n, ErrSizeMismatch
  121. }
  122. shaStr := hex.EncodeToString(r.hash.Sum(nil))
  123. if shaStr != r.expectedHash {
  124. return n, ErrHashMismatch
  125. }
  126. }
  127. return n, err
  128. }
  129. func newHashingReader(expectedSize int64, expectedHash string, reader io.Reader) *hashingReader {
  130. return &hashingReader{
  131. internal: reader,
  132. expectedSize: expectedSize,
  133. expectedHash: expectedHash,
  134. hash: sha256.New(),
  135. }
  136. }