From 290008c4a9f9c8ea2f769c522efa1b87c48ff481 Mon Sep 17 00:00:00 2001 From: "Du, Chengbin" Date: Sun, 25 Dec 2022 16:08:45 +0800 Subject: [PATCH] fix httpie args --- Makefile | 4 +-- connector/connector.go | 4 +-- connector/httpie.go | 5 +++ connector/httpie_test.go | 70 +++++++++++++++++++++++++++------------- curl/cmdline.go | 5 ++- curl/curl.go | 6 ++-- httpie/cmdline.go | 57 +++++++------------------------- httpie/cmdline_test.go | 2 +- httpie/flag.go | 10 +++--- httpie/item.go | 4 ++- main.go | 3 +- shellwords/quote.go | 23 +++++++++++++ 12 files changed, 110 insertions(+), 83 deletions(-) create mode 100644 shellwords/quote.go diff --git a/Makefile b/Makefile index 30dc1da..aec24b5 100644 --- a/Makefile +++ b/Makefile @@ -39,8 +39,8 @@ initGithooks: clean: @rm -rf $(BUILD_DIR) - @rm -f curl2httpie - @rm -f artifacts + @rm -rf curl2httpie + @rm -rf artifacts @mkdir artifacts test: diff --git a/connector/connector.go b/connector/connector.go index 2e28a8e..f5bf4f5 100644 --- a/connector/connector.go +++ b/connector/connector.go @@ -21,8 +21,8 @@ func Convert(args []string) (cmdStringer fmt.Stringer, warningMessages []Warning switch args[0] { case "curl": return Curl2Httpie(args[1:]) - case "http": - return Httpie2Curl(args[1:]) + case "http", "https": + return Httpie2Curl(args) } err = ErrUnknownCommandType diff --git a/connector/httpie.go b/connector/httpie.go index d370abf..51172a8 100644 --- a/connector/httpie.go +++ b/connector/httpie.go @@ -115,6 +115,11 @@ func Httpie2Curl(args []string) (cmdStringer fmt.Stringer, warningMessages []War } } + if httpieInstance.IsHttps && + !strings.HasPrefix(curlCmdLine.URL, "https://") && + !strings.HasPrefix(curlCmdLine.URL, "http://") { + curlCmdLine.URL = "https://" + curlCmdLine.URL + } cmdStringer = curlCmdLine.NewStringer(true) return diff --git a/connector/httpie_test.go b/connector/httpie_test.go index 7df46dd..7a4d867 100644 --- a/connector/httpie_test.go +++ b/connector/httpie_test.go @@ -9,83 +9,107 @@ func TestHttpie2Curl(t *testing.T) { }{ { []string{"http", ":/foo"}, - `curl 'localhost/foo'`, + `curl localhost/foo`, }, { []string{"http", ":3000/bar"}, - `curl 'localhost:3000/bar'`, + `curl localhost:3000/bar`, }, { []string{"http", ":"}, - `curl 'localhost/'`, + `curl localhost/`, }, { []string{"http", "example.org", "id==1"}, - `curl 'example.org?id=1'`, + `curl example.org?id=1`, }, { []string{"http", "--auth", "username", "example.org", "id==1"}, - `curl --user 'username' 'example.org?id=1'`, + `curl --user username example.org?id=1`, }, { []string{"http", "--auth", "username", "example.org", "id==1", "foo:bar"}, - `curl --user 'username' --header 'foo: bar' 'example.org?id=1'`, + `curl --user username --header 'foo: bar' example.org?id=1`, }, { []string{"http", "--form", "--auth", "username", "example.org", "id==1", "foo:bar", "foo=bar"}, - `curl --user 'username' --header 'foo: bar' --data 'foo=bar' 'example.org?id=1'`, + `curl --user username --header 'foo: bar' --data foo=bar example.org?id=1`, }, { []string{"http", "--auth", "username", "example.org", "id==1", "foo:bar", "foo=bar"}, - `curl --user 'username' --header 'foo: bar' --header 'Content-Type: application/json' --data '{"foo":"bar"}' 'example.org?id=1'`, + `curl --user username --header 'foo: bar' --header 'Content-Type: application/json' --data '{"foo":"bar"}' example.org?id=1`, }, { []string{"http", "-f", "--auth", "username", "example.org", "id==1", "foo:bar", "foo=bar", "file@test_obj.json"}, - `curl --user 'username' --header 'foo: bar' --form 'file=@"test_obj.json"' --data 'foo=bar' 'example.org?id=1'`, + `curl --user username --header 'foo: bar' --form 'file=@"test_obj.json"' --data foo=bar example.org?id=1`, }, { []string{"http", "--auth", "username", "example.org", "id==1", "foo:bar", "foo=bar", `a:={"foo": "bar"}`}, - `curl --user 'username' --header 'foo: bar' --header 'Content-Type: application/json' --data '{"a":{"foo":"bar"},"foo":"bar"}' 'example.org?id=1'`, + `curl --user username --header 'foo: bar' --header 'Content-Type: application/json' --data '{"a":{"foo":"bar"},"foo":"bar"}' example.org?id=1`, }, { []string{"http", "--auth", "username", "POST", "example.org", "id==1", "foo:bar", "foo=bar", `a:={"foo": "bar"}`}, - `curl --request 'POST' --user 'username' --header 'foo: bar' --header 'Content-Type: application/json' --data '{"a":{"foo":"bar"},"foo":"bar"}' 'example.org?id=1'`, + `curl --request POST --user username --header 'foo: bar' --header 'Content-Type: application/json' --data '{"a":{"foo":"bar"},"foo":"bar"}' example.org?id=1`, }, { []string{"http", "PUT", "z.cn"}, - `curl --request 'PUT' 'z.cn'`, + `curl --request PUT z.cn`, }, { []string{"http", "z.cn"}, - "curl 'z.cn'", + "curl z.cn", }, { []string{"http", "--auth", "username", "--auth-type", "basic", "example.org", "id==1"}, - `curl --user 'username' --basic 'example.org?id=1'`, + `curl --user username --basic example.org?id=1`, }, { []string{"http", "--auth", "username", "--auth-type", "digest", "example.org", "id==1"}, - `curl --user 'username' --digest 'example.org?id=1'`, + `curl --user username --digest example.org?id=1`, }, { []string{"http", "--auth", "username", "--auth-type", "digest", "--proxy", "http:http://foo.bar:3128", "example.org", "id==1"}, - `curl --user 'username' --digest --proxy 'http:http://foo.bar:3128' 'example.org?id=1'`, + `curl --user username --digest --proxy http:http://foo.bar:3128 example.org?id=1`, }, { []string{"http", "--auth", "username", "--auth-type", "digest", "--proxy", "http:http://foo.bar:3128", "example.org", "id==1"}, - `curl --user 'username' --digest --proxy 'http:http://foo.bar:3128' 'example.org?id=1'`, + `curl --user username --digest --proxy http:http://foo.bar:3128 example.org?id=1`, }, { []string{"http", "--auth", "username", "--auth-type", "digest", "--proxy", "http:http://foo.bar:3128", "--follow", "example.org", "id==1"}, - `curl --user 'username' --digest --proxy 'http:http://foo.bar:3128' --location 'example.org?id=1'`, + `curl --user username --digest --proxy http:http://foo.bar:3128 --location example.org?id=1`, }, { []string{"http", "--auth", "username", "--auth-type", "digest", "--proxy", "http:http://foo.bar:3128", "--follow", "--max-redirects", "10", "example.org", "id==1"}, - `curl --user 'username' --digest --proxy 'http:http://foo.bar:3128' --location --max-redirs '10' 'example.org?id=1'`, + `curl --user username --digest --proxy http:http://foo.bar:3128 --location --max-redirs 10 example.org?id=1`, }, { []string{"http", "--auth", "username", "--auth-type", "digest", "--proxy", "http:http://foo.bar:3128", "--follow", "--max-redirects", "10", "--timeout", "30", "example.org", "id==1"}, - `curl --user 'username' --digest --proxy 'http:http://foo.bar:3128' --location --max-redirs '10' --max-time '30' 'example.org?id=1'`, + `curl --user username --digest --proxy http:http://foo.bar:3128 --location --max-redirs 10 --max-time 30 example.org?id=1`, + }, + { + []string{"https", "--auth", "username", "--auth-type", "digest", "--proxy", "http:http://foo.bar:3128", "--follow", "--max-redirects", "10", "--timeout", "30", "example.org", "id==1"}, + `curl --user username --digest --proxy http:http://foo.bar:3128 --location --max-redirs 10 --max-time 30 https://example.org?id=1`, + }, + { + []string{"https", "pie.dev"}, + `curl https://pie.dev`, + }, + { + []string{"http", "pie.dev"}, + `curl pie.dev`, + }, + { + []string{"https", "pie.dev", "key==mykey", "secret==mysecret"}, + `curl 'https://pie.dev?key=mykey&secret=mysecret'`, + }, + { + []string{"http", "-a", "username:password", "pie.dev"}, + `curl --user username:password pie.dev`, + }, + { + []string{"http", "pie.dev", "-a", "username:password"}, + `curl --user username:password pie.dev`, }, } @@ -94,13 +118,13 @@ func TestHttpie2Curl(t *testing.T) { // want string // }{ // { - // []string{"http", "--auth", "username", "example.org", "id==1", "foo:bar", "foo=bar", `a:={"foo": "bar"}`}, - // `curl --user 'username' --header 'foo: bar' --header 'Content-Type: application/json' --data '{"a":{"foo":"bar"},"foo":"bar"}' 'example.org?id=1'`, + // []string{"http", "pie.dev", "-a", "username:password"}, + // `curl --user username:password pie.dev`, // }, // } for _, c := range cases { - gotStringer, warningMessages, err := Httpie2Curl(c.in[1:]) + gotStringer, warningMessages, err := Httpie2Curl(c.in) if len(warningMessages) > 0 { t.Logf("Httpie2Curl warning messages: %#v in: %#v", warningMessages, c.in) } diff --git a/curl/cmdline.go b/curl/cmdline.go index f84b7dc..4f25b6f 100644 --- a/curl/cmdline.go +++ b/curl/cmdline.go @@ -3,6 +3,8 @@ package curl import ( "fmt" "strings" + + "github.com/dcb9/curl2httpie/shellwords" ) type CmdLine struct { @@ -33,7 +35,8 @@ func (cmdlineStringer *CmdLineStringer) String() string { if len(cmdlineStringer.Options) > 0 { parts = append(parts, strings.Join(options, " ")) } - parts = append(parts, fmt.Sprintf("'%s'", cmdlineStringer.URL)) + + parts = append(parts, shellwords.AddQuoteIfNeeded(cmdlineStringer.URL)) return strings.Join(parts, " ") } diff --git a/curl/curl.go b/curl/curl.go index 98196b1..58e4630 100644 --- a/curl/curl.go +++ b/curl/curl.go @@ -4,6 +4,8 @@ import ( "fmt" "io/ioutil" "strings" + + "github.com/dcb9/curl2httpie/shellwords" ) type Tag string @@ -32,10 +34,10 @@ func (o *Option) String(useLongName bool) string { if o.HasArg { if useLongName { - return fmt.Sprintf(`--%s '%s'`, o.Long, arg) + return fmt.Sprintf(`--%s %s`, o.Long, shellwords.AddQuoteIfNeeded(arg)) } - return fmt.Sprintf(`-%s '%s'`, string(o.Short), arg) + return fmt.Sprintf(`-%s %s`, string(o.Short), shellwords.AddQuoteIfNeeded(arg)) } if useLongName { diff --git a/httpie/cmdline.go b/httpie/cmdline.go index 75af7e8..8ab4a19 100644 --- a/httpie/cmdline.go +++ b/httpie/cmdline.go @@ -6,12 +6,15 @@ import ( "io/ioutil" "net/http" "strings" + + "github.com/dcb9/curl2httpie/shellwords" ) type CmdLine struct { Flags []*Flag Method *Method URL string + IsHttps bool Items []*Item HasBody bool ContentType string @@ -34,23 +37,6 @@ func (cl *CmdLine) AddItem(i *Item) { cl.Items = append(cl.Items, i) } -func needQuote(s string) bool { - return -1 != strings.IndexFunc(s, func(r rune) bool { - switch r { - case '&', '@', '#', '[', ']', '{', '}', ' ', '(', ')', '*': - return true - } - return false - }) -} - -func addQuoteIfNeeded(s string) string { - if needQuote(s) { - return fmt.Sprintf("'%s'", s) - } - return fmt.Sprintf("%s", s) -} - func (cl *CmdLine) String() string { // slice s := make([]string, 0, len(cl.Flags)+len(cl.Items)+3) // http method url @@ -86,7 +72,7 @@ func (cl *CmdLine) String() string { s = append(s, "--"+cl.ContentType) } - s = append(s, cl.Method.String(), addQuoteIfNeeded(cl.URL)) + s = append(s, cl.Method.String(), shellwords.AddQuoteIfNeeded(cl.URL)) for _, v := range cl.Items { s = append(s, v.String()) @@ -114,51 +100,30 @@ func NewCmdLine() *CmdLine { func NewCmdLineByArgs(args []string) (*CmdLine, error) { cmdLine := NewCmdLine() + if args[0] == "https" { + cmdLine.IsHttps = true + } + args = args[1:] if len(args) == 1 { cmdLine.URL = args[0] return cmdLine, nil } var err error - cmdLine.Flags, err = getFlagsByArgs(args) + var pureArgs []string + cmdLine.Flags, pureArgs, err = removeFlags(args) if err != nil { return nil, fmt.Errorf("NewCmdLineByArgs: %w", err) } - cmdLine.Method, cmdLine.URL, cmdLine.Items, err = getMethodURLAndItems(args) + cmdLine.Method, cmdLine.URL, cmdLine.Items, err = getMethodURLAndItems(pureArgs) return cmdLine, nil } func getMethodURLAndItems(args []string) (method *Method, url string, items []*Item, err error) { method = NewMethod("") - var lastFlagIndex int - foundFlag := false - for i := len(args) - 1; i >= 0; i-- { - if strings.HasPrefix(args[i], "-") { - lastFlagIndex = i - foundFlag = true - break - } - } - possibleMethodIndex := 0 - if foundFlag { - var flags []*Flag - flags, err = getFlagsByArgs(args[lastFlagIndex:]) - if err != nil { - return - } - if len(flags) < 1 { - err = fmt.Errorf("invalid flags") - return - } - if flags[0].HasArg { - possibleMethodIndex = lastFlagIndex + 2 - } else { - possibleMethodIndex = lastFlagIndex + 1 - } - } urlIndex := possibleMethodIndex possibleMethod := strings.ToUpper(args[possibleMethodIndex]) diff --git a/httpie/cmdline_test.go b/httpie/cmdline_test.go index 722eb15..eb08991 100644 --- a/httpie/cmdline_test.go +++ b/httpie/cmdline_test.go @@ -129,7 +129,7 @@ func TestNewCmdLineByArgs(t *testing.T) { } for _, c := range cases { - got, err := NewCmdLineByArgs(c.in[1:]) + got, err := NewCmdLineByArgs(c.in) if err != nil { t.Fatalf("NewCmdLineByArgs error: %s in: %#v", err.Error(), c.in) } diff --git a/httpie/flag.go b/httpie/flag.go index fc7a713..c2e1933 100644 --- a/httpie/flag.go +++ b/httpie/flag.go @@ -3,6 +3,7 @@ package httpie import ( "fmt" + "github.com/dcb9/curl2httpie/shellwords" flag "github.com/spf13/pflag" ) @@ -30,7 +31,7 @@ func (f *Flag) String() string { if f.Separator == "" { f.Separator = " " // Use whitespace as default separator } - arg = fmt.Sprintf(`%s%s`, f.Separator, addQuoteIfNeeded(f.Arg)) + arg = fmt.Sprintf(`%s%s`, f.Separator, shellwords.AddQuoteIfNeeded(f.Arg)) } return fmt.Sprintf("--%s%s", f.Long, arg) @@ -119,7 +120,7 @@ var AllFlags = []*Flag{ CertKeyFlag, } -func getFlagsByArgs(args []string) ([]*Flag, error) { +func removeFlags(args []string) ([]*Flag, []string, error) { CommandLine := flag.NewFlagSet("httpie", flag.ContinueOnError) boolValues := make([]*bool, len(AllFlags)) stringValues := make([]*string, len(AllFlags)) @@ -140,7 +141,7 @@ func getFlagsByArgs(args []string) ([]*Flag, error) { } err := CommandLine.Parse(args) if err != nil { - return nil, fmt.Errorf("GetFlagsByArgs: %w", err) + return nil, nil, fmt.Errorf("GetFlagsByArgs: %w", err) } flags := make([]*Flag, 0, len(args)) for i, f := range AllFlags { @@ -155,5 +156,6 @@ func getFlagsByArgs(args []string) ([]*Flag, error) { } } } - return flags, nil + + return flags, CommandLine.Args(), nil } diff --git a/httpie/item.go b/httpie/item.go index 80708fca2..adea56c 100644 --- a/httpie/item.go +++ b/httpie/item.go @@ -2,6 +2,8 @@ package httpie import ( "fmt" + + "github.com/dcb9/curl2httpie/shellwords" ) const ( @@ -19,7 +21,7 @@ type Item struct { } func (i *Item) String() string { - if needQuote(i.V) { + if shellwords.NeedQuote(i.V) { return fmt.Sprintf(`'%s%s%s'`, i.K, i.S, i.V) } return fmt.Sprintf(`%s%s%s`, i.K, i.S, i.V) diff --git a/main.go b/main.go index e4257bc..8ac51a9 100644 --- a/main.go +++ b/main.go @@ -2,10 +2,11 @@ package main import ( "fmt" - "github.com/dcb9/curl2httpie/constant" "log" "os" + "github.com/dcb9/curl2httpie/constant" + "github.com/dcb9/curl2httpie/connector" ) diff --git a/shellwords/quote.go b/shellwords/quote.go new file mode 100644 index 0000000..38ec722 --- /dev/null +++ b/shellwords/quote.go @@ -0,0 +1,23 @@ +package shellwords + +import ( + "fmt" + "strings" +) + +func NeedQuote(s string) bool { + return -1 != strings.IndexFunc(s, func(r rune) bool { + switch r { + case '&', '@', '#', '[', ']', '{', '}', ' ', '(', ')', '*': + return true + } + return false + }) +} + +func AddQuoteIfNeeded(s string) string { + if NeedQuote(s) { + return fmt.Sprintf("'%s'", s) + } + return fmt.Sprintf("%s", s) +}