/src/swift-nio/Sources/NIOHTTP1/HTTPHeaderValidator.swift
Line | Count | Source (jump to first uncovered line) |
1 | | //===----------------------------------------------------------------------===// |
2 | | // |
3 | | // This source file is part of the SwiftNIO open source project |
4 | | // |
5 | | // Copyright (c) 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 | | import NIOCore |
15 | | |
16 | | /// A ChannelHandler to validate that outbound request headers are spec-compliant. |
17 | | /// |
18 | | /// The HTTP RFCs constrain the bytes that are validly present within a HTTP/1.1 header block. |
19 | | /// ``NIOHTTPRequestHeadersValidator`` polices this constraint and ensures that only valid header blocks |
20 | | /// are emitted on the network. If a header block is invalid, then ``NIOHTTPRequestHeadersValidator`` |
21 | | /// will send a ``HTTPParserError/invalidHeaderToken``. |
22 | | /// |
23 | | /// ``NIOHTTPRequestHeadersValidator`` will also valid that the HTTP trailers are within specification, |
24 | | /// if they are present. |
25 | | public final class NIOHTTPRequestHeadersValidator: ChannelOutboundHandler, RemovableChannelHandler { |
26 | | public typealias OutboundIn = HTTPClientRequestPart |
27 | | public typealias OutboundOut = HTTPClientRequestPart |
28 | | |
29 | 0 | public init() {} |
30 | | |
31 | 0 | public func write(context: ChannelHandlerContext, data: NIOAny, promise: EventLoopPromise<Void>?) { |
32 | 0 | switch Self.unwrapOutboundIn(data) { |
33 | 0 | case .head(let head): |
34 | 0 | guard head.headers.areValidToSend else { |
35 | 0 | promise?.fail(HTTPParserError.invalidHeaderToken) |
36 | 0 | context.fireErrorCaught(HTTPParserError.invalidHeaderToken) |
37 | 0 | return |
38 | 0 | } |
39 | 0 | case .body, .end(.none): |
40 | 0 | () |
41 | 0 | case .end(.some(let trailers)): |
42 | 0 | guard trailers.areValidToSend else { |
43 | 0 | promise?.fail(HTTPParserError.invalidHeaderToken) |
44 | 0 | context.fireErrorCaught(HTTPParserError.invalidHeaderToken) |
45 | 0 | return |
46 | 0 | } |
47 | 0 | } |
48 | 0 |
|
49 | 0 | context.write(data, promise: promise) |
50 | 0 | } |
51 | | } |
52 | | |
53 | | /// A ChannelHandler to validate that outbound response headers are spec-compliant. |
54 | | /// |
55 | | /// The HTTP RFCs constrain the bytes that are validly present within a HTTP/1.1 header block. |
56 | | /// ``NIOHTTPResponseHeadersValidator`` polices this constraint and ensures that only valid header blocks |
57 | | /// are emitted on the network. If a header block is invalid, then ``NIOHTTPResponseHeadersValidator`` |
58 | | /// will send a ``HTTPParserError/invalidHeaderToken``. |
59 | | /// |
60 | | /// ``NIOHTTPResponseHeadersValidator`` will also valid that the HTTP trailers are within specification, |
61 | | /// if they are present. |
62 | | public final class NIOHTTPResponseHeadersValidator: ChannelOutboundHandler, RemovableChannelHandler { |
63 | | public typealias OutboundIn = HTTPServerResponsePart |
64 | | public typealias OutboundOut = HTTPServerResponsePart |
65 | | |
66 | | private enum State { |
67 | | /// Validating response parts. |
68 | | case validating |
69 | | /// Dropping all response parts. This is a terminal state. |
70 | | case dropping |
71 | | } |
72 | | |
73 | | private var state: State |
74 | | |
75 | 0 | public init() { |
76 | 0 | self.state = .validating |
77 | 0 | } |
78 | | |
79 | 0 | public func write(context: ChannelHandlerContext, data: NIOAny, promise: EventLoopPromise<Void>?) { |
80 | 0 | switch (Self.unwrapOutboundIn(data), self.state) { |
81 | 0 | case (.head(let head), .validating): |
82 | 0 | if head.headers.areValidToSend { |
83 | 0 | context.write(data, promise: promise) |
84 | 0 | } else { |
85 | 0 | self.state = .dropping |
86 | 0 | promise?.fail(HTTPParserError.invalidHeaderToken) |
87 | 0 | context.fireErrorCaught(HTTPParserError.invalidHeaderToken) |
88 | 0 | } |
89 | 0 |
|
90 | 0 | case (.body, .validating): |
91 | 0 | context.write(data, promise: promise) |
92 | 0 |
|
93 | 0 | case (.end(let trailers), .validating): |
94 | 0 | // No trailers are always valid trailers. |
95 | 0 | if trailers?.areValidToSend ?? true { |
96 | 0 | context.write(data, promise: promise) |
97 | 0 | } else { |
98 | 0 | self.state = .dropping |
99 | 0 | promise?.fail(HTTPParserError.invalidHeaderToken) |
100 | 0 | context.fireErrorCaught(HTTPParserError.invalidHeaderToken) |
101 | 0 | } |
102 | 0 |
|
103 | 0 | case (.head, .dropping), (.body, .dropping), (.end, .dropping): |
104 | 0 | promise?.fail(HTTPParserError.invalidHeaderToken) |
105 | 0 | } |
106 | 0 | } |
107 | | } |
108 | | |
109 | | @available(*, unavailable) |
110 | | extension NIOHTTPRequestHeadersValidator: Sendable {} |
111 | | |
112 | | @available(*, unavailable) |
113 | | extension NIOHTTPResponseHeadersValidator: Sendable {} |