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