Coverage Report

Created: 2025-09-08 06:42

/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 {}