package curl2info import ( "errors" "fmt" "io/ioutil" "log" "net/http" "net/http/cookiejar" "net/url" "os" "regexp" "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 Insecure bool } // NewCURL new 一个 curl 出来 func NewCURL(scurl ...string) *CURL { if len(scurl) != 0 { if len(scurl) > 1 { panic(errors.New("NewCURL only accept one curl info")) } curl, err := ParseRawCURL(scurl[0]) if err != nil { panic(err) } return curl } u := &CURL{} u.Insecure = false u.Header = make(http.Header) u.CookieJar, _ = cookiejar.New(nil) u.Body = requests.NewBody() 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) 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 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() 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 HTTP POST data // --data-ascii HTTP POST ASCII data // --data-binary HTTP POST binary data // --data-raw HTTP POST data, '@' allowed // --data-urlencode 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("^--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} } 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 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) } }