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