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
|
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 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
//
//===----------------------------------------------------------------------===//
extension String {
/// Validates a given header field value against the definition in RFC 9110.
///
/// The spec in [RFC 9110](https://httpwg.org/specs/rfc9110.html#fields.values) defines the valid
/// characters as the following:
///
/// ```
/// field-value = *field-content
/// field-content = field-vchar
/// [ 1*( SP / HTAB / field-vchar ) field-vchar ]
/// field-vchar = VCHAR / obs-text
/// obs-text = %x80-FF
/// ```
///
/// Additionally, it makes the following note:
///
/// "Field values containing CR, LF, or NUL characters are invalid and dangerous, due to the
/// varying ways that implementations might parse and interpret those characters; a recipient
/// of CR, LF, or NUL within a field value MUST either reject the message or replace each of
/// those characters with SP before further processing or forwarding of that message. Field
/// values containing other CTL characters are also invalid; however, recipients MAY retain
/// such characters for the sake of robustness when they appear within a safe context (e.g.,
/// an application-specific quoted string that will not be processed by any downstream HTTP
/// parser)."
///
/// As we cannot guarantee the context is safe, this code will reject all ASCII control characters
/// directly _except_ for HTAB, which is explicitly allowed.
fileprivate var isValidHeaderFieldValue: Bool {
let fastResult = self.utf8.withContiguousStorageIfAvailable { ptr in
ptr.allSatisfy { $0.isValidHeaderFieldValueByte }
}
if let fastResult = fastResult {
return fastResult
} else {
return self.utf8._isValidHeaderFieldValue_slowPath
}
}
/// Validates a given header field name against the definition in RFC 9110.
///
/// The spec in [RFC 9110](https://httpwg.org/specs/rfc9110.html#fields.values) defines the valid
/// characters as the following:
///
/// ```
/// field-name = token
///
/// token = 1*tchar
///
/// tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*"
/// / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~"
/// / DIGIT / ALPHA
/// ; any VCHAR, except delimiters
/// ```
///
/// We implement this check directly.
fileprivate var isValidHeaderFieldName: Bool {
let fastResult = self.utf8.withContiguousStorageIfAvailable { ptr in
ptr.allSatisfy { $0.isValidHeaderFieldNameByte }
}
if let fastResult = fastResult {
return fastResult
} else {
return self.utf8._isValidHeaderFieldName_slowPath
}
}
}
extension String.UTF8View {
/// The equivalent of `String.isValidHeaderFieldName`, but a slow-path when a
/// contiguous UTF8 storage is not available.
///
/// This is deliberately `@inline(never)`, as slow paths should be forcibly outlined
/// to encourage inlining the fast-path.
@inline(never)
fileprivate var _isValidHeaderFieldName_slowPath: Bool {
self.allSatisfy { $0.isValidHeaderFieldNameByte }
}
/// The equivalent of `String.isValidHeaderFieldValue`, but a slow-path when a
/// contiguous UTF8 storage is not available.
///
/// This is deliberately `@inline(never)`, as slow paths should be forcibly outlined
/// to encourage inlining the fast-path.
@inline(never)
fileprivate var _isValidHeaderFieldValue_slowPath: Bool {
self.allSatisfy { $0.isValidHeaderFieldValueByte }
}
}
extension UInt8 {
/// Validates a byte of a given header field name against the definition in RFC 9110.
///
/// The spec in [RFC 9110](https://httpwg.org/specs/rfc9110.html#fields.values) defines the valid
/// characters as the following:
///
/// ```
/// field-name = token
///
/// token = 1*tchar
///
/// tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*"
/// / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~"
/// / DIGIT / ALPHA
/// ; any VCHAR, except delimiters
/// ```
///
/// We implement this check directly.
///
/// We use inline always here to force the check to be inlined, which it isn't always, leading to less optimal code.
@inline(__always)
fileprivate var isValidHeaderFieldNameByte: Bool {
switch self {
case UInt8(ascii: "0")...UInt8(ascii: "9"), // DIGIT
UInt8(ascii: "a")...UInt8(ascii: "z"),
UInt8(ascii: "A")...UInt8(ascii: "Z"), // ALPHA
UInt8(ascii: "!"), UInt8(ascii: "#"),
UInt8(ascii: "$"), UInt8(ascii: "%"),
UInt8(ascii: "&"), UInt8(ascii: "'"),
UInt8(ascii: "*"), UInt8(ascii: "+"),
UInt8(ascii: "-"), UInt8(ascii: "."),
UInt8(ascii: "^"), UInt8(ascii: "_"),
UInt8(ascii: "`"), UInt8(ascii: "|"),
UInt8(ascii: "~"):
return true
default:
return false
}
}
/// Validates a byte of a given header field value against the definition in RFC 9110.
///
/// The spec in [RFC 9110](https://httpwg.org/specs/rfc9110.html#fields.values) defines the valid
/// characters as the following:
///
/// ```
/// field-value = *field-content
/// field-content = field-vchar
/// [ 1*( SP / HTAB / field-vchar ) field-vchar ]
/// field-vchar = VCHAR / obs-text
/// obs-text = %x80-FF
/// ```
///
/// Additionally, it makes the following note:
///
/// "Field values containing CR, LF, or NUL characters are invalid and dangerous, due to the
/// varying ways that implementations might parse and interpret those characters; a recipient
/// of CR, LF, or NUL within a field value MUST either reject the message or replace each of
/// those characters with SP before further processing or forwarding of that message. Field
/// values containing other CTL characters are also invalid; however, recipients MAY retain
/// such characters for the sake of robustness when they appear within a safe context (e.g.,
/// an application-specific quoted string that will not be processed by any downstream HTTP
/// parser)."
///
/// As we cannot guarantee the context is safe, this code will reject all ASCII control characters
/// directly _except_ for HTAB, which is explicitly allowed.
///
/// We use inline always here to force the check to be inlined, which it isn't always, leading to less optimal code.
@inline(__always)
fileprivate var isValidHeaderFieldValueByte: Bool {
switch self {
case UInt8(ascii: "\t"):
// HTAB, explicitly allowed.
return true
case 0...0x1f, 0x7F:
// ASCII control character, forbidden.
return false
default:
// Printable or non-ASCII, allowed.
return true
}
}
}
extension HTTPHeaders {
/// Whether these HTTPHeaders are valid to send on the wire.
var areValidToSend: Bool {
for (name, value) in self.headers {
if !name.isValidHeaderFieldName {
return false
}
if !value.isValidHeaderFieldValue {
return false
}
}
return true
}
}
|