github.com/wanddynosios/cli/v8@v8.7.9-0.20240221182337-1a92e3a7017f/api/router/wrapper/request_logger.go (about)

     1  package wrapper
     2  
     3  import (
     4  	"fmt"
     5  	"io/ioutil"
     6  	"net/http"
     7  	"sort"
     8  	"strings"
     9  	"time"
    10  
    11  	"code.cloudfoundry.org/cli/api/router"
    12  )
    13  
    14  //go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 . RequestLoggerOutput
    15  
    16  // RequestLoggerOutput is the interface for displaying logs
    17  type RequestLoggerOutput interface {
    18  	DisplayHeader(name string, value string) error
    19  	DisplayHost(name string) error
    20  	DisplayJSONBody(body []byte) error
    21  	DisplayMessage(msg string) error
    22  	DisplayRequestHeader(method string, uri string, httpProtocol string) error
    23  	DisplayResponseHeader(httpProtocol string, status string) error
    24  	DisplayType(name string, requestDate time.Time) error
    25  	HandleInternalError(err error)
    26  	Start() error
    27  	Stop() error
    28  }
    29  
    30  // RequestLogger is the wrapper that logs requests to and responses from the
    31  // Cloud Controller server
    32  type RequestLogger struct {
    33  	connection router.Connection
    34  	output     RequestLoggerOutput
    35  }
    36  
    37  // NewRequestLogger returns a pointer to a RequestLogger wrapper
    38  func NewRequestLogger(output RequestLoggerOutput) *RequestLogger {
    39  	return &RequestLogger{
    40  		output: output,
    41  	}
    42  }
    43  
    44  // Make records the request and the response to UI
    45  func (logger *RequestLogger) Make(request *router.Request, passedResponse *router.Response) error {
    46  	err := logger.displayRequest(request)
    47  	if err != nil {
    48  		logger.output.HandleInternalError(err)
    49  	}
    50  
    51  	err = logger.connection.Make(request, passedResponse)
    52  
    53  	if passedResponse.HTTPResponse != nil {
    54  		displayErr := logger.displayResponse(passedResponse)
    55  		if displayErr != nil {
    56  			logger.output.HandleInternalError(displayErr)
    57  		}
    58  	}
    59  
    60  	return err
    61  }
    62  
    63  // Wrap sets the connection on the RequestLogger and returns itself
    64  func (logger *RequestLogger) Wrap(innerconnection router.Connection) router.Connection {
    65  	logger.connection = innerconnection
    66  	return logger
    67  }
    68  
    69  func (logger *RequestLogger) displayRequest(request *router.Request) error {
    70  	err := logger.output.Start()
    71  	if err != nil {
    72  		return err
    73  	}
    74  	defer logger.output.Stop()
    75  
    76  	err = logger.output.DisplayType("REQUEST", time.Now())
    77  	if err != nil {
    78  		return err
    79  	}
    80  	err = logger.output.DisplayRequestHeader(request.Method, request.URL.RequestURI(), request.Proto)
    81  	if err != nil {
    82  		return err
    83  	}
    84  	err = logger.output.DisplayHost(request.URL.Host)
    85  	if err != nil {
    86  		return err
    87  	}
    88  	err = logger.displaySortedHeaders(request.Header)
    89  	if err != nil {
    90  		return err
    91  	}
    92  
    93  	contentType := request.Header.Get("Content-Type")
    94  	if request.Body != nil {
    95  		if strings.Contains(contentType, "json") {
    96  			rawRequestBody, err := ioutil.ReadAll(request.Body)
    97  			if err != nil {
    98  				return err
    99  			}
   100  
   101  			defer request.ResetBody()
   102  
   103  			return logger.output.DisplayJSONBody(rawRequestBody)
   104  		} else if strings.Contains(contentType, "x-www-form-urlencoded") {
   105  			rawRequestBody, err := ioutil.ReadAll(request.Body)
   106  			if err != nil {
   107  				return err
   108  			}
   109  
   110  			defer request.ResetBody()
   111  
   112  			return logger.output.DisplayMessage(fmt.Sprintf("[application/x-www-form-urlencoded %s]", rawRequestBody))
   113  		}
   114  	}
   115  	if contentType != "" {
   116  		return logger.output.DisplayMessage(fmt.Sprintf("[%s Content Hidden]", strings.Split(contentType, ";")[0]))
   117  	}
   118  	return nil
   119  }
   120  
   121  func (logger *RequestLogger) displayResponse(passedResponse *router.Response) error {
   122  	err := logger.output.Start()
   123  	if err != nil {
   124  		return err
   125  	}
   126  	defer logger.output.Stop()
   127  
   128  	err = logger.output.DisplayType("RESPONSE", time.Now())
   129  	if err != nil {
   130  		return err
   131  	}
   132  	err = logger.output.DisplayResponseHeader(passedResponse.HTTPResponse.Proto, passedResponse.HTTPResponse.Status)
   133  	if err != nil {
   134  		return err
   135  	}
   136  	err = logger.displaySortedHeaders(passedResponse.HTTPResponse.Header)
   137  	if err != nil {
   138  		return err
   139  	}
   140  	return logger.output.DisplayJSONBody(passedResponse.RawResponse)
   141  }
   142  
   143  func (logger *RequestLogger) displaySortedHeaders(headers http.Header) error {
   144  	keys := []string{}
   145  	for key := range headers {
   146  		keys = append(keys, key)
   147  	}
   148  	sort.Strings(keys)
   149  
   150  	for _, key := range keys {
   151  		for _, value := range headers[key] {
   152  			err := logger.output.DisplayHeader(key, redactHeaders(key, value))
   153  			if err != nil {
   154  				return err
   155  			}
   156  		}
   157  	}
   158  	return nil
   159  }
   160  
   161  func redactHeaders(key string, value string) string {
   162  	redactedKeys := []string{"Authorization", "Set-Cookie"}
   163  	for _, redactedKey := range redactedKeys {
   164  		if key == redactedKey {
   165  			return "[PRIVATE DATA HIDDEN]"
   166  		}
   167  	}
   168  
   169  	return value
   170  }