.
675 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | # CanaryConvertor
11 | Export HttpCanary Records to Postman Collection.Only Zip Achieve Supported.
12 |
13 | 将HttpCanary导出的HTTP报文转换到Postman集合文件。
14 |
15 |
16 |
17 | ## 🍱 特性
18 | * 将HttpCanary抓包记录存档(zip)导出为Postman集合存档,便于在Postman中调试。
19 | * 支持HTTP 1.0/1.1/2 报文。(注意:Canary导出的HTTP/2 报文非标准的二进制帧,仅为文本化报文)
20 | * 自动转化Response,支持Gzip.
21 | * 导出集合名称为被抓包App包名。
22 |
23 | ## 🌰 用法
24 | 编译后执行:
25 | ```
26 | ./HcyConverter PATH/TO/ZIP -o ./
27 | ```
28 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module HcyConverter
2 |
3 | go 1.16
4 |
5 | require github.com/urfave/cli/v2 v2.3.0
6 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
2 | github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
3 | github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
4 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
5 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
6 | github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
7 | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
8 | github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
9 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
10 | github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
11 | github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
12 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
13 | gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
14 |
--------------------------------------------------------------------------------
/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lx200916/CanaryConvertor/abc4a9ce0551c64043d8c79ff7a1c35279379c96/icon.png
--------------------------------------------------------------------------------
/postman.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bufio"
5 | "bytes"
6 | "compress/gzip"
7 | "encoding/json"
8 | "fmt"
9 | "io/ioutil"
10 | "net/http"
11 | "net/url"
12 | "regexp"
13 | "strings"
14 | )
15 | import "archive/zip"
16 |
17 | type PostmanKV struct {
18 | Key string `json:"key"`
19 | Value *string `json:"value"`
20 | }
21 | type PostmanForm struct {
22 | Key string `json:"key"`
23 | Value *string `json:"value"`
24 | Type string `json:"type"`
25 | Src string `json:"src,omitempty"`
26 | }
27 | type Postman struct {
28 | Info struct {
29 | Name string `json:"name"`
30 | Schema string `json:"schema"`
31 | } `json:"info"`
32 | Item []*PostmanItem `json:"item,omitempty"`
33 | }
34 | type PostmanResponse struct {
35 | ResponseTime string `json:"responseTime"`
36 | PostmanPreviewLang string `json:"_postman_previewlanguage,omitempty"`
37 | OriginalRequest PostmanRequest `json:"originalRequest,omitempty"`
38 | Header *[]PostmanKV `json:"header"`
39 | Body string `json:"body,omitempty"`
40 | Code int `json:"code,omitempty"`
41 | Status string `json:"status,omitempty"`
42 | Name string `json:"name,omitempty"`
43 | }
44 | type PostmanRequest struct {
45 | Method string `json:"method"`
46 | Header *[]PostmanKV `json:"header"`
47 | Body PostmanRaw `json:"body"`
48 | URL struct {
49 | Raw string `json:"raw"`
50 | Protocol string `json:"protocol"`
51 | Host []string `json:"host"`
52 | Path []string `json:"path"`
53 | Query *[]PostmanKV `json:"query,omitempty""`
54 | Port string `json:"port,omitempty"`
55 | } `json:"url"`
56 | }
57 |
58 | type PostmanItem struct {
59 | Name string `json:"name"`
60 | Request PostmanRequest `json:"request"`
61 | Response *[]PostmanResponse `json:"response"`
62 | }
63 |
64 | type DictionaryTree struct {
65 | Files []string
66 | App string
67 | URL string
68 | Duration string
69 | Time string
70 | Protocol string
71 | }
72 |
73 | type PostmanRaw struct {
74 | Mode string `json:"mode"`
75 | Raw string `json:"raw,omitempty"`
76 | Urlencoded *[]PostmanForm `json:"urlencoded,omitempty"`
77 | Formdata *[]PostmanForm `json:"formdata,omitempty"`
78 | Options *PostmanRawOptions `json:"options,omitempty"`
79 | }
80 | type PostmanRawOptions struct {
81 | Raw struct {
82 | Language string `json:"language"`
83 | } `json:"raw"`
84 | }
85 | type HttpCanaryJson struct {
86 | App string `json:"app"`
87 | Duration string `json:"duration"`
88 | //Headers Headers `json:"headers"`
89 | Method string `json:"method"`
90 | Protocol string `json:"protocol"`
91 | //RemoteIP string `json:"remoteIp"`
92 | //RemotePort int `json:"remotePort"`
93 | SessionID string `json:"sessionId"`
94 | Time string `json:"time"`
95 | URL string `json:"url"`
96 | }
97 |
98 | func createPostman(appName string) Postman {
99 | return Postman{
100 | Info: struct {
101 | Name string `json:"name"`
102 | Schema string `json:"schema"`
103 | }{Name: appName, Schema: "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"},
104 | }
105 | }
106 | func toPostman(inputFile string, output string) error {
107 |
108 | directoryDict := make(map[string]*DictionaryTree)
109 | FileDict := make(map[string]*zip.File)
110 | PostmanDict := make(map[string]*Postman)
111 | file, err := zip.OpenReader(inputFile)
112 | if err != nil {
113 | return err
114 | }
115 | for _, i2 := range file.File {
116 | //fmt.Println(i2.Name)
117 | fileName := i2.Name
118 | if strings.Contains(fileName, "/") {
119 | path := strings.Split(fileName, "/")
120 |
121 | if directoryDict[path[0]] == nil {
122 | directoryDict[path[0]] = &DictionaryTree{}
123 | }
124 | directoryDict[path[0]].Files = append(directoryDict[path[0]].Files, path[1])
125 | FileDict[fileName] = i2
126 | if strings.Contains(fileName, "request.json") {
127 | closer, err := i2.Open()
128 | if err != nil {
129 | return err
130 | }
131 | var content []byte
132 | content, err = ioutil.ReadAll(closer)
133 |
134 | if err != nil {
135 | fmt.Println(err)
136 | return err
137 | }
138 |
139 | var httpCanaryJson HttpCanaryJson
140 | err = json.Unmarshal(content, &httpCanaryJson)
141 | if err != nil {
142 | fmt.Println(err)
143 | return err
144 | }
145 | if httpCanaryJson.App != "" && PostmanDict[httpCanaryJson.App] == nil {
146 | postman := createPostman(httpCanaryJson.App)
147 | PostmanDict[httpCanaryJson.App] = &postman
148 | }
149 | directoryDict[path[0]].App = httpCanaryJson.App
150 | directoryDict[path[0]].URL = httpCanaryJson.URL
151 | directoryDict[path[0]].Duration = httpCanaryJson.Duration
152 | directoryDict[path[0]].Time = httpCanaryJson.Time
153 | directoryDict[path[0]].Protocol = httpCanaryJson.Protocol
154 |
155 | }
156 | }
157 | }
158 | defer file.Close()
159 | //fmt.Println(directoryDict)
160 | for i, i2 := range directoryDict {
161 | var request *http.Request
162 | var postmanItem *PostmanItem
163 | var postmanRequest PostmanRequest
164 | if _, ok := FileDict[i+"/request.hcy"]; ok {
165 | open, err := FileDict[i+"/request.hcy"].Open()
166 | if err != nil {
167 | fmt.Println(err)
168 | return err
169 | }
170 | var buf *bufio.Reader
171 | if i2.Protocol == "h2" {
172 | reg, _ := regexp.Compile(`h2\s*$`)
173 | content, err := ioutil.ReadAll(open)
174 |
175 | if err != nil {
176 | fmt.Println(err)
177 | return err
178 | }
179 | lines := strings.Split(string(content), "\n")
180 | lines[0] = reg.ReplaceAllString(lines[0], "HTTP/1.1")
181 | buf = bufio.NewReader(bytes.NewBufferString(strings.Join(lines, "\n")))
182 |
183 | } else {
184 | buf = bufio.NewReader(open)
185 | }
186 |
187 | request, err = http.ReadRequest(buf)
188 | if err != nil {
189 | fmt.Println(err)
190 | return err
191 | }
192 |
193 | parse, err := url.Parse(i2.URL)
194 | if err != nil {
195 | fmt.Println(err)
196 | return err
197 | }
198 | //fmt.Println(parse)
199 | host := parse.Host
200 | port := ""
201 | if strings.Contains(host, ":") {
202 | port = strings.Split(host, ":")[1]
203 | host = strings.Split(host, ":")[0]
204 |
205 | }
206 | postmanRequest = PostmanRequest{Method: request.Method, URL: struct {
207 | Raw string `json:"raw"`
208 | Protocol string `json:"protocol"`
209 | Host []string `json:"host"`
210 | Path []string `json:"path"`
211 | Query *[]PostmanKV `json:"query,omitempty""`
212 | Port string `json:"port,omitempty"`
213 | }{Raw: i2.URL, Protocol: parse.Scheme, Port: port, Host: strings.Split(host, "."), Path: strings.Split(parse.Path, "/")[1:]},
214 | }
215 | postmanItem = &PostmanItem{Name: request.RequestURI, Request: postmanRequest}
216 | if parse.RawQuery != "" {
217 | query, err := url.ParseQuery(parse.RawQuery)
218 | if err == nil {
219 | var queryList []PostmanKV
220 | for s, i3 := range query {
221 | if len(i3) > 0 {
222 | queryList = append(queryList, PostmanKV{s, &i3[len(i3)-1]})
223 | } else {
224 | queryList = append(queryList, PostmanKV{s, nil})
225 |
226 | }
227 | }
228 | postmanItem.Request.URL.Query = &queryList
229 | }
230 | }
231 | if len(request.Header) > 0 {
232 | var headers []PostmanKV
233 | for s, i3 := range request.Header {
234 | if len(i3) > 0 {
235 | headers = append(headers, PostmanKV{s, &i3[0]})
236 |
237 | } else {
238 | headers = append(headers, PostmanKV{s, nil})
239 | }
240 | }
241 | postmanItem.Request.Header = &headers
242 | }
243 |
244 | if err != nil {
245 | return err
246 | }
247 | contentType := request.Header.Get("Content-Type")
248 | switch {
249 | case strings.Contains(contentType, "form-data"):
250 | err = request.ParseMultipartForm(10240)
251 | if err == nil {
252 | var kvs []PostmanForm
253 | postmanItem.Request.Body.Mode = "formdata"
254 | for s, i3 := range request.PostForm {
255 | if len(i3) > 0 {
256 | kvs = append(kvs, PostmanForm{Key: s, Value: &i3[0], Type: "text"})
257 | } else {
258 | kvs = append(kvs, PostmanForm{Key: s, Type: "text"})
259 |
260 | }
261 | }
262 | if len(request.MultipartForm.File) > 0 {
263 |
264 | for s, _ := range request.MultipartForm.File {
265 | kvs = append(kvs, PostmanForm{Key: s, Type: "file"})
266 | }
267 |
268 | }
269 | postmanItem.Request.Body.Formdata = &kvs
270 |
271 | }
272 | case strings.Contains(contentType, "x-www-form-urlencoded"):
273 | err = request.ParseForm()
274 | if err == nil {
275 | var kvs []PostmanForm
276 | postmanItem.Request.Body.Mode = "urlencoded"
277 | for s, i3 := range request.PostForm {
278 | if len(i3) > 0 {
279 | kvs = append(kvs, PostmanForm{Key: s, Value: &i3[0], Type: "text"})
280 | } else {
281 | kvs = append(kvs, PostmanForm{Key: s, Type: "text"})
282 |
283 | }
284 | }
285 | postmanItem.Request.Body.Urlencoded = &kvs
286 |
287 | }
288 | default:
289 | postmanItem.Request.Body.Mode = "raw"
290 | var content []byte
291 | content, err = ioutil.ReadAll(request.Body)
292 | postmanItem.Request.Body.Raw = string(content)
293 | previewlang := Previewlang(contentType)
294 | postmanItem.Request.Body.Options = &PostmanRawOptions{Raw: struct {
295 | Language string `json:"language"`
296 | }(struct{ Language string }{Language: previewlang})}
297 | }
298 | if postmanItem.Request.Body.Mode == "" {
299 | postmanItem.Request.Body.Mode = "raw"
300 | var content []byte
301 | content, err = ioutil.ReadAll(request.Body)
302 | postmanItem.Request.Body.Raw = string(content)
303 | }
304 |
305 | PostmanDict[i2.App].Item = append(PostmanDict[i2.App].Item, postmanItem)
306 |
307 | }
308 | if _, ok := FileDict[i+"/response.hcy"]; ok {
309 | open, err := FileDict[i+"/response.hcy"].Open()
310 | if err != nil {
311 | fmt.Println(err)
312 | return err
313 | }
314 | var buf *bufio.Reader
315 | if i2.Protocol == "h2" {
316 | content, err := ioutil.ReadAll(open)
317 |
318 | if err != nil {
319 | fmt.Println(err)
320 | return err
321 | }
322 | lines := strings.Split(string(content), "\n")
323 | lines[0] = strings.Replace(lines[0], "h2", "HTTP/1.1", 1)
324 | buf = bufio.NewReader(bytes.NewBufferString(strings.Join(lines, "\n")))
325 |
326 | } else {
327 | buf = bufio.NewReader(open)
328 | }
329 | response, err := http.ReadResponse(buf, request)
330 |
331 | if err != nil {
332 | fmt.Println(err)
333 | return err
334 | }
335 | var postmanResponseWrapper []PostmanResponse
336 | responsePostman := PostmanResponse{
337 | ResponseTime: i2.Duration,
338 | Name: fmt.Sprintf("%s,Imported From HttpCanary", i2.Time),
339 | }
340 | if len(response.Header) > 0 {
341 | var headers []PostmanKV
342 | for s, i3 := range response.Header {
343 | if len(i3) > 0 {
344 | headers = append(headers, PostmanKV{s, &i3[0]})
345 |
346 | } else {
347 | headers = append(headers, PostmanKV{s, nil})
348 | }
349 | }
350 | responsePostman.Header = &headers
351 | }
352 | if response.ContentLength == 0 {
353 | continue
354 | }
355 | reader := response.Body
356 | if response.Header.Get("Content-Encoding") == "gzip" {
357 | reader, err = gzip.NewReader(reader)
358 | if err != nil {
359 | fmt.Println(err)
360 |
361 | }
362 | }
363 | var content []byte
364 | content, err = ioutil.ReadAll(reader)
365 | responsePostman.Body = string(content)
366 | responsePostman.Code = response.StatusCode
367 | responsePostman.Status = response.Status
368 | responsePostman.PostmanPreviewLang = Previewlang(response.Header.Get("Content-Type"))
369 | responsePostman.OriginalRequest = postmanItem.Request
370 | postmanResponseWrapper = append(postmanResponseWrapper, responsePostman)
371 | postmanItem.Response = &postmanResponseWrapper
372 | }
373 | }
374 | if !strings.HasSuffix(output, "/") {
375 | output += "/"
376 | }
377 | for s, postman := range PostmanDict {
378 | marshal, err := json.Marshal(postman)
379 | if err != nil {
380 | fmt.Println(err)
381 | return err
382 | }
383 | err = ioutil.WriteFile(fmt.Sprintf("%s%s_collection.json", output, s), marshal, 0644)
384 | if err != nil {
385 | fmt.Println(err)
386 | return err
387 | }
388 |
389 | }
390 | return nil
391 | }
392 |
393 | func Previewlang(contentType string) string {
394 | if strings.Contains(contentType, "json") {
395 | return "json"
396 | }
397 | if strings.Contains(contentType, "html") {
398 | return "html"
399 | }
400 | if strings.Contains(contentType, "javascript") {
401 | return "javascript"
402 | }
403 | if strings.Contains(contentType, "xml") {
404 | return "xml"
405 | }
406 | return "auto"
407 | }
408 |
--------------------------------------------------------------------------------