1import type { Continue, FailureReason, ReturnValue } from "../HeadersParser.ts"
2
3const constMaxPairs = 100
4const constMaxSize = 16 * 1024
5
6const State = {
7 key: 0,
8 whitespace: 1,
9 value: 2
10} as const
11type State = (typeof State)[keyof typeof State]
12
13const constContinue: Continue = { _tag: "Continue" }
14
15// RFC 7230 token characters, allowed in header names
16// The previous table stopped at byte 126; zero-filled entries for bytes 127-255
17// preserve its rejection behavior because callers accept only entries equal to 1.
18const constNameChars = new Uint8Array(256)
19for (const char of "!#$%&'*+-.^_`|~0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") {
20 constNameChars[char.charCodeAt(0)] = 1
21}
22
23// HTAB, visible ASCII and obs-text, allowed in header values
24const constValueChars = new Uint8Array(256)
25constValueChars[9] = 1
26for (let i = 32; i <= 126; i++) {
27 constValueChars[i] = 1
28}
29for (let i = 128; i <= 255; i++) {
30 constValueChars[i] = 1
31}
32
33export function make() {
34 const decoder = new TextDecoder()
35 const state = {
36 state: State.key as State,
37 headers: Object.create(null) as Record<string, string | Array<string>>,
38 key: "",
39 value: undefined as undefined | Uint8Array,
40 crlf: 0,
41 previousChunk: undefined as undefined | Uint8Array,
42 pairs: 0,
43 size: 0
44 }
45
46 function reset(value: ReturnValue): ReturnValue {
47 state.state = State.key
48 state.headers = Object.create(null)
49 state.key = ""
50 state.value = undefined
51 state.crlf = 0
52 state.previousChunk = undefined
53 state.pairs = 0
54 state.size = 0
55 return value
56 }
57
58 function concatUint8Array(a: Uint8Array, b: Uint8Array): Uint8Array {
59 const newUint8Array = new Uint8Array(a.length + b.length)
60 newUint8Array.set(a)
61 newUint8Array.set(b, a.length)
62 return newUint8Array
63 }
64
65 function error(reason: FailureReason) {
66 return reset({ _tag: "Failure", reason, headers: state.headers })
67 }
68
69 return function write(chunk: Uint8Array, start: number): ReturnValue {
70 let endOffset = 0
71 let previousCursor: number | undefined
72 if (state.previousChunk !== undefined) {
73 endOffset = state.previousChunk.length
74 previousCursor = endOffset
75
76 const newChunk = new Uint8Array(chunk.length + endOffset)
77 newChunk.set(state.previousChunk)
78 newChunk.set(chunk, endOffset)
79 state.previousChunk = undefined
80 chunk = newChunk
81 }
82 const end = chunk.length
83
84 outer: while (start < end) {
85 if (state.state === State.key) {
86 let i = start
87 for (; i < end; i++) {
88 if (state.size++ > constMaxSize) {
89 return error("HeaderTooLarge")
90 }
91
92 if (chunk[i] === 58) {
93 state.key += decoder.decode(chunk.subarray(start, i)).toLowerCase()
94 if (state.key.length === 0) {
95 return error("InvalidHeaderName")
96 }
97
98 if (
99 chunk[i + 1] === 32 &&
100 chunk[i + 2] !== 32 &&
101 chunk[i + 2] !== 9
102 ) {
103 start = i + 2
104 state.state = State.value
105 state.size++
106 } else if (chunk[i + 1] !== 32 && chunk[i + 1] !== 9) {
107 start = i + 1
108 state.state = State.value
109 } else {
110 start = i + 1
111 state.state = State.whitespace
112 }
113
114 break
115 } else if (constNameChars[chunk[i]] !== 1) {
116 return error("InvalidHeaderName")
117 }
118 }
119 if (i === end) {
120 state.key += decoder.decode(chunk.subarray(start, end)).toLowerCase()
121 return constContinue
122 }
123 }
124
125 if (state.state === State.whitespace) {
126 for (; start < end; start++) {
127 if (state.size++ > constMaxSize) {
128 return error("HeaderTooLarge")
129 }
130
131 if (chunk[start] !== 32 && chunk[start] !== 9) {
132 state.state = State.value
133 break
134 }
135 }
136 if (start === end) {
137 return constContinue
138 }
139 }
140
141 if (state.state === State.value) {
142 let i = start
143 if (previousCursor !== undefined) {
144 i = previousCursor
145 previousCursor = undefined
146 }
147 for (; i < end; i++) {
148 if (state.size++ > constMaxSize) {
149 return error("HeaderTooLarge")
150 }
151
152 if (chunk[i] === 13 || state.crlf > 0) {
153 let byte = chunk[i]
154
155 if (byte === 13 && state.crlf === 0) {
156 state.crlf = 1
157 i++
158 state.size++
159 byte = chunk[i]
160 }
161 if (byte === 10 && state.crlf === 1) {
162 state.crlf = 2
163 i++
164 state.size++
165 byte = chunk[i]
166 }
167 if (byte === 13 && state.crlf === 2) {
168 state.crlf = 3
169 i++
170 state.size++
171 byte = chunk[i]
172 }
173 if (byte === 10 && state.crlf === 3) {
174 state.crlf = 4
175 i++
176 state.size++
177 }
178
179 if (state.crlf < 4 && i >= end) {
180 state.previousChunk = chunk.subarray(start)
181 return constContinue
182 } else if (state.crlf >= 2) {
183 state.value = state.value === undefined
184 ? chunk.subarray(start, i - state.crlf)
185 : concatUint8Array(
186 state.value,
187 chunk.subarray(start, i - state.crlf)
188 )
189 const value = decoder.decode(state.value)
190 if (state.headers[state.key] === undefined) {
191 state.headers[state.key] = value
192 } else if (typeof state.headers[state.key] === "string") {
193 state.headers[state.key] = [
194 state.headers[state.key] as string,
195 value
196 ]
197 } else {
198 ;(state.headers[state.key] as Array<string>).push(value)
199 }
200
201 start = i
202 state.size--
203
204 if (state.crlf !== 4 && state.pairs === constMaxPairs) {
205 return error("TooManyHeaders")
206 } else if (state.crlf === 3) {
207 return error("InvalidHeaderValue")
208 } else if (state.crlf === 4) {
209 return reset({
210 _tag: "Headers",
211 headers: state.headers,
212 endPosition: start - endOffset
213 })
214 }
215
216 state.pairs++
217 state.key = ""
218 state.value = undefined
219 state.crlf = 0
220 state.state = State.key
221
222 continue outer
223 }
224 } else if (constValueChars[chunk[i]] !== 1) {
225 return error("InvalidHeaderValue")
226 }
227 }
228
229 if (i === end) {
230 state.value = state.value === undefined
231 ? chunk.subarray(start, end)
232 : concatUint8Array(state.value, chunk.subarray(start, end))
233 return constContinue
234 }
235 }
236 }
237
238 if (start > end) {
239 state.size += end - start
240 }
241
242 return constContinue
243 }
244}