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) (p *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) (n int32) {
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 + int32(s[i]-'0')
73 }
74 return n
75 }
76
77 func buildHTTPResponse(status int32, headers map[string]string, body []byte) (buf []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 = buf | "HTTP/1.1 "
118 buf = appendInt(buf, status)
119 buf = buf | " "
120 buf = buf | statusText
121 buf = buf | "\r\n"
122 hasCL := false
123 for k, v := range headers {
124 if k == "Content-Length" {
125 hasCL = true
126 }
127 buf = buf | k
128 buf = buf | ": "
129 buf = buf | v
130 buf = buf | "\r\n"
131 }
132 if !hasCL {
133 buf = buf | "Content-Length: "
134 buf = appendInt(buf, len(body))
135 buf = buf | "\r\n"
136 }
137 buf = buf | "Connection: keep-alive\r\n"
138 buf = buf | "\r\n"
139 buf = buf | body
140 return buf
141 }
142
143 func writeHTTPResponse(fd int32, status int32, headers map[string]string, body []byte) (ok 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) (m 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 int32) (buf2 []byte) {
177 if n == 0 {
178 return buf | "0"
179 }
180 if n < 0 {
181 buf = 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 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 int32) (buf2 []byte) { return appendInt(buf, n) }
196
197 func AppendStr(buf []byte, s string) (buf2 []byte) { return buf | s }
198
199 func AppendJSONString(buf []byte, s string) (buf2 []byte) {
200 buf = buf | "\""
201 for i := 0; i < len(s); i++ {
202 c := s[i]
203 switch c {
204 case '"':
205 buf = buf | "\\\""
206 case '\\':
207 buf = buf | "\\\\"
208 case '\n':
209 buf = buf | "\\n"
210 case '\r':
211 buf = buf | "\\r"
212 case '\t':
213 buf = buf | "\\t"
214 default:
215 if c < 0x20 {
216 buf = buf | "\\u00"
217 buf = buf | string([]byte{hexNibble(c >> 4), hexNibble(c & 0x0f)})
218 } else {
219 buf = buf | string([]byte{c})
220 }
221 }
222 }
223 buf = buf | "\""
224 return buf
225 }
226
227 func hexNibble(b byte) (bv 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) (s 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) (ok bool) {
246 return len(s) >= len(prefix) && s[:len(prefix)] == prefix
247 }
248
249 func hasPrefix(s, prefix string) (ok bool) {
250 return len(s) >= len(prefix) && s[:len(prefix)] == prefix
251 }
252
253 var botAgents []string
254
255 func isBot(ua string) (ok bool) {
256 if ua == "" {
257 return false
258 }
259 for _, bot := range botAgents {
260 if bytes.Contains([]byte(ua), []byte(bot)) {
261 return true
262 }
263 }
264 return false
265 }
266
267 func makeCopy(b []byte) (buf []byte) {
268 c := []byte{:len(b)}
269 copy(c, b)
270 return c
271 }
272
273 func trimSpace(b []byte) (buf []byte) {
274 for len(b) > 0 && (b[0] == ' ' || b[0] == '\t') {
275 b = b[1:]
276 }
277 for len(b) > 0 && (b[len(b)-1] == ' ' || b[len(b)-1] == '\t') {
278 b = b[:len(b)-1]
279 }
280 return b
281 }
282
283 func toLower(b []byte) (buf []byte) {
284 for i := range b {
285 if b[i] >= 'A' && b[i] <= 'Z' {
286 b[i] = b[i] + 32
287 }
288 }
289 return b
290 }
291
292 // ParseAddr splits "host:port" into ([4]byte IP, int32 port).
293 func ParseAddr(addr string) (ip [4]byte, n int32) {
294 ab := []byte(addr)
295 var ip [4]byte
296 colon := -1
297 for i := len(ab) - 1; i >= 0; i-- {
298 if ab[i] == ':' {
299 colon = i
300 break
301 }
302 }
303 if colon < 0 {
304 return ip, 0
305 }
306 port := 0
307 for i := colon + 1; i < len(ab); i++ {
308 port = port*10 + int32(ab[i]-'0')
309 }
310 host := ab[:colon]
311 if len(host) > 0 {
312 octet := 0
313 idx := 0
314 for i := 0; i < len(host); i++ {
315 if host[i] == '.' {
316 if idx < 4 {
317 ip[idx] = byte(octet)
318 }
319 idx++
320 octet = 0
321 } else {
322 octet = octet*10 + int32(host[i]-'0')
323 }
324 }
325 if idx < 4 {
326 ip[idx] = byte(octet)
327 }
328 }
329 return ip, port
330 }
331