all repos — flounder @ 2dff9268624c71e83dc10145e6eb29058f3f0fb2

A small site builder for the Gemini protocol

log.go (view raw)

  1package main
  2
  3import (
  4	"github.com/gorilla/handlers"
  5	"io"
  6	"net"
  7	"net/http"
  8	"net/url"
  9	"strconv"
 10	"time"
 11	"unicode/utf8"
 12)
 13
 14// Copy pasted from gorilla handler library, modified slightly
 15
 16const lowerhex = "0123456789abcdef"
 17
 18func logFormatter(writer io.Writer, params handlers.LogFormatterParams) {
 19	buf := buildCommonLogLine(params.Request, params.URL, params.TimeStamp, params.StatusCode, params.Size)
 20	buf = append(buf, '\n')
 21	writer.Write(buf)
 22}
 23
 24// buildCommonLogLine builds a log entry for req in Apache Common Log Format.
 25// ts is the timestamp with which the entry should be logged.
 26// status and size are used to provide the response HTTP status and size.
 27func buildCommonLogLine(req *http.Request, url url.URL, ts time.Time, status int, size int) []byte {
 28	user := newGetAuthUser(req)
 29	username := "-"
 30	if user.Username != "" {
 31		username = user.Username
 32	}
 33
 34	// Get forwarded IP address
 35	ipAddr := req.Header.Get("X-Real-IP")
 36	if ipAddr == "" {
 37		ipAddr = req.RemoteAddr
 38	}
 39
 40	host, _, err := net.SplitHostPort(ipAddr)
 41	if err != nil {
 42		host = ipAddr
 43	}
 44
 45	uri := req.RequestURI
 46
 47	// Requests using the CONNECT method over HTTP/2.0 must use
 48	// the authority field (aka r.Host) to identify the target.
 49	// Refer: https://httpwg.github.io/specs/rfc7540.html#CONNECT
 50	if req.ProtoMajor == 2 && req.Method == "CONNECT" {
 51		uri = req.Host
 52	}
 53	if uri == "" {
 54		uri = url.RequestURI()
 55	}
 56
 57	desthost := req.Host
 58
 59	buf := make([]byte, 0, 3*(len(host)+len(desthost)+len(username)+len(req.Method)+len(uri)+len(req.Proto)+50)/2)
 60	buf = append(buf, host...)
 61	buf = append(buf, " - "...)
 62	buf = append(buf, username...)
 63	buf = append(buf, " ["...)
 64	buf = append(buf, ts.Format("02/Jan/2006:15:04:05 -0700")...)
 65	buf = append(buf, `] `...)
 66	buf = append(buf, desthost...)
 67	buf = append(buf, ` "`...)
 68	buf = append(buf, req.Method...)
 69	buf = append(buf, " "...)
 70	buf = appendQuoted(buf, uri)
 71	buf = append(buf, " "...)
 72	buf = append(buf, req.Proto...)
 73	buf = append(buf, `" `...)
 74	buf = append(buf, strconv.Itoa(status)...)
 75	buf = append(buf, " "...)
 76	buf = append(buf, strconv.Itoa(size)...)
 77	return buf
 78}
 79
 80func appendQuoted(buf []byte, s string) []byte {
 81	var runeTmp [utf8.UTFMax]byte
 82	for width := 0; len(s) > 0; s = s[width:] {
 83		r := rune(s[0])
 84		width = 1
 85		if r >= utf8.RuneSelf {
 86			r, width = utf8.DecodeRuneInString(s)
 87		}
 88		if width == 1 && r == utf8.RuneError {
 89			buf = append(buf, `\x`...)
 90			buf = append(buf, lowerhex[s[0]>>4])
 91			buf = append(buf, lowerhex[s[0]&0xF])
 92			continue
 93		}
 94		if r == rune('"') || r == '\\' { // always backslashed
 95			buf = append(buf, '\\')
 96			buf = append(buf, byte(r))
 97			continue
 98		}
 99		if strconv.IsPrint(r) {
100			n := utf8.EncodeRune(runeTmp[:], r)
101			buf = append(buf, runeTmp[:n]...)
102			continue
103		}
104		switch r {
105		case '\a':
106			buf = append(buf, `\a`...)
107		case '\b':
108			buf = append(buf, `\b`...)
109		case '\f':
110			buf = append(buf, `\f`...)
111		case '\n':
112			buf = append(buf, `\n`...)
113		case '\r':
114			buf = append(buf, `\r`...)
115		case '\t':
116			buf = append(buf, `\t`...)
117		case '\v':
118			buf = append(buf, `\v`...)
119		default:
120			switch {
121			case r < ' ':
122				buf = append(buf, `\x`...)
123				buf = append(buf, lowerhex[s[0]>>4])
124				buf = append(buf, lowerhex[s[0]&0xF])
125			case r > utf8.MaxRune:
126				r = 0xFFFD
127				fallthrough
128			case r < 0x10000:
129				buf = append(buf, `\u`...)
130				for s := 12; s >= 0; s -= 4 {
131					buf = append(buf, lowerhex[r>>uint(s)&0xF])
132				}
133			default:
134				buf = append(buf, `\U`...)
135				for s := 28; s >= 0; s -= 4 {
136					buf = append(buf, lowerhex[r>>uint(s)&0xF])
137				}
138			}
139		}
140	}
141	return buf
142}
143
144// Parse logs and write to database
145
146// Anonymize user and IP?