Coverage Report

Created: 2026-10-28 06:59

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/swift-nio/Sources/NIOHTTP1/HTTPServerProtocolErrorHandler.swift
Line
Count
Source
1
//===----------------------------------------------------------------------===//
2
//
3
// This source file is part of the SwiftNIO open source project
4
//
5
// Copyright (c) 2017-2021 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 simple channel handler that catches errors emitted by parsing HTTP requests
17
/// and sends 400 Bad Request responses.
18
///
19
/// This channel handler provides the basic behaviour that the majority of simple HTTP
20
/// servers want. This handler does not suppress the parser errors: it allows them to
21
/// continue to pass through the pipeline so that other handlers (e.g. logging ones) can
22
/// deal with the error.
23
public final class HTTPServerProtocolErrorHandler: ChannelDuplexHandler, RemovableChannelHandler {
24
    public typealias InboundIn = HTTPServerRequestPart
25
    public typealias InboundOut = HTTPServerRequestPart
26
    public typealias OutboundIn = HTTPServerResponsePart
27
    public typealias OutboundOut = HTTPServerResponsePart
28
29
0
    private var hasUnterminatedResponse: Bool = false
30
31
0
    public init() {}
32
33
0
    public func errorCaught(context: ChannelHandlerContext, error: Error) {
34
0
        guard error is HTTPParserError else {
35
0
            context.fireErrorCaught(error)
36
0
            return
37
0
        }
38
0
39
0
        // Any HTTPParserError is automatically fatal, and we don't actually need (or want) to
40
0
        // provide that error to the client: we just want to tell it that it screwed up and then
41
0
        // let the rest of the pipeline shut the door in its face. However, we can only send an
42
0
        // HTTP error response if another response hasn't started yet.
43
0
        //
44
0
        // A side note here: we cannot block or do any delayed work. ByteToMessageDecoder is going
45
0
        // to come along and close the channel right after we return from this function.
46
0
        if !self.hasUnterminatedResponse {
47
0
            let headers = HTTPHeaders([("Connection", "close"), ("Content-Length", "0")])
48
0
            let head = HTTPResponseHead(version: .http1_1, status: .badRequest, headers: headers)
49
0
            context.write(HTTPServerProtocolErrorHandler.wrapOutboundOut(.head(head)), promise: nil)
50
0
            context.writeAndFlush(HTTPServerProtocolErrorHandler.wrapOutboundOut(.end(nil)), promise: nil)
51
0
        }
52
0
53
0
        // Now pass the error on in case someone else wants to see it.
54
0
        context.fireErrorCaught(error)
55
0
    }
56
57
0
    public func write(context: ChannelHandlerContext, data: NIOAny, promise: EventLoopPromise<Void>?) {
58
0
        let res = HTTPServerProtocolErrorHandler.unwrapOutboundIn(data)
59
0
        switch res {
60
0
        case .head(let head) where head.isInformational:
61
0
            precondition(!self.hasUnterminatedResponse)
62
0
        case .head:
63
0
            precondition(!self.hasUnterminatedResponse)
64
0
            self.hasUnterminatedResponse = true
65
0
        case .body:
66
0
            precondition(self.hasUnterminatedResponse)
67
0
        case .end:
68
0
            precondition(self.hasUnterminatedResponse)
69
0
            self.hasUnterminatedResponse = false
70
0
        }
71
0
        context.write(data, promise: promise)
72
0
    }
73
}
74
75
@available(*, unavailable)
76
extension HTTPServerProtocolErrorHandler: Sendable {}