curl2info/parse_curl.go

394 lines
11 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
}
func init() {
optionTrie = NewTrie()
oelist := []*optionExecute{
{"-H", 10, parseHeader, nil},
{"-X", 10, parseOptX, nil},
{"-A", 15, parseUserAgent, &extract{re: "^-A +(.+)", execute: extractData}},
{"-I", 15, parseOptI, nil},
{"-d", 10, parseBodyASCII, &extract{re: "^-d +(.+)", execute: extractData}},
{"-u", 15, parseUser, &extract{re: "^-u +(.+)", execute: extractData}},
{"-k", 15, parseInsecure, nil},
// Body
{"--data", 10, parseBodyASCII, &extract{re: "--data +(.+)", execute: extractData}},
{"--data-urlencode", 10, parseBodyURLEncode, &extract{re: "--data-urlencode +(.+)", execute: extractData}},
{"--data-binary", 10, parseBodyBinary, &extract{re: "--data-binary +(.+)", execute: extractData}},
{"--data-ascii", 10, parseBodyASCII, &extract{re: "--data-ascii +(.+)", execute: extractData}},
{"--data-raw", 10, parseBodyRaw, &extract{re: "--data-raw +(.+)", execute: extractData}},
//"--"
{"--header", 10, parseHeader, nil},
{"--insecure", 15, parseInsecure, nil},
{"--call", 10, parseCallBack, &extract{re: "--call +(.+)", execute: extractData}},
{"--user-agent", 15, parseUserAgent, &extract{re: "--user-agent +(.+)", execute: extractData}},
{"--user", 15, parseUser, &extract{re: "--user +(.+)", execute: extractData}},
{"--connect-timeout", 15, parseTimeout, &extract{re: "--connect-timeout +(.+)", execute: extractData}},
}
for _, oe := range oelist {
optionTrie.Insert(oe)
}
log.Println("support options:", optionTrie.AllWords())
}
// ParseRawCURL curl_bash 可以用trie改进 没空改
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, `"`)
} else if scurl[0] == '\'' && scurl[len(scurl)-1] == '\'' {
scurl = strings.Trim(scurl, `'`)
}
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 := judgeOptions(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 judgeOptions(u *CURL, soption string) *parseFunction {
word := TrieStrWord(soption)
if ioe := optionTrie.SearchMostPrefix(&word); ioe != nil {
oe := ioe.(*optionExecute)
return oe.BuildFunction(u, soption)
}
log.Println(soption, " no haved this option")
return nil
}
func judgeAndParseOptions(u *CURL, soption string) *parseFunction {
switch prefix := soption[0:2]; prefix {
case "-H":
return &parseFunction{ParamCURL: u, ParamData: soption, ExecuteFunction: parseHeader, Priority: 10}
case "-X":
return &parseFunction{ParamCURL: u, ParamData: soption, ExecuteFunction: parseOptX, Priority: 10}
case "-A": // User-Agent 先后顺序的问题
data := extractData("^-A +(.+)", soption)
return &parseFunction{ParamCURL: u, ParamData: data, ExecuteFunction: parseUserAgent, Priority: 15}
case "-I":
return &parseFunction{ParamCURL: u, ParamData: soption, ExecuteFunction: parseOptI, Priority: 15}
case "--":
return parseLongOption(u, soption)
case "-d":
data := extractData("^-d +(.+)", soption)
return &parseFunction{ParamCURL: u, ParamData: data, ExecuteFunction: parseBodyASCII, Priority: 10}
case "-u":
data := extractData("^-u +(.+)", soption)
return &parseFunction{ParamCURL: u, ParamData: data, ExecuteFunction: parseUser, Priority: 15}
case "-k": // -k, --insecure Allow insecure server connections when using SSL
return &parseFunction{ParamCURL: u, ParamData: soption, ExecuteFunction: parseInsecure, Priority: 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], "'")
switch dtype {
case "binary":
return &parseFunction{ParamCURL: u, ParamData: data, ExecuteFunction: parseBodyBinary, Priority: 10}
case "ascii":
return &parseFunction{ParamCURL: u, ParamData: data, ExecuteFunction: parseBodyASCII, Priority: 10}
case "raw":
return &parseFunction{ParamCURL: u, ParamData: data, ExecuteFunction: parseBodyRaw, Priority: 10}
case "urlencode":
return &parseFunction{ParamCURL: u, ParamData: data, ExecuteFunction: parseBodyURLEncode, Priority: 10}
case "data":
return &parseFunction{ParamCURL: u, ParamData: data, ExecuteFunction: parseBodyASCII, Priority: 10}
}
case regexp.MustCompile("^--header").MatchString(soption):
return &parseFunction{ParamCURL: u, ParamData: soption, ExecuteFunction: parseHeader, Priority: 10}
case regexp.MustCompile("^--call").MatchString(soption):
data := extractData("^--call +(.+)", soption)
return &parseFunction{ParamCURL: u, ParamData: data, ExecuteFunction: parseCallBack, Priority: 10}
case regexp.MustCompile("^--user-agent").MatchString(soption):
data := extractData("^--user-agent +(.+)", soption)
return &parseFunction{ParamCURL: u, ParamData: data, ExecuteFunction: parseUserAgent, Priority: 15}
case regexp.MustCompile("^--user").MatchString(soption):
data := extractData("^--user +(.+)", soption)
return &parseFunction{ParamCURL: u, ParamData: data, ExecuteFunction: parseUser, Priority: 15}
case regexp.MustCompile("^--insecure").MatchString(soption):
return &parseFunction{ParamCURL: u, ParamData: soption, ExecuteFunction: parseInsecure, Priority: 15}
case regexp.MustCompile("^--connect-timeout").MatchString(soption):
data := extractData("^--connect-timeout +(.+)", soption)
return &parseFunction{ParamCURL: u, ParamData: data, ExecuteFunction: parseTimeout, Priority: 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) {
if u.Method != "" {
u.Method = "POST"
}
u.Body.SetPrefix(requests.TypeURLENCODED)
u.Body.SetIOBody(data)
}
func parseBodyRaw(u *CURL, data string) {
if u.Method != "" {
u.Method = "POST"
}
u.Body.SetPrefix(requests.TypeURLENCODED)
u.Body.SetIOBody(data)
}
func parseBodyASCII(u *CURL, data string) {
if u.Method != "" {
u.Method = "POST"
}
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) {
if u.Method != "" {
u.Method = "POST"
}
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)
}
}