@ -11,7 +11,6 @@ import (
"os"
"os/exec"
"path/filepath"
"regexp"
"strconv"
"strings"
"time"
@ -20,7 +19,7 @@ import (
asymkey_model "code.gitea.io/gitea/models/asymkey"
git_model "code.gitea.io/gitea/models/git"
"code.gitea.io/gitea/models/perm"
"code.gitea.io/gitea/mod ules/container "
"code.gitea.io/gitea/mod els/repo "
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/lfstransfer"
@ -37,14 +36,6 @@ import (
"github.com/urfave/cli/v2"
)
const (
verbUploadPack = "git-upload-pack"
verbUploadArchive = "git-upload-archive"
verbReceivePack = "git-receive-pack"
verbLfsAuthenticate = "git-lfs-authenticate"
verbLfsTransfer = "git-lfs-transfer"
)
// CmdServ represents the available serv sub-command.
var CmdServ = & cli . Command {
Name : "serv" ,
@ -78,22 +69,6 @@ func setup(ctx context.Context, debug bool) {
}
}
var (
// keep getAccessMode() in sync
allowedCommands = container . SetOf (
verbUploadPack ,
verbUploadArchive ,
verbReceivePack ,
verbLfsAuthenticate ,
verbLfsTransfer ,
)
allowedCommandsLfs = container . SetOf (
verbLfsAuthenticate ,
verbLfsTransfer ,
)
alphaDashDotPattern = regexp . MustCompile ( ` [^\w-\.] ` )
)
// fail prints message to stdout, it's mainly used for git serv and git hook commands.
// The output will be passed to git client and shown to user.
func fail ( ctx context . Context , userMessage , logMsgFmt string , args ... any ) error {
@ -139,19 +114,20 @@ func handleCliResponseExtra(extra private.ResponseExtra) error {
func getAccessMode ( verb , lfsVerb string ) perm . AccessMode {
switch verb {
case verbUploadPack, v erbUploadArchive:
case git. CmdVerbUploadPack , git . CmdV erbUploadArchive:
return perm . AccessModeRead
case v erbReceivePack:
case git. CmdV erbReceivePack:
return perm . AccessModeWrite
case verbLfsAuthenticate, v erbLfsTransfer:
case git. CmdVerbLfsAuthenticate , git . CmdV erbLfsTransfer:
switch lfsVerb {
case "upload" :
case git . CmdSubVerbLfsUpload :
return perm . AccessModeWrite
case "download" :
case git . CmdSubVerbLfsDownload :
return perm . AccessModeRead
}
}
// should be unreachable
setting . PanicInDevOrTesting ( "unknown verb: %s %s" , verb , lfsVerb )
return perm . AccessModeNone
}
@ -230,12 +206,12 @@ func runServ(c *cli.Context) error {
log . Debug ( "SSH_ORIGINAL_COMMAND: %s" , os . Getenv ( "SSH_ORIGINAL_COMMAND" ) )
}
word s, err := shellquote . Split ( cmd )
sshCmdArg s, err := shellquote . Split ( cmd )
if err != nil {
return fail ( ctx , "Error parsing arguments" , "Failed to parse arguments: %v" , err )
}
if len ( word s) < 2 {
if len ( sshCmdArg s) < 2 {
if git . DefaultFeatures ( ) . SupportProcReceive {
// for AGit Flow
if cmd == "ssh_info" {
@ -246,25 +222,21 @@ func runServ(c *cli.Context) error {
return fail ( ctx , "Too few arguments" , "Too few arguments in cmd: %s" , cmd )
}
verb := words [ 0 ]
repoPath := strings . TrimPrefix ( words [ 1 ] , "/" )
var lfsVerb string
rr := strings . SplitN ( repoPath , "/" , 2 )
if len ( rr ) != 2 {
repoPath := strings . TrimPrefix ( sshCmdArgs [ 1 ] , "/" )
repoPathFields := strings . SplitN ( repoPath , "/" , 2 )
if len ( repoPathFields ) != 2 {
return fail ( ctx , "Invalid repository path" , "Invalid repository path: %v" , repoPath )
}
username := r r [ 0 ]
reponame := strings . TrimSuffix ( r r [ 1 ] , ".git" )
username := repoPathFields [ 0 ]
reponame := strings . TrimSuffix ( r epoPathFields [ 1 ] , ".git" ) // “the-repo-name" or "the-repo-name.wiki"
// LowerCase and trim the repoPath as that's how they are stored.
// This should be done after splitting the repoPath into username and reponame
// so that username and reponame are not affected.
repoPath = strings . ToLower ( strings . TrimSpace ( repoPath ) )
if alphaDashDotPattern . MatchString ( reponame ) {
if ! repo . IsValidSSHAccessRepoName ( reponame ) {
return fail ( ctx , "Invalid repo name" , "Invalid repo name: %s" , reponame )
}
@ -286,22 +258,23 @@ func runServ(c *cli.Context) error {
} ( )
}
if allowedCommands . Contains ( verb ) {
if allowedCommandsLfs . Contains ( verb ) {
if ! setting . LFS . StartServer {
return fail ( ctx , "LFS Server is not enabled" , "" )
}
if verb == verbLfsTransfer && ! setting . LFS . AllowPureSSH {
return fail ( ctx , "LFS SSH transfer is not enabled" , "" )
}
if len ( words ) > 2 {
lfsVerb = words [ 2 ]
}
}
} else {
verb , lfsVerb := sshCmdArgs [ 0 ] , ""
if ! git . IsAllowedVerbForServe ( verb ) {
return fail ( ctx , "Unknown git command" , "Unknown git command %s" , verb )
}
if git . IsAllowedVerbForServeLfs ( verb ) {
if ! setting . LFS . StartServer {
return fail ( ctx , "LFS Server is not enabled" , "" )
}
if verb == git . CmdVerbLfsTransfer && ! setting . LFS . AllowPureSSH {
return fail ( ctx , "LFS SSH transfer is not enabled" , "" )
}
if len ( sshCmdArgs ) > 2 {
lfsVerb = sshCmdArgs [ 2 ]
}
}
requestedMode := getAccessMode ( verb , lfsVerb )
results , extra := private . ServCommand ( ctx , keyID , username , reponame , requestedMode , verb , lfsVerb )
@ -310,7 +283,7 @@ func runServ(c *cli.Context) error {
}
// LFS SSH protocol
if verb == v erbLfsTransfer {
if verb == git. CmdV erbLfsTransfer {
token , err := getLFSAuthToken ( ctx , lfsVerb , results )
if err != nil {
return err
@ -319,7 +292,7 @@ func runServ(c *cli.Context) error {
}
// LFS token authentication
if verb == v erbLfsAuthenticate {
if verb == git. CmdV erbLfsAuthenticate {
url := fmt . Sprintf ( "%s%s/%s.git/info/lfs" , setting . AppURL , url . PathEscape ( results . OwnerName ) , url . PathEscape ( results . RepoName ) )
token , err := getLFSAuthToken ( ctx , lfsVerb , results )