-
Notifications
You must be signed in to change notification settings - Fork 15
/
headers.odin
138 lines (117 loc) · 3.61 KB
/
headers.odin
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
package http
import "core:strings"
// A case-insensitive ASCII map for storing headers.
Headers :: struct {
_kv: map[string]string,
readonly: bool,
}
headers_init :: proc(h: ^Headers, allocator := context.temp_allocator) {
h._kv.allocator = allocator
}
headers_count :: #force_inline proc(h: Headers) -> int {
return len(h._kv)
}
/*
Sets a header, given key is first sanitized, final (sanitized) key is returned.
*/
headers_set :: proc(h: ^Headers, k: string, v: string, loc := #caller_location) -> string {
if h.readonly {
panic("these headers are readonly, did you accidentally try to set a header on the request?", loc)
}
l := sanitize_key(h^, k)
h._kv[l] = v
return l
}
/*
Unsafely set header, given key is assumed to be a lowercase string and to be without newlines.
*/
headers_set_unsafe :: #force_inline proc(h: ^Headers, k: string, v: string, loc := #caller_location) {
assert(!h.readonly, "these headers are readonly, did you accidentally try to set a header on the request?", loc)
h._kv[k] = v
}
headers_get :: proc(h: Headers, k: string) -> (string, bool) #optional_ok {
return h._kv[sanitize_key(h, k)]
}
/*
Unsafely get header, given key is assumed to be a lowercase string.
*/
headers_get_unsafe :: #force_inline proc(h: Headers, k: string) -> (string, bool) #optional_ok {
return h._kv[k]
}
headers_has :: proc(h: Headers, k: string) -> bool {
return sanitize_key(h, k) in h._kv
}
/*
Unsafely check for a header, given key is assumed to be a lowercase string.
*/
headers_has_unsafe :: #force_inline proc(h: Headers, k: string) -> bool {
return k in h._kv
}
headers_delete :: proc(h: ^Headers, k: string) -> (deleted_key: string, deleted_value: string) {
return delete_key(&h._kv, sanitize_key(h^, k))
}
/*
Unsafely delete a header, given key is assumed to be a lowercase string.
*/
headers_delete_unsafe :: #force_inline proc(h: ^Headers, k: string) {
delete_key(&h._kv, k)
}
/* Common Helpers */
headers_set_content_type :: proc {
headers_set_content_type_mime,
headers_set_content_type_string,
}
headers_set_content_type_string :: #force_inline proc(h: ^Headers, ct: string) {
headers_set_unsafe(h, "content-type", ct)
}
headers_set_content_type_mime :: #force_inline proc(h: ^Headers, ct: Mime_Type) {
headers_set_unsafe(h, "content-type", mime_to_content_type(ct))
}
headers_set_close :: #force_inline proc(h: ^Headers) {
headers_set_unsafe(h, "connection", "close")
}
/*
Escapes any newlines and converts ASCII to lowercase.
*/
@(private="file")
sanitize_key :: proc(h: Headers, k: string) -> string {
allocator := h._kv.allocator if h._kv.allocator.procedure != nil else context.temp_allocator
// general +4 in rare case of newlines, so we might not need to reallocate.
b := strings.builder_make(0, len(k)+4, allocator)
for c in k {
switch c {
case 'A'..='Z': strings.write_rune(&b, c + 32)
case '\n': strings.write_string(&b, "\\n")
case: strings.write_rune(&b, c)
}
}
return strings.to_string(b)
// NOTE: implementation that only allocates if needed, but we use arena's anyway so just allocating
// some space should be about as fast?
//
// b: strings.Builder = ---
// i: int
// for c in v {
// if c == '\n' || (c >= 'A' && c <= 'Z') {
// b = strings.builder_make(0, len(v)+4, allocator)
// strings.write_string(&b, v[:i])
// alloc = true
// break
// }
// i+=1
// }
//
// if !alloc {
// return v, false
// }
//
// for c in v[i:] {
// switch c {
// case 'A'..='Z': strings.write_rune(&b, c + 32)
// case '\n': strings.write_string(&b, "\\n")
// case: strings.write_rune(&b, c)
// }
// }
//
// return strings.to_string(b), true
}