github.com/mook-as/cf-cli@v7.0.0-beta.28.0.20200120190804-b91c115fae48+incompatible/cf/net/request_dumper.go (about) 1 package net 2 3 import ( 4 "bytes" 5 "io/ioutil" 6 "net/http" 7 "net/http/httputil" 8 "net/url" 9 "sort" 10 "strings" 11 "time" 12 13 . "code.cloudfoundry.org/cli/cf/i18n" 14 "code.cloudfoundry.org/cli/cf/terminal" 15 "code.cloudfoundry.org/cli/cf/trace" 16 "code.cloudfoundry.org/cli/util/ui" 17 ) 18 19 //go:generate counterfeiter . RequestDumperInterface 20 21 type RequestDumperInterface interface { 22 DumpRequest(*http.Request) 23 DumpResponse(*http.Response) 24 } 25 26 type RequestDumper struct { 27 printer trace.Printer 28 } 29 30 func NewRequestDumper(printer trace.Printer) RequestDumper { 31 return RequestDumper{printer: printer} 32 } 33 34 func (p RequestDumper) DumpRequest(req *http.Request) { 35 p.printer.Printf("\n%s [%s]\n", terminal.HeaderColor(T("REQUEST:")), time.Now().Format(time.RFC3339)) 36 37 p.printer.Printf("%s %s %s\n", req.Method, req.URL.RequestURI(), req.Proto) 38 39 p.printer.Printf("Host: %s", req.URL.Host) 40 41 headers := ui.RedactHeaders(req.Header) 42 p.displaySortedHeaders(headers) 43 44 contentType := req.Header.Get("Content-Type") 45 if strings.Contains(contentType, "multipart/form-data") { 46 p.printer.Println(T("[MULTIPART/FORM-DATA CONTENT HIDDEN]")) 47 } 48 49 if req.Body == nil { 50 return 51 } 52 53 requestBody, err := ioutil.ReadAll(req.Body) 54 req.Body.Close() 55 req.Body = ioutil.NopCloser(bytes.NewBuffer(requestBody)) 56 57 if len(requestBody) == 0 { 58 return 59 } 60 61 if strings.Contains(contentType, "application/json") { 62 if err != nil { 63 p.printer.Println("Unable to read Request Body:", err) 64 return 65 } 66 67 sanitizedJSON, err := ui.SanitizeJSON(requestBody) 68 69 if err != nil { 70 p.printer.Println("Failed to sanitize json body:", err) 71 return 72 } 73 74 p.printer.Printf("%s\n", sanitizedJSON) 75 } 76 if strings.Contains(contentType, "x-www-form-urlencoded") { 77 78 formData, err := url.ParseQuery(string(requestBody)) 79 if err != nil { 80 p.printer.Println("Failed to parse form:", err) 81 return 82 } 83 84 redactedData := p.redactFormData(formData) 85 p.displayFormData(redactedData) 86 } 87 } 88 89 func (p RequestDumper) displaySortedHeaders(headers http.Header) { 90 sortedHeaders := []string{} 91 for key, _ := range headers { 92 sortedHeaders = append(sortedHeaders, key) 93 } 94 sort.Strings(sortedHeaders) 95 96 for _, header := range sortedHeaders { 97 for _, value := range headers[header] { 98 p.printer.Printf("%s: %s\n", T(header), value) 99 } 100 } 101 p.printer.Printf("\n") 102 } 103 104 func (p RequestDumper) redactFormData(formData url.Values) url.Values { 105 for key := range formData { 106 if key == "password" || key == "Authorization" || strings.Contains(key, "token") { 107 formData.Set(key, ui.RedactedValue) 108 } 109 } 110 return formData 111 } 112 113 func (p RequestDumper) displayFormData(formData url.Values) { 114 var buf strings.Builder 115 keys := make([]string, 0, len(formData)) 116 for k := range formData { 117 keys = append(keys, k) 118 } 119 sort.Strings(keys) 120 for _, k := range keys { 121 vs := formData[k] 122 for _, v := range vs { 123 if buf.Len() > 0 { 124 buf.WriteByte('&') 125 } 126 buf.WriteString(k) 127 buf.WriteByte('=') 128 buf.WriteString(v) 129 } 130 } 131 p.printer.Printf("%s\n", buf.String()) 132 } 133 134 func (p RequestDumper) DumpResponse(res *http.Response) { 135 dumpedResponse, err := httputil.DumpResponse(res, true) 136 if err != nil { 137 p.printer.Printf(T("Error dumping response\n{{.Err}}\n", map[string]interface{}{"Err": err})) 138 } else { 139 p.printer.Printf("\n%s [%s]\n%s\n", terminal.HeaderColor(T("RESPONSE:")), time.Now().Format(time.RFC3339), trace.Sanitize(string(dumpedResponse))) 140 } 141 }