/src/swift-nio/Sources/NIOHTTP1/HTTPDecoder.swift
Line | Count | Source |
1 | | //===----------------------------------------------------------------------===// |
2 | | // |
3 | | // This source file is part of the SwiftNIO open source project |
4 | | // |
5 | | // Copyright (c) 2017-2022 Apple Inc. and the SwiftNIO project authors |
6 | | // Licensed under Apache License v2.0 |
7 | | // |
8 | | // See LICENSE.txt for license information |
9 | | // See CONTRIBUTORS.txt for the list of SwiftNIO project authors |
10 | | // |
11 | | // SPDX-License-Identifier: Apache-2.0 |
12 | | // |
13 | | //===----------------------------------------------------------------------===// |
14 | | |
15 | | import NIOCore |
16 | | |
17 | | #if compiler(>=6.1) |
18 | | private import CNIOLLHTTP |
19 | | #else |
20 | | @_implementationOnly import CNIOLLHTTP |
21 | | #endif |
22 | | |
23 | | extension UnsafeMutablePointer where Pointee == llhttp_t { |
24 | | /// Returns the `KeepAliveState` for the current message that is parsed. |
25 | 161k | fileprivate var keepAliveState: KeepAliveState { |
26 | 161k | c_nio_llhttp_should_keep_alive(self) == 0 ? .close : .keepAlive |
27 | 161k | } |
28 | | } |
29 | | |
30 | | private enum HTTPDecodingState { |
31 | | case beforeMessageBegin |
32 | | case afterMessageBegin |
33 | | case url |
34 | | case headerName |
35 | | case headerValue |
36 | | case trailerName |
37 | | case trailerValue |
38 | | case headersComplete |
39 | | } |
40 | | |
41 | | private class BetterHTTPParser { |
42 | | /// Maximum size of a HTTP header field name or value. |
43 | | /// This number is derived largely from the historical behaviour of NIO. |
44 | | private static let maximumHeaderFieldSize = 80 * 1024 |
45 | | |
46 | 13.2k | var delegate: HTTPDecoderDelegate! = nil |
47 | 13.2k | private var parser: llhttp_t? = llhttp_t() // nil if unaccessible because reference passed away exclusively |
48 | | private var settings: UnsafeMutablePointer<llhttp_settings_t> |
49 | 13.2k | private var decodingState: HTTPDecodingState = .beforeMessageBegin |
50 | 13.2k | private var firstNonDiscardableOffset: Int? = nil |
51 | 13.2k | private var currentFieldByteLength = 0 |
52 | 13.2k | private var httpParserOffset = 0 |
53 | 13.2k | private var rawBytesView: UnsafeRawBufferPointer = .init(start: UnsafeRawPointer(bitPattern: 0xcafbabe), count: 0) |
54 | 13.2k | private var httpErrno: llhttp_errno_t? = nil |
55 | 13.2k | private var richerError: Error? = nil |
56 | | private let kind: HTTPDecoderKind |
57 | 13.2k | var requestHeads = CircularBuffer<HTTPRequestHead>(initialCapacity: 1) |
58 | | |
59 | | enum MessageContinuation { |
60 | | case normal |
61 | | case skipBody |
62 | | case error(llhttp_errno_t) |
63 | | } |
64 | | |
65 | 11.5M | private static func fromOpaque(_ opaque: UnsafePointer<llhttp_t>?) -> BetterHTTPParser { |
66 | 11.5M | Unmanaged<BetterHTTPParser>.fromOpaque(UnsafeRawPointer(opaque!.pointee.data)).takeUnretainedValue() |
67 | 11.5M | } |
68 | | |
69 | 13.2k | init(kind: HTTPDecoderKind) { |
70 | 13.2k | self.kind = kind |
71 | 13.2k | self.settings = UnsafeMutablePointer.allocate(capacity: 1) |
72 | 13.2k | c_nio_llhttp_settings_init(self.settings) |
73 | 2.50M | self.settings.pointee.on_body = { opaque, bytes, len in |
74 | 2.50M | BetterHTTPParser.fromOpaque(opaque).didReceiveBodyData(UnsafeRawBufferPointer(start: bytes, count: len)) |
75 | 2.50M | return 0 |
76 | 2.50M | } |
77 | 1.66M | self.settings.pointee.on_header_field = { opaque, bytes, len in |
78 | 1.66M | BetterHTTPParser.fromOpaque(opaque).didReceiveHeaderFieldData( |
79 | 1.66M | UnsafeRawBufferPointer(start: bytes, count: len) |
80 | 1.66M | ) |
81 | 1.66M | } |
82 | 1.66M | self.settings.pointee.on_header_value = { opaque, bytes, len in |
83 | 1.66M | BetterHTTPParser.fromOpaque(opaque).didReceiveHeaderValueData( |
84 | 1.66M | UnsafeRawBufferPointer(start: bytes, count: len) |
85 | 1.66M | ) |
86 | 1.66M | } |
87 | 13.2k | self.settings.pointee.on_status = { opaque, bytes, len in |
88 | 0 | BetterHTTPParser.fromOpaque(opaque).didReceiveStatusData(UnsafeRawBufferPointer(start: bytes, count: len)) |
89 | 0 | return 0 |
90 | 0 | } |
91 | 166k | self.settings.pointee.on_url = { opaque, bytes, len in |
92 | 166k | BetterHTTPParser.fromOpaque(opaque).didReceiveURLData(UnsafeRawBufferPointer(start: bytes, count: len)) |
93 | 166k | } |
94 | 2.52M | self.settings.pointee.on_chunk_complete = { opaque in |
95 | 2.52M | BetterHTTPParser.fromOpaque(opaque).didReceiveChunkCompleteNotification() |
96 | 2.52M | return 0 |
97 | 2.52M | } |
98 | 2.52M | self.settings.pointee.on_chunk_header = { opaque in |
99 | 2.52M | BetterHTTPParser.fromOpaque(opaque).didReceiveChunkHeaderNotification() |
100 | 2.52M | return 0 |
101 | 2.52M | } |
102 | 170k | self.settings.pointee.on_message_begin = { opaque in |
103 | 170k | BetterHTTPParser.fromOpaque(opaque).didReceiveMessageBeginNotification() |
104 | 170k | return 0 |
105 | 170k | } |
106 | 161k | self.settings.pointee.on_headers_complete = { opaque in |
107 | 161k | let parser = BetterHTTPParser.fromOpaque(opaque) |
108 | 161k | switch parser.didReceiveHeadersCompleteNotification( |
109 | 161k | versionMajor: Int(opaque!.pointee.http_major), |
110 | 161k | versionMinor: Int(opaque!.pointee.http_minor), |
111 | 161k | statusCode: Int(opaque!.pointee.status_code), |
112 | 161k | isUpgrade: opaque!.pointee.upgrade != 0, |
113 | 161k | method: llhttp_method(rawValue: numericCast(opaque!.pointee.method)), |
114 | 161k | keepAliveState: opaque!.keepAliveState |
115 | 161k | ) { |
116 | 161k | case .normal: |
117 | 161k | return 0 |
118 | 161k | case .skipBody: |
119 | 0 | return 1 |
120 | 161k | case .error(let err): |
121 | 227 | parser.httpErrno = err |
122 | 227 | return -1 // error |
123 | 161k | } |
124 | 161k | } |
125 | 158k | self.settings.pointee.on_message_complete = { opaque in |
126 | 158k | BetterHTTPParser.fromOpaque(opaque).didReceiveMessageCompleteNotification() |
127 | 158k | // Temporary workaround for https://github.com/nodejs/llhttp/issues/202, should be removed |
128 | 158k | // when that issue is fixed. We're tracking the work in https://github.com/apple/swift-nio/issues/2274. |
129 | 158k | opaque?.pointee.content_length = 0 |
130 | 158k | return 0 |
131 | 158k | } |
132 | 13.2k | self.withExclusiveHTTPParser { parserPtr in |
133 | 13.2k | switch kind { |
134 | 13.2k | case .request: |
135 | 13.2k | c_nio_llhttp_init(parserPtr, HTTP_REQUEST, self.settings) |
136 | 13.2k | case .response: |
137 | 0 | c_nio_llhttp_init(parserPtr, HTTP_RESPONSE, self.settings) |
138 | 13.2k | } |
139 | 13.2k | } |
140 | 13.2k | } |
141 | | |
142 | 13.2k | deinit { |
143 | 13.2k | self.settings.deallocate() |
144 | 13.2k | } |
145 | | |
146 | 3.48M | private func start(bytes: UnsafeRawBufferPointer, newState: HTTPDecodingState) { |
147 | 3.48M | assert(self.firstNonDiscardableOffset == nil) |
148 | 3.48M | self.firstNonDiscardableOffset = bytes.baseAddress! - self.rawBytesView.baseAddress! |
149 | 3.48M | self.decodingState = newState |
150 | 3.48M | } |
151 | | |
152 | 3.48M | private func finish(_ callout: (inout HTTPDecoderDelegate, UnsafeRawBufferPointer) throws -> Void) rethrows { |
153 | 3.48M | var currentFieldByteLength = 0 |
154 | 3.48M | swap(¤tFieldByteLength, &self.currentFieldByteLength) |
155 | 3.48M | let start = self.rawBytesView.startIndex + self.firstNonDiscardableOffset! |
156 | 3.48M | let end = start + currentFieldByteLength |
157 | 3.48M | self.firstNonDiscardableOffset = nil |
158 | 3.48M | precondition(start >= self.rawBytesView.startIndex && end <= self.rawBytesView.endIndex) |
159 | 3.48M | try callout(&self.delegate, .init(rebasing: self.rawBytesView[start..<end])) |
160 | 3.48M | } |
161 | | |
162 | 2.50M | private func didReceiveBodyData(_ bytes: UnsafeRawBufferPointer) { |
163 | 2.50M | self.delegate.didReceiveBody(bytes) |
164 | 2.50M | } |
165 | | |
166 | 1.66M | private func didReceiveHeaderFieldData(_ bytes: UnsafeRawBufferPointer) -> CInt { |
167 | 1.66M | switch self.decodingState { |
168 | 1.66M | case .headerName, .trailerName: |
169 | 1.28k | () |
170 | 1.66M | case .headerValue: |
171 | 674k | self.finish { delegate, bytes in |
172 | 674k | delegate.didReceiveHeaderValue(bytes) |
173 | 674k | } |
174 | 674k | self.start(bytes: bytes, newState: .headerName) |
175 | 1.66M | case .trailerValue: |
176 | 865k | self.finish { delegate, bytes in |
177 | 865k | delegate.didReceiveTrailerValue(bytes) |
178 | 865k | } |
179 | 865k | self.start(bytes: bytes, newState: .trailerName) |
180 | 1.66M | case .url: |
181 | 105k | self.finish { delegate, bytes in |
182 | 105k | delegate.didReceiveURL(bytes) |
183 | 105k | } |
184 | 105k | self.start(bytes: bytes, newState: .headerName) |
185 | 1.66M | case .headersComplete: |
186 | 14.8k | // these are trailers |
187 | 14.8k | self.start(bytes: bytes, newState: .trailerName) |
188 | 1.66M | case .afterMessageBegin: |
189 | 0 | // in case we're parsing responses |
190 | 0 | self.start(bytes: bytes, newState: .headerName) |
191 | 1.66M | case .beforeMessageBegin: |
192 | 0 | preconditionFailure() |
193 | 1.66M | } |
194 | 1.66M | return self.validateHeaderLength(bytes.count) |
195 | 1.66M | } |
196 | | |
197 | 1.66M | private func didReceiveHeaderValueData(_ bytes: UnsafeRawBufferPointer) -> CInt { |
198 | 1.66M | do { |
199 | 1.66M | switch self.decodingState { |
200 | 1.66M | case .headerValue, .trailerValue: |
201 | 1.31k | () |
202 | 1.66M | case .headerName: |
203 | 779k | try self.finish { delegate, bytes in |
204 | 779k | try delegate.didReceiveHeaderName(bytes) |
205 | 779k | } |
206 | 779k | self.start(bytes: bytes, newState: .headerValue) |
207 | 1.66M | case .trailerName: |
208 | 879k | try self.finish { delegate, bytes in |
209 | 879k | try delegate.didReceiveTrailerName(bytes) |
210 | 879k | } |
211 | 879k | self.start(bytes: bytes, newState: .trailerValue) |
212 | 1.66M | case .beforeMessageBegin, .afterMessageBegin, .headersComplete, .url: |
213 | 0 | preconditionFailure() |
214 | 1.66M | } |
215 | 1.66M | return self.validateHeaderLength(bytes.count) |
216 | 1.66M | } catch { |
217 | 0 | self.richerError = error |
218 | 0 | return -1 |
219 | 0 | } |
220 | 1.66M | } |
221 | | |
222 | 0 | private func didReceiveStatusData(_ bytes: UnsafeRawBufferPointer) { |
223 | 0 | // we don't do anything special here because we'll need the whole 'head' anyway |
224 | 0 | } |
225 | | |
226 | 166k | private func didReceiveURLData(_ bytes: UnsafeRawBufferPointer) -> CInt { |
227 | 166k | switch self.decodingState { |
228 | 166k | case .url: |
229 | 356 | () |
230 | 166k | case .afterMessageBegin: |
231 | 166k | self.start(bytes: bytes, newState: .url) |
232 | 166k | case .beforeMessageBegin, .headersComplete, .headerName, .headerValue, .trailerName, .trailerValue: |
233 | 0 | preconditionFailure() |
234 | 166k | } |
235 | 166k | return self.validateHeaderLength(bytes.count) |
236 | 166k | } |
237 | | |
238 | 2.52M | private func didReceiveChunkCompleteNotification() { |
239 | 2.52M | // nothing special to do, we handle chunks just like any other body part |
240 | 2.52M | } |
241 | | |
242 | 2.52M | private func didReceiveChunkHeaderNotification() { |
243 | 2.52M | // nothing special to do, we handle chunks just like any other body part |
244 | 2.52M | } |
245 | | |
246 | 170k | private func didReceiveMessageBeginNotification() { |
247 | 170k | switch self.decodingState { |
248 | 170k | case .beforeMessageBegin: |
249 | 170k | self.decodingState = .afterMessageBegin |
250 | 170k | case .headersComplete, .headerName, .headerValue, .trailerName, .trailerValue, .afterMessageBegin, .url: |
251 | 0 | preconditionFailure() |
252 | 170k | } |
253 | 170k | } |
254 | | |
255 | 158k | private func didReceiveMessageCompleteNotification() { |
256 | 158k | switch self.decodingState { |
257 | 158k | case .headersComplete: |
258 | 144k | () |
259 | 158k | case .trailerValue: |
260 | 14.3k | self.finish { delegate, bytes in |
261 | 14.3k | delegate.didReceiveTrailerValue(bytes) |
262 | 14.3k | } |
263 | 158k | case .beforeMessageBegin, .headerName, .headerValue, .trailerName, .afterMessageBegin, .url: |
264 | 0 | preconditionFailure() |
265 | 158k | } |
266 | 158k | self.decodingState = .beforeMessageBegin |
267 | 158k | self.delegate.didFinishMessage() |
268 | 158k | } |
269 | | |
270 | | private func didReceiveHeadersCompleteNotification( |
271 | | versionMajor: Int, |
272 | | versionMinor: Int, |
273 | | statusCode: Int, |
274 | | isUpgrade: Bool, |
275 | | method: llhttp_method, |
276 | | keepAliveState: KeepAliveState |
277 | 161k | ) -> MessageContinuation { |
278 | 161k | switch self.decodingState { |
279 | 161k | case .headerValue: |
280 | 103k | self.finish { delegate, bytes in |
281 | 103k | delegate.didReceiveHeaderValue(bytes) |
282 | 103k | } |
283 | 161k | case .url: |
284 | 58.6k | self.finish { delegate, bytes in |
285 | 58.6k | delegate.didReceiveURL(bytes) |
286 | 58.6k | } |
287 | 161k | case .afterMessageBegin: |
288 | 0 | // we're okay here for responses (as they don't have URLs) but for requests we must have seen a URL/headers |
289 | 0 | precondition(self.kind == .response) |
290 | 161k | case .beforeMessageBegin, .headersComplete, .headerName, .trailerName, .trailerValue: |
291 | 0 | preconditionFailure() |
292 | 161k | } |
293 | 161k | assert(self.firstNonDiscardableOffset == nil) |
294 | 161k | self.decodingState = .headersComplete |
295 | 161k | |
296 | 161k | var skipBody = false |
297 | 161k | |
298 | 161k | if self.kind == .response { |
299 | 0 | // http_parser doesn't correctly handle responses to HEAD requests. We have to do something |
300 | 0 | // annoyingly opaque here, and in those cases return 1 instead of 0. This forces http_parser |
301 | 0 | // to not expect a request body. |
302 | 0 | // |
303 | 0 | // The same logic applies to CONNECT: RFC 7230 says that regardless of what the headers say, |
304 | 0 | // responses to CONNECT never have HTTP-level bodies. |
305 | 0 | // |
306 | 0 | // Finally, we need to work around a bug in http_parser for 1XX, 204, and 304 responses. |
307 | 0 | // RFC 7230 says: |
308 | 0 | // |
309 | 0 | // > ... any response with a 1xx (Informational), |
310 | 0 | // > 204 (No Content), or 304 (Not Modified) status |
311 | 0 | // > code is always terminated by the first empty line after the |
312 | 0 | // > header fields, regardless of the header fields present in the |
313 | 0 | // > message, and thus cannot contain a message body. |
314 | 0 | // |
315 | 0 | // However, http_parser only does this for responses that do not contain length fields. That |
316 | 0 | // does not meet the requirement of RFC 7230. This is an outstanding http_parser issue: |
317 | 0 | // https://github.com/nodejs/http-parser/issues/251. As a result, we check for these status |
318 | 0 | // codes and override http_parser's handling as well. |
319 | 0 | guard !self.requestHeads.isEmpty else { |
320 | 0 | self.richerError = NIOHTTPDecoderError.unsolicitedResponse |
321 | 0 | return .error(HPE_INTERNAL) |
322 | 0 | } |
323 | 0 |
|
324 | 0 | if 100 <= statusCode && statusCode < 200 && statusCode != 101 { |
325 | 0 | // if the response status is in the range of 100..<200 but not 101 we don't want to |
326 | 0 | // pop the request method. The actual request head is expected with the next HTTP |
327 | 0 | // head. |
328 | 0 | skipBody = true |
329 | 0 | } else { |
330 | 0 | let method = self.requestHeads.removeFirst().method |
331 | 0 | if method == .HEAD || method == .CONNECT { |
332 | 0 | skipBody = true |
333 | 0 | } else if statusCode / 100 == 1 // 1XX codes |
334 | 0 | || statusCode == 204 || statusCode == 304 |
335 | 0 | { |
336 | 0 | skipBody = true |
337 | 0 | } |
338 | 0 | } |
339 | 161k | } |
340 | 161k | |
341 | 161k | let success = self.delegate.didFinishHead( |
342 | 161k | versionMajor: versionMajor, |
343 | 161k | versionMinor: versionMinor, |
344 | 161k | isUpgrade: isUpgrade, |
345 | 161k | method: method, |
346 | 161k | statusCode: statusCode, |
347 | 161k | keepAliveState: keepAliveState |
348 | 161k | ) |
349 | 161k | guard success else { |
350 | 227 | return .error(HPE_INVALID_VERSION) |
351 | 161k | } |
352 | 161k | |
353 | 161k | return skipBody ? .skipBody : .normal |
354 | 161k | } |
355 | | |
356 | 13.2k | func start() { |
357 | 13.2k | self.withExclusiveHTTPParser { parserPtr in |
358 | 13.2k | parserPtr.pointee.data = Unmanaged.passRetained(self).toOpaque() |
359 | 13.2k | } |
360 | 13.2k | } |
361 | | |
362 | 13.2k | func stop() { |
363 | 13.2k | self.withExclusiveHTTPParser { parserPtr in |
364 | 13.2k | let selfRef = parserPtr.pointee.data |
365 | 13.2k | Unmanaged<BetterHTTPParser>.fromOpaque(selfRef!).release() |
366 | 13.2k | parserPtr.pointee.data = UnsafeMutableRawPointer(bitPattern: 0xdedbeef) |
367 | 13.2k | } |
368 | 13.2k | } |
369 | | |
370 | 3.48M | private func validateHeaderLength(_ newLength: Int) -> CInt { |
371 | 3.48M | self.currentFieldByteLength += newLength |
372 | 3.48M | if self.currentFieldByteLength > Self.maximumHeaderFieldSize { |
373 | 61 | self.richerError = HTTPParserError.headerOverflow |
374 | 61 | return -1 |
375 | 3.48M | } |
376 | 3.48M | |
377 | 3.48M | return 0 |
378 | 3.48M | } |
379 | | |
380 | | @inline(__always) // this need to be optimised away |
381 | 66.2k | func withExclusiveHTTPParser<T>(_ body: (UnsafeMutablePointer<llhttp_t>) -> T) -> T { |
382 | 66.2k | var parser: llhttp_t? = nil |
383 | 66.2k | assert(self.parser != nil, "parser must not be nil here, must be a re-entrancy issue") |
384 | 66.2k | swap(&parser, &self.parser) |
385 | 66.2k | defer { |
386 | 66.2k | assert(self.parser == nil, "parser must not nil here") |
387 | 66.2k | swap(&parser, &self.parser) |
388 | 66.2k | } |
389 | 66.2k | return body(&parser!) |
390 | 66.2k | } |
391 | | |
392 | 26.6k | func feedInput(_ bytes: UnsafeRawBufferPointer?) throws -> Int { |
393 | 26.6k | var bytesRead = 0 |
394 | 26.6k | let parserErrno: llhttp_errno_t = self.withExclusiveHTTPParser { parserPtr -> llhttp_errno_t in |
395 | 26.6k | var rc: llhttp_errno_t |
396 | 26.6k | |
397 | 26.6k | if let bytes = bytes { |
398 | 17.4k | self.rawBytesView = bytes |
399 | 17.4k | defer { |
400 | 17.4k | self.rawBytesView = .init(start: UnsafeRawPointer(bitPattern: 0xdafbabe), count: 0) |
401 | 17.4k | } |
402 | 17.4k | |
403 | 17.4k | let startPointer = bytes.baseAddress! + self.httpParserOffset |
404 | 17.4k | let bytesToRead = bytes.count - self.httpParserOffset |
405 | 17.4k | |
406 | 17.4k | rc = c_nio_llhttp_execute_swift( |
407 | 17.4k | parserPtr, |
408 | 17.4k | startPointer, |
409 | 17.4k | bytesToRead |
410 | 17.4k | ) |
411 | 17.4k | |
412 | 17.4k | if rc == HPE_PAUSED_UPGRADE { |
413 | 181 | // This is a special pause. We don't need to stop here (our other code will prevent us |
414 | 181 | // parsing past this point, but we do need a special hook to work out how many bytes were read. |
415 | 181 | // The force-unwrap is safe: we know we hit an "error". |
416 | 181 | bytesRead = UnsafeRawPointer(c_nio_llhttp_get_error_pos(parserPtr)!) - startPointer |
417 | 181 | c_nio_llhttp_resume_after_upgrade(parserPtr) |
418 | 181 | rc = HPE_OK |
419 | 17.2k | } else { |
420 | 17.2k | bytesRead = bytesToRead |
421 | 17.2k | } |
422 | 17.4k | } else { |
423 | 9.16k | rc = c_nio_llhttp_finish(parserPtr) |
424 | 9.16k | bytesRead = 0 |
425 | 9.16k | } |
426 | 26.6k | |
427 | 26.6k | return rc |
428 | 26.6k | } |
429 | 26.6k | |
430 | 26.6k | // self.parser must be non-nil here because we can't be re-entered here (ByteToMessageDecoder guarantee) |
431 | 26.6k | guard parserErrno == HPE_OK else { |
432 | 12.3k | // if we chose to abort (eg. wrong HTTP version) the error will be in self.httpErrno, otherwise http_parser |
433 | 12.3k | // will tell us... |
434 | 12.3k | // self.parser must be non-nil here because we can't be re-entered here (ByteToMessageDecoder guarantee) |
435 | 12.3k | // If we have a richer error than the errno code, and the errno is internal, we'll use it. Otherwise, we use the |
436 | 12.3k | // error from http_parser. |
437 | 12.3k | let err = self.httpErrno ?? parserErrno |
438 | 12.3k | if err == HPE_INTERNAL || err == HPE_USER, let richerError = self.richerError { |
439 | 61 | throw richerError |
440 | 12.2k | } else { |
441 | 12.2k | throw HTTPParserError.httpError(fromCHTTPParserErrno: err)! |
442 | 12.2k | } |
443 | 14.2k | } |
444 | 14.2k | |
445 | 14.2k | if let firstNonDiscardableOffset = self.firstNonDiscardableOffset { |
446 | 8.43k | self.httpParserOffset += bytesRead - firstNonDiscardableOffset |
447 | 8.43k | self.firstNonDiscardableOffset = 0 |
448 | 8.43k | return firstNonDiscardableOffset |
449 | 8.43k | } else { |
450 | 5.85k | // By definition we've consumed all of the http parser offset at this stage. There may still be bytes |
451 | 5.85k | // left in the buffer though: we didn't consume them because they aren't ours to consume, as they may belong |
452 | 5.85k | // to an upgraded protocol. |
453 | 5.85k | // |
454 | 5.85k | // Set the HTTP parser offset back to zero, and tell the parent that we consumed |
455 | 5.85k | // the whole buffer. |
456 | 5.85k | let consumedBytes = self.httpParserOffset + bytesRead |
457 | 5.85k | self.httpParserOffset = 0 |
458 | 5.85k | return consumedBytes |
459 | 5.85k | } |
460 | 14.2k | } |
461 | | } |
462 | | |
463 | | private protocol HTTPDecoderDelegate { |
464 | | mutating func didReceiveBody(_ bytes: UnsafeRawBufferPointer) |
465 | | mutating func didReceiveHeaderName(_ bytes: UnsafeRawBufferPointer) throws |
466 | | mutating func didReceiveHeaderValue(_ bytes: UnsafeRawBufferPointer) |
467 | | mutating func didReceiveTrailerName(_ bytes: UnsafeRawBufferPointer) throws |
468 | | mutating func didReceiveTrailerValue(_ bytes: UnsafeRawBufferPointer) |
469 | | mutating func didReceiveURL(_ bytes: UnsafeRawBufferPointer) |
470 | | mutating func didFinishHead( |
471 | | versionMajor: Int, |
472 | | versionMinor: Int, |
473 | | isUpgrade: Bool, |
474 | | method: llhttp_method, |
475 | | statusCode: Int, |
476 | | keepAliveState: KeepAliveState |
477 | | ) -> Bool |
478 | | mutating func didFinishMessage() |
479 | | } |
480 | | |
481 | | /// A `ByteToMessageDecoder` used to decode HTTP/1.x responses. See the documentation |
482 | | /// on `HTTPDecoder` for more. |
483 | | /// |
484 | | /// The `HTTPResponseDecoder` must be placed later in the channel pipeline than the `HTTPRequestEncoder`, |
485 | | /// as it needs to see the outbound messages in order to keep track of what the HTTP request methods |
486 | | /// were for accurate decoding. |
487 | | /// |
488 | | /// Rather than set this up manually, consider using `ChannelPipeline.addHTTPClientHandlers`. |
489 | | public typealias HTTPResponseDecoder = HTTPDecoder<HTTPClientResponsePart, HTTPClientRequestPart> |
490 | | |
491 | | /// A `ByteToMessageDecoder` used to decode HTTP requests. See the documentation |
492 | | /// on `HTTPDecoder` for more. |
493 | | /// |
494 | | /// While the `HTTPRequestDecoder` does not currently have a specific ordering requirement in the |
495 | | /// `ChannelPipeline` (unlike `HTTPResponseDecoder`), it is possible that it will develop one. For |
496 | | /// that reason, applications should try to ensure that the `HTTPRequestDecoder` *later* in the |
497 | | /// `ChannelPipeline` than the `HTTPResponseEncoder`. |
498 | | /// |
499 | | /// Rather than set this up manually, consider using `ChannelPipeline.configureHTTPServerPipeline`. |
500 | | public typealias HTTPRequestDecoder = HTTPDecoder<HTTPServerRequestPart, HTTPServerResponsePart> |
501 | | |
502 | | public enum HTTPDecoderKind: Sendable { |
503 | | case request |
504 | | case response |
505 | | } |
506 | | |
507 | | extension HTTPDecoder: WriteObservingByteToMessageDecoder |
508 | | where In == HTTPClientResponsePart, Out == HTTPClientRequestPart { |
509 | | public typealias OutboundIn = Out |
510 | | |
511 | 0 | public func write(data: HTTPClientRequestPart) { |
512 | 0 | if case .head(let head) = data { |
513 | 0 | self.parser.requestHeads.append(head) |
514 | 0 | } |
515 | 0 | } |
516 | | } |
517 | | |
518 | | /// A `ChannelInboundHandler` that parses HTTP/1-style messages, converting them from |
519 | | /// unstructured bytes to a sequence of HTTP messages. |
520 | | /// |
521 | | /// The `HTTPDecoder` is a generic channel handler which can produce messages in |
522 | | /// either the form of `HTTPClientResponsePart` or `HTTPServerRequestPart`: that is, |
523 | | /// it produces messages that correspond to the semantic units of HTTP produced by |
524 | | /// the remote peer. |
525 | | public final class HTTPDecoder<In, Out>: ByteToMessageDecoder, HTTPDecoderDelegate { |
526 | | public typealias InboundOut = In |
527 | | |
528 | | // things we build incrementally |
529 | 68.4k | private var headers: [(String, String)] = [] |
530 | 68.4k | private var trailers: [(String, String)]? = nil |
531 | 68.4k | private var currentHeaderName: String? = nil |
532 | 68.4k | private var url: String? = nil |
533 | 68.4k | private var isUpgrade: Bool? = nil |
534 | | |
535 | | // temporary, set and unset by `feedInput` |
536 | 68.4k | private var buffer: ByteBuffer? = nil |
537 | 68.4k | private var context: ChannelHandlerContext? = nil |
538 | | |
539 | | // the actual state |
540 | | private let parser: BetterHTTPParser |
541 | | private let leftOverBytesStrategy: RemoveAfterUpgradeStrategy |
542 | | private let informationalResponseStrategy: NIOInformationalResponseStrategy |
543 | | private let kind: HTTPDecoderKind |
544 | 68.4k | private var stopParsing = false // set on upgrade or HTTP version error |
545 | 68.4k | private var lastResponseHeaderWasInformational = false |
546 | | |
547 | | /// Creates a new instance of `HTTPDecoder`. |
548 | | /// |
549 | | /// - Parameters: |
550 | | /// - leftOverBytesStrategy: The strategy to use when removing the decoder from the pipeline and an upgrade was, |
551 | | /// detected. Note that this does not affect what happens on EOF. |
552 | 116k | public convenience init(leftOverBytesStrategy: RemoveAfterUpgradeStrategy = .dropBytes) { |
553 | 116k | self.init(leftOverBytesStrategy: leftOverBytesStrategy, informationalResponseStrategy: .drop) |
554 | 116k | } |
555 | | |
556 | | /// Creates a new instance of `HTTPDecoder`. |
557 | | /// |
558 | | /// - Parameters: |
559 | | /// - leftOverBytesStrategy: The strategy to use when removing the decoder from the pipeline and an upgrade was, |
560 | | /// detected. Note that this does not affect what happens on EOF. |
561 | | /// - informationalResponseStrategy: Should informational responses (like http status 100) be forwarded or dropped. |
562 | | /// Default is `.drop`. This property is only respected when decoding responses. |
563 | | public init( |
564 | | leftOverBytesStrategy: RemoveAfterUpgradeStrategy = .dropBytes, |
565 | | informationalResponseStrategy: NIOInformationalResponseStrategy = .drop |
566 | 33.9k | ) { |
567 | 33.9k | self.headers.reserveCapacity(16) |
568 | 33.9k | if In.self == HTTPServerRequestPart.self { |
569 | 33.9k | self.kind = .request |
570 | 33.9k | } else if In.self == HTTPClientResponsePart.self { |
571 | 0 | self.kind = .response |
572 | 0 | } else { |
573 | 0 | preconditionFailure("unknown HTTP message type \(In.self)") |
574 | 0 | } |
575 | 33.9k | self.parser = BetterHTTPParser(kind: kind) |
576 | 33.9k | self.leftOverBytesStrategy = leftOverBytesStrategy |
577 | 33.9k | self.informationalResponseStrategy = informationalResponseStrategy |
578 | 33.9k | } |
579 | | |
580 | 937k | func didReceiveBody(_ bytes: UnsafeRawBufferPointer) { |
581 | 2.50M | let offset = self.buffer!.withUnsafeReadableBytes { allBytes -> Int in |
582 | 2.50M | let offset = bytes.baseAddress! - allBytes.baseAddress! |
583 | 2.50M | assert(offset >= 0) |
584 | 2.50M | assert(offset + bytes.count <= allBytes.count) |
585 | 2.50M | return offset |
586 | 2.50M | } |
587 | 937k | self.buffer!.moveReaderIndex(forwardBy: offset) |
588 | 937k | switch self.kind { |
589 | 937k | case .request: |
590 | 937k | self.context!.fireChannelRead( |
591 | 937k | NIOAny(HTTPServerRequestPart.body(self.buffer!.readSlice(length: bytes.count)!)) |
592 | 937k | ) |
593 | 937k | case .response: |
594 | 0 | self.context!.fireChannelRead( |
595 | 0 | NIOAny(HTTPClientResponsePart.body(self.buffer!.readSlice(length: bytes.count)!)) |
596 | 0 | ) |
597 | 937k | } |
598 | 937k | |
599 | 937k | } |
600 | | |
601 | 69.4k | func didReceiveHeaderName(_ bytes: UnsafeRawBufferPointer) throws { |
602 | 69.4k | assert(self.currentHeaderName == nil) |
603 | 69.4k | |
604 | 69.4k | // Defensive check: llhttp tolerates a zero-length header field name, but we don't. |
605 | 69.4k | guard bytes.count > 0 else { |
606 | 0 | throw HTTPParserError.invalidHeaderToken |
607 | 69.4k | } |
608 | 69.4k | self.currentHeaderName = String(decoding: bytes, as: Unicode.UTF8.self) |
609 | 69.4k | } |
610 | | |
611 | 68.7k | func didReceiveHeaderValue(_ bytes: UnsafeRawBufferPointer) { |
612 | 68.7k | self.headers.append((self.currentHeaderName!, String(decoding: bytes, as: Unicode.UTF8.self))) |
613 | 68.7k | self.currentHeaderName = nil |
614 | 68.7k | } |
615 | | |
616 | 49.9k | func didReceiveTrailerName(_ bytes: UnsafeRawBufferPointer) throws { |
617 | 49.9k | assert(self.currentHeaderName == nil) |
618 | 49.9k | |
619 | 49.9k | // Defensive check: llhttp tolerates a zero-length header field name, but we don't. |
620 | 49.9k | guard bytes.count > 0 else { |
621 | 0 | throw HTTPParserError.invalidHeaderToken |
622 | 49.9k | } |
623 | 49.9k | self.currentHeaderName = String(decoding: bytes, as: Unicode.UTF8.self) |
624 | 49.9k | } |
625 | | |
626 | 49.9k | func didReceiveTrailerValue(_ bytes: UnsafeRawBufferPointer) { |
627 | 49.9k | if self.trailers == nil { |
628 | 8.37k | self.trailers = [] |
629 | 8.37k | } |
630 | 49.9k | self.trailers?.append((self.currentHeaderName!, String(decoding: bytes, as: Unicode.UTF8.self))) |
631 | 49.9k | self.currentHeaderName = nil |
632 | 49.9k | } |
633 | | |
634 | 45.3k | func didReceiveURL(_ bytes: UnsafeRawBufferPointer) { |
635 | 45.3k | assert(self.url == nil) |
636 | 45.3k | self.url = String(decoding: bytes, as: Unicode.UTF8.self) |
637 | 45.3k | } |
638 | | |
639 | | fileprivate func didFinishHead( |
640 | | versionMajor: Int, |
641 | | versionMinor: Int, |
642 | | isUpgrade: Bool, |
643 | | method: llhttp_method, |
644 | | statusCode: Int, |
645 | | keepAliveState: KeepAliveState |
646 | 161k | ) -> Bool { |
647 | 161k | let message: NIOAny? |
648 | 161k | |
649 | 161k | guard versionMajor == 1 else { |
650 | 227 | self.stopParsing = true |
651 | 227 | self.context!.fireErrorCaught(HTTPParserError.invalidVersion) |
652 | 227 | return false |
653 | 161k | } |
654 | 161k | |
655 | 161k | switch self.kind { |
656 | 161k | case .request: |
657 | 161k | let reqHead = HTTPRequestHead( |
658 | 161k | version: .init(major: versionMajor, minor: versionMinor), |
659 | 161k | method: HTTPMethod.from(httpParserMethod: method), |
660 | 161k | uri: self.url!, |
661 | 161k | headers: HTTPHeaders( |
662 | 161k | self.headers, |
663 | 161k | keepAliveState: keepAliveState |
664 | 161k | ) |
665 | 161k | ) |
666 | 161k | message = NIOAny(HTTPServerRequestPart.head(reqHead)) |
667 | 161k | |
668 | 161k | case .response where (100..<200).contains(statusCode) && statusCode != 101: |
669 | 0 | self.lastResponseHeaderWasInformational = true |
670 | 0 | switch self.informationalResponseStrategy.base { |
671 | 0 | case .forward: |
672 | 0 | let resHeadPart = HTTPClientResponsePart.head( |
673 | 0 | versionMajor: versionMajor, |
674 | 0 | versionMinor: versionMinor, |
675 | 0 | statusCode: statusCode, |
676 | 0 | keepAliveState: keepAliveState, |
677 | 0 | headers: self.headers |
678 | 0 | ) |
679 | 0 | message = NIOAny(resHeadPart) |
680 | 0 | case .drop: |
681 | 0 | message = nil |
682 | 0 | } |
683 | 161k | |
684 | 161k | case .response: |
685 | 0 | self.lastResponseHeaderWasInformational = false |
686 | 0 | let resHeadPart = HTTPClientResponsePart.head( |
687 | 0 | versionMajor: versionMajor, |
688 | 0 | versionMinor: versionMinor, |
689 | 0 | statusCode: statusCode, |
690 | 0 | keepAliveState: keepAliveState, |
691 | 0 | headers: self.headers |
692 | 0 | ) |
693 | 0 | message = NIOAny(resHeadPart) |
694 | 161k | } |
695 | 161k | self.url = nil |
696 | 161k | self.headers.removeAll(keepingCapacity: true) |
697 | 161k | if let message = message { |
698 | 161k | self.context!.fireChannelRead(message) |
699 | 161k | } |
700 | 161k | self.isUpgrade = isUpgrade |
701 | 161k | return true |
702 | 161k | } |
703 | | |
704 | 42.5k | func didFinishMessage() { |
705 | 42.5k | var trailers: [(String, String)]? = nil |
706 | 42.5k | swap(&trailers, &self.trailers) |
707 | 42.5k | switch self.kind { |
708 | 42.5k | case .request: |
709 | 42.5k | self.context!.fireChannelRead(NIOAny(HTTPServerRequestPart.end(trailers.map(HTTPHeaders.init)))) |
710 | 42.5k | case .response: |
711 | 0 | if !self.lastResponseHeaderWasInformational { |
712 | 0 | self.context!.fireChannelRead(NIOAny(HTTPClientResponsePart.end(trailers.map(HTTPHeaders.init)))) |
713 | 0 | } |
714 | 42.5k | } |
715 | 42.5k | self.stopParsing = self.isUpgrade! |
716 | 42.5k | self.isUpgrade = nil |
717 | 42.5k | } |
718 | | |
719 | 13.2k | public func decoderAdded(context: ChannelHandlerContext) { |
720 | 13.2k | self.parser.delegate = self |
721 | 13.2k | self.parser.start() |
722 | 13.2k | } |
723 | | |
724 | 13.2k | public func decoderRemoved(context: ChannelHandlerContext) { |
725 | 13.2k | self.parser.stop() |
726 | 13.2k | self.parser.delegate = nil |
727 | 13.2k | } |
728 | | |
729 | 9.16k | private func feedEOF(context: ChannelHandlerContext) throws { |
730 | 9.16k | self.context = context |
731 | 9.16k | defer { |
732 | 9.16k | self.context = nil |
733 | 9.16k | } |
734 | 9.16k | // we don't care how much http_parser consumed here because we just fed an EOF so there won't be any more data. |
735 | 9.16k | _ = try self.parser.feedInput(nil) |
736 | 720 | } |
737 | | |
738 | 17.4k | private func feedInput(context: ChannelHandlerContext, buffer: inout ByteBuffer) throws { |
739 | 17.4k | self.buffer = buffer |
740 | 17.4k | self.context = context |
741 | 17.4k | defer { |
742 | 17.4k | self.buffer = nil |
743 | 17.4k | self.context = nil |
744 | 17.4k | } |
745 | 17.4k | let consumed = try buffer.withUnsafeReadableBytes { bytes -> Int in |
746 | 17.4k | try self.parser.feedInput(bytes) |
747 | 13.5k | } |
748 | 13.5k | buffer.moveReaderIndex(forwardBy: consumed) |
749 | 13.5k | } |
750 | | |
751 | 17.4k | public func decode(context: ChannelHandlerContext, buffer: inout ByteBuffer) throws -> DecodingState { |
752 | 17.4k | if !self.stopParsing { |
753 | 17.4k | try self.feedInput(context: context, buffer: &buffer) |
754 | 13.5k | } |
755 | 13.5k | return .needMoreData |
756 | 17.4k | } |
757 | | |
758 | | public func decodeLast( |
759 | | context: ChannelHandlerContext, |
760 | | buffer: inout ByteBuffer, |
761 | | seenEOF: Bool |
762 | 9.34k | ) throws -> DecodingState { |
763 | 9.34k | if !self.stopParsing { |
764 | 9.16k | while buffer.readableBytes > 0, case .continue = try self.decode(context: context, buffer: &buffer) {} |
765 | 9.16k | if seenEOF { |
766 | 9.16k | try self.feedEOF(context: context) |
767 | 720 | } |
768 | 901 | } |
769 | 901 | if buffer.readableBytes > 0 && !seenEOF { |
770 | 0 | // We only do this if we haven't seen EOF because the left-overs strategy must only be invoked when we're |
771 | 0 | // sure that this is the completion of an upgrade. |
772 | 0 | switch self.leftOverBytesStrategy { |
773 | 0 | case .dropBytes: |
774 | 0 | () |
775 | 0 | case .fireError: |
776 | 0 | context.fireErrorCaught(ByteToMessageDecoderError.leftoverDataWhenDone(buffer)) |
777 | 0 | case .forwardBytes: |
778 | 0 | context.fireChannelRead(NIOAny(buffer)) |
779 | 0 | } |
780 | 901 | } |
781 | 901 | return .needMoreData |
782 | 9.34k | } |
783 | | } |
784 | | |
785 | | @available(*, unavailable) |
786 | | extension HTTPDecoder: Sendable {} |
787 | | |
788 | | /// Strategy to use when a HTTPDecoder is removed from a pipeline after a HTTP upgrade was detected. |
789 | | public enum RemoveAfterUpgradeStrategy: Sendable { |
790 | | /// Forward all the remaining bytes that are currently buffered in the deccoder to the next handler in the pipeline. |
791 | | case forwardBytes |
792 | | /// Fires a `ByteToMessageDecoder.leftoverDataWhenDone` error through the pipeline |
793 | | case fireError |
794 | | /// Discard all the remaining bytes that are currently buffered in the decoder. |
795 | | case dropBytes |
796 | | } |
797 | | |
798 | | /// Strategy to use when a HTTPDecoder receives an informational HTTP response (1xx except 101) |
799 | | public struct NIOInformationalResponseStrategy: Hashable, Sendable { |
800 | | @usableFromInline |
801 | | enum Base: Sendable { |
802 | | case drop |
803 | | case forward |
804 | | } |
805 | | |
806 | | @usableFromInline |
807 | | var base: Base |
808 | | |
809 | | @inlinable |
810 | 116k | init(_ base: Base) { |
811 | 116k | self.base = base |
812 | 116k | } |
813 | | |
814 | | /// Drop the informational response and only forward the "real" response |
815 | | @inlinable |
816 | 116k | public static var drop: NIOInformationalResponseStrategy { |
817 | 116k | Self(.drop) |
818 | 116k | } |
819 | | /// Forward the informational response and then forward the "real" response. This will result in |
820 | | /// multiple `head` before an `end` is emitted. |
821 | | @inlinable |
822 | 0 | public static var forward: NIOInformationalResponseStrategy { |
823 | 0 | Self(.forward) |
824 | 0 | } |
825 | | } |
826 | | |
827 | | extension HTTPParserError { |
828 | | /// Create a `HTTPParserError` from an error returned by `http_parser`. |
829 | | /// |
830 | | /// - Parameter fromCHTTPParserErrno: The error from the underlying library. |
831 | | /// - Returns: The corresponding `HTTPParserError`, or `nil` if there is no |
832 | | /// corresponding error. |
833 | 12.2k | fileprivate static func httpError(fromCHTTPParserErrno: llhttp_errno_t) -> HTTPParserError? { |
834 | 12.2k | switch fromCHTTPParserErrno { |
835 | 12.2k | case HPE_INTERNAL: |
836 | 0 | return .invalidInternalState |
837 | 12.2k | case HPE_STRICT: |
838 | 214 | return .strictModeAssertion |
839 | 12.2k | case HPE_LF_EXPECTED: |
840 | 28 | return .lfExpected |
841 | 12.2k | case HPE_UNEXPECTED_CONTENT_LENGTH: |
842 | 3 | return .unexpectedContentLength |
843 | 12.2k | case HPE_CLOSED_CONNECTION: |
844 | 70 | return .closedConnection |
845 | 12.2k | case HPE_INVALID_METHOD: |
846 | 2.04k | return .invalidMethod |
847 | 12.2k | case HPE_INVALID_URL: |
848 | 163 | return .invalidURL |
849 | 12.2k | case HPE_INVALID_CONSTANT: |
850 | 199 | return .invalidConstant |
851 | 12.2k | case HPE_INVALID_VERSION: |
852 | 368 | return .invalidVersion |
853 | 12.2k | case HPE_INVALID_HEADER_TOKEN, |
854 | 476 | HPE_UNEXPECTED_SPACE: |
855 | 476 | return .invalidHeaderToken |
856 | 12.2k | case HPE_INVALID_CONTENT_LENGTH: |
857 | 36 | return .invalidContentLength |
858 | 12.2k | case HPE_INVALID_CHUNK_SIZE: |
859 | 125 | return .invalidChunkSize |
860 | 12.2k | case HPE_INVALID_STATUS: |
861 | 0 | return .invalidStatus |
862 | 12.2k | case HPE_INVALID_EOF_STATE: |
863 | 8.44k | return .invalidEOFState |
864 | 12.2k | case HPE_PAUSED, HPE_PAUSED_UPGRADE, HPE_PAUSED_H2_UPGRADE: |
865 | 2 | return .paused |
866 | 12.2k | case HPE_INVALID_TRANSFER_ENCODING, |
867 | 83 | HPE_CR_EXPECTED, |
868 | 83 | HPE_CB_MESSAGE_BEGIN, |
869 | 83 | HPE_CB_HEADERS_COMPLETE, |
870 | 83 | HPE_CB_MESSAGE_COMPLETE, |
871 | 83 | HPE_CB_CHUNK_HEADER, |
872 | 83 | HPE_CB_CHUNK_COMPLETE, |
873 | 83 | HPE_USER, |
874 | 83 | HPE_CB_URL_COMPLETE, |
875 | 83 | HPE_CB_STATUS_COMPLETE, |
876 | 83 | HPE_CB_HEADER_FIELD_COMPLETE, |
877 | 83 | HPE_CB_HEADER_VALUE_COMPLETE: |
878 | 83 | // The downside of enums here, we don't have a case for these. Map them to .unknown for now. |
879 | 83 | return .unknown |
880 | 12.2k | default: |
881 | 0 | return nil |
882 | 12.2k | } |
883 | 12.2k | } |
884 | | } |
885 | | |
886 | | extension HTTPMethod { |
887 | | /// Create a `HTTPMethod` from a given `http_method` produced by |
888 | | /// `http_parser`. |
889 | | /// |
890 | | /// - Parameter httpParserMethod: The method returned by `http_parser`. |
891 | | /// - Returns: The corresponding `HTTPMethod`. |
892 | 161k | fileprivate static func from(httpParserMethod: llhttp_method) -> HTTPMethod { |
893 | 161k | switch httpParserMethod { |
894 | 161k | case HTTP_DELETE: |
895 | 1.02k | return .DELETE |
896 | 161k | case HTTP_GET: |
897 | 7.89k | return .GET |
898 | 161k | case HTTP_HEAD: |
899 | 1.16k | return .HEAD |
900 | 161k | case HTTP_POST: |
901 | 1.27k | return .POST |
902 | 161k | case HTTP_PUT: |
903 | 93.5k | return .PUT |
904 | 161k | case HTTP_CONNECT: |
905 | 43 | return .CONNECT |
906 | 161k | case HTTP_OPTIONS: |
907 | 2.73k | return .OPTIONS |
908 | 161k | case HTTP_TRACE: |
909 | 786 | return .TRACE |
910 | 161k | case HTTP_COPY: |
911 | 981 | return .COPY |
912 | 161k | case HTTP_LOCK: |
913 | 689 | return .LOCK |
914 | 161k | case HTTP_MKCOL: |
915 | 652 | return .MKCOL |
916 | 161k | case HTTP_MOVE: |
917 | 1.08k | return .MOVE |
918 | 161k | case HTTP_PROPFIND: |
919 | 947 | return .PROPFIND |
920 | 161k | case HTTP_PROPPATCH: |
921 | 2.68k | return .PROPPATCH |
922 | 161k | case HTTP_SEARCH: |
923 | 4.56k | return .SEARCH |
924 | 161k | case HTTP_UNLOCK: |
925 | 8.92k | return .UNLOCK |
926 | 161k | case HTTP_BIND: |
927 | 1.21k | return .BIND |
928 | 161k | case HTTP_REBIND: |
929 | 921 | return .REBIND |
930 | 161k | case HTTP_UNBIND: |
931 | 1.02k | return .UNBIND |
932 | 161k | case HTTP_ACL: |
933 | 3.40k | return .ACL |
934 | 161k | case HTTP_REPORT: |
935 | 1.17k | return .REPORT |
936 | 161k | case HTTP_MKACTIVITY: |
937 | 748 | return .MKACTIVITY |
938 | 161k | case HTTP_CHECKOUT: |
939 | 1.07k | return .CHECKOUT |
940 | 161k | case HTTP_MERGE: |
941 | 884 | return .MERGE |
942 | 161k | case HTTP_MSEARCH: |
943 | 1.17k | return .MSEARCH |
944 | 161k | case HTTP_NOTIFY: |
945 | 1.27k | return .NOTIFY |
946 | 161k | case HTTP_SUBSCRIBE: |
947 | 1.00k | return .SUBSCRIBE |
948 | 161k | case HTTP_UNSUBSCRIBE: |
949 | 1.07k | return .UNSUBSCRIBE |
950 | 161k | case HTTP_PATCH: |
951 | 915 | return .PATCH |
952 | 161k | case HTTP_PURGE: |
953 | 917 | return .PURGE |
954 | 161k | case HTTP_MKCALENDAR: |
955 | 1.08k | return .MKCALENDAR |
956 | 161k | case HTTP_LINK: |
957 | 795 | return .LINK |
958 | 161k | case HTTP_UNLINK: |
959 | 991 | return .UNLINK |
960 | 161k | case HTTP_SOURCE: |
961 | 1.71k | // This isn't ideal really. |
962 | 1.71k | return .RAW(value: "SOURCE") |
963 | 161k | case HTTP_PRI: |
964 | 0 | return .RAW(value: "PRI") |
965 | 161k | case HTTP_DESCRIBE: |
966 | 618 | return .RAW(value: "DESCRIBE") |
967 | 161k | case HTTP_ANNOUNCE: |
968 | 549 | return .RAW(value: "ANNOUNCE") |
969 | 161k | case HTTP_SETUP: |
970 | 654 | return .RAW(value: "SETUP") |
971 | 161k | case HTTP_PLAY: |
972 | 1.44k | return .RAW(value: "PLAY") |
973 | 161k | case HTTP_PAUSE: |
974 | 911 | return .RAW(value: "PAUSE") |
975 | 161k | case HTTP_TEARDOWN: |
976 | 713 | return .RAW(value: "TEARDOWN") |
977 | 161k | case HTTP_GET_PARAMETER: |
978 | 933 | return .RAW(value: "GET_PARAMETER") |
979 | 161k | case HTTP_SET_PARAMETER: |
980 | 934 | return .RAW(value: "SET_PARAMETER") |
981 | 161k | case HTTP_REDIRECT: |
982 | 867 | return .RAW(value: "REDIRECT") |
983 | 161k | case HTTP_RECORD: |
984 | 996 | return .RAW(value: "RECORD") |
985 | 161k | case HTTP_FLUSH: |
986 | 1.03k | return .RAW(value: "FLUSH") |
987 | 161k | default: |
988 | 1.38k | return .RAW(value: String(cString: c_nio_llhttp_method_name(httpParserMethod))) |
989 | 161k | } |
990 | 161k | } |
991 | | } |
992 | | |
993 | | /// Errors thrown by `HTTPRequestDecoder` and `HTTPResponseDecoder` in addition to |
994 | | /// `HTTPParserError`. |
995 | | public struct NIOHTTPDecoderError: Error { |
996 | | private enum BaseError: Hashable { |
997 | | case unsolicitedResponse |
998 | | } |
999 | | |
1000 | | private let baseError: BaseError |
1001 | | } |
1002 | | |
1003 | | extension NIOHTTPDecoderError { |
1004 | | /// A response was received from a server without an associated request having been sent. |
1005 | | public static let unsolicitedResponse: NIOHTTPDecoderError = .init(baseError: .unsolicitedResponse) |
1006 | | } |
1007 | | |
1008 | | extension NIOHTTPDecoderError: Hashable {} |
1009 | | |
1010 | | extension NIOHTTPDecoderError: CustomDebugStringConvertible { |
1011 | 0 | public var debugDescription: String { |
1012 | 0 | String(describing: self.baseError) |
1013 | 0 | } |
1014 | | } |
1015 | | |
1016 | | extension HTTPClientResponsePart { |
1017 | | fileprivate static func head( |
1018 | | versionMajor: Int, |
1019 | | versionMinor: Int, |
1020 | | statusCode: Int, |
1021 | | keepAliveState: KeepAliveState, |
1022 | | headers: [(String, String)] |
1023 | 0 | ) -> HTTPClientResponsePart { |
1024 | 0 | HTTPClientResponsePart.head( |
1025 | 0 | HTTPResponseHead( |
1026 | 0 | version: .init(major: versionMajor, minor: versionMinor), |
1027 | 0 | status: .init(statusCode: statusCode), |
1028 | 0 | headers: HTTPHeaders(headers, keepAliveState: keepAliveState) |
1029 | 0 | ) |
1030 | 0 | ) |
1031 | 0 | } |
1032 | | } |