342 lines
8.9 KiB
Go
342 lines
8.9 KiB
Go
package curl2info
|
|
|
|
import (
|
|
"fmt"
|
|
"io/ioutil"
|
|
"log"
|
|
"net/http"
|
|
"net/http/cookiejar"
|
|
"net/url"
|
|
"os"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"474420502.top/eson/requests"
|
|
)
|
|
|
|
// CURL 信息结构
|
|
type CURL struct {
|
|
ParsedURL *url.URL
|
|
Method string
|
|
Header http.Header
|
|
CookieJar http.CookieJar
|
|
Cookies []*http.Cookie
|
|
Body *requests.Body
|
|
Auth *requests.BasicAuth
|
|
Timeout int // second
|
|
Insecure bool
|
|
|
|
CallBack string
|
|
}
|
|
|
|
// NewCURL new 一个 curl 出来
|
|
func NewCURL() *CURL {
|
|
|
|
u := &CURL{}
|
|
u.Insecure = false
|
|
|
|
u.Header = make(http.Header)
|
|
u.CookieJar, _ = cookiejar.New(nil)
|
|
u.Body = requests.NewBody()
|
|
u.Timeout = 30
|
|
|
|
return u
|
|
}
|
|
|
|
func (curl *CURL) String() string {
|
|
if curl != nil {
|
|
return fmt.Sprintf("Method: %s\nParsedURL: %s\nHeader: %s\nCookie: %s",
|
|
curl.Method, curl.ParsedURL.String(), curl.Header, curl.Cookies)
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// CreateSession 创建Session
|
|
func (curl *CURL) CreateSession() *requests.Session {
|
|
ses := requests.NewSession()
|
|
ses.SetHeader(curl.Header)
|
|
ses.SetCookies(curl.ParsedURL, curl.Cookies)
|
|
ses.SetConfig(requests.CRequestTimeout, curl.Timeout)
|
|
|
|
if curl.Auth != nil {
|
|
ses.SetConfig(requests.CBasicAuth, curl.Auth)
|
|
}
|
|
|
|
if curl.Insecure {
|
|
ses.SetConfig(requests.CInsecure, curl.Insecure)
|
|
}
|
|
|
|
return ses
|
|
}
|
|
|
|
// CreateWorkflow 根据Session 创建Workflow
|
|
func (curl *CURL) CreateWorkflow(ses *requests.Session) *requests.Workflow {
|
|
var wf *requests.Workflow
|
|
|
|
if ses == nil {
|
|
ses = curl.CreateSession()
|
|
}
|
|
|
|
switch curl.Method {
|
|
case "HEAD":
|
|
wf = ses.Head(curl.ParsedURL.String())
|
|
case "GET":
|
|
wf = ses.Get(curl.ParsedURL.String())
|
|
case "POST":
|
|
wf = ses.Post(curl.ParsedURL.String())
|
|
case "PUT":
|
|
wf = ses.Put(curl.ParsedURL.String())
|
|
case "PATCH":
|
|
wf = ses.Patch(curl.ParsedURL.String())
|
|
case "OPTIONS":
|
|
wf = ses.Options(curl.ParsedURL.String())
|
|
case "DELETE":
|
|
wf = ses.Delete(curl.ParsedURL.String())
|
|
}
|
|
|
|
wf.SetBody(curl.Body)
|
|
return wf
|
|
}
|
|
|
|
// ParseRawCURL curl_bash
|
|
func ParseRawCURL(scurl string) (cURL *CURL, err error) {
|
|
|
|
defer func() {
|
|
if _err := recover(); _err != nil {
|
|
cURL = nil
|
|
err = _err.(error)
|
|
}
|
|
}()
|
|
|
|
executor := newPQueueExecute()
|
|
|
|
curl := NewCURL()
|
|
|
|
if scurl[0] == '"' && scurl[len(scurl)-1] == '"' {
|
|
scurl = strings.Trim(scurl, `"`)
|
|
scurl = strings.TrimSpace(scurl)
|
|
} else if scurl[0] == '\'' && scurl[len(scurl)-1] == '\'' {
|
|
scurl = strings.Trim(scurl, `'`)
|
|
scurl = strings.TrimSpace(scurl)
|
|
} else {
|
|
scurl = strings.TrimSpace(scurl)
|
|
}
|
|
|
|
scurl = strings.TrimLeft(scurl, "curl")
|
|
mathches := regexp.MustCompile(`--[^ ]+ +'[^']+'|--[^ ]+ +[^ ]+|-[A-Za-z] +'[^']+'|-[A-Za-z] +[^ ]+| '[^']+'|--[a-z]+ {0,}`).FindAllString(scurl, -1)
|
|
for _, m := range mathches {
|
|
m = strings.TrimSpace(m)
|
|
switch v := m[0]; v {
|
|
case '\'':
|
|
purl, err := url.Parse(strings.Trim(m, "'"))
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
curl.ParsedURL = purl
|
|
case '-':
|
|
exec := judgeAndParseOptions(curl, m)
|
|
if exec != nil {
|
|
executor.Push(exec)
|
|
}
|
|
}
|
|
}
|
|
|
|
for executor.Len() > 0 {
|
|
exec := executor.Pop()
|
|
exec.Execute()
|
|
}
|
|
|
|
if curl.Method == "" {
|
|
curl.Method = "GET"
|
|
}
|
|
|
|
return curl, nil
|
|
}
|
|
|
|
func judgeAndParseOptions(u *CURL, soption string) *parseFunction {
|
|
switch prefix := soption[0:2]; prefix {
|
|
case "-H":
|
|
return &parseFunction{ParamCURL: u, ParamData: soption, ExecuteFunction: parseHeader, Prioty: 10}
|
|
case "-X":
|
|
return &parseFunction{ParamCURL: u, ParamData: soption, ExecuteFunction: parseOptX, Prioty: 10}
|
|
case "-A": // User-Agent 先后顺序的问题
|
|
data := extractData("^-A +(.+)", soption)
|
|
return &parseFunction{ParamCURL: u, ParamData: data, ExecuteFunction: parseUserAgent, Prioty: 15}
|
|
case "-I":
|
|
return &parseFunction{ParamCURL: u, ParamData: soption, ExecuteFunction: parseOptI, Prioty: 15}
|
|
case "--":
|
|
return parseLongOption(u, soption)
|
|
case "-d":
|
|
data := extractData("^-d +(.+)", soption)
|
|
return &parseFunction{ParamCURL: u, ParamData: data, ExecuteFunction: parseBodyASCII, Prioty: 10}
|
|
case "-u":
|
|
data := extractData("^-u +(.+)", soption)
|
|
return &parseFunction{ParamCURL: u, ParamData: data, ExecuteFunction: parseUser, Prioty: 15}
|
|
case "-k": // -k, --insecure Allow insecure server connections when using SSL
|
|
return &parseFunction{ParamCURL: u, ParamData: soption, ExecuteFunction: parseInsecure, Prioty: 15}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func parseLongOption(u *CURL, soption string) *parseFunction {
|
|
// -d, --data <data> HTTP POST data
|
|
// --data-ascii <data> HTTP POST ASCII data
|
|
// --data-binary <data> HTTP POST binary data
|
|
// --data-raw <data> HTTP POST data, '@' allowed
|
|
// --data-urlencode <data> HTTP POST data url encoded
|
|
|
|
switch {
|
|
case regexp.MustCompile("^--data |^--data-urlencode|^--data-binary|^--data-ascii|^--data-raw").MatchString(soption):
|
|
|
|
datas := regexp.MustCompile("^--data-(binary) +(.+)|^--data-(ascii) +(.+)|^--data-(raw) +(.+)|^--data-(urlencode) +(.+)|^--(data) +(.+)").FindStringSubmatch(soption)
|
|
dtype := datas[1]
|
|
data := strings.Trim(datas[2], "'")
|
|
|
|
if u.Method != "" {
|
|
u.Method = "POST"
|
|
}
|
|
|
|
switch dtype {
|
|
case "binary":
|
|
return &parseFunction{ParamCURL: u, ParamData: data, ExecuteFunction: parseBodyBinary, Prioty: 10}
|
|
case "ascii":
|
|
return &parseFunction{ParamCURL: u, ParamData: data, ExecuteFunction: parseBodyASCII, Prioty: 10}
|
|
case "raw":
|
|
return &parseFunction{ParamCURL: u, ParamData: data, ExecuteFunction: parseBodyRaw, Prioty: 10}
|
|
case "urlencode":
|
|
return &parseFunction{ParamCURL: u, ParamData: data, ExecuteFunction: parseBodyURLEncode, Prioty: 10}
|
|
case "data":
|
|
return &parseFunction{ParamCURL: u, ParamData: data, ExecuteFunction: parseBodyASCII, Prioty: 10}
|
|
}
|
|
|
|
case regexp.MustCompile("^--header").MatchString(soption):
|
|
return &parseFunction{ParamCURL: u, ParamData: soption, ExecuteFunction: parseHeader, Prioty: 10}
|
|
case regexp.MustCompile("^--call").MatchString(soption):
|
|
data := extractData("^--call +(.+)", soption)
|
|
return &parseFunction{ParamCURL: u, ParamData: data, ExecuteFunction: parseCallBack, Prioty: 10}
|
|
case regexp.MustCompile("^--user-agent").MatchString(soption):
|
|
data := extractData("^--user-agent +(.+)", soption)
|
|
return &parseFunction{ParamCURL: u, ParamData: data, ExecuteFunction: parseUserAgent, Prioty: 15}
|
|
case regexp.MustCompile("^--user").MatchString(soption):
|
|
data := extractData("^--user +(.+)", soption)
|
|
return &parseFunction{ParamCURL: u, ParamData: data, ExecuteFunction: parseUser, Prioty: 15}
|
|
case regexp.MustCompile("^--insecure").MatchString(soption):
|
|
return &parseFunction{ParamCURL: u, ParamData: soption, ExecuteFunction: parseInsecure, Prioty: 15}
|
|
case regexp.MustCompile("^--connect-timeout").MatchString(soption):
|
|
data := extractData("^--connect-timeout +(.+)", soption)
|
|
return &parseFunction{ParamCURL: u, ParamData: data, ExecuteFunction: parseTimeout, Prioty: 15}
|
|
}
|
|
|
|
log.Println("can't parseOption", soption)
|
|
return nil
|
|
}
|
|
|
|
func extractData(re, soption string) string {
|
|
datas := regexp.MustCompile(re).FindStringSubmatch(soption)
|
|
return strings.Trim(datas[1], "'")
|
|
}
|
|
|
|
func parseCallBack(u *CURL, value string) {
|
|
u.CallBack = value
|
|
}
|
|
|
|
func parseTimeout(u *CURL, value string) {
|
|
timeout, err := strconv.Atoi(value)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
u.Timeout = timeout
|
|
}
|
|
|
|
func parseInsecure(u *CURL, soption string) {
|
|
u.Insecure = true
|
|
}
|
|
|
|
func parseUser(u *CURL, soption string) {
|
|
auth := strings.Split(soption, ":")
|
|
u.Auth = &requests.BasicAuth{User: auth[0], Password: auth[1]}
|
|
}
|
|
|
|
func parseUserAgent(u *CURL, value string) {
|
|
u.Header.Add("User-Agent", value)
|
|
}
|
|
|
|
func parseOptI(u *CURL, soption string) {
|
|
u.Method = "HEAD"
|
|
}
|
|
|
|
func parseOptX(u *CURL, soption string) {
|
|
matches := regexp.MustCompile("-X +(.+)").FindStringSubmatch(soption)
|
|
method := strings.Trim(matches[1], "'")
|
|
u.Method = method
|
|
}
|
|
|
|
func parseBodyURLEncode(u *CURL, data string) {
|
|
u.Body.SetPrefix(requests.TypeURLENCODED)
|
|
u.Body.SetIOBody(data)
|
|
}
|
|
|
|
func parseBodyRaw(u *CURL, data string) {
|
|
u.Body.SetPrefix(requests.TypeURLENCODED)
|
|
u.Body.SetIOBody(data)
|
|
}
|
|
|
|
func parseBodyASCII(u *CURL, data string) {
|
|
u.Body.SetPrefix(requests.TypeURLENCODED)
|
|
|
|
if data[0] != '@' {
|
|
u.Body.SetIOBody(data)
|
|
} else {
|
|
f, err := os.Open(data[1:])
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
defer f.Close()
|
|
|
|
bdata, err := ioutil.ReadAll(f)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
u.Body.SetIOBody(bdata)
|
|
}
|
|
}
|
|
|
|
// 处理@ 并且替/r/n符号
|
|
func parseBodyBinary(u *CURL, data string) {
|
|
u.Body.SetPrefix(requests.TypeURLENCODED)
|
|
|
|
if data[0] != '@' {
|
|
u.Body.SetIOBody(data)
|
|
} else {
|
|
f, err := os.Open(data[1:])
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
defer f.Close()
|
|
bdata, err := ioutil.ReadAll(f)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
bdata = regexp.MustCompile("\n|\r").ReplaceAll(bdata, []byte(""))
|
|
u.Body.SetIOBody(bdata)
|
|
}
|
|
}
|
|
|
|
func parseHeader(u *CURL, soption string) {
|
|
matches := regexp.MustCompile(`'([^:]+): ([^']+)'`).FindAllStringSubmatch(soption, 1)[0]
|
|
key := matches[1]
|
|
value := matches[2]
|
|
|
|
switch key {
|
|
case "Cookie":
|
|
u.Cookies = ReadRawCookies(value, "")
|
|
u.CookieJar.SetCookies(u.ParsedURL, u.Cookies)
|
|
case "Content-Type":
|
|
u.Body.SetPrefix(value)
|
|
default:
|
|
u.Header.Add(key, value)
|
|
}
|
|
|
|
}
|