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
|
@ -1,13 +1,14 @@
|
||||||
package lfs
|
package lfs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"code.gitea.io/gitea/models"
|
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -82,6 +83,20 @@ func (s *ContentStore) Exists(meta *models.LFSMetaObject) bool {
|
||||||
return true
|
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 {
|
func transformKey(key string) string {
|
||||||
if len(key) < 5 {
|
if len(key) < 5 {
|
||||||
return key
|
return key
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"path"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -15,6 +16,7 @@ import (
|
||||||
"code.gitea.io/gitea/modules/context"
|
"code.gitea.io/gitea/modules/context"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
|
||||||
"github.com/dgrijalva/jwt-go"
|
"github.com/dgrijalva/jwt-go"
|
||||||
"gopkg.in/macaron.v1"
|
"gopkg.in/macaron.v1"
|
||||||
)
|
)
|
||||||
|
@ -66,7 +68,12 @@ type ObjectError struct {
|
||||||
|
|
||||||
// ObjectLink builds a URL linking to the object.
|
// ObjectLink builds a URL linking to the object.
|
||||||
func (v *RequestVars) ObjectLink() string {
|
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.
|
// 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)
|
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
|
// Represent takes a RequestVars and Meta and turns it into a Representation suitable
|
||||||
// for json encoding
|
// for json encoding
|
||||||
func Represent(rv *RequestVars, meta *models.LFSMetaObject, download, upload bool) *Representation {
|
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}
|
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
|
return rep
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -681,6 +681,7 @@ func RegisterRoutes(m *macaron.Macaron) {
|
||||||
m.Get("/objects/:oid/:filename", lfs.ObjectOidHandler)
|
m.Get("/objects/:oid/:filename", lfs.ObjectOidHandler)
|
||||||
m.Any("/objects/:oid", lfs.ObjectOidHandler)
|
m.Any("/objects/:oid", lfs.ObjectOidHandler)
|
||||||
m.Post("/objects", lfs.PostHandler)
|
m.Post("/objects", lfs.PostHandler)
|
||||||
|
m.Post("/verify", lfs.VerifyHandler)
|
||||||
m.Any("/*", func(ctx *context.Context) {
|
m.Any("/*", func(ctx *context.Context) {
|
||||||
ctx.Handle(404, "", nil)
|
ctx.Handle(404, "", nil)
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in a new issue