mirror of
				https://gitee.com/gitea/gitea
				synced 2025-11-04 08:30:25 +08:00 
			
		
		
		
	Add LFS object verification step after upload (#2868)
* Add LFS object verification step after upload * Fix file verification condition and small refactor * Fix URLs * Remove newline and return status 422 on failed verification * Better error hadling
This commit is contained in:
		@@ -1,13 +1,14 @@
 | 
			
		||||
package lfs
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"code.gitea.io/gitea/models"
 | 
			
		||||
	"crypto/sha256"
 | 
			
		||||
	"encoding/hex"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"io"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
@@ -82,6 +83,20 @@ func (s *ContentStore) Exists(meta *models.LFSMetaObject) bool {
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Verify returns true if the object exists in the content store and size is correct.
 | 
			
		||||
func (s *ContentStore) Verify(meta *models.LFSMetaObject) (bool, error) {
 | 
			
		||||
	path := filepath.Join(s.BasePath, transformKey(meta.Oid))
 | 
			
		||||
 | 
			
		||||
	fi, err := os.Stat(path)
 | 
			
		||||
	if os.IsNotExist(err) || err == nil && fi.Size() != meta.Size {
 | 
			
		||||
		return false, nil
 | 
			
		||||
	} else if err != nil {
 | 
			
		||||
		return false, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return true, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func transformKey(key string) string {
 | 
			
		||||
	if len(key) < 5 {
 | 
			
		||||
		return key
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,7 @@ import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"path"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
@@ -15,6 +16,7 @@ import (
 | 
			
		||||
	"code.gitea.io/gitea/modules/context"
 | 
			
		||||
	"code.gitea.io/gitea/modules/log"
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
 | 
			
		||||
	"github.com/dgrijalva/jwt-go"
 | 
			
		||||
	"gopkg.in/macaron.v1"
 | 
			
		||||
)
 | 
			
		||||
@@ -66,7 +68,12 @@ type ObjectError struct {
 | 
			
		||||
 | 
			
		||||
// ObjectLink builds a URL linking to the object.
 | 
			
		||||
func (v *RequestVars) ObjectLink() string {
 | 
			
		||||
	return fmt.Sprintf("%s%s/%s/info/lfs/objects/%s", setting.AppURL, v.User, v.Repo, v.Oid)
 | 
			
		||||
	return setting.AppURL + path.Join(v.User, v.Repo, "info/lfs/objects", v.Oid)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// VerifyLink builds a URL for verifying the object.
 | 
			
		||||
func (v *RequestVars) VerifyLink() string {
 | 
			
		||||
	return setting.AppURL + path.Join(v.User, v.Repo, "info/lfs/verify")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// link provides a structure used to build a hypermedia representation of an HTTP link.
 | 
			
		||||
@@ -320,6 +327,40 @@ func PutHandler(ctx *context.Context) {
 | 
			
		||||
	logRequest(ctx.Req, 200)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// VerifyHandler verify oid and its size from the content store
 | 
			
		||||
func VerifyHandler(ctx *context.Context) {
 | 
			
		||||
	if !setting.LFS.StartServer {
 | 
			
		||||
		writeStatus(ctx, 404)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !ContentMatcher(ctx.Req) {
 | 
			
		||||
		writeStatus(ctx, 400)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	rv := unpack(ctx)
 | 
			
		||||
 | 
			
		||||
	meta, _ := getAuthenticatedRepoAndMeta(ctx, rv, true)
 | 
			
		||||
	if meta == nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	contentStore := &ContentStore{BasePath: setting.LFS.ContentPath}
 | 
			
		||||
	ok, err := contentStore.Verify(meta)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.Resp.WriteHeader(500)
 | 
			
		||||
		fmt.Fprintf(ctx.Resp, `{"message":"%s"}`, err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if !ok {
 | 
			
		||||
		writeStatus(ctx, 422)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	logRequest(ctx.Req, 200)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Represent takes a RequestVars and Meta and turns it into a Representation suitable
 | 
			
		||||
// for json encoding
 | 
			
		||||
func Represent(rv *RequestVars, meta *models.LFSMetaObject, download, upload bool) *Representation {
 | 
			
		||||
@@ -347,6 +388,11 @@ func Represent(rv *RequestVars, meta *models.LFSMetaObject, download, upload boo
 | 
			
		||||
		rep.Actions["upload"] = &link{Href: rv.ObjectLink(), Header: header}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if upload && !download {
 | 
			
		||||
		// Force client side verify action while gitea lacks proper server side verification
 | 
			
		||||
		rep.Actions["verify"] = &link{Href: rv.VerifyLink(), Header: header}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return rep
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -681,6 +681,7 @@ func RegisterRoutes(m *macaron.Macaron) {
 | 
			
		||||
				m.Get("/objects/:oid/:filename", lfs.ObjectOidHandler)
 | 
			
		||||
				m.Any("/objects/:oid", lfs.ObjectOidHandler)
 | 
			
		||||
				m.Post("/objects", lfs.PostHandler)
 | 
			
		||||
				m.Post("/verify", lfs.VerifyHandler)
 | 
			
		||||
				m.Any("/*", func(ctx *context.Context) {
 | 
			
		||||
					ctx.Handle(404, "", nil)
 | 
			
		||||
				})
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user