2020-04-05 01:20:50 -05:00
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
2016-12-26 02:16:37 +01:00
package lfs
import (
"crypto/sha256"
"encoding/hex"
"errors"
2020-11-01 04:51:48 +08:00
"fmt"
2021-03-06 20:21:56 +00:00
"hash"
2016-12-26 02:16:37 +01:00
"io"
"os"
2017-11-08 15:04:19 +02:00
2020-03-09 19:56:18 +00:00
"code.gitea.io/gitea/modules/log"
2020-09-08 23:45:10 +08:00
"code.gitea.io/gitea/modules/storage"
2016-12-26 02:16:37 +01:00
)
var (
2021-04-09 00:25:57 +02:00
// ErrHashMismatch occurs if the content has does not match OID
ErrHashMismatch = errors . New ( "Content hash does not match OID" )
// ErrSizeMismatch occurs if the content size does not match
ErrSizeMismatch = errors . New ( "Content size does not match" )
2016-12-26 02:16:37 +01:00
)
2020-11-01 04:51:48 +08:00
// ErrRangeNotSatisfiable represents an error which request range is not satisfiable.
type ErrRangeNotSatisfiable struct {
FromByte int64
}
// IsErrRangeNotSatisfiable returns true if the error is an ErrRangeNotSatisfiable
func IsErrRangeNotSatisfiable ( err error ) bool {
_ , ok := err . ( ErrRangeNotSatisfiable )
return ok
}
2021-04-09 00:25:57 +02:00
func ( err ErrRangeNotSatisfiable ) Error ( ) string {
return fmt . Sprintf ( "Requested range %d is not satisfiable" , err . FromByte )
}
2016-12-26 02:16:37 +01:00
// ContentStore provides a simple file system based storage.
type ContentStore struct {
2020-09-08 23:45:10 +08:00
storage . ObjectStorage
2016-12-26 02:16:37 +01:00
}
2021-04-09 00:25:57 +02:00
// NewContentStore creates the default ContentStore
func NewContentStore ( ) * ContentStore {
contentStore := & ContentStore { ObjectStorage : storage . LFS }
return contentStore
}
2017-03-14 20:52:01 -04:00
// Get takes a Meta object and retrieves the content from the store, returning
2021-04-06 15:22:34 +02:00
// it as an io.ReadSeekCloser.
2021-04-09 00:25:57 +02:00
func ( s * ContentStore ) Get ( pointer Pointer ) ( storage . Object , error ) {
f , err := s . Open ( pointer . RelativePath ( ) )
2016-12-26 02:16:37 +01:00
if err != nil {
2021-04-09 00:25:57 +02:00
log . Error ( "Whilst trying to read LFS OID[%s]: Unable to open Error: %v" , pointer . Oid , err )
2016-12-26 02:16:37 +01:00
return nil , err
}
return f , err
}
// Put takes a Meta object and an io.Reader and writes the content to the store.
2021-04-09 00:25:57 +02:00
func ( s * ContentStore ) Put ( pointer Pointer , r io . Reader ) error {
p := pointer . RelativePath ( )
2021-03-06 20:21:56 +00:00
// Wrap the provided reader with an inline hashing and size checker
2021-04-09 00:25:57 +02:00
wrappedRd := newHashingReader ( pointer . Size , pointer . Oid , r )
2021-03-06 20:21:56 +00:00
// now pass the wrapped reader to Save - if there is a size mismatch or hash mismatch then
// the errors returned by the newHashingReader should percolate up to here
2021-04-09 00:25:57 +02:00
written , err := s . Save ( p , wrappedRd , pointer . Size )
2016-12-26 02:16:37 +01:00
if err != nil {
2021-04-09 00:25:57 +02:00
log . Error ( "Whilst putting LFS OID[%s]: Failed to copy to tmpPath: %s Error: %v" , pointer . Oid , p , err )
2016-12-26 02:16:37 +01:00
return err
}
2021-03-06 20:21:56 +00:00
// This shouldn't happen but it is sensible to test
2021-04-09 00:25:57 +02:00
if written != pointer . Size {
2020-09-08 23:45:10 +08:00
if err := s . Delete ( p ) ; err != nil {
2021-04-09 00:25:57 +02:00
log . Error ( "Cleaning the LFS OID[%s] failed: %v" , pointer . Oid , err )
2020-09-08 23:45:10 +08:00
}
2021-04-09 00:25:57 +02:00
return ErrSizeMismatch
2016-12-26 02:16:37 +01:00
}
2020-03-09 19:56:18 +00:00
return nil
2016-12-26 02:16:37 +01:00
}
// Exists returns true if the object exists in the content store.
2021-04-09 00:25:57 +02:00
func ( s * ContentStore ) Exists ( pointer Pointer ) ( bool , error ) {
_ , err := s . ObjectStorage . Stat ( pointer . RelativePath ( ) )
2020-09-08 23:45:10 +08:00
if err != nil {
if os . IsNotExist ( err ) {
return false , nil
}
return false , err
2016-12-26 02:16:37 +01:00
}
2020-09-08 23:45:10 +08:00
return true , nil
2016-12-26 02:16:37 +01:00
}
2017-11-08 15:04:19 +02:00
// Verify returns true if the object exists in the content store and size is correct.
2021-04-09 00:25:57 +02:00
func ( s * ContentStore ) Verify ( pointer Pointer ) ( bool , error ) {
p := pointer . RelativePath ( )
2020-09-08 23:45:10 +08:00
fi , err := s . ObjectStorage . Stat ( p )
2021-04-09 00:25:57 +02:00
if os . IsNotExist ( err ) || ( err == nil && fi . Size ( ) != pointer . Size ) {
2017-11-08 15:04:19 +02:00
return false , nil
} else if err != nil {
2021-04-09 00:25:57 +02:00
log . Error ( "Unable stat file: %s for LFS OID[%s] Error: %v" , p , pointer . Oid , err )
2017-11-08 15:04:19 +02:00
return false , err
}
return true , nil
}
2021-03-06 20:21:56 +00:00
2021-04-09 00:25:57 +02:00
// ReadMetaObject will read a models.LFSMetaObject and return a reader
func ReadMetaObject ( pointer Pointer ) ( io . ReadCloser , error ) {
contentStore := NewContentStore ( )
return contentStore . Get ( pointer )
}
2021-03-06 20:21:56 +00:00
type hashingReader struct {
internal io . Reader
currentSize int64
expectedSize int64
hash hash . Hash
expectedHash string
}
func ( r * hashingReader ) Read ( b [ ] byte ) ( int , error ) {
n , err := r . internal . Read ( b )
if n > 0 {
r . currentSize += int64 ( n )
wn , werr := r . hash . Write ( b [ : n ] )
if wn != n || werr != nil {
return n , werr
}
}
if err != nil && err == io . EOF {
if r . currentSize != r . expectedSize {
2021-04-09 00:25:57 +02:00
return n , ErrSizeMismatch
2021-03-06 20:21:56 +00:00
}
shaStr := hex . EncodeToString ( r . hash . Sum ( nil ) )
if shaStr != r . expectedHash {
2021-04-09 00:25:57 +02:00
return n , ErrHashMismatch
2021-03-06 20:21:56 +00:00
}
}
return n , err
}
func newHashingReader ( expectedSize int64 , expectedHash string , reader io . Reader ) * hashingReader {
return & hashingReader {
internal : reader ,
expectedSize : expectedSize ,
expectedHash : expectedHash ,
hash : sha256 . New ( ) ,
}
}