[mirror] Scalable static site server for Git forges (like GitHub Pages)
at latest 4.7 kB view raw
1package git_pages 2 3import ( 4 "cmp" 5 "fmt" 6 "net" 7 "net/http" 8 "regexp" 9 "slices" 10 "strconv" 11 "strings" 12) 13 14var httpAcceptRegexp = regexp.MustCompile(`` + 15 // token optionally prefixed by whitespace 16 `^[ \t]*([a-zA-Z0-9$!#$%&'*+./^_\x60|~-]+)` + 17 // quality value prefixed by a semicolon optionally surrounded by whitespace 18 `(?:[ \t]*;[ \t]*q=(0(?:\.[0-9]{1,3})?|1(?:\.0{1,3})?))?` + 19 // optional whitespace followed by comma or end of line 20 `[ \t]*(?:,|$)`, 21) 22 23type httpAcceptOffer struct { 24 code string 25 qval float64 26} 27 28func parseGenericAcceptHeader(headerValue string) (result []httpAcceptOffer) { 29 for headerValue != "" { 30 matches := httpAcceptRegexp.FindStringSubmatch(headerValue) 31 if matches == nil { 32 return 33 } 34 offer := httpAcceptOffer{strings.ToLower(matches[1]), 1.0} 35 if matches[2] != "" { 36 offer.qval, _ = strconv.ParseFloat(matches[2], 64) 37 } 38 result = append(result, offer) 39 headerValue = headerValue[len(matches[0]):] 40 } 41 return 42} 43 44func preferredAcceptOffer(offers []httpAcceptOffer) string { 45 slices.SortStableFunc(offers, func(a, b httpAcceptOffer) int { 46 return -cmp.Compare(a.qval, b.qval) 47 }) 48 for _, offer := range offers { 49 if offer.qval != 0 { 50 return offer.code 51 } 52 } 53 return "" 54} 55 56type HTTPContentTypes struct { 57 contentTypes []httpAcceptOffer 58} 59 60func ParseAcceptHeader(headerValue string) (result HTTPContentTypes) { 61 if headerValue == "" { 62 headerValue = "*/*" 63 } 64 result = HTTPContentTypes{parseGenericAcceptHeader(headerValue)} 65 return 66} 67 68func (e *HTTPContentTypes) Negotiate(offers ...string) string { 69 prefs := make(map[string]float64, len(offers)) 70 for _, code := range offers { 71 prefs[code] = 0 72 } 73 for _, ctyp := range e.contentTypes { 74 if ctyp.code == "*/*" { 75 for code := range prefs { 76 prefs[code] = ctyp.qval 77 } 78 } else if _, ok := prefs[ctyp.code]; ok { 79 prefs[ctyp.code] = ctyp.qval 80 } 81 } 82 ctyps := make([]httpAcceptOffer, len(offers)) 83 for idx, code := range offers { 84 ctyps[idx] = httpAcceptOffer{code, prefs[code]} 85 } 86 return preferredAcceptOffer(ctyps) 87} 88 89type HTTPEncodings struct { 90 encodings []httpAcceptOffer 91} 92 93func ParseAcceptEncodingHeader(headerValue string) (result HTTPEncodings) { 94 result = HTTPEncodings{parseGenericAcceptHeader(headerValue)} 95 if len(result.encodings) == 0 { 96 // RFC 9110 says (https://httpwg.org/specs/rfc9110.html#field.accept-encoding): 97 // "If no Accept-Encoding header field is in the request, any content 98 // coding is considered acceptable by the user agent." 99 // In practice, no client expects to receive a compressed response 100 // without having sent Accept-Encoding in the request. 101 } 102 return 103} 104 105// Negotiate returns the most preferred encoding that is acceptable by the 106// client, or an empty string if no encodings are acceptable. 107func (e *HTTPEncodings) Negotiate(offers ...string) string { 108 prefs := make(map[string]float64, len(offers)) 109 for _, code := range offers { 110 prefs[code] = 0 111 } 112 implicitIdentity := true 113 for _, enc := range e.encodings { 114 if enc.code == "*" { 115 for code := range prefs { 116 prefs[code] = enc.qval 117 } 118 implicitIdentity = false 119 } else if _, ok := prefs[enc.code]; ok { 120 prefs[enc.code] = enc.qval 121 } 122 if enc.code == "*" || enc.code == "identity" { 123 implicitIdentity = false 124 } 125 } 126 if _, ok := prefs["identity"]; ok && implicitIdentity { 127 prefs["identity"] = -1 // sort last 128 } 129 encs := make([]httpAcceptOffer, len(offers)) 130 for idx, code := range offers { 131 encs[idx] = httpAcceptOffer{code, prefs[code]} 132 } 133 return preferredAcceptOffer(encs) 134} 135 136func chainHTTPMiddleware(middleware ...func(http.Handler) http.Handler) func(http.Handler) http.Handler { 137 return func(handler http.Handler) http.Handler { 138 for idx := len(middleware) - 1; idx >= 0; idx-- { 139 handler = middleware[idx](handler) 140 } 141 return handler 142 } 143} 144 145func remoteAddrMiddleware(handler http.Handler) http.Handler { 146 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 147 var readXForwardedFor bool 148 switch config.Audit.IncludeIPs { 149 case "X-Forwarded-For": 150 readXForwardedFor = true 151 case "RemoteAddr", "": 152 readXForwardedFor = false 153 default: 154 panic(fmt.Errorf("config.Audit.IncludeIPs is set to an unknown value (%q)", 155 config.Audit.IncludeIPs)) 156 } 157 158 usingOriginalRemoteAddr := true 159 if readXForwardedFor { 160 forwardedFor := strings.Split(r.Header.Get("X-Forwarded-For"), ",") 161 if len(forwardedFor) > 0 { 162 remoteAddr := strings.TrimSpace(forwardedFor[len(forwardedFor)-1]) 163 if remoteAddr != "" { 164 r.RemoteAddr = remoteAddr 165 usingOriginalRemoteAddr = false 166 } 167 } 168 } 169 if usingOriginalRemoteAddr { 170 if ipAddress, _, err := net.SplitHostPort(r.RemoteAddr); err == nil { 171 r.RemoteAddr = ipAddress 172 } 173 } 174 175 handler.ServeHTTP(w, r) 176 }) 177}