http.mx raw
1 package transport
2
3 import "bytes"
4
5 // parseHTTPHeaders parses request-line + headers from a header block (no body).
6 func parseHTTPHeaders(data []byte) *httpReq {
7 lineEnd := bytes.IndexByte(data, '\n')
8 if lineEnd < 0 {
9 return nil
10 }
11 line := data[:lineEnd]
12 if len(line) > 0 && line[len(line)-1] == '\r' {
13 line = line[:len(line)-1]
14 }
15
16 sp1 := bytes.IndexByte(line, ' ')
17 if sp1 < 0 {
18 return nil
19 }
20 method := string(makeCopy(line[:sp1]))
21 rest := line[sp1+1:]
22 sp2 := bytes.IndexByte(rest, ' ')
23 var path string
24 if sp2 >= 0 {
25 path = string(makeCopy(rest[:sp2]))
26 } else {
27 path = string(makeCopy(rest))
28 }
29 if qi := bytes.IndexByte([]byte(path), '?'); qi >= 0 {
30 path = path[:qi]
31 }
32
33 headers := map[string]string{}
34 pos := lineEnd + 1
35 for pos < len(data) {
36 nlPos := bytes.IndexByte(data[pos:], '\n')
37 var hline []byte
38 if nlPos < 0 {
39 hline = data[pos:]
40 pos = len(data)
41 } else {
42 nlPos += pos
43 hline = data[pos:nlPos]
44 pos = nlPos + 1
45 }
46 if len(hline) > 0 && hline[len(hline)-1] == '\r' {
47 hline = hline[:len(hline)-1]
48 }
49 if len(hline) == 0 {
50 continue
51 }
52 colon := bytes.IndexByte(hline, ':')
53 if colon >= 0 {
54 key := string(toLower(makeCopy(hline[:colon])))
55 val := string(makeCopy(trimSpace(hline[colon+1:])))
56 headers[key] = val
57 }
58 }
59
60 return &httpReq{method: method, path: path, headers: headers}
61 }
62
63 func parseContentLength(s string) int {
64 if s == "" {
65 return 0
66 }
67 n := 0
68 for i := 0; i < len(s); i++ {
69 if s[i] < '0' || s[i] > '9' {
70 return 0
71 }
72 n = n*10 + int(s[i]-'0')
73 }
74 return n
75 }
76
77 func buildHTTPResponse(status int, headers map[string]string, body []byte) []byte {
78 statusText := "OK"
79 switch status {
80 case 200:
81 statusText = "OK"
82 case 204:
83 statusText = "No Content"
84 case 206:
85 statusText = "Partial Content"
86 case 301:
87 statusText = "Moved Permanently"
88 case 302:
89 statusText = "Found"
90 case 304:
91 statusText = "Not Modified"
92 case 400:
93 statusText = "Bad Request"
94 case 401:
95 statusText = "Unauthorized"
96 case 403:
97 statusText = "Forbidden"
98 case 404:
99 statusText = "Not Found"
100 case 405:
101 statusText = "Method Not Allowed"
102 case 415:
103 statusText = "Unsupported Media Type"
104 case 429:
105 statusText = "Too Many Requests"
106 case 500:
107 statusText = "Internal Server Error"
108 case 502:
109 statusText = "Bad Gateway"
110 case 503:
111 statusText = "Service Unavailable"
112 case 504:
113 statusText = "Gateway Timeout"
114 }
115
116 var buf []byte
117 buf = append(buf, "HTTP/1.1 "...)
118 buf = appendInt(buf, status)
119 buf = append(buf, ' ')
120 buf = append(buf, statusText...)
121 buf = append(buf, "\r\n"...)
122 hasCL := false
123 for k, v := range headers {
124 if k == "Content-Length" {
125 hasCL = true
126 }
127 buf = append(buf, k...)
128 buf = append(buf, ": "...)
129 buf = append(buf, v...)
130 buf = append(buf, "\r\n"...)
131 }
132 if !hasCL {
133 buf = append(buf, "Content-Length: "...)
134 buf = appendInt(buf, len(body))
135 buf = append(buf, "\r\n"...)
136 }
137 buf = append(buf, "Connection: keep-alive\r\n"...)
138 buf = append(buf, "\r\n"...)
139 buf = append(buf, body...)
140 return buf
141 }
142
143 func writeHTTPResponse(fd int, status int, headers map[string]string, body []byte) bool {
144 return writeAll(fd, buildHTTPResponse(status, headers, body)) == nil
145 }
146
147 // DecodeHeaders deserializes null-separated key\0val\0 pairs to a map.
148 // Used by static worker responses.
149 func DecodeHeaders(b []byte) map[string]string {
150 h := map[string]string{}
151 for len(b) > 0 {
152 ki := 0
153 for ki < len(b) && b[ki] != 0 {
154 ki++
155 }
156 if ki >= len(b) {
157 break
158 }
159 k := string(b[:ki])
160 b = b[ki+1:]
161 vi := 0
162 for vi < len(b) && b[vi] != 0 {
163 vi++
164 }
165 v := string(b[:vi])
166 if vi < len(b) {
167 b = b[vi+1:]
168 } else {
169 b = b[vi:]
170 }
171 h[k] = v
172 }
173 return h
174 }
175
176 func appendInt(buf []byte, n int) []byte {
177 if n == 0 {
178 return append(buf, '0')
179 }
180 if n < 0 {
181 buf = append(buf, '-')
182 n = -n
183 }
184 var digits [20]byte
185 i := len(digits)
186 for n > 0 {
187 i--
188 digits[i] = byte('0' + n%10)
189 n /= 10
190 }
191 return append(buf, digits[i:]...)
192 }
193
194 // AppendInt is the exported version used by server for NIP-11 JSON construction.
195 func AppendInt(buf []byte, n int) []byte { return appendInt(buf, n) }
196
197 func AppendStr(buf []byte, s string) []byte { return append(buf, s...) }
198
199 func AppendJSONString(buf []byte, s string) []byte {
200 buf = append(buf, '"')
201 for i := 0; i < len(s); i++ {
202 c := s[i]
203 switch c {
204 case '"':
205 buf = append(buf, '\\', '"')
206 case '\\':
207 buf = append(buf, '\\', '\\')
208 case '\n':
209 buf = append(buf, '\\', 'n')
210 case '\r':
211 buf = append(buf, '\\', 'r')
212 case '\t':
213 buf = append(buf, '\\', 't')
214 default:
215 if c < 0x20 {
216 buf = append(buf, '\\', 'u', '0', '0',
217 hexNibble(c>>4), hexNibble(c&0x0f))
218 } else {
219 buf = append(buf, c)
220 }
221 }
222 }
223 buf = append(buf, '"')
224 return buf
225 }
226
227 func hexNibble(b byte) byte {
228 if b < 10 {
229 return '0' + b
230 }
231 return 'a' + (b - 10)
232 }
233
234 // FirstXFF returns the leftmost IP from an X-Forwarded-For header.
235 func FirstXFF(xff string) string {
236 for i := 0; i < len(xff); i++ {
237 if xff[i] == ',' {
238 return string(makeCopy(trimSpace([]byte(xff[:i]))))
239 }
240 }
241 return string(makeCopy(trimSpace([]byte(xff))))
242 }
243
244 // HasPrefix is exported for use by server.
245 func HasPrefix(s, prefix string) bool {
246 return len(s) >= len(prefix) && s[:len(prefix)] == prefix
247 }
248
249 func hasPrefix(s, prefix string) bool {
250 return len(s) >= len(prefix) && s[:len(prefix)] == prefix
251 }
252
253 var botAgents = []string{
254 "SemrushBot", "AhrefsBot", "GPTBot", "ClaudeBot",
255 "MJ12bot", "DotBot", "PetalBot", "Bytespider",
256 "Sogou", "YandexBot", "BLEXBot", "DataForSeoBot",
257 }
258
259 func isBot(ua string) bool {
260 if ua == "" {
261 return false
262 }
263 for _, bot := range botAgents {
264 if bytes.Contains([]byte(ua), []byte(bot)) {
265 return true
266 }
267 }
268 return false
269 }
270
271 func makeCopy(b []byte) []byte {
272 c := []byte{:len(b)}
273 copy(c, b)
274 return c
275 }
276
277 func trimSpace(b []byte) []byte {
278 for len(b) > 0 && (b[0] == ' ' || b[0] == '\t') {
279 b = b[1:]
280 }
281 for len(b) > 0 && (b[len(b)-1] == ' ' || b[len(b)-1] == '\t') {
282 b = b[:len(b)-1]
283 }
284 return b
285 }
286
287 func toLower(b []byte) []byte {
288 for i := range b {
289 if b[i] >= 'A' && b[i] <= 'Z' {
290 b[i] = b[i] + 32
291 }
292 }
293 return b
294 }
295
296 // ParseAddr splits "host:port" into ([4]byte IP, int port).
297 func ParseAddr(addr string) ([4]byte, int) {
298 ab := []byte(addr)
299 var ip [4]byte
300 colon := -1
301 for i := len(ab) - 1; i >= 0; i-- {
302 if ab[i] == ':' {
303 colon = i
304 break
305 }
306 }
307 if colon < 0 {
308 return ip, 0
309 }
310 port := 0
311 for i := colon + 1; i < len(ab); i++ {
312 port = port*10 + int(ab[i]-'0')
313 }
314 host := ab[:colon]
315 if len(host) > 0 {
316 octet := 0
317 idx := 0
318 for i := 0; i < len(host); i++ {
319 if host[i] == '.' {
320 if idx < 4 {
321 ip[idx] = byte(octet)
322 }
323 idx++
324 octet = 0
325 } else {
326 octet = octet*10 + int(host[i]-'0')
327 }
328 }
329 if idx < 4 {
330 ip[idx] = byte(octet)
331 }
332 }
333 return ip, port
334 }
335
336