Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
4281413
Use constants.
KN4CK3R Apr 13, 2021
9b8936a
Use IsValid instead of isOidValid
KN4CK3R Apr 13, 2021
6a8a4a9
Restructured code. Moved static checks out of loop.
KN4CK3R Apr 13, 2021
5f68237
Made method private.
KN4CK3R Apr 14, 2021
7d0e8d8
Restructured batch api. Add support for individual errors.
KN4CK3R Apr 14, 2021
c51ac32
Let router decide if LFS is enabled.
KN4CK3R Apr 15, 2021
406127b
Renamed methods.
KN4CK3R Apr 15, 2021
e5bcd0d
Return correct status from verify handler.
KN4CK3R Apr 15, 2021
2b9daab
Unified media type check in router.
KN4CK3R Apr 15, 2021
dad7521
Changed error code according to spec.
KN4CK3R Apr 15, 2021
f3fe4de
Moved checks into router.
KN4CK3R Apr 15, 2021
89607a2
Removed invalid v1 api methods.
KN4CK3R Apr 15, 2021
afd6eba
Unified methods.
KN4CK3R Apr 16, 2021
aff8fc8
Display better error messages.
KN4CK3R Apr 16, 2021
24279e1
Added size parameter. Create meta object on upload.
KN4CK3R Apr 16, 2021
c45f43c
Use object error on invalid size.
KN4CK3R Apr 16, 2021
709fcfa
Skip upload if object exists.
KN4CK3R Apr 16, 2021
d11bd55
Moved methods.
KN4CK3R Apr 16, 2021
497283f
Suppress fields in response.
KN4CK3R Apr 16, 2021
e0db2b2
Changed error on accept.
KN4CK3R Apr 16, 2021
5a932da
Fixed tests.
KN4CK3R Apr 16, 2021
2b0a713
Added tests.
KN4CK3R Apr 16, 2021
25c3a01
Merge branch 'master' of https://github.com/go-gitea/gitea into fix-l…
KN4CK3R Apr 16, 2021
9bc1cf6
Use ErrorResponse object.
KN4CK3R Apr 18, 2021
f80fe0f
Merge branch 'master' of https://github.com/go-gitea/gitea into fix-l…
KN4CK3R Apr 18, 2021
3093f76
Test against message property.
KN4CK3R Apr 18, 2021
1c3ac29
Add support for the old invalid lfs client.
KN4CK3R Apr 27, 2021
1bcbff8
Fixed the check because MinIO wraps the error.
KN4CK3R Apr 27, 2021
9eaa2e4
Use individual repositories.
KN4CK3R Apr 27, 2021
a3d323b
Removed unnecessary postgresql workaround.
KN4CK3R Apr 27, 2021
bec50ee
Merge branch 'main' of https://github.com/go-gitea/gitea into fix-lfs…
KN4CK3R May 18, 2021
5472d54
Merge branch 'main' into fix-lfs-server
6543 May 19, 2021
1443a2c
Merge branch 'main' into fix-lfs-server
6543 Jun 5, 2021
b0e6b8e
Use StatusNotFound instead of StatusForbidden.
KN4CK3R Jun 5, 2021
9f29ed5
Fixed status codes in tests.
KN4CK3R Jun 5, 2021
f411fe8
Merge branch 'main' into fix-lfs-server
lafriks Jun 5, 2021
0ccad52
Merge branch 'main' into fix-lfs-server
6543 Jun 5, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Restructured batch api. Add support for individual errors.
  • Loading branch information
KN4CK3R committed Apr 14, 2021
commit 7d0e8d85afa5a41e2b84a3b98ef84860218b13ff
2 changes: 1 addition & 1 deletion routers/routes/web.go
Original file line number Diff line number Diff line change
Expand Up @@ -1098,7 +1098,7 @@ func RegisterRoutes(m *web.Route) {
m.Get("/objects/{oid}/{filename}", lfs.ObjectOidHandler)
m.Any("/objects/{oid}", lfs.ObjectOidHandler)
m.Post("/objects", lfs.PostHandler)
m.Post("/verify", lfs.VerifyHandler)
m.Post("/verify/{oid}", lfs.VerifyHandler)
m.Group("/locks", func() {
m.Get("/", lfs.GetListLockHandler)
m.Post("/", lfs.PostLockHandler)
Expand Down
132 changes: 71 additions & 61 deletions services/lfs/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ func (rc *requestContext) ObjectLink(oid string) string {
}

// VerifyLink builds a URL for verifying the object.
func (rc *requestContext) VerifyLink() string {
return setting.AppURL + path.Join(rc.User, rc.Repo+".git", "info/lfs/verify")
func (rc *requestContext) VerifyLink(oid string) string {
return setting.AppURL + path.Join(rc.User, rc.Repo+".git", "info/lfs/verify", oid)
}

// ObjectOidHandler is the main request routing entry point into LFS server functions
Expand Down Expand Up @@ -195,7 +195,7 @@ func getMetaHandler(ctx *context.Context) {
if ctx.Req.Method == "GET" {
json := jsoniter.ConfigCompatibleWithStandardLibrary
enc := json.NewEncoder(ctx.Resp)
if err := enc.Encode(represent(rc, meta.Pointer, true, false)); err != nil {
if err := enc.Encode(buildObjectResponse(rc, meta.Pointer, true, false, 0)); err != nil {
log.Error("Failed to encode representation as json. Error: %v", err)
}
}
Expand Down Expand Up @@ -267,7 +267,7 @@ func PostHandler(ctx *context.Context) {

json := jsoniter.ConfigCompatibleWithStandardLibrary
enc := json.NewEncoder(ctx.Resp)
if err := enc.Encode(represent(rc, meta.Pointer, meta.Existing, true)); err != nil {
if err := enc.Encode(buildObjectResponse(rc, meta.Pointer, meta.Existing, true, 0)); err != nil {
log.Error("Failed to encode representation as json. Error: %v", err)
}
logRequest(ctx.Req, sentStatus)
Expand All @@ -289,6 +289,17 @@ func BatchHandler(ctx *context.Context) {

bv := unpackbatch(ctx)

var isUpload bool
if bv.Operation == "upload" {
isUpload = true
} else if bv.Operation == "download" {
isUpload = false
} else {
log.Info("Attempt to BATCH with invalid operation: %s", bv.Operation)
writeStatus(ctx, http.StatusBadRequest)
return
}

reqCtx := &requestContext{
User: ctx.Params("username"),
Repo: strings.TrimSuffix(ctx.Params("reponame"), ".git"),
Expand All @@ -302,12 +313,7 @@ func BatchHandler(ctx *context.Context) {
return
}

requireWrite := false
if bv.Operation == "upload" {
requireWrite = true
}

if !authenticate(ctx, repository, reqCtx.Authorization, requireWrite) {
if !authenticate(ctx, repository, reqCtx.Authorization, isUpload) {
requireAuth(ctx)
return
}
Expand All @@ -316,41 +322,57 @@ func BatchHandler(ctx *context.Context) {

var responseObjects []*lfs_module.ObjectResponse

// Create a response object
for _, object := range bv.Objects {
if !object.IsValid() {
log.Info("Invalid LFS OID[%s] attempt to BATCH in %s/%s", object.Oid, reqCtx.User, reqCtx.Repo)
responseObjects = append(responseObjects, buildDownloadObjectResponse(reqCtx, object, http.StatusUnprocessableEntity))
continue
}

if requireWrite && setting.LFS.MaxFileSize > 0 && object.Size > setting.LFS.MaxFileSize {
log.Info("Denied LFS OID[%s] upload of size %d to %s/%s because of LFS_MAX_FILE_SIZE=%d", object.Oid, object.Size, reqCtx.User, reqCtx.Repo, setting.LFS.MaxFileSize)
writeStatus(ctx, http.StatusRequestEntityTooLarge)
exist, err := contentStore.Exists(object)
if err != nil {
log.Error("Unable to check if LFS OID[%s] exist on %s/%s. Error: %v", object.Oid, reqCtx.User, reqCtx.Repo, err)
writeStatus(ctx, http.StatusInternalServerError)
return
}

exist, err := contentStore.Exists(object)
if err != nil {
log.Error("Unable to check if LFS OID[%s] exist on %s / %s. Error: %v", object.Oid, reqCtx.User, reqCtx.Repo, err)
meta, metaErr := repository.GetLFSMetaObjectByOid(object.Oid)
if metaErr != nil && metaErr != models.ErrLFSObjectNotExist {
log.Error("Unable to get LFS MetaObject [%s] for %s/%s. Error: %v", object.Oid, reqCtx.User, reqCtx.Repo, metaErr)
writeStatus(ctx, http.StatusInternalServerError)
return
}

meta, err := repository.GetLFSMetaObjectByOid(object.Oid)
if err == nil { // Object is found and exists
if exist {
responseObjects = append(responseObjects, represent(reqCtx, meta.Pointer, true, false))
continue
var responseObject *lfs_module.ObjectResponse
if isUpload {
if !exists && setting.LFS.MaxFileSize > 0 && object.Size > setting.LFS.MaxFileSize {
log.Info("Denied LFS OID[%s] upload of size %d to %s/%s because of LFS_MAX_FILE_SIZE=%d", object.Oid, object.Size, reqCtx.User, reqCtx.Repo, setting.LFS.MaxFileSize)
writeStatus(ctx, http.StatusRequestEntityTooLarge)
return
}
}

// Object is not found
meta, err = models.NewLFSMetaObject(&models.LFSMetaObject{Pointer: object, RepositoryID: repository.ID})
if err == nil {
responseObjects = append(responseObjects, represent(reqCtx, meta.Pointer, meta.Existing, !exist))
if exists {
if meta == nil {
_, err := models.NewLFSMetaObject(&models.LFSMetaObject{Pointer: object, RepositoryID: repository.ID})
if err != nil {
log.Error("Unable to create LFS MetaObject [%s] for %s/%s. Error: %v", object.Oid, reqCtx.User, reqCtx.Repo, metaErr)
writeStatus(ctx, http.StatusInternalServerError)
return
}
}
}

responseObject = buildObjectResponse(reqCtx, object, false, !exists, 0)
} else {
log.Error("Unable to write LFS OID[%s] size %d meta object in %v/%v to database. Error: %v", object.Oid, object.Size, reqCtx.User, reqCtx.Repo, err)
errorCode := 0
if !exist || meta == nil {
errorCode = http.StatusNotFound
} else if meta.Size != object.Size {
errorCode = http.StatusUnprocessableEntity
}

responseObject = buildObjectResponse(reqCtx, object, true, false, errorCode)
}
responseObjects = append(responseObjects, responseObject)
}

ctx.Resp.Header().Set("Content-Type", lfs_module.MediaType)
Expand Down Expand Up @@ -432,44 +454,32 @@ func VerifyHandler(ctx *context.Context) {
logRequest(ctx.Req, http.StatusOK)
}

// represent takes a requestContext and Meta and turns it into a ObjectResponse suitable
// for json encoding
func represent(rc *requestContext, pointer lfs_module.Pointer, download, upload bool) *lfs_module.ObjectResponse {
rep := &lfs_module.ObjectResponse{
Pointer: pointer,
Actions: make(map[string]*lfs_module.Link),
}

header := make(map[string]string)

if rc.Authorization == "" {
//https://github.com/github/git-lfs/issues/1088
header["Authorization"] = "Authorization: Basic dummy"
func buildObjectResponse(rc *requestContext, pointer lfs_module.Pointer, download, upload bool, errorCode int) *lfs_module.ObjectResponse {
rep := &lfs_module.ObjectResponse{Pointer: pointer}
if errorCode > 0 {
rep.Error = &ObjectError{
Code: errorCode,
Message: http.StatusText(errorCode),
}
} else {
header["Authorization"] = rc.Authorization
}
rep.Actions = make(map[string]*lfs_module.Link)

if download {
rep.Actions["download"] = &lfs_module.Link{Href: rc.ObjectLink(pointer.Oid), Header: header}
}

if upload {
rep.Actions["upload"] = &lfs_module.Link{Href: rc.ObjectLink(pointer.Oid), Header: header}
}

if upload && !download {
// Force client side verify action while gitea lacks proper server side verification
header := make(map[string]string)
verifyHeader := make(map[string]string)
for k, v := range header {
verifyHeader[k] = v

if len(rc.Authorization) > 0 {
header["Authorization"] = rc.Authorization
verifyHeader["Authorization"] = rc.Authorization
}

// This is only needed to workaround https://github.com/git-lfs/git-lfs/issues/3662
verifyHeader["Accept"] = lfs_module.MediaType

rep.Actions["verify"] = &lfs_module.Link{Href: rc.VerifyLink(), Header: verifyHeader}
if download {
rep.Actions["download"] = &lfs_module.Link{Href: rc.ObjectLink(pointer.Oid), Header: header}
}
if upload {
rep.Actions["upload"] = &lfs_module.Link{Href: rc.ObjectLink(pointer.Oid), Header: header}
rep.Actions["verify"] = &lfs_module.Link{Href: rc.VerifyLink(pointer.Oid), Header: verifyHeader}
}
}

return rep
}

Expand Down