@@ -63,12 +63,15 @@ func (curlCmd *CurlCommand) Run() error {
6363 return errorutils .CheckErrorf ("Curl command must not include certificate flag (--cert or --key)." )
6464 }
6565
66- // Build the full URL from the API path (first non-flag argument) .
67- // Remove the API path and append the full URL at the end.
68- if err := curlCmd . buildAndAppendUrl (); err != nil {
66+ // Get target url for the curl command .
67+ uriIndex , targetUri , err := curlCmd . buildCommandUrl ( curlCmd . url )
68+ if err != nil {
6969 return err
7070 }
7171
72+ // Replace url argument with complete url.
73+ curlCmd .arguments [uriIndex ] = targetUri
74+
7275 cmdWithoutCreds := strings .Join (curlCmd .arguments , " " )
7376 // Add credentials to curl command.
7477 credentialsMessage := curlCmd .addCommandCredentials ()
@@ -103,81 +106,28 @@ func (curlCmd *CurlCommand) addCommandCredentials() string {
103106 return certificateHelpPrefix + "-u***:***"
104107}
105108
106- // buildAndAppendUrl finds the first non-flag argument (the API path), removes it,
107- // builds the full URL, and appends it at the end. This allows curl flags to appear in any order.
108- func (curlCmd * CurlCommand ) buildAndAppendUrl () error {
109- // Common curl flags that take a value in the next argument
110- flagsWithValues := map [string ]bool {
111- "-X" : true , "-H" : true , "-d" : true , "-o" : true , "-A" : true , "-e" : true ,
112- "-T" : true , "-b" : true , "-c" : true , "-F" : true , "-m" : true , "-w" : true ,
113- "-x" : true , "-y" : true , "-z" : true , "-C" : true , "-K" : true , "-E" : true ,
114- "--request" : true , "--header" : true , "--data" : true , "--output" : true ,
115- "--user-agent" : true , "--referer" : true , "--upload-file" : true ,
116- "--cookie" : true , "--cookie-jar" : true , "--form" : true , "--max-time" : true ,
117- "--write-out" : true , "--proxy" : true , "--cert" : true , "--key" : true ,
118- "--cacert" : true , "--capath" : true , "--connect-timeout" : true ,
119- "--retry" : true , "--retry-delay" : true , "--retry-max-time" : true ,
120- "--speed-limit" : true , "--speed-time" : true , "--limit-rate" : true ,
121- "--max-filesize" : true , "--max-redirs" : true , "--data-binary" : true ,
122- "--data-urlencode" : true , "--data-raw" : true , "--data-ascii" : true ,
109+ func (curlCmd * CurlCommand ) buildCommandUrl (url string ) (uriIndex int , uriValue string , err error ) {
110+ // Find command's URL argument.
111+ // Representing the target API for the Curl command.
112+ uriIndex , uriValue = curlCmd .findUriValueAndIndex ()
113+ if uriIndex == - 1 {
114+ err = errorutils .CheckErrorf ("Could not find argument in curl command." )
115+ return
123116 }
124117
125- // Find the first non-flag argument (the API path)
126- // Skip arguments that are values for flags
127- apiPathIndex := - 1
128- skipNext := false
129-
130- for i , arg := range curlCmd .arguments {
131- // Skip if this is a flag value
132- if skipNext {
133- skipNext = false
134- continue
135- }
136-
137- // Check if this is a flag
138- if strings .HasPrefix (arg , "-" ) {
139- // Check if it's a flag that takes a value (and value is not inline)
140- if flagsWithValues [arg ] {
141- skipNext = true
142- }
143- // Check for long flags with inline values like --header=value
144- if strings .Contains (arg , "=" ) {
145- skipNext = false
146- }
147- // For short flags, check if value is inline like -XGET
148- if len (arg ) > 2 && ! strings .HasPrefix (arg , "--" ) {
149- skipNext = false
150- }
151- continue
152- }
153-
154- // Found a non-flag argument that's not a flag value - this is the API path
155- apiPathIndex = i
156- break
157- }
158-
159- if apiPathIndex == - 1 {
160- return errorutils .CheckErrorf ("Could not find API path argument in curl command." )
161- }
162-
163- apiPath := curlCmd .arguments [apiPathIndex ]
164-
165118 // If user provided full-url, throw an error.
166- if strings .HasPrefix (apiPath , "http://" ) || strings .HasPrefix (apiPath , "https://" ) {
167- return errorutils .CheckErrorf ("Curl command must not include full-url, but only the REST API URI (e.g '/api/system/ping')." )
119+ if strings .HasPrefix (uriValue , "http://" ) || strings .HasPrefix (uriValue , "https://" ) {
120+ err = errorutils .CheckErrorf ("Curl command must not include full-url, but only the REST API URI (e.g '/api/system/ping')." )
121+ return
168122 }
169123
170- // Remove the API path from its current position
171- curlCmd .arguments = append (curlCmd .arguments [:apiPathIndex ], curlCmd .arguments [apiPathIndex + 1 :]... )
172-
173124 // Trim '/' prefix if exists.
174- apiPath = strings .TrimPrefix (apiPath , "/" )
125+ uriValue = strings .TrimPrefix (uriValue , "/" )
175126
176- // Build full URL and append at the end
177- fullUrl := curlCmd .url + apiPath
178- curlCmd .arguments = append (curlCmd .arguments , fullUrl )
127+ // Attach url to the api.
128+ uriValue = url + uriValue
179129
180- return nil
130+ return
181131}
182132
183133// Returns server details
@@ -191,6 +141,104 @@ func (curlCmd *CurlCommand) GetServerDetails() (*config.ServerDetails, error) {
191141 return config .GetSpecificConfig (serverIdValue , true , true )
192142}
193143
144+ // curlBooleanFlags contains curl flags that do NOT take a value.
145+ var curlBooleanFlags = map [string ]bool {
146+ "-#" : true , "-0" : true , "-1" : true , "-2" : true , "-3" : true , "-4" : true , "-6" : true ,
147+ "-a" : true , "-B" : true , "-f" : true , "-g" : true , "-G" : true , "-I" : true , "-i" : true ,
148+ "-j" : true , "-J" : true , "-k" : true , "-l" : true , "-L" : true , "-M" : true , "-n" : true ,
149+ "-N" : true , "-O" : true , "-p" : true , "-q" : true , "-R" : true , "-s" : true , "-S" : true ,
150+ "-v" : true , "-V" : true , "-Z" : true ,
151+ "--anyauth" : true , "--append" : true , "--basic" : true , "--ca-native" : true ,
152+ "--cert-status" : true , "--compressed" : true , "--compressed-ssh" : true ,
153+ "--create-dirs" : true , "--crlf" : true , "--digest" : true , "--disable" : true ,
154+ "--disable-eprt" : true , "--disable-epsv" : true , "--disallow-username-in-url" : true ,
155+ "--doh-cert-status" : true , "--doh-insecure" : true , "--fail" : true ,
156+ "--fail-early" : true , "--fail-with-body" : true , "--false-start" : true ,
157+ "--form-escape" : true , "--ftp-create-dirs" : true , "--ftp-pasv" : true ,
158+ "--ftp-pret" : true , "--ftp-skip-pasv-ip" : true , "--ftp-ssl-ccc" : true ,
159+ "--ftp-ssl-control" : true , "--get" : true , "--globoff" : true ,
160+ "--haproxy-protocol" : true , "--head" : true , "--http0.9" : true , "--http1.0" : true ,
161+ "--http1.1" : true , "--http2" : true , "--http2-prior-knowledge" : true ,
162+ "--http3" : true , "--http3-only" : true , "--ignore-content-length" : true ,
163+ "--include" : true , "--insecure" : true , "--ipv4" : true , "--ipv6" : true ,
164+ "--junk-session-cookies" : true , "--list-only" : true , "--location" : true ,
165+ "--location-trusted" : true , "--mail-rcpt-allowfails" : true , "--manual" : true ,
166+ "--metalink" : true , "--negotiate" : true , "--netrc" : true , "--netrc-optional" : true ,
167+ "--next" : true , "--no-alpn" : true , "--no-buffer" : true , "--no-clobber" : true ,
168+ "--no-keepalive" : true , "--no-npn" : true , "--no-progress-meter" : true ,
169+ "--no-sessionid" : true , "--ntlm" : true , "--ntlm-wb" : true , "--parallel" : true ,
170+ "--parallel-immediate" : true , "--path-as-is" : true , "--post301" : true ,
171+ "--post302" : true , "--post303" : true , "--progress-bar" : true ,
172+ "--proxy-anyauth" : true , "--proxy-basic" : true , "--proxy-ca-native" : true ,
173+ "--proxy-digest" : true , "--proxy-http2" : true , "--proxy-insecure" : true ,
174+ "--proxy-negotiate" : true , "--proxy-ntlm" : true , "--proxy-ssl-allow-beast" : true ,
175+ "--proxy-ssl-auto-client-cert" : true , "--proxy-tlsv1" : true , "--proxytunnel" : true ,
176+ "--raw" : true , "--remote-header-name" : true , "--remote-name" : true ,
177+ "--remote-name-all" : true , "--remote-time" : true , "--remove-on-error" : true ,
178+ "--retry-all-errors" : true , "--retry-connrefused" : true , "--sasl-ir" : true ,
179+ "--show-error" : true , "--silent" : true , "--socks5-basic" : true ,
180+ "--socks5-gssapi" : true , "--socks5-gssapi-nec" : true , "--ssl" : true ,
181+ "--ssl-allow-beast" : true , "--ssl-auto-client-cert" : true , "--ssl-no-revoke" : true ,
182+ "--ssl-reqd" : true , "--ssl-revoke-best-effort" : true , "--sslv2" : true ,
183+ "--sslv3" : true , "--styled-output" : true , "--suppress-connect-headers" : true ,
184+ "--tcp-fastopen" : true , "--tcp-nodelay" : true , "--tftp-no-options" : true ,
185+ "--tlsv1" : true , "--tlsv1.0" : true , "--tlsv1.1" : true , "--tlsv1.2" : true ,
186+ "--tlsv1.3" : true , "--tr-encoding" : true , "--trace-ids" : true ,
187+ "--trace-time" : true , "--use-ascii" : true , "--verbose" : true , "--version" : true ,
188+ "--xattr" : true ,
189+ }
190+
191+ // Find the URL argument in the Curl Command.
192+ // A command flag is prefixed by '-' or '--'.
193+ // Use this method ONLY after removing all JFrog-CLI flags, i.e. flags in the form: '--my-flag=value' are not allowed.
194+ // An argument is any provided candidate which is not a flag or a flag value.
195+ func (curlCmd * CurlCommand ) findUriValueAndIndex () (int , string ) {
196+ skipNextArg := false
197+ for index , arg := range curlCmd .arguments {
198+ // Check if this arg should be skipped (it's a value for the previous flag)
199+ if skipNextArg {
200+ skipNextArg = false
201+ continue
202+ }
203+
204+ // Check if this is a flag
205+ if strings .HasPrefix (arg , "-" ) {
206+ // Check for flags with inline values like --header=value or -XGET
207+ if strings .HasPrefix (arg , "--" ) && strings .Contains (arg , "=" ) {
208+ continue
209+ }
210+
211+ // Check if this is a known standalone flag (no value needed)
212+ if curlBooleanFlags [arg ] {
213+ continue
214+ }
215+
216+ // For short flags (not starting with --)
217+ if ! strings .HasPrefix (arg , "--" ) && len (arg ) > 2 {
218+ // Could be inline value (e.g., -XGET, -ofile.txt) or combined flags (e.g., -vvv, -sS)
219+ // Check if the base flag is standalone
220+ baseFlag := arg [:2 ]
221+ if curlBooleanFlags [baseFlag ] {
222+ // Combined standalone flags like -vvv or -sS, no skip needed
223+ continue
224+ }
225+ // Inline value like -XGET or -ofile.txt, no skip needed
226+ continue
227+ }
228+
229+ // Flag not in standalone list - it takes a value in the next argument
230+ skipNextArg = true
231+ continue
232+ }
233+
234+ // Found a non-flag argument - this is the URL/API path
235+ return index , arg
236+ }
237+
238+ // If reached here, didn't find an argument.
239+ return - 1 , ""
240+ }
241+
194242// Return true if the curl command includes credentials flag.
195243// The searched flags are not CLI flags.
196244func (curlCmd * CurlCommand ) isCredentialsFlagExists () bool {
0 commit comments