@@ -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,62 @@ 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
+ 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 {
109
116
for _ , arg := range args {
110
117
c .args = append (c .args , string (arg ))
111
118
}
112
119
return c
113
120
}
114
121
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.
117
150
func (c * Command ) AddDynamicArguments (args ... string ) * Command {
118
151
for _ , arg := range args {
119
- if arg != "" && arg [ 0 ] == '-' {
152
+ if ! isSafeDynArg ( arg ) {
120
153
c .brokenArgs = append (c .brokenArgs , arg )
121
154
}
122
155
}
@@ -137,14 +170,14 @@ func (c *Command) AddDashesAndList(list ...string) *Command {
137
170
return c
138
171
}
139
172
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 )
146
179
}
147
- return CmdArg ( s )
180
+ return ret
148
181
}
149
182
150
183
// 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
364
397
}
365
398
366
399
// AllowLFSFiltersArgs return globalCommandArgs with lfs filter, it should only be used for tests
367
- func AllowLFSFiltersArgs () [] CmdArg {
400
+ func AllowLFSFiltersArgs () TrustedCmdArgs {
368
401
// Now here we should explicitly allow lfs filters to run
369
- filteredLFSGlobalArgs := make ([] CmdArg , len (globalCommandArgs ))
402
+ filteredLFSGlobalArgs := make (TrustedCmdArgs , len (globalCommandArgs ))
370
403
j := 0
371
404
for _ , arg := range globalCommandArgs {
372
405
if strings .Contains (string (arg ), "lfs" ) {
0 commit comments