1 package httpretty
2
3 import (
4 "bufio"
5 "bytes"
6 "crypto/tls"
7 "crypto/x509"
8 "fmt"
9 "io"
10 "io/ioutil"
11 "mime"
12 "net"
13 "net/http"
14 "sort"
15 "strings"
16 "time"
17
18 "github.com/henvic/httpretty/internal/color"
19 "github.com/henvic/httpretty/internal/header"
20 )
21
22 func newPrinter(l *Logger) printer {
23 l.mu.Lock()
24 defer l.mu.Unlock()
25
26 return printer{
27 logger: l,
28 flusher: l.flusher,
29 }
30 }
31
32 type printer struct {
33 flusher Flusher
34
35 logger *Logger
36 buf bytes.Buffer
37 }
38
39 func (p *printer) maybeOnReady() {
40 if p.flusher == OnReady {
41 p.flush()
42 }
43 }
44
45 func (p *printer) flush() {
46 if p.flusher == NoBuffer {
47 return
48 }
49
50 p.logger.mu.Lock()
51 defer p.logger.mu.Unlock()
52 defer p.buf.Reset()
53 w := p.logger.getWriter()
54 fmt.Fprint(w, p.buf.String())
55 }
56
57 func (p *printer) print(a ...interface{}) {
58 p.logger.mu.Lock()
59 defer p.logger.mu.Unlock()
60 w := p.logger.getWriter()
61
62 if p.flusher == NoBuffer {
63 fmt.Fprint(w, a...)
64 return
65 }
66
67 fmt.Fprint(&p.buf, a...)
68 }
69
70 func (p *printer) println(a ...interface{}) {
71 p.logger.mu.Lock()
72 defer p.logger.mu.Unlock()
73 w := p.logger.getWriter()
74
75 if p.flusher == NoBuffer {
76 fmt.Fprintln(w, a...)
77 return
78 }
79
80 fmt.Fprintln(&p.buf, a...)
81 }
82
83 func (p *printer) printf(format string, a ...interface{}) {
84 p.logger.mu.Lock()
85 defer p.logger.mu.Unlock()
86 w := p.logger.getWriter()
87
88 if p.flusher == NoBuffer {
89 fmt.Fprintf(w, format, a...)
90 return
91 }
92
93 fmt.Fprintf(&p.buf, format, a...)
94 }
95
96 func (p *printer) printRequest(req *http.Request) {
97 if p.logger.RequestHeader {
98 p.printRequestHeader(req)
99 p.maybeOnReady()
100 }
101
102 if p.logger.RequestBody && req.Body != nil {
103 p.printRequestBody(req)
104 p.maybeOnReady()
105 }
106 }
107
108 func (p *printer) printRequestInfo(req *http.Request) {
109 to := req.URL.String()
110
111
112 if req.URL.Host == "" {
113 to = req.Host + to
114 schema := "http://"
115
116 if req.TLS != nil {
117 schema = "https://"
118 }
119
120 to = schema + to
121 }
122
123 p.printf("* Request to %s\n", p.format(color.FgBlue, to))
124
125 if req.RemoteAddr != "" {
126 p.printf("* Request from %s\n", p.format(color.FgBlue, req.RemoteAddr))
127 }
128 }
129
130
131 func (p *printer) checkFilter(req *http.Request) (skip bool) {
132 filter := p.logger.getFilter()
133
134 if req == nil {
135 p.printf("> %s\n", p.format(color.FgRed, "error: null request"))
136 return true
137 }
138
139 if filter == nil {
140 return false
141 }
142
143 ok, err := safeFilter(filter, req)
144
145 if err != nil {
146 p.printf("* cannot filter request: %s: %s\n", p.format(color.FgBlue, fmt.Sprintf("%s %s", req.Method, req.URL)), p.format(color.FgRed, err.Error()))
147 return false
148 }
149
150 return ok
151 }
152
153 func safeFilter(filter Filter, req *http.Request) (skip bool, err error) {
154 defer func() {
155 if e := recover(); e != nil {
156 err = fmt.Errorf("panic: %v", e)
157 }
158 }()
159 return filter(req)
160 }
161
162 func (p *printer) printResponse(resp *http.Response) {
163 if resp == nil {
164 p.printf("< %s\n", p.format(color.FgRed, "error: null response"))
165 p.maybeOnReady()
166 return
167 }
168
169 if p.logger.ResponseHeader {
170 p.printResponseHeader(resp.Proto, resp.Status, resp.Header)
171 p.maybeOnReady()
172 }
173
174 if p.logger.ResponseBody && resp.Body != nil && (resp.Request == nil || resp.Request.Method != http.MethodHead) {
175 p.printResponseBodyOut(resp)
176 p.maybeOnReady()
177 }
178
179 }
180
181 func (p *printer) checkBodyFiltered(h http.Header) (skip bool, err error) {
182 if f := p.logger.getBodyFilter(); f != nil {
183 defer func() {
184 if e := recover(); e != nil {
185 p.printf("* panic while filtering body: %v\n", e)
186 }
187 }()
188 return f(h)
189 }
190 return false, nil
191 }
192
193 func (p *printer) printResponseBodyOut(resp *http.Response) {
194 if resp.ContentLength == 0 {
195 return
196 }
197
198 skip, err := p.checkBodyFiltered(resp.Header)
199
200 if err != nil {
201 p.printf("* %s\n", p.format(color.FgRed, "error on response body filter: ", err.Error()))
202 }
203
204 if skip {
205 return
206 }
207
208 if contentType := resp.Header.Get("Content-Type"); contentType != "" && isBinaryMediatype(contentType) {
209 p.println("* body contains binary data")
210 return
211 }
212
213 if p.logger.MaxResponseBody > 0 && resp.ContentLength > p.logger.MaxResponseBody {
214 p.printf("* body is too long (%d bytes) to print, skipping (longer than %d bytes)\n", resp.ContentLength, p.logger.MaxResponseBody)
215 return
216 }
217
218 contentType := resp.Header.Get("Content-Type")
219
220 if resp.ContentLength == -1 {
221 if newBody := p.printBodyUnknownLength(contentType, p.logger.MaxResponseBody, resp.Body); newBody != nil {
222 resp.Body = newBody
223 }
224
225 return
226 }
227
228 var buf bytes.Buffer
229 tee := io.TeeReader(resp.Body, &buf)
230 defer resp.Body.Close()
231
232 defer func() {
233 resp.Body = ioutil.NopCloser(&buf)
234 }()
235
236 p.printBodyReader(contentType, tee)
237 }
238
239
240
241 func isBinary(body []byte) bool {
242 if len(body) > 512 {
243 body = body[512:]
244 }
245
246
247
248 if len(body) >= 3 && (bytes.Equal(body[:2], []byte{0xFE, 0xFF}) ||
249 bytes.Equal(body[:2], []byte{0xFF, 0xFE}) ||
250 bytes.Equal(body[:3], []byte{0xEF, 0xBB, 0xBF})) {
251 return false
252 }
253
254
255
256
257 for _, b := range body {
258 switch {
259 case b <= 0x08,
260 b == 0x0B,
261 0x0E <= b && b <= 0x1A,
262 0x1C <= b && b <= 0x1F:
263 return true
264 }
265 }
266
267
268 mediatype, _, err := mime.ParseMediaType(http.DetectContentType(body))
269 if err != nil {
270 return false
271 }
272
273 return isBinaryMediatype(mediatype)
274 }
275
276 var binaryMediatypes = map[string]struct{}{
277 "application/pdf": struct{}{},
278 "application/postscript": struct{}{},
279 "image": struct{}{},
280 "audio": struct{}{},
281 "application/ogg": struct{}{},
282 "video": struct{}{},
283 "application/vnd.ms-fontobject": struct{}{},
284 "font": struct{}{},
285 "application/x-gzip": struct{}{},
286 "application/zip": struct{}{},
287 "application/x-rar-compressed": struct{}{},
288 "application/wasm": struct{}{},
289 }
290
291 func isBinaryMediatype(mediatype string) bool {
292 if _, ok := binaryMediatypes[mediatype]; ok {
293 return true
294 }
295
296 if parts := strings.SplitN(mediatype, "/", 2); len(parts) == 2 {
297 if _, ok := binaryMediatypes[parts[0]]; ok {
298 return true
299 }
300 }
301
302 return false
303 }
304
305 const maxDefaultUnknownReadable = 4096
306
307 func (p *printer) printBodyUnknownLength(contentType string, maxLength int64, r io.ReadCloser) (newBody io.ReadCloser) {
308 shortReader := bufio.NewReader(r)
309
310 if maxLength == 0 {
311 maxLength = maxDefaultUnknownReadable
312 }
313
314 pb := make([]byte, maxLength+1)
315 n, err := io.ReadFull(shortReader, pb)
316 pb = pb[0:n]
317 buf := bytes.NewReader(pb)
318 newBody = newBodyReaderBuf(buf, r)
319
320 switch {
321
322
323
324 case err == io.EOF && n == 0:
325 case err == nil && int64(n) > maxLength:
326 p.printf("* body is too long, skipping (contains more than %d bytes)\n", n-1)
327 case err == io.ErrUnexpectedEOF || err == nil:
328
329 p.printBodyReader(contentType, bytes.NewReader(pb))
330 default:
331 p.printf("* cannot read body: %v (%d bytes read)\n", err, n)
332 }
333 return
334 }
335
336 func findPeerCertificate(hostname string, state *tls.ConnectionState) (cert *x509.Certificate) {
337 if chains := state.VerifiedChains; chains != nil && chains[0] != nil && chains[0][0] != nil {
338 return chains[0][0]
339 }
340
341 if hostname == "" && len(state.PeerCertificates) > 0 {
342
343 return state.PeerCertificates[0]
344 }
345
346
347 for _, cert := range state.PeerCertificates {
348 if err := cert.VerifyHostname(hostname); err == nil {
349 return cert
350 }
351 }
352
353 return nil
354 }
355
356 func (p *printer) printTLSInfo(state *tls.ConnectionState, skipVerifyChains bool) {
357 if state == nil {
358 return
359 }
360
361 protocol := tlsProtocolVersions[state.Version]
362
363 if protocol == "" {
364 protocol = fmt.Sprintf("%#v", state.Version)
365 }
366
367 cipher := tlsCiphers[state.CipherSuite]
368
369 if cipher == "" {
370 cipher = fmt.Sprintf("%#v", state.CipherSuite)
371 }
372
373 p.printf("* TLS connection using %s / %s", p.format(color.FgBlue, protocol), p.format(color.FgBlue, cipher))
374
375 if !skipVerifyChains && state.VerifiedChains == nil {
376 p.print(" (insecure=true)")
377 }
378
379 p.println()
380
381 if state.NegotiatedProtocol != "" {
382 p.printf("* ALPN: %v accepted\n", p.format(color.FgBlue, state.NegotiatedProtocol))
383 }
384 }
385
386 func (p *printer) printOutgoingClientTLS(config *tls.Config) {
387 if config == nil || len(config.Certificates) == 0 {
388 return
389 }
390
391 p.println("* Client certificate:")
392 cert := config.Certificates[0].Leaf
393
394 if cert == nil {
395
396
397
398 p.println(`** unparsed certificate found, skipping`)
399 return
400 }
401
402 p.printCertificate("", cert)
403 }
404
405 func (p *printer) printIncomingClientTLS(state *tls.ConnectionState) {
406
407 if state == nil || len(state.PeerCertificates) == 0 {
408 return
409 }
410
411 p.println("* Client certificate:")
412 cert := findPeerCertificate("", state)
413
414 if cert == nil {
415 p.println(p.format(color.FgRed, "** No valid certificate was found"))
416 return
417 }
418
419 p.printCertificate("", cert)
420 }
421
422 func (p *printer) printTLSServer(host string, state *tls.ConnectionState) {
423 if state == nil {
424 return
425 }
426
427 hostname, _, err := net.SplitHostPort(host)
428
429 if err != nil {
430
431 hostname = host
432 }
433
434 p.println("* Server certificate:")
435 cert := findPeerCertificate(hostname, state)
436
437 if cert == nil {
438 p.println(p.format(color.FgRed, "** No valid certificate was found"))
439 return
440 }
441
442
443 p.printCertificate(hostname, cert)
444 }
445
446 func (p *printer) printCertificate(hostname string, cert *x509.Certificate) {
447 p.printf(`* subject: %v
448 * start date: %v
449 * expire date: %v
450 * issuer: %v
451 `,
452 p.format(color.FgBlue, cert.Subject),
453 p.format(color.FgBlue, cert.NotBefore.Format(time.UnixDate)),
454 p.format(color.FgBlue, cert.NotAfter.Format(time.UnixDate)),
455 p.format(color.FgBlue, cert.Issuer),
456 )
457
458 if hostname == "" {
459 return
460 }
461
462 if err := cert.VerifyHostname(hostname); err != nil {
463 p.printf("* %s\n", p.format(color.FgRed, err.Error()))
464 return
465 }
466
467 p.println("* TLS certificate verify ok.")
468 }
469
470 func (p *printer) printServerResponse(req *http.Request, rec *responseRecorder) {
471 if p.logger.ResponseHeader {
472
473
474 p.printResponseHeader(req.Proto, fmt.Sprintf("%d %s", rec.statusCode, http.StatusText(rec.statusCode)), rec.Header())
475 }
476
477 if !p.logger.ResponseBody || rec.size == 0 {
478 return
479 }
480
481 skip, err := p.checkBodyFiltered(rec.Header())
482
483 if err != nil {
484 p.printf("* %s\n", p.format(color.FgRed, "error on response body filter: ", err.Error()))
485 }
486
487 if skip {
488 return
489 }
490
491 if mediatype := req.Header.Get("Content-Type"); mediatype != "" && isBinaryMediatype(mediatype) {
492 p.println("* body contains binary data")
493 return
494 }
495
496 if p.logger.MaxResponseBody > 0 && rec.size > p.logger.MaxResponseBody {
497 p.printf("* body is too long (%d bytes) to print, skipping (longer than %d bytes)\n", rec.size, p.logger.MaxResponseBody)
498 return
499 }
500
501 p.printBodyReader(rec.Header().Get("Content-Type"), rec.buf)
502 }
503
504 func (p *printer) printResponseHeader(proto, status string, h http.Header) {
505 p.printf("< %s %s\n",
506 p.format(color.FgBlue, color.Bold, proto),
507 p.format(color.FgRed, status))
508
509 p.printHeaders('<', h)
510 p.println()
511 }
512
513 func (p *printer) printBodyReader(contentType string, r io.Reader) {
514 mediatype, _, _ := mime.ParseMediaType(contentType)
515 body, err := ioutil.ReadAll(r)
516
517 if err != nil {
518 p.printf("* cannot read body: %v\n", p.format(color.FgRed, err.Error()))
519 return
520 }
521
522 if isBinary(body) {
523 p.println("* body contains binary data")
524 return
525 }
526
527 for _, f := range p.logger.Formatters {
528 if ok := p.safeBodyMatch(f, mediatype); !ok {
529 continue
530 }
531
532 var formatted bytes.Buffer
533 switch err := p.safeBodyFormat(f, &formatted, body); {
534 case err != nil:
535 p.printf("* body cannot be formatted: %v\n%s\n", p.format(color.FgRed, err.Error()), string(body))
536 default:
537 p.println(formatted.String())
538 }
539 return
540 }
541
542 p.println(string(body))
543 }
544
545 func (p *printer) safeBodyMatch(f Formatter, mediatype string) bool {
546 defer func() {
547 if e := recover(); e != nil {
548 p.printf("* panic while testing body format: %v\n", e)
549 }
550 }()
551
552 return f.Match(mediatype)
553 }
554
555 func (p *printer) safeBodyFormat(f Formatter, w io.Writer, src []byte) (err error) {
556 defer func() {
557
558 if e := recover(); e != nil {
559 err = fmt.Errorf("panic: %v", e)
560 }
561 }()
562
563 return f.Format(w, src)
564 }
565
566 func (p *printer) format(s ...interface{}) string {
567 if p.logger.Colors {
568 return color.Format(s...)
569 }
570
571 return color.StripAttributes(s...)
572 }
573
574 func (p *printer) printHeaders(prefix rune, h http.Header) {
575 if !p.logger.SkipSanitize {
576 h = header.Sanitize(header.DefaultSanitizers, h)
577 }
578
579 skipped := p.logger.cloneSkipHeader()
580
581 for _, key := range sortHeaderKeys(h) {
582 for _, v := range h[key] {
583 if _, skip := skipped[key]; skip {
584 continue
585 }
586 p.printf("%c %s%s %s\n", prefix,
587 p.format(color.FgBlue, color.Bold, key),
588 p.format(color.FgRed, ":"),
589 p.format(color.FgYellow, v))
590 }
591 }
592 }
593
594 func sortHeaderKeys(h http.Header) []string {
595 keys := make([]string, 0, len(h))
596
597 for key := range h {
598 keys = append(keys, key)
599 }
600
601 sort.Strings(keys)
602
603 return keys
604 }
605
606 func (p *printer) printRequestHeader(req *http.Request) {
607 p.printf("> %s %s %s\n",
608 p.format(color.FgBlue, color.Bold, req.Method),
609 p.format(color.FgYellow, req.URL.RequestURI()),
610 p.format(color.FgBlue, req.Proto))
611
612 host := req.Host
613
614 if host == "" {
615 host = req.URL.Host
616 }
617
618 if host != "" {
619 p.printf("> %s%s %s\n",
620 p.format(color.FgBlue, color.Bold, "Host"),
621 p.format(color.FgRed, ":"),
622 p.format(color.FgYellow, host),
623 )
624 }
625
626 p.printHeaders('>', req.Header)
627 p.println()
628 }
629
630 func (p *printer) printRequestBody(req *http.Request) {
631
632 if req.Body == nil {
633 return
634 }
635
636 skip, err := p.checkBodyFiltered(req.Header)
637
638 if err != nil {
639 p.printf("* %s\n", p.format(color.FgRed, "error on request body filter: ", err.Error()))
640 }
641
642 if skip {
643 return
644 }
645
646 if mediatype := req.Header.Get("Content-Type"); mediatype != "" && isBinaryMediatype(mediatype) {
647 p.println("* body contains binary data")
648 return
649 }
650
651
652 if p.logger.MaxRequestBody > 0 && req.ContentLength > p.logger.MaxRequestBody {
653 p.printf("* body is too long (%d bytes) to print, skipping (longer than %d bytes)\n",
654 req.ContentLength, p.logger.MaxRequestBody)
655 return
656 }
657
658 contentType := req.Header.Get("Content-Type")
659
660 if req.ContentLength > 0 {
661 var buf bytes.Buffer
662 tee := io.TeeReader(req.Body, &buf)
663 defer req.Body.Close()
664
665 defer func() {
666 req.Body = ioutil.NopCloser(&buf)
667 }()
668
669 p.printBodyReader(contentType, tee)
670 return
671 }
672
673 if newBody := p.printBodyUnknownLength(contentType, p.logger.MaxRequestBody, req.Body); newBody != nil {
674 req.Body = newBody
675 }
676 }
677
678 func (p *printer) printTimeRequest() (end func()) {
679 startRequest := time.Now()
680
681 p.printf("* Request at %v\n", startRequest)
682
683 return func() {
684 p.printf("* Request took %v\n", time.Since(startRequest))
685 }
686 }
687
View as plain text