Coverage Report

Created: 2025-07-23 06:54

/src/swift-nio/Sources/NIOHTTP1/HTTPHeaders+Validation.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
15
extension String {
16
    /// Validates a given header field value against the definition in RFC 9110.
17
    ///
18
    /// The spec in [RFC 9110](https://httpwg.org/specs/rfc9110.html#fields.values) defines the valid
19
    /// characters as the following:
20
    ///
21
    /// ```
22
    /// field-value    = *field-content
23
    /// field-content  = field-vchar
24
    ///                  [ 1*( SP / HTAB / field-vchar ) field-vchar ]
25
    /// field-vchar    = VCHAR / obs-text
26
    /// obs-text       = %x80-FF
27
    /// ```
28
    ///
29
    /// Additionally, it makes the following note:
30
    ///
31
    /// "Field values containing CR, LF, or NUL characters are invalid and dangerous, due to the
32
    /// varying ways that implementations might parse and interpret those characters; a recipient
33
    /// of CR, LF, or NUL within a field value MUST either reject the message or replace each of
34
    /// those characters with SP before further processing or forwarding of that message. Field
35
    /// values containing other CTL characters are also invalid; however, recipients MAY retain
36
    /// such characters for the sake of robustness when they appear within a safe context (e.g.,
37
    /// an application-specific quoted string that will not be processed by any downstream HTTP
38
    /// parser)."
39
    ///
40
    /// As we cannot guarantee the context is safe, this code will reject all ASCII control characters
41
    /// directly _except_ for HTAB, which is explicitly allowed.
42
0
    fileprivate var isValidHeaderFieldValue: Bool {
43
0
        let fastResult = self.utf8.withContiguousStorageIfAvailable { ptr in
44
0
            ptr.allSatisfy { $0.isValidHeaderFieldValueByte }
45
0
        }
46
0
        if let fastResult = fastResult {
47
0
            return fastResult
48
0
        } else {
49
0
            return self.utf8._isValidHeaderFieldValue_slowPath
50
0
        }
51
0
    }
52
53
    /// Validates a given header field name against the definition in RFC 9110.
54
    ///
55
    /// The spec in [RFC 9110](https://httpwg.org/specs/rfc9110.html#fields.values) defines the valid
56
    /// characters as the following:
57
    ///
58
    /// ```
59
    /// field-name     = token
60
    ///
61
    /// token          = 1*tchar
62
    ///
63
    /// tchar          = "!" / "#" / "$" / "%" / "&" / "'" / "*"
64
    ///                / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~"
65
    ///                / DIGIT / ALPHA
66
    ///                ; any VCHAR, except delimiters
67
    /// ```
68
    ///
69
    /// We implement this check directly.
70
0
    fileprivate var isValidHeaderFieldName: Bool {
71
0
        let fastResult = self.utf8.withContiguousStorageIfAvailable { ptr in
72
0
            ptr.allSatisfy { $0.isValidHeaderFieldNameByte }
73
0
        }
74
0
        if let fastResult = fastResult {
75
0
            return fastResult
76
0
        } else {
77
0
            return self.utf8._isValidHeaderFieldName_slowPath
78
0
        }
79
0
    }
80
}
81
82
extension String.UTF8View {
83
    /// The equivalent of `String.isValidHeaderFieldName`, but a slow-path when a
84
    /// contiguous UTF8 storage is not available.
85
    ///
86
    /// This is deliberately `@inline(never)`, as slow paths should be forcibly outlined
87
    /// to encourage inlining the fast-path.
88
    @inline(never)
89
0
    fileprivate var _isValidHeaderFieldName_slowPath: Bool {
90
0
        self.allSatisfy { $0.isValidHeaderFieldNameByte }
91
0
    }
92
93
    /// The equivalent of `String.isValidHeaderFieldValue`, but a slow-path when a
94
    /// contiguous UTF8 storage is not available.
95
    ///
96
    /// This is deliberately `@inline(never)`, as slow paths should be forcibly outlined
97
    /// to encourage inlining the fast-path.
98
    @inline(never)
99
0
    fileprivate var _isValidHeaderFieldValue_slowPath: Bool {
100
0
        self.allSatisfy { $0.isValidHeaderFieldValueByte }
101
0
    }
102
}
103
104
extension UInt8 {
105
    /// Validates a byte of a given header field name against the definition in RFC 9110.
106
    ///
107
    /// The spec in [RFC 9110](https://httpwg.org/specs/rfc9110.html#fields.values) defines the valid
108
    /// characters as the following:
109
    ///
110
    /// ```
111
    /// field-name     = token
112
    ///
113
    /// token          = 1*tchar
114
    ///
115
    /// tchar          = "!" / "#" / "$" / "%" / "&" / "'" / "*"
116
    ///                / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~"
117
    ///                / DIGIT / ALPHA
118
    ///                ; any VCHAR, except delimiters
119
    /// ```
120
    ///
121
    /// We implement this check directly.
122
    ///
123
    /// We use inline always here to force the check to be inlined, which it isn't always, leading to less optimal code.
124
    @inline(__always)
125
0
    fileprivate var isValidHeaderFieldNameByte: Bool {
126
0
        switch self {
127
0
        case UInt8(ascii: "0")...UInt8(ascii: "9"),  // DIGIT
128
0
            UInt8(ascii: "a")...UInt8(ascii: "z"),
129
0
            UInt8(ascii: "A")...UInt8(ascii: "Z"),  // ALPHA
130
0
            UInt8(ascii: "!"), UInt8(ascii: "#"),
131
0
            UInt8(ascii: "$"), UInt8(ascii: "%"),
132
0
            UInt8(ascii: "&"), UInt8(ascii: "'"),
133
0
            UInt8(ascii: "*"), UInt8(ascii: "+"),
134
0
            UInt8(ascii: "-"), UInt8(ascii: "."),
135
0
            UInt8(ascii: "^"), UInt8(ascii: "_"),
136
0
            UInt8(ascii: "`"), UInt8(ascii: "|"),
137
0
            UInt8(ascii: "~"):
138
0
139
0
            return true
140
0
141
0
        default:
142
0
            return false
143
0
        }
144
0
    }
145
146
    /// Validates a byte of a given header field value against the definition in RFC 9110.
147
    ///
148
    /// The spec in [RFC 9110](https://httpwg.org/specs/rfc9110.html#fields.values) defines the valid
149
    /// characters as the following:
150
    ///
151
    /// ```
152
    /// field-value    = *field-content
153
    /// field-content  = field-vchar
154
    ///                  [ 1*( SP / HTAB / field-vchar ) field-vchar ]
155
    /// field-vchar    = VCHAR / obs-text
156
    /// obs-text       = %x80-FF
157
    /// ```
158
    ///
159
    /// Additionally, it makes the following note:
160
    ///
161
    /// "Field values containing CR, LF, or NUL characters are invalid and dangerous, due to the
162
    /// varying ways that implementations might parse and interpret those characters; a recipient
163
    /// of CR, LF, or NUL within a field value MUST either reject the message or replace each of
164
    /// those characters with SP before further processing or forwarding of that message. Field
165
    /// values containing other CTL characters are also invalid; however, recipients MAY retain
166
    /// such characters for the sake of robustness when they appear within a safe context (e.g.,
167
    /// an application-specific quoted string that will not be processed by any downstream HTTP
168
    /// parser)."
169
    ///
170
    /// As we cannot guarantee the context is safe, this code will reject all ASCII control characters
171
    /// directly _except_ for HTAB, which is explicitly allowed.
172
    ///
173
    /// We use inline always here to force the check to be inlined, which it isn't always, leading to less optimal code.
174
    @inline(__always)
175
0
    fileprivate var isValidHeaderFieldValueByte: Bool {
176
0
        switch self {
177
0
        case UInt8(ascii: "\t"):
178
0
            // HTAB, explicitly allowed.
179
0
            return true
180
0
        case 0...0x1f, 0x7F:
181
0
            // ASCII control character, forbidden.
182
0
            return false
183
0
        default:
184
0
            // Printable or non-ASCII, allowed.
185
0
            return true
186
0
        }
187
0
    }
188
}
189
190
extension HTTPHeaders {
191
    /// Whether these HTTPHeaders are valid to send on the wire.
192
0
    var areValidToSend: Bool {
193
0
        for (name, value) in self.headers {
194
0
            if !name.isValidHeaderFieldName {
195
0
                return false
196
0
            }
197
0
198
0
            if !value.isValidHeaderFieldValue {
199
0
                return false
200
0
            }
201
0
        }
202
0
203
0
        return true
204
0
    }
205
}