@@ -978,6 +978,158 @@ interface LiteralPathCandidates {
978978 opaque : boolean ;
979979}
980980
981+ const CURL_LONG_VALUE_OPTIONS = new Set (
982+ `--abstract-unix-socket --alt-svc --aws-sigv4 --cacert --capath --cert
983+ --cert-type --ciphers --config --connect-timeout --connect-to --continue-at
984+ --cookie --cookie-jar --create-file-mode --crlfile --curves --data
985+ --data-ascii --data-binary --data-raw --data-urlencode --delegation
986+ --dns-interface --dns-ipv4-addr --dns-ipv6-addr --dns-servers --doh-url
987+ --dump-header --egd-file --engine --etag-compare --etag-save
988+ --expect100-timeout --form --form-string --ftp-account
989+ --ftp-alternative-to-user --ftp-method --ftp-port --ftp-ssl-ccc-mode
990+ --happy-eyeballs-timeout-ms --haproxy-clientip --header --help
991+ --hostpubmd5 --hostpubsha256 --hsts --interface --ipfs-gateway --json
992+ --keepalive-time --key --key-type --krb --libcurl --limit-rate
993+ --local-port --login-options --mail-auth --mail-from --mail-rcpt
994+ --max-filesize --max-redirs --max-time --netrc-file --noproxy
995+ --oauth2-bearer --output --output-dir --parallel-max --pass --pinnedpubkey
996+ --proto --proto-default --proto-redir --proxy-cacert --proxy-capath
997+ --proxy-cert --proxy-cert-type --proxy-ciphers --proxy-crlfile
998+ --proxy-header --proxy-key --proxy-key-type --proxy-pass
999+ --proxy-pinnedpubkey --proxy-service-name --proxy-tls13-ciphers
1000+ --proxy-tlsauthtype --proxy-tlspassword --proxy-tlsuser --proxy-user
1001+ --proxy1.0 --pubkey --quote --random-file --range --rate --referer
1002+ --request --request-target --resolve --retry --retry-delay --retry-max-time
1003+ --sasl-authzid --service-name --socks4 --socks4a --socks5
1004+ --socks5-gssapi-service --socks5-hostname --speed-limit --speed-time
1005+ --stderr --telnet-option --tftp-blksize --time-cond --tls-max
1006+ --tls13-ciphers --tlsauthtype --tlspassword --tlsuser --trace
1007+ --trace-ascii --trace-config --unix-socket --upload-file --url
1008+ --url-query --user --user-agent --variable --write-out`
1009+ . split ( / \s + / )
1010+ . filter ( Boolean ) ,
1011+ ) ;
1012+ const CURL_SHORT_VALUE_OPTIONS = new Set (
1013+ "AbcCdDeEFhHKmoPQrtTuUwXyYz" . split ( "" ) ,
1014+ ) ;
1015+ const EXPLICIT_URL_SCHEME = / ^ [ A - Z a - z ] [ A - Z a - z 0 - 9 + . - ] * : / ;
1016+
1017+ interface CurlFileOperandInspection {
1018+ values : string [ ] ;
1019+ opaque : boolean ;
1020+ }
1021+
1022+ function normalizeCurlFileOperand (
1023+ operand : string ,
1024+ dialect : ShellDialect ,
1025+ ) : { value ?: string ; opaque : boolean } {
1026+ const expansion = expandShellToken ( operand , dialect ) ;
1027+ if ( ! expansion . expandable ) return { opaque : true } ;
1028+ let localPath = expansion . expanded ;
1029+ if ( localPath . startsWith ( "//" ) ) {
1030+ const localhost = / ^ \/ \/ l o c a l h o s t (? = \/ | $ ) / i. exec ( localPath ) ?. [ 0 ] ;
1031+ if ( localhost === undefined ) return { opaque : true } ;
1032+ localPath = localPath . slice ( localhost . length ) || "/" ;
1033+ }
1034+ try {
1035+ localPath = decodeURIComponent ( localPath ) ;
1036+ } catch {
1037+ return { opaque : true } ;
1038+ }
1039+ if ( / [ { } ] / . test ( localPath ) ) return { opaque : true } ;
1040+ return { value : localPath , opaque : false } ;
1041+ }
1042+
1043+ function curlFileOperands (
1044+ command : readonly string [ ] ,
1045+ executableIndex : number ,
1046+ program : string ,
1047+ dialect : ShellDialect ,
1048+ ) : CurlFileOperandInspection {
1049+ if ( program !== "curl" || dialect !== "posix" ) {
1050+ return { values : [ ] , opaque : false } ;
1051+ }
1052+
1053+ const operands : string [ ] = [ ] ;
1054+ const protocols : string [ ] = [ ] ;
1055+ let malformedProtocol = false ;
1056+ let optionsEnded = false ;
1057+
1058+ for ( let index = executableIndex + 1 ; index < command . length ; index ++ ) {
1059+ const token = command [ index ] ?? "" ;
1060+ if ( ! optionsEnded && token === "--" ) {
1061+ optionsEnded = true ;
1062+ continue ;
1063+ }
1064+ if ( ! optionsEnded && token === "--proto-default" ) {
1065+ const protocol = command [ index + 1 ] ;
1066+ if ( protocol === undefined || protocol . startsWith ( "-" ) ) {
1067+ malformedProtocol = true ;
1068+ } else {
1069+ protocols . push ( protocol . toLowerCase ( ) ) ;
1070+ index ++ ;
1071+ }
1072+ continue ;
1073+ }
1074+ if ( ! optionsEnded && token . startsWith ( "--proto-default=" ) ) {
1075+ const protocol = token . slice ( "--proto-default=" . length ) ;
1076+ if ( protocol . length === 0 ) malformedProtocol = true ;
1077+ else protocols . push ( protocol . toLowerCase ( ) ) ;
1078+ continue ;
1079+ }
1080+ if ( ! optionsEnded && token === "--url" ) {
1081+ const operand = command [ index + 1 ] ;
1082+ if ( operand === undefined ) malformedProtocol = true ;
1083+ else {
1084+ operands . push ( operand ) ;
1085+ index ++ ;
1086+ }
1087+ continue ;
1088+ }
1089+ if ( ! optionsEnded && token . startsWith ( "--url=" ) ) {
1090+ operands . push ( token . slice ( "--url=" . length ) ) ;
1091+ continue ;
1092+ }
1093+ if ( ! optionsEnded && token . startsWith ( "--" ) ) {
1094+ const equalsIndex = token . indexOf ( "=" ) ;
1095+ const option = equalsIndex === - 1 ? token : token . slice ( 0 , equalsIndex ) ;
1096+ if ( equalsIndex === - 1 && CURL_LONG_VALUE_OPTIONS . has ( option ) ) index ++ ;
1097+ continue ;
1098+ }
1099+ if ( ! optionsEnded && token . startsWith ( "-" ) && token !== "-" ) {
1100+ const options = token . slice ( 1 ) ;
1101+ for ( let optionIndex = 0 ; optionIndex < options . length ; optionIndex ++ ) {
1102+ if ( ! CURL_SHORT_VALUE_OPTIONS . has ( options [ optionIndex ] ?? "" ) ) continue ;
1103+ if ( optionIndex === options . length - 1 ) index ++ ;
1104+ break ;
1105+ }
1106+ continue ;
1107+ }
1108+ operands . push ( token ) ;
1109+ }
1110+
1111+ const protocolSet = new Set ( protocols ) ;
1112+ const fileCapable = protocolSet . has ( "file" ) ;
1113+ let opaque =
1114+ operands . length > 0 &&
1115+ ( malformedProtocol || ( fileCapable && protocolSet . size > 1 ) ) ;
1116+ if ( ! fileCapable ) return { values : [ ] , opaque } ;
1117+
1118+ const values : string [ ] = [ ] ;
1119+ for ( const operand of operands ) {
1120+ const expansion = expandShellToken ( operand , dialect ) ;
1121+ if ( ! expansion . expandable ) {
1122+ opaque = true ;
1123+ continue ;
1124+ }
1125+ if ( EXPLICIT_URL_SCHEME . test ( expansion . expanded ) ) continue ;
1126+ const normalized = normalizeCurlFileOperand ( operand , dialect ) ;
1127+ opaque ||= normalized . opaque ;
1128+ if ( normalized . value !== undefined ) values . push ( normalized . value ) ;
1129+ }
1130+ return { values, opaque } ;
1131+ }
1132+
9811133function literalPathCandidates (
9821134 commands : string [ ] [ ] ,
9831135 dialect : ShellDialect ,
@@ -1002,6 +1154,14 @@ function literalPathCandidates(
10021154 ) ;
10031155 candidates . push ( ...fileOptions . values ) ;
10041156 opaque ||= fileOptions . opaque ;
1157+ const curlOperands = curlFileOperands (
1158+ command ,
1159+ transparent . executableIndex ,
1160+ program ,
1161+ dialect ,
1162+ ) ;
1163+ candidates . push ( ...curlOperands . values ) ;
1164+ opaque ||= curlOperands . opaque ;
10051165 for ( const token of command ) {
10061166 if ( token . startsWith ( "--env-file=" ) ) {
10071167 candidates . push ( token . slice ( "--env-file=" . length ) ) ;
0 commit comments