Skip to content

Commit

Permalink
first commit
Browse files Browse the repository at this point in the history
  • Loading branch information
killlowkey committed Nov 6, 2021
0 parents commit 8bbb60f
Show file tree
Hide file tree
Showing 7 changed files with 376 additions and 0 deletions.
19 changes: 19 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
### Go template
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib

# Test binary, built with `go test -c`
*.test

# Output of the go coverage tool, specifically when used with LiteIDE
*.out

# Dependency directories (remove the comment below to include it)
# vendor/

.vscode
.idea
33 changes: 33 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# parse-curl
解析 curl 命令,返回一个表示请求的对象

## example
```shell
curl 'http://google.com/' \
-H 'Accept-Encoding: gzip, deflate, sdch' \
-H 'Accept-Language: en-US,en;q=0.8,da;q=0.6' \
-H 'Upgrade-Insecure-Requests: 1' \
-H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.110 Safari/537.36' \
-H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8' \
-H 'Connection: keep-alive' \
--compressed
```
out
```json
{
"method": "GET",
"url": "http://google.com/",
"header": {
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
"Accept-Encoding": "gzip, deflate, sdch",
"Accept-Language": "en-US,en;q=0.8,da;q=0.6",
"Connection": "keep-alive",
"Upgrade-Insecure-Requests": "1",
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.110 Safari/537.36"
},
"body": ""
}
```

## reference
* https://github.com/tj/parse-curl.js
11 changes: 11 additions & 0 deletions examples/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package main

import (
"fmt"
parseCurl "parse-curl"
)

func main() {
request, _ := parseCurl.Parse("curl 'http://google.com/' \\\n -H 'Accept-Encoding: gzip, deflate, sdch' \\\n -H 'Accept-Language: en-US,en;q=0.8,da;q=0.6' \\\n -H 'Upgrade-Insecure-Requests: 1' \\\n -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.110 Safari/537.36' \\\n -H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8' \\\n -H 'Connection: keep-alive' \\\n --compressed\n")
fmt.Println(request.ToJson(true))
}
10 changes: 10 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
module parse-curl

go 1.16

require (
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect
github.com/kr/pretty v0.3.0 // indirect
github.com/mattn/go-shellwords v1.0.12 // indirect
github.com/rogpeppe/go-internal v1.8.0 // indirect
)
18 changes: 18 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk=
github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
197 changes: 197 additions & 0 deletions parse.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
package parse_curl

import (
"bytes"
"encoding/base64"
"encoding/json"
"github.com/mattn/go-shellwords"
"strings"
)

type Header map[string]string

type Request struct {
Method string `json:"method"`
Url string `json:"url"`
Header Header `json:"header"`
Body string `json:"body"`
}

func (r *Request) ToJson(format bool) string {
buffer := &bytes.Buffer{}
encoder := json.NewEncoder(buffer)
encoder.SetEscapeHTML(false)

if format {
encoder.SetIndent("", " ")
}
_ = encoder.Encode(r)

return string(buffer.Bytes())
}

func Parse(curl string) (*Request, bool) {
if strings.Index(curl, "curl ") != 0 {
return nil, false
}

// https://github.com/mattn/go-shellwords
// https://github.com/tj/parse-curl.js
args, err := shellwords.Parse(curl)
if err != nil {
return nil, false
}

args = rewrite(args)
request := &Request{Method: "GET", Header: Header{}}
state := ""

for _, arg := range args {
switch true {
case isUrl(arg):
request.Url = arg
break

case arg == "-A" || arg == "--user-agent":
state = "user-agent"
break

case arg == "-H" || arg == "--header":
state = "header"
break

case arg == "-d" || arg == "--data" || arg == "--data-ascii" || arg == "--data-raw":
state = "data"
break

case arg == "-u" || arg == "--user":
state = "user"
break

case arg == "-I" || arg == "--head":
request.Method = "HEAD"
break

case arg == "-X" || arg == "--request":
state = "method"
break

case arg == "-b" || arg == "--cookie":
state = "cookie"
break

case len(arg) > 0:
switch state {
case "header":
fields := parseField(arg)
request.Header[fields[0]] = strings.TrimSpace(fields[1])
state = ""
break

case "user-agent":
request.Header["User-Agent"] = arg
state = ""
break

case "data":
if request.Method == "GET" || request.Method == "HEAD" {
request.Method = "POST"
}

if !hasContentType(*request) {
request.Header["Content-Type"] = "application/x-www-form-urlencoded"
}

if len(request.Body) == 0 {
request.Body = arg
} else {
request.Body = request.Body + "&" + arg
}

state = ""
break

case "user":
request.Header["Authorization"] = "Basic " +
base64.StdEncoding.EncodeToString([]byte(arg))
state = ""
break

case "method":
request.Method = arg
state = ""
break

case "cookie":
request.Header["Cookie"] = arg
state = ""
break

default:
break
}
}

}

// format json body
if value, ok := request.Header["Content-Type"]; ok && value == "application/json" {
decoder := json.NewDecoder(strings.NewReader(request.Body))
jsonData := make(map[string]interface{})
if err := decoder.Decode(&jsonData); err == nil {
buffer := &bytes.Buffer{}
encoder := json.NewEncoder(buffer)
encoder.SetEscapeHTML(false)
if err = encoder.Encode(jsonData); err == nil {
request.Body = strings.ReplaceAll(buffer.String(), "\n", "")
}
}
}

return request, true
}

func rewrite(args []string) []string {
res := make([]string, 0)

for _, arg := range args {

arg = strings.TrimSpace(arg)

if arg == "\n" {
continue
}

if strings.Contains(arg, "\n") {
arg = strings.ReplaceAll(arg, "\n", "")
}

// split request method
if strings.Index(arg, "-X") == 0 {
res = append(res, arg[0:2])
res = append(res, arg[2:])
} else {
res = append(res, arg)
}
}

return res
}

func isUrl(url string) bool {
return strings.HasPrefix(url, "http://") ||
strings.HasPrefix(url, "https://")
}

func parseField(arg string) []string {
index := strings.Index(arg, ":")
return []string{arg[0:index], arg[index+2:]}
}

func hasContentType(request Request) bool {
if _, ok := request.Header["Content-Type"]; ok {
return true
}

return false
}
88 changes: 88 additions & 0 deletions parse_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package parse_curl

import (
"github.com/bmizerany/assert"
"testing"
)

type M map[string]interface{}

func TestParse(t *testing.T) {

addSample(t, "curl -XPUT http://api.sloths.com/sloth/4", M{
"method": "PUT",
"url": "http://api.sloths.com/sloth/4",
})

addSample(t, "curl http://api.sloths.com", M{
"method": "GET",
"url": "http://api.sloths.com",
})

addSample(t, "curl -H \"Accept-Encoding: gzip\" --compressed http://api.sloths.com", M{
"method": "GET",
"url": "http://api.sloths.com",
"header": M{
"Accept-Encoding": "gzip",
},
})

addSample(t, "curl -X DELETE http://api.sloths.com/sloth/4", M{
"method": "DELETE",
"url": "http://api.sloths.com/sloth/4",
})

addSample(t, "curl -d \"foo=bar\" https://api.sloths.com", M{
"method": "POST",
"url": "https://api.sloths.com",
"header": M{
"Content-Type": "application/x-www-form-urlencoded",
},
"body": "foo=bar",
})

addSample(t, "curl -H \"Accept: text/plain\" --header \"User-Agent: slothy\" https://api.sloths.com", M{
"method": "GET",
"url": "https://api.sloths.com",
"header": M{
"Accept": "text/plain",
"User-Agent": "slothy",
},
})

addSample(t, "curl --cookie 'species=sloth;type=galactic' slothy https://api.sloths.com", M{
"method": "GET",
"url": "https://api.sloths.com",
"header": M{
"Cookie": "species=sloth;type=galactic",
},
})

addSample(t, "curl --location --request GET 'http://api.sloths.com/users?token=admin'", M{
"method": "GET",
"url": "http://api.sloths.com/users?token=admin",
})
}

func addSample(t *testing.T, url string, exp M) {
request, _ := Parse(url)
check(t, exp, request)
}

func check(t *testing.T, exp M, got *Request) {
for key, value := range exp {
switch key {
case "method":
assert.Equal(t, value, got.Method)
case "url":
assert.Equal(t, value, got.Url)
case "body":
assert.Equal(t, value, got.Body)
case "header":
headers := value.(M)
for k, v := range headers {
assert.Equal(t, v, got.Header[k])
}
}
}
}

0 comments on commit 8bbb60f

Please sign in to comment.