2020-04-05 11:50:50 +05:30
// 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 06:46:37 +05:30
package lfs
import (
"encoding/base64"
"fmt"
"io"
"net/http"
2017-11-08 18:34:19 +05:30
"path"
2016-12-26 06:46:37 +05:30
"regexp"
"strconv"
"strings"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context"
2021-04-09 03:55:57 +05:30
lfs_module "code.gitea.io/gitea/modules/lfs"
2016-12-26 06:46:37 +05:30
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
2017-11-08 18:34:19 +05:30
2016-12-26 06:46:37 +05:30
"github.com/dgrijalva/jwt-go"
2021-03-02 02:38:10 +05:30
jsoniter "github.com/json-iterator/go"
2016-12-26 06:46:37 +05:30
)
2021-04-09 03:55:57 +05:30
// requestContext contain variables from the HTTP request.
type requestContext struct {
2016-12-26 06:46:37 +05:30
User string
Repo string
Authorization string
}
2020-03-10 01:26:18 +05:30
// Claims is a JWT Token Claims
type Claims struct {
RepoID int64
Op string
UserID int64
jwt . StandardClaims
}
2016-12-26 06:46:37 +05:30
// ObjectLink builds a URL linking to the object.
2021-04-09 03:55:57 +05:30
func ( rc * requestContext ) ObjectLink ( oid string ) string {
return setting . AppURL + path . Join ( rc . User , rc . Repo + ".git" , "info/lfs/objects" , oid )
2017-11-08 18:34:19 +05:30
}
// VerifyLink builds a URL for verifying the object.
2021-04-09 03:55:57 +05:30
func ( rc * requestContext ) VerifyLink ( ) string {
return setting . AppURL + path . Join ( rc . User , rc . Repo + ".git" , "info/lfs/verify" )
2016-12-26 06:46:37 +05:30
}
2018-05-01 07:16:04 +05:30
var oidRegExp = regexp . MustCompile ( ` ^[A-Fa-f0-9]+$ ` )
2018-07-19 21:09:19 +05:30
func isOidValid ( oid string ) bool {
return oidRegExp . MatchString ( oid )
}
2016-12-26 06:46:37 +05:30
// ObjectOidHandler is the main request routing entry point into LFS server functions
func ObjectOidHandler ( ctx * context . Context ) {
if ! setting . LFS . StartServer {
2020-03-10 01:26:18 +05:30
log . Debug ( "Attempt to access LFS server but LFS server is disabled" )
2016-12-26 06:46:37 +05:30
writeStatus ( ctx , 404 )
return
}
if ctx . Req . Method == "GET" || ctx . Req . Method == "HEAD" {
if MetaMatcher ( ctx . Req ) {
2017-10-30 17:41:56 +05:30
getMetaHandler ( ctx )
2016-12-26 06:46:37 +05:30
return
}
2019-05-25 02:51:00 +05:30
getContentHandler ( ctx )
return
} else if ctx . Req . Method == "PUT" {
2016-12-26 06:46:37 +05:30
PutHandler ( ctx )
return
}
2020-03-10 01:26:18 +05:30
log . Warn ( "Unhandled LFS method: %s for %s/%s OID[%s]" , ctx . Req . Method , ctx . Params ( "username" ) , ctx . Params ( "reponame" ) , ctx . Params ( "oid" ) )
writeStatus ( ctx , 404 )
2016-12-26 06:46:37 +05:30
}
2021-04-09 03:55:57 +05:30
func getAuthenticatedRepoAndMeta ( ctx * context . Context , rc * requestContext , p lfs_module . Pointer , requireWrite bool ) ( * models . LFSMetaObject , * models . Repository ) {
if ! isOidValid ( p . Oid ) {
log . Info ( "Attempt to access invalid LFS OID[%s] in %s/%s" , p . Oid , rc . User , rc . Repo )
2018-07-19 21:09:19 +05:30
writeStatus ( ctx , 404 )
return nil , nil
}
2021-04-09 03:55:57 +05:30
repository , err := models . GetRepositoryByOwnerAndName ( rc . User , rc . Repo )
2016-12-26 06:46:37 +05:30
if err != nil {
2021-04-09 03:55:57 +05:30
log . Error ( "Unable to get repository: %s/%s Error: %v" , rc . User , rc . Repo , err )
2016-12-26 06:46:37 +05:30
writeStatus ( ctx , 404 )
2017-10-30 17:41:56 +05:30
return nil , nil
2016-12-26 06:46:37 +05:30
}
2021-04-09 03:55:57 +05:30
if ! authenticate ( ctx , repository , rc . Authorization , requireWrite ) {
2017-10-30 17:41:56 +05:30
requireAuth ( ctx )
return nil , nil
}
2016-12-26 06:46:37 +05:30
2021-04-09 03:55:57 +05:30
meta , err := repository . GetLFSMetaObjectByOid ( p . Oid )
2016-12-26 06:46:37 +05:30
if err != nil {
2021-04-09 03:55:57 +05:30
log . Error ( "Unable to get LFS OID[%s] Error: %v" , p . Oid , err )
2016-12-26 06:46:37 +05:30
writeStatus ( ctx , 404 )
2017-10-30 17:41:56 +05:30
return nil , nil
2016-12-26 06:46:37 +05:30
}
2017-10-30 17:41:56 +05:30
return meta , repository
}
// getContentHandler gets the content from the content store
func getContentHandler ( ctx * context . Context ) {
2021-04-09 03:55:57 +05:30
rc , p := unpack ( ctx )
2017-10-30 17:41:56 +05:30
2021-04-09 03:55:57 +05:30
meta , _ := getAuthenticatedRepoAndMeta ( ctx , rc , p , false )
2017-10-30 17:41:56 +05:30
if meta == nil {
2020-03-10 01:26:18 +05:30
// Status already written in getAuthenticatedRepoAndMeta
2016-12-26 06:46:37 +05:30
return
}
// Support resume download using Range header
2020-05-11 14:07:59 +05:30
var fromByte , toByte int64
toByte = meta . Size - 1
2016-12-26 06:46:37 +05:30
statusCode := 200
if rangeHdr := ctx . Req . Header . Get ( "Range" ) ; rangeHdr != "" {
2020-05-11 14:07:59 +05:30
regex := regexp . MustCompile ( ` bytes=(\d+)\-(\d*).* ` )
2016-12-26 06:46:37 +05:30
match := regex . FindStringSubmatch ( rangeHdr )
2019-06-13 01:11:28 +05:30
if len ( match ) > 1 {
2016-12-26 06:46:37 +05:30
statusCode = 206
fromByte , _ = strconv . ParseInt ( match [ 1 ] , 10 , 32 )
2020-05-11 14:07:59 +05:30
2021-04-06 18:52:34 +05:30
if fromByte >= meta . Size {
writeStatus ( ctx , http . StatusRequestedRangeNotSatisfiable )
return
}
2020-05-11 14:07:59 +05:30
if match [ 2 ] != "" {
_toByte , _ := strconv . ParseInt ( match [ 2 ] , 10 , 32 )
if _toByte >= fromByte && _toByte < toByte {
toByte = _toByte
}
}
ctx . Resp . Header ( ) . Set ( "Content-Range" , fmt . Sprintf ( "bytes %d-%d/%d" , fromByte , toByte , meta . Size - fromByte ) )
2020-08-13 22:48:18 +05:30
ctx . Resp . Header ( ) . Set ( "Access-Control-Expose-Headers" , "Content-Range" )
2016-12-26 06:46:37 +05:30
}
}
2021-04-09 03:55:57 +05:30
contentStore := lfs_module . NewContentStore ( )
content , err := contentStore . Get ( meta . Pointer )
2016-12-26 06:46:37 +05:30
if err != nil {
2021-04-06 18:52:34 +05:30
// Errors are logged in contentStore.Get
writeStatus ( ctx , http . StatusNotFound )
2016-12-26 06:46:37 +05:30
return
}
2020-03-10 01:26:18 +05:30
defer content . Close ( )
2016-12-26 06:46:37 +05:30
2021-04-06 18:52:34 +05:30
if fromByte > 0 {
_ , err = content . Seek ( fromByte , io . SeekStart )
if err != nil {
log . Error ( "Whilst trying to read LFS OID[%s]: Unable to seek to %d Error: %v" , meta . Oid , fromByte , err )
writeStatus ( ctx , http . StatusInternalServerError )
return
}
}
2020-05-11 14:07:59 +05:30
contentLength := toByte + 1 - fromByte
ctx . Resp . Header ( ) . Set ( "Content-Length" , strconv . FormatInt ( contentLength , 10 ) )
2016-12-26 06:46:37 +05:30
ctx . Resp . Header ( ) . Set ( "Content-Type" , "application/octet-stream" )
filename := ctx . Params ( "filename" )
if len ( filename ) > 0 {
decodedFilename , err := base64 . RawURLEncoding . DecodeString ( filename )
if err == nil {
ctx . Resp . Header ( ) . Set ( "Content-Disposition" , "attachment; filename=\"" + string ( decodedFilename ) + "\"" )
2020-08-13 22:48:18 +05:30
ctx . Resp . Header ( ) . Set ( "Access-Control-Expose-Headers" , "Content-Disposition" )
2016-12-26 06:46:37 +05:30
}
}
ctx . Resp . WriteHeader ( statusCode )
2020-05-11 14:07:59 +05:30
if written , err := io . CopyN ( ctx . Resp , content , contentLength ) ; err != nil {
2020-03-10 01:26:18 +05:30
log . Error ( "Error whilst copying LFS OID[%s] to the response after %d bytes. Error: %v" , meta . Oid , written , err )
}
2016-12-26 06:46:37 +05:30
logRequest ( ctx . Req , statusCode )
}
2017-10-30 17:41:56 +05:30
// getMetaHandler retrieves metadata about the object
func getMetaHandler ( ctx * context . Context ) {
2021-04-09 03:55:57 +05:30
rc , p := unpack ( ctx )
2016-12-26 06:46:37 +05:30
2021-04-09 03:55:57 +05:30
meta , _ := getAuthenticatedRepoAndMeta ( ctx , rc , p , false )
2017-10-30 17:41:56 +05:30
if meta == nil {
2020-03-10 01:26:18 +05:30
// Status already written in getAuthenticatedRepoAndMeta
2016-12-26 06:46:37 +05:30
return
}
2021-04-09 03:55:57 +05:30
ctx . Resp . Header ( ) . Set ( "Content-Type" , lfs_module . MediaType )
2016-12-26 06:46:37 +05:30
if ctx . Req . Method == "GET" {
2021-03-02 02:38:10 +05:30
json := jsoniter . ConfigCompatibleWithStandardLibrary
2016-12-26 06:46:37 +05:30
enc := json . NewEncoder ( ctx . Resp )
2021-04-09 03:55:57 +05:30
if err := enc . Encode ( represent ( rc , meta . Pointer , true , false ) ) ; err != nil {
2020-03-10 01:26:18 +05:30
log . Error ( "Failed to encode representation as json. Error: %v" , err )
}
2016-12-26 06:46:37 +05:30
}
logRequest ( ctx . Req , 200 )
}
// PostHandler instructs the client how to upload data
func PostHandler ( ctx * context . Context ) {
if ! setting . LFS . StartServer {
2020-03-10 01:26:18 +05:30
log . Debug ( "Attempt to access LFS server but LFS server is disabled" )
2016-12-26 06:46:37 +05:30
writeStatus ( ctx , 404 )
return
}
if ! MetaMatcher ( ctx . Req ) {
2021-04-09 03:55:57 +05:30
log . Info ( "Attempt to POST without accepting the correct media type: %s" , lfs_module . MediaType )
2016-12-26 06:46:37 +05:30
writeStatus ( ctx , 400 )
return
}
2021-04-09 03:55:57 +05:30
rc , p := unpack ( ctx )
2016-12-26 06:46:37 +05:30
2021-04-09 03:55:57 +05:30
repository , err := models . GetRepositoryByOwnerAndName ( rc . User , rc . Repo )
2016-12-26 06:46:37 +05:30
if err != nil {
2021-04-09 03:55:57 +05:30
log . Error ( "Unable to get repository: %s/%s Error: %v" , rc . User , rc . Repo , err )
2016-12-26 06:46:37 +05:30
writeStatus ( ctx , 404 )
return
}
2021-04-09 03:55:57 +05:30
if ! authenticate ( ctx , repository , rc . Authorization , true ) {
2016-12-26 06:46:37 +05:30
requireAuth ( ctx )
2018-05-01 07:16:04 +05:30
return
}
2021-04-09 03:55:57 +05:30
if ! isOidValid ( p . Oid ) {
log . Info ( "Invalid LFS OID[%s] attempt to POST in %s/%s" , p . Oid , rc . User , rc . Repo )
2018-05-01 07:16:04 +05:30
writeStatus ( ctx , 404 )
return
2016-12-26 06:46:37 +05:30
}
2021-04-09 03:55:57 +05:30
if setting . LFS . MaxFileSize > 0 && p . Size > setting . LFS . MaxFileSize {
log . Info ( "Denied LFS OID[%s] upload of size %d to %s/%s because of LFS_MAX_FILE_SIZE=%d" , p . Oid , p . Size , rc . User , rc . Repo , setting . LFS . MaxFileSize )
2020-02-28 10:16:57 +05:30
writeStatus ( ctx , 413 )
return
}
2021-04-09 03:55:57 +05:30
meta , err := models . NewLFSMetaObject ( & models . LFSMetaObject { Pointer : p , RepositoryID : repository . ID } )
2016-12-26 06:46:37 +05:30
if err != nil {
2021-04-09 03:55:57 +05:30
log . Error ( "Unable to write LFS OID[%s] size %d meta object in %v/%v to database. Error: %v" , p . Oid , p . Size , rc . User , rc . Repo , err )
2016-12-26 06:46:37 +05:30
writeStatus ( ctx , 404 )
return
}
2021-04-09 03:55:57 +05:30
ctx . Resp . Header ( ) . Set ( "Content-Type" , lfs_module . MediaType )
2016-12-26 06:46:37 +05:30
sentStatus := 202
2021-04-09 03:55:57 +05:30
contentStore := lfs_module . NewContentStore ( )
exist , err := contentStore . Exists ( p )
2020-09-08 21:15:10 +05:30
if err != nil {
2021-04-09 03:55:57 +05:30
log . Error ( "Unable to check if LFS OID[%s] exist on %s / %s. Error: %v" , p . Oid , rc . User , rc . Repo , err )
2020-09-08 21:15:10 +05:30
writeStatus ( ctx , 500 )
return
}
if meta . Existing && exist {
2016-12-26 06:46:37 +05:30
sentStatus = 200
}
ctx . Resp . WriteHeader ( sentStatus )
2021-03-02 02:38:10 +05:30
json := jsoniter . ConfigCompatibleWithStandardLibrary
2016-12-26 06:46:37 +05:30
enc := json . NewEncoder ( ctx . Resp )
2021-04-09 03:55:57 +05:30
if err := enc . Encode ( represent ( rc , meta . Pointer , meta . Existing , true ) ) ; err != nil {
2020-03-10 01:26:18 +05:30
log . Error ( "Failed to encode representation as json. Error: %v" , err )
}
2016-12-26 06:46:37 +05:30
logRequest ( ctx . Req , sentStatus )
}
// BatchHandler provides the batch api
func BatchHandler ( ctx * context . Context ) {
if ! setting . LFS . StartServer {
2020-03-10 01:26:18 +05:30
log . Debug ( "Attempt to access LFS server but LFS server is disabled" )
2016-12-26 06:46:37 +05:30
writeStatus ( ctx , 404 )
return
}
if ! MetaMatcher ( ctx . Req ) {
2021-04-09 03:55:57 +05:30
log . Info ( "Attempt to BATCH without accepting the correct media type: %s" , lfs_module . MediaType )
2016-12-26 06:46:37 +05:30
writeStatus ( ctx , 400 )
return
}
bv := unpackbatch ( ctx )
2021-04-09 03:55:57 +05:30
reqCtx := & requestContext {
User : ctx . Params ( "username" ) ,
Repo : strings . TrimSuffix ( ctx . Params ( "reponame" ) , ".git" ) ,
Authorization : ctx . Req . Header . Get ( "Authorization" ) ,
}
var responseObjects [ ] * lfs_module . ObjectResponse
2016-12-26 06:46:37 +05:30
// Create a response object
for _ , object := range bv . Objects {
2018-07-19 21:09:19 +05:30
if ! isOidValid ( object . Oid ) {
2021-04-09 03:55:57 +05:30
log . Info ( "Invalid LFS OID[%s] attempt to BATCH in %s/%s" , object . Oid , reqCtx . User , reqCtx . Repo )
2018-07-19 21:09:19 +05:30
continue
}
2021-04-09 03:55:57 +05:30
repository , err := models . GetRepositoryByOwnerAndName ( reqCtx . User , reqCtx . Repo )
2016-12-26 06:46:37 +05:30
if err != nil {
2021-04-09 03:55:57 +05:30
log . Error ( "Unable to get repository: %s/%s Error: %v" , reqCtx . User , reqCtx . Repo , err )
2016-12-26 06:46:37 +05:30
writeStatus ( ctx , 404 )
return
}
requireWrite := false
if bv . Operation == "upload" {
requireWrite = true
}
2021-04-09 03:55:57 +05:30
if ! authenticate ( ctx , repository , reqCtx . Authorization , requireWrite ) {
2016-12-26 06:46:37 +05:30
requireAuth ( ctx )
return
}
2021-04-09 03:55:57 +05:30
contentStore := lfs_module . NewContentStore ( )
2017-10-30 17:41:56 +05:30
meta , err := repository . GetLFSMetaObjectByOid ( object . Oid )
2020-09-08 21:15:10 +05:30
if err == nil { // Object is found and exists
2021-04-09 03:55:57 +05:30
exist , err := contentStore . Exists ( meta . Pointer )
2020-09-08 21:15:10 +05:30
if err != nil {
2021-04-09 03:55:57 +05:30
log . Error ( "Unable to check if LFS OID[%s] exist on %s / %s. Error: %v" , object . Oid , reqCtx . User , reqCtx . Repo , err )
2020-09-08 21:15:10 +05:30
writeStatus ( ctx , 500 )
return
}
if exist {
2021-04-09 03:55:57 +05:30
responseObjects = append ( responseObjects , represent ( reqCtx , meta . Pointer , true , false ) )
2020-09-08 21:15:10 +05:30
continue
}
2016-12-26 06:46:37 +05:30
}
2020-03-04 02:27:27 +05:30
if requireWrite && setting . LFS . MaxFileSize > 0 && object . Size > setting . LFS . MaxFileSize {
2021-04-09 03:55:57 +05:30
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 )
2020-03-04 02:27:27 +05:30
writeStatus ( ctx , 413 )
return
}
2018-07-19 21:09:19 +05:30
// Object is not found
2021-04-09 03:55:57 +05:30
meta , err = models . NewLFSMetaObject ( & models . LFSMetaObject { Pointer : object , RepositoryID : repository . ID } )
2018-07-19 21:09:19 +05:30
if err == nil {
2021-04-09 03:55:57 +05:30
exist , err := contentStore . Exists ( meta . Pointer )
2020-09-08 21:15:10 +05:30
if err != nil {
2021-04-09 03:55:57 +05:30
log . Error ( "Unable to check if LFS OID[%s] exist on %s / %s. Error: %v" , object . Oid , reqCtx . User , reqCtx . Repo , err )
2020-09-08 21:15:10 +05:30
writeStatus ( ctx , 500 )
return
}
2021-04-09 03:55:57 +05:30
responseObjects = append ( responseObjects , represent ( reqCtx , meta . Pointer , meta . Existing , ! exist ) )
2020-03-10 01:26:18 +05:30
} else {
2021-04-09 03:55:57 +05:30
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 )
2016-12-26 06:46:37 +05:30
}
}
2021-04-09 03:55:57 +05:30
ctx . Resp . Header ( ) . Set ( "Content-Type" , lfs_module . MediaType )
2016-12-26 06:46:37 +05:30
2021-04-09 03:55:57 +05:30
respobj := & lfs_module . BatchResponse { Objects : responseObjects }
2016-12-26 06:46:37 +05:30
2021-03-02 02:38:10 +05:30
json := jsoniter . ConfigCompatibleWithStandardLibrary
2016-12-26 06:46:37 +05:30
enc := json . NewEncoder ( ctx . Resp )
2020-03-10 01:26:18 +05:30
if err := enc . Encode ( respobj ) ; err != nil {
log . Error ( "Failed to encode representation as json. Error: %v" , err )
}
2016-12-26 06:46:37 +05:30
logRequest ( ctx . Req , 200 )
}
// PutHandler receives data from the client and puts it into the content store
func PutHandler ( ctx * context . Context ) {
2021-04-09 03:55:57 +05:30
rc , p := unpack ( ctx )
2016-12-26 06:46:37 +05:30
2021-04-09 03:55:57 +05:30
meta , repository := getAuthenticatedRepoAndMeta ( ctx , rc , p , true )
2017-10-30 17:41:56 +05:30
if meta == nil {
2020-03-10 01:26:18 +05:30
// Status already written in getAuthenticatedRepoAndMeta
2016-12-26 06:46:37 +05:30
return
}
2021-04-09 03:55:57 +05:30
contentStore := lfs_module . NewContentStore ( )
2021-01-26 21:06:53 +05:30
defer ctx . Req . Body . Close ( )
2021-04-09 03:55:57 +05:30
if err := contentStore . Put ( meta . Pointer , ctx . Req . Body ) ; err != nil {
2020-03-10 01:26:18 +05:30
// Put will log the error itself
2016-12-26 06:46:37 +05:30
ctx . Resp . WriteHeader ( 500 )
2021-04-09 03:55:57 +05:30
if err == lfs_module . ErrSizeMismatch || err == lfs_module . ErrHashMismatch {
2020-03-10 01:26:18 +05:30
fmt . Fprintf ( ctx . Resp , ` { "message":"%s"} ` , err )
} else {
fmt . Fprintf ( ctx . Resp , ` { "message":"Internal Server Error"} ` )
}
2021-04-09 03:55:57 +05:30
if _ , err = repository . RemoveLFSMetaObjectByOid ( p . Oid ) ; err != nil {
log . Error ( "Whilst removing metaobject for LFS OID[%s] due to preceding error there was another Error: %v" , p . Oid , err )
2017-10-30 17:41:56 +05:30
}
2016-12-26 06:46:37 +05:30
return
}
logRequest ( ctx . Req , 200 )
}
2017-11-08 18:34:19 +05:30
// VerifyHandler verify oid and its size from the content store
func VerifyHandler ( ctx * context . Context ) {
if ! setting . LFS . StartServer {
2020-03-10 01:26:18 +05:30
log . Debug ( "Attempt to access LFS server but LFS server is disabled" )
2017-11-08 18:34:19 +05:30
writeStatus ( ctx , 404 )
return
}
2019-05-25 02:51:00 +05:30
if ! MetaMatcher ( ctx . Req ) {
2021-04-09 03:55:57 +05:30
log . Info ( "Attempt to VERIFY without accepting the correct media type: %s" , lfs_module . MediaType )
2017-11-08 18:34:19 +05:30
writeStatus ( ctx , 400 )
return
}
2021-04-09 03:55:57 +05:30
rc , p := unpack ( ctx )
2017-11-08 18:34:19 +05:30
2021-04-09 03:55:57 +05:30
meta , _ := getAuthenticatedRepoAndMeta ( ctx , rc , p , true )
2017-11-08 18:34:19 +05:30
if meta == nil {
2020-03-10 01:26:18 +05:30
// Status already written in getAuthenticatedRepoAndMeta
2017-11-08 18:34:19 +05:30
return
}
2021-04-09 03:55:57 +05:30
contentStore := lfs_module . NewContentStore ( )
ok , err := contentStore . Verify ( meta . Pointer )
2017-11-08 18:34:19 +05:30
if err != nil {
2020-03-10 01:26:18 +05:30
// Error will be logged in Verify
2017-11-08 18:34:19 +05:30
ctx . Resp . WriteHeader ( 500 )
2020-03-10 01:26:18 +05:30
fmt . Fprintf ( ctx . Resp , ` { "message":"Internal Server Error"} ` )
2017-11-08 18:34:19 +05:30
return
}
if ! ok {
writeStatus ( ctx , 422 )
return
}
logRequest ( ctx . Req , 200 )
}
2021-04-09 03:55:57 +05:30
// represent takes a requestContext and Meta and turns it into a ObjectResponse suitable
2016-12-26 06:46:37 +05:30
// for json encoding
2021-04-09 03:55:57 +05:30
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 ) ,
2016-12-26 06:46:37 +05:30
}
header := make ( map [ string ] string )
2021-04-09 03:55:57 +05:30
if rc . Authorization == "" {
2016-12-26 06:46:37 +05:30
//https://github.com/github/git-lfs/issues/1088
header [ "Authorization" ] = "Authorization: Basic dummy"
} else {
2021-04-09 03:55:57 +05:30
header [ "Authorization" ] = rc . Authorization
2016-12-26 06:46:37 +05:30
}
if download {
2021-04-09 03:55:57 +05:30
rep . Actions [ "download" ] = & lfs_module . Link { Href : rc . ObjectLink ( pointer . Oid ) , Header : header }
2016-12-26 06:46:37 +05:30
}
if upload {
2021-04-09 03:55:57 +05:30
rep . Actions [ "upload" ] = & lfs_module . Link { Href : rc . ObjectLink ( pointer . Oid ) , Header : header }
2016-12-26 06:46:37 +05:30
}
2017-11-08 18:34:19 +05:30
if upload && ! download {
// Force client side verify action while gitea lacks proper server side verification
2019-05-25 02:51:00 +05:30
verifyHeader := make ( map [ string ] string )
for k , v := range header {
verifyHeader [ k ] = v
}
// This is only needed to workaround https://github.com/git-lfs/git-lfs/issues/3662
2021-04-09 03:55:57 +05:30
verifyHeader [ "Accept" ] = lfs_module . MediaType
2019-05-25 02:51:00 +05:30
2021-04-09 03:55:57 +05:30
rep . Actions [ "verify" ] = & lfs_module . Link { Href : rc . VerifyLink ( ) , Header : verifyHeader }
2017-11-08 18:34:19 +05:30
}
2016-12-26 06:46:37 +05:30
return rep
}
// MetaMatcher provides a mux.MatcherFunc that only allows requests that contain
2021-04-09 03:55:57 +05:30
// an Accept header with the lfs_module.MediaType
2021-01-26 21:06:53 +05:30
func MetaMatcher ( r * http . Request ) bool {
2016-12-26 06:46:37 +05:30
mediaParts := strings . Split ( r . Header . Get ( "Accept" ) , ";" )
mt := mediaParts [ 0 ]
2021-04-09 03:55:57 +05:30
return mt == lfs_module . MediaType
2016-12-26 06:46:37 +05:30
}
2021-04-09 03:55:57 +05:30
func unpack ( ctx * context . Context ) ( * requestContext , lfs_module . Pointer ) {
2016-12-26 06:46:37 +05:30
r := ctx . Req
2021-04-09 03:55:57 +05:30
rc := & requestContext {
2016-12-26 06:46:37 +05:30
User : ctx . Params ( "username" ) ,
Repo : strings . TrimSuffix ( ctx . Params ( "reponame" ) , ".git" ) ,
Authorization : r . Header . Get ( "Authorization" ) ,
}
2021-04-09 03:55:57 +05:30
p := lfs_module . Pointer { Oid : ctx . Params ( "oid" ) }
2016-12-26 06:46:37 +05:30
if r . Method == "POST" { // Maybe also check if +json
2021-04-09 03:55:57 +05:30
var p2 lfs_module . Pointer
2021-01-26 21:06:53 +05:30
bodyReader := r . Body
2019-10-10 23:12:28 +05:30
defer bodyReader . Close ( )
2021-03-02 02:38:10 +05:30
json := jsoniter . ConfigCompatibleWithStandardLibrary
2019-10-10 23:12:28 +05:30
dec := json . NewDecoder ( bodyReader )
2021-04-09 03:55:57 +05:30
err := dec . Decode ( & p2 )
2016-12-26 06:46:37 +05:30
if err != nil {
2020-03-10 01:26:18 +05:30
// The error is logged as a WARN here because this may represent misbehaviour rather than a true error
2021-04-09 03:55:57 +05:30
log . Warn ( "Unable to decode POST request vars for LFS OID[%s] in %s/%s: Error: %v" , p . Oid , rc . User , rc . Repo , err )
return rc , p
2016-12-26 06:46:37 +05:30
}
2021-04-09 03:55:57 +05:30
p . Oid = p2 . Oid
p . Size = p2 . Size
2016-12-26 06:46:37 +05:30
}
2021-04-09 03:55:57 +05:30
return rc , p
2016-12-26 06:46:37 +05:30
}
// TODO cheap hack, unify with unpack
2021-04-09 03:55:57 +05:30
func unpackbatch ( ctx * context . Context ) * lfs_module . BatchRequest {
2016-12-26 06:46:37 +05:30
r := ctx . Req
2021-04-09 03:55:57 +05:30
var bv lfs_module . BatchRequest
2016-12-26 06:46:37 +05:30
2021-01-26 21:06:53 +05:30
bodyReader := r . Body
2019-10-10 23:12:28 +05:30
defer bodyReader . Close ( )
2021-03-02 02:38:10 +05:30
json := jsoniter . ConfigCompatibleWithStandardLibrary
2019-10-10 23:12:28 +05:30
dec := json . NewDecoder ( bodyReader )
2016-12-26 06:46:37 +05:30
err := dec . Decode ( & bv )
if err != nil {
2020-03-10 01:26:18 +05:30
// The error is logged as a WARN here because this may represent misbehaviour rather than a true error
log . Warn ( "Unable to decode BATCH request vars in %s/%s: Error: %v" , ctx . Params ( "username" ) , strings . TrimSuffix ( ctx . Params ( "reponame" ) , ".git" ) , err )
2016-12-26 06:46:37 +05:30
return & bv
}
return & bv
}
func writeStatus ( ctx * context . Context , status int ) {
message := http . StatusText ( status )
mediaParts := strings . Split ( ctx . Req . Header . Get ( "Accept" ) , ";" )
mt := mediaParts [ 0 ]
if strings . HasSuffix ( mt , "+json" ) {
message = ` { "message":" ` + message + ` "} `
}
ctx . Resp . WriteHeader ( status )
fmt . Fprint ( ctx . Resp , message )
logRequest ( ctx . Req , status )
}
2021-01-26 21:06:53 +05:30
func logRequest ( r * http . Request , status int ) {
2016-12-26 06:46:37 +05:30
log . Debug ( "LFS request - Method: %s, URL: %s, Status %d" , r . Method , r . URL , status )
}
// authenticate uses the authorization string to determine whether
// or not to proceed. This server assumes an HTTP Basic auth format.
func authenticate ( ctx * context . Context , repository * models . Repository , authorization string , requireWrite bool ) bool {
accessMode := models . AccessModeRead
if requireWrite {
accessMode = models . AccessModeWrite
}
2019-01-31 19:06:57 +05:30
// ctx.IsSigned is unnecessary here, this will be checked in perm.CanAccess
2018-11-28 16:56:14 +05:30
perm , err := models . GetUserRepoPermission ( repository , ctx . User )
if err != nil {
2020-03-10 01:26:18 +05:30
log . Error ( "Unable to GetUserRepoPermission for user %-v in repo %-v Error: %v" , ctx . User , repository )
2018-11-28 16:56:14 +05:30
return false
2016-12-26 06:46:37 +05:30
}
2019-01-31 19:06:57 +05:30
canRead := perm . CanAccess ( accessMode , models . UnitTypeCode )
if canRead {
return true
2016-12-26 06:46:37 +05:30
}
2018-01-27 22:18:15 +05:30
user , repo , opStr , err := parseToken ( authorization )
2016-12-26 06:46:37 +05:30
if err != nil {
2020-03-10 01:26:18 +05:30
// Most of these are Warn level - the true internal server errors are logged in parseToken already
log . Warn ( "Authentication failure for provided token with Error: %v" , err )
2016-12-26 06:46:37 +05:30
return false
}
2018-01-27 22:18:15 +05:30
ctx . User = user
if opStr == "basic" {
2018-11-28 16:56:14 +05:30
perm , err = models . GetUserRepoPermission ( repository , ctx . User )
if err != nil {
2020-03-10 01:26:18 +05:30
log . Error ( "Unable to GetUserRepoPermission for user %-v in repo %-v Error: %v" , ctx . User , repository )
2018-11-28 16:56:14 +05:30
return false
}
return perm . CanAccess ( accessMode , models . UnitTypeCode )
2016-12-26 06:46:37 +05:30
}
2018-01-27 22:18:15 +05:30
if repository . ID == repo . ID {
if requireWrite && opStr != "upload" {
return false
}
return true
2016-12-26 06:46:37 +05:30
}
2018-01-27 22:18:15 +05:30
return false
2016-12-26 06:46:37 +05:30
}
2018-01-27 22:18:15 +05:30
func parseToken ( authorization string ) ( * models . User , * models . Repository , string , error ) {
if authorization == "" {
return nil , nil , "unknown" , fmt . Errorf ( "No token" )
}
if strings . HasPrefix ( authorization , "Bearer " ) {
2020-03-10 01:26:18 +05:30
token , err := jwt . ParseWithClaims ( authorization [ 7 : ] , & Claims { } , func ( t * jwt . Token ) ( interface { } , error ) {
2018-01-27 22:18:15 +05:30
if _ , ok := t . Method . ( * jwt . SigningMethodHMAC ) ; ! ok {
return nil , fmt . Errorf ( "unexpected signing method: %v" , t . Header [ "alg" ] )
}
return setting . LFS . JWTSecretBytes , nil
} )
if err != nil {
2020-03-10 01:26:18 +05:30
// The error here is WARN level because it is caused by bad authorization rather than an internal server error
2018-01-27 22:18:15 +05:30
return nil , nil , "unknown" , err
2016-12-26 06:46:37 +05:30
}
2020-03-10 01:26:18 +05:30
claims , claimsOk := token . Claims . ( * Claims )
2018-01-27 22:18:15 +05:30
if ! token . Valid || ! claimsOk {
return nil , nil , "unknown" , fmt . Errorf ( "Token claim invalid" )
}
2020-03-10 01:26:18 +05:30
r , err := models . GetRepositoryByID ( claims . RepoID )
2018-01-27 22:18:15 +05:30
if err != nil {
2020-03-10 01:26:18 +05:30
log . Error ( "Unable to GetRepositoryById[%d]: Error: %v" , claims . RepoID , err )
return nil , nil , claims . Op , err
2018-01-27 22:18:15 +05:30
}
2020-03-10 01:26:18 +05:30
u , err := models . GetUserByID ( claims . UserID )
2018-01-27 22:18:15 +05:30
if err != nil {
2020-03-10 01:26:18 +05:30
log . Error ( "Unable to GetUserById[%d]: Error: %v" , claims . UserID , err )
return nil , r , claims . Op , err
2018-01-27 22:18:15 +05:30
}
2020-03-10 01:26:18 +05:30
return u , r , claims . Op , nil
2016-12-26 06:46:37 +05:30
}
2018-01-27 22:18:15 +05:30
if strings . HasPrefix ( authorization , "Basic " ) {
c , err := base64 . StdEncoding . DecodeString ( strings . TrimPrefix ( authorization , "Basic " ) )
if err != nil {
return nil , nil , "basic" , err
}
cs := string ( c )
i := strings . IndexByte ( cs , ':' )
if i < 0 {
return nil , nil , "basic" , fmt . Errorf ( "Basic auth invalid" )
}
user , password := cs [ : i ] , cs [ i + 1 : ]
u , err := models . GetUserByName ( user )
if err != nil {
2020-03-10 01:26:18 +05:30
log . Error ( "Unable to GetUserByName[%d]: Error: %v" , user , err )
2018-01-27 22:18:15 +05:30
return nil , nil , "basic" , err
}
2019-01-31 02:48:54 +05:30
if ! u . IsPasswordSet ( ) || ! u . ValidatePassword ( password ) {
2018-01-27 22:18:15 +05:30
return nil , nil , "basic" , fmt . Errorf ( "Basic auth failed" )
}
return u , nil , "basic" , nil
2016-12-26 06:46:37 +05:30
}
2018-01-27 22:18:15 +05:30
return nil , nil , "unknown" , fmt . Errorf ( "Token not found" )
2016-12-26 06:46:37 +05:30
}
func requireAuth ( ctx * context . Context ) {
ctx . Resp . Header ( ) . Set ( "WWW-Authenticate" , "Basic realm=gitea-lfs" )
writeStatus ( ctx , 401 )
}