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:
		
							parent
							
								
									61f5c22503
								
							
						
					
					
						commit
						ba2e0240c6
					
				
					 3 changed files with 64 additions and 2 deletions
				
			
		|  | @ -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) | ||||
| 				}) | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue