@@ -16,14 +16,20 @@ import (
16
16
"time"
17
17
"unsafe"
18
18
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
19
20
"code.gitea.io/gitea/modules/log"
20
21
"code.gitea.io/gitea/modules/process"
21
22
"code.gitea.io/gitea/modules/util"
22
23
)
23
24
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
+
24
30
var (
25
31
// globalCommandArgs global command args for external package setting
26
- globalCommandArgs [] CmdArg
32
+ globalCommandArgs TrustedCmdArgs
27
33
28
34
// defaultCommandExecutionTimeout default command execution timeout duration
29
35
defaultCommandExecutionTimeout = 360 * time .Second
@@ -42,8 +48,6 @@ type Command struct {
42
48
brokenArgs []string
43
49
}
44
50
45
- type CmdArg string
46
-
47
51
func (c * Command ) String () string {
48
52
if len (c .args ) == 0 {
49
53
return c .name
@@ -53,7 +57,7 @@ func (c *Command) String() string {
53
57
54
58
// NewCommand creates and returns a new Git Command based on given command and arguments.
55
59
// 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 {
57
61
// Make an explicit copy of globalCommandArgs, otherwise append might overwrite it
58
62
cargs := make ([]string , 0 , len (globalCommandArgs )+ len (args ))
59
63
for _ , arg := range globalCommandArgs {
@@ -70,15 +74,9 @@ func NewCommand(ctx context.Context, args ...CmdArg) *Command {
70
74
}
71
75
}
72
76
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
-
79
77
// 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
80
78
// 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 {
82
80
cargs := make ([]string , 0 , len (args ))
83
81
for _ , arg := range args {
84
82
cargs = append (cargs , string (arg ))
@@ -96,27 +94,70 @@ func (c *Command) SetParentContext(ctx context.Context) *Command {
96
94
return c
97
95
}
98
96
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()
101
98
func (c * Command ) SetDescription (desc string ) * Command {
102
99
c .desc = desc
103
100
return c
104
101
}
105
102
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
+ // isSafeArgumentValue checks if the argument is safe to be used as a value (not an option)
104
+ func isSafeArgumentValue (s string ) bool {
105
+ return s == "" || s [0 ] != '-'
106
+ }
107
+
108
+ // isValidArgumentOption checks if the argument is a valid option (starting with '-').
109
+ // It doesn't check whether the option is supported or not
110
+ func isValidArgumentOption (s string ) bool {
111
+ return s != "" && s [0 ] == '-'
112
+ }
113
+
114
+ // AddArguments adds new git arguments (option/value) to the command. It only accepts string literals, or trusted CmdArg.
115
+ // Type CmdArg is in the internal package, so it can not be used outside of this package directly,
116
+ // it makes sure that user-provided arguments won't cause RCE risks.
117
+ // User-provided arguments should be passed by other AddXxx functions
118
+ func (c * Command ) AddArguments (args ... internal.CmdArg ) * Command {
109
119
for _ , arg := range args {
110
120
c .args = append (c .args , string (arg ))
111
121
}
112
122
return c
113
123
}
114
124
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
125
+ // AddOptionValues adds a new option with a list of non-option values
126
+ // For example: AddOptionValues("--opt", val) means 2 arguments: {"--opt", val}.
127
+ // The values are treated as dynamic argument values. It equals to: AddArguments("--opt") then AddDynamicArguments(val).
128
+ func (c * Command ) AddOptionValues (opt internal.CmdArg , args ... string ) * Command {
129
+ if ! isValidArgumentOption (string (opt )) {
130
+ c .brokenArgs = append (c .brokenArgs , string (opt ))
131
+ return c
132
+ }
133
+ c .args = append (c .args , string (opt ))
134
+ c .AddDynamicArguments (args ... )
135
+ return c
136
+ }
137
+
138
+ // AddOptionFormat adds a new option with a format string and arguments
139
+ // For example: AddOptionFormat("--opt=%s %s", val1, val2) means 1 argument: {"--opt=val1 val2"}.
140
+ func (c * Command ) AddOptionFormat (opt string , args ... any ) * Command {
141
+ if ! isValidArgumentOption (opt ) {
142
+ c .brokenArgs = append (c .brokenArgs , opt )
143
+ return c
144
+ }
145
+ // a quick check to make sure the format string matches the number of arguments, to find low-level mistakes ASAP
146
+ if strings .Count (strings .ReplaceAll (opt , "%%" , "" ), "%" ) != len (args ) {
147
+ c .brokenArgs = append (c .brokenArgs , opt )
148
+ return c
149
+ }
150
+ s := fmt .Sprintf (opt , args ... )
151
+ c .args = append (c .args , s )
152
+ return c
153
+ }
154
+
155
+ // AddDynamicArguments adds new dynamic argument values to the command.
156
+ // The arguments may come from user input and can not be trusted, so no leading '-' is allowed to avoid passing options.
157
+ // TODO: in the future, this function can be renamed to AddArgumentValues
117
158
func (c * Command ) AddDynamicArguments (args ... string ) * Command {
118
159
for _ , arg := range args {
119
- if arg != "" && arg [ 0 ] == '-' {
160
+ if ! isSafeArgumentValue ( arg ) {
120
161
c .brokenArgs = append (c .brokenArgs , arg )
121
162
}
122
163
}
@@ -137,14 +178,14 @@ func (c *Command) AddDashesAndList(list ...string) *Command {
137
178
return c
138
179
}
139
180
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 )
181
+ // ToTrustedCmdArgs converts a list of strings (trusted as argument) to TrustedCmdArgs
182
+ // In most cases, it shouldn't be used. Use AddXxx function instead
183
+ func ToTrustedCmdArgs ( args [] string ) TrustedCmdArgs {
184
+ ret := make ( TrustedCmdArgs , len ( args ))
185
+ for i , arg := range args {
186
+ ret [ i ] = internal . CmdArg ( arg )
146
187
}
147
- return CmdArg ( s )
188
+ return ret
148
189
}
149
190
150
191
// RunOpts represents parameters to run the command. If UseContextTimeout is specified, then Timeout is ignored.
@@ -364,9 +405,9 @@ func (c *Command) RunStdBytes(opts *RunOpts) (stdout, stderr []byte, runErr RunS
364
405
}
365
406
366
407
// AllowLFSFiltersArgs return globalCommandArgs with lfs filter, it should only be used for tests
367
- func AllowLFSFiltersArgs () [] CmdArg {
408
+ func AllowLFSFiltersArgs () TrustedCmdArgs {
368
409
// Now here we should explicitly allow lfs filters to run
369
- filteredLFSGlobalArgs := make ([] CmdArg , len (globalCommandArgs ))
410
+ filteredLFSGlobalArgs := make (TrustedCmdArgs , len (globalCommandArgs ))
370
411
j := 0
371
412
for _ , arg := range globalCommandArgs {
372
413
if strings .Contains (string (arg ), "lfs" ) {
0 commit comments