|
9 | 9 | "encoding/hex"
|
10 | 10 | "errors"
|
11 | 11 | "fmt"
|
| 12 | + "hash" |
12 | 13 | "io"
|
13 | 14 | "os"
|
14 | 15 |
|
@@ -66,30 +67,27 @@ func (s *ContentStore) Get(meta *models.LFSMetaObject, fromByte int64) (io.ReadC
|
66 | 67 |
|
67 | 68 | // Put takes a Meta object and an io.Reader and writes the content to the store.
|
68 | 69 | func (s *ContentStore) Put(meta *models.LFSMetaObject, r io.Reader) error {
|
69 |
| - hash := sha256.New() |
70 |
| - rd := io.TeeReader(r, hash) |
71 | 70 | p := meta.RelativePath()
|
72 |
| - written, err := s.Save(p, rd) |
| 71 | + |
| 72 | + // Wrap the provided reader with an inline hashing and size checker |
| 73 | + wrappedRd := newHashingReader(meta.Size, meta.Oid, r) |
| 74 | + |
| 75 | + // now pass the wrapped reader to Save - if there is a size mismatch or hash mismatch then |
| 76 | + // the errors returned by the newHashingReader should percolate up to here |
| 77 | + written, err := s.Save(p, wrappedRd) |
73 | 78 | if err != nil {
|
74 | 79 | log.Error("Whilst putting LFS OID[%s]: Failed to copy to tmpPath: %s Error: %v", meta.Oid, p, err)
|
75 | 80 | return err
|
76 | 81 | }
|
77 | 82 |
|
| 83 | + // This shouldn't happen but it is sensible to test |
78 | 84 | if written != meta.Size {
|
79 | 85 | if err := s.Delete(p); err != nil {
|
80 | 86 | log.Error("Cleaning the LFS OID[%s] failed: %v", meta.Oid, err)
|
81 | 87 | }
|
82 | 88 | return errSizeMismatch
|
83 | 89 | }
|
84 | 90 |
|
85 |
| - shaStr := hex.EncodeToString(hash.Sum(nil)) |
86 |
| - if shaStr != meta.Oid { |
87 |
| - if err := s.Delete(p); err != nil { |
88 |
| - log.Error("Cleaning the LFS OID[%s] failed: %v", meta.Oid, err) |
89 |
| - } |
90 |
| - return errHashMismatch |
91 |
| - } |
92 |
| - |
93 | 91 | return nil
|
94 | 92 | }
|
95 | 93 |
|
@@ -118,3 +116,45 @@ func (s *ContentStore) Verify(meta *models.LFSMetaObject) (bool, error) {
|
118 | 116 |
|
119 | 117 | return true, nil
|
120 | 118 | }
|
| 119 | + |
| 120 | +type hashingReader struct { |
| 121 | + internal io.Reader |
| 122 | + currentSize int64 |
| 123 | + expectedSize int64 |
| 124 | + hash hash.Hash |
| 125 | + expectedHash string |
| 126 | +} |
| 127 | + |
| 128 | +func (r *hashingReader) Read(b []byte) (int, error) { |
| 129 | + n, err := r.internal.Read(b) |
| 130 | + |
| 131 | + if n > 0 { |
| 132 | + r.currentSize += int64(n) |
| 133 | + wn, werr := r.hash.Write(b[:n]) |
| 134 | + if wn != n || werr != nil { |
| 135 | + return n, werr |
| 136 | + } |
| 137 | + } |
| 138 | + |
| 139 | + if err != nil && err == io.EOF { |
| 140 | + if r.currentSize != r.expectedSize { |
| 141 | + return n, errSizeMismatch |
| 142 | + } |
| 143 | + |
| 144 | + shaStr := hex.EncodeToString(r.hash.Sum(nil)) |
| 145 | + if shaStr != r.expectedHash { |
| 146 | + return n, errHashMismatch |
| 147 | + } |
| 148 | + } |
| 149 | + |
| 150 | + return n, err |
| 151 | +} |
| 152 | + |
| 153 | +func newHashingReader(expectedSize int64, expectedHash string, reader io.Reader) *hashingReader { |
| 154 | + return &hashingReader{ |
| 155 | + internal: reader, |
| 156 | + expectedSize: expectedSize, |
| 157 | + expectedHash: expectedHash, |
| 158 | + hash: sha256.New(), |
| 159 | + } |
| 160 | +} |
0 commit comments