all repos — flounder @ fd90cb7259cd85bf9321f577cf085e434d4cbfa6

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(req.RemoteAddr)
 41	if err != nil {
 42		host = req.RemoteAddr
 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	buf := make([]byte, 0, 3*(len(host)+len(username)+len(req.Method)+len(uri)+len(req.Proto)+50)/2)
 58	buf = append(buf, host...)
 59	buf = append(buf, " - "...)
 60	buf = append(buf, username...)
 61	buf = append(buf, " ["...)
 62	buf = append(buf, ts.Format("02/Jan/2006:15:04:05 -0700")...)
 63	buf = append(buf, `] "`...)
 64	buf = append(buf, req.Method...)
 65	buf = append(buf, " "...)
 66	buf = appendQuoted(buf, uri)
 67	buf = append(buf, " "...)
 68	buf = append(buf, req.Proto...)
 69	buf = append(buf, `" `...)
 70	buf = append(buf, strconv.Itoa(status)...)
 71	buf = append(buf, " "...)
 72	buf = append(buf, strconv.Itoa(size)...)
 73	return buf
 74}
 75
 76func appendQuoted(buf []byte, s string) []byte {
 77	var runeTmp [utf8.UTFMax]byte
 78	for width := 0; len(s) > 0; s = s[width:] {
 79		r := rune(s[0])
 80		width = 1
 81		if r >= utf8.RuneSelf {
 82			r, width = utf8.DecodeRuneInString(s)
 83		}
 84		if width == 1 && r == utf8.RuneError {
 85			buf = append(buf, `\x`...)
 86			buf = append(buf, lowerhex[s[0]>>4])
 87			buf = append(buf, lowerhex[s[0]&0xF])
 88			continue
 89		}
 90		if r == rune('"') || r == '\\' { // always backslashed
 91			buf = append(buf, '\\')
 92			buf = append(buf, byte(r))
 93			continue
 94		}
 95		if strconv.IsPrint(r) {
 96			n := utf8.EncodeRune(runeTmp[:], r)
 97			buf = append(buf, runeTmp[:n]...)
 98			continue
 99		}
100		switch r {
101		case '\a':
102			buf = append(buf, `\a`...)
103		case '\b':
104			buf = append(buf, `\b`...)
105		case '\f':
106			buf = append(buf, `\f`...)
107		case '\n':
108			buf = append(buf, `\n`...)
109		case '\r':
110			buf = append(buf, `\r`...)
111		case '\t':
112			buf = append(buf, `\t`...)
113		case '\v':
114			buf = append(buf, `\v`...)
115		default:
116			switch {
117			case r < ' ':
118				buf = append(buf, `\x`...)
119				buf = append(buf, lowerhex[s[0]>>4])
120				buf = append(buf, lowerhex[s[0]&0xF])
121			case r > utf8.MaxRune:
122				r = 0xFFFD
123				fallthrough
124			case r < 0x10000:
125				buf = append(buf, `\u`...)
126				for s := 12; s >= 0; s -= 4 {
127					buf = append(buf, lowerhex[r>>uint(s)&0xF])
128				}
129			default:
130				buf = append(buf, `\U`...)
131				for s := 28; s >= 0; s -= 4 {
132					buf = append(buf, lowerhex[r>>uint(s)&0xF])
133				}
134			}
135		}
136	}
137	return buf
138}
139
140// Parse logs and write to database
141
142// Anonymize user and IP?