github.com/git-lfs/git-lfs@v2.5.2+incompatible/lfsapi/verbose.go (about)

     1  package lfsapi
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"fmt"
     7  	"io"
     8  	"net/http"
     9  	"net/http/httputil"
    10  	"strings"
    11  
    12  	"github.com/rubyist/tracerx"
    13  )
    14  
    15  func (c *Client) traceRequest(req *http.Request) (*tracedRequest, error) {
    16  	tracerx.Printf("HTTP: %s", traceReq(req))
    17  
    18  	if c.Verbose {
    19  		if dump, err := httputil.DumpRequest(req, false); err == nil {
    20  			c.traceHTTPDump(">", dump)
    21  		}
    22  	}
    23  
    24  	body, ok := req.Body.(ReadSeekCloser)
    25  	if body != nil && !ok {
    26  		return nil, fmt.Errorf("Request body must implement io.ReadCloser and io.Seeker. Got: %T", body)
    27  	}
    28  
    29  	if body != nil && ok {
    30  		body.Seek(0, io.SeekStart)
    31  		tr := &tracedRequest{
    32  			verbose:        c.Verbose && isTraceableContent(req.Header),
    33  			verboseOut:     c.VerboseOut,
    34  			ReadSeekCloser: body,
    35  		}
    36  		req.Body = tr
    37  		return tr, nil
    38  	}
    39  
    40  	return nil, nil
    41  }
    42  
    43  type tracedRequest struct {
    44  	BodySize   int64
    45  	verbose    bool
    46  	verboseOut io.Writer
    47  	ReadSeekCloser
    48  }
    49  
    50  func (r *tracedRequest) Read(b []byte) (int, error) {
    51  	n, err := tracedRead(r.ReadSeekCloser, b, r.verboseOut, false, r.verbose)
    52  	r.BodySize += int64(n)
    53  	return n, err
    54  }
    55  
    56  func (c *Client) traceResponse(req *http.Request, tracedReq *tracedRequest, res *http.Response) {
    57  	if tracedReq != nil {
    58  		c.httpLogger.LogRequest(req, tracedReq.BodySize)
    59  	}
    60  
    61  	if res == nil {
    62  		c.httpLogger.LogResponse(req, -1, 0)
    63  		return
    64  	}
    65  
    66  	tracerx.Printf("HTTP: %d", res.StatusCode)
    67  
    68  	verboseBody := isTraceableContent(res.Header)
    69  	res.Body = &tracedResponse{
    70  		httpLogger: c.httpLogger,
    71  		response:   res,
    72  		gitTrace:   verboseBody,
    73  		verbose:    verboseBody && c.Verbose,
    74  		verboseOut: c.VerboseOut,
    75  		ReadCloser: res.Body,
    76  	}
    77  
    78  	if !c.Verbose {
    79  		return
    80  	}
    81  
    82  	if dump, err := httputil.DumpResponse(res, false); err == nil {
    83  		if verboseBody {
    84  			fmt.Fprintf(c.VerboseOut, "\n\n")
    85  		} else {
    86  			fmt.Fprintf(c.VerboseOut, "\n")
    87  		}
    88  		c.traceHTTPDump("<", dump)
    89  	}
    90  }
    91  
    92  type tracedResponse struct {
    93  	BodySize   int64
    94  	httpLogger *syncLogger
    95  	response   *http.Response
    96  	verbose    bool
    97  	gitTrace   bool
    98  	verboseOut io.Writer
    99  	eof        bool
   100  	io.ReadCloser
   101  }
   102  
   103  func (r *tracedResponse) Read(b []byte) (int, error) {
   104  	n, err := tracedRead(r.ReadCloser, b, r.verboseOut, r.gitTrace, r.verbose)
   105  	r.BodySize += int64(n)
   106  
   107  	if err == io.EOF && !r.eof {
   108  		r.httpLogger.LogResponse(r.response.Request, r.response.StatusCode, r.BodySize)
   109  		r.eof = true
   110  	}
   111  	return n, err
   112  }
   113  
   114  func tracedRead(r io.Reader, b []byte, verboseOut io.Writer, gitTrace, verbose bool) (int, error) {
   115  	n, err := r.Read(b)
   116  	if err == nil || err == io.EOF {
   117  		if n > 0 && (gitTrace || verbose) {
   118  			chunk := string(b[0:n])
   119  			if gitTrace {
   120  				tracerx.Printf("HTTP: %s", chunk)
   121  			}
   122  
   123  			if verbose {
   124  				fmt.Fprint(verboseOut, chunk)
   125  			}
   126  		}
   127  	}
   128  
   129  	return n, err
   130  }
   131  
   132  func (c *Client) traceHTTPDump(direction string, dump []byte) {
   133  	scanner := bufio.NewScanner(bytes.NewBuffer(dump))
   134  
   135  	for scanner.Scan() {
   136  		line := scanner.Text()
   137  		if !c.DebuggingVerbose && strings.HasPrefix(strings.ToLower(line), "authorization: basic") {
   138  			fmt.Fprintf(c.VerboseOut, "%s Authorization: Basic * * * * *\n", direction)
   139  		} else {
   140  			fmt.Fprintf(c.VerboseOut, "%s %s\n", direction, line)
   141  		}
   142  	}
   143  }
   144  
   145  var tracedTypes = []string{"json", "text", "xml", "html"}
   146  
   147  func isTraceableContent(h http.Header) bool {
   148  	ctype := strings.ToLower(strings.SplitN(h.Get("Content-Type"), ";", 2)[0])
   149  	for _, tracedType := range tracedTypes {
   150  		if strings.Contains(ctype, tracedType) {
   151  			return true
   152  		}
   153  	}
   154  	return false
   155  }
   156  
   157  func traceReq(req *http.Request) string {
   158  	return fmt.Sprintf("%s %s", req.Method, strings.SplitN(req.URL.String(), "?", 2)[0])
   159  }