Showing preview only (1,262K chars total). Download the full file or copy to clipboard to get everything.
Repository: apple/swift-http-structured-headers
Branch: main
Commit: 933538faa42c
Files: 69
Total size: 1.2 MB
Directory structure:
gitextract_vxft_rju/
├── .editorconfig
├── .github/
│ ├── release.yml
│ └── workflows/
│ ├── main.yml
│ ├── pull_request.yml
│ └── pull_request_label.yml
├── .gitignore
├── .licenseignore
├── .spi.yml
├── .swift-format
├── CONTRIBUTORS.txt
├── LICENSE.txt
├── Package.swift
├── README.md
├── Sources/
│ ├── RawStructuredFieldValues/
│ │ ├── ASCII.swift
│ │ ├── ComponentTypes.swift
│ │ ├── Docs.docc/
│ │ │ └── index.md
│ │ ├── Errors.swift
│ │ ├── FieldParser.swift
│ │ ├── FieldSerializer.swift
│ │ ├── OrderedMap.swift
│ │ └── PseudoDecimal.swift
│ ├── StructuredFieldValues/
│ │ ├── Decoder/
│ │ │ ├── BareInnerListDecoder.swift
│ │ │ ├── BareItemDecoder.swift
│ │ │ ├── DictionaryKeyedContainer.swift
│ │ │ ├── KeyedInnerListDecoder.swift
│ │ │ ├── KeyedItemDecoder.swift
│ │ │ ├── KeyedTopLevelListDecoder.swift
│ │ │ ├── ParametersDecoder.swift
│ │ │ ├── StructuredFieldValueDecoder.swift
│ │ │ ├── StructuredHeaderCodingKey.swift
│ │ │ └── TopLevelListDecoder.swift
│ │ ├── DisplayString.swift
│ │ ├── Docs.docc/
│ │ │ └── index.md
│ │ ├── Encoder/
│ │ │ ├── StructuredFieldKeyedEncodingContainer.swift
│ │ │ ├── StructuredFieldUnkeyedEncodingContainer.swift
│ │ │ └── StructuredFieldValueEncoder.swift
│ │ └── StructuredFieldValue.swift
│ └── sh-parser/
│ └── main.swift
├── Tests/
│ ├── StructuredFieldValuesTests/
│ │ ├── Base32.swift
│ │ ├── Fixtures.swift
│ │ ├── StructuredFieldDecoderTests.swift
│ │ ├── StructuredFieldEncoderTests.swift
│ │ ├── StructuredFieldParserTests.swift
│ │ └── StructuredFieldSerializerTests.swift
│ └── TestFixtures/
│ ├── binary.json
│ ├── boolean.json
│ ├── date.json
│ ├── dictionary.json
│ ├── display-string.json
│ ├── examples.json
│ ├── item.json
│ ├── key-generated.json
│ ├── large-generated.json
│ ├── list.json
│ ├── listlist.json
│ ├── number-generated.json
│ ├── number.json
│ ├── param-dict.json
│ ├── param-list.json
│ ├── param-listlist.json
│ ├── serialisation-tests/
│ │ ├── key-generated.json
│ │ ├── number.json
│ │ ├── string-generated.json
│ │ └── token-generated.json
│ ├── string-generated.json
│ ├── string.json
│ ├── token-generated.json
│ └── token.json
└── dev/
└── git.commit.template
================================================
FILE CONTENTS
================================================
================================================
FILE: .editorconfig
================================================
root = true
[*]
indent_style = space
indent_size = 4
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
================================================
FILE: .github/release.yml
================================================
changelog:
categories:
- title: SemVer Major
labels:
- ⚠️ semver/major
- title: SemVer Minor
labels:
- 🆕 semver/minor
- title: SemVer Patch
labels:
- 🔨 semver/patch
- title: Other Changes
labels:
- semver/none
================================================
FILE: .github/workflows/main.yml
================================================
name: Main
permissions:
contents: read
on:
push:
branches: [main]
schedule:
- cron: "0 8,20 * * *"
jobs:
unit-tests:
name: Unit tests
uses: apple/swift-nio/.github/workflows/unit_tests.yml@main
with:
linux_5_10_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error"
linux_6_1_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error"
linux_6_2_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error"
linux_6_3_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error"
linux_nightly_next_arguments_override: "--explicit-target-dependency-import-check error"
linux_nightly_main_arguments_override: "--explicit-target-dependency-import-check error"
static-sdk:
name: Static SDK
uses: apple/swift-nio/.github/workflows/static_sdk.yml@main
macos-tests:
name: macOS tests
uses: apple/swift-nio/.github/workflows/macos_tests.yml@main
with:
runner_pool: nightly
build_scheme: swift-http-structured-headers-Package
xcode_16_2_build_arguments_override: "-Xswiftc -Xfrontend -Xswiftc -require-explicit-sendable"
xcode_16_3_build_arguments_override: "-Xswiftc -Xfrontend -Xswiftc -require-explicit-sendable"
release-builds:
name: Release builds
uses: apple/swift-nio/.github/workflows/release_builds.yml@main
================================================
FILE: .github/workflows/pull_request.yml
================================================
name: PR
permissions:
contents: read
on:
pull_request:
types: [opened, reopened, synchronize]
jobs:
soundness:
name: Soundness
uses: swiftlang/github-workflows/.github/workflows/soundness.yml@0.0.7
with:
license_header_check_project_name: "SwiftNIO"
unit-tests:
name: Unit tests
uses: apple/swift-nio/.github/workflows/unit_tests.yml@main
with:
linux_5_10_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error"
linux_6_1_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error"
linux_6_2_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error"
linux_6_3_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error"
linux_nightly_next_arguments_override: "--explicit-target-dependency-import-check error"
linux_nightly_main_arguments_override: "--explicit-target-dependency-import-check error"
cxx-interop:
name: Cxx interop
uses: apple/swift-nio/.github/workflows/cxx_interop.yml@main
static-sdk:
name: Static SDK
uses: apple/swift-nio/.github/workflows/static_sdk.yml@main
macos-tests:
name: macOS tests
uses: apple/swift-nio/.github/workflows/macos_tests.yml@main
with:
runner_pool: general
build_scheme: swift-http-structured-headers-Package
xcode_16_2_build_arguments_override: "-Xswiftc -Xfrontend -Xswiftc -require-explicit-sendable"
xcode_16_3_build_arguments_override: "-Xswiftc -Xfrontend -Xswiftc -require-explicit-sendable"
release-builds:
name: Release builds
uses: apple/swift-nio/.github/workflows/release_builds.yml@main
================================================
FILE: .github/workflows/pull_request_label.yml
================================================
name: PR label
permissions:
contents: read
on:
pull_request:
types: [labeled, unlabeled, opened, reopened, synchronize]
jobs:
semver-label-check:
name: Semantic version label check
runs-on: ubuntu-latest
timeout-minutes: 1
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
persist-credentials: false
- name: Check for Semantic Version label
uses: apple/swift-nio/.github/actions/pull_request_semver_label_checker@main
================================================
FILE: .gitignore
================================================
.DS_Store
/.build
/Packages
/*.xcodeproj
xcuserdata/
.swiftpm/
Package.resolved
================================================
FILE: .licenseignore
================================================
.gitignore
**/.gitignore
.licenseignore
.gitattributes
.git-blame-ignore-revs
.mailfilter
.mailmap
.spi.yml
.swift-format
.editorconfig
.github/*
*.md
*.txt
*.yml
*.yaml
*.json
Package.swift
**/Package.swift
Package@-*.swift
**/Package@-*.swift
Package.resolved
**/Package.resolved
Makefile
*.modulemap
**/*.modulemap
**/*.docc/*
*.xcprivacy
**/*.xcprivacy
*.symlink
**/*.symlink
Dockerfile
**/Dockerfile
Snippets/*
dev/git.commit.template
.unacceptablelanguageignore
================================================
FILE: .spi.yml
================================================
version: 1
builder:
configs:
- documentation_targets: [RawStructuredFieldValues, StructuredFieldValues]
================================================
FILE: .swift-format
================================================
{
"version" : 1,
"indentation" : {
"spaces" : 4
},
"tabWidth" : 4,
"fileScopedDeclarationPrivacy" : {
"accessLevel" : "private"
},
"spacesAroundRangeFormationOperators" : false,
"indentConditionalCompilationBlocks" : false,
"indentSwitchCaseLabels" : false,
"lineBreakAroundMultilineExpressionChainComponents" : false,
"lineBreakBeforeControlFlowKeywords" : false,
"lineBreakBeforeEachArgument" : true,
"lineBreakBeforeEachGenericRequirement" : true,
"lineLength" : 120,
"maximumBlankLines" : 1,
"respectsExistingLineBreaks" : true,
"prioritizeKeepingFunctionOutputTogether" : true,
"noAssignmentInExpressions" : {
"allowedFunctions" : [
"XCTAssertNoThrow",
"XCTAssertThrowsError"
]
},
"rules" : {
"AllPublicDeclarationsHaveDocumentation" : false,
"AlwaysUseLiteralForEmptyCollectionInit" : false,
"AlwaysUseLowerCamelCase" : false,
"AmbiguousTrailingClosureOverload" : true,
"BeginDocumentationCommentWithOneLineSummary" : false,
"DoNotUseSemicolons" : true,
"DontRepeatTypeInStaticProperties" : true,
"FileScopedDeclarationPrivacy" : true,
"FullyIndirectEnum" : true,
"GroupNumericLiterals" : true,
"IdentifiersMustBeASCII" : true,
"NeverForceUnwrap" : false,
"NeverUseForceTry" : false,
"NeverUseImplicitlyUnwrappedOptionals" : false,
"NoAccessLevelOnExtensionDeclaration" : true,
"NoAssignmentInExpressions" : true,
"NoBlockComments" : true,
"NoCasesWithOnlyFallthrough" : true,
"NoEmptyTrailingClosureParentheses" : true,
"NoLabelsInCasePatterns" : true,
"NoLeadingUnderscores" : false,
"NoParensAroundConditions" : true,
"NoVoidReturnOnFunctionSignature" : true,
"OmitExplicitReturns" : true,
"OneCasePerLine" : true,
"OneVariableDeclarationPerLine" : true,
"OnlyOneTrailingClosureArgument" : true,
"OrderedImports" : true,
"ReplaceForEachWithForLoop" : true,
"ReturnVoidInsteadOfEmptyTuple" : true,
"UseEarlyExits" : false,
"UseExplicitNilCheckInConditions" : false,
"UseLetInEveryBoundCaseVariable" : false,
"UseShorthandTypeNames" : true,
"UseSingleLinePropertyGetter" : false,
"UseSynthesizedInitializer" : false,
"UseTripleSlashForDocumentationComments" : true,
"UseWhereClausesInForLoops" : false,
"ValidateDocumentationComments" : false
}
}
================================================
FILE: CONTRIBUTORS.txt
================================================
For the purpose of tracking copyright, this is the list of individuals and
organizations who have contributed source code to SwiftNIO.
For employees of an organization/company where the copyright of work done
by employees of that company is held by the company itself, only the company
needs to be listed here.
## COPYRIGHT HOLDERS
- Apple Inc. (all contributors with '@apple.com')
### Contributors
- Cory Benfield <lukasa@apple.com>
**Updating this list**
Please do not edit this file manually. It is generated using `./scripts/generate_contributors_list.sh`. If a name is misspelled or appearing multiple times: add an entry in `./.mailmap`
================================================
FILE: LICENSE.txt
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: Package.swift
================================================
// swift-tools-version:6.1
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2020-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
//
//===----------------------------------------------------------------------===//
import PackageDescription
let strictConcurrencyDevelopment = false
let strictConcurrencySettings: [SwiftSetting] = {
var initialSettings: [SwiftSetting] = []
if strictConcurrencyDevelopment {
// -warnings-as-errors here is a workaround so that IDE-based development can
// get tripped up on -require-explicit-sendable.
initialSettings.append(.unsafeFlags(["-require-explicit-sendable", "-warnings-as-errors"]))
}
return initialSettings
}()
let package = Package(
name: "swift-http-structured-headers",
products: [
.library(
name: "StructuredFieldValues",
targets: ["StructuredFieldValues"]
),
.library(
name: "RawStructuredFieldValues",
targets: ["RawStructuredFieldValues"]
),
],
targets: [
.target(
name: "RawStructuredFieldValues",
dependencies: [],
swiftSettings: strictConcurrencySettings
),
.target(
name: "StructuredFieldValues",
dependencies: ["RawStructuredFieldValues"],
swiftSettings: strictConcurrencySettings
),
.executableTarget(
name: "sh-parser",
dependencies: ["RawStructuredFieldValues"],
swiftSettings: strictConcurrencySettings
),
.testTarget(
name: "StructuredFieldValuesTests",
dependencies: ["RawStructuredFieldValues", "StructuredFieldValues"],
swiftSettings: strictConcurrencySettings
),
]
)
// --- STANDARD CROSS-REPO SETTINGS DO NOT EDIT --- //
for target in package.targets {
switch target.type {
case .regular, .test, .executable:
var settings = target.swiftSettings ?? []
// https://github.com/swiftlang/swift-evolution/blob/main/proposals/0444-member-import-visibility.md
settings.append(.enableUpcomingFeature("MemberImportVisibility"))
target.swiftSettings = settings
case .macro, .plugin, .system, .binary:
() // not applicable
@unknown default:
() // we don't know what to do here, do nothing
}
}
// --- END: STANDARD CROSS-REPO SETTINGS DO NOT EDIT --- //
================================================
FILE: README.md
================================================
# swift-http-structured-headers
A Swift implementation of the HTTP Structured Header Field Value specification.
Provides parsing and serialization facilities for structured header field values, as well as implementations of `Encoder` and `Decoder` to allow using `Codable` data types as the payloads of HTTP structured header fields.
## About Structured Header Field Values
HTTP Structured Header Field Values are a HTTP extension recorded in [RFC 9651](https://www.ietf.org/rfc/rfc9651.html). They provide a set of data types and algorithms for handling HTTP header field values in a consistent way, allowing a single parser and serializer to handle a wide range of header field values.
## Swift HTTP Structured Header Field Values
This package provides a parser and serializer that implement RFC 9651. They are entirely complete, able to handle all valid HTTP structured header field values. This package also provides `Encoder` and `Decoder` objects for working with Codable in Swift. This allows rapid prototyping and experimentation with HTTP structured header field values, as well as interaction with the wider Swift Codable community.
This package provides two top-level modules: `StructuredFieldValues` and `RawStructuredFieldValues`.
The base module, `RawStructuredFieldValues`, provides a low-level implementation of a serializer and parser. Both of these have been written to avoid using Foundation, making them suitable for a range of use-cases where Foundation is not available. They rely entirely on the Swift standard library and are implemented as generically as possible. One of the limitations due to the absence of Foundation is that this interface is not capable of performing Base64 encoding or decoding: users are free to bring whatever encoder and decoder they choose to use.
This API is low-level, exposing the raw parse tree as the format for the serializer and parser. This allows high-performance and high-flexibility parsing and serialization, at the cost of being verbose and complex. Users are required to understand the structured header format and to operate the slightly awkward types, but maximal fidelity is retained and the performance overhead is low.
The upper-level module, `StructuredFieldValues`, brings along the `Encoder` and `Decoder` and also adds a dependency on Foundation. This Foundation dependency is necessary to correctly handle the base64 formatting, as well as to provide a good natural container for binary data: `Data`, and for dates: `Date`. This interface is substantially friendlier and easier to work with, using Swift's `Codable` support to provide a great user experience.
In most cases users should prefer to use `StructuredFieldValues` unless they know they need the performance advantages of `RawStructuredFieldValues`. The experience will be much better.
## Working with Structured Header Field Values
`swift-http-structured-headers` has a simple, easy-to-use high-level API for working with structured header field values. To begin with, let's consider the [HTTP Client Hints specification](https://www.rfc-editor.org/rfc/rfc8942.html). This defines the following new header field:
> The Accept-CH response header field indicates server support for the hints indicated in its value. Servers wishing to receive user agent information through Client Hints SHOULD add Accept-CH response header to their responses as early as possible.
>
> Accept-CH is a Structured Header. Its value MUST be an sf-list whose members are tokens. Its ABNF is:
>
> Accept-CH = sf-list
>
> For example:
>
> Accept-CH: Sec-CH-Example, Sec-CH-Example-2
`swift-http-structured-headers` can parse and serialize this field very simply:
```swift
let field = Array("Sec-CH-Example, Sec-CH-Example-2".utf8)
struct AcceptCH: StructuredFieldValue {
static let structuredFieldType: StructuredFieldType = .list
var items: [String]
}
// Decoding
let decoder = StructuredFieldValueDecoder()
let parsed = try decoder.decode(AcceptCH.self, from: field)
// Encoding
let encoder = StructuredFieldValueEncoder()
let serialized = try encoder.encode(AcceptCH(items: ["Sec-CH-Example", "Sec-CH-Example-2"]))
```
However, structured header field values can be substantially more complex. Structured header fields can make use of 4 containers and 8 base item types. The containers are:
1. Dictionaries. These are top-level elements and associate token keys with values. The values may be items, or may be inner lists, and each value may also have parameters associated with them. `StructuredFieldValues` can model dictionaries as either Swift objects (where the property names are dictionary keys).
2. Lists. These are top-level elements, providing a sequence of items or inner lists. Each item or inner list may have parameters associated with them. `StructuredFieldValues` models these as Swift objects with one key, `items`, that must be a collection of entries.
3. Inner Lists. These are lists that may be sub-entries of a dictionary or a list. The list entries are items, which may have parameters associated with them: additionally, an inner list may have parameters associated with itself as well. `StructuredFieldValues` models these as either Swift `Array`s _or_, if it's important to extract parameters, as a two-field Swift `struct` where one field is called `items` and contains an `Array`, and other field is called `parameters` and contains a dictionary.
4. Parameters. Parameters associate token keys with items without parameters. These are used to store metadata about objects within a field. `StructuredFieldValues` models these as either Swift objects (where the property names are the parameter keys) or as Swift dictionaries.
The base types are:
1. Booleans. `StructuredFieldValues` models these as Swift's `Bool` type.
2. Integers. `StructuredFieldValues` models these as any fixed-width integer type.
3. Decimals. `StructuredFieldValues` models these as any floating-point type, or as Foundation's `Decimal`.
4. Tokens. `StructuredFieldValues` models these as Swift's `String` type, where the range of characters is restricted.
5. Strings. `StructuredFieldValues` models these as Swift's `String` type.
6. Binary data. `StructuredFieldValues` models this as Foundation's `Data` type.
7. Dates. `StructuredFieldValues` models these as Foundation's `Date` type.
8. Display strings. `StructuredFieldValues` models these as the `DisplayString` type which it provides.
For any Structured Header Field Value Item, the item may either be represented directly by the appropriate type, or by a Swift struct with two properties: `item` and `parameters`. This latter mode is how parameters on a given item may be captured.
The top-level Structured Header Field Value must identify what kind of header field it corresponds to: `.item`, `.list`, or `.dictionary`. This is inherent in the type of the field and will be specified in the relevant field specification.
## Lower Levels
In some cases the Codable interface will not be either performant enough or powerful enough for the intended use-case. In cases like this, users can use the types in the `RawStructuredFieldValues` module instead.
There are two core types: `StructuredFieldValueParser` and `StructuredFieldValueSerializer`. Rather than work with high-level Swift objects, these two objects either produce or accept a Swift representation of the data tree for a given structured header field.
This exposes the maximum amount of information about the header field. It allows users to handle situations where Codable cannot necessarily provide the relevant information, such in cases where dictionary ordering is semantic, or where it's necessary to control whether fields are tokens or strings more closely.
These APIs also have lower overhead than the `StructuredFieldValues` APIs.
The cost is that the APIs are substantially more verbose. Consider the above header field, `Accept-CH`. To parse or serialize this in `RawStructuredFieldValues` would look like this:
```swift
let field = Array("Sec-CH-Example, Sec-CH-Example-2".utf8)
var parser = StructuredFieldValueParser(field)
let parsed = parser.parseListFieldValue()
print(parsed)
// [
// .item(Item(bareItem: .token("Sec-CH-Example"), parameters: [])),
// .item(Item(bareItem: .token("Sec-CH-Example-2"), parameters: [])),
// ]
var serializer = StructuredFieldValueSerializer()
let serialized = serializer.writeListFieldValue(parsed)
```
Notice the substantially more verbose types involved in this operation. These types are highly generic, giving the opportunity for parsing and serializing that greatly reduces the runtime overhead. They also make it easier to distinguish between tokens and strings, and to observe the order of objects in dictionaries or parameters, which can be lost at the Codable level.
In general, users should consider this API only when they are confident they need either the flexibility or the performance. This may be valuable for header fields that do not evolve often, or that are highly dynamic.
## Security
swift-http-structured-headers has a security policy outlined in [SECURITY.md](SECURITY.md).
================================================
FILE: Sources/RawStructuredFieldValues/ASCII.swift
================================================
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2020 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
//
//===----------------------------------------------------------------------===//
let asciiSpace = UInt8(ascii: " ")
let asciiTab = UInt8(ascii: "\t")
let asciiOpenParenthesis = UInt8(ascii: "(")
let asciiCloseParenthesis = UInt8(ascii: ")")
let asciiDash = UInt8(ascii: "-")
let asciiUnderscore = UInt8(ascii: "_")
let asciiZero = UInt8(ascii: "0")
let asciiOne = UInt8(ascii: "1")
let asciiNine = UInt8(ascii: "9")
let asciiDigits = asciiZero...asciiNine
let asciiDquote = UInt8(ascii: "\"")
let asciiColon = UInt8(ascii: ":")
let asciiSemicolon = UInt8(ascii: ";")
let asciiBackslash = UInt8(ascii: "\\")
let asciiQuestionMark = UInt8(ascii: "?")
let asciiExclamationMark = UInt8(ascii: "!")
let asciiAt = UInt8(ascii: "@")
let asciiOctothorpe = UInt8(ascii: "#")
let asciiDollar = UInt8(ascii: "$")
let asciiPercent = UInt8(ascii: "%")
let asciiAmpersand = UInt8(ascii: "&")
let asciiSquote = UInt8(ascii: "'")
let asciiCaret = UInt8(ascii: "^")
let asciiBacktick = UInt8(ascii: "`")
let asciiPipe = UInt8(ascii: "|")
let asciiTilde = UInt8(ascii: "~")
let asciiAsterisk = UInt8(ascii: "*")
let asciiEqual = UInt8(ascii: "=")
let asciiPlus = UInt8(ascii: "+")
let asciiSlash = UInt8(ascii: "/")
let asciiPeriod = UInt8(ascii: ".")
let asciiComma = UInt8(ascii: ",")
let asciiCapitalA = UInt8(ascii: "A")
let asciiCapitalF = UInt8(ascii: "F")
let asciiCapitalZ = UInt8(ascii: "Z")
let asciiLowerA = UInt8(ascii: "a")
let asciiLowerF = UInt8(ascii: "f")
let asciiLowerZ = UInt8(ascii: "z")
let asciiCapitals = asciiCapitalA...asciiCapitalZ
let asciiLowercases = asciiLowerA...asciiLowerZ
let asciiHexCapitals = asciiCapitalA...asciiCapitalF
let asciiHexLowercases = asciiLowerA...asciiLowerF
================================================
FILE: Sources/RawStructuredFieldValues/ComponentTypes.swift
================================================
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2020 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
//
//===----------------------------------------------------------------------===//
// This file contains common, currency, component types.
//
// These types are used by the parser, the serializer, and by users of the direct encoding/decoding APIs.
// They are not used by those using the Codable interface.
// MARK: - ItemOrInnerList
/// `ItemOrInnerList` represents the values in a structured header dictionary, or the
/// entries in a structured header list.
public enum ItemOrInnerList: Sendable {
case item(Item)
case innerList(InnerList)
}
extension ItemOrInnerList: Hashable {}
// MARK: - BareItem
/// `BareItem` is a representation of the base data types at the bottom of a structured
/// header field. These types are not parameterised: they are raw data.
@available(*, deprecated, renamed: "RFC9651BareItem")
public enum BareItem: Sendable {
/// A boolean item.
case bool(Bool)
/// An integer item.
case integer(Int)
/// A decimal item.
case decimal(PseudoDecimal)
/// A string item.
case string(String)
/// A byte sequence. This case must contain base64-encoded data, as
/// `StructuredHeaders` does not do base64 encoding or decoding.
case undecodedByteSequence(String)
/// A token item.
case token(String)
}
@available(*, deprecated)
extension BareItem: ExpressibleByBooleanLiteral {
public init(booleanLiteral value: Bool) {
self = .bool(value)
}
}
@available(*, deprecated)
extension BareItem: ExpressibleByIntegerLiteral {
public init(integerLiteral value: Int) {
self = .integer(value)
}
}
@available(*, deprecated)
extension BareItem: ExpressibleByFloatLiteral {
public init(floatLiteral value: Float64) {
self = .decimal(.init(floatLiteral: value))
}
}
@available(*, deprecated)
extension BareItem: ExpressibleByStringLiteral {
public init(stringLiteral value: StringLiteralType) {
if value.structuredHeadersIsValidToken {
self = .token(value)
} else {
self = .string(value)
}
}
}
@available(*, deprecated)
extension BareItem {
init(transforming newItem: RFC9651BareItem) throws {
switch newItem {
case .bool(let b):
self = .bool(b)
case .integer(let i):
self = .integer(Int(i))
case .decimal(let d):
self = .decimal(d)
case .string(let s):
self = .string(s)
case .undecodedByteSequence(let s):
self = .undecodedByteSequence(s)
case .token(let t):
self = .token(t)
case .date:
throw StructuredHeaderError.invalidItem
case .displayString:
throw StructuredHeaderError.invalidItem
}
}
}
@available(*, deprecated)
extension BareItem: Hashable {}
/// `RFC9651BareItem` is a representation of the base data types at the bottom of a structured
/// header field. These types are not parameterised: they are raw data.
public enum RFC9651BareItem: Sendable {
/// A boolean item.
case bool(Bool)
/// An integer item.
case integer(Int64)
/// A decimal item.
case decimal(PseudoDecimal)
/// A string item.
case string(String)
/// A byte sequence. This case must contain base64-encoded data, as
/// `StructuredHeaders` does not do base64 encoding or decoding.
case undecodedByteSequence(String)
/// A token item.
case token(String)
/// A date item.
case date(Int64)
/// A display string item.
case displayString(String)
}
extension RFC9651BareItem: ExpressibleByBooleanLiteral {
public init(booleanLiteral value: Bool) {
self = .bool(value)
}
}
extension RFC9651BareItem: ExpressibleByIntegerLiteral {
public init(integerLiteral value: Int64) {
self = .integer(value)
}
}
extension RFC9651BareItem: ExpressibleByFloatLiteral {
public init(floatLiteral value: Float64) {
self = .decimal(.init(floatLiteral: value))
}
}
extension RFC9651BareItem: ExpressibleByStringLiteral {
public init(stringLiteral value: StringLiteralType) {
if value.structuredHeadersIsValidToken {
self = .token(value)
} else {
self = .string(value)
}
}
}
extension RFC9651BareItem {
@available(*, deprecated)
init(transforming oldItem: BareItem) throws {
switch oldItem {
case .bool(let b):
self = .bool(b)
case .integer(let i):
self = .integer(Int64(i))
case .decimal(let d):
self = .decimal(d)
case .string(let s):
self = .string(s)
case .undecodedByteSequence(let s):
self = .undecodedByteSequence(s)
case .token(let t):
self = .token(t)
}
}
}
extension RFC9651BareItem: Hashable {}
// MARK: - Item
/// `Item` represents a structured header field item: a combination of a `bareItem`
/// and some parameters.
public struct Item: Sendable {
/// The `BareItem` that this `Item` contains.
@available(*, deprecated, renamed: "rfc9651BareItem")
public var bareItem: BareItem {
get {
try! .init(transforming: self.rfc9651BareItem)
}
set {
try! self.rfc9651BareItem = .init(transforming: newValue)
}
}
/// The parameters associated with `bareItem`
@available(*, deprecated, renamed: "rfc9651Parameters")
public var parameters: OrderedMap<String, BareItem> {
get {
try! self.rfc9651Parameters.mapValues { try .init(transforming: $0) }
}
set {
try! self.rfc9651Parameters = newValue.mapValues { try .init(transforming: $0) }
}
}
/// The `BareItem` that this `Item` contains.
public var rfc9651BareItem: RFC9651BareItem
/// The parameters associated with `rfc9651BareItem`
public var rfc9651Parameters: OrderedMap<String, RFC9651BareItem>
@available(*, deprecated)
public init(bareItem: BareItem, parameters: OrderedMap<String, BareItem>) {
self.rfc9651BareItem = .integer(1)
self.rfc9651Parameters = OrderedMap()
self.bareItem = bareItem
self.parameters = parameters
}
public init(bareItem: RFC9651BareItem, parameters: OrderedMap<String, RFC9651BareItem>) {
self.rfc9651BareItem = bareItem
self.rfc9651Parameters = parameters
}
}
extension Item: Hashable {}
// MARK: - BareInnerList
/// A `BareInnerList` represents the items contained within an ``InnerList``, without
/// the associated parameters.
public struct BareInnerList: Hashable, Sendable {
private var items: [Item]
public init() {
self.items = []
}
public mutating func append(_ item: Item) {
self.items.append(item)
}
}
extension BareInnerList: ExpressibleByArrayLiteral {
public init(arrayLiteral elements: Item...) {
self.items = elements
}
}
// TODO: RangeReplaceableCollection I guess
extension BareInnerList: RandomAccessCollection, MutableCollection {
public struct Index: Sendable {
fileprivate var baseIndex: Array<Item>.Index
init(_ baseIndex: Array<Item>.Index) {
self.baseIndex = baseIndex
}
}
public var count: Int {
self.items.count
}
public var startIndex: Index {
Index(self.items.startIndex)
}
public var endIndex: Index {
Index(self.items.endIndex)
}
public func index(after i: Index) -> Index {
Index(self.items.index(after: i.baseIndex))
}
public func index(before i: Index) -> Index {
Index(self.items.index(before: i.baseIndex))
}
public func index(_ i: Index, offsetBy offset: Int) -> Index {
Index(self.items.index(i.baseIndex, offsetBy: offset))
}
public subscript(index: Index) -> Item {
get {
self.items[index.baseIndex]
}
set {
self.items[index.baseIndex] = newValue
}
}
}
extension BareInnerList.Index: Hashable {}
extension BareInnerList.Index: Comparable {
public static func < (lhs: Self, rhs: Self) -> Bool {
lhs.baseIndex < rhs.baseIndex
}
}
// MARK: - InnerList
/// An `InnerList` is a list of items, with some associated parameters.
public struct InnerList: Hashable, Sendable {
/// The items contained within this inner list.
public var bareInnerList: BareInnerList
/// The parameters associated with the `bareInnerList`.
@available(*, deprecated, renamed: "rfc9651Parameters")
public var parameters: OrderedMap<String, BareItem> {
get {
try! self.rfc9651Parameters.mapValues { try .init(transforming: $0) }
}
set {
try! self.rfc9651Parameters = newValue.mapValues { try .init(transforming: $0) }
}
}
/// The parameters associated with the `bareInnerList`.
public var rfc9651Parameters: OrderedMap<String, RFC9651BareItem>
@available(*, deprecated)
public init(bareInnerList: BareInnerList, parameters: OrderedMap<String, BareItem>) {
self.rfc9651Parameters = OrderedMap()
self.bareInnerList = bareInnerList
self.parameters = parameters
}
public init(bareInnerList: BareInnerList, parameters: OrderedMap<String, RFC9651BareItem>) {
self.bareInnerList = bareInnerList
self.rfc9651Parameters = parameters
}
}
extension String {
/// Whether this string is a valid structured headers token, or whether it would
/// need to be stored in a structured headers string.
public var structuredHeadersIsValidToken: Bool {
let view = self.utf8
switch view.first {
case .some(asciiCapitals), .some(asciiLowercases), .some(asciiAsterisk):
// Good
()
default:
return false
}
for byte in view {
switch byte {
// Valid token characters are RFC 7230 tchar, colon, and slash.
// tchar is:
//
// tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*"
// / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~"
// / DIGIT / ALPHA
//
// The following unfortunate case statement covers this. Tokens; not even once.
case asciiExclamationMark, asciiOctothorpe, asciiDollar, asciiPercent,
asciiAmpersand, asciiSquote, asciiAsterisk, asciiPlus, asciiDash,
asciiPeriod, asciiCaret, asciiUnderscore, asciiBacktick, asciiPipe,
asciiTilde, asciiDigits, asciiCapitals, asciiLowercases,
asciiColon, asciiSlash:
// Good
()
default:
// Bad token
return false
}
}
return true
}
}
================================================
FILE: Sources/RawStructuredFieldValues/Docs.docc/index.md
================================================
# ``RawStructuredFieldValues``
A Swift implementation of the HTTP Structured Header Field Value specification.
Provides parsing and serialization facilities for structured header field values.
## Overview
### About Structured Header Field Values
HTTP Structured Header Field Values are a HTTP extension recorded in [RFC 9651](https://www.ietf.org/rfc/rfc9651.html). They provide a set of data types and algorithms for handling HTTP header field values in a consistent way, allowing a single parser and serializer to handle a wide range of header field values.
### Swift HTTP Structured Header Field Values
This package provides a parser and serializer that implement RFC 9651. They are entirely complete, able to handle all valid HTTP structured header field values.
This package provides two top-level modules: `StructuredFieldValues` and `RawStructuredFieldValues`.
This module, `RawStructuredFieldValues`, provides a low-level implementation of a serializer and parser. Both of these have been written to avoid using Foundation, making them suitable for a range of use-cases where Foundation is not available. They rely entirely on the Swift standard library and are implemented as generically as possible. One of the limitations due to the absence of Foundation is that this interface is not capable of performing Base64 encoding or decoding: users are free to bring whatever encoder and decoder they choose to use.
This API is low-level, exposing the raw parse tree as the format for the serializer and parser. This allows high-performance and high-flexibility parsing and serialization, at the cost of being verbose and complex. Users are required to understand the structured header format and to operate the slightly awkward types, but maximal fidelity is retained and the performance overhead is low.
The upper-level module, `StructuredFieldValues`, brings along the `Encoder` and `Decoder` and also adds a dependency on Foundation. This Foundation dependency is necessary to correctly handle the base64 formatting, as well as to provide a good natural container for binary data: `Data`, and for dates: `Date`. This interface is substantially friendlier and easier to work with, using Swift's `Codable` support to provide a great user experience.
In most cases users should prefer to use `StructuredFieldValues` unless they know they need the performance advantages of `RawStructuredFieldValues`. The experience will be much better.
### Working with Structured Header Field Values
`RawStructuredFieldValues` has a powerful API for working with structured header field values.
There are two core types: ``StructuredFieldValueParser`` and ``StructuredFieldValueSerializer``. Rather than work with high-level Swift objects, these two objects either produce or accept a Swift representation of the data tree for a given structured header field.
This exposes the maximum amount of information about the header field. It allows users to handle situations where `Codable` cannot necessarily provide the relevant information, such in cases where dictionary ordering is semantic, or where it's necessary to control whether fields are tokens or strings more closely.
These APIs also have lower overhead than the `StructuredFieldValues` APIs.
The cost is that the APIs are substantially more verbose. As an example, let's consider the [HTTP Client Hints specification](https://www.rfc-editor.org/rfc/rfc8942.html). This defines the following new header field:
```
The Accept-CH response header field indicates server support for the hints indicated in its value. Servers wishing to receive user agent information through Client Hints SHOULD add Accept-CH response header to their responses as early as possible.
Accept-CH is a Structured Header. Its value MUST be an sf-list whose members are tokens. Its ABNF is:
Accept-CH = sf-list
For example:
Accept-CH: Sec-CH-Example, Sec-CH-Example-2
```
To parse or serialize this in `RawStructuredFieldValues` would look like this:
```swift
let field = Array("Sec-CH-Example, Sec-CH-Example-2".utf8)
var parser = StructuredFieldValueParser(field)
let parsed = parser.parseListFieldValue()
print(parsed)
// [
// .item(Item(bareItem: .token("Sec-CH-Example"), parameters: [])),
// .item(Item(bareItem: .token("Sec-CH-Example-2"), parameters: [])),
// ]
var serializer = StructuredFieldValueSerializer()
let serialized = serializer.writeListFieldValue(parsed)
```
Notice the substantially more verbose types involved in this operation. These types are highly generic, giving the opportunity for parsing and serializing that greatly reduces the runtime overhead. They also make it easier to distinguish between tokens and strings, and to observe the order of objects in dictionaries or parameters, which can be lost at the Codable level.
In general, users should consider this API only when they are confident they need either the flexibility or the performance. This may be valuable for header fields that do not evolve often, or that are highly dynamic.
## Topics
### Representing Structured Field Values
- ``InnerList``
- ``BareInnerList``
- ``Item``
- ``BareItem``
- ``ItemOrInnerList``
### Helper Types
- ``OrderedMap``
- ``PseudoDecimal``
### Parsing and Serializing
- ``StructuredFieldValueParser``
- ``StructuredFieldValueSerializer``
### Errors
- ``StructuredHeaderError``
================================================
FILE: Sources/RawStructuredFieldValues/Errors.swift
================================================
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2020 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
//
//===----------------------------------------------------------------------===//
// MARK: - StructuredHeaderError
/// Errors that may be encountered when working with structured headers.
public struct StructuredHeaderError: Error, Sendable {
private enum _BaseError: Hashable {
case invalidTrailingBytes
case invalidInnerList
case invalidItem
case invalidKey
case invalidIntegerOrDecimal
case invalidString
case invalidByteSequence
case invalidBoolean
case invalidToken
case invalidDate
case invalidDisplayString
case invalidList
case invalidDictionary
case missingKey
case invalidTypeForItem
case integerOutOfRange
case indexOutOfRange
}
private var base: _BaseError
private init(_ base: _BaseError) {
self.base = base
}
}
extension StructuredHeaderError {
public static let invalidTrailingBytes = StructuredHeaderError(.invalidTrailingBytes)
public static let invalidInnerList = StructuredHeaderError(.invalidInnerList)
public static let invalidItem = StructuredHeaderError(.invalidItem)
public static let invalidKey = StructuredHeaderError(.invalidKey)
public static let invalidIntegerOrDecimal = StructuredHeaderError(.invalidIntegerOrDecimal)
public static let invalidString = StructuredHeaderError(.invalidString)
public static let invalidByteSequence = StructuredHeaderError(.invalidByteSequence)
public static let invalidBoolean = StructuredHeaderError(.invalidBoolean)
public static let invalidToken = StructuredHeaderError(.invalidToken)
public static let invalidDate = StructuredHeaderError(.invalidDate)
public static let invalidDisplayString = StructuredHeaderError(.invalidDisplayString)
public static let invalidList = StructuredHeaderError(.invalidList)
public static let invalidDictionary = StructuredHeaderError(.invalidDictionary)
public static let missingKey = StructuredHeaderError(.missingKey)
public static let invalidTypeForItem = StructuredHeaderError(.invalidTypeForItem)
public static let integerOutOfRange = StructuredHeaderError(.integerOutOfRange)
public static let indexOutOfRange = StructuredHeaderError(.indexOutOfRange)
}
extension StructuredHeaderError: Hashable {}
extension StructuredHeaderError: CustomStringConvertible {
public var description: String {
String(describing: self.base)
}
}
================================================
FILE: Sources/RawStructuredFieldValues/FieldParser.swift
================================================
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2020-2021 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
//
//===----------------------------------------------------------------------===//
/// A `StructuredFieldValueParser` is the basic parsing object for structured header field values.
public struct StructuredFieldValueParser<BaseData: RandomAccessCollection> where BaseData.Element == UInt8 {
// Right now I'm on the fence about whether this should be generic. It's convenient,
// and makes it really easy for us to express the parsing over a wide range of data types.
// But it risks code size in a really nasty way! We should validate that we don't pay too
// much for this flexibility.
private var underlyingData: BaseData.SubSequence
public init(_ data: BaseData) {
self.underlyingData = data[...]
}
}
extension StructuredFieldValueParser: Sendable where BaseData: Sendable, BaseData.SubSequence: Sendable {}
extension StructuredFieldValueParser {
// Helper typealiases to avoid the explosion of generic parameters
@available(*, deprecated, renamed: "RFC9651BareItem")
public typealias BareItem = RawStructuredFieldValues.BareItem
public typealias RFC9651BareItem = RawStructuredFieldValues.RFC9651BareItem
public typealias Item = RawStructuredFieldValues.Item
public typealias BareInnerList = RawStructuredFieldValues.BareInnerList
public typealias InnerList = RawStructuredFieldValues.InnerList
public typealias ItemOrInnerList = RawStructuredFieldValues.ItemOrInnerList
public typealias Key = String
/// Parse the HTTP structured field value as a list.
///
/// This is a straightforward implementation of the parser in the spec.
///
/// - throws: If the field value could not be parsed.
/// - returns: An array of items or inner lists.
public mutating func parseListFieldValue() throws -> [ItemOrInnerList] {
// Step one, strip leading spaces.
self.underlyingData.stripLeadingSpaces()
// Step 2, enter the core list parsing loop.
let members = try self._parseAList()
// Final step, strip trailing spaces (which are now leading spaces, natch).
self.underlyingData.stripLeadingSpaces()
// The data is _required_ to be empty now, if it isn't we fail.
guard self.underlyingData.count == 0 else {
throw StructuredHeaderError.invalidTrailingBytes
}
return members
}
/// Parse the HTTP structured header field value as a dictionary.
///
/// - throws: If the field value could not be parsed.
/// - returns: An ``OrderedMap`` corresponding to the entries in the dictionary.
public mutating func parseDictionaryFieldValue() throws -> OrderedMap<Key, ItemOrInnerList> {
// Step one, strip leading spaces.
self.underlyingData.stripLeadingSpaces()
// Step 2, enter the core dictionary parsing loop.
let map = try self._parseADictionary()
// Final step, strip trailing spaces (which are now leading spaces, natch).
self.underlyingData.stripLeadingSpaces()
// The data is _required_ to be empty now, if it isn't we fail.
guard self.underlyingData.count == 0 else {
throw StructuredHeaderError.invalidTrailingBytes
}
return map
}
/// Parse the HTTP structured header field value as an item.
///
/// - throws: If the field value could not be parsed.
/// - returns: The ``Item`` in the field.
public mutating func parseItemFieldValue() throws -> Item {
// Step one, strip leading spaces.
self.underlyingData.stripLeadingSpaces()
// Step 2, do the core parse.
let item = try self._parseAnItem()
// Final step, strip trailing spaces (which are now leading spaces, natch).
self.underlyingData.stripLeadingSpaces()
// The data is _required_ to be empty now, if it isn't we fail.
guard self.underlyingData.count == 0 else {
throw StructuredHeaderError.invalidTrailingBytes
}
return item
}
private mutating func _parseAList() throws -> [ItemOrInnerList] {
var results: [ItemOrInnerList] = []
loop: while self.underlyingData.count > 0 {
results.append(try self._parseAnItemOrInnerList())
self.underlyingData.stripLeadingOWS()
// If we've consumed all the data, the parse is finished.
guard let next = self.underlyingData.popFirst() else {
break loop
}
// Otherwise, the next character needs to be a comma.
guard next == asciiComma else {
throw StructuredHeaderError.invalidList
}
self.underlyingData.stripLeadingOWS()
guard self.underlyingData.count > 0 else {
// Trailing comma!
throw StructuredHeaderError.invalidList
}
}
return results
}
private mutating func _parseADictionary() throws -> OrderedMap<Key, ItemOrInnerList> {
var results = OrderedMap<Key, ItemOrInnerList>()
loop: while self.underlyingData.count > 0 {
let key = try self._parseAKey()
if self.underlyingData.first == asciiEqual {
self.underlyingData.consumeFirst()
results[key] = try self._parseAnItemOrInnerList()
} else {
results[key] = .item(Item(bareItem: true, parameters: try self._parseParameters()))
}
self.underlyingData.stripLeadingOWS()
/// If we've consumed all the data, the parse is finished.
guard let next = self.underlyingData.popFirst() else {
break loop
}
guard next == asciiComma else {
throw StructuredHeaderError.invalidDictionary
}
self.underlyingData.stripLeadingOWS()
guard self.underlyingData.count > 0 else {
// Trailing comma!
throw StructuredHeaderError.invalidList
}
}
return results
}
private mutating func _parseAnItemOrInnerList() throws -> ItemOrInnerList {
if self.underlyingData.first == asciiOpenParenthesis {
return .innerList(try self._parseAnInnerList())
} else {
return .item(try self._parseAnItem())
}
}
private mutating func _parseAnInnerList() throws -> InnerList {
precondition(self.underlyingData.popFirst() == asciiOpenParenthesis)
var innerList = BareInnerList()
while self.underlyingData.count > 0 {
self.underlyingData.stripLeadingSpaces()
if self.underlyingData.first == asciiCloseParenthesis {
// Consume, parse parameters, and complete.
self.underlyingData.consumeFirst()
let parameters = try self._parseParameters()
return InnerList(bareInnerList: innerList, parameters: parameters)
}
innerList.append(try self._parseAnItem())
let nextChar = self.underlyingData.first
guard nextChar == asciiSpace || nextChar == asciiCloseParenthesis else {
throw StructuredHeaderError.invalidInnerList
}
}
// If we got here, we never got the close character for the list. Not good! This is an error.
throw StructuredHeaderError.invalidInnerList
}
private mutating func _parseAnItem() throws -> Item {
let bareItem = try _parseABareItem()
let parameters = try self._parseParameters()
return Item(bareItem: bareItem, parameters: parameters)
}
private mutating func _parseABareItem() throws -> RFC9651BareItem {
guard let first = self.underlyingData.first else {
throw StructuredHeaderError.invalidItem
}
switch first {
case asciiDash, asciiDigits:
return try self._parseAnIntegerOrDecimal(isDate: false)
case asciiDquote:
return try self._parseAString()
case asciiColon:
return try self._parseAByteSequence()
case asciiQuestionMark:
return try self._parseABoolean()
case asciiCapitals, asciiLowercases, asciiAsterisk:
return try self._parseAToken()
case asciiAt:
return try self._parseADate()
case asciiPercent:
return try self._parseADisplayString()
default:
throw StructuredHeaderError.invalidItem
}
}
private mutating func _parseAnIntegerOrDecimal(isDate: Bool) throws -> RFC9651BareItem {
var sign = Int64(1)
var type = IntegerOrDecimal.integer
if let first = self.underlyingData.first, first == asciiDash {
sign = -1
self.underlyingData.consumeFirst()
}
guard let first = self.underlyingData.first, asciiDigits.contains(first) else {
throw StructuredHeaderError.invalidIntegerOrDecimal
}
var index = self.underlyingData.startIndex
let endIndex = self.underlyingData.endIndex
loop: while index < endIndex {
switch self.underlyingData[index] {
case asciiDigits:
// Do nothing
()
case asciiPeriod where type == .integer:
// If output_date is decimal, fail parsing.
if isDate {
throw StructuredHeaderError.invalidDate
}
// If input_number contains more than 12 characters, fail parsing. Otherwise,
// set type to decimal and consume.
if self.underlyingData.distance(from: self.underlyingData.startIndex, to: index) > 12 {
if isDate {
throw StructuredHeaderError.invalidDate
} else {
throw StructuredHeaderError.invalidIntegerOrDecimal
}
}
type = .decimal
default:
// Non period or number, we're done parsing.
break loop
}
// "Consume" the character by advancing.
self.underlyingData.formIndex(after: &index)
// If type is integer and the input contains more than 15 characters, or type is decimal and more than 16,
// fail parsing.
let count = self.underlyingData.distance(from: self.underlyingData.startIndex, to: index)
switch type {
case .integer:
if count > 15 {
if isDate {
throw StructuredHeaderError.invalidDate
} else {
throw StructuredHeaderError.invalidIntegerOrDecimal
}
}
case .decimal:
assert(isDate == false)
if count > 16 {
throw StructuredHeaderError.invalidIntegerOrDecimal
}
}
}
// Consume the string.
let integerBytes = self.underlyingData[..<index]
self.underlyingData = self.underlyingData[index...]
switch type {
case .integer:
// This intermediate string is sad, we should rewrite this manually to avoid it.
// This force-unwrap is safe, as we have validated that all characters are ascii digits.
let baseInt = Int64(String(decoding: integerBytes, as: UTF8.self), radix: 10)!
let resultingInt = baseInt * sign
if isDate {
return .date(resultingInt)
} else {
return .integer(resultingInt)
}
case .decimal:
// This must be non-nil, otherwise we couldn't have flipped to the decimal type.
let periodIndex = integerBytes.firstIndex(of: asciiPeriod)!
let periodIndexDistance = integerBytes.distance(from: periodIndex, to: integerBytes.endIndex)
if periodIndexDistance == 1 || periodIndexDistance > 4 {
// Period may not be last, or have more than three characters after it.
throw StructuredHeaderError.invalidIntegerOrDecimal
}
// Same notes here as above
var decimal = PseudoDecimal(bytes: integerBytes)
decimal.mantissa *= Int64(sign)
return .decimal(decimal)
}
}
private mutating func _parseAString() throws -> RFC9651BareItem {
assert(self.underlyingData.first == asciiDquote)
self.underlyingData.consumeFirst()
// Ok, let's pause. Here we need to parse out a String and turn it into...well, into something.
// It doesn't have to be a String now, but at some stage a user is going to want it to be a String,
// so we need to include the idea that we'll have to manifest a String at some point.
//
// The wrinkle here is we have to deal with escapes. Two characters may appear escaped in strings:
// dquote and backslash. Worse, _only_ those two may appear escaped: any other escape sequence is invalid.
// This means the most naive algorithm, which also happens to be best for the cache and branch predictor
// (just treat a string as a byte slice, walk forward until we find dquote, and slice it out) doesn't work.
//
// We can choose to do this _anyway_, by searching for the first unescaped dquote. But we have to remember
// to handle the escaping at some point, and we also have to police character validity. Doing this later risks
// that we'll have evicted these bytes from cache, forcing a cache miss to get them back. Not ideal.
//
// So we do a different thing. We walk the string twice: first to validate and find its length, and then the
// second time to actually create the String. We can't do this in one step without risking gravely over-allocating
// for the String, which would be sad, so we tolerate doing it twice. While we do it, we record whether the string
// contains escapes. If it doesn't, we know that we can fall back to the optimised String construction with no branches,
// making this fairly quick. If it does, well, we have branchy awkward code, but at least we have confidence that our
// data is in cache.
// First, walk 1: find the length, validate as we go, check for escapes.
var escapes = 0
var index = self.underlyingData.startIndex
var endIndex = self.underlyingData.endIndex
loop: while index < endIndex {
let char = self.underlyingData[index]
switch char {
case asciiBackslash:
self.underlyingData.formIndex(after: &index)
if index == endIndex {
throw StructuredHeaderError.invalidString
}
let next = self.underlyingData[index]
guard next == asciiDquote || next == asciiBackslash else {
throw StructuredHeaderError.invalidString
}
escapes += 1
case asciiDquote:
// Unquoted dquote, this is the end of the string.
endIndex = index
break loop
case 0x00...0x1F, 0x7F...:
// Forbidden bytes in string: string must be VCHAR and SP.
throw StructuredHeaderError.invalidString
default:
// Allowed, unescape, uncontrol byte.
()
}
self.underlyingData.formIndex(after: &index)
}
// Oops, fell off the back of the string.
if endIndex == self.underlyingData.endIndex {
throw StructuredHeaderError.invalidString
}
let stringSlice = self.underlyingData[self.underlyingData.startIndex..<index]
self.underlyingData.formIndex(after: &index)
self.underlyingData = self.underlyingData[index...]
// Ok, now we check: if we have encountered an escape, we have to fall back to the slow mode. If not,
// we can initialize the string directly.
if escapes == 0 {
return .string(String(decoding: stringSlice, as: UTF8.self))
} else {
return .string(String.decodingEscapes(stringSlice, escapes: escapes))
}
}
private mutating func _parseAByteSequence() throws -> RFC9651BareItem {
assert(self.underlyingData.first == asciiColon)
self.underlyingData.consumeFirst()
var index = self.underlyingData.startIndex
while index < self.underlyingData.endIndex {
switch self.underlyingData[index] {
case asciiColon:
// Hey, this is the end! The base64 data is the data prior to here.
let consumedSlice = self.underlyingData[..<index]
// Skip the colon and consume it.
self.underlyingData.formIndex(after: &index)
self.underlyingData = self.underlyingData[index...]
return .undecodedByteSequence(String(decoding: consumedSlice, as: UTF8.self))
case asciiCapitals, asciiLowercases, asciiDigits, asciiPlus, asciiSlash, asciiEqual:
// All valid characters for Base64 here.
self.underlyingData.formIndex(after: &index)
default:
// Invalid character
throw StructuredHeaderError.invalidByteSequence
}
}
// Whoops, got to the end, invalid byte sequence.
throw StructuredHeaderError.invalidByteSequence
}
private mutating func _parseABoolean() throws -> RFC9651BareItem {
assert(self.underlyingData.first == asciiQuestionMark)
self.underlyingData.consumeFirst()
switch self.underlyingData.first {
case asciiOne:
self.underlyingData.consumeFirst()
return true
case asciiZero:
self.underlyingData.consumeFirst()
return false
default:
// Whoops!
throw StructuredHeaderError.invalidBoolean
}
}
private mutating func _parseAToken() throws -> RFC9651BareItem {
assert(
asciiCapitals.contains(self.underlyingData.first!) || asciiLowercases.contains(self.underlyingData.first!)
|| self.underlyingData.first! == asciiAsterisk
)
var index = self.underlyingData.startIndex
loop: while index < self.underlyingData.endIndex {
switch self.underlyingData[index] {
// Valid token characters are RFC 7230 tchar, colon, and slash.
// tchar is:
//
// tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*"
// / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~"
// / DIGIT / ALPHA
//
// The following unfortunate case statement covers this. Tokens; not even once.
case asciiExclamationMark, asciiOctothorpe, asciiDollar, asciiPercent,
asciiAmpersand, asciiSquote, asciiAsterisk, asciiPlus, asciiDash,
asciiPeriod, asciiCaret, asciiUnderscore, asciiBacktick, asciiPipe,
asciiTilde, asciiDigits, asciiCapitals, asciiLowercases,
asciiColon, asciiSlash:
// Good, consume
self.underlyingData.formIndex(after: &index)
default:
// Token complete
break loop
}
}
// Token is complete either when we stop getting valid token characters or when
// we get to the end of the string.
let tokenSlice = self.underlyingData[..<index]
self.underlyingData = self.underlyingData[index...]
return .token(String(decoding: tokenSlice, as: UTF8.self))
}
private mutating func _parseADate() throws -> RFC9651BareItem {
assert(self.underlyingData.first == asciiAt)
self.underlyingData.consumeFirst()
return try self._parseAnIntegerOrDecimal(isDate: true)
}
private mutating func _parseADisplayString() throws -> RFC9651BareItem {
assert(self.underlyingData.first == asciiPercent)
self.underlyingData.consumeFirst()
guard self.underlyingData.first == asciiDquote else {
throw StructuredHeaderError.invalidDisplayString
}
self.underlyingData.consumeFirst()
var byteArray = [UInt8]()
while let char = self.underlyingData.first {
self.underlyingData.consumeFirst()
switch char {
case 0x00...0x1F, 0x7F...:
throw StructuredHeaderError.invalidDisplayString
case asciiPercent:
if self.underlyingData.count < 2 {
throw StructuredHeaderError.invalidDisplayString
}
let octetHex = EncodedHex(self.underlyingData.prefix(2))
self.underlyingData = self.underlyingData.dropFirst(2)
guard let octet = octetHex.decode() else {
throw StructuredHeaderError.invalidDisplayString
}
byteArray.append(octet)
case asciiDquote:
if #available(macOS 15.0, iOS 18.0, tvOS 18.0, watchOS 11.0, visionOS 2.0, *) {
let unicodeSequence = String(validating: byteArray, as: UTF8.self)
guard let unicodeSequence else {
throw StructuredHeaderError.invalidDisplayString
}
return .displayString(unicodeSequence)
} else {
return try _decodeDisplayString(byteArray: &byteArray)
}
default:
byteArray.append(char)
}
}
// Fail parsing — reached the end of the string without finding a closing DQUOTE.
throw StructuredHeaderError.invalidDisplayString
}
/// This method is called in environments where `String(validating:as:)` is unavailable. It uses
/// `String(validatingUTF8:)` which requires `byteArray` to be null terminated. `String(validating:as:)`
/// does not require that requirement. Therefore, it does not perform null checks, which makes it more optimal.
private func _decodeDisplayString(byteArray: inout [UInt8]) throws -> RFC9651BareItem {
// String(validatingUTF8:) requires byteArray to be null-terminated.
byteArray.append(0)
let unicodeSequence = byteArray.withUnsafeBytes {
$0.withMemoryRebound(to: CChar.self) {
// This force-unwrap is safe, as the buffer must successfully bind to CChar.
String(validatingCString: $0.baseAddress!)
}
}
guard let unicodeSequence else {
throw StructuredHeaderError.invalidDisplayString
}
return .displayString(unicodeSequence)
}
private mutating func _parseParameters() throws -> OrderedMap<Key, RFC9651BareItem> {
var parameters = OrderedMap<Key, RFC9651BareItem>()
// We want to loop while we still have bytes _and_ while the first character is asciiSemicolon.
// This covers both.
while self.underlyingData.first == asciiSemicolon {
// Consume the colon
self.underlyingData.consumeFirst()
self.underlyingData.stripLeadingSpaces()
let paramName = try self._parseAKey()
var paramValue: RFC9651BareItem = true
if self.underlyingData.first == asciiEqual {
self.underlyingData.consumeFirst()
paramValue = try self._parseABareItem()
}
parameters[paramName] = paramValue
}
return parameters
}
private mutating func _parseAKey() throws -> Key {
guard let first = self.underlyingData.first, asciiLowercases.contains(first) || first == asciiAsterisk else {
throw StructuredHeaderError.invalidKey
}
let key = self.underlyingData.prefix(while: {
switch $0 {
case asciiLowercases, asciiDigits, asciiUnderscore, asciiDash, asciiPeriod, asciiAsterisk:
return true
default:
return false
}
})
self.underlyingData = self.underlyingData.dropFirst(key.count)
return String(decoding: key, as: UTF8.self)
}
}
private enum IntegerOrDecimal {
case integer
case decimal
}
extension RandomAccessCollection where Element == UInt8, SubSequence == Self {
mutating func stripLeadingSpaces() {
self = self.drop(while: { $0 == asciiSpace })
}
mutating func stripLeadingOWS() {
self = self.drop(while: { $0 == asciiSpace || $0 == asciiTab })
}
mutating func consumeFirst() {
self = self.dropFirst()
}
}
extension String {
// This is the slow path, so we never inline this.
@inline(never)
fileprivate static func decodingEscapes<Bytes: RandomAccessCollection>(_ bytes: Bytes, escapes: Int) -> String
where Bytes.Element == UInt8 {
// We assume the string is previously validated, so the escapes are easily removed. See the doc comment for
// `StrippingStringEscapesCollection` for more details on what we're doing here.
let unescapedBytes = StrippingStringEscapesCollection(bytes, escapes: escapes)
if #available(macOS 10.16, macCatalyst 14.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *) {
return String(unsafeUninitializedCapacity: unescapedBytes.count) { innerPtr in
let (_, endIndex) = innerPtr.initialize(from: unescapedBytes)
return endIndex
}
} else {
return String(decoding: unescapedBytes, as: UTF8.self)
}
}
}
/// This helper struct is used to try to minimise the surface area of the unsafe string constructor.
///
/// We have a goal to create the String as cheaply as possible in the presence of escapes. Using the safe constructor will not
/// do that: if a non-contiguous collection is used to create a String directly, an intermediate Array will be used to flatten
/// the collection. As an escaped string is definitionally non-contiguous, we would hit that path, which is very sad.
/// Until this issue is fixed (https://bugs.swift.org/browse/SR-13111) we take a different approach: we use
/// `String.init(unsafeUninitializedCapacity:initializingWith)`. This is an unsafe function, so to reduce the unsafety as much
/// as possible we define this safe wrapping Collection and then use `copyBytes` to implement the initialization.
private struct StrippingStringEscapesCollection<BaseCollection: RandomAccessCollection>
where BaseCollection.Element == UInt8 {
private var base: BaseCollection
private var escapes: UInt
init(_ base: BaseCollection, escapes: Int) {
self.base = base
self.escapes = UInt(escapes)
}
}
extension StrippingStringEscapesCollection: Collection {
fileprivate struct Index {
fileprivate var _baseIndex: BaseCollection.Index
fileprivate init(baseIndex: BaseCollection.Index) {
self._baseIndex = baseIndex
}
}
// This is an extremely important customisation point! Our base collection is random access,
// so we know that on the base this is O(1), but as this collection is _not_ random access here
// it's O(n).
fileprivate var count: Int {
self.base.count - Int(self.escapes)
}
fileprivate var startIndex: Index {
// Tricky note here, but start index _might_ be an ascii backslash, which we have to skip.
Index(baseIndex: self.unescapedIndex(self.base.startIndex))
}
fileprivate var endIndex: Index {
Index(baseIndex: self.base.endIndex)
}
fileprivate func index(after i: Index) -> Index {
let next = self.base.index(after: i._baseIndex)
return Index(baseIndex: self.unescapedIndex(next))
}
fileprivate subscript(index: Index) -> UInt8 {
self.base[index._baseIndex]
}
private func unescapedIndex(_ baseIndex: BaseCollection.Index) -> BaseCollection.Index {
if baseIndex == self.base.endIndex {
return baseIndex
}
if self.base[baseIndex] == asciiBackslash {
return self.base.index(after: baseIndex)
} else {
return baseIndex
}
}
}
extension StrippingStringEscapesCollection.Index: Equatable {}
extension StrippingStringEscapesCollection.Index: Comparable {
fileprivate static func < (lhs: Self, rhs: Self) -> Bool {
lhs._baseIndex < rhs._baseIndex
}
}
/// `EncodedHex` represents a (possibly invalid) hex value in UTF8.
struct EncodedHex {
private(set) var firstChar: UInt8
private(set) var secondChar: UInt8
init<Bytes: RandomAccessCollection>(_ bytes: Bytes) where Bytes.Element == UInt8 {
precondition(bytes.count == 2)
self.firstChar = bytes[bytes.startIndex]
self.secondChar = bytes[bytes.index(after: bytes.startIndex)]
}
/// Validates and converts `EncodedHex` to a base 10 UInt8.
///
/// If `EncodedHex` does not represent a valid hex value, the result of this method is nil.
fileprivate func decode() -> UInt8? {
guard
let firstCharAsInteger = self.htoi(self.firstChar),
let secondCharAsInteger = self.htoi(self.secondChar)
else { return nil }
return (firstCharAsInteger << 4) + secondCharAsInteger
}
/// Converts a hex character given in UTF8 to its integer value.
private func htoi(_ asciiChar: UInt8) -> UInt8? {
switch asciiChar {
case asciiZero...asciiNine:
return asciiChar - asciiZero
case asciiLowerA...asciiLowerF:
return asciiChar - asciiLowerA + 10
default:
return nil
}
}
}
================================================
FILE: Sources/RawStructuredFieldValues/FieldSerializer.swift
================================================
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2020-2021 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
//
//===----------------------------------------------------------------------===//
private let validIntegerRange = Int64(-999_999_999_999_999)...Int64(999_999_999_999_999)
/// A `StructuredFieldValueSerializer` is the basic parsing object for structured header field values.
public struct StructuredFieldValueSerializer: Sendable {
private var data: [UInt8]
public init() {
self.data = []
}
}
extension StructuredFieldValueSerializer {
/// Writes a structured dictionary header field value.
///
/// - parameters:
/// - root: The dictionary object.
/// - throws: If the dictionary could not be serialized.
/// - returns: The bytes of the serialized header field value.
public mutating func writeDictionaryFieldValue(_ root: OrderedMap<String, ItemOrInnerList>) throws -> [UInt8] {
guard root.count > 0 else {
return []
}
defer {
self.data.removeAll(keepingCapacity: true)
}
try self.serializeADictionary(root)
return self.data
}
/// Writes a structured list header field value.
///
/// - parameters:
/// - list: The list object.
/// - throws: If the list could not be serialized.
/// - returns: The bytes of the serialized header field value.
public mutating func writeListFieldValue(_ list: [ItemOrInnerList]) throws -> [UInt8] {
guard list.count > 0 else {
return []
}
defer {
self.data.removeAll(keepingCapacity: true)
}
try self.serializeAList(list)
return self.data
}
/// Writes a structured item header field value.
///
/// - parameters:
/// - item: The item.
/// - throws: If the item could not be serialized.
/// - returns: The bytes of the serialized header field value.
public mutating func writeItemFieldValue(_ item: Item) throws -> [UInt8] {
defer {
self.data.removeAll(keepingCapacity: true)
}
try self.serializeAnItem(item)
return self.data
}
}
extension StructuredFieldValueSerializer {
private mutating func serializeADictionary(_ dictionary: OrderedMap<String, ItemOrInnerList>) throws {
for (name, value) in dictionary {
try self.serializeAKey(name)
if case .item(let item) = value, case .bool(true) = item.rfc9651BareItem {
try self.serializeParameters(item.rfc9651Parameters)
} else {
self.data.append(asciiEqual)
switch value {
case .innerList(let inner):
try self.serializeAnInnerList(inner)
case .item(let item):
try self.serializeAnItem(item)
}
}
self.data.append(asciiComma)
self.data.append(asciiSpace)
}
self.data.removeLast(2)
}
private mutating func serializeAList(_ list: [ItemOrInnerList]) throws {
for element in list.dropLast() {
switch element {
case .innerList(let innerList):
try self.serializeAnInnerList(innerList)
case .item(let item):
try self.serializeAnItem(item)
}
self.data.append(asciiComma)
self.data.append(asciiSpace)
}
if let last = list.last {
switch last {
case .innerList(let innerList):
try self.serializeAnInnerList(innerList)
case .item(let item):
try self.serializeAnItem(item)
}
}
}
private mutating func serializeAnInnerList(_ innerList: InnerList) throws {
self.data.append(asciiOpenParenthesis)
for item in innerList.bareInnerList.dropLast() {
try self.serializeAnItem(item)
self.data.append(asciiSpace)
}
if let last = innerList.bareInnerList.last {
try self.serializeAnItem(last)
}
self.data.append(asciiCloseParenthesis)
try self.serializeParameters(innerList.rfc9651Parameters)
}
private mutating func serializeAnItem(_ item: Item) throws {
try self.serializeABareItem(item.rfc9651BareItem)
try self.serializeParameters(item.rfc9651Parameters)
}
private mutating func serializeParameters(_ parameters: OrderedMap<String, RFC9651BareItem>) throws {
for (key, value) in parameters {
self.data.append(asciiSemicolon)
try self.serializeAKey(key)
if case .bool(true) = value {
// Don't serialize boolean true
continue
}
self.data.append(asciiEqual)
try self.serializeABareItem(value)
}
}
private mutating func serializeAKey(_ key: String) throws {
// We touch each byte twice here, but that's ok: this is cache friendly and less branchy (the copy gets to be memcpy in some cases!)
try key.validateStructuredHeaderKey()
self.data.append(contentsOf: key.utf8)
}
private mutating func serializeABareItem(_ item: RFC9651BareItem) throws {
switch item {
case .integer(let int):
guard let wideInt = Int64(exactly: int), validIntegerRange.contains(wideInt) else {
throw StructuredHeaderError.invalidIntegerOrDecimal
}
self.data.append(contentsOf: String(int, radix: 10).utf8)
case .decimal(let decimal):
self.data.append(contentsOf: String(decimal).utf8)
case .string(let string):
let bytes = string.utf8
guard bytes.allSatisfy({ !(0x00...0x1F).contains($0) && $0 != 0x7F && $0 < 0x80 }) else {
throw StructuredHeaderError.invalidString
}
self.data.append(asciiDquote)
for byte in bytes {
if byte == asciiBackslash || byte == asciiDquote {
self.data.append(asciiBackslash)
}
self.data.append(byte)
}
self.data.append(asciiDquote)
case .token(let token):
guard token.structuredHeadersIsValidToken else {
throw StructuredHeaderError.invalidToken
}
self.data.append(contentsOf: token.utf8)
case .undecodedByteSequence(let bytes):
// We require the user to have gotten this right.
self.data.append(asciiColon)
self.data.append(contentsOf: bytes.utf8)
self.data.append(asciiColon)
case .bool(let bool):
self.data.append(asciiQuestionMark)
let character = bool ? asciiOne : asciiZero
self.data.append(character)
case .date(let date):
self.data.append(asciiAt)
// Then, serialize as integer.
guard let wideInt = Int64(exactly: date), validIntegerRange.contains(wideInt) else {
throw StructuredHeaderError.invalidDate
}
self.data.append(contentsOf: String(date, radix: 10).utf8)
case .displayString(let displayString):
let bytes = displayString.utf8
self.data.append(asciiPercent)
self.data.append(asciiDquote)
for byte in bytes {
if byte == asciiPercent
|| byte == asciiDquote
|| (0x00...0x1F).contains(byte)
|| (0x7F...).contains(byte)
{
self.data.append(asciiPercent)
let encodedByte = UInt8.encodeToHex(byte)
self.data.append(encodedByte.firstChar)
self.data.append(encodedByte.secondChar)
} else {
self.data.append(byte)
}
}
self.data.append(asciiDquote)
}
}
}
extension String {
func validateStructuredHeaderKey() throws {
let utf8View = self.utf8
if let firstByte = utf8View.first {
switch firstByte {
case asciiLowercases, asciiAsterisk:
// Good
()
default:
throw StructuredHeaderError.invalidKey
}
}
let validKey = utf8View.dropFirst().allSatisfy {
switch $0 {
case asciiLowercases, asciiDigits, asciiUnderscore,
asciiDash, asciiPeriod, asciiAsterisk:
return true
default:
return false
}
}
guard validKey else {
throw StructuredHeaderError.invalidKey
}
}
}
extension UInt8 {
/// Converts an integer in base 10 to hex of type `EncodedHex`.
fileprivate static func encodeToHex(_ int: Self) -> EncodedHex {
let firstChar = self.itoh(int >> 4)
let secondChar = self.itoh(int & 0x0F)
return EncodedHex([firstChar, secondChar])
}
/// Converts an integer to its hex character in UTF8.
private static func itoh(_ int: Self) -> Self {
(int > 9) ? (asciiLowerA + int - 10) : (asciiZero + int)
}
}
================================================
FILE: Sources/RawStructuredFieldValues/OrderedMap.swift
================================================
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2020 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
//
//===----------------------------------------------------------------------===//
/// `OrderedMap` is a data type that has associative-array properties, but that
/// maintains insertion order.
///
/// Our initial implementation takes advantage of the fact that the vast majority of
/// maps in structured headers are small (fewer than 20 elements), and so the
/// distinction between hashing and linear search is not really a concern. However, for
/// future implementation flexibility, we continue to require that keys be hashable.
///
/// Note that this preserves _original_ insertion order: if you overwrite a key's value, the
/// key does not move to "last". This is a specific requirement for Structured Headers and may
/// harm the generality of this implementation.
public struct OrderedMap<Key, Value> where Key: Hashable {
private var backing: [Entry]
public init() {
self.backing = []
}
/// Look up the value for a given key.
///
/// Warning! Unlike a regular dictionary, we do not promise this will be O(1)!
public subscript(key: Key) -> Value? {
get {
self.backing.first(where: { $0.key == key }).map { $0.value }
}
set {
if let existing = self.backing.firstIndex(where: { $0.key == key }) {
if let newValue = newValue {
self.backing[existing] = Entry(key: key, value: newValue)
} else {
self.backing.remove(at: existing)
}
} else if let newValue = newValue {
self.backing.append(Entry(key: key, value: newValue))
}
}
}
func mapValues<NewValue>(_ body: (Value) throws -> NewValue) rethrows -> OrderedMap<Key, NewValue> {
var returnValue = OrderedMap<Key, NewValue>()
returnValue.backing = try self.backing.map { try .init(key: $0.key, value: body($0.value)) }
return returnValue
}
}
// MARK: - Helper struct for storing elements
extension OrderedMap {
// This struct takes some explaining.
//
// We don't want to maintain too much code here. In particular, we'd like to have straightforward equatable and hashable
// implementations. However, tuples aren't equatable or hashable. So we need to actually store something that is: a nominal
// type. That's this!
//
// This existence of this struct is a pure implementation detail and not exposed to the user of the type.
fileprivate struct Entry {
var key: Key
var value: Value
}
}
extension OrderedMap: Sendable where Key: Sendable, Value: Sendable {}
extension OrderedMap.Entry: Sendable where Key: Sendable, Value: Sendable {}
// MARK: - Collection conformances
extension OrderedMap: RandomAccessCollection, MutableCollection {
public struct Index: Sendable {
fileprivate var baseIndex: Array<(Key, Value)>.Index
fileprivate init(_ baseIndex: Array<(Key, Value)>.Index) {
self.baseIndex = baseIndex
}
}
public var startIndex: Index {
Index(self.backing.startIndex)
}
public var endIndex: Index {
Index(self.backing.endIndex)
}
public var count: Int {
self.backing.count
}
public subscript(position: Index) -> (Key, Value) {
get {
let element = self.backing[position.baseIndex]
return (element.key, element.value)
}
set {
self.backing[position.baseIndex] = Entry(key: newValue.0, value: newValue.1)
}
}
public func index(_ i: Index, offsetBy distance: Int) -> Index {
Index(self.backing.index(i.baseIndex, offsetBy: distance))
}
public func index(after i: Index) -> Index {
self.index(i, offsetBy: 1)
}
public func index(before i: Index) -> Index {
self.index(i, offsetBy: -1)
}
}
extension OrderedMap.Index: Hashable {}
extension OrderedMap.Index: Comparable {
public static func < (lhs: Self, rhs: Self) -> Bool {
lhs.baseIndex < rhs.baseIndex
}
}
extension OrderedMap.Index: Strideable {
public func advanced(by n: Int) -> Self {
Self(self.baseIndex.advanced(by: n))
}
public func distance(to other: Self) -> Int {
self.baseIndex.distance(to: other.baseIndex)
}
}
// MARK: - Helper conformances
extension OrderedMap: ExpressibleByDictionaryLiteral {
public init(dictionaryLiteral elements: (Key, Value)...) {
self.backing = elements.map { Entry(key: $0.0, value: $0.1) }
}
}
extension OrderedMap: CustomDebugStringConvertible {
public var debugDescription: String {
let backingRepresentation = self.backing.map { "\($0.key): \($0.value)" }.joined(separator: ", ")
return "[\(backingRepresentation)]"
}
}
// MARK: - Conditional conformances
extension OrderedMap.Entry: Equatable where Value: Equatable {}
extension OrderedMap: Equatable where Value: Equatable {}
extension OrderedMap.Entry: Hashable where Value: Hashable {}
extension OrderedMap: Hashable where Value: Hashable {}
================================================
FILE: Sources/RawStructuredFieldValues/PseudoDecimal.swift
================================================
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2020 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
//
//===----------------------------------------------------------------------===//
#if canImport(Darwin)
import Darwin
#elseif canImport(Glibc)
import Glibc
#elseif canImport(Musl)
import Musl
#elseif canImport(Android)
import Android
#elseif canImport(WinSDK)
import WinSDK
#else
#error("Unsupported OS")
#endif
/// A pseudo-representation of a decimal number.
///
/// The goal of this type is to remove a dependency on Foundation. As Structured Headers require only a very simple
/// conception of a decimal, there is no need to bring in the entire Foundation dependency: it's sufficient to define
/// a trivial type to represent the _idea_ of a decimal number. We don't have any requirement to do math on this type:
/// we just need to be able to use the type to store the fixed point representation of the value.
///
/// For ease of conversion to _actual_ decimal types, we use a mantissa/exponent/sign representation much like other
/// types would. Note that this is a base 10 exponent, not a base 2 exponent: the exponent multiplies by 10, not 2. We also
/// encode the sign bit implicitly in the sign bit of the mantissa.
///
/// The range of this type is from -999,999,999,999.999 to 999,999,999,999.999. This means the maximum value of the significand
/// is 999,999,999,999,999. The exponent ranges from -3 to 0. Additionally, there may be no more than 12 decimal digits before
/// the decimal place, so while the maximum value of the significand is 999,999,999,999,999, that is only acceptable if the
/// exponent is -3.
public struct PseudoDecimal: Hashable, Sendable {
private var _mantissa: Int64
private var _exponent: Int8
public var mantissa: Int64 {
get {
self._mantissa
}
set {
Self.fatalValidate(mantissa: newValue, exponent: self._exponent)
self._mantissa = newValue
}
}
public var exponent: Int8 {
get {
self._exponent
}
set {
Self.fatalValidate(mantissa: self._mantissa, exponent: newValue)
self._exponent = newValue
}
}
public init(mantissa: Int, exponent: Int) {
self._mantissa = Int64(mantissa)
self._exponent = Int8(exponent)
Self.fatalValidate(mantissa: self._mantissa, exponent: self._exponent)
}
internal init<Bytes: RandomAccessCollection>(bytes: Bytes) where Bytes.Element == UInt8 {
let elements = bytes.split(separator: asciiPeriod, maxSplits: 1)
// Precondition is safe, this can only be called from the parser which has already validated the formatting.
precondition(elements.count == 2)
var nonSignBytes = bytes.count
var sign = Int64(1)
if bytes.first == asciiDash {
sign = -1
nonSignBytes &-= 1
}
var mantissa = Int64(0)
var periodOffset = 0
for (offset, element) in bytes.enumerated() {
if element == asciiPeriod {
periodOffset = offset
continue
}
let integerValue = element - asciiZero
assert(integerValue < 10 && integerValue >= 0)
mantissa *= 10
mantissa += Int64(integerValue)
}
self._mantissa = mantissa * sign
// Check where the period was in relation to the rest of the string. We can have anywhere between
// 1 and 3 digits after the period. Note that if the period was last, offset == nonSignBytes - 1, so
// the actual values here are 2 to 4.
switch nonSignBytes - periodOffset {
case 2:
self._exponent = -1
case 3:
self._exponent = -2
case 4:
self._exponent = -3
default:
preconditionFailure("Unexpected value of offset: have \(nonSignBytes) bytes and offset \(periodOffset)")
}
}
private static func validate(mantissa: Int64, exponent: Int8) throws {
// We compare against our upper and lower bounds. The maximum allowable magnitude of the mantissa is contingent
// on the exponent.
switch exponent {
case 0 where mantissa.magnitude <= 999_999_999_999,
-1 where mantissa.magnitude <= 9_999_999_999_999,
-2 where mantissa.magnitude <= 99_999_999_999_999,
-3 where mantissa.magnitude <= 999_999_999_999_999:
// All acceptable
()
default:
throw StructuredHeaderError.invalidIntegerOrDecimal
}
}
private static func fatalValidate(mantissa: Int64, exponent: Int8) {
do {
try Self.validate(mantissa: mantissa, exponent: exponent)
} catch {
preconditionFailure(
"Invalid value for structured header decimal: mantissa \(mantissa) exponent \(exponent)"
)
}
}
fileprivate mutating func canonicalise() {
// This cannonicalises the decimal into a standard format suitable for direct serialisation.
//
// Structured headers prefers decimals to be serialized with no leading or trailing zeros. Leading zeros
// are easily avoided, but trailing zeros are a function of the exponent. As we must have at least one
// digit after the decimal place, exponent zero is not suitable, so if that's our current exponent we
// will arrange to have exponent -1. If the exponent is -1, we'll leave it alone. For -3 and -2 exponents
// we'll try to cascade them down toward exponent -1 if we can divide the mantissa by 10 evenly, as that will
// eliminate trailing zeros after the decimal place.
switch self._exponent {
case 0:
self._mantissa *= 10
self._exponent = -1
case -3 where self._mantissa % 10 == 0:
self._mantissa /= 10
self._exponent &+= 1
fallthrough
case -2:
if self._mantissa % 10 == 0 {
self._mantissa /= 10
self._exponent &+= 1
}
default:
// These other cases don't require any work.
()
}
}
}
extension PseudoDecimal: ExpressibleByFloatLiteral {
public init(_ value: Double) throws {
// This is kinda dumb, but it's basically good enough: we multiply by 1000 and then round that type
// to an integer.
var multiplied = value * 1000
multiplied.round(.toNearestOrEven)
let mantissa = Int64(exactly: multiplied)!
self._mantissa = mantissa
self._exponent = -3
try Self.validate(mantissa: self._mantissa, exponent: self._exponent)
}
public init(floatLiteral value: Double) {
do {
try self.init(value)
} catch {
preconditionFailure("Invalid value for structured header decimal: \(value)")
}
}
}
extension String {
public init(_ decimal: PseudoDecimal) {
var local = decimal
local.canonicalise()
// First, serialize the mantissa. We do this with a magnitude because we add a sign later.
var string = String(local.mantissa.magnitude, radix: 10)
// Then, based on the exponent, we insert a period. Turns out the offset for
// where we want to insert this character is exactly the exponent.
// Before we do this, quick check: we may not even have that many digits! If we don't,
// we need to pad with leading zeros until we do.
let neededPadding = Int(local.exponent.magnitude) - string.count
if neededPadding > 0 {
string = String(repeating: "0", count: neededPadding) + string
}
let periodIndex = string.index(string.endIndex, offsetBy: Int(local.exponent))
let needLeadingZero = periodIndex == string.startIndex
string.insert(".", at: periodIndex)
// One check: did we insert the period at the front? If we did, add a leading zero. Note
// that the above insert invalidated the indices so we must ask for the new start index.
if needLeadingZero {
string.insert("0", at: string.startIndex)
}
if local.mantissa < 0 {
string.insert("-", at: string.startIndex)
}
self = string
}
}
// Swift Numerics would let us do this for all the floating point types.
extension Double {
public init(_ pseudoDecimal: PseudoDecimal) {
self = Double(pseudoDecimal.mantissa) * pow(10, Double(pseudoDecimal.exponent))
}
}
================================================
FILE: Sources/StructuredFieldValues/Decoder/BareInnerListDecoder.swift
================================================
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2020-2021 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
//
//===----------------------------------------------------------------------===//
import Foundation
import RawStructuredFieldValues
struct BareInnerListDecoder<BaseData: RandomAccessCollection> where BaseData.Element == UInt8 {
private var list: BareInnerList
private var currentOffset: Int
private var decoder: _StructuredFieldDecoder<BaseData>
init(_ list: BareInnerList, decoder: _StructuredFieldDecoder<BaseData>) {
self.list = list
self.currentOffset = 0
self.decoder = decoder
}
}
extension BareInnerListDecoder: UnkeyedDecodingContainer {
var codingPath: [CodingKey] {
self.decoder.codingPath
}
var count: Int? {
self.list.count
}
var isAtEnd: Bool {
self.currentOffset == self.list.count
}
var currentIndex: Int {
self.currentOffset
}
mutating func decodeNil() throws -> Bool {
// We never decode nil.
false
}
mutating func decode<T>(_ type: T.Type) throws -> T where T: Decodable {
// This is a request to decode a scalar. We decode the next entry and increment the index.
guard !self.isAtEnd else {
throw StructuredHeaderError.indexOutOfRange
}
let codingKey = _StructuredHeaderCodingKey(intValue: self.currentOffset)
defer {
self.currentOffset += 1
}
try self.decoder.push(codingKey)
defer {
self.decoder.pop()
}
switch type {
case is Data.Type:
let container = try self.decoder.singleValueContainer()
return try container.decode(Data.self) as! T
case is Decimal.Type:
let container = try self.decoder.singleValueContainer()
return try container.decode(Decimal.self) as! T
case is Date.Type:
let container = try self.decoder.singleValueContainer()
return try container.decode(Date.self) as! T
case is DisplayString.Type:
let container = try self.decoder.singleValueContainer()
return try container.decode(DisplayString.self) as! T
default:
return try type.init(from: self.decoder)
}
}
mutating func nestedContainer<NestedKey>(keyedBy type: NestedKey.Type) throws -> KeyedDecodingContainer<NestedKey>
where NestedKey: CodingKey {
// This is a request to decode a full item. We decode the next entry and increment the index.
guard !self.isAtEnd else {
throw StructuredHeaderError.indexOutOfRange
}
let codingKey = _StructuredHeaderCodingKey(intValue: self.currentOffset)
defer {
self.currentOffset += 1
}
try self.decoder.push(codingKey)
defer {
self.decoder.pop()
}
return try self.decoder.container(keyedBy: type)
}
mutating func nestedUnkeyedContainer() throws -> UnkeyedDecodingContainer {
guard !self.isAtEnd else {
throw StructuredHeaderError.indexOutOfRange
}
let codingKey = _StructuredHeaderCodingKey(intValue: self.currentOffset)
defer {
self.currentOffset += 1
}
try self.decoder.push(codingKey)
defer {
self.decoder.pop()
}
return try self.decoder.unkeyedContainer()
}
mutating func superDecoder() throws -> Decoder {
// No inheritance here folks
throw StructuredHeaderError.invalidTypeForItem
}
}
================================================
FILE: Sources/StructuredFieldValues/Decoder/BareItemDecoder.swift
================================================
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2020-2021 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
//
//===----------------------------------------------------------------------===//
import Foundation
import RawStructuredFieldValues
struct BareItemDecoder {
private var item: RFC9651BareItem
private var _codingPath: [_StructuredHeaderCodingKey]
init(_ item: RFC9651BareItem, codingPath: [_StructuredHeaderCodingKey]) {
self.item = item
self._codingPath = codingPath
}
}
extension BareItemDecoder: SingleValueDecodingContainer {
var codingPath: [CodingKey] {
self._codingPath as [CodingKey]
}
func decode(_ type: UInt8.Type) throws -> UInt8 {
try self._decodeFixedWidthInteger(type)
}
func decode(_ type: Int8.Type) throws -> Int8 {
try self._decodeFixedWidthInteger(type)
}
func decode(_ type: UInt16.Type) throws -> UInt16 {
try self._decodeFixedWidthInteger(type)
}
func decode(_ type: Int16.Type) throws -> Int16 {
try self._decodeFixedWidthInteger(type)
}
func decode(_ type: UInt32.Type) throws -> UInt32 {
try self._decodeFixedWidthInteger(type)
}
func decode(_ type: Int32.Type) throws -> Int32 {
try self._decodeFixedWidthInteger(type)
}
func decode(_ type: UInt64.Type) throws -> UInt64 {
try self._decodeFixedWidthInteger(type)
}
func decode(_ type: Int64.Type) throws -> Int64 {
try self._decodeFixedWidthInteger(type)
}
@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
func decode(_ type: UInt128.Type) throws -> UInt128 {
try self._decodeFixedWidthInteger(type)
}
@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
func decode(_ type: Int128.Type) throws -> Int128 {
try self._decodeFixedWidthInteger(type)
}
func decode(_ type: UInt.Type) throws -> UInt {
try self._decodeFixedWidthInteger(type)
}
func decode(_ type: Int.Type) throws -> Int {
try self._decodeFixedWidthInteger(type)
}
func decode(_ type: Float.Type) throws -> Float {
try self._decodeBinaryFloatingPoint(type)
}
func decode(_ type: Double.Type) throws -> Double {
try self._decodeBinaryFloatingPoint(type)
}
func decode(_: String.Type) throws -> String {
switch self.item {
case .string(let string):
return string
case .token(let token):
return token
default:
throw StructuredHeaderError.invalidTypeForItem
}
}
func decode(_: Bool.Type) throws -> Bool {
guard case .bool(let bool) = self.item else {
throw StructuredHeaderError.invalidTypeForItem
}
return bool
}
func decode(_: Data.Type) throws -> Data {
guard case .undecodedByteSequence(let data) = self.item else {
throw StructuredHeaderError.invalidTypeForItem
}
guard let decoded = Data(base64Encoded: data) else {
throw StructuredHeaderError.invalidByteSequence
}
return decoded
}
func decode(_: Decimal.Type) throws -> Decimal {
guard case .decimal(let pseudoDecimal) = self.item else {
throw StructuredHeaderError.invalidTypeForItem
}
return Decimal(
sign: pseudoDecimal.mantissa > 0 ? .plus : .minus,
exponent: Int(pseudoDecimal.exponent),
significand: Decimal(pseudoDecimal.mantissa.magnitude)
)
}
func decode(_: Date.Type) throws -> Date {
guard case .date(let date) = self.item else {
throw StructuredHeaderError.invalidTypeForItem
}
return Date(timeIntervalSince1970: Double(date))
}
func decode(_: DisplayString.Type) throws -> DisplayString {
guard case .displayString(let string) = self.item else {
throw StructuredHeaderError.invalidTypeForItem
}
return DisplayString(rawValue: string)
}
func decodeNil() -> Bool {
// Items are never nil.
false
}
func decode<T>(_ type: T.Type) throws -> T where T: Decodable {
switch type {
case is UInt8.Type:
return try self.decode(UInt8.self) as! T
case is Int8.Type:
return try self.decode(Int8.self) as! T
case is UInt16.Type:
return try self.decode(UInt16.self) as! T
case is Int16.Type:
return try self.decode(Int16.self) as! T
case is UInt32.Type:
return try self.decode(UInt32.self) as! T
case is Int32.Type:
return try self.decode(Int32.self) as! T
case is UInt64.Type:
return try self.decode(UInt64.self) as! T
case is Int64.Type:
return try self.decode(Int64.self) as! T
case is UInt.Type:
return try self.decode(UInt.self) as! T
case is Int.Type:
return try self.decode(Int.self) as! T
case is Float.Type:
return try self.decode(Float.self) as! T
case is Double.Type:
return try self.decode(Double.self) as! T
case is String.Type:
return try self.decode(String.self) as! T
case is Bool.Type:
return try self.decode(Bool.self) as! T
case is Data.Type:
return try self.decode(Data.self) as! T
case is Decimal.Type:
return try self.decode(Decimal.self) as! T
case is Date.Type:
return try self.decode(Date.self) as! T
case is DisplayString.Type:
return try self.decode(DisplayString.self) as! T
default:
throw StructuredHeaderError.invalidTypeForItem
}
}
private func _decodeBinaryFloatingPoint<T: BinaryFloatingPoint>(_: T.Type) throws -> T {
guard case .decimal(let decimal) = self.item else {
throw StructuredHeaderError.invalidTypeForItem
}
// Going via Double is a bit sad. Swift Numerics would help here.
return T(Double(decimal))
}
private func _decodeFixedWidthInteger<T: FixedWidthInteger>(_: T.Type) throws -> T {
guard case .integer(let int) = self.item else {
throw StructuredHeaderError.invalidTypeForItem
}
guard let result = T(exactly: int) else {
throw StructuredHeaderError.integerOutOfRange
}
return result
}
}
================================================
FILE: Sources/StructuredFieldValues/Decoder/DictionaryKeyedContainer.swift
================================================
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2020-2021 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
//
//===----------------------------------------------------------------------===//
import Foundation
import RawStructuredFieldValues
struct DictionaryKeyedContainer<Key: CodingKey, BaseData: RandomAccessCollection> where BaseData.Element == UInt8 {
private var dictionary: OrderedMap<String, ItemOrInnerList>
private var decoder: _StructuredFieldDecoder<BaseData>
init(_ dictionary: OrderedMap<String, ItemOrInnerList>, decoder: _StructuredFieldDecoder<BaseData>) {
self.dictionary = dictionary
self.decoder = decoder
}
}
extension DictionaryKeyedContainer: KeyedDecodingContainerProtocol {
var codingPath: [CodingKey] {
self.decoder.codingPath
}
var allKeys: [Key] {
self.dictionary.compactMap { Key(stringValue: $0.0) }
}
func contains(_ key: Key) -> Bool {
self.dictionary.contains(where: { $0.0 == key.stringValue })
}
func decodeNil(forKey key: Key) throws -> Bool {
// We will decode nil if the key is not present.
!self.contains(key)
}
func decode<T>(_ type: T.Type, forKey key: Key) throws -> T where T: Decodable {
try self.decoder.push(_StructuredHeaderCodingKey(key, keyDecodingStrategy: self.decoder.keyDecodingStrategy))
defer {
self.decoder.pop()
}
switch type {
case is Data.Type:
let container = try self.decoder.singleValueContainer()
return try container.decode(Data.self) as! T
case is Decimal.Type:
let container = try self.decoder.singleValueContainer()
return try container.decode(Decimal.self) as! T
case is Date.Type:
let container = try self.decoder.singleValueContainer()
return try container.decode(Date.self) as! T
case is DisplayString.Type:
let container = try self.decoder.singleValueContainer()
return try container.decode(DisplayString.self) as! T
default:
return try type.init(from: self.decoder)
}
}
func nestedContainer<NestedKey: CodingKey>(
keyedBy type: NestedKey.Type,
forKey key: Key
) throws -> KeyedDecodingContainer<NestedKey> {
try self.decoder.push(_StructuredHeaderCodingKey(key, keyDecodingStrategy: self.decoder.keyDecodingStrategy))
defer {
self.decoder.pop()
}
return try self.decoder.container(keyedBy: type)
}
func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer {
try self.decoder.push(_StructuredHeaderCodingKey(key, keyDecodingStrategy: self.decoder.keyDecodingStrategy))
defer {
self.decoder.pop()
}
return try self.decoder.unkeyedContainer()
}
func superDecoder() throws -> Decoder {
// Dictionary headers never support inherited types.
throw StructuredHeaderError.invalidTypeForItem
}
func superDecoder(forKey key: Key) throws -> Decoder {
// Dictionary headers never support inherited types.
throw StructuredHeaderError.invalidTypeForItem
}
}
================================================
FILE: Sources/StructuredFieldValues/Decoder/KeyedInnerListDecoder.swift
================================================
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2020-2021 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
//
//===----------------------------------------------------------------------===//
import Foundation
import RawStructuredFieldValues
private let keyedInnerListDecoderSupportedKeys = ["items", "parameters"]
/// Used when someone has requested a keyed decoder for a property of inner list type.
///
/// There are only two valid keys for this: "items" and "parameters".
struct KeyedInnerListDecoder<Key: CodingKey, BaseData: RandomAccessCollection> where BaseData.Element == UInt8 {
private var innerList: InnerList
private var decoder: _StructuredFieldDecoder<BaseData>
init(_ innerList: InnerList, decoder: _StructuredFieldDecoder<BaseData>) {
self.innerList = innerList
self.decoder = decoder
}
}
extension KeyedInnerListDecoder: KeyedDecodingContainerProtocol {
var codingPath: [CodingKey] {
self.decoder.codingPath
}
var allKeys: [Key] {
keyedInnerListDecoderSupportedKeys.compactMap { Key(stringValue: $0) }
}
func contains(_ key: Key) -> Bool {
keyedInnerListDecoderSupportedKeys.contains(key.stringValue)
}
func decodeNil(forKey key: Key) throws -> Bool {
// Keys are never nil for this type.
false
}
func decode<T>(_ type: T.Type, forKey key: Key) throws -> T where T: Decodable {
try self.decoder.push(_StructuredHeaderCodingKey(key, keyDecodingStrategy: self.decoder.keyDecodingStrategy))
defer {
self.decoder.pop()
}
switch type {
case is Data.Type:
let container = try self.decoder.singleValueContainer()
return try container.decode(Data.self) as! T
case is Decimal.Type:
let container = try self.decoder.singleValueContainer()
return try container.decode(Decimal.self) as! T
case is Date.Type:
let container = try self.decoder.singleValueContainer()
return try container.decode(Date.self) as! T
case is DisplayString.Type:
let container = try self.decoder.singleValueContainer()
return try container.decode(DisplayString.self) as! T
default:
return try type.init(from: self.decoder)
}
}
func nestedContainer<NestedKey: CodingKey>(
keyedBy type: NestedKey.Type,
forKey key: Key
) throws -> KeyedDecodingContainer<NestedKey> {
try self.decoder.push(_StructuredHeaderCodingKey(key, keyDecodingStrategy: self.decoder.keyDecodingStrategy))
defer {
self.decoder.pop()
}
return try self.decoder.container(keyedBy: type)
}
func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer {
try self.decoder.push(_StructuredHeaderCodingKey(key, keyDecodingStrategy: self.decoder.keyDecodingStrategy))
defer {
self.decoder.pop()
}
return try self.decoder.unkeyedContainer()
}
func superDecoder() throws -> Decoder {
// Items never support inherited types.
throw StructuredHeaderError.invalidTypeForItem
}
func superDecoder(forKey key: Key) throws -> Decoder {
// Items never support inherited types.
throw StructuredHeaderError.invalidTypeForItem
}
}
================================================
FILE: Sources/StructuredFieldValues/Decoder/KeyedItemDecoder.swift
================================================
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2020-2021 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
//
//===----------------------------------------------------------------------===//
import Foundation
import RawStructuredFieldValues
private let keyedItemDecoderSupportedKeys = ["item", "parameters"]
/// Used when someone has requested a keyed decoder for a property of item type.
///
/// There are only two valid keys for this: "item" and "parameters".
struct KeyedItemDecoder<Key: CodingKey, BaseData: RandomAccessCollection> where BaseData.Element == UInt8 {
private var item: Item
private var decoder: _StructuredFieldDecoder<BaseData>
init(_ item: Item, decoder: _StructuredFieldDecoder<BaseData>) {
self.item = item
self.decoder = decoder
}
}
extension KeyedItemDecoder: KeyedDecodingContainerProtocol {
var codingPath: [CodingKey] {
self.decoder.codingPath
}
var allKeys: [Key] {
keyedItemDecoderSupportedKeys.compactMap { Key(stringValue: $0) }
}
func contains(_ key: Key) -> Bool {
keyedItemDecoderSupportedKeys.contains(key.stringValue)
}
func decodeNil(forKey key: Key) throws -> Bool {
// Keys are never nil for this type.
false
}
func decode<T>(_ type: T.Type, forKey key: Key) throws -> T where T: Decodable {
try self.decoder.push(_StructuredHeaderCodingKey(key, keyDecodingStrategy: self.decoder.keyDecodingStrategy))
defer {
self.decoder.pop()
}
switch type {
case is Data.Type:
let container = try self.decoder.singleValueContainer()
return try container.decode(Data.self) as! T
case is Decimal.Type:
let container = try self.decoder.singleValueContainer()
return try container.decode(Decimal.self) as! T
case is Date.Type:
let container = try self.decoder.singleValueContainer()
return try container.decode(Date.self) as! T
case is DisplayString.Type:
let container = try self.decoder.singleValueContainer()
return try container.decode(DisplayString.self) as! T
default:
return try type.init(from: self.decoder)
}
}
func nestedContainer<NestedKey: CodingKey>(
keyedBy type: NestedKey.Type,
forKey key: Key
) throws -> KeyedDecodingContainer<NestedKey> {
try self.decoder.push(_StructuredHeaderCodingKey(key, keyDecodingStrategy: self.decoder.keyDecodingStrategy))
defer {
self.decoder.pop()
}
return try self.decoder.container(keyedBy: type)
}
func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer {
try self.decoder.push(_StructuredHeaderCodingKey(key, keyDecodingStrategy: self.decoder.keyDecodingStrategy))
defer {
self.decoder.pop()
}
return try self.decoder.unkeyedContainer()
}
func superDecoder() throws -> Decoder {
// Items never support inherited types.
throw StructuredHeaderError.invalidTypeForItem
}
func superDecoder(forKey key: Key) throws -> Decoder {
// Items never support inherited types.
throw StructuredHeaderError.invalidTypeForItem
}
}
================================================
FILE: Sources/StructuredFieldValues/Decoder/KeyedTopLevelListDecoder.swift
================================================
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2020-2021 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
//
//===----------------------------------------------------------------------===//
import Foundation
import RawStructuredFieldValues
private let keyedTopLevelListDecoderSupportedKeys = ["items"]
/// Used when someone has requested a keyed decoder for a property of list type.
///
/// There is only one valid key for this: "items".
struct KeyedTopLevelListDecoder<Key: CodingKey, BaseData: RandomAccessCollection> where BaseData.Element == UInt8 {
private var list: [ItemOrInnerList]
private var decoder: _StructuredFieldDecoder<BaseData>
init(_ list: [ItemOrInnerList], decoder: _StructuredFieldDecoder<BaseData>) {
self.list = list
self.decoder = decoder
}
}
extension KeyedTopLevelListDecoder: KeyedDecodingContainerProtocol {
var codingPath: [CodingKey] {
self.decoder.codingPath
}
var allKeys: [Key] {
keyedTopLevelListDecoderSupportedKeys.compactMap { Key(stringValue: $0) }
}
func contains(_ key: Key) -> Bool {
keyedTopLevelListDecoderSupportedKeys.contains(key.stringValue)
}
func decodeNil(forKey key: Key) throws -> Bool {
// Keys are never nil for this type.
false
}
func decode<T>(_ type: T.Type, forKey key: Key) throws -> T where T: Decodable {
try self.decoder.push(_StructuredHeaderCodingKey(key, keyDecodingStrategy: self.decoder.keyDecodingStrategy))
defer {
self.decoder.pop()
}
switch type {
case is Data.Type:
let container = try self.decoder.singleValueContainer()
return try container.decode(Data.self) as! T
case is Decimal.Type:
let container = try self.decoder.singleValueContainer()
return try container.decode(Decimal.self) as! T
case is Date.Type:
let container = try self.decoder.singleValueContainer()
return try container.decode(Date.self) as! T
case is DisplayString.Type:
let container = try self.decoder.singleValueContainer()
return try container.decode(DisplayString.self) as! T
default:
return try type.init(from: self.decoder)
}
}
func nestedContainer<NestedKey: CodingKey>(
keyedBy type: NestedKey.Type,
forKey key: Key
) throws -> KeyedDecodingContainer<NestedKey> {
try self.decoder.push(_StructuredHeaderCodingKey(key, keyDecodingStrategy: self.decoder.keyDecodingStrategy))
defer {
self.decoder.pop()
}
return try self.decoder.container(keyedBy: type)
}
func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer {
try self.decoder.push(_StructuredHeaderCodingKey(key, keyDecodingStrategy: self.decoder.keyDecodingStrategy))
defer {
self.decoder.pop()
}
return try self.decoder.unkeyedContainer()
}
func superDecoder() throws -> Decoder {
// Items never support inherited types.
throw StructuredHeaderError.invalidTypeForItem
}
func superDecoder(forKey key: Key) throws -> Decoder {
// Items never support inherited types.
throw StructuredHeaderError.invalidTypeForItem
}
}
================================================
FILE: Sources/StructuredFieldValues/Decoder/ParametersDecoder.swift
================================================
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2020-2021 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
//
//===----------------------------------------------------------------------===//
import Foundation
import RawStructuredFieldValues
struct ParametersDecoder<Key: CodingKey, BaseData: RandomAccessCollection> where BaseData.Element == UInt8 {
private var parameters: OrderedMap<String, RFC9651BareItem>
private var decoder: _StructuredFieldDecoder<BaseData>
init(_ parameters: OrderedMap<String, RFC9651BareItem>, decoder: _StructuredFieldDecoder<BaseData>) {
self.parameters = parameters
self.decoder = decoder
}
}
extension ParametersDecoder: KeyedDecodingContainerProtocol {
var codingPath: [CodingKey] {
self.decoder.codingPath
}
var allKeys: [Key] {
self.parameters.compactMap { Key(stringValue: $0.0) }
}
func contains(_ key: Key) -> Bool {
self.parameters.contains(where: { $0.0 == key.stringValue })
}
func decodeNil(forKey key: Key) throws -> Bool {
// We will decode nil if the key is not present.
!self.contains(key)
}
func decode<T>(_ type: T.Type, forKey key: Key) throws -> T where T: Decodable {
try self.decoder.push(_StructuredHeaderCodingKey(key, keyDecodingStrategy: self.decoder.keyDecodingStrategy))
defer {
self.decoder.pop()
}
switch type {
case is Data.Type:
let container = try self.decoder.singleValueContainer()
return try container.decode(Data.self) as! T
case is Decimal.Type:
let container = try self.decoder.singleValueContainer()
return try container.decode(Decimal.self) as! T
case is Date.Type:
let container = try self.decoder.singleValueContainer()
return try container.decode(Date.self) as! T
case is DisplayString.Type:
let container = try self.decoder.singleValueContainer()
return try container.decode(DisplayString.self) as! T
default:
return try type.init(from: self.decoder)
}
}
func nestedContainer<NestedKey: CodingKey>(
keyedBy type: NestedKey.Type,
forKey key: Key
) throws -> KeyedDecodingContainer<NestedKey> {
try self.decoder.push(_StructuredHeaderCodingKey(key, keyDecodingStrategy: self.decoder.keyDecodingStrategy))
defer {
self.decoder.pop()
}
return try self.decoder.container(keyedBy: type)
}
func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer {
try self.decoder.push(_StructuredHeaderCodingKey(key, keyDecodingStrategy: self.decoder.keyDecodingStrategy))
defer {
self.decoder.pop()
}
return try self.decoder.unkeyedContainer()
}
func superDecoder() throws -> Decoder {
// Parameters never support inherited types.
throw StructuredHeaderError.invalidTypeForItem
}
func superDecoder(forKey key: Key) throws -> Decoder {
// Parameters never support inherited types.
throw StructuredHeaderError.invalidTypeForItem
}
}
================================================
FILE: Sources/StructuredFieldValues/Decoder/StructuredFieldValueDecoder.swift
================================================
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2020-2021 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
//
//===----------------------------------------------------------------------===//
import Foundation
import RawStructuredFieldValues
/// A `StructuredFieldValueDecoder` allows decoding `Decodable` objects from a HTTP
/// structured header field.
public struct StructuredFieldValueDecoder: Sendable {
/// A strategy that should be used to map coding keys to wire format keys.
public var keyDecodingStrategy: KeyDecodingStrategy?
public init() {}
}
extension StructuredFieldValueDecoder {
/// A strategy that should be used to map coding keys to wire format keys.
public struct KeyDecodingStrategy: Hashable, Sendable {
fileprivate enum Base: Hashable {
case lowercase
}
fileprivate var base: Base
/// Lowercase all coding keys before searching for them in keyed containers such as
/// dictionaries or parameters.
public static let lowercase = KeyDecodingStrategy(base: .lowercase)
}
}
extension StructuredFieldValueDecoder {
/// Attempt to decode an object from a structured header field.
///
/// - parameters:
/// - type: The type of the object to decode.
/// - data: The bytes of the structured header field.
/// - throws: If the header field could not be parsed, or could not be decoded.
/// - returns: An object of type `StructuredField`.
public func decode<StructuredField: StructuredFieldValue, BaseData: RandomAccessCollection>(
_ type: StructuredField.Type = StructuredField.self,
from data: BaseData
) throws -> StructuredField where BaseData.Element == UInt8 {
switch StructuredField.structuredFieldType {
case .item:
return try self.decodeItemField(from: data)
case .list:
return try self.decodeListField(from: data)
case .dictionary:
return try self.decodeDictionaryField(from: data)
}
}
/// Attempt to decode an object from a structured header dictionary field.
///
/// - parameters:
/// - type: The type of the object to decode.
/// - data: The bytes of the structured header field.
/// - throws: If the header field could not be parsed, or could not be decoded.
/// - returns: An object of type `StructuredField`.
private func decodeDictionaryField<StructuredField: Decodable, BaseData: RandomAccessCollection>(
_ type: StructuredField.Type = StructuredField.self,
from data: BaseData
) throws -> StructuredField where BaseData.Element == UInt8 {
let parser = StructuredFieldValueParser(data)
let decoder = _StructuredFieldDecoder(parser, keyDecodingStrategy: self.keyDecodingStrategy)
try decoder.parseDictionaryField()
return try type.init(from: decoder)
}
/// Attempt to decode an object from a structured header list field.
///
/// - parameters:
/// - type: The type of the object to decode.
/// - data: The bytes of the structured header field.
/// - throws: If the header field could not be parsed, or could not be decoded.
/// - returns: An object of type `StructuredField`.
private func decodeListField<StructuredField: Decodable, BaseData: RandomAccessCollection>(
_ type: StructuredField.Type = StructuredField.self,
from data: BaseData
) throws -> StructuredField where BaseData.Element == UInt8 {
let parser = StructuredFieldValueParser(data)
let decoder = _StructuredFieldDecoder(parser, keyDecodingStrategy: self.keyDecodingStrategy)
try decoder.parseListField()
return try type.init(from: decoder)
}
/// Attempt to decode an object from a structured header item field.
///
/// - parameters:
/// - type: The type of the object to decode.
/// - data: The bytes of the structured header field.
/// - throws: If the header field could not be parsed, or could not be decoded.
/// - returns: An object of type `StructuredField`.
private func decodeItemField<StructuredField: Decodable, BaseData: RandomAccessCollection>(
_ type: StructuredField.Type = StructuredField.self,
from data: BaseData
) throws -> StructuredField where BaseData.Element == UInt8 {
let parser = StructuredFieldValueParser(data)
let decoder = _StructuredFieldDecoder(parser, keyDecodingStrategy: self.keyDecodingStrategy)
try decoder.parseItemField()
// An escape hatch here for top-level data: if we don't do this, it'll ask for
// an unkeyed container and get very confused.
switch type {
case is Data.Type:
let container = try decoder.singleValueContainer()
return try container.decode(Data.self) as! StructuredField
case is Decimal.Type:
let container = try decoder.singleValueContainer()
return try container.decode(Decimal.self) as! StructuredField
case is Date.Type:
let container = try decoder.singleValueContainer()
return try container.decode(Date.self) as! StructuredField
case is DisplayString.Type:
let container = try decoder.singleValueContainer()
return try container.decode(DisplayString.self) as! StructuredField
default:
return try type.init(from: decoder)
}
}
}
class _StructuredFieldDecoder<BaseData: RandomAccessCollection> where BaseData.Element == UInt8 {
private var parser: StructuredFieldValueParser<BaseData>
// For now we use a stack here because the CoW operations on Array would suck. Ideally I'd just have us decode
// our way down with values, but doing that is a CoWy nightmare from which we cannot escape.
private var _codingStack: [CodingStackEntry]
var keyDecodingStrategy: StructuredFieldValueDecoder.KeyDecodingStrategy?
init(
_ parser: StructuredFieldValueParser<BaseData>,
keyDecodingStrategy: StructuredFieldValueDecoder.KeyDecodingStrategy?
) {
self.parser = parser
self._codingStack = []
self.keyDecodingStrategy = keyDecodingStrategy
}
}
extension _StructuredFieldDecoder: Decoder {
var codingPath: [CodingKey] {
self._codingStack.map { $0.key as CodingKey }
}
var userInfo: [CodingUserInfoKey: Any] {
[:]
}
func push(_ codingKey: _StructuredHeaderCodingKey) throws {
// This force-unwrap is safe: we cannot create containers without having first
// produced the base element, which will always be present.
let nextElement = try self.currentElement!.innerElement(for: codingKey)
self._codingStack.append(CodingStackEntry(key: codingKey, element: nextElement))
}
func pop() {
self._codingStack.removeLast()
}
func container<Key>(keyedBy type: Key.Type) throws -> KeyedDecodingContainer<Key> where Key: CodingKey {
switch self.currentElement! {
case .dictionary(let dictionary):
return KeyedDecodingContainer(DictionaryKeyedContainer(dictionary, decoder: self))
case .item(let item):
return KeyedDecodingContainer(KeyedItemDecoder(item, decoder: self))
case .list(let list):
return KeyedDecodingContainer(KeyedTopLevelListDecoder(list, decoder: self))
case .innerList(let innerList):
return KeyedDecodingContainer(KeyedInnerListDecoder(innerList, decoder: self))
case .parameters(let parameters):
return KeyedDecodingContainer(ParametersDecoder(parameters, decoder: self))
case .bareItem, .bareInnerList:
// No keyed container for these types.
throw StructuredHeaderError.invalidTypeForItem
}
}
func unkeyedContainer() throws -> UnkeyedDecodingContainer {
// We have unkeyed containers for lists, inner lists, and bare inner lists.
switch self.currentElement! {
case .list(let items):
return TopLevelListDecoder(items, decoder: self)
case .innerList(let innerList):
return BareInnerListDecoder(innerList.bareInnerList, decoder: self)
case .bareInnerList(let bareInnerList):
return BareInnerListDecoder(bareInnerList, decoder: self)
case .dictionary, .item, .bareItem, .parameters:
// No unkeyed container for these types.
throw StructuredHeaderError.invalidTypeForItem
}
}
func singleValueContainer() throws -> SingleValueDecodingContainer {
// We have single value containers for items and bareItems.
switch self.currentElement! {
case .item(let item):
return BareItemDecoder(item.rfc9651BareItem, codingPath: self._codingStack.map { $0.key })
case .bareItem(let bareItem):
return BareItemDecoder(bareItem, codingPath: self._codingStack.map { $0.key })
case .dictionary, .list, .innerList, .bareInnerList, .parameters:
throw StructuredHeaderError.invalidTypeForItem
}
}
func parseDictionaryField() throws {
precondition(self._codingStack.isEmpty)
let parsed = try self.parser.parseDictionaryFieldValue()
// We unconditionally add to the base of the coding stack here. This element is never popped off.
self._codingStack.append(CodingStackEntry(key: .init(stringValue: ""), element: .dictionary(parsed)))
}
func parseListField() throws {
precondition(self._codingStack.isEmpty)
let parsed = try self.parser.parseListFieldValue()
// We unconditionally add to the base of the coding stack here. This element is never popped off.
self._codingStack.append(CodingStackEntry(key: .init(stringValue: ""), element: .list(parsed)))
}
func parseItemField() throws {
precondition(self._codingStack.isEmpty)
let parsed = try self.parser.parseItemFieldValue()
// We unconditionally add to the base of the coding stack here. This element is never popped off.
self._codingStack.append(CodingStackEntry(key: .init(stringValue: ""), element: .item(parsed)))
}
}
extension _StructuredFieldDecoder {
/// The basic elements that make up a Structured Header
fileprivate enum Element {
case dictionary(OrderedMap<String, ItemOrInnerList>)
case list([ItemOrInnerList])
case item(Item)
case innerList(InnerList)
case bareItem(RFC9651BareItem)
case bareInnerList(BareInnerList)
case parameters(OrderedMap<String, RFC9651BareItem>)
func innerElement(for key: _StructuredHeaderCodingKey) throws -> Element {
switch self {
case .dictionary(let dictionary):
guard let element = dictionary.first(where: { $0.0 == key.stringValue }) else {
throw StructuredHeaderError.invalidTypeForItem
}
switch element.1 {
case .item(let item):
return .item(item)
case .innerList(let innerList):
return .innerList(innerList)
}
case .list(let list):
if let offset = key.intValue {
guard offset < list.count else {
throw StructuredHeaderError.invalidTypeForItem
}
let index = list.index(list.startIndex, offsetBy: offset)
switch list[index] {
case .item(let item):
return .item(item)
case .innerList(let innerList):
return .innerList(innerList)
}
} else if key.stringValue == "items" {
// Oh, the outer layer is keyed. That's fine, just put ourselves
// back on the stack.
return .list(list)
} else {
throw StructuredHeaderError.invalidTypeForItem
}
case .item(let item):
// Two keys, "item" and "parameters".
switch key.stringValue {
case "item":
return .bareItem(item.rfc9651BareItem)
case "parameters":
return .parameters(item.rfc9651Parameters)
default:
throw StructuredHeaderError.invalidTypeForItem
}
case .innerList(let innerList):
// Quick check: is this an integer key? If it is, treat this like a bare inner list. Otherwise
// there are two string keys: "items" and "parameters"
if key.intValue != nil {
return try Element.bareInnerList(innerList.bareInnerList).innerElement(for: key)
}
switch key.stringValue {
case "items":
return .bareInnerList(innerList.bareInnerList)
case "parameters":
return .parameters(innerList.rfc9651Parameters)
default:
throw StructuredHeaderError.invalidTypeForItem
}
case .bareItem:
// Bare items may never be parsed through.
throw StructuredHeaderError.invalidTypeForItem
case .bareInnerList(let innerList):
guard let offset = key.intValue, offset < innerList.count else {
throw StructuredHeaderError.invalidTypeForItem
}
let index = innerList.index(innerList.startIndex, offsetBy: offset)
return .item(innerList[index])
case .parameters(let params):
guard let element = params.first(where: { $0.0 == key.stringValue }) else {
throw StructuredHeaderError.invalidTypeForItem
}
return .bareItem(element.1)
}
}
}
}
extension _StructuredFieldDecoder {
/// An entry in the coding stack for _StructuredFieldDecoder.
///
/// This is used to keep track of where we are in the decode.
private struct CodingStackEntry {
var key: _StructuredHeaderCodingKey
var element: Element
}
/// The element at the current head of the coding stack.
private var currentElement: Element? {
self._codingStack.last.map { $0.element }
}
}
================================================
FILE: Sources/StructuredFieldValues/Decoder/StructuredHeaderCodingKey.swift
================================================
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2020-2021 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
//
//===----------------------------------------------------------------------===//
import RawStructuredFieldValues
struct _StructuredHeaderCodingKey: CodingKey {
var stringValue: String
var intValue: Int?
init(stringValue: String) {
self.stringValue = stringValue
self.intValue = nil
}
init(intValue: Int) {
self.stringValue = "\(intValue)"
self.intValue = intValue
}
init<Key: CodingKey>(_ key: Key, keyDecodingStrategy: StructuredFieldValueDecoder.KeyDecodingStrategy?) {
switch keyDecodingStrategy {
case .none:
self.stringValue = key.stringValue
case .some(.lowercase):
self.stringValue = key.stringValue.lowercased()
default:
preconditionFailure("Invalid key decoding strategy")
}
self.intValue = key.intValue
}
}
================================================
FILE: Sources/StructuredFieldValues/Decoder/TopLevelListDecoder.swift
================================================
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2020-2021 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
//
//===----------------------------------------------------------------------===//
import Foundation
import RawStructuredFieldValues
struct TopLevelListDecoder<BaseData: RandomAccessCollection> where BaseData.Element == UInt8 {
private var list: [ItemOrInnerList]
private var currentOffset: Int
private var decoder: _StructuredFieldDecoder<BaseData>
init(_ list: [ItemOrInnerList], decoder: _StructuredFieldDecoder<BaseData>) {
self.list = list
self.currentOffset = 0
self.decoder = decoder
}
}
extension TopLevelListDecoder: UnkeyedDecodingContainer {
var codingPath: [CodingKey] {
self.decoder.codingPath
}
var count: Int? {
self.list.count
}
var isAtEnd: Bool {
self.currentOffset == self.list.count
}
var currentIndex: Int {
self.currentOffset
}
mutating func decodeNil() throws -> Bool {
// We never decode nil.
false
}
mutating func decode<T>(_ type: T.Type) throws -> T where T: Decodable {
// This is a request to decode a scalar. We decode the next entry and increment the index.
guard !self.isAtEnd else {
throw StructuredHeaderError.indexOutOfRange
}
let codingKey = _StructuredHeaderCodingKey(intValue: self.currentOffset)
defer {
self.currentOffset += 1
}
try self.decoder.push(codingKey)
defer {
self.decoder.pop()
}
switch type {
case is Data.Type:
let container = try self.decoder.singleValueContainer()
return try container.decode(Data.self) as! T
case is Decimal.Type:
let container = try self.decoder.singleValueContainer()
return try container.decode(Decimal.self) as! T
case is Date.Type:
let container = try self.decoder.singleValueContainer()
return try container.decode(Date.self) as! T
case is DisplayString.Type:
let container = try self.decoder.singleValueContainer()
return try container.decode(DisplayString.self) as! T
default:
return try type.init(from: self.decoder)
}
}
mutating func nestedContainer<NestedKey>(keyedBy type: NestedKey.Type) throws -> KeyedDecodingContainer<NestedKey>
where NestedKey: CodingKey {
// This is a request to decode a full item. We decode the next entry and increment the index.
guard !self.isAtEnd else {
throw StructuredHeaderError.indexOutOfRange
}
let codingKey = _StructuredHeaderCodingKey(intValue: self.currentOffset)
defer {
self.currentOffset += 1
}
try self.decoder.push(codingKey)
defer {
self.decoder.pop()
}
return try self.decoder.container(keyedBy: type)
}
mutating func nestedUnkeyedContainer() throws -> UnkeyedDecodingContainer {
guard !self.isAtEnd else {
throw StructuredHeaderError.indexOutOfRange
}
let codingKey = _StructuredHeaderCodingKey(intValue: self.currentOffset)
defer {
self.currentOffset += 1
}
try self.decoder.push(codingKey)
defer {
self.decoder.pop()
}
return try self.decoder.unkeyedContainer()
}
mutating func superDecoder() throws -> Decoder {
// No inheritance here folks
throw StructuredHeaderError.invalidTypeForItem
}
}
================================================
FILE: Sources/StructuredFieldValues/DisplayString.swift
================================================
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2020-2024 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
//
//===----------------------------------------------------------------------===//
/// A type that represents the Display String Structured Type.
public struct DisplayString: RawRepresentable, Codable, Equatable, Hashable, Sendable {
public typealias RawValue = String
public var rawValue: String
public init(rawValue: String) {
self.rawValue = rawValue
}
}
================================================
FILE: Sources/StructuredFieldValues/Docs.docc/index.md
================================================
# ``StructuredFieldValues``
A Swift implementation of the HTTP Structured Header Field Value specification.
Provides parsing and serialization facilities for structured header field values, as well as implementations of `Encoder` and `Decoder` to allow using `Codable` data types as the payloads of HTTP structured header fields.
## Overview
### About Structured Header Field Values
HTTP Structured Header Field Values are a HTTP extension recorded in [RFC 9651](https://www.ietf.org/rfc/rfc9651.html). They provide a set of data types and algorithms for handling HTTP header field values in a consistent way, allowing a single parser and serializer to handle a wide range of header field values.
### Swift HTTP Structured Header Field Values
This package provides a parser and serializer that implement RFC 9651. They are entirely complete, able to handle all valid HTTP structured header field values. This package also provides `Encoder` and `Decoder` objects for working with Codable in Swift. This allows rapid prototyping and experimentation with HTTP structured header field values, as well as interaction with the wider Swift Codable community.
This package provides two top-level modules: `StructuredFieldValues` and `RawStructuredFieldValues`.
This module, `StructuredFieldValues`, uses the related module `RawStructuredFieldValues` to implement `Encoder` and `Decoder`. This interface is friendly and easy to work with.
Users who have performance problems with this solution or have specific representational needs should investigate `RawStructuredFieldValues`.
### Working with Structured Header Field Values
`StructuredFieldValues` has a simple, easy-to-use high-level API for working with structured header field values. To begin with, let's consider the [HTTP Client Hints specification](https://www.rfc-editor.org/rfc/rfc8942.html). This defines the following new header field:
```
The Accept-CH response header field indicates server support for the hints indicated in its value. Servers wishing to receive user agent information through Client Hints SHOULD add Accept-CH response header to their responses as early as possible.
Accept-CH is a Structured Header. Its value MUST be an sf-list whose members are tokens. Its ABNF is:
Accept-CH = sf-list
For example:
Accept-CH: Sec-CH-Example, Sec-CH-Example-2
```
`swift-http-structured-headers` can parse and serialize this field very simply:
```swift
let field = Array("Sec-CH-Example, Sec-CH-Example-2".utf8)
struct AcceptCH: StructuredFieldValue {
static let structuredFieldType: StructuredFieldType = .list
var items: [String]
}
// Decoding
let decoder = StructuredFieldValueDecoder()
let parsed = try decoder.decode(AcceptCH.self, from: field)
// Encoding
let encoder = StructuredFieldValueEncoder()
let serialized = try encoder.encode(AcceptCH(items: ["Sec-CH-Example", "Sec-CH-Example-2"]))
```
However, structured header field values can be substantially more complex. Structured header fields can make use of 4 containers and 8 base item types. The containers are:
1. Dictionaries. These are top-level elements and associate token keys with values. The values may be items, or may be inner lists, and each value may also have parameters associated with them. `StructuredFieldValues` can model dictionaries as either Swift objects (where the property names are dictionary keys).
2. Lists. These are top-level elements, providing a sequence of items or inner lists. Each item or inner list may have parameters associated with them. `StructuredFieldValues` models these as Swift objects with one key, `items`, that must be a collection of entries.
3. Inner Lists. These are lists that may be sub-entries of a dictionary or a list. The list entries are items, which may have parameters associated with them: additionally, an inner list may have parameters associated with itself as well. `StructuredFieldValues` models these as either Swift `Array`s _or_, if it's important to extract parameters, as a two-field Swift `struct` where one field is called `items` and contains an `Array`, and other field is called `parameters` and contains a dictionary.
4. Parameters. Parameters associate token keys with items without parameters. These are used to store metadata about objects within a field. `StructuredFieldValues` models these as either Swift objects (where the property names are the parameter keys) or as Swift dictionaries.
The base types are:
1. Booleans. `StructuredFieldValues` models these as Swift's `Bool` type.
2. Integers. `StructuredFieldValues` models these as any fixed-width integer type.
3. Decimals. `StructuredFieldValues` models these as any floating-point type, or as Foundation's `Decimal`.
4. Tokens. `StructuredFieldValues` models these as Swift's `String` type, where the range of characters is restricted.
5. Strings. `StructuredFieldValues` models these as Swift's `String` type.
6. Binary data. `StructuredFieldValues` models this as Foundation's `Data` type.
7. Dates. `StructuredFieldValues` models these as Foundation's `Date` type.
8. Display strings. `StructuredFieldValues` models these as the `DisplayString` type which it provides.
For any Structured Header Field Value Item, the item may either be represented directly by the appropriate type, or by a Swift struct with two properties: `item` and `parameters`. This latter mode is how parameters on a given item may be captured.
The top-level Structured Header Field Value must identify what kind of header field it corresponds to by using ``StructuredFieldType``: `.item`, `.list`, or `.dictionary`. This is inherent in the type of the field and will be specified in the relevant field specification.
## Topics
### Declaring Codable Types
- ``StructuredFieldValue``
- ``StructuredFieldType``
### Helper Types
- ``DisplayString``
### Encoding and Decoding
- ``StructuredFieldValueEncoder``
- ``StructuredFieldValueDecoder``
================================================
FILE: Sources/StructuredFieldValues/Encoder/StructuredFieldKeyedEncodingContainer.swift
================================================
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2020-2021 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
//
//===----------------------------------------------------------------------===//
import RawStructuredFieldValues
/// This object translates between the keys used by the encoder and the keys used by the encoding object.
///
/// All methods here call back to similar methods on the encoder.
struct StructuredFieldKeyedEncodingContainer<Key: CodingKey> {
private var encoder: _StructuredFieldEncoder
init(encoder: _StructuredFieldEncoder) {
self.encoder = encoder
}
}
extension StructuredFieldKeyedEncodingContainer: KeyedEncodingContainerProtocol {
var codingPath: [CodingKey] {
self.encoder.codingPath
}
mutating func encodeNil(forKey key: Key) throws {
// Nil has no representation in structured headers
}
mutating func encode(_ value: Bool, forKey key: Key) throws {
try self.encoder.encode(value, forKey: key.stringValue)
}
mutating func encode(_ value: String, forKey key: Key) throws {
try self.encoder.encode(value, forKey: key.stringValue)
}
mutating func encode(_ value: Double, forKey key: Key) throws {
try self.encoder.encode(value, forKey: key.stringValue)
}
mutating func encode(_ value: Float, forKey key: Key) throws {
try self.encoder.encode(value, forKey: key.stringValue)
}
mutating func encode(_ value: Int, forKey key: Key) throws {
try self.encoder.encode(value, forKey: key.stringValue)
}
mutating func encode(_ value: Int8, forKey key: Key) throws {
try self.encoder.encode(value, forKey: key.stringValue)
}
mutating func encode(_ value: Int16, forKey key: Key) throws {
try self.encoder.encode(value, forKey: key.stringValue)
}
mutating func encode(_ value: Int32, forKey key: Key) throws {
try self.encoder.encode(value, forKey: key.stringValue)
}
mutating func encode(_ value: Int64, forKey key: Key) throws {
try self.encoder.encode(value, forKey: key.stringValue)
}
mutating func encode(_ value: UInt, forKey key: Key) throws {
try self.encoder.encode(value, forKey: key.stringValue)
}
mutating func encode(_ value: UInt8, forKey key: Key) throws {
try self.encoder.encode(value, forKey: key.stringValue)
}
mutating func encode(_ value: UInt16, forKey key: Key) throws {
try self.encoder.encode(value, forKey: key.stringValue)
}
mutating func encode(_ value: UInt32, forKey key: Key) throws {
try self.encoder.encode(value, forKey: key.stringValue)
}
mutating func encode(_ value: UInt64, forKey key: Key) throws {
try self.encoder.encode(value, forKey: key.stringValue)
}
mutating func encode<T>(_ value: T, forKey key: Key) throws where T: Encodable {
try self.encoder.encode(value, forKey: key.stringValue)
}
mutating func nestedContainer<NestedKey>(
keyedBy keyType: NestedKey.Type,
forKey key: Key
) -> KeyedEncodingContainer<NestedKey> where NestedKey: CodingKey {
self.encoder.container(keyedBy: keyType)
}
mutating func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer {
self.encoder.unkeyedContainer()
}
mutating func superEncoder() -> Encoder {
self.encoder
}
mutating func superEncoder(forKey key: Key) -> Encoder {
self.encoder
}
}
================================================
FILE: Sources/StructuredFieldValues/Encoder/StructuredFieldUnkeyedEncodingContainer.swift
================================================
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2020-2021 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
//
//===----------------------------------------------------------------------===//
import RawStructuredFieldValues
/// This object translates between the unkeyed coding API and methods on the encoder.
///
/// All methods here call back to similar methods on the encoder.
struct StructuredFieldUnkeyedEncodingContainer {
private var encoder: _StructuredFieldEncoder
init(encoder: _StructuredFieldEncoder) {
self.encoder = encoder
}
}
extension StructuredFieldUnkeyedEncodingContainer: UnkeyedEncodingContainer {
var codingPath: [CodingKey] {
self.encoder.codingPath
}
var count: Int {
self.encoder.count
}
func encodeNil() throws {
try self.encoder.appendNil()
}
func encode(_ value: Bool) throws {
try self.encoder.append(value)
}
func encode(_ value: String) throws {
try self.encoder.append(value)
}
func encode(_ value: Double) throws {
try self.encoder.append(value)
}
func encode(_ value: Float) throws {
try self.encoder.append(value)
}
func encode(_ value: Int) throws {
try self.encoder.append(value)
}
func encode(_ value: Int8) throws {
try self.encoder.append(value)
}
func encode(_ value: Int16) throws {
try self.encoder.append(value)
}
func encode(_ value: Int32) throws {
try self.encoder.append(value)
}
func encode(_ value: Int64) throws {
try self.encoder.append(value)
}
func encode(_ value: UInt) throws {
try self.encoder.append(value)
}
func encode(_ value: UInt8) throws {
try self.encoder.append(value)
}
func encode(_ value: UInt16) throws {
try self.encoder.append(value)
}
func encode(_ value: UInt32) throws {
try self.encoder.append(value)
}
func encode(_ value: UInt64) throws {
try self.encoder.append(value)
}
func encode<T>(_ value: T) throws where T: Encodable {
try self.encoder.append(value)
}
mutating func nestedContainer<NestedKey>(keyedBy keyType: NestedKey.Type) -> KeyedEncodingContainer<NestedKey>
where NestedKey: CodingKey {
self.encoder.container(keyedBy: keyType)
}
mutating func nestedUnkeyedContainer() -> UnkeyedEncodingContainer {
self.encoder.unkeyedContainer()
}
mutating func superEncoder() -> Encoder {
self.encoder
}
}
================================================
FILE: Sources/StructuredFieldValues/Encoder/StructuredFieldValueEncoder.swift
================================================
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2020-2021 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
//
//===----------------------------------------------------------------------===//
import Foundation
import RawStructuredFieldValues
/// A `StructuredFieldValueEncoder` allows encoding `Encodable` objects to the format of a HTTP
/// structured header field.
public struct StructuredFieldValueEncoder: Sendable {
public var keyEncodingStrategy: KeyEncodingStrategy?
public init() {}
}
extension StructuredFieldValueEncoder {
/// A strategy that should be used to map coding keys to wire format keys.
public struct KeyEncodingStrategy: Hashable, Sendable {
fileprivate enum Base: Hashable {
case lowercase
}
fileprivate var base: Base
/// Lowercase all coding keys before encoding them in keyed containers such as
/// dictionaries or parameters.
public static let lowercase = KeyEncodingStrategy(base: .lowercase)
}
}
extension StructuredFieldValueEncoder {
/// Attempt to encode an object into a structured header field.
///
/// - parameters:
/// - data: The object to encode.
/// - throws: If the header field could not be encoded, or could not be serialized.
/// - returns: The bytes representing the HTTP structured header field.
public func encode<StructuredField: StructuredFieldValue>(_ data: StructuredField) throws -> [UInt8] {
switch StructuredField.structuredFieldType {
case .item:
return try self.encodeItemField(data)
case .list:
return try self.encodeListField(data)
case .dictionary:
return try self.encodeDictionaryField(data)
}
}
/// Attempt to encode an object into a structured header dictionary field.
///
/// - parameters:
/// - data: The object to encode.
/// - throws: If the header field could not be encoded, or could not be serialized.
/// - returns: The bytes representing the HTTP structured header field.
private func encodeDictionaryField<StructuredField: Encodable>(_ data: StructuredField) throws -> [UInt8] {
let serializer = StructuredFieldValueSerializer()
let encoder = _StructuredFieldEncoder(serializer, keyEncodingStrategy: self.keyEncodingStrategy)
return try encoder.encodeDictionaryField(data)
}
/// Attempt to encode an object into a structured header list field.
///
/// - parameters:
/// - data: The object to encode.
/// - throws: If the header field could not be encoded, or could not be serialized.
/// - returns: The bytes representing the HTTP structured header field.
private func encodeListField<StructuredField: Encodable>(_ data: StructuredField) throws -> [UInt8] {
let serializer = StructuredFieldValueSerializer()
let encoder = _StructuredFieldEncoder(serializer, keyEncodingStrategy: self.keyEncodingStrategy)
return try encoder.encodeListField(data)
}
/// Attempt to encode an object into a structured header item field.
///
/// - parameters:
/// - data: The object to encode.
/// - throws: If the header field could not be encoded, or could not be serialized.
/// - returns: The bytes representing the HTTP structured header field.
private func encodeItemField<StructuredField: Encodable>(_ data: StructuredField) throws -> [UInt8] {
let serializer = StructuredFieldValueSerializer()
let encoder = _StructuredFieldEncoder(serializer, keyEncodingStrategy: self.keyEncodingStrategy)
return try encoder.encodeItemField(data)
}
}
class _StructuredFieldEncoder {
private var serializer: StructuredFieldValueSerializer
// For now we use a stack here because the CoW operations on Array would stuck. Ideally I'd just have us decode
// our way down with values, but doing that is a CoWy nightmare from which we cannot escape.
private var _codingPath: [CodingStackEntry]
private var currentStackEntry: CodingStackEntry
internal var keyEncodingStrategy: StructuredFieldValueEncoder.KeyEncodingStrategy?
init(
_ serializer: StructuredFieldValueSerializer,
keyEncodingStrategy: StructuredFieldValueEncoder.KeyEncodingStrategy?
) {
self.serializer = serializer
self._codingPath = []
self.keyEncodingStrategy = keyEncodingStrategy
// This default doesn't matter right now.
self.currentStackEntry = CodingStackEntry(key: .init(stringValue: ""), storage: .itemHeader)
}
fileprivate func encodeDictionaryField<StructuredField: Encodable>(_ data: StructuredField) throws -> [UInt8] {
self.push(key: .init(stringValue: ""), newStorage: .dictionaryHeader)
try data.encode(to: self)
switch self.currentStackEntry.storage {
case .dictionary(let map):
return try self.serializer.writeDictionaryFieldValue(map)
case .dictionaryHeader:
// No encoding happened.
return []
case .listHeader, .list, .itemHeader, .item, .bareInnerList, .innerList,
.parameters, .itemOrInnerList:
throw StructuredHeaderError.invalidTypeForItem
}
}
fileprivate func encodeListField<StructuredField: Encodable>(_ data: StructuredField) throws -> [UInt8] {
self.push(key: .init(stringValue: ""), newStorage: .listHeader)
try data.encode(to: self)
switch self.currentStackEntry.storage {
case .list(let list):
return try self.serializer.writeListFieldValue(list)
case .listHeader:
// No encoding happened
return []
case .dictionaryHeader, .dictionary, .itemHeader, .item, .bareInnerList, .innerList,
.parameters, .itemOrInnerList:
throw StructuredHeaderError.invalidTypeForItem
}
}
fileprivate func encodeItemField<StructuredField: Encodable>(_ data: StructuredField) throws -> [UInt8] {
self.push(key: .init(stringValue: ""), newStorage: .itemHeader)
// There's an awkward special hook here: if the outer type is `Data`, `Decimal`, `Date` or
// `DisplayString`, we skip the regular encoding path.
//
// Everything else goes through the normal flow.
switch data {
case is Data:
try self.encode(data)
case is Decimal:
try self.encode(data)
case is Date:
try self.encode(data)
case is DisplayString:
try self.encode(data)
default:
try data.encode(to: self)
}
switch self.currentStackEntry.storage {
case .item(let item):
return try self.serializer.writeItemFieldValue(Item(item))
case .itemHeader:
// No encoding happened
return []
case .dictionaryHeader, .dictionary, .listHeader, .list, .bareInnerList, .innerList,
.parameters, .itemOrInnerList:
throw StructuredHeaderError.invalidTypeForItem
}
}
}
extension _StructuredFieldEncoder: Encoder {
var codingPath: [CodingKey] {
self._codingPath.map { $0.key as CodingKey }
}
var userInfo: [CodingUserInfoKey: Any] {
[:]
}
func push(key: _StructuredHeaderCodingKey, newStorage: NodeType) {
self._codingPath.append(self.currentStackEntry)
self.currentStackEntry = .init(key: key, storage: newStorage)
}
func pop() throws {
// This is called when we've completed the storage in the current container.
// We can pop the value at the base of the stack, then "insert" the current one
// into it, and save the new value as the new current.
let current = self.currentStackEntry
var newCurrent = self._codingPath.removeLast()
try newCurrent.storage.insert(current.storage, atKey: current.key)
self.currentStackEntry = newCurrent
}
func container<Key>(keyedBy type: Key.Type) -> KeyedEncodingContainer<Key> where Key: CodingKey {
KeyedEncodingContainer(StructuredFieldKeyedEncodingContainer(encoder: self))
}
func unkeyedContainer() -> UnkeyedEncodingContainer {
StructuredFieldUnkeyedEncodingContainer(encoder: self)
}
func singleValueContainer() -> SingleValueEncodingContainer {
self
}
}
extension _StructuredFieldEncoder: SingleValueEncodingContainer {
func encodeNil() throws {
// bare items are never nil.
throw StructuredHeaderError.invalidTypeForItem
}
func encode(_ value: Bool) throws {
try self.currentStackEntry.storage.insertBareItem(.bool(value))
}
func encode(_ value: String) throws {
if value.structuredHeadersIsValidToken {
try self.currentStackEntry.storage.insertBareItem(.token(value))
} else {
try self.currentStackEntry.storage.insertBareItem(.string(value))
}
}
func encode(_ value: Double) throws {
try self._encodeBinaryFloatingPoint(value)
}
func encode(_ value: Float) throws {
try self._encodeBinaryFloatingPoint(value)
}
func encode(_ value: Int) throws {
try self._encodeFixedWidthInteger(value)
}
func encode(_ value: Int8) throws {
try self._encodeFixedWidthInteger(value)
}
func encode(_ value: Int16) throws {
try self._encodeFixedWidthInteger(value)
}
func encode(_ value: Int32) throws {
try self._encodeFixedWidthInteger(value)
}
func encode(_ value: Int64) throws {
try self._encodeFixedWidthInteger(value)
}
@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
func encode(_ value: Int128) throws {
try self._encodeFixedWidthInteger(value)
}
func encode(_ value: UInt) throws {
try self._encodeFixedWidthInteger(value)
}
func encode(_ value: UInt8) throws {
try self._encodeFixedWidthInteger(value)
}
func encode(_ value: UInt16) throws {
try self._encodeFixedWidthInteger(value)
}
func encode(_ value: UInt32) throws {
try self._encodeFixedWidthInteger(value)
}
func encode(_ value: UInt64) throws {
try self._encodeFixedWidthInteger(value)
}
@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
func encode(_ value: UInt128) throws {
try self._encodeFixedWidthInteger(value)
}
func encode(_ data: Data) throws {
let encoded = data.base64EncodedString()
try self.currentStackEntry.storage.insertBareItem(.undecodedByteSequence(encoded))
}
func encode(_ data: Decimal) throws {
let significand = (data.significand.magnitude as NSNumber).intValue // Yes, really.
guard let exponent = Int8(exactly: data.exponent) else {
throw StructuredHeaderError.invalidIntegerOrDecimal
}
let pd = PseudoDecimal(mantissa: significand * (data.isSignMinus ? -1 : 1), exponent: Int(exponent))
try self.currentStackEntry.storage.insertBareItem(.decimal(pd))
}
func encode(_ data: Date) throws {
let date = Int64(data.timeIntervalSince1970)
try self.currentStackEntry.storage.insertBareItem(.date(date))
}
func encode(_ data: DisplayString) throws {
try self.currentStackEntry.storage.insertBareItem(.displayString(data.rawValue))
}
func encode<T>(_ value: T) throws where T: Encodable {
switch value {
case let value as UInt8:
try self.encode(value)
case let value as Int8:
try self.encode(value)
case let value as UInt16:
try self.encode(value)
case let value as Int16:
try self.encode(value)
case let value as UInt32:
try self.encode(value)
case let value as Int32:
try self.encode(value)
case let value as UInt64:
try self.encode(value)
case let value as Int64:
try self.encode(value)
case let value as Int:
try self.encode(value)
case let value as UInt:
try self.encode(value)
case let value as Float:
try self.encode(value)
case let value as Double:
try self.encode(value)
case let value as String:
try self.encode(value)
case let value as Bool:
try self.encode(value)
case let value as Data:
try self.encode(value)
case let value as Decimal:
try self.encode(value)
case let value as Date:
try self.encode(value)
case let value as DisplayString:
try self.encode(value)
default:
throw StructuredHeaderError.invalidTypeForItem
}
}
private func _encodeBinaryFloatingPoint<T: BinaryFloatingPoint>(_ value: T) throws {
// We go via double and encode it as a decimal.
let pseudoDecimal = try PseudoDecimal(Double(value))
try self.currentStackEntry.storage.insertBareItem(.decimal(pseudoDecimal))
}
private func _encodeFixedWidthInteger<T: FixedWidthInteger>(_ value: T) throws {
guard let base = Int64(exactly: value) else {
throw StructuredHeaderError.integerOutOfRange
}
try self.currentStackEntry.storage.insertBareItem(.integer(base))
}
}
extension _StructuredFieldEncoder {
// This extension sort-of corresponds to the unkeyed encoding container: all of
// these methods are called from there.
var count: Int {
switch self.currentStackEntry.storage {
case .bareInnerList(let list):
return list.count
case .innerList(let list):
return list.bareInnerList.count
case .itemOrInnerList:
return 0
case .list(let list):
return list.count
case .listHeader:
return 0
case .dictionaryHeader, .dictionary, .itemHeader, .item, .parameters:
fatalError("Cannot have unkeyed container at \(self.currentStackEntry)")
}
}
func appendNil() throws {
// list entries are never nil.
throw StructuredHeaderError.invalidTypeForItem
}
func append(_ value: Bool) throws {
try self.currentStackEntry.storage.appendBareItem(.bool(value))
}
func append(_ value: String) throws {
if value.structuredHeadersIsValidToken {
try self.currentStackEntry.storage.appendBareItem(.token(value))
} else {
try self.currentStackEntry.storage.appendBareItem(.string(value))
}
}
func append(_ value: Double) throws {
try self._appendBinaryFloatingPoint(value)
}
func append(_ value: Float) throws {
try self._appendBinaryFloatingPoint(value)
}
func append(_ value: Int) throws {
try self._appendFixedWidthInteger(value)
}
func append(_ value: Int8) throws {
try self._appendFixedWidthInteger(value)
}
func append(_ value: Int16) throws {
try self._appendFixedWidthInteger(value)
}
func append(_ value: Int32) throws {
try self._appendFixedWidthInteger(value)
}
func append(_ value: Int64) throws {
try self._appendFixedWidthInteger(value)
}
func append(_ value: UInt) throws {
try self._appendFixedWidthInteger(value)
}
func append(_ value: UInt8) throws {
try self._appendFixedWidthInteger(value)
}
func append(_ value: UInt16) throws {
try self._appendFixedWidthInteger(value)
}
func append(_ value: UInt32) throws {
try self._appendFixedWidthInteger(value)
}
func append(_ value: UInt64) throws {
try self._appendFixedWidthInteger(value)
}
func append(_ value: Data) throws {
try self.currentStackEntry.storage.appendBareItem(.undecodedByteSequence(value.base64EncodedString()))
}
func append(_ value: Decimal) throws {
let significand = (value.significand.magnitude as NSNumber).intValue // Yes, really.
guard let exponent = Int8(exactly: value.exponent) else {
throw StructuredHeaderError.invalidIntegerOrDecimal
}
let pd = PseudoDecimal(mantissa: significand * (value.isSignMinus ? -1 : 1), exponent: Int(exponent))
try self.currentStackEntry.storage.appendBareItem(.decimal(pd))
}
func append(_ value: Date) throws {
let date = Int64(value.timeIntervalSince1970)
try self.currentStackEntry.storage.appendBareItem(.date(date))
}
func append(_ value: DisplayString) throws {
try self.currentStackEntry.storage.appendBareItem(.displayString(value.rawValue))
}
func append<T>(_ value: T) throws where T: Encodable {
switch value {
case let value as UInt8:
try self.append(value)
case let value as Int8:
try self.append(value)
case let value as UInt16:
try self.append(value)
case let value as Int16:
try self.append(value)
case let value as UInt32:
try self.append(value)
case let value as Int32:
try self.append(value)
case let value as UInt64:
try self.append(value)
case let value as Int64:
try self.append(value)
case let value as Int:
try self.append(value)
case let value as UInt:
try self.append(value)
case let value as Float:
try self.append(value)
case let value as Double:
try self.append(value)
case let value as String:
try self.append(value)
case let value as Bool:
try self.append(value)
case let value as Data:
try self.append(value)
case let value as Decimal:
try self.append(value)
case let value as Date:
try self.append(value)
case let value as DisplayString:
try self.append(value)
default:
// Some other codable type.
switch self.currentStackEntry.storage {
case .listHeader, .list:
// This may be an item or inner list.
self.push(key: .init(intValue: self.count), newStorage: .itemOrInnerList([:]))
try value.encode(to: self)
try self.pop()
case .itemOrInnerList(let params):
// This is an inner list.
self.currentStackEntry.storage = .innerList(InnerList(bareInnerList: [], parameters: params))
fallthrough
case .innerList, .bareInnerList:
// This may only be an item.
self.push(key: .init(intValue: self.count), newStorage: .item(.init(bareItem: nil, parameters: [:])))
try value.encode(to: self)
try self.pop()
case .dictionaryHeader, .dictionary, .itemHeader, .item, .parameters:
throw StructuredHeaderError.invalidTypeForItem
}
}
}
private func _appendBinaryFloatingPoint<T: BinaryFloatingPoint>(_ value: T) throws {
// We go via double and encode it as a decimal.
let pseudoDecimal = try PseudoDecimal(Double(value))
try self.currentStackEntry.storage.appendBareItem(.decimal(pseudoDecimal))
}
private func _appendFixedWidthInteger<T: FixedWidthInteger>(_ value: T) throws {
guard let base = Int64(exactly: value) else {
throw StructuredHeaderError.integerOutOfRange
}
try self.currentStackEntry.storage.appendBareItem(.integer(base))
}
}
extension _StructuredFieldEncoder {
// This extension sort-of corresponds to the keyed encoding container: all of
// these methods are called from there. All our keyed encoding containers use
// string keys.
func encode(_ value: Bool, forKey key: String) throws {
let key = self.sanitizeKey(key)
try self.currentStackEntry.storage.insertBareItem(.bool(value), atKey: key)
}
func encode(_ value: String, forKey key: String) throws {
let key = self.sanitizeKey(key)
if value.structuredHeadersIsValidToken {
try self.currentStackEntry.storage.insertBareItem(.token(value), atKey: key)
} else {
try self.currentStackEntry.storage.insertBareItem(.string(value), atKey: key)
}
}
func encode(_ value: Double, forKey key: String) throws {
let key = self.sanitizeKey(key)
try self._encodeBinaryFloatingPoint(value, forKey: key)
}
func encode(_ value: Float, forKey key: String) throws {
let key = self.sanitizeKey(key)
try self._encodeBinaryFloatingPoint(value, forKey: key)
}
func encode(_ value: Int, forKey key: String) throws {
let key = self.sanitizeKey(key)
try self._encodeFixedWidthInteger(value, forKey: key)
}
func encode(_ value: Int8, forKey key: String) throws {
let key = self.sanitizeKey(key)
try self._encodeFixedWidthInteger(value, forKey: key)
}
func encode(_ value: Int16, forKey key: String) throws {
let key = self.sanitizeKey(key)
try self._encodeFixedWidthInteger(value, forKey: key)
}
func encode(_ value: Int32, forKey key: String) throws {
let key = self.sanitizeKey(key)
try self._encodeFixedWidthInteger(value, forKey: key)
}
func encode(_ value: Int64, forKey key: String) throws {
let key = self.sanitizeKey(key)
try self._encodeFixedWidthInteger(value, forKey: key)
}
func encode(_ value: UInt, forKey key: String) throws {
let key = self.sanitizeKey(key)
try self._encodeFixedWidthInteger(value, forKey: key)
}
func encode(_ value: UInt8, forKey key: String) throws {
let key = self.sanitizeKey(key)
try self._encodeFixedWidthInteger(value, forKey: key)
}
func encode(_ value: UInt16, forKey key: String) throws {
let key = self.sanitizeKey(key)
try self._encodeFixedWidthInteger(value, forKey: key)
}
func encode(_ value: UInt32, forKey key: String) throws {
let key = self.sanitizeKey(key)
try self._encodeFixedWidthInteger(value, forKey: key)
}
func encode(_ value: UInt64, forKey key: String) throws {
let key = self.sanitizeKey(key)
try self._encodeFixedWidthInteger(value, forKey: key)
}
func encode(_ value: Data, forKey key: String) throws {
let key = self.sanitizeKey(key)
try self.currentStackEntry.storage.insertBareItem(
.undecodedByteSequence(value.base64EncodedString()),
atKey: key
)
}
func encode(_ value: Decimal, forKey key: String) throws {
let significand = (value.significand.magnitude as NSNumber).intValue // Yes, really.
guard let exponent = Int8(exactly: value.exponent) else {
throw StructuredHeaderError.invalidIntegerOrDecimal
}
let pd = PseudoDecimal(mantissa: significand * (value.isSignMinus ? -1 : 1), exponent: Int(exponent))
try self.currentStackEntry.storage.insertBareItem(.decimal(pd), atKey: key)
}
func encode(_ value: Date, forKey key: String) throws {
let key = self.sanitizeKey(key)
let date = Int64(value.timeIntervalSince1970)
try self.currentStackEntry.storage.insertBareItem(.date(date), atKey: key)
}
func encode(_ value: DisplayString, forKey key: String) throws {
let key = self.sanitizeKey(key)
let displayString = value.rawValue
try self.currentStackEntry.storage.insertBareItem(.displayString(displayString), atKey: key)
}
func encode<T>(_ value: T, forKey key: String) throws where T: Encodable {
let key = self.sanitizeKey(key)
switch value {
case let value as UInt8:
try self.encode(value, forKey: key)
case let value as Int8:
try self.encode(value, forKey: key)
case let value as UInt16:
try self.encode(value, forKey: key)
case let value as Int16:
try self.encode(value, forKey: key)
case let va
gitextract_vxft_rju/
├── .editorconfig
├── .github/
│ ├── release.yml
│ └── workflows/
│ ├── main.yml
│ ├── pull_request.yml
│ └── pull_request_label.yml
├── .gitignore
├── .licenseignore
├── .spi.yml
├── .swift-format
├── CONTRIBUTORS.txt
├── LICENSE.txt
├── Package.swift
├── README.md
├── Sources/
│ ├── RawStructuredFieldValues/
│ │ ├── ASCII.swift
│ │ ├── ComponentTypes.swift
│ │ ├── Docs.docc/
│ │ │ └── index.md
│ │ ├── Errors.swift
│ │ ├── FieldParser.swift
│ │ ├── FieldSerializer.swift
│ │ ├── OrderedMap.swift
│ │ └── PseudoDecimal.swift
│ ├── StructuredFieldValues/
│ │ ├── Decoder/
│ │ │ ├── BareInnerListDecoder.swift
│ │ │ ├── BareItemDecoder.swift
│ │ │ ├── DictionaryKeyedContainer.swift
│ │ │ ├── KeyedInnerListDecoder.swift
│ │ │ ├── KeyedItemDecoder.swift
│ │ │ ├── KeyedTopLevelListDecoder.swift
│ │ │ ├── ParametersDecoder.swift
│ │ │ ├── StructuredFieldValueDecoder.swift
│ │ │ ├── StructuredHeaderCodingKey.swift
│ │ │ └── TopLevelListDecoder.swift
│ │ ├── DisplayString.swift
│ │ ├── Docs.docc/
│ │ │ └── index.md
│ │ ├── Encoder/
│ │ │ ├── StructuredFieldKeyedEncodingContainer.swift
│ │ │ ├── StructuredFieldUnkeyedEncodingContainer.swift
│ │ │ └── StructuredFieldValueEncoder.swift
│ │ └── StructuredFieldValue.swift
│ └── sh-parser/
│ └── main.swift
├── Tests/
│ ├── StructuredFieldValuesTests/
│ │ ├── Base32.swift
│ │ ├── Fixtures.swift
│ │ ├── StructuredFieldDecoderTests.swift
│ │ ├── StructuredFieldEncoderTests.swift
│ │ ├── StructuredFieldParserTests.swift
│ │ └── StructuredFieldSerializerTests.swift
│ └── TestFixtures/
│ ├── binary.json
│ ├── boolean.json
│ ├── date.json
│ ├── dictionary.json
│ ├── display-string.json
│ ├── examples.json
│ ├── item.json
│ ├── key-generated.json
│ ├── large-generated.json
│ ├── list.json
│ ├── listlist.json
│ ├── number-generated.json
│ ├── number.json
│ ├── param-dict.json
│ ├── param-list.json
│ ├── param-listlist.json
│ ├── serialisation-tests/
│ │ ├── key-generated.json
│ │ ├── number.json
│ │ ├── string-generated.json
│ │ └── token-generated.json
│ ├── string-generated.json
│ ├── string.json
│ ├── token-generated.json
│ └── token.json
└── dev/
└── git.commit.template
Condensed preview — 69 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,367K chars).
[
{
"path": ".editorconfig",
"chars": 130,
"preview": "root = true\n\n[*]\nindent_style = space\nindent_size = 4\nend_of_line = lf\ninsert_final_newline = true\ntrim_trailing_whitesp"
},
{
"path": ".github/release.yml",
"chars": 284,
"preview": "changelog:\n categories:\n - title: SemVer Major\n labels:\n - ⚠️ semver/major\n - title: SemVer Minor\n "
},
{
"path": ".github/workflows/main.yml",
"chars": 1616,
"preview": "name: Main\n\npermissions:\n contents: read\n\non:\n push:\n branches: [main]\n schedule:\n - cron: \"0 8,2"
},
{
"path": ".github/workflows/pull_request.yml",
"chars": 1902,
"preview": "name: PR\n\npermissions:\n contents: read\n\non:\n pull_request:\n types: [opened, reopened, synchronize]\n\njobs:\n "
},
{
"path": ".github/workflows/pull_request_label.yml",
"chars": 510,
"preview": "name: PR label\n\npermissions:\n contents: read\n\non:\n pull_request:\n types: [labeled, unlabeled, opened, reopened, syn"
},
{
"path": ".gitignore",
"chars": 80,
"preview": ".DS_Store\n/.build\n/Packages\n/*.xcodeproj\nxcuserdata/\n.swiftpm/\nPackage.resolved\n"
},
{
"path": ".licenseignore",
"chars": 468,
"preview": ".gitignore\n**/.gitignore\n.licenseignore\n.gitattributes\n.git-blame-ignore-revs\n.mailfilter\n.mailmap\n.spi.yml\n.swift-forma"
},
{
"path": ".spi.yml",
"chars": 110,
"preview": "version: 1\nbuilder:\n configs:\n - documentation_targets: [RawStructuredFieldValues, StructuredFieldValues]\n"
},
{
"path": ".swift-format",
"chars": 2380,
"preview": "{\n \"version\" : 1,\n \"indentation\" : {\n \"spaces\" : 4\n },\n \"tabWidth\" : 4,\n \"fileScopedDeclarationPrivacy\" : {\n "
},
{
"path": "CONTRIBUTORS.txt",
"chars": 650,
"preview": "For the purpose of tracking copyright, this is the list of individuals and\norganizations who have contributed source cod"
},
{
"path": "LICENSE.txt",
"chars": 11358,
"preview": "\n Apache License\n Version 2.0, January 2004\n "
},
{
"path": "Package.swift",
"chars": 2745,
"preview": "// swift-tools-version:6.1\n//===----------------------------------------------------------------------===//\n//\n// This s"
},
{
"path": "README.md",
"chars": 9148,
"preview": "# swift-http-structured-headers\n\nA Swift implementation of the HTTP Structured Header Field Value specification.\n\nProvid"
},
{
"path": "Sources/RawStructuredFieldValues/ASCII.swift",
"chars": 2105,
"preview": "//===----------------------------------------------------------------------===//\n//\n// This source file is part of the S"
},
{
"path": "Sources/RawStructuredFieldValues/ComponentTypes.swift",
"chars": 11325,
"preview": "//===----------------------------------------------------------------------===//\n//\n// This source file is part of the S"
},
{
"path": "Sources/RawStructuredFieldValues/Docs.docc/index.md",
"chars": 5373,
"preview": "# ``RawStructuredFieldValues``\n\nA Swift implementation of the HTTP Structured Header Field Value specification.\n\nProvide"
},
{
"path": "Sources/RawStructuredFieldValues/Errors.swift",
"chars": 2893,
"preview": "//===----------------------------------------------------------------------===//\n//\n// This source file is part of the S"
},
{
"path": "Sources/RawStructuredFieldValues/FieldParser.swift",
"chars": 30479,
"preview": "//===----------------------------------------------------------------------===//\n//\n// This source file is part of the S"
},
{
"path": "Sources/RawStructuredFieldValues/FieldSerializer.swift",
"chars": 9611,
"preview": "//===----------------------------------------------------------------------===//\n//\n// This source file is part of the S"
},
{
"path": "Sources/RawStructuredFieldValues/OrderedMap.swift",
"chars": 5494,
"preview": "//===----------------------------------------------------------------------===//\n//\n// This source file is part of the S"
},
{
"path": "Sources/RawStructuredFieldValues/PseudoDecimal.swift",
"chars": 8937,
"preview": "//===----------------------------------------------------------------------===//\n//\n// This source file is part of the S"
},
{
"path": "Sources/StructuredFieldValues/Decoder/BareInnerListDecoder.swift",
"chars": 3934,
"preview": "//===----------------------------------------------------------------------===//\n//\n// This source file is part of the S"
},
{
"path": "Sources/StructuredFieldValues/Decoder/BareItemDecoder.swift",
"chars": 6833,
"preview": "//===----------------------------------------------------------------------===//\n//\n// This source file is part of the S"
},
{
"path": "Sources/StructuredFieldValues/Decoder/DictionaryKeyedContainer.swift",
"chars": 3541,
"preview": "//===----------------------------------------------------------------------===//\n//\n// This source file is part of the S"
},
{
"path": "Sources/StructuredFieldValues/Decoder/KeyedInnerListDecoder.swift",
"chars": 3679,
"preview": "//===----------------------------------------------------------------------===//\n//\n// This source file is part of the S"
},
{
"path": "Sources/StructuredFieldValues/Decoder/KeyedItemDecoder.swift",
"chars": 3616,
"preview": "//===----------------------------------------------------------------------===//\n//\n// This source file is part of the S"
},
{
"path": "Sources/StructuredFieldValues/Decoder/KeyedTopLevelListDecoder.swift",
"chars": 3651,
"preview": "//===----------------------------------------------------------------------===//\n//\n// This source file is part of the S"
},
{
"path": "Sources/StructuredFieldValues/Decoder/ParametersDecoder.swift",
"chars": 3511,
"preview": "//===----------------------------------------------------------------------===//\n//\n// This source file is part of the S"
},
{
"path": "Sources/StructuredFieldValues/Decoder/StructuredFieldValueDecoder.swift",
"chars": 14850,
"preview": "//===----------------------------------------------------------------------===//\n//\n// This source file is part of the S"
},
{
"path": "Sources/StructuredFieldValues/Decoder/StructuredHeaderCodingKey.swift",
"chars": 1286,
"preview": "//===----------------------------------------------------------------------===//\n//\n// This source file is part of the S"
},
{
"path": "Sources/StructuredFieldValues/Decoder/TopLevelListDecoder.swift",
"chars": 3940,
"preview": "//===----------------------------------------------------------------------===//\n//\n// This source file is part of the S"
},
{
"path": "Sources/StructuredFieldValues/DisplayString.swift",
"chars": 798,
"preview": "//===----------------------------------------------------------------------===//\n//\n// This source file is part of the S"
},
{
"path": "Sources/StructuredFieldValues/Docs.docc/index.md",
"chars": 5941,
"preview": "# ``StructuredFieldValues``\n\nA Swift implementation of the HTTP Structured Header Field Value specification.\n\nProvides p"
},
{
"path": "Sources/StructuredFieldValues/Encoder/StructuredFieldKeyedEncodingContainer.swift",
"chars": 3805,
"preview": "//===----------------------------------------------------------------------===//\n//\n// This source file is part of the S"
},
{
"path": "Sources/StructuredFieldValues/Encoder/StructuredFieldUnkeyedEncodingContainer.swift",
"chars": 2872,
"preview": "//===----------------------------------------------------------------------===//\n//\n// This source file is part of the S"
},
{
"path": "Sources/StructuredFieldValues/Encoder/StructuredFieldValueEncoder.swift",
"chars": 37382,
"preview": "//===----------------------------------------------------------------------===//\n//\n// This source file is part of the S"
},
{
"path": "Sources/StructuredFieldValues/StructuredFieldValue.swift",
"chars": 1221,
"preview": "//===----------------------------------------------------------------------===//\n//\n// This source file is part of the S"
},
{
"path": "Sources/sh-parser/main.swift",
"chars": 6007,
"preview": "//===----------------------------------------------------------------------===//\n//\n// This source file is part of the S"
},
{
"path": "Tests/StructuredFieldValuesTests/Base32.swift",
"chars": 2343,
"preview": "//===----------------------------------------------------------------------===//\n//\n// This source file is part of the S"
},
{
"path": "Tests/StructuredFieldValuesTests/Fixtures.swift",
"chars": 5938,
"preview": "//===----------------------------------------------------------------------===//\n//\n// This source file is part of the S"
},
{
"path": "Tests/StructuredFieldValuesTests/StructuredFieldDecoderTests.swift",
"chars": 28203,
"preview": "//===----------------------------------------------------------------------===//\n//\n// This source file is part of the S"
},
{
"path": "Tests/StructuredFieldValuesTests/StructuredFieldEncoderTests.swift",
"chars": 20540,
"preview": "//===----------------------------------------------------------------------===//\n//\n// This source file is part of the S"
},
{
"path": "Tests/StructuredFieldValuesTests/StructuredFieldParserTests.swift",
"chars": 13138,
"preview": "//===----------------------------------------------------------------------===//\n//\n// This source file is part of the S"
},
{
"path": "Tests/StructuredFieldValuesTests/StructuredFieldSerializerTests.swift",
"chars": 9853,
"preview": "//===----------------------------------------------------------------------===//\n//\n// This source file is part of the S"
},
{
"path": "Tests/TestFixtures/binary.json",
"chars": 1784,
"preview": "[\n {\n \"name\": \"basic binary\",\n \"raw\": [\":aGVsbG8=:\"],\n \"header_type\": \"item\",\n \"expected\""
},
{
"path": "Tests/TestFixtures/boolean.json",
"chars": 1443,
"preview": "[\n {\n \"name\": \"basic true boolean\",\n \"raw\": [\"?1\"],\n \"header_type\": \"item\",\n \"expected\": "
},
{
"path": "Tests/TestFixtures/date.json",
"chars": 1036,
"preview": "[\n {\n \"name\": \"date - 1970-01-01 00:00:00\",\n \"raw\": [\"@0\"],\n \"header_type\": \"item\",\n \"exp"
},
{
"path": "Tests/TestFixtures/dictionary.json",
"chars": 4647,
"preview": "[\n {\n \"name\": \"basic dictionary\",\n \"raw\": [\"en=\\\"Applepie\\\", da=:w4ZibGV0w6ZydGUK:\"],\n \"header_t"
},
{
"path": "Tests/TestFixtures/display-string.json",
"chars": 3285,
"preview": "[\n {\n \"name\": \"basic display string (ascii content)\",\n \"raw\": [\"%\\\"foo bar\\\"\"],\n \"header_type\": "
},
{
"path": "Tests/TestFixtures/examples.json",
"chars": 5979,
"preview": "[\n {\n \"name\": \"Foo-Example\",\n \"raw\": [\"2; foourl=\\\"https://foo.example.com/\\\"\"],\n \"header_type\":"
},
{
"path": "Tests/TestFixtures/item.json",
"chars": 735,
"preview": "[\n {\n \"name\": \"empty item\",\n \"raw\": [\"\"],\n \"header_type\": \"item\",\n \"must_fail\": true\n "
},
{
"path": "Tests/TestFixtures/key-generated.json",
"chars": 113372,
"preview": "[\n {\n \"name\": \"0x00 in dictionary key\",\n \"raw\": [\n \"a\\u0000a=1\"\n ],\n \"header_t"
},
{
"path": "Tests/TestFixtures/large-generated.json",
"chars": 498701,
"preview": "[\n {\n \"name\": \"large dictionary\",\n \"raw\": [\n \"a0=1, a1=1, a2=1, a3=1, a4=1, a5=1, a6=1, a7=1"
},
{
"path": "Tests/TestFixtures/list.json",
"chars": 1592,
"preview": "[\n {\n \"name\": \"basic list\",\n \"raw\": [\"1, 42\"],\n \"header_type\": \"list\",\n \"expected\": [[1, "
},
{
"path": "Tests/TestFixtures/listlist.json",
"chars": 1552,
"preview": "[\n {\n \"name\": \"basic list of lists\",\n \"raw\": [\"(1 2), (42 43)\"],\n \"header_type\": \"list\",\n "
},
{
"path": "Tests/TestFixtures/number-generated.json",
"chars": 46235,
"preview": "[\n {\n \"name\": \"1 digits of zero\",\n \"raw\": [\n \"0\"\n ],\n \"header_type\": \"item\",\n "
},
{
"path": "Tests/TestFixtures/number.json",
"chars": 4634,
"preview": "[\n {\n \"name\": \"basic integer\",\n \"raw\": [\"42\"],\n \"header_type\": \"item\",\n \"expected\": [42, "
},
{
"path": "Tests/TestFixtures/param-dict.json",
"chars": 3393,
"preview": "[\n {\n \"name\": \"basic parameterised dict\",\n \"raw\": [\"abc=123;a=1;b=2, def=456, ghi=789;q=9;r=\\\"+w\\\"\"],\n "
},
{
"path": "Tests/TestFixtures/param-list.json",
"chars": 3595,
"preview": "[\n {\n \"name\": \"basic parameterised list\",\n \"raw\": [\"abc_123;a=1;b=2; cdef_456, ghi;q=9;r=\\\"+w\\\"\"],\n "
},
{
"path": "Tests/TestFixtures/param-listlist.json",
"chars": 1006,
"preview": "[\n {\n \"name\": \"parameterised inner list\",\n \"raw\": [\"(abc_123);a=1;b=2, cdef_456\"],\n \"header_type"
},
{
"path": "Tests/TestFixtures/serialisation-tests/key-generated.json",
"chars": 116623,
"preview": "[\n {\n \"name\": \"0x00 in dictionary key - serialise only\",\n \"expected\": {\n \"a\\u0000a\": [\n "
},
{
"path": "Tests/TestFixtures/serialisation-tests/number.json",
"chars": 1528,
"preview": "[\n {\n \"name\": \"too big postive integer - serialize\",\n \"header_type\": \"item\",\n \"expected\": [10000"
},
{
"path": "Tests/TestFixtures/serialisation-tests/string-generated.json",
"chars": 6285,
"preview": "[\n {\n \"name\": \"0x00 in string - serialise only\",\n \"expected\": [\n \"\\u0000\",\n {}\n "
},
{
"path": "Tests/TestFixtures/serialisation-tests/token-generated.json",
"chars": 33433,
"preview": "[\n {\n \"name\": \"0x00 in token - serialise only\",\n \"header_type\": \"item\",\n \"expected\": [\n "
},
{
"path": "Tests/TestFixtures/string-generated.json",
"chars": 43518,
"preview": "[\n {\n \"name\": \"0x00 in string\",\n \"raw\": [\n \"\\\" \\u0000 \\\"\"\n ],\n \"header_type\": "
},
{
"path": "Tests/TestFixtures/string.json",
"chars": 2339,
"preview": "[\n {\n \"name\": \"basic string\",\n \"raw\": [\"\\\"foo bar\\\"\"],\n \"header_type\": \"item\",\n \"expected"
},
{
"path": "Tests/TestFixtures/token-generated.json",
"chars": 55040,
"preview": "[\n {\n \"name\": \"0x00 in token\",\n \"raw\": [\n \"a\\u0000a\"\n ],\n \"header_type\": \"item"
},
{
"path": "Tests/TestFixtures/token.json",
"chars": 1125,
"preview": "[\n {\n \"name\": \"basic token - item\",\n \"raw\": [\"a_b-c.d3:f%00/*\"],\n \"header_type\": \"item\",\n "
},
{
"path": "dev/git.commit.template",
"chars": 259,
"preview": "One line description of your change\n\nMotivation:\n\nExplain here the context, and why you're making that change.\nWhat is t"
}
]
About this extraction
This page contains the full source code of the apple/swift-http-structured-headers GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 69 files (1.2 MB), approximately 274.6k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.