206 lines
7.8 KiB
Swift
206 lines
7.8 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
//
|
|
// 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
|
|
}
|
|
}
|