@@ -16,14 +16,20 @@ import (
1616 "time"
1717 "unsafe"
1818
19+ "code.gitea.io/gitea/modules/git/internal" //nolint:depguard // only this file can use the internal type CmdArg, other files and packages should use AddXxx functions
1920 "code.gitea.io/gitea/modules/log"
2021 "code.gitea.io/gitea/modules/process"
2122 "code.gitea.io/gitea/modules/util"
2223)
2324
25+ // TrustedCmdArgs returns the trusted arguments for git command.
26+ // It's mainly for passing user-provided and trusted arguments to git command
27+ // In most cases, it shouldn't be used. Use AddXxx function instead
28+ type TrustedCmdArgs []internal.CmdArg
29+
2430var (
2531 // globalCommandArgs global command args for external package setting
26- globalCommandArgs [] CmdArg
32+ globalCommandArgs TrustedCmdArgs
2733
2834 // defaultCommandExecutionTimeout default command execution timeout duration
2935 defaultCommandExecutionTimeout = 360 * time .Second
@@ -42,8 +48,6 @@ type Command struct {
4248 brokenArgs []string
4349}
4450
45- type CmdArg string
46-
4751func (c * Command ) String () string {
4852 if len (c .args ) == 0 {
4953 return c .name
@@ -53,7 +57,7 @@ func (c *Command) String() string {
5357
5458// NewCommand creates and returns a new Git Command based on given command and arguments.
5559// Each argument should be safe to be trusted. User-provided arguments should be passed to AddDynamicArguments instead.
56- func NewCommand (ctx context.Context , args ... CmdArg ) * Command {
60+ func NewCommand (ctx context.Context , args ... internal. CmdArg ) * Command {
5761 // Make an explicit copy of globalCommandArgs, otherwise append might overwrite it
5862 cargs := make ([]string , 0 , len (globalCommandArgs )+ len (args ))
5963 for _ , arg := range globalCommandArgs {
@@ -70,15 +74,9 @@ func NewCommand(ctx context.Context, args ...CmdArg) *Command {
7074 }
7175}
7276
73- // NewCommandNoGlobals creates and returns a new Git Command based on given command and arguments only with the specify args and don't care global command args
74- // Each argument should be safe to be trusted. User-provided arguments should be passed to AddDynamicArguments instead.
75- func NewCommandNoGlobals (args ... CmdArg ) * Command {
76- return NewCommandContextNoGlobals (DefaultContext , args ... )
77- }
78-
7977// NewCommandContextNoGlobals creates and returns a new Git Command based on given command and arguments only with the specify args and don't care global command args
8078// Each argument should be safe to be trusted. User-provided arguments should be passed to AddDynamicArguments instead.
81- func NewCommandContextNoGlobals (ctx context.Context , args ... CmdArg ) * Command {
79+ func NewCommandContextNoGlobals (ctx context.Context , args ... internal. CmdArg ) * Command {
8280 cargs := make ([]string , 0 , len (args ))
8381 for _ , arg := range args {
8482 cargs = append (cargs , string (arg ))
@@ -96,27 +94,62 @@ func (c *Command) SetParentContext(ctx context.Context) *Command {
9694 return c
9795}
9896
99- // SetDescription sets the description for this command which be returned on
100- // c.String()
97+ // SetDescription sets the description for this command which be returned on c.String()
10198func (c * Command ) SetDescription (desc string ) * Command {
10299 c .desc = desc
103100 return c
104101}
105102
106- // AddArguments adds new git argument(s) to the command. Each argument must be safe to be trusted.
107- // User-provided arguments should be passed to AddDynamicArguments instead.
108- func (c * Command ) AddArguments (args ... CmdArg ) * Command {
103+ func isSafeDynArg (s string ) bool {
104+ return s == "" || s [0 ] != '-'
105+ }
106+
107+ func isValidOption (s string ) bool {
108+ return s != "" && s [0 ] == '-'
109+ }
110+
111+ // AddArguments adds new git arguments to the command. It only accepts string literals, or trusted CmdArg.
112+ // Type CmdArg is in the internal package, so it can not be used outside of this package directly,
113+ // it makes sure that user-provided arguments won't cause RCE risks.
114+ // User-provided arguments should be passed by other AddXxx functions
115+ func (c * Command ) AddArguments (args ... internal.CmdArg ) * Command {
109116 for _ , arg := range args {
110117 c .args = append (c .args , string (arg ))
111118 }
112119 return c
113120}
114121
115- // AddDynamicArguments adds new dynamic argument(s) to the command.
116- // The arguments may come from user input and can not be trusted, so no leading '-' is allowed to avoid passing options
122+ // AddOptionValues adds a new option with a list of non-option values
123+ // For example: AddOptionValues("--opt", val) means 2 arguments: {"--opt", val}.
124+ // The values are treated as dynamic arguments. It equals to: AddArguments("--opt") then AddDynamicArguments(val).
125+ func (c * Command ) AddOptionValues (opt internal.CmdArg , args ... string ) * Command {
126+ if ! isValidOption (string (opt )) {
127+ c .brokenArgs = append (c .brokenArgs , string (opt ))
128+ return c
129+ }
130+ c .args = append (c .args , string (opt ))
131+ c .AddDynamicArguments (args ... )
132+ return c
133+ }
134+
135+ // AddOptionFormat adds a new option with a format string and arguments
136+ // For example: AddOptionFormat("--opt=%s %s", val1, val2) means 1 argument: {"--opt=val1 val2"}.
137+ func (c * Command ) AddOptionFormat (opt string , args ... any ) * Command {
138+ if ! isValidOption (opt ) {
139+ c .brokenArgs = append (c .brokenArgs , opt )
140+ return c
141+ }
142+
143+ s := fmt .Sprintf (opt , args ... )
144+ c .args = append (c .args , s )
145+ return c
146+ }
147+
148+ // AddDynamicArguments adds new dynamic arguments to the command.
149+ // The arguments may come from user input and can not be trusted, so no leading '-' is allowed to avoid passing options.
117150func (c * Command ) AddDynamicArguments (args ... string ) * Command {
118151 for _ , arg := range args {
119- if arg != "" && arg [ 0 ] == '-' {
152+ if ! isSafeDynArg ( arg ) {
120153 c .brokenArgs = append (c .brokenArgs , arg )
121154 }
122155 }
@@ -137,14 +170,14 @@ func (c *Command) AddDashesAndList(list ...string) *Command {
137170 return c
138171}
139172
140- // CmdArgCheck checks whether the string is safe to be used as a dynamic argument.
141- // It panics if the check fails. Usually it should not be used, it's just for refactoring purpose
142- // deprecated
143- func CmdArgCheck ( s string ) CmdArg {
144- if s != "" && s [ 0 ] == '-' {
145- panic ( "invalid git cmd argument: " + s )
173+ // ToTrustedCmdArgs converts a list of strings (trusted as argument) to TrustedCmdArgs
174+ // In most cases, it shouldn't be used. Use AddXxx function instead
175+ func ToTrustedCmdArgs ( args [] string ) TrustedCmdArgs {
176+ ret := make ( TrustedCmdArgs , len ( args ))
177+ for i , arg := range args {
178+ ret [ i ] = internal . CmdArg ( arg )
146179 }
147- return CmdArg ( s )
180+ return ret
148181}
149182
150183// RunOpts represents parameters to run the command. If UseContextTimeout is specified, then Timeout is ignored.
@@ -364,9 +397,9 @@ func (c *Command) RunStdBytes(opts *RunOpts) (stdout, stderr []byte, runErr RunS
364397}
365398
366399// AllowLFSFiltersArgs return globalCommandArgs with lfs filter, it should only be used for tests
367- func AllowLFSFiltersArgs () [] CmdArg {
400+ func AllowLFSFiltersArgs () TrustedCmdArgs {
368401 // Now here we should explicitly allow lfs filters to run
369- filteredLFSGlobalArgs := make ([] CmdArg , len (globalCommandArgs ))
402+ filteredLFSGlobalArgs := make (TrustedCmdArgs , len (globalCommandArgs ))
370403 j := 0
371404 for _ , arg := range globalCommandArgs {
372405 if strings .Contains (string (arg ), "lfs" ) {
0 commit comments