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  }