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  }