Showing preview only (1,816K chars total). Download the full file or copy to clipboard to get everything.
Repository: vapor/vapor
Branch: main
Commit: ff88583e10f0
Files: 325
Total size: 1.7 MB
Directory structure:
gitextract_n1aicf92/
├── .github/
│ ├── CODEOWNERS
│ ├── contributing.md
│ ├── dependabot.yml
│ ├── maintainers.md
│ └── workflows/
│ ├── api-docs.yml
│ ├── sponsors.yml
│ └── test.yml
├── .gitignore
├── .spi.yml
├── AGENTS.md
├── LICENSE
├── NOTICES.txt
├── Package.swift
├── README.md
├── Sources/
│ ├── CVaporBcrypt/
│ │ ├── bcrypt.c
│ │ ├── bcrypt.h
│ │ ├── blf.c
│ │ ├── blf.h
│ │ └── include/
│ │ └── module.modulemap
│ ├── Development/
│ │ ├── Resources/
│ │ │ └── fileio.txt
│ │ ├── configure.swift
│ │ ├── entrypoint.swift
│ │ └── routes.swift
│ ├── Vapor/
│ │ ├── Application.swift
│ │ ├── Authentication/
│ │ │ ├── AuthenticationCache.swift
│ │ │ ├── Authenticator.swift
│ │ │ ├── BasicAuthorization.swift
│ │ │ ├── BearerAuthorization.swift
│ │ │ ├── GuardMiddleware.swift
│ │ │ ├── RedirectMiddleware.swift
│ │ │ └── SessionAuthenticatable.swift
│ │ ├── Bcrypt/
│ │ │ └── Bcrypt.swift
│ │ ├── Cache/
│ │ │ ├── Application+Cache.swift
│ │ │ ├── Cache.swift
│ │ │ ├── CacheExpirationTime.swift
│ │ │ ├── MemoryCache.swift
│ │ │ └── Request+Cache.swift
│ │ ├── Client/
│ │ │ ├── Application+Clients.swift
│ │ │ ├── Client.swift
│ │ │ ├── ClientRequest.swift
│ │ │ ├── ClientResponse.swift
│ │ │ └── Request+Client.swift
│ │ ├── Commands/
│ │ │ ├── BootCommand.swift
│ │ │ ├── CommandContext+Application.swift
│ │ │ ├── RoutesCommand.swift
│ │ │ └── ServeCommand.swift
│ │ ├── Concurrency/
│ │ │ ├── AnyResponse+Concurrency.swift
│ │ │ ├── AsyncBasicResponder.swift
│ │ │ ├── AsyncMiddleware.swift
│ │ │ ├── AsyncPasswordHasher+Concurrency.swift
│ │ │ ├── AsyncSessionDriver.swift
│ │ │ ├── Authentication+Concurrency.swift
│ │ │ ├── Cache+Concurrency.swift
│ │ │ ├── Client+Concurrency.swift
│ │ │ ├── RequestBody+Concurrency.swift
│ │ │ ├── Responder+Concurrency.swift
│ │ │ ├── ResponseCodable+Concurrency.swift
│ │ │ ├── RoutesBuilder+Concurrency.swift
│ │ │ ├── ViewRenderer+Concurrency.swift
│ │ │ └── WebSocket+Concurrency.swift
│ │ ├── Content/
│ │ │ ├── ContainerGetPathExecutor.swift
│ │ │ ├── Content.swift
│ │ │ ├── ContentCoders.swift
│ │ │ ├── ContentConfiguration.swift
│ │ │ ├── ContentContainer.swift
│ │ │ ├── JSONCoder+Custom.swift
│ │ │ ├── JSONCoders+Content.swift
│ │ │ ├── PlaintextDecoder.swift
│ │ │ ├── PlaintextEncoder.swift
│ │ │ ├── URLQueryCoders.swift
│ │ │ └── URLQueryContainer.swift
│ │ ├── Core/
│ │ │ ├── Core.swift
│ │ │ └── Running.swift
│ │ ├── Deprecations/
│ │ │ ├── CORSMiddleware+AllowOriginSetting.swift
│ │ │ ├── CORSMiddleware+Configuration+exposedHeaders.swift
│ │ │ ├── DotEnvFile+load.swift
│ │ │ ├── Routes+caseInsenstive.swift
│ │ │ └── Validatable+validate.swift
│ │ ├── Docs.docc/
│ │ │ └── index.md
│ │ ├── Environment/
│ │ │ ├── Environment+Process.swift
│ │ │ ├── Environment+Secret.swift
│ │ │ └── Environment.swift
│ │ ├── Error/
│ │ │ ├── Abort.swift
│ │ │ ├── AbortError.swift
│ │ │ ├── DebuggableError.swift
│ │ │ ├── Demangler.swift
│ │ │ ├── ErrorSource.swift
│ │ │ └── StackTrace.swift
│ │ ├── Exports.swift
│ │ ├── HTTP/
│ │ │ ├── Application+HTTP.swift
│ │ │ ├── BasicResponder.swift
│ │ │ ├── BodyStream.swift
│ │ │ ├── Client/
│ │ │ │ ├── Application+HTTP+Client.swift
│ │ │ │ └── EventLoopHTTPClient.swift
│ │ │ ├── EndpointCache.swift
│ │ │ ├── HTTPMethod+String.swift
│ │ │ ├── HTTPStatus.swift
│ │ │ ├── Headers/
│ │ │ │ ├── HTTPCookies.swift
│ │ │ │ ├── HTTPHeaderCacheControl.swift
│ │ │ │ ├── HTTPHeaderExpires.swift
│ │ │ │ ├── HTTPHeaderLastModified.swift
│ │ │ │ ├── HTTPHeaders+Cache.swift
│ │ │ │ ├── HTTPHeaders+Connection.swift
│ │ │ │ ├── HTTPHeaders+ContentDisposition.swift
│ │ │ │ ├── HTTPHeaders+ContentRange.swift
│ │ │ │ ├── HTTPHeaders+Directive.swift
│ │ │ │ ├── HTTPHeaders+Forwarded.swift
│ │ │ │ ├── HTTPHeaders+Link.swift
│ │ │ │ ├── HTTPHeaders+Name.swift
│ │ │ │ ├── HTTPHeaders+ResponseCompression.swift
│ │ │ │ ├── HTTPHeaders.swift
│ │ │ │ ├── HTTPMediaType.swift
│ │ │ │ └── HTTPMediaTypePreference.swift
│ │ │ ├── Responder.swift
│ │ │ └── Server/
│ │ │ ├── Application+HTTP+Server.swift
│ │ │ ├── HTTPServer.swift
│ │ │ ├── HTTPServerConfiguration+RequestDecompressionConfiguration.swift
│ │ │ ├── HTTPServerConfiguration+ResponseCompressionConfiguration.swift
│ │ │ ├── HTTPServerHandler.swift
│ │ │ ├── HTTPServerRequestDecoder.swift
│ │ │ ├── HTTPServerResponseEncoder.swift
│ │ │ └── HTTPServerUpgradeHandler.swift
│ │ ├── Logging/
│ │ │ ├── Logger+Report.swift
│ │ │ └── LoggingSystem+Environment.swift
│ │ ├── Middleware/
│ │ │ ├── Application+Middleware.swift
│ │ │ ├── CORSMiddleware.swift
│ │ │ ├── ErrorMiddleware.swift
│ │ │ ├── FileMiddleware.swift
│ │ │ ├── Middleware.swift
│ │ │ ├── MiddlewareConfiguration.swift
│ │ │ ├── ResponseCompressionMiddleware.swift
│ │ │ ├── RouteLoggingMiddleware.swift
│ │ │ └── TracingMiddleware.swift
│ │ ├── Multipart/
│ │ │ ├── File+Multipart.swift
│ │ │ ├── FormDataDecoder+Content.swift
│ │ │ └── FormDataEncoder+Content.swift
│ │ ├── Passwords/
│ │ │ ├── Application+Password.swift
│ │ │ ├── Application+Passwords.swift
│ │ │ ├── AsyncPasswordHasher.swift
│ │ │ ├── BcryptHasher.swift
│ │ │ ├── PasswordHasher.swift
│ │ │ ├── PlaintextHasher.swift
│ │ │ └── Request+Password.swift
│ │ ├── Request/
│ │ │ ├── Redirect.swift
│ │ │ ├── Request+Body.swift
│ │ │ ├── Request+BodyStream.swift
│ │ │ └── Request.swift
│ │ ├── Responder/
│ │ │ ├── Application+Responder.swift
│ │ │ └── DefaultResponder.swift
│ │ ├── Response/
│ │ │ ├── Response+Body.swift
│ │ │ ├── Response.swift
│ │ │ └── ResponseCodable.swift
│ │ ├── Routing/
│ │ │ ├── Application+Routes.swift
│ │ │ ├── Parameters+Require.swift
│ │ │ ├── Request+WebSocket.swift
│ │ │ ├── Route.swift
│ │ │ ├── RouteCollection.swift
│ │ │ ├── Routes.swift
│ │ │ ├── RoutesBuilder+Group.swift
│ │ │ ├── RoutesBuilder+Method.swift
│ │ │ ├── RoutesBuilder+Middleware.swift
│ │ │ ├── RoutesBuilder+WebSocket.swift
│ │ │ └── RoutesBuilder.swift
│ │ ├── Security/
│ │ │ ├── OTP.swift
│ │ │ └── ValidatedCertificateChain.swift
│ │ ├── Server/
│ │ │ ├── Application+Servers.swift
│ │ │ └── Server.swift
│ │ ├── Services/
│ │ │ ├── App+Service.swift
│ │ │ ├── Req+Service.swift
│ │ │ └── Service.swift
│ │ ├── Sessions/
│ │ │ ├── Application+Sessions.swift
│ │ │ ├── MemorySessions.swift
│ │ │ ├── Request+Session.swift
│ │ │ ├── Session.swift
│ │ │ ├── SessionCache.swift
│ │ │ ├── SessionData.swift
│ │ │ ├── SessionDriver.swift
│ │ │ ├── SessionsConfiguration.swift
│ │ │ └── SessionsMiddleware.swift
│ │ ├── URLEncodedForm/
│ │ │ ├── URLEncodedFormData.swift
│ │ │ ├── URLEncodedFormDecoder.swift
│ │ │ ├── URLEncodedFormEncoder.swift
│ │ │ ├── URLEncodedFormError.swift
│ │ │ ├── URLEncodedFormParser.swift
│ │ │ ├── URLEncodedFormSerializer.swift
│ │ │ └── URLQueryFragmentConvertible.swift
│ │ ├── Utilities/
│ │ │ ├── AnyResponse.swift
│ │ │ ├── Array+Random.swift
│ │ │ ├── Base32.swift
│ │ │ ├── Base64.swift
│ │ │ ├── BaseN.swift
│ │ │ ├── BasicCodingKey.swift
│ │ │ ├── ByteCount.swift
│ │ │ ├── Bytes+Hex.swift
│ │ │ ├── Bytes+SecureCompare.swift
│ │ │ ├── Collection+Safe.swift
│ │ │ ├── DataProtocol+Copy.swift
│ │ │ ├── DecoderUnwrapper.swift
│ │ │ ├── DirectoryConfiguration.swift
│ │ │ ├── DotEnv.swift
│ │ │ ├── Extendable.swift
│ │ │ ├── File.swift
│ │ │ ├── FileIO.swift
│ │ │ ├── LifecycleHandler.swift
│ │ │ ├── OptionalType.swift
│ │ │ ├── RFC1123.swift
│ │ │ ├── SocketAddress+Hostname.swift
│ │ │ ├── Storage.swift
│ │ │ ├── String+IsIPAddress.swift
│ │ │ ├── Thread.swift
│ │ │ ├── URI.swift
│ │ │ └── VaporSendableMetadataType.swift
│ │ ├── Validation/
│ │ │ ├── RangeResult.swift
│ │ │ ├── Validatable.swift
│ │ │ ├── Validation.swift
│ │ │ ├── ValidationKey.swift
│ │ │ ├── Validations.swift
│ │ │ ├── ValidationsError.swift
│ │ │ ├── Validator.swift
│ │ │ ├── ValidatorResult.swift
│ │ │ └── Validators/
│ │ │ ├── And.swift
│ │ │ ├── Case.swift
│ │ │ ├── CharacterSet.swift
│ │ │ ├── Count.swift
│ │ │ ├── Custom.swift
│ │ │ ├── Email.swift
│ │ │ ├── Empty.swift
│ │ │ ├── In.swift
│ │ │ ├── Nil.swift
│ │ │ ├── NilIgnoring.swift
│ │ │ ├── Not.swift
│ │ │ ├── Or.swift
│ │ │ ├── Pattern.swift
│ │ │ ├── Range.swift
│ │ │ ├── URL.swift
│ │ │ └── Valid.swift
│ │ ├── View/
│ │ │ ├── Application+Views.swift
│ │ │ ├── PlaintextRenderer.swift
│ │ │ ├── Request+View.swift
│ │ │ ├── View.swift
│ │ │ └── ViewRenderer.swift
│ │ └── _Deprecations.swift
│ ├── VaporTestUtils/
│ │ ├── TestingApplication.swift
│ │ ├── TestingHTTPRequest.swift
│ │ ├── TestingHTTPResponse.swift
│ │ └── Utilities.swift
│ ├── VaporTesting/
│ │ ├── +TestingHTTPResponse.swift
│ │ ├── Docs.docc/
│ │ │ └── index.md
│ │ ├── Exports.swift
│ │ ├── TestingApplicationTester.swift
│ │ ├── VaporTestingContext.swift
│ │ ├── XCTest+Migration.swift
│ │ └── withApp.swift
│ └── XCTVapor/
│ ├── +Application.swift
│ ├── +XCTHTTPResponse.swift
│ ├── Docs.docc/
│ │ └── index.md
│ ├── Exports.swift
│ ├── XCTApplicationTester.swift
│ ├── XCTVaporContext.swift
│ ├── XCTVaporTests.swift
│ └── typealiases.swift
└── Tests/
└── VaporTests/
├── ApplicationCreationTests.swift
├── ApplicationTests.swift
├── AsyncAuthTests.swift
├── AsyncCacheTests.swift
├── AsyncClientTests.swift
├── AsyncCommandsTests.swift
├── AsyncEnvironmentTests.swift
├── AsyncFileTests.swift
├── AsyncMiddlewareTests.swift
├── AsyncPasswordTests.swift
├── AsyncRequestTests.swift
├── AsyncRouteTests.swift
├── AsyncSessionTests.swift
├── AsyncWebSocketTests.swift
├── AuthenticationTests.swift
├── BaseNTests.swift
├── BcryptTests.swift
├── BodyStreamStateTests.swift
├── CacheTests.swift
├── ClientTests.swift
├── ConditionalResponseCompressionTests.swift
├── ContentTests.swift
├── DotEnvTests.swift
├── EndpointCacheTests.swift
├── EnvironmentSecretTests.swift
├── ErrorTests.swift
├── FileTests.swift
├── HTTPCacheTests.swift
├── HTTPHeaderTests.swift
├── HTTPMediaTypeSetTests.swift
├── LoggingTests.swift
├── MetricsTests.swift
├── MiddlewareTests.swift
├── PasswordTests.swift
├── PipelineTests.swift
├── QueryTests.swift
├── RequestTests.swift
├── RouteTests.swift
├── SecurityTests.swift
├── ServerTests.swift
├── ServiceTests.swift
├── SessionTests.swift
├── URITests.swift
├── URLEncodedFormTests.swift
├── Utilities/
│ ├── ByteBuffer+Helpers.swift
│ ├── CapturingMetricsSystem.swift
│ ├── ResponderClient.swift
│ ├── SubUtilities/
│ │ └── index.html
│ ├── TestLogging.swift
│ ├── TestTracer.swift
│ ├── ThreadSafe.swift
│ ├── expired.crt
│ ├── expired.key
│ ├── foo bar.html
│ ├── foo.txt
│ ├── index.html
│ ├── long-test-file.txt
│ ├── my-secret-env-content
│ └── test.env
├── UtilityTests.swift
├── ValidationTests.swift
├── VaporTesting.swift
├── ViewTests.swift
└── WebSocketTests.swift
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/CODEOWNERS
================================================
* @0xTim @gwynne
================================================
FILE: .github/contributing.md
================================================
# Contributing to Vapor
👋 Welcome to the Vapor team!
## Testing
Once in Xcode, select the `vapor-Package` scheme and use `CMD+U` to run the tests.
You can use the `Development` executables for testing out your code.
Don't forget to add tests for your new features.
If you are fixing a single GitHub issue in particular, you can add a test named `testGH<issue number>` to ensure
that your fix is working. This will also help prevent regression.
## API Documentation
Make sure that any new public API is covered by API documentation and update any existing documentation where relevant. See [Formatting Quick Help](https://developer.apple.com/library/archive/documentation/Xcode/Reference/xcode_markup_formatting_ref/SymbolDocumentation.html#//apple_ref/doc/uid/TP40016497-CH51-SW1) and [Swift Documentation](https://nshipster.com/swift-documentation/) for more information.
## Style Guide
When contributing code to Vapor, please try to follow existing style for consistency.
### Xcode Formatting
Avoid using "Xcode style" indentation where possible. This can be difficult to recreate in other editors.
❌
```swift
func foo(bar: String,
baz: Int) {
...
}
```
Use normal indentation instead.
✅
```swift
func foo(
bar: String,
baz: Int
) {
...
}
```
### Explicit self
When accessing member properties and methods, use explicit `self`.
```swift
struct Foo {
var bar: Int
func baz() {
// ❌
print(bar)
// ✅
print(self.bar)
}
}
```
This makes it easier to tell whether a local variable is being used or not, especially when reading code without syntax highlighting.
## SemVer
Vapor follows [SemVer](https://semver.org). This means that any changes to the source code that can cause
existing code to stop compiling _must_ wait until the next major version to be included.
Code that is only additive and will not break any existing code can be included in the next minor release.
## Releases
All vapor/* packages use automated releases. When a PR is merged, the Vapor bot will check for certain PR labels and ship a new release right away. This ensures users get access to new features as soon as possible and helps to reduce human error.
The release will use the PR's title and body directly. It will also include a note about who authored the release and who merged it.
### Release title
The release title should be concise description. For example:
- ✅ HTTP streaming improvements
- ✅ Add case-insensitive routing
- ✅ Fix `routes` command symbol usage
The release titles should use sentence capitalization and not be too verbose. They should also use present tense.
- ❌ Fix `routes` Command Symbol Usage
- ❌ Add new method on RouteBuilder called `caseInsensitive` which can be used to enable case-insensitive routing
- ❌ Fixed `routes` command symbol usage
### Release body
The release body (or description) should contain more in-depth information about the change and code examples if possible.
✅
````md
Allows configuring case-insensitive routing (#2354, fixes #1928).
```swift
// Enables case-insensitive routing.
// Defaults to false.
app.routes.caseInsensitive = true
```
````
The release body should include links to any associated PRs and issues. Issues that are fixed by this change should be prefixed with `fixes` so that they are closed automatically. The first line of the release body should be a concise description of the change. This can be followed by a more detailed explanation and code examples.
Releases with a large number of changes can separated using bullets. Special comments or notes can be added using quote blocks `>`.
✅
```md
Improves HTTP request and response streaming (#2404).
- Streaming request body skipping will only happen if the entire response has been sent before the user _starts_ reading the request body (fixes #2393).
> Note: Previously, streaming request bodies would be drained automatically by Vapor as soon as the response head was sent. This made it impossible to implement realtime streaming, like an echo server. With these changes, you have much more control over streaming HTTP while still preventing hanging if the request body is ignored entirely.
- Response body stream now supports omitting the `count` parameter (fixes #2393).
> Note: Previously streaming bodies required a count and would always set the `content-length` header. Now, setting a count of `-1` indicates a stream with indeterminate length. `-1` will be used if the stream count is omitted. This results in `transfer-encoding: chunked` being used automatically.
```
Release bodies should be in present tense third person. They should mention only information relevant to release notes. Any additional information or questions can be included in PR comments.
❌
```md
I've implemented two fixes to HTTP request streaming. I'm wondering if I need to implement three?
Here's what I've done so far:
...
```
The following PR labels are supported by Vapor's release bot.
- `semver-patch`: Bumps the patch version. 0.0.x
- `semver-minor`: Bumps the minor version: 0.x.0
- `semver-major`: Bumps the major version: x.0.0 (rarely used)
- `release`: Advances the pre-release identifier (alpha -> beta -> rc)
When Vapor's release bot makes a release, it will automatically notify the `#release` channel in Vapor's team chat and include a link to the release on the merged PR.
## Maintainers
Each repo under the Vapor organization has at least one [volunteer maintainer.](maintainers.md) [vapor/vapor's](https://github.com/vapor/vapor) current list of maintainers is:
- [@MrLotU](https://github.com/MrLotU)
- [@Joannis](https://github.com/Joannis)
- [@0xTim](https://github.com/0xtim)
- [@gwynne](https://github.com/gwynne)
- [@siemensikkema](https://github.com/siemensikkema)
## Extras
Here are some bash functions to help you test Swift on Linux easily from macOS (you must have Docker installed).
### Swift Linux
```bash
# Starts docker-machine and exports env variables.
_docker_start() {
docker-machine start default
eval "$(docker-machine env default)"
}
alias docker-start='_docker_start'
# Executes /usr/bin/swift in a Swift 4.1 docker container
_swift_linux() {
_docker_start
docker run -it -v $PWD:/root/code -w /root/code norionomura/swift:swift-4.1-branch /usr/bin/swift $1
}
alias swift-linux='_swift_linux'
```
You can add these methods to your `~/.bash_profile`. Just run `source ~/.bash_profile` after or restart your terminal.
Once added, you can run the following to test Swift projects on both macOS and Linux.
```sh
swift test
swift-linux test
```
### Clean SPM
Add the following code to your bash profile to make cleaning SPM temporary files easy.
```bash
# Cleans out all temporary SPM files
_spm_clean() {
rm Package.resolved
rm -rf .build
rm -rf *.xcodeproj
rm -rf Packages
}
alias spm-clean='_spm_clean'
```
Once added, you can run `spm-clean`.
```sh
spm-clean
```
----------
Join us on Discord if you have any questions: [http://vapor.team](http://vapor.team).
— Thanks! 🙌
================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "daily"
allow:
- dependency-type: all
groups:
dependencies:
patterns:
- "*"
- package-ecosystem: "swift"
directory: "/"
schedule:
interval: "daily"
allow:
- dependency-type: all
groups:
dependencies:
patterns:
- "*"
================================================
FILE: .github/maintainers.md
================================================
# Being a Maintainer
If you would like to volunteer to be a maintainer for this repo, or any other repo under the Vapor org, submit
a PR to that repo adding your handle to the current maintainers list. The core team in addition to the existing
maintainers will determine if you are a good fit and either accept and merge the PR or provide steps that need
to be taken in order to be accepted.
## Perks
- Special "Maintainer" role in Discord with purple highlight.
- Write access to maintained repo (including approve and merge permissions).
- Invite to "Maintainers" team on GitHub.
- Opportunity to actively participate in deciding on new features in package.
- Access to private `#maintainers` channel in Discord.
- Name listed as maintainer in repo's `contributing.md`.
## Responsibilities
- Merge PRs.
- Tag releases.
- Resolve issues.
- Curate `contributing.md` and related docs.
## Rules
- Maintainers can review, approve, merge, and tag any `0.0.x`, or "patch", releases.
- Maintainers can review, approve, and tag any `0.x`, or "minor", releases. Core team is required to merge.
- Maintainers can close resolved or off-topic issues.
- PRs will be open for 24 hours before they are closed or merged to allow time for review by all maintainers.
================================================
FILE: .github/workflows/api-docs.yml
================================================
name: deploy-api-docs
on:
push:
branches:
- main
permissions:
contents: read
id-token: write
jobs:
build-and-deploy:
uses: vapor/api-docs/.github/workflows/build-and-deploy-docs-workflow.yml@main
secrets: inherit
with:
package_name: vapor
modules: Vapor,XCTVapor,VaporTesting
pathsToInvalidate: /vapor/* /xctvapor/* /vaportesting/*
================================================
FILE: .github/workflows/sponsors.yml
================================================
name: Generate Sponsors README
on: workflow_dispatch
permissions:
id-token: write
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Get token
id: get-token
uses: actions/create-github-app-token@v3
with:
app-id: ${{ vars.PENNY_APP_ID }}
private-key: ${{ secrets.PENNY_APP_PRIVATE_KEY }}
- name: Generate Sponsors
uses: JamesIves/github-sponsors-readme-action@v1
with:
token: ${{ steps.get-token.outputs.token }}
file: "README.md"
minimum: 500
maximum: 9900
marker: "backers"
organization: true
- name: Commit and create PR
uses: peter-evans/create-pull-request@v8
with:
token: ${{ steps.get-token.outputs.token }}
commit-message: "[skip ci] Update README with new sponsor"
committer: "Penny[bot] <360798+penny[bot]@vapor.codes>"
author: "Penny[bot] <360798+penny[bot]@vapor.codes>"
branch: update-sponsors
delete-branch: true
title: Update README with new sponsor
body: Update README adding new sponsor
labels: no-release-needed,sponsors
reviewers: 0xTim
================================================
FILE: .github/workflows/test.yml
================================================
name: test
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
on:
pull_request: { types: [opened, reopened, synchronize, ready_for_review] }
push: { branches: [ main ] }
jobs:
test-providers:
permissions:
contents: read
if: ${{ !(github.event.pull_request.draft || false) }}
strategy:
fail-fast: false
matrix:
provider:
- vapor/jwt
- vapor/fluent
- vapor/leaf
- vapor/queues
- vapor/apns
runs-on: ubuntu-latest
container: swift:6.2
steps:
- name: Check out Vapor
uses: actions/checkout@v6
with:
path: vapor
- name: Check out provider
uses: actions/checkout@v6
with:
repository: ${{ matrix.provider }}
path: provider
- name: Use local Vapor
run: swift package --package-path ./provider edit vapor --path ./vapor
- name: Run tests
run: swift test --package-path ./provider
unit-tests:
permissions:
contents: read
uses: vapor/ci/.github/workflows/run-unit-tests.yml@main
with:
with_release_mode_testing: true
with_tsan: false
with_musl: true
extra_musl_flags: --target Vapor
ios_xcodebuild_action: ''
ios_scheme_name: Vapor
secrets: inherit
submit-dependencies:
permissions:
contents: write
if: ${{ github.event_name == 'push' }}
uses: vapor/ci/.github/workflows/submit-deps.yml@main
secrets: inherit
test-parallel:
permissions:
contents: read
if: ${{ !(github.event.pull_request.draft || false) }}
runs-on: ubuntu-latest
container: swift:6.2
steps:
- name: Check out Vapor
uses: actions/checkout@v6
- name: Run tests
run: swift test --parallel
================================================
FILE: .gitignore
================================================
Packages
.build
.index-build
.DS_Store
*.xcodeproj
DerivedData/
Package.resolved
.swiftpm
Tests/LinuxMain.swift
.vscode
.bash_history
.cache/
# API Docs Generation
generate-package-api-docs.swift
public/
theme-settings.json
================================================
FILE: .spi.yml
================================================
version: 1
metadata:
authors: "Maintained by the Vapor Core Team with hundreds of contributions from the Vapor Community."
external_links:
documentation: "https://docs.vapor.codes"
================================================
FILE: AGENTS.md
================================================
# AI & Automated Agent Policy
This document outlines our policy on AI-assisted contributions and automated agents interacting with this project.
## Issues
### Good First Issues
Issues tagged with `good-first-issue` are there to allow new contributors to learn the processs of contributing to open source and Vapor with relatively easy tasks, to help grow open source. These issues are written by human maintainers and are intended to be solved by humans learning the codebase. **Do not use automated agents to claim, triage, or submit solutions to `good-first-issue` tickets.**
### Security Issues
Security vulnerabilities must be reported by humans through our responsible disclosure process. Automated scanning tools may identify potential issues, but all security reports must be reviewed, verified, and submitted by a human. Automated or AI-generated security reports will be closed without action. See our [organisation security policy](https://github.com/vapor/.github/blob/main/SECURITY.md) for disclosure instructions.
### General Issues
Issues must reflect genuine, human-identified bugs or feature requests. We will close issues that appear to be generated entirely by an automated agent without meaningful human review. Low-effort, bot-generated issues waste maintainer time and will not be tolerated.
## Pull Requests
We welcome AI-assisted contributions under the following conditions:
- **A human must author and submit the PR.** Using AI tools (Copilot, Claude, Cursor, etc.) to help write or refine code is fine, but a human must understand, review, and take responsibility for every change in the PR.
- **Fully automated PRs will be closed.** If a PR appears to have been generated and submitted by an agent with no meaningful human involvement, we will close it.
- **Contributors must be able to discuss their changes.** Maintainers may ask questions about implementation choices during review. You should be able to explain and defend your approach.
- **AI-generated code must meet the same standards as any other contribution.** It must pass CI, follow project conventions, include tests where appropriate, and be consistent with the existing codebase.
## How We Identify Automated Contributions
We look for patterns such as:
- Generic or templated issue descriptions with no project-specific context
- PRs submitted moments after an issue is opened
- Inability to respond to review feedback in a meaningful way
- Bulk submissions across multiple issues in a short timeframe
- Commit messages or PR descriptions that read like raw LLM output
Maintainers reserve the right to close any issue or PR that we believe violates this policy. These decisions are made at our discretion and are final.
## Why This Policy Exists
Open source thrives on human collaboration. Automated noise—whether from bots filing low-quality issues or agents submitting unreviewed code—drains maintainer energy and undermines the community we are building. This policy exists to protect contributor experience, maintain code quality, and keep Vapor a welcoming place for people who want to learn and contribute.
================================================
FILE: LICENSE
================================================
The MIT License (MIT)
Copyright (c) 2020 Qutheory, LLC
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: NOTICES.txt
================================================
//===----------------------------------------------------------------------===//
//
// This source file is part of the Vapor open source project
//
// Copyright (c) 2017-2021 Vapor project authors
// Licensed under MIT
//
// See LICENSE for license information
//
// SPDX-License-Identifier: MIT
//
//===----------------------------------------------------------------------===//
This product contains a derivation of the TestMetrics test implementation
from Swift Metrics.
* LICENSE (Apache License 2.0):
* https://www.apache.org/licenses/LICENSE-2.0
* HOMEPAGE:
* https://github.com/apple/swift-metrics
This product contains an implementation of AsyncLazySequence taken from Async
HTTP Client
* LICENSE (Apache License 2.0):
* https://www.apache.org/licenses/LICENSE-2.0
* HOMEPAGE:
* https://github.com/swift-server/async-http-client
================================================
FILE: Package.swift
================================================
// swift-tools-version:6.0
import PackageDescription
let package = Package(
name: "vapor",
platforms: [
.macOS(.v10_15),
.iOS(.v13),
.tvOS(.v13),
.watchOS(.v6)
],
products: [
.library(name: "Vapor", targets: ["Vapor"]),
.library(name: "XCTVapor", targets: ["XCTVapor"]),
.library(name: "VaporTesting", targets: ["VaporTesting"]),
],
dependencies: [
// HTTP client library built on SwiftNIO
.package(url: "https://github.com/swift-server/async-http-client.git", from: "1.24.0"),
// Sugary extensions for the SwiftNIO library
.package(url: "https://github.com/vapor/async-kit.git", from: "1.15.0"),
// 💻 APIs for creating interactive CLI tools.
.package(url: "https://github.com/vapor/console-kit.git", from: "4.14.0"),
// 🔑 Hashing (SHA2, HMAC), encryption (AES), public-key (RSA), and random data generation.
.package(url: "https://github.com/apple/swift-crypto.git", "1.0.0" ..< "5.0.0"),
// 🚍 High-performance trie-node router.
.package(url: "https://github.com/vapor/routing-kit.git", from: "4.9.0"),
// Event-driven network application framework for high performance protocol servers & clients, non-blocking.
.package(url: "https://github.com/apple/swift-nio.git", from: "2.81.0"),
// Bindings to OpenSSL-compatible libraries for TLS support in SwiftNIO
.package(url: "https://github.com/apple/swift-nio-ssl.git", from: "2.34.0"),
// HTTP/2 support for SwiftNIO
.package(url: "https://github.com/apple/swift-nio-http2.git", from: "1.28.0"),
// Useful code around SwiftNIO.
.package(url: "https://github.com/apple/swift-nio-extras.git", from: "1.24.0"),
// Swift logging API
.package(url: "https://github.com/apple/swift-log.git", from: "1.8.0"),
// Swift metrics API
.package(url: "https://github.com/apple/swift-metrics.git", from: "2.5.0"),
// Swift tracing API
.package(url: "https://github.com/apple/swift-distributed-tracing.git", from: "1.1.0"),
// Swift service context
.package(url: "https://github.com/apple/swift-service-context.git", from: "1.0.0"),
// Swift collection algorithms
.package(url: "https://github.com/apple/swift-algorithms.git", from: "1.0.0"),
// WebSocket client library built on SwiftNIO
.package(url: "https://github.com/vapor/websocket-kit.git", from: "2.13.0"),
// MultipartKit, Multipart encoding and decoding
.package(url: "https://github.com/vapor/multipart-kit.git", from: "4.2.1"),
// Low-level atomic operations
.package(url: "https://github.com/apple/swift-atomics.git", from: "1.1.0"),
// X509 certificate types for the Swift ecosystem
.package(url: "https://github.com/apple/swift-certificates.git", from: "1.14.0"),
// Work with certificate encoding schemes
.package(url: "https://github.com/apple/swift-asn1.git", from: "1.0.0")
],
targets: [
// C helpers
.target(name: "CVaporBcrypt"),
// Vapor
.target(
name: "Vapor",
dependencies: [
.product(name: "AsyncHTTPClient", package: "async-http-client"),
.product(name: "AsyncKit", package: "async-kit"),
.target(name: "CVaporBcrypt"),
.product(name: "ConsoleKit", package: "console-kit"),
.product(name: "Logging", package: "swift-log"),
.product(name: "Metrics", package: "swift-metrics"),
.product(name: "Tracing", package: "swift-distributed-tracing"),
.product(name: "ServiceContextModule", package: "swift-service-context"),
.product(name: "NIO", package: "swift-nio"),
.product(name: "NIOConcurrencyHelpers", package: "swift-nio"),
.product(name: "NIOCore", package: "swift-nio"),
.product(name: "NIOExtras", package: "swift-nio-extras"),
.product(name: "NIOFoundationCompat", package: "swift-nio"),
.product(name: "NIOHTTPCompression", package: "swift-nio-extras"),
.product(name: "NIOHTTP1", package: "swift-nio"),
.product(name: "NIOHTTP2", package: "swift-nio-http2"),
.product(name: "NIOSSL", package: "swift-nio-ssl"),
.product(name: "NIOWebSocket", package: "swift-nio"),
.product(name: "Crypto", package: "swift-crypto"),
.product(name: "Algorithms", package: "swift-algorithms"),
.product(name: "RoutingKit", package: "routing-kit"),
.product(name: "WebSocketKit", package: "websocket-kit"),
.product(name: "MultipartKit", package: "multipart-kit"),
.product(name: "Atomics", package: "swift-atomics"),
.product(name: "_NIOFileSystem", package: "swift-nio"),
.product(name: "_NIOFileSystemFoundationCompat", package: "swift-nio"),
.product(name: "X509", package: "swift-certificates"),
.product(name: "SwiftASN1", package: "swift-asn1"),
],
swiftSettings: swiftSettings
),
// Development
.executableTarget(
name: "Development",
dependencies: [
.target(name: "Vapor"),
],
resources: [.copy("Resources")],
swiftSettings: swiftSettings
),
// Testing
.target(
name: "VaporTestUtils",
dependencies: [
.target(name: "Vapor"),
],
swiftSettings: swiftSettings
),
.target(
name: "VaporTesting",
dependencies: [
.target(name: "VaporTestUtils"),
.target(name: "Vapor"),
],
swiftSettings: swiftSettings
),
.target(
name: "XCTVapor",
dependencies: [
.target(name: "VaporTestUtils"),
.target(name: "Vapor"),
],
swiftSettings: swiftSettings
),
.testTarget(
name: "VaporTests",
dependencies: [
.product(name: "NIOTestUtils", package: "swift-nio"),
.product(name: "SwiftASN1", package: "swift-asn1"),
.target(name: "XCTVapor"),
.target(name: "VaporTesting"),
.target(name: "Vapor"),
],
resources: [
.copy("Utilities/foo.txt"),
.copy("Utilities/index.html"),
.copy("Utilities/SubUtilities/"),
.copy("Utilities/foo bar.html"),
.copy("Utilities/test.env"),
.copy("Utilities/my-secret-env-content"),
.copy("Utilities/expired.crt"),
.copy("Utilities/expired.key"),
.copy("Utilities/long-test-file.txt"),
],
swiftSettings: swiftSettings
),
]
)
var swiftSettings: [SwiftSetting] { [
//.enableUpcomingFeature("ExistentialAny"),
//.enableUpcomingFeature("InternalImportsByDefault"),
.enableUpcomingFeature("MemberImportVisibility"),
.enableUpcomingFeature("InferIsolatedConformances"),
//.enableUpcomingFeature("NonisolatedNonsendingByDefault"),
.enableUpcomingFeature("ImmutableWeakCaptures"),
] }
================================================
FILE: README.md
================================================
<a href="https://discord.gg/vapor">

</a>
<p align="center">
<a href="https://docs.vapor.codes/4.0/">
<img src="https://design.vapor.codes/images/readthedocs.svg" alt="Documentation">
</a>
<a href="https://discord.gg/vapor">
<img src="https://design.vapor.codes/images/discordchat.svg" alt="Team Chat">
</a>
<a href="LICENSE">
<img src="https://design.vapor.codes/images/mitlicense.svg" alt="MIT License">
</a>
<a href="https://github.com/vapor/vapor/actions/workflows/test.yml">
<img src="https://img.shields.io/github/actions/workflow/status/vapor/vapor/test.yml?event=push&style=plastic&logo=github&label=tests&logoColor=%23ccc" alt="Continuous Integration">
</a>
<a href="https://codecov.io/gh/vapor/vapor">
<img src="https://img.shields.io/codecov/c/github/vapor/vapor?style=plastic&logo=codecov&label=codecov" alt="Code Coverage">
</a>
<a href="https://swift.org">
<img src="https://design.vapor.codes/images/swift60up.svg" alt="Swift 6.0+">
</a>
<a href="https://hachyderm.io/@codevapor">
<img src="https://img.shields.io/badge/%20-@codevapor-6364f6.svg?style=plastic&logo=mastodon&labelColor=gray&logoColor=%239394ff" alt="Mastodon">
</a>
</p>
<br>
Vapor is an HTTP web framework for Swift. It provides a beautifully expressive and easy-to-use foundation for your next website, API, or cloud project.
Take a look at some of the [awesome stuff](https://github.com/vapor-community/awesome-vapor) created with Vapor.
### 💧 Community
Join the welcoming community of fellow Vapor developers on [Discord](https://vapor.team).
### 🚀 Contributing
To contribute a **feature or idea** to Vapor, [create an issue](https://github.com/vapor/vapor/issues/new) explaining your idea or bring it up on [Discord](https://vapor.team).
If you find a **bug**, please [create an issue](https://github.com/vapor/vapor/issues/new).
If you find a **security vulnerability**, please contact [security@vapor.codes](mailto:security@vapor.codes) as soon as possible.
### 💛 Sponsors
Support Vapor's development by [becoming a sponsor](https://github.com/sponsors/vapor).
<a href="https://www.brokenhands.io">
<img src="https://user-images.githubusercontent.com/9938337/137103192-21f99099-6aaa-4cc1-a1a7-21ee767a72d1.png" height="100px" alt="Broken Hands">
</a>
<a href="https://www.emergetools.com">
<img src="https://user-images.githubusercontent.com/9938337/265658253-cb37d2fa-3251-497f-8eeb-ba7c95af373b.svg" height="100px" alt="Emerge Tools">
</a>
<a href="https://github.com/MrLotU">
<img src="https://user-images.githubusercontent.com/1342803/79599312-426a8580-80b3-11ea-89b3-8b2722485e37.png" height="100px" alt="Jari">
</a>
<a href="https://github.com/DonutDane">
<img src="https://user-images.githubusercontent.com/9938337/265657642-6b6b1705-9611-4547-8e2f-a3773fda87c6.png" height="100px" alt="Donut Dane">
</a>
<a href="https://macstadium.com">
<img src="https://uploads-ssl.webflow.com/5ac3c046c82724970fc60918/5c019d917bba312af7553b49_MacStadium-developerlogo.png" height="100px" alt="MacStadium">
</a>
### 💚 Backers
Support Vapor's development by [becoming a backer](https://github.com/sponsors/vapor).
<!-- backers --><a href="https://github.com/slashmo"><img src="https://github.com/slashmo.png" width="60px" alt="Moritz Lang" /></a><a href="https://github.com/maartene"><img src="https://github.com/maartene.png" width="60px" alt="Maarten Engels" /></a><a href="https://github.com/tkrajacic"><img src="https://github.com/tkrajacic.png" width="60px" alt="Thomas Krajacic" /></a><a href="https://github.com/jessetipton"><img src="https://github.com/jessetipton.png" width="60px" alt="Jesse Tipton" /></a><a href="https://github.com/steve-h"><img src="https://github.com/steve-h.png" width="60px" alt="Steve Hume" /></a><a href="https://github.com/mikkelu"><img src="https://github.com/mikkelu.png" width="60px" alt="Mikkel Ulstrup" /></a><a href="https://github.com/g-Off"><img src="https://github.com/g-Off.png" width="60px" alt="Geoffrey Foster" /></a><a href="https://github.com/PSchmiedmayer"><img src="https://github.com/PSchmiedmayer.png" width="60px" alt="Paul Schmiedmayer" /></a><a href="https://github.com/ScottRobbins"><img src="https://github.com/ScottRobbins.png" width="60px" alt="Scott Robbins" /></a><a href="https://github.com/finestructure"><img src="https://github.com/finestructure.png" width="60px" alt="Sven A. Schmidt" /></a><a href="https://github.com/SpencerCurtis"><img src="https://github.com/SpencerCurtis.png" width="60px" alt="Spencer Curtis" /></a><a href="https://github.com/rausnitz"><img src="https://github.com/rausnitz.png" width="60px" alt="Zach Rausnitz" /></a><a href="https://github.com/masterofinsanity"><img src="https://github.com/masterofinsanity.png" width="60px" alt="Tim „Timinator“ Kretzschmar" /></a><a href="https://github.com/klaas"><img src="https://github.com/klaas.png" width="60px" alt="Klaas" /></a><a href="https://github.com/Andrewangeta"><img src="https://github.com/Andrewangeta.png" width="60px" alt="Andrew Edwards" /></a><a href="https://github.com/addli"><img src="https://github.com/addli.png" width="60px" alt="+Li, Inc." /></a><a href="https://github.com/doozMen"><img src="https://github.com/doozMen.png" width="60px" alt="Stijn Willems" /></a><a href="https://github.com/bitwit"><img src="https://github.com/bitwit.png" width="60px" alt="Kyle Newsome" /></a><a href="https://github.com/viaaurelia"><img src="https://github.com/viaaurelia.png" width="60px" alt="Via Aurelia Solutions" /></a><a href="https://github.com/kkiermasz"><img src="https://github.com/kkiermasz.png" width="60px" alt="Jakub Kiermasz" /></a><a href="https://github.com/bdrelling"><img src="https://github.com/bdrelling.png" width="60px" alt="Brian Drelling" /></a><a href="https://github.com/mayondigital"><img src="https://github.com/mayondigital.png" width="60px" alt="" /></a><a href="https://github.com/mattesmohr"><img src="https://github.com/mattesmohr.png" width="60px" alt="Mattes Mohr" /></a><a href="https://github.com/scibidoo"><img src="https://github.com/scibidoo.png" width="60px" alt="Jamie" /></a><a href="https://github.com/GalenRhodes"><img src="https://github.com/GalenRhodes.png" width="60px" alt="Galen Rhodes" /></a><a href="https://github.com/litmaps"><img src="https://github.com/litmaps.png" width="60px" alt="Litmaps" /></a><a href="https://github.com/davdroman"><img src="https://github.com/davdroman.png" width="60px" alt="David Roman" /></a><a href="https://github.com/Strobocop"><img src="https://github.com/Strobocop.png" width="60px" alt="Brian Strobach" /></a><a href="https://github.com/kishikawakatsumi"><img src="https://github.com/kishikawakatsumi.png" width="60px" alt="Kishikawa Katsumi" /></a><a href="https://github.com/mkll"><img src="https://github.com/mkll.png" width="60px" alt="Alex Sherbakov" /></a><a href="https://github.com/getsidetrack"><img src="https://github.com/getsidetrack.png" width="60px" alt="Sidetrack" /></a><a href="https://github.com/GregKarpati"><img src="https://github.com/GregKarpati.png" width="60px" alt="Greg Karpati" /></a><a href="https://github.com/fananek"><img src="https://github.com/fananek.png" width="60px" alt="František Mikš" /></a><a href="https://github.com/jagreenwood"><img src="https://github.com/jagreenwood.png" width="60px" alt="Jeremy Greenwood" /></a><a href="https://github.com/rayfix"><img src="https://github.com/rayfix.png" width="60px" alt="Ray Fix" /></a><a href="https://github.com/micomiloloza"><img src="https://github.com/micomiloloza.png" width="60px" alt="Mićo Miloloža" /></a><a href="https://github.com/awamser"><img src="https://github.com/awamser.png" width="60px" alt="Alan" /></a><a href="https://github.com/Suboptimierer"><img src="https://github.com/Suboptimierer.png" width="60px" alt="Jonas Sannewald" /></a><a href="https://github.com/TapEnvy-us-LLC"><img src="https://github.com/TapEnvy-us-LLC.png" width="60px" alt="TapEnvy.us, LLC" /></a><a href="https://github.com/JawadHF"><img src="https://github.com/JawadHF.png" width="60px" alt="Jawad" /></a><a href="https://github.com/PARAIPAN9"><img src="https://github.com/PARAIPAN9.png" width="60px" alt="PARAIPAN SORIN" /></a><a href="https://github.com/KalynDavis"><img src="https://github.com/KalynDavis.png" width="60px" alt="Kalyn Davis" /></a><a href="https://github.com/stevapple"><img src="https://github.com/stevapple.png" width="60px" alt="YR Chen" /></a><a href="https://github.com/roncuevas"><img src="https://github.com/roncuevas.png" width="60px" alt="Aarón Martínez Cuevas" /></a><!-- backers -->
<a href="https://opencollective.com/vapor/backer/0/website" target="_blank"><img src="https://opencollective.com/vapor/backer/0/avatar.svg"></a>
<a href="https://opencollective.com/vapor/backer/1/website" target="_blank"><img src="https://opencollective.com/vapor/backer/1/avatar.svg"></a>
<a href="https://opencollective.com/vapor/backer/2/website" target="_blank"><img src="https://opencollective.com/vapor/backer/2/avatar.svg"></a>
<a href="https://opencollective.com/vapor/backer/3/website" target="_blank"><img src="https://opencollective.com/vapor/backer/3/avatar.svg"></a>
<a href="https://opencollective.com/vapor/backer/4/website" target="_blank"><img src="https://opencollective.com/vapor/backer/4/avatar.svg"></a>
<a href="https://opencollective.com/vapor/backer/5/website" target="_blank"><img src="https://opencollective.com/vapor/backer/5/avatar.svg"></a>
<a href="https://opencollective.com/vapor/backer/6/website" target="_blank"><img src="https://opencollective.com/vapor/backer/6/avatar.svg"></a>
<a href="https://opencollective.com/vapor/backer/7/website" target="_blank"><img src="https://opencollective.com/vapor/backer/7/avatar.svg"></a>
<a href="https://opencollective.com/vapor/backer/8/website" target="_blank"><img src="https://opencollective.com/vapor/backer/8/avatar.svg"></a>
<a href="https://opencollective.com/vapor/backer/9/website" target="_blank"><img src="https://opencollective.com/vapor/backer/9/avatar.svg"></a>
<a href="https://opencollective.com/vapor/backer/10/website" target="_blank"><img src="https://opencollective.com/vapor/backer/10/avatar.svg"></a>
<a href="https://opencollective.com/vapor/backer/11/website" target="_blank"><img src="https://opencollective.com/vapor/backer/11/avatar.svg"></a>
<a href="https://opencollective.com/vapor/backer/12/website" target="_blank"><img src="https://opencollective.com/vapor/backer/12/avatar.svg"></a>
<a href="https://opencollective.com/vapor/backer/13/website" target="_blank"><img src="https://opencollective.com/vapor/backer/13/avatar.svg"></a>
<a href="https://opencollective.com/vapor/backer/14/website" target="_blank"><img src="https://opencollective.com/vapor/backer/14/avatar.svg"></a>
<a href="https://opencollective.com/vapor/backer/15/website" target="_blank"><img src="https://opencollective.com/vapor/backer/15/avatar.svg"></a>
<a href="https://opencollective.com/vapor/backer/16/website" target="_blank"><img src="https://opencollective.com/vapor/backer/16/avatar.svg"></a>
<a href="https://opencollective.com/vapor/backer/17/website" target="_blank"><img src="https://opencollective.com/vapor/backer/17/avatar.svg"></a>
<a href="https://opencollective.com/vapor/backer/18/website" target="_blank"><img src="https://opencollective.com/vapor/backer/18/avatar.svg"></a>
<a href="https://opencollective.com/vapor/backer/19/website" target="_blank"><img src="https://opencollective.com/vapor/backer/19/avatar.svg"></a>
<a href="https://opencollective.com/vapor/backer/20/website" target="_blank"><img src="https://opencollective.com/vapor/backer/20/avatar.svg"></a>
<a href="https://opencollective.com/vapor/backer/21/website" target="_blank"><img src="https://opencollective.com/vapor/backer/21/avatar.svg"></a>
<a href="https://opencollective.com/vapor/backer/22/website" target="_blank"><img src="https://opencollective.com/vapor/backer/22/avatar.svg"></a>
<a href="https://opencollective.com/vapor/backer/23/website" target="_blank"><img src="https://opencollective.com/vapor/backer/23/avatar.svg"></a>
<a href="https://opencollective.com/vapor/backer/24/website" target="_blank"><img src="https://opencollective.com/vapor/backer/24/avatar.svg"></a>
<a href="https://opencollective.com/vapor/backer/25/website" target="_blank"><img src="https://opencollective.com/vapor/backer/25/avatar.svg"></a>
<a href="https://opencollective.com/vapor/backer/26/website" target="_blank"><img src="https://opencollective.com/vapor/backer/26/avatar.svg"></a>
<a href="https://opencollective.com/vapor/backer/27/website" target="_blank"><img src="https://opencollective.com/vapor/backer/27/avatar.svg"></a>
<a href="https://opencollective.com/vapor/backer/28/website" target="_blank"><img src="https://opencollective.com/vapor/backer/28/avatar.svg"></a>
<a href="https://opencollective.com/vapor/backer/29/website" target="_blank"><img src="https://opencollective.com/vapor/backer/29/avatar.svg"></a>
<a href="https://opencollective.com/vapor/backer/30/website" target="_blank"><img src="https://opencollective.com/vapor/backer/30/avatar.svg"></a>
<a href="https://opencollective.com/vapor/backer/31/website" target="_blank"><img src="https://opencollective.com/vapor/backer/31/avatar.svg"></a>
<a href="https://opencollective.com/vapor/backer/32/website" target="_blank"><img src="https://opencollective.com/vapor/backer/32/avatar.svg"></a>
<a href="https://opencollective.com/vapor/backer/33/website" target="_blank"><img src="https://opencollective.com/vapor/backer/33/avatar.svg"></a>
<a href="https://opencollective.com/vapor/backer/34/website" target="_blank"><img src="https://opencollective.com/vapor/backer/34/avatar.svg"></a>
<a href="https://opencollective.com/vapor/backer/35/website" target="_blank"><img src="https://opencollective.com/vapor/backer/35/avatar.svg"></a>
<a href="https://opencollective.com/vapor/backer/36/website" target="_blank"><img src="https://opencollective.com/vapor/backer/36/avatar.svg"></a>
<a href="https://opencollective.com/vapor/backer/37/website" target="_blank"><img src="https://opencollective.com/vapor/backer/37/avatar.svg"></a>
<a href="https://opencollective.com/vapor/backer/38/website" target="_blank"><img src="https://opencollective.com/vapor/backer/38/avatar.svg"></a>
<a href="https://opencollective.com/vapor/backer/39/website" target="_blank"><img src="https://opencollective.com/vapor/backer/39/avatar.svg"></a>
<a href="https://opencollective.com/vapor/backer/40/website" target="_blank"><img src="https://opencollective.com/vapor/backer/40/avatar.svg"></a>
<a href="https://opencollective.com/vapor/backer/41/website" target="_blank"><img src="https://opencollective.com/vapor/backer/41/avatar.svg"></a>
<a href="https://opencollective.com/vapor/backer/42/website" target="_blank"><img src="https://opencollective.com/vapor/backer/42/avatar.svg"></a>
<a href="https://opencollective.com/vapor/backer/43/website" target="_blank"><img src="https://opencollective.com/vapor/backer/43/avatar.svg"></a>
<a href="https://opencollective.com/vapor/backer/44/website" target="_blank"><img src="https://opencollective.com/vapor/backer/44/avatar.svg"></a>
<a href="https://opencollective.com/vapor/backer/45/website" target="_blank"><img src="https://opencollective.com/vapor/backer/45/avatar.svg"></a>
<a href="https://opencollective.com/vapor/backer/46/website" target="_blank"><img src="https://opencollective.com/vapor/backer/46/avatar.svg"></a>
<a href="https://opencollective.com/vapor/backer/47/website" target="_blank"><img src="https://opencollective.com/vapor/backer/47/avatar.svg"></a>
<a href="https://opencollective.com/vapor/backer/48/website" target="_blank"><img src="https://opencollective.com/vapor/backer/48/avatar.svg"></a>
================================================
FILE: Sources/CVaporBcrypt/bcrypt.c
================================================
/* $OpenBSD: bcrypt.c,v 1.55 2015/09/13 15:33:48 guenther Exp $ */
/*
* Copyright (c) 2014 Ted Unangst <tedu@openbsd.org>
* Copyright (c) 1997 Niels Provos <provos@umich.edu>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/* This password hashing algorithm was designed by David Mazieres
* <dm@lcs.mit.edu> and works as follows:
*
* 1. state := InitState ()
* 2. state := ExpandKey (state, salt, password)
* 3. REPEAT rounds:
* state := ExpandKey (state, 0, password)
* state := ExpandKey (state, 0, salt)
* 4. ctext := "OrpheanBeholderScryDoubt"
* 5. REPEAT 64:
* ctext := Encrypt_ECB (state, ctext);
* 6. RETURN Concatenate (salt, ctext);
*
*/
#include <sys/types.h>
#include "blf.h"
#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "bcrypt.h"
char *bcrypt_gensalt(u_int8_t);
static int decode_base64(u_int8_t *, size_t, const char *);
/*
* the core bcrypt function
*/
int
vapor_bcrypt_hashpass(const char *key, const char *salt, char *encrypted,
size_t encryptedlen)
{
blf_ctx state;
u_int32_t rounds, i, k;
u_int16_t j;
size_t key_len;
u_int8_t salt_len, logr, minor;
u_int8_t ciphertext[4 * BCRYPT_WORDS] = "OrpheanBeholderScryDoubt";
u_int8_t csalt[BCRYPT_MAXSALT];
u_int32_t cdata[BCRYPT_WORDS];
if (encryptedlen < BCRYPT_HASHSPACE)
goto inval;
/* Check and discard "$" identifier */
if (salt[0] != '$')
goto inval;
salt += 1;
if (salt[0] != BCRYPT_VERSION)
goto inval;
/* Check for minor versions */
switch ((minor = salt[1])) {
case 'a':
key_len = (u_int8_t)(strlen(key) + 1);
break;
case 'b':
/* strlen() returns a size_t, but the function calls
* below result in implicit casts to a narrower integer
* type, so cap key_len at the actual maximum supported
* length here to avoid integer wraparound */
key_len = strlen(key);
if (key_len > 72)
key_len = 72;
key_len++; /* include the NUL */
break;
default:
goto inval;
}
if (salt[2] != '$')
goto inval;
/* Discard version + "$" identifier */
salt += 3;
/* Check and parse num rounds */
if (!isdigit((unsigned char)salt[0]) ||
!isdigit((unsigned char)salt[1]) || salt[2] != '$')
goto inval;
logr = (salt[1] - '0') + ((salt[0] - '0') * 10);
if (logr < BCRYPT_MINLOGROUNDS || logr > 31)
goto inval;
/* Computer power doesn't increase linearly, 2^x should be fine */
rounds = 1U << logr;
/* Discard num rounds + "$" identifier */
salt += 3;
if (strlen(salt) * 3 / 4 < BCRYPT_MAXSALT)
goto inval;
/* We dont want the base64 salt but the raw data */
if (decode_base64(csalt, BCRYPT_MAXSALT, salt))
goto inval;
salt_len = BCRYPT_MAXSALT;
/* Setting up S-Boxes and Subkeys */
Vapor_Blowfish_initstate(&state);
Vapor_Blowfish_expandstate(&state, csalt, salt_len,
(u_int8_t *) key, key_len);
for (k = 0; k < rounds; k++) {
Vapor_Blowfish_expand0state(&state, (u_int8_t *) key, key_len);
Vapor_Blowfish_expand0state(&state, csalt, salt_len);
}
/* This can be precomputed later */
j = 0;
for (i = 0; i < BCRYPT_WORDS; i++)
cdata[i] = Vapor_Blowfish_stream2word(ciphertext, 4 * BCRYPT_WORDS, &j);
/* Now do the encryption */
for (k = 0; k < 64; k++)
vapor_blf_enc(&state, cdata, BCRYPT_WORDS / 2);
for (i = 0; i < BCRYPT_WORDS; i++) {
ciphertext[4 * i + 3] = cdata[i] & 0xff;
cdata[i] = cdata[i] >> 8;
ciphertext[4 * i + 2] = cdata[i] & 0xff;
cdata[i] = cdata[i] >> 8;
ciphertext[4 * i + 1] = cdata[i] & 0xff;
cdata[i] = cdata[i] >> 8;
ciphertext[4 * i + 0] = cdata[i] & 0xff;
}
snprintf(encrypted, 8, "$2%c$%2.2u$", minor, logr);
vapor_encode_base64(encrypted + 7, csalt, BCRYPT_MAXSALT);
vapor_encode_base64(encrypted + 7 + 22, ciphertext, 4 * BCRYPT_WORDS - 1);
explicit_bzero(&state, sizeof(state));
explicit_bzero(ciphertext, sizeof(ciphertext));
explicit_bzero(csalt, sizeof(csalt));
explicit_bzero(cdata, sizeof(cdata));
return 0;
inval:
errno = EINVAL;
return -1;
}
/*
* internal utilities
*/
static const u_int8_t Base64Code[] =
"./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
static const u_int8_t index_64[128] = {
255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 0, 1, 54, 55,
56, 57, 58, 59, 60, 61, 62, 63, 255, 255,
255, 255, 255, 255, 255, 2, 3, 4, 5, 6,
7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27,
255, 255, 255, 255, 255, 255, 28, 29, 30,
31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
41, 42, 43, 44, 45, 46, 47, 48, 49, 50,
51, 52, 53, 255, 255, 255, 255, 255
};
#define CHAR64(c) ( (c) > 127 ? 255 : index_64[(c)])
/*
* read buflen (after decoding) bytes of data from b64data
*/
static int
decode_base64(u_int8_t *buffer, size_t len, const char *b64data)
{
u_int8_t *bp = buffer;
const u_int8_t *p = (u_int8_t *)b64data;
u_int8_t c1, c2, c3, c4;
while (bp < buffer + len) {
c1 = CHAR64(*p);
/* Invalid data */
if (c1 == 255)
return -1;
c2 = CHAR64(*(p + 1));
if (c2 == 255)
return -1;
*bp++ = (c1 << 2) | ((c2 & 0x30) >> 4);
if (bp >= buffer + len)
break;
c3 = CHAR64(*(p + 2));
if (c3 == 255)
return -1;
*bp++ = ((c2 & 0x0f) << 4) | ((c3 & 0x3c) >> 2);
if (bp >= buffer + len)
break;
c4 = CHAR64(*(p + 3));
if (c4 == 255)
return -1;
*bp++ = ((c3 & 0x03) << 6) | c4;
p += 4;
}
return 0;
}
/*
* Turn len bytes of data into base64 encoded data.
* This works without = padding.
*/
int
vapor_encode_base64(char *b64buffer, const u_int8_t *data, size_t len)
{
u_int8_t *bp = (u_int8_t *)b64buffer;
const u_int8_t *p = data;
u_int8_t c1, c2;
while (p < data + len) {
c1 = *p++;
*bp++ = Base64Code[(c1 >> 2)];
c1 = (c1 & 0x03) << 4;
if (p >= data + len) {
*bp++ = Base64Code[c1];
break;
}
c2 = *p++;
c1 |= (c2 >> 4) & 0x0f;
*bp++ = Base64Code[c1];
c1 = (c2 & 0x0f) << 2;
if (p >= data + len) {
*bp++ = Base64Code[c1];
break;
}
c2 = *p++;
c1 |= (c2 >> 6) & 0x03;
*bp++ = Base64Code[c1];
*bp++ = Base64Code[c2 & 0x3f];
}
*bp = '\0';
return 0;
}
================================================
FILE: Sources/CVaporBcrypt/bcrypt.h
================================================
#include <sys/types.h>
#include <string.h>
#include <stdio.h>
#if defined(_WIN32)
typedef unsigned char uint8_t;
typedef uint8_t u_int8_t;
typedef unsigned short uint16_t;
typedef uint16_t u_int16_t;
typedef unsigned uint32_t;
typedef uint32_t u_int32_t;
typedef unsigned long long uint64_t;
typedef uint64_t u_int64_t;
#define snprintf _snprintf
#define __attribute__(unused)
#else
#include <stdint.h>
#endif
#define explicit_bzero(s,n) memset(s, 0, n)
#define DEF_WEAK(f)
/* This implementation is adaptable to current computing power.
* You can have up to 2^31 rounds which should be enough for some
* time to come.
*/
#define BCRYPT_VERSION '2'
#define BCRYPT_MAXSALT 16 /* Precomputation is just so nice */
#define BCRYPT_WORDS 6 /* Ciphertext words */
#define BCRYPT_MINLOGROUNDS 4 /* we have log2(rounds) in salt */
#define BCRYPT_SALTSPACE (7 + (BCRYPT_MAXSALT * 4 + 2) / 3 + 1)
#define BCRYPT_HASHSPACE 61
int vapor_bcrypt_hashpass(const char *key, const char *salt, char *encrypted, size_t encryptedlen);
int vapor_encode_base64(char *, const u_int8_t *, size_t);
================================================
FILE: Sources/CVaporBcrypt/blf.c
================================================
/* $OpenBSD: blf.c,v 1.7 2007/11/26 09:28:34 martynas Exp $ */
/*
* Blowfish block cipher for OpenBSD
* Copyright 1997 Niels Provos <provos@physnet.uni-hamburg.de>
* All rights reserved.
*
* Implementation advice by David Mazieres <dm@lcs.mit.edu>.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by Niels Provos.
* 4. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
* This code is derived from section 14.3 and the given source
* in section V of Applied Cryptography, second edition.
* Blowfish is an unpatented fast block cipher designed by
* Bruce Schneier.
*/
#include <sys/types.h>
#include "blf.h"
#undef inline
#ifdef __GNUC__
#define inline __inline
#else /* !__GNUC__ */
#define inline
#endif /* !__GNUC__ */
/* Function for Feistel Networks */
#define F(s, x) ((((s)[ (((x)>>24)&0xFF)] \
+ (s)[0x100 + (((x)>>16)&0xFF)]) \
^ (s)[0x200 + (((x)>> 8)&0xFF)]) \
+ (s)[0x300 + ( (x) &0xFF)])
#define BLFRND(s,p,i,j,n) (i ^= F(s,j) ^ (p)[n])
void
Vapor_Blowfish_encipher(blf_ctx *c, u_int32_t *x)
{
u_int32_t Xl;
u_int32_t Xr;
u_int32_t *s = c->S[0];
u_int32_t *p = c->P;
Xl = x[0];
Xr = x[1];
Xl ^= p[0];
BLFRND(s, p, Xr, Xl, 1); BLFRND(s, p, Xl, Xr, 2);
BLFRND(s, p, Xr, Xl, 3); BLFRND(s, p, Xl, Xr, 4);
BLFRND(s, p, Xr, Xl, 5); BLFRND(s, p, Xl, Xr, 6);
BLFRND(s, p, Xr, Xl, 7); BLFRND(s, p, Xl, Xr, 8);
BLFRND(s, p, Xr, Xl, 9); BLFRND(s, p, Xl, Xr, 10);
BLFRND(s, p, Xr, Xl, 11); BLFRND(s, p, Xl, Xr, 12);
BLFRND(s, p, Xr, Xl, 13); BLFRND(s, p, Xl, Xr, 14);
BLFRND(s, p, Xr, Xl, 15); BLFRND(s, p, Xl, Xr, 16);
x[0] = Xr ^ p[17];
x[1] = Xl;
}
void
Vapor_Blowfish_decipher(blf_ctx *c, u_int32_t *x)
{
u_int32_t Xl;
u_int32_t Xr;
u_int32_t *s = c->S[0];
u_int32_t *p = c->P;
Xl = x[0];
Xr = x[1];
Xl ^= p[17];
BLFRND(s, p, Xr, Xl, 16); BLFRND(s, p, Xl, Xr, 15);
BLFRND(s, p, Xr, Xl, 14); BLFRND(s, p, Xl, Xr, 13);
BLFRND(s, p, Xr, Xl, 12); BLFRND(s, p, Xl, Xr, 11);
BLFRND(s, p, Xr, Xl, 10); BLFRND(s, p, Xl, Xr, 9);
BLFRND(s, p, Xr, Xl, 8); BLFRND(s, p, Xl, Xr, 7);
BLFRND(s, p, Xr, Xl, 6); BLFRND(s, p, Xl, Xr, 5);
BLFRND(s, p, Xr, Xl, 4); BLFRND(s, p, Xl, Xr, 3);
BLFRND(s, p, Xr, Xl, 2); BLFRND(s, p, Xl, Xr, 1);
x[0] = Xr ^ p[0];
x[1] = Xl;
}
void
Vapor_Blowfish_initstate(blf_ctx *c)
{
/* P-box and S-box tables initialized with digits of Pi */
static const blf_ctx initstate =
{ {
{
0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7,
0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99,
0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16,
0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e,
0x0d95748f, 0x728eb658, 0x718bcd58, 0x82154aee,
0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013,
0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef,
0x8e79dcb0, 0x603a180e, 0x6c9e0e8b, 0xb01e8a3e,
0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60,
0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440,
0x55ca396a, 0x2aab10b6, 0xb4cc5c34, 0x1141e8ce,
0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a,
0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e,
0xafd6ba33, 0x6c24cf5c, 0x7a325381, 0x28958677,
0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193,
0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032,
0xef845d5d, 0xe98575b1, 0xdc262302, 0xeb651b88,
0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239,
0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e,
0x21c66842, 0xf6e96c9a, 0x670c9c61, 0xabd388f0,
0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3,
0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98,
0xa1f1651d, 0x39af0176, 0x66ca593e, 0x82430e88,
0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe,
0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6,
0x4ed3aa62, 0x363f7706, 0x1bfedf72, 0x429b023d,
0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b,
0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7,
0xe3fe501a, 0xb6794c3b, 0x976ce0bd, 0x04c006ba,
0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463,
0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f,
0x6dfc511f, 0x9b30952c, 0xcc814544, 0xaf5ebd09,
0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3,
0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb,
0x5579c0bd, 0x1a60320a, 0xd6a100c6, 0x402c7279,
0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8,
0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab,
0x323db5fa, 0xfd238760, 0x53317b48, 0x3e00df82,
0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db,
0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573,
0x695b27b0, 0xbbca58c8, 0xe1ffa35d, 0xb8f011a0,
0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b,
0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790,
0xe1ddf2da, 0xa4cb7e33, 0x62fb1341, 0xcee4c6e8,
0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4,
0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0,
0xd08ed1d0, 0xafc725e0, 0x8e3c5b2f, 0x8e7594b7,
0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c,
0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad,
0x2f2f2218, 0xbe0e1777, 0xea752dfe, 0x8b021fa1,
0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299,
0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9,
0x165fa266, 0x80957705, 0x93cc7314, 0x211a1477,
0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf,
0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49,
0x00250e2d, 0x2071b35e, 0x226800bb, 0x57b8e0af,
0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa,
0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5,
0x83260376, 0x6295cfa9, 0x11c81968, 0x4e734a41,
0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915,
0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400,
0x08ba6fb5, 0x571be91f, 0xf296ec6b, 0x2a0dd915,
0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664,
0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a},
{
0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623,
0xad6ea6b0, 0x49a7df7d, 0x9cee60b8, 0x8fedb266,
0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1,
0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e,
0x3f54989a, 0x5b429d65, 0x6b8fe4d6, 0x99f73fd6,
0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1,
0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e,
0x09686b3f, 0x3ebaefc9, 0x3c971814, 0x6b6a70a1,
0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737,
0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8,
0xb03ada37, 0xf0500c0d, 0xf01c1f04, 0x0200b3ff,
0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd,
0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701,
0x3ae5e581, 0x37c2dadc, 0xc8b57634, 0x9af3dda7,
0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41,
0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331,
0x4e548b38, 0x4f6db908, 0x6f420d03, 0xf60a04bf,
0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af,
0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e,
0x5512721f, 0x2e6b7124, 0x501adde6, 0x9f84cd87,
0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c,
0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2,
0xef1c1847, 0x3215d908, 0xdd433b37, 0x24c2ba16,
0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd,
0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b,
0x043556f1, 0xd7a3c76b, 0x3c11183b, 0x5924a509,
0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e,
0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3,
0x771fe71c, 0x4e3d06fa, 0x2965dcb9, 0x99e71d0f,
0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a,
0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4,
0xf2f74ea7, 0x361d2b3d, 0x1939260f, 0x19c27960,
0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66,
0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28,
0xc332ddef, 0xbe6c5aa5, 0x65582185, 0x68ab9802,
0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84,
0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510,
0x13cca830, 0xeb61bd96, 0x0334fe1e, 0xaa0363cf,
0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14,
0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e,
0x648b1eaf, 0x19bdf0ca, 0xa02369b9, 0x655abb50,
0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7,
0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8,
0xf837889a, 0x97e32d77, 0x11ed935f, 0x16681281,
0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99,
0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696,
0xcdb30aeb, 0x532e3054, 0x8fd948e4, 0x6dbc3128,
0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73,
0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0,
0x45eee2b6, 0xa3aaabea, 0xdb6c4f15, 0xfacb4fd0,
0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105,
0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250,
0xcf62a1f2, 0x5b8d2646, 0xfc8883a0, 0xc1c7b6a3,
0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285,
0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00,
0x58428d2a, 0x0c55f5ea, 0x1dadf43e, 0x233f7061,
0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb,
0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e,
0xa6078084, 0x19f8509e, 0xe8efd855, 0x61d99735,
0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc,
0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9,
0xdb73dbd3, 0x105588cd, 0x675fda79, 0xe3674340,
0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20,
0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7},
{
0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934,
0x411520f7, 0x7602d4f7, 0xbcf46b2e, 0xd4a20068,
0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af,
0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840,
0x4d95fc1d, 0x96b591af, 0x70f4ddd3, 0x66a02f45,
0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504,
0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a,
0x28507825, 0x530429f4, 0x0a2c86da, 0xe9b66dfb,
0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee,
0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6,
0xaace1e7c, 0xd3375fec, 0xce78a399, 0x406b2a42,
0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b,
0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2,
0x3a6efa74, 0xdd5b4332, 0x6841e7f7, 0xca7820fb,
0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527,
0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b,
0x55a867bc, 0xa1159a58, 0xcca92963, 0x99e1db33,
0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c,
0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3,
0x95c11548, 0xe4c66d22, 0x48c1133f, 0xc70f86dc,
0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17,
0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564,
0x257b7834, 0x602a9c60, 0xdff8e8a3, 0x1f636c1b,
0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115,
0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922,
0x85b2a20e, 0xe6ba0d99, 0xde720c8c, 0x2da2f728,
0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0,
0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e,
0x0a476341, 0x992eff74, 0x3a6f6eab, 0xf4f8fd37,
0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d,
0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804,
0xf1290dc7, 0xcc00ffa3, 0xb5390f92, 0x690fed0b,
0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3,
0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb,
0x37392eb3, 0xcc115979, 0x8026e297, 0xf42e312d,
0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c,
0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350,
0x1a6b1018, 0x11caedfa, 0x3d25bdd8, 0xe2e1c3c9,
0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a,
0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe,
0x9dbc8057, 0xf0f7c086, 0x60787bf8, 0x6003604d,
0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc,
0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f,
0x77a057be, 0xbde8ae24, 0x55464299, 0xbf582e61,
0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2,
0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9,
0x7aeb2661, 0x8b1ddf84, 0x846a0e79, 0x915f95e2,
0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c,
0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e,
0xb77f19b6, 0xe0a9dc09, 0x662d09a1, 0xc4324633,
0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10,
0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169,
0xdcb7da83, 0x573906fe, 0xa1e2ce9b, 0x4fcd7f52,
0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027,
0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5,
0xf0177a28, 0xc0f586e0, 0x006058aa, 0x30dc7d62,
0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634,
0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76,
0x6f05e409, 0x4b7c0188, 0x39720a3d, 0x7c927c24,
0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc,
0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4,
0x1e50ef5e, 0xb161e6f8, 0xa28514d9, 0x6c51133c,
0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837,
0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0},
{
0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b,
0x5cb0679e, 0x4fa33742, 0xd3822740, 0x99bc9bbe,
0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b,
0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4,
0x5748ab2f, 0xbc946e79, 0xc6a376d2, 0x6549c2c8,
0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6,
0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304,
0xa1fad5f0, 0x6a2d519a, 0x63ef8ce2, 0x9a86ee22,
0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4,
0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6,
0x2826a2f9, 0xa73a3ae1, 0x4ba99586, 0xef5562e9,
0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59,
0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593,
0xe990fd5a, 0x9e34d797, 0x2cf0b7d9, 0x022b8b51,
0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28,
0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c,
0xe029ac71, 0xe019a5e6, 0x47b0acfd, 0xed93fa9b,
0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28,
0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c,
0x15056dd4, 0x88f46dba, 0x03a16125, 0x0564f0bd,
0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a,
0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319,
0x7533d928, 0xb155fdf5, 0x03563482, 0x8aba3cbb,
0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f,
0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991,
0xea7a90c2, 0xfb3e7bce, 0x5121ce64, 0x774fbe32,
0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680,
0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166,
0xb39a460a, 0x6445c0dd, 0x586cdecf, 0x1c20c8ae,
0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb,
0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5,
0x72eacea8, 0xfa6484bb, 0x8d6612ae, 0xbf3c6f47,
0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370,
0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d,
0x4040cb08, 0x4eb4e2cc, 0x34d2466a, 0x0115af84,
0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048,
0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8,
0x611560b1, 0xe7933fdc, 0xbb3a792b, 0x344525bd,
0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9,
0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7,
0x1a908749, 0xd44fbd9a, 0xd0dadecb, 0xd50ada38,
0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f,
0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c,
0xbf97222c, 0x15e6fc2a, 0x0f91fc71, 0x9b941525,
0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1,
0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442,
0xe0ec6e0e, 0x1698db3b, 0x4c98a0be, 0x3278e964,
0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e,
0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8,
0xdf359f8d, 0x9b992f2e, 0xe60b6f47, 0x0fe3f11d,
0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f,
0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299,
0xf523f357, 0xa6327623, 0x93a83531, 0x56cccd02,
0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc,
0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614,
0xe6c6c7bd, 0x327a140a, 0x45e1d006, 0xc3f27b9a,
0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6,
0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b,
0x53113ec0, 0x1640e3d3, 0x38abbd60, 0x2547adf0,
0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060,
0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e,
0x1948c25c, 0x02fb8a8c, 0x01c36ae4, 0xd6ebe1f9,
0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f,
0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6}
},
{
0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344,
0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89,
0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c,
0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917,
0x9216d5d9, 0x8979fb1b
} };
*c = initstate;
}
u_int32_t
Vapor_Blowfish_stream2word(const u_int8_t *data, u_int16_t databytes,
u_int16_t *current)
{
u_int8_t i;
u_int16_t j;
u_int32_t temp;
temp = 0x00000000;
j = *current;
for (i = 0; i < 4; i++, j++) {
if (j >= databytes)
j = 0;
temp = (temp << 8) | data[j];
}
*current = j;
return temp;
}
void
Vapor_Blowfish_expand0state(blf_ctx *c, const u_int8_t *key, u_int16_t keybytes)
{
u_int16_t i;
u_int16_t j;
u_int16_t k;
u_int32_t temp;
u_int32_t data[2];
j = 0;
for (i = 0; i < BLF_N + 2; i++) {
/* Extract 4 int8 to 1 int32 from keystream */
temp = Vapor_Blowfish_stream2word(key, keybytes, &j);
c->P[i] = c->P[i] ^ temp;
}
j = 0;
data[0] = 0x00000000;
data[1] = 0x00000000;
for (i = 0; i < BLF_N + 2; i += 2) {
Vapor_Blowfish_encipher(c, data);
c->P[i] = data[0];
c->P[i + 1] = data[1];
}
for (i = 0; i < 4; i++) {
for (k = 0; k < 256; k += 2) {
Vapor_Blowfish_encipher(c, data);
c->S[i][k] = data[0];
c->S[i][k + 1] = data[1];
}
}
}
void
Vapor_Blowfish_expandstate(blf_ctx *c, const u_int8_t *data, u_int16_t databytes,
const u_int8_t *key, u_int16_t keybytes)
{
u_int16_t i;
u_int16_t j;
u_int16_t k;
u_int32_t temp;
u_int32_t d[2];
j = 0;
for (i = 0; i < BLF_N + 2; i++) {
/* Extract 4 int8 to 1 int32 from keystream */
temp = Vapor_Blowfish_stream2word(key, keybytes, &j);
c->P[i] = c->P[i] ^ temp;
}
j = 0;
d[0] = 0x00000000;
d[1] = 0x00000000;
for (i = 0; i < BLF_N + 2; i += 2) {
d[0] ^= Vapor_Blowfish_stream2word(data, databytes, &j);
d[1] ^= Vapor_Blowfish_stream2word(data, databytes, &j);
Vapor_Blowfish_encipher(c, d);
c->P[i] = d[0];
c->P[i + 1] = d[1];
}
for (i = 0; i < 4; i++) {
for (k = 0; k < 256; k += 2) {
d[0] ^= Vapor_Blowfish_stream2word(data, databytes, &j);
d[1] ^= Vapor_Blowfish_stream2word(data, databytes, &j);
Vapor_Blowfish_encipher(c, d);
c->S[i][k] = d[0];
c->S[i][k + 1] = d[1];
}
}
}
void
vapor_blf_key(blf_ctx *c, const u_int8_t *k, u_int16_t len)
{
/* Initialize S-boxes and subkeys with Pi */
Vapor_Blowfish_initstate(c);
/* Transform S-boxes and subkeys with key */
Vapor_Blowfish_expand0state(c, k, len);
}
void
vapor_blf_enc(blf_ctx *c, u_int32_t *data, u_int16_t blocks)
{
u_int32_t *d;
u_int16_t i;
d = data;
for (i = 0; i < blocks; i++) {
Vapor_Blowfish_encipher(c, d);
d += 2;
}
}
void
vapor_blf_dec(blf_ctx *c, u_int32_t *data, u_int16_t blocks)
{
u_int32_t *d;
u_int16_t i;
d = data;
for (i = 0; i < blocks; i++) {
Vapor_Blowfish_decipher(c, d);
d += 2;
}
}
void
vapor_blf_ecb_encrypt(blf_ctx *c, u_int8_t *data, u_int32_t len)
{
u_int32_t l, r, d[2];
u_int32_t i;
for (i = 0; i < len; i += 8) {
l = data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3];
r = data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7];
d[0] = l;
d[1] = r;
Vapor_Blowfish_encipher(c, d);
l = d[0];
r = d[1];
data[0] = l >> 24 & 0xff;
data[1] = l >> 16 & 0xff;
data[2] = l >> 8 & 0xff;
data[3] = l & 0xff;
data[4] = r >> 24 & 0xff;
data[5] = r >> 16 & 0xff;
data[6] = r >> 8 & 0xff;
data[7] = r & 0xff;
data += 8;
}
}
void
vapor_blf_ecb_decrypt(blf_ctx *c, u_int8_t *data, u_int32_t len)
{
u_int32_t l, r, d[2];
u_int32_t i;
for (i = 0; i < len; i += 8) {
l = data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3];
r = data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7];
d[0] = l;
d[1] = r;
Vapor_Blowfish_decipher(c, d);
l = d[0];
r = d[1];
data[0] = l >> 24 & 0xff;
data[1] = l >> 16 & 0xff;
data[2] = l >> 8 & 0xff;
data[3] = l & 0xff;
data[4] = r >> 24 & 0xff;
data[5] = r >> 16 & 0xff;
data[6] = r >> 8 & 0xff;
data[7] = r & 0xff;
data += 8;
}
}
void
vapor_blf_cbc_encrypt(blf_ctx *c, u_int8_t *iv, u_int8_t *data, u_int32_t len)
{
u_int32_t l, r, d[2];
u_int32_t i, j;
for (i = 0; i < len; i += 8) {
for (j = 0; j < 8; j++)
data[j] ^= iv[j];
l = data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3];
r = data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7];
d[0] = l;
d[1] = r;
Vapor_Blowfish_encipher(c, d);
l = d[0];
r = d[1];
data[0] = l >> 24 & 0xff;
data[1] = l >> 16 & 0xff;
data[2] = l >> 8 & 0xff;
data[3] = l & 0xff;
data[4] = r >> 24 & 0xff;
data[5] = r >> 16 & 0xff;
data[6] = r >> 8 & 0xff;
data[7] = r & 0xff;
iv = data;
data += 8;
}
}
void
vapor_blf_cbc_decrypt(blf_ctx *c, u_int8_t *iva, u_int8_t *data, u_int32_t len)
{
u_int32_t l, r, d[2];
u_int8_t *iv;
u_int32_t i, j;
iv = data + len - 16;
data = data + len - 8;
for (i = len - 8; i >= 8; i -= 8) {
l = data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3];
r = data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7];
d[0] = l;
d[1] = r;
Vapor_Blowfish_decipher(c, d);
l = d[0];
r = d[1];
data[0] = l >> 24 & 0xff;
data[1] = l >> 16 & 0xff;
data[2] = l >> 8 & 0xff;
data[3] = l & 0xff;
data[4] = r >> 24 & 0xff;
data[5] = r >> 16 & 0xff;
data[6] = r >> 8 & 0xff;
data[7] = r & 0xff;
for (j = 0; j < 8; j++)
data[j] ^= iv[j];
iv -= 8;
data -= 8;
}
l = data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3];
r = data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7];
d[0] = l;
d[1] = r;
Vapor_Blowfish_decipher(c, d);
l = d[0];
r = d[1];
data[0] = l >> 24 & 0xff;
data[1] = l >> 16 & 0xff;
data[2] = l >> 8 & 0xff;
data[3] = l & 0xff;
data[4] = r >> 24 & 0xff;
data[5] = r >> 16 & 0xff;
data[6] = r >> 8 & 0xff;
data[7] = r & 0xff;
for (j = 0; j < 8; j++)
data[j] ^= iva[j];
}
================================================
FILE: Sources/CVaporBcrypt/blf.h
================================================
/* $OpenBSD: blf.h,v 1.6 2007/02/21 19:25:40 grunk Exp $ */
/*
* Blowfish - a fast block cipher designed by Bruce Schneier
*
* Copyright 1997 Niels Provos <provos@physnet.uni-hamburg.de>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by Niels Provos.
* 4. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef _BLF_H_
#define _BLF_H_
#include "bcrypt.h"
/* Schneier states the maximum key length to be 56 bytes.
* The way how the subkeys are initialized by the key up
* to (N+2)*4 i.e. 72 bytes are utilized.
* Warning: For normal blowfish encryption only 56 bytes
* of the key affect all cipherbits.
*/
#define BLF_N 16 /* Number of Subkeys */
#define BLF_MAXKEYLEN ((BLF_N-2)*4) /* 448 bits */
#define BLF_MAXUTILIZED ((BLF_N+2)*4) /* 576 bits */
/* Blowfish context */
typedef struct BlowfishContext {
u_int32_t S[4][256]; /* S-Boxes */
u_int32_t P[BLF_N + 2]; /* Subkeys */
} blf_ctx;
/* Raw access to customized Blowfish
* blf_key is just:
* Blowfish_initstate( state )
* Blowfish_expand0state( state, key, keylen )
*/
void Vapor_Blowfish_encipher(blf_ctx *, u_int32_t *);
void Vapor_Blowfish_decipher(blf_ctx *, u_int32_t *);
void Vapor_Blowfish_initstate(blf_ctx *);
void Vapor_Blowfish_expand0state(blf_ctx *, const u_int8_t *, u_int16_t);
void Vapor_Blowfish_expandstate(blf_ctx *, const u_int8_t *, u_int16_t, const u_int8_t *, u_int16_t);
/* Standard Blowfish */
void vapor_blf_key(blf_ctx *, const u_int8_t *, u_int16_t);
void vapor_blf_enc(blf_ctx *, u_int32_t *, u_int16_t);
void vapor_blf_dec(blf_ctx *, u_int32_t *, u_int16_t);
/* Converts u_int8_t to u_int32_t */
u_int32_t Vapor_Blowfish_stream2word(const u_int8_t *, u_int16_t ,
u_int16_t *);
void vapor_blf_ecb_encrypt(blf_ctx *, u_int8_t *, u_int32_t);
void vapor_blf_ecb_decrypt(blf_ctx *, u_int8_t *, u_int32_t);
void vapor_blf_cbc_encrypt(blf_ctx *, u_int8_t *, u_int8_t *, u_int32_t);
void vapor_blf_cbc_decrypt(blf_ctx *, u_int8_t *, u_int8_t *, u_int32_t);
#endif
================================================
FILE: Sources/CVaporBcrypt/include/module.modulemap
================================================
module CVaporBcrypt [system][extern_c] {
header "../bcrypt.h"
export *
}
================================================
FILE: Sources/Development/Resources/fileio.txt
================================================
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc ut eleifend turpis, quis vestibulum odio. Sed vestibulum, lorem at sagittis mattis, nunc sem euismod augue, id accumsan ipsum turpis bibendum leo. Praesent in faucibus nulla. Sed egestas aliquam velit eu tempor. Quisque egestas risus non justo posuere, id imperdiet erat porta. Phasellus commodo, justo ut volutpat elementum, tortor felis egestas metus, vel congue elit sem in ipsum. Suspendisse potenti. Fusce pharetra pellentesque nulla at blandit. Proin at dui eu ex lacinia tempus. Donec faucibus est vel erat faucibus ullamcorper. Integer vel nisi erat. Sed porta risus magna, eu ultricies massa tempus quis. Donec dignissim fringilla lobortis. Morbi sapien sem, cursus quis sollicitudin id, varius eget purus. Morbi at commodo diam. Duis efficitur blandit nisl nec ultricies. Praesent ultrices nulla eget mattis lobortis. Morbi euismod pharetra purus quis efficitur. Cras nec elit nec purus ullamcorper consequat ut in libero. Duis ultricies hendrerit odio vitae hendrerit. Pellentesque pharetra malesuada purus tincidunt tincidunt. Aliquam volutpat, nisl et porta vehicula, lorem elit ultricies purus, et cursus massa lectus vitae leo. Donec ornare lacinia sem non mollis. Aenean quis nulla vel diam tempus malesuada. Vestibulum in orci purus. Donec et leo luctus, congue massa sed, blandit diam. Aliquam non purus vitae nisl sollicitudin malesuada. Vivamus sed urna convallis, mattis nisi ac, pellentesque dui. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nullam laoreet erat id vehicula lacinia. In hac habitasse platea dictumst. Duis dapibus, sapien nec dignissim tempus, nulla massa interdum enim, posuere imperdiet nulla justo semper leo. Curabitur tempus rutrum magna, vitae tristique tortor porttitor vitae. Donec molestie nibh sit amet quam elementum, eget maximus neque scelerisque. Pellentesque a elit sed arcu faucibus tempor. Morbi congue, nunc a imperdiet sodales, dolor elit placerat nisl, eu elementum elit mi id diam. Duis pretium quam in tellus scelerisque aliquam. Curabitur egestas euismod nunc, ut tempus est pellentesque sed. Aenean dignissim sodales lorem, quis convallis nibh pretium id. Suspendisse sed elementum lacus. Fusce congue magna hendrerit purus dictum consectetur. Duis ut purus a ipsum facilisis aliquet. Suspendisse nec sollicitudin tellus. Quisque sit amet leo sit amet lorem tincidunt euismod. Cras ut lectus quis lorem fermentum commodo. Quisque euismod non nisi tempor rhoncus. Nam sodales, ante id vehicula pretium, tortor turpis porta ligula, et malesuada ex velit non odio. Proin scelerisque tellus iaculis vulputate tempus. Fusce gravida odio eget dignissim luctus. Phasellus hendrerit ultricies nibh, id consequat libero maximus et. Suspendisse rhoncus orci eget leo fermentum, in pretium ligula fringilla. Ut mollis dictum condimentum. Quisque lacus ipsum, molestie in tristique in, porttitor vel massa. Proin sagittis metus et eleifend rhoncus. Cras porta non turpis vel molestie. Duis bibendum nisl nec lectus cursus pretium. Nam porta nisi id tortor hendrerit, quis feugiat diam eleifend. Ut luctus sagittis nulla. Aliquam at libero lectus. Praesent sed egestas sem, sed tempus neque. Duis ultrices nisl nec laoreet ultricies. Nulla id urna quis lacus tincidunt scelerisque. Sed erat arcu, efficitur sed sagittis nec, venenatis et est. Integer viverra laoreet odio ut posuere. Pellentesque quis libero risus. Ut interdum id magna pellentesque aliquet. Pellentesque id commodo est, a tincidunt ex. Donec elementum urna leo, ac gravida ligula tempus vel. Suspendisse molestie aliquet ligula nec lobortis. Aenean vel elementum massa, at dapibus elit. Fusce ac pellentesque risus, at hendrerit erat. Aenean vel placerat tortor, non finibus risus. Vestibulum ultrices rutrum blandit. Integer id ultrices arcu. Nunc molestie urna vel bibendum imperdiet. Mauris finibus lorem non leo vulputate auctor. Praesent mollis velit vitae magna faucibus venenatis. Donec egestas euismod tortor, quis vehicula elit porta in. Cras tempor justo ut tortor.
================================================
FILE: Sources/Development/configure.swift
================================================
import Vapor
import NIOConcurrencyHelpers
import NIOSSL
public func configure(_ app: Application) throws {
app.logger.logLevel = Environment.process.LOG_LEVEL ?? .debug
app.http.server.configuration.hostname = "127.0.0.1"
if app.environment == .tls {
app.http.server.configuration.port = 8443
try app.http.server.configuration.tlsConfiguration = .makeServerConfiguration(
certificateChain: NIOSSLCertificate.fromPEMBytes(TLSData.sampleServerCertificatePEM).map { .certificate($0) },
privateKey: .privateKey(.init(bytes: TLSData.sampleServerPrivateKeyPEM, format: .pem))
)
}
// routes
try routes(app)
}
actor MemoryCache {
var storage: [String: String] = [:]
func get(_ key: String) -> String? { self.storage[key] }
func set(_ key: String, to value: String?) { self.storage[key] = value }
}
extension Environment {
static var tls: Environment { .custom(name: "tls") }
}
enum TLSData {
static var sampleServerCertificatePEM: [UInt8] { .init("""
-----BEGIN CERTIFICATE-----
MIIDeTCCAmGgAwIBAgIUMJzqelT95d/JU2Yp4/XHuqhJTs4wDQYJKoZIhvcNAQEL\nBQAwTDELMAkGA1UEBhMCVVMxKTAnBgNVBAoMIFZhcG9yIERldmVsb3BtZW50IEV4
YW1wbGUgU2VydmVyMRIwEAYDVQQDDAlsb2NhbGhvc3QwHhcNMjMwMzEwMTIyNjQw\nWhcNMjcwMzEwMTIyNjQwWjBMMQswCQYDVQQGEwJVUzEpMCcGA1UECgwgVmFwb3Ig
RGV2ZWxvcG1lbnQgRXhhbXBsZSBTZXJ2ZXIxEjAQBgNVBAMMCWxvY2FsaG9zdDCC\nASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALavC7FuHyTwEbYUEtUDHPdE
LCglZGypp1+qE1XuhQ1qPgx7FMBKXAYYLjSyEfK1GaorBXfLGW5xNHfSrJYVmhm2\nUOGPJbZvFtXZeufQz8B31u6sfXEJNWbJ6K8HUZkPyNRJROS5IBhDRiKxUTJOT+Ph
pT1ZooRNd/9/v/0JoM4HEXE4oO7KIb4fM4IuVIfTdib42aMH7jKMVfVr7N2zOFnm\nMd0fmc5y0Gx/tvr13EN92lGlS3V4+YTWr7KsueQYvplJiDJ0E3AipLXRYtarsJqD
nWhktpvbknbf9LntKJo9yL+O6CRifS8zBn/cqfFo7vuIRQyhd2q/ndjiqQoOqx8C\nAwEAAaNTMFEwHQYDVR0OBBYEFDNaYh5eewiz63D/z4Imzmd3Ey+kMB8GA1UdIwQY
MBaAFDNaYh5eewiz63D/z4Imzmd3Ey+kMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZI\nhvcNAQELBQADggEBAEOxFji5Jlx3LdTjVG3cy5PnZWFGrREw4JE6vl258upGTEaz
m/TQOBiSWxEG5SfMSFjaNzoHu5BU+uTUyr/gCUseFoseA+C+wsCikfSPKpmfLEW0\nNF49c6fPYWCu39wMpNCgrcXgde29V3Sar5WfYclFnQUEHqSRL22Yq+JNPnokFrja
L9jOe/0MbZ34Gurjj9LMlVDg3p8FTKJJ9qipPMVBPy+/8ABm4qu7vx0Kacuskgc8\nu8RErJ0sqir7ggBGqgRp+Z+DC5UcqlMUZZQPKSLpCqfdrOIcDrTK9u/PU9cGdALh
C+4n5ZIHWu66eCvARnqbCTwcOwGMxkKX/4FpI54=
-----END CERTIFICATE-----
""".utf8)
}
static var sampleServerPrivateKeyPEM: [UInt8] { .init("""
-----BEGIN PRIVATE KEY-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC2rwuxbh8k8BG2\nFBLVAxz3RCwoJWRsqadfqhNV7oUNaj4MexTASlwGGC40shHytRmqKwV3yxlucTR3
0qyWFZoZtlDhjyW2bxbV2Xrn0M/Ad9burH1xCTVmyeivB1GZD8jUSUTkuSAYQ0Yi\nsVEyTk/j4aU9WaKETXf/f7/9CaDOBxFxOKDuyiG+HzOCLlSH03Ym+NmjB+4yjFX1
a+zdszhZ5jHdH5nOctBsf7b69dxDfdpRpUt1ePmE1q+yrLnkGL6ZSYgydBNwIqS1\n0WLWq7Cag51oZLab25J23/S57SiaPci/jugkYn0vMwZ/3KnxaO77iEUMoXdqv53Y
4qkKDqsfAgMBAAECggEANfp17YjY2fy3dwHqaJdhZSx3Eauuxy6/3lPuH6t5E/Qq\n/lwVzxWJqGFXsclV5U2eljndBT71Nj1r3+XXigc6/9Lvhh5aadPcPvbiSoHYCQo/
70j3TcGHTmZlguYaNaxEznkRyrVqptCl9hVHpSIfl/lx7jVAgHA1f0CblWRVZ9qM\nhX42ye0xxamUdfcUFYvZ4n7Tz5G36HP+cOKlrN6nGfhmOf3hb3ILuPymCJdECvFM
xsCXpHA2r4biYMRwkWO2+X5Scx7nVrQYizJG1iTdZnY6g1heRhzUDTcf7yjyr2++\nd+9VRI0KW5gO7q0sIarHgy7ItAAWgjGhGBASU4rebQKBgQC5xeE47WUzA1zQoDof
u55tSp32hHKkw2ysdIO3LUSVHS/VhMMNHTo0fqhvgY7Wd25M9OBVmWbTvnSNmcU+\niVdVcd9rmD+jPEBftwjRKtXkQgJpMNuDszOQ3bJYqAUkRfT7ceuiil3H4Awdq6UE
qvynmn7MYZN7CxcefvOQlSdpKwKBgQD7vjXAOJn7ZyGRsb1lKxNCHL9GvZthsM1h\nvyH8uvUm94Ztw5g9ON2qgPZQIwZxEY+LxnjfqaooKHE38rNdKYHMLsfgNQ2tdopR
2sEqVL0aQP25YAYiL4jI7hGI7GwgiSiywvmhGWjU5ZIu8fKqc+8pZy4vW/EgVctQ\nuLntBvgj3QKBgGaSLEV7RcoBzEhgf1cwB0w+y7Ll9EqmoCUj++mys9BFGjkhIXTn
M1DysdtHRG+D58HT3t1EYrL80GuygGaD/FVwFzTYDiL5zG1MqTCcHxb1n1EnKbyw\nwAL3dVZgBt69RYNjpf/Lt/X47ZegQu+t3OxJcEM2iPCB8hTjcWXeBLGbAoGAEjcT
IJN34M73iNk5gQZ64D/AP1gc1Ba85aO0y9qjPmyOl4adj2B7+YhXSjkekDPbFRwJ\nRvW50CoM9yVigQ0tzR5dbAWqtbBsFbwkWfHDtRCayzz9dJ/H3/IJ5sRkln4WKckd
0uBJy43I5AixrE+zMGW828RlUBelHHQhT9s/PSkCgYA6kQyV5FLBLrmnV5B8bdiU\ndEF7H/YHrRMvzH3bwfgaw7CxjUcreQ2LX84fkUFtZXzR2VS5bdZKGFDmSX0QVGPq
zmVLrxbf0kgQjEEaSHsdIutYblHJg3bSjKoyOC20TB34sps7hpBRq6joJwjWH/rz\n6XoqNBpphe8vTBZn/FSkog==
-----END PRIVATE KEY-----
""".utf8)
}
}
================================================
FILE: Sources/Development/entrypoint.swift
================================================
import Vapor
import Logging
@main
struct Entrypoint {
static func main() async throws {
var env = try Environment.detect()
try LoggingSystem.bootstrap(from: &env)
let app = try await Application.make(env)
do {
try configure(app)
try await app.execute()
try await app.asyncShutdown()
} catch {
try? await app.asyncShutdown()
throw error
}
}
}
================================================
FILE: Sources/Development/routes.swift
================================================
import class Foundation.Bundle
import Vapor
import NIOCore
import NIOHTTP1
import NIOConcurrencyHelpers
import _NIOFileSystem
struct Creds: Content {
var email: String
var password: String
}
public func routes(_ app: Application) throws {
app.on(.GET, "ping") { req -> StaticString in
return "123" as StaticString
}
// ( echo -e 'POST /slow-stream HTTP/1.1\r\nContent-Length: 1000000000\r\n\r\n'; dd if=/dev/zero; ) | nc localhost 8080
app.on(.POST, "slow-stream", body: .stream) { req -> EventLoopFuture<String> in
let done = req.eventLoop.makePromise(of: String.self)
let totalBox = NIOLoopBoundBox(0, eventLoop: req.eventLoop)
req.body.drain { result in
let promise = req.eventLoop.makePromise(of: Void.self)
switch result {
case .buffer(let buffer):
req.eventLoop.scheduleTask(in: .milliseconds(1000)) {
totalBox.value += buffer.readableBytes
promise.succeed(())
}
case .error(let error):
done.fail(error)
case .end:
promise.succeed(())
done.succeed(totalBox.value.description)
}
// manually return pre-completed future
// this should balloon in memory
// return req.eventLoop.makeSucceededFuture(())
// return real future that indicates bytes were handled
// this should use very little memory
return promise.futureResult
}
return done.futureResult
}
app.get("test", "head") { req -> String in
return "OK!"
}
app.post("test", "head") { req -> String in
return "OK!"
}
app.post("login") { req -> String in
let creds = try req.content.decode(Creds.self)
return "\(creds)"
}
app.on(.POST, "large-file", body: .collect(maxSize: 1_000_000_000)) { req -> String in
return req.body.data?.readableBytes.description ?? "none"
}
app.get("json") { req -> [String: String] in
return ["foo": "bar"]
}.description("returns some test json")
app.webSocket("ws") { req, ws in
ws.onText { ws, text in
ws.send(text.reversed())
if text == "close" {
ws.close(promise: nil)
}
}
let ip = req.remoteAddress?.description ?? "<no ip>"
ws.send("Hello 👋 \(ip)")
}
app.on(.POST, "file", body: .stream) { req -> EventLoopFuture<String> in
let promise = req.eventLoop.makePromise(of: String.self)
req.body.drain { result in
switch result {
case .buffer(let buffer):
debugPrint(buffer)
case .error(let error):
promise.fail(error)
case .end:
promise.succeed("Done")
}
return req.eventLoop.makeSucceededFuture(())
}
return promise.futureResult
}
app.get("shutdown") { req -> HTTPStatus in
guard let running = req.application.running else {
throw Abort(.internalServerError)
}
running.stop()
return .ok
}
let cache = MemoryCache()
app.get("cache", "get", ":key") { req -> String in
guard let key = req.parameters.get("key") else {
throw Abort(.internalServerError)
}
return "\(key) = \(await cache.get(key) ?? "nil")"
}
app.get("cache", "set", ":key", ":value") { req -> String in
guard let key = req.parameters.get("key") else {
throw Abort(.internalServerError)
}
guard let value = req.parameters.get("value") else {
throw Abort(.internalServerError)
}
await cache.set(key, to: value)
return "\(key) = \(value)"
}
app.get("hello", ":name") { req in
return req.parameters.get("name") ?? "<nil>"
}
app.get("search") { req in
return req.query["q"] ?? "none"
}
let sessions = app.grouped("sessions")
.grouped(app.sessions.middleware)
sessions.get("set", ":value") { req -> HTTPStatus in
req.session.data["name"] = req.parameters.get("value")
return .ok
}
sessions.get("get") { req -> String in
req.session.data["name"] ?? "n/a"
}
sessions.get("del") { req -> String in
req.session.destroy()
return "done"
}
app.get("client") { req in
return req.client.get("http://httpbin.org/status/201").map { $0.description }
}
app.get("client-json") { req -> EventLoopFuture<String> in
struct HTTPBinResponse: Decodable {
struct Slideshow: Decodable {
var title: String
}
var slideshow: Slideshow
}
return req.client.get("http://httpbin.org/json")
.flatMapThrowing { try $0.content.decode(HTTPBinResponse.self) }
.map { $0.slideshow.title }
}
let users = app.grouped("users")
users.get { req in
return "users"
}
users.get(":userID") { req in
return req.parameters.get("userID") ?? "no id"
}
app.directory.viewsDirectory = "/Users/tanner/Desktop"
app.get("view") { req in
req.view.render("hello.txt", ["name": "world"])
}
app.get("error") { req -> String in
throw TestError()
}
app.get("secret") { req in
guard let secret = try await Environment.secret(path: "PASSWORD_SECRET") else {
throw Abort(.badRequest)
}
return secret
}
app.on(.POST, "max-256", body: .collect(maxSize: 256)) { req -> HTTPStatus in
print("in route")
return .ok
}
@available(*, deprecated, message: "Testing deprecated functions")
func deprecatedUploadHandler(_ req: Request) -> EventLoopFuture<HTTPStatus> {
enum BodyStreamWritingToDiskError: Error {
case streamFailure(Error)
case fileHandleClosedFailure(Error)
case multipleFailures([BodyStreamWritingToDiskError])
}
return req.application.fileio.openFile(
path: Bundle.module.url(forResource: "Resources/fileio", withExtension: "txt")?.path ?? "",
mode: .write,
flags: .allowFileCreation(),
eventLoop: req.eventLoop
).flatMap { fileHandle in
let promise = req.eventLoop.makePromise(of: HTTPStatus.self)
let fileHandleBox = NIOLoopBound(fileHandle, eventLoop: req.eventLoop)
req.body.drain { part in
let fileHandle = fileHandleBox.value
switch part {
case .buffer(let buffer):
return req.application.fileio.write(
fileHandle: fileHandle,
buffer: buffer,
eventLoop: req.eventLoop
)
case .error(let drainError):
do {
try fileHandle.close()
promise.fail(BodyStreamWritingToDiskError.streamFailure(drainError))
} catch {
promise.fail(BodyStreamWritingToDiskError.multipleFailures([
.fileHandleClosedFailure(error),
.streamFailure(drainError)
]))
}
return req.eventLoop.makeSucceededFuture(())
case .end:
do {
try fileHandle.close()
promise.succeed(.ok)
} catch {
promise.fail(BodyStreamWritingToDiskError.fileHandleClosedFailure(error))
}
return req.eventLoop.makeSucceededFuture(())
}
}
return promise.futureResult
}
}
app.on(.POST, "upload", body: .stream) { req -> HTTPStatus in
return try await FileSystem.shared.withFileHandle(
forWritingAt: .init(Bundle.module.url(forResource: "Resources/fileio", withExtension: "txt")?.path ?? ""),
options: .newFile(replaceExisting: true)) { handle in
var writer = handle.bufferedWriter()
for try await part in req.body {
try await writer.write(contentsOf: part)
}
return .ok
}
}
let asyncRoutes = app.grouped("async").grouped(TestAsyncMiddleware(number: 1))
asyncRoutes.get("client") { req async throws -> String in
let response = try await req.client.get("https://www.google.com")
guard let body = response.body else {
throw Abort(.internalServerError)
}
return String(buffer: body)
}
asyncRoutes.get("client2") { req -> String in
let response = try await req.client.get("https://www.google.com")
guard let body = response.body else {
throw Abort(.internalServerError)
}
return String(buffer: body)
}
asyncRoutes.get("content") { req in
Creds(email: "name", password: "password")
}
asyncRoutes.get("content2") { req async throws -> Creds in
return Creds(email: "name", password: "password")
}
asyncRoutes.get("contentArray") { req async throws -> [Creds] in
let cred1 = Creds(email: "name", password: "password")
return [cred1]
}
@Sendable
func opaqueRouteTester(_ req: Request) async throws -> some AsyncResponseEncodable {
"Hello World"
}
asyncRoutes.get("opaque", use: opaqueRouteTester)
// Make sure jumping between multiple different types of middleware works
asyncRoutes.grouped(TestAsyncMiddleware(number: 2), TestMiddleware(number: 3), TestAsyncMiddleware(number: 4), TestMiddleware(number: 5)).get("middleware") { req async throws -> String in
return "OK"
}
let basicAuthRoutes = asyncRoutes.grouped(Test.authenticator(), Test.guardMiddleware())
basicAuthRoutes.get("auth") { req async throws -> String in
return try req.auth.require(Test.self).name
}
struct Test: Authenticatable {
static func authenticator() -> AsyncAuthenticator {
TestAuthenticator()
}
var name: String
}
struct TestAuthenticator: AsyncBasicAuthenticator {
typealias User = Test
func authenticate(basic: BasicAuthorization, for request: Request) async throws {
if basic.username == "test" && basic.password == "secret" {
let test = Test(name: "Vapor")
request.auth.login(test)
}
}
}
}
struct TestError: AbortError, DebuggableError {
var status: HTTPResponseStatus {
.internalServerError
}
var reason: String {
"This is a test."
}
var source: ErrorSource?
init(
file: String = #fileID,
function: String = #function,
line: UInt = #line,
column: UInt = #column,
range: Range<UInt>? = nil
) {
self.source = .init(
file: file,
function: function,
line: line,
column: column,
range: range
)
}
}
struct TestAsyncMiddleware: AsyncMiddleware {
let number: Int
func respond(to request: Request, chainingTo next: AsyncResponder) async throws -> Response {
request.logger.debug("In async middleware - \(number)")
let response = try await next.respond(to: request)
request.logger.debug("In async middleware way out - \(number)")
return response
}
}
struct TestMiddleware: Middleware {
let number: Int
func respond(to request: Request, chainingTo next: Responder) -> EventLoopFuture<Response> {
request.logger.debug("In non-async middleware - \(number)")
return next.respond(to: request).map { response in
request.logger.debug("In non-async middleware way out - \(self.number)")
return response
}
}
}
================================================
FILE: Sources/Vapor/Application.swift
================================================
import ConsoleKit
import Logging
import NIOConcurrencyHelpers
import NIOCore
import NIOPosix
/// Core type representing a Vapor application.
public final class Application: Sendable {
public var environment: Environment {
get {
self._environment.withLockedValue { $0 }
}
set {
self._environment.withLockedValue { $0 = newValue }
}
}
public var storage: Storage {
get {
self._storage.withLockedValue { $0 }
}
set {
self._storage.withLockedValue { $0 = newValue }
}
}
public var didShutdown: Bool {
self._didShutdown.withLockedValue { $0 }
}
public var logger: Logger {
get {
self._logger.withLockedValue { $0 }
}
set {
self._logger.withLockedValue { $0 = newValue }
}
}
/// If enabled, tracing propagation is automatically handled by restoring & setting `request.serviceContext` automatically across Vapor-internal EventLoopFuture boundaries.
/// If disabled, traces will not automatically nest, and the user should restore & set `request.serviceContext` manually where needed.
/// There are performance implications to enabling this feature.
public var traceAutoPropagation: Bool {
get {
self._traceAutoPropagation.withLockedValue { $0 }
}
set {
self._traceAutoPropagation.withLockedValue { $0 = newValue }
}
}
public struct Lifecycle: Sendable {
var handlers: [LifecycleHandler]
init() {
self.handlers = []
}
public mutating func use(_ handler: LifecycleHandler) {
self.handlers.append(handler)
}
}
public var lifecycle: Lifecycle {
get {
self._lifecycle.withLockedValue { $0 }
}
set {
self._lifecycle.withLockedValue { $0 = newValue }
}
}
public final class Locks: Sendable {
public let main: NIOLock
// Is there a type we can use to make this Sendable but reuse the existing lock we already have?
private let storage: NIOLockedValueBox<[ObjectIdentifier: NIOLock]>
init() {
self.main = .init()
self.storage = .init([:])
}
public func lock<Key>(for key: Key.Type) -> NIOLock
where Key: LockKey {
self.main.withLock {
self.storage.withLockedValue {
$0.insertOrReturn(.init(), at: .init(Key.self))
}
}
}
}
public var locks: Locks {
get {
self._locks.withLockedValue { $0 }
}
set {
self._locks.withLockedValue { $0 = newValue }
}
}
public var sync: NIOLock {
self.locks.main
}
public enum EventLoopGroupProvider: Sendable {
case shared(EventLoopGroup)
@available(*, deprecated, renamed: "singleton", message: "Use '.singleton' for a shared 'EventLoopGroup', for better performance")
case createNew
public static var singleton: EventLoopGroupProvider {
.shared(MultiThreadedEventLoopGroup.singleton)
}
}
public let eventLoopGroupProvider: EventLoopGroupProvider
public let eventLoopGroup: EventLoopGroup
internal let isBooted: NIOLockedValueBox<Bool>
private let _environment: NIOLockedValueBox<Environment>
private let _storage: NIOLockedValueBox<Storage>
private let _didShutdown: NIOLockedValueBox<Bool>
private let _logger: NIOLockedValueBox<Logger>
private let _traceAutoPropagation: NIOLockedValueBox<Bool>
private let _lifecycle: NIOLockedValueBox<Lifecycle>
private let _locks: NIOLockedValueBox<Locks>
@available(*, noasync, message: "This initialiser cannot be used in async contexts, use Application.make(_:_:) instead")
@available(*, deprecated, message: "Migrate to using the async APIs. Use use Application.make(_:_:) instead")
public convenience init(
_ environment: Environment = .development,
_ eventLoopGroupProvider: EventLoopGroupProvider = .singleton
) {
self.init(environment, eventLoopGroupProvider, async: false, logger: Logger(label: "codes.vapor.application"))
self.asyncCommands.use(self.servers.command, as: "serve", isDefault: true)
DotEnvFile.load(for: environment, on: .shared(self.eventLoopGroup), fileio: self.fileio, logger: self.logger)
}
// async flag here is just to stop the compiler from complaining about duplicates
private init(_ environment: Environment = .development, _ eventLoopGroupProvider: EventLoopGroupProvider = .singleton, async: Bool, logger: Logger) {
self._environment = .init(environment)
self.eventLoopGroupProvider = eventLoopGroupProvider
switch eventLoopGroupProvider {
case .shared(let group):
self.eventLoopGroup = group
case .createNew:
self.eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount)
}
self._locks = .init(.init())
self._didShutdown = .init(false)
self._logger = .init(logger)
self._traceAutoPropagation = .init(false)
self._storage = .init(.init(logger: logger))
self._lifecycle = .init(.init())
self.isBooted = .init(false)
self.core.initialize(asyncEnvironment: async)
self.caches.initialize()
self.views.initialize()
self.passwords.use(.bcrypt)
self.sessions.initialize()
self.sessions.use(.memory)
self.responder.initialize()
self.responder.use(.default)
self.servers.initialize()
self.servers.use(.http)
self.clients.initialize()
self.clients.use(.http)
self.asyncCommands.use(RoutesCommand(), as: "routes")
}
public static func make(_ environment: Environment = .development, _ eventLoopGroupProvider: EventLoopGroupProvider = .singleton) async throws -> Application {
try await make(environment, eventLoopGroupProvider, logger: Logger(label: "codes.vapor.application"))
}
public static func make(_ environment: Environment = .development, _ eventLoopGroupProvider: EventLoopGroupProvider = .singleton, logger: Logger) async throws -> Application {
let app = Application(environment, eventLoopGroupProvider, async: true, logger: logger)
await app.asyncCommands.use(app.servers.asyncCommand, as: "serve", isDefault: true)
await DotEnvFile.load(for: app.environment, fileio: app.fileio, logger: app.logger)
return app
}
/// Starts the ``Application`` using the ``start()`` method, then waits for any running tasks to complete.
/// If your application is started without arguments, the default argument is used.
///
/// Under normal circumstances, ``run()`` runs until a shutdown is triggered, then waits for the web server to
/// (manually) shut down before returning.
///
/// > Warning: You should probably be using ``execute()`` instead of this method.
@available(*, noasync, message: "Use the async execute() method instead.")
public func run() throws {
do {
try self.start()
try self.running?.onStop.wait()
} catch {
self.logger.report(error: error)
throw error
}
}
/// Starts the ``Application`` asynchronous using the ``startup()`` method, then waits for any running tasks
/// to complete. If your application is started without arguments, the default argument is used.
///
/// Under normal circumstances, ``execute()`` runs until a shutdown is triggered, then wait for the web server to
/// (manually) shut down before returning.
public func execute() async throws {
do {
try await self.startup()
try await self.running?.onStop.get()
} catch {
self.logger.report(error: error)
throw error
}
}
/// When called, this will execute the startup command provided through an argument. If no startup command is
/// provided, the default is used. Under normal circumstances, this will start running Vapor's webserver.
///
/// If you start Vapor through this method, you'll need to prevent your Swift Executable from closing yourself.
/// If you want to run your ``Application`` indefinitely, or until your code shuts the application down,
/// use ``run()`` instead.
///
/// > Warning: You should probably be using ``startup()`` instead of this method.
@available(*, noasync, message: "Use the async startup() method instead.")
public func start() throws {
try self.eventLoopGroup.any().makeFutureWithTask { try await self.startup() }.wait()
}
/// When called, this will asynchronously execute the startup command provided through an argument. If no startup
/// command is provided, the default is used. Under normal circumstances, this will start running Vapor's webserver.
///
/// If you start Vapor through this method, you'll need to prevent your Swift Executable from closing yourself.
/// If you want to run your ``Application`` indefinitely, or until your code shuts the application down,
/// use ``execute()`` instead.
public func startup() async throws {
try await self.asyncBoot()
let combinedCommands = AsyncCommands(
commands: self.asyncCommands.commands.merging(self.commands.commands) { $1 },
defaultCommand: self.asyncCommands.defaultCommand ?? self.commands.defaultCommand,
enableAutocomplete: self.asyncCommands.enableAutocomplete || self.commands.enableAutocomplete
).group()
var context = CommandContext(console: self.console, input: self.environment.commandInput)
context.application = self
try await self.console.run(combinedCommands, with: context)
}
@available(*, noasync, message: "This can potentially block the thread and should not be called in an async context", renamed: "asyncBoot()")
/// Called when the applications starts up, will trigger the lifecycle handlers
public func boot() throws {
try self.isBooted.withLockedValue { booted in
guard !booted else {
return
}
booted = true
try self.lifecycle.handlers.forEach { try $0.willBoot(self) }
try self.lifecycle.handlers.forEach { try $0.didBoot(self) }
}
}
/// Called when the applications starts up, will trigger the lifecycle handlers. The asynchronous version of ``boot()``
public func asyncBoot() async throws {
/// Skip the boot process if already booted
guard !self.isBooted.withLockedValue({
var result = true
swap(&$0, &result)
return result
}) else {
return
}
for handler in self.lifecycle.handlers {
try await handler.willBootAsync(self)
}
for handler in self.lifecycle.handlers {
try await handler.didBootAsync(self)
}
}
@available(*, noasync, message: "This can block the thread and should not be called in an async context", renamed: "asyncShutdown()")
public func shutdown() {
assert(!self.didShutdown, "Application has already shut down")
self.logger.debug("Application shutting down")
self.logger.trace("Shutting down providers")
self.lifecycle.handlers.reversed().forEach { $0.shutdown(self) }
self.lifecycle.handlers = []
self.logger.trace("Clearing Application storage")
self.storage.shutdown()
self.storage.clear()
switch self.eventLoopGroupProvider {
case .shared:
self.logger.trace("Running on shared EventLoopGroup. Not shutting down EventLoopGroup.")
case .createNew:
self.logger.trace("Shutting down EventLoopGroup")
do {
try self.eventLoopGroup.syncShutdownGracefully()
} catch {
self.logger.warning("Shutting down EventLoopGroup failed: \(error)")
}
}
self._didShutdown.withLockedValue { $0 = true }
self.logger.trace("Application shutdown complete")
}
public func asyncShutdown() async throws {
assert(!self.didShutdown, "Application has already shut down")
self.logger.debug("Application shutting down")
self.logger.trace("Shutting down providers")
for handler in self.lifecycle.handlers.reversed() {
await handler.shutdownAsync(self)
}
self.lifecycle.handlers = []
self.logger.trace("Clearing Application storage")
await self.storage.asyncShutdown()
self.storage.clear()
switch self.eventLoopGroupProvider {
case .shared:
self.logger.trace("Running on shared EventLoopGroup. Not shutting down EventLoopGroup.")
case .createNew:
self.logger.trace("Shutting down EventLoopGroup")
do {
try await self.eventLoopGroup.shutdownGracefully()
} catch {
self.logger.warning("Shutting down EventLoopGroup failed: \(error)")
}
}
self._didShutdown.withLockedValue { $0 = true }
self.logger.trace("Application shutdown complete")
}
deinit {
self.logger.trace("Application deinitialized, goodbye!")
if !self.didShutdown {
self.logger.error("Application.shutdown() was not called before Application deinitialized.")
self.shutdown()
}
}
}
public protocol LockKey {}
extension Dictionary {
fileprivate mutating func insertOrReturn(_ value: @autoclosure () -> Value, at key: Key) -> Value {
if let existing = self[key] {
return existing
}
let newValue = value()
self[key] = newValue
return newValue
}
}
================================================
FILE: Sources/Vapor/Authentication/AuthenticationCache.swift
================================================
import NIOConcurrencyHelpers
extension Request {
/// Helper for accessing authenticated objects.
/// See `Authenticator` for more information.
public var auth: Authentication {
return .init(request: self)
}
/// Request helper for storing and fetching authenticated objects.
public struct Authentication {
let request: Request
init(request: Request) {
self.request = request
}
}
}
extension Request.Authentication {
/// Authenticates the supplied instance for this request.
public func login<A>(_ instance: A)
where A: Authenticatable
{
self.cache[A.self] = UnsafeAuthenticationBox(instance)
}
/// Unauthenticates an authenticatable type.
public func logout<A>(_ type: A.Type = A.self)
where A: Authenticatable
{
self.cache[A.self] = nil
}
/// Returns an instance of the supplied type. Throws if no
/// instance of that type has been authenticated or if there
/// was a problem.
@discardableResult public func require<A>(_ type: A.Type = A.self) throws -> A
where A: Authenticatable
{
guard let a = self.get(A.self) else {
throw Abort(.unauthorized)
}
return a
}
/// Returns the authenticated instance of the supplied type.
/// - note: `nil` if no type has been authed.
public func get<A>(_ type: A.Type = A.self) -> A?
where A: Authenticatable
{
return self.cache[A.self]?.authenticated
}
/// Returns `true` if the type has been authenticated.
public func has<A>(_ type: A.Type = A.self) -> Bool
where A: Authenticatable
{
return self.get(A.self) != nil
}
private final class Cache: Sendable {
private let storage: NIOLockedValueBox<[ObjectIdentifier: Sendable]>
init() {
self.storage = .init([:])
}
internal subscript<A>(_ type: A.Type) -> UnsafeAuthenticationBox<A>?
where A: Authenticatable
{
get {
storage.withLockedValue { $0[ObjectIdentifier(A.self)] as? UnsafeAuthenticationBox<A> }
}
set {
storage.withLockedValue { $0[ObjectIdentifier(A.self)] = newValue }
}
}
}
private struct CacheKey: StorageKey {
typealias Value = Cache
}
private var cache: Cache {
get {
if let existing = self.request.storage[CacheKey.self] {
return existing
} else {
let new = Cache()
self.request.storage[CacheKey.self] = new
return new
}
}
set {
self.request.storage[CacheKey.self] = newValue
}
}
}
// This is to get around the fact that for legacy reasons we can't enforce Sendability on Authenticatable
// types (e.g. Fluent 4 models can never be Sendable because they're reference types with mutable values
// required by protocols and property wrappers). This allows us to store the Authenticatable type in a
// safe-most-of-the-time way. This does introduce an edge case where type could be stored and mutated in
// multiple places. But given how Vapor and its users use Authentication this should almost never
// occur and it was decided the trade-off was acceptable
// As the name implies, the usage of this is unsafe because it disables the sendable checking of the
// compiler and does not add any synchronization.
@usableFromInline
internal struct UnsafeAuthenticationBox<A>: @unchecked Sendable {
@usableFromInline
let authenticated: A
@inlinable
init(_ authenticated: A) {
self.authenticated = authenticated
}
}
================================================
FILE: Sources/Vapor/Authentication/Authenticator.swift
================================================
import NIOCore
/// Capable of being authenticated.
public protocol Authenticatable { }
/// Helper for creating authentication middleware.
///
/// See `RequestAuthenticator` and `SessionAuthenticator` for more information.
public protocol Authenticator: Middleware { }
/// Help for creating authentication middleware based on `Request`.
///
/// `Authenticator`'s use the incoming request to check for authentication information.
/// If valid authentication credentials are present, the authenticated user is added to `req.auth`.
public protocol RequestAuthenticator: Authenticator {
func authenticate(request: Request) -> EventLoopFuture<Void>
}
extension RequestAuthenticator {
public func respond(to request: Request, chainingTo next: Responder) -> EventLoopFuture<Response> {
return self.authenticate(request: request).flatMap {
next.respond(to: request)
}
}
}
// MARK: Basic
/// Helper for creating authentication middleware using the Basic authorization header.
public protocol BasicAuthenticator: RequestAuthenticator {
func authenticate(basic: BasicAuthorization, for request: Request) -> EventLoopFuture<Void>
}
extension BasicAuthenticator {
public func authenticate(request: Request) -> EventLoopFuture<Void> {
guard let basicAuthorization = request.headers.basicAuthorization else {
return request.eventLoop.makeSucceededFuture(())
}
return self.authenticate(basic: basicAuthorization, for: request)
}
}
// MARK: Bearer
/// Helper for creating authentication middleware using the Bearer authorization header.
public protocol BearerAuthenticator: RequestAuthenticator {
func authenticate(bearer: BearerAuthorization, for request: Request) -> EventLoopFuture<Void>
}
extension BearerAuthenticator {
public func authenticate(request: Request) -> EventLoopFuture<Void> {
guard let bearerAuthorization = request.headers.bearerAuthorization else {
return request.eventLoop.makeSucceededFuture(())
}
return self.authenticate(bearer: bearerAuthorization, for: request)
}
}
// MARK: Credentials
/// Helper for creating authentication middleware using request body contents.
public protocol CredentialsAuthenticator: RequestAuthenticator {
associatedtype Credentials: Content
func authenticate(credentials: Credentials, for request: Request) -> EventLoopFuture<Void>
}
extension CredentialsAuthenticator {
public func authenticate(request: Request) -> EventLoopFuture<Void> {
return request.body.collect(max: nil).flatMap { _ -> EventLoopFuture<Void> in
let credentials: Credentials
do {
credentials = try request.content.decode(Credentials.self)
} catch {
return request.eventLoop.makeSucceededFuture(())
}
return self.authenticate(credentials: credentials, for: request)
}
}
}
================================================
FILE: Sources/Vapor/Authentication/BasicAuthorization.swift
================================================
import Foundation
import NIOHTTP1
/// A basic username and password.
public struct BasicAuthorization: Sendable {
/// The username, sometimes an email address
public let username: String
/// The plaintext password
public let password: String
/// Create a new `BasicAuthorization`.
public init(username: String, password: String) {
self.username = username
self.password = password
}
}
extension HTTPHeaders {
/// Access or set the `Authorization: Basic: ...` header.
public var basicAuthorization: BasicAuthorization? {
get {
guard let string = self.first(name: .authorization) else {
return nil
}
let headerParts = string.split(separator: " ")
guard headerParts.count == 2 else {
return nil
}
guard headerParts[0].lowercased() == "basic" else {
return nil
}
guard let decodedToken = Data(base64Encoded: .init(headerParts[1])) else {
return nil
}
let parts = String.init(decoding: decodedToken, as: UTF8.self).split(separator: ":", maxSplits: 1, omittingEmptySubsequences: false)
guard parts.count == 2 else {
return nil
}
return .init(username: .init(parts[0]), password: .init(parts[1]))
}
set {
if let basic = newValue {
let credentials = "\(basic.username):\(basic.password)"
let encoded = Data(credentials.utf8).base64EncodedString()
replaceOrAdd(name: .authorization, value: "Basic \(encoded)")
} else {
remove(name: .authorization)
}
}
}
}
================================================
FILE: Sources/Vapor/Authentication/BearerAuthorization.swift
================================================
import NIOHTTP1
/// A bearer token.
public struct BearerAuthorization: Sendable {
/// The plaintext token
public let token: String
/// Create a new `BearerAuthorization`
public init(token: String) {
self.token = token
}
}
extension HTTPHeaders {
/// Access or set the `Authorization: Bearer: ...` header.
public var bearerAuthorization: BearerAuthorization? {
get {
guard let string = self.first(name: .authorization) else {
return nil
}
let headerParts = string.split(separator: " ")
guard headerParts.count == 2 else {
return nil
}
guard headerParts[0].lowercased() == "bearer" else {
return nil
}
return .init(token: String(headerParts[1]))
}
set {
if let bearer = newValue {
replaceOrAdd(name: .authorization, value: "Bearer \(bearer.token)")
} else {
remove(name: .authorization)
}
}
}
}
================================================
FILE: Sources/Vapor/Authentication/GuardMiddleware.swift
================================================
import NIOCore
extension Authenticatable {
/// This middleware ensures that an `Authenticatable` type `A` has been authenticated
/// by a previous `Middleware` or throws an `Error`. The middlewares that actually perform
/// authentication will _not_ throw errors if they fail to authenticate the user (except in
/// some exceptional cases like malformed data). This allows the middlewares to be composed
/// together to create chains of authentication for multiple user types.
///
/// Use this middleware to protect routes that might not otherwise attempt to access the
/// authenticated user (which always requires prior authentication).
///
/// Use `Authenticatable.guardMiddleware(...)` to create an instance.
///
/// Use this middleware in conjunction with other middleware such as `BearerAuthenticator`
/// and `BasicAuthenticator` to do the actual authentication.
///
/// - parameters:
/// - throwing: `Error` to throw if the type is not authed.
public static func guardMiddleware(
throwing error: Error = Abort(.unauthorized, reason: "\(Self.self) not authenticated.")
) -> Middleware {
return GuardAuthenticationMiddleware<Self>(throwing: error)
}
}
private final class GuardAuthenticationMiddleware<A>: Middleware
where A: Authenticatable
{
/// Error to throw when guard fails.
private let error: Error
/// Creates a new `GuardAuthenticationMiddleware`.
///
/// - parameters:
/// - type: `Authenticatable` type to ensure is authed.
/// - error: `Error` to throw if the type is not authed.
internal init(_ type: A.Type = A.self, throwing error: Error) {
self.error = error
}
public func respond(to request: Request, chainingTo next: Responder) -> EventLoopFuture<Response> {
guard request.auth.has(A.self) else {
return request.eventLoop.makeFailedFuture(self.error)
}
return next.respond(to: request)
}
}
================================================
FILE: Sources/Vapor/Authentication/RedirectMiddleware.swift
================================================
import NIOCore
extension Authenticatable {
/// Basic middleware to redirect unauthenticated requests to the supplied path
///
/// - parameters:
/// - path: The path to redirect to if the request is not authenticated
public static func redirectMiddleware(path: String) -> Middleware {
self.redirectMiddleware(makePath: { _ in path })
}
/// Basic middleware to redirect unauthenticated requests to the supplied path
///
/// - parameters:
/// - makePath: The closure that returns the redirect path based on the given `Request` object
@preconcurrency public static func redirectMiddleware(makePath: @Sendable @escaping (Request) -> String) -> Middleware {
RedirectMiddleware<Self>(Self.self, makePath: makePath)
}
}
private final class RedirectMiddleware<A>: Middleware
where A: Authenticatable
{
let makePath: @Sendable (Request) -> String
@preconcurrency init(_ authenticatableType: A.Type = A.self, makePath: @Sendable @escaping (Request) -> String) {
self.makePath = makePath
}
func respond(to request: Request, chainingTo next: Responder) -> EventLoopFuture<Response> {
if request.auth.has(A.self) {
return next.respond(to: request)
}
let redirect = request.redirect(to: self.makePath(request))
return request.eventLoop.makeSucceededFuture(redirect)
}
}
================================================
FILE: Sources/Vapor/Authentication/SessionAuthenticatable.swift
================================================
import NIOCore
/// Helper for creating authentication middleware in conjunction with `SessionsMiddleware`.
public protocol SessionAuthenticator: Authenticator {
associatedtype User: SessionAuthenticatable
/// Authenticate a model with the supplied ID.
func authenticate(sessionID: User.SessionID, for request: Request) -> EventLoopFuture<Void>
}
extension SessionAuthenticator {
public func respond(to request: Request, chainingTo next: Responder) -> EventLoopFuture<Response> {
// if the user has already been authenticated
// by a previous middleware, continue
if request.auth.has(User.self) {
return next.respond(to: request)
}
let future: EventLoopFuture<Void>
if request.hasSession, let aID = request.session.authenticated(User.self) {
// try to find user with id from session
future = self.authenticate(sessionID: aID, for: request)
} else {
// no need to authenticate
future = request.eventLoop.makeSucceededFuture(())
}
// map the auth future to a response
return future.flatMap { _ in
// respond to the request
return next.respond(to: request).map { response in
if let user = request.auth.get(User.self) {
// if a user has been authed (or is still authed), store in the session
request.session.authenticate(user)
} else if request.hasSession {
// if no user is authed, it's possible they've been unauthed.
// remove from session.
request.session.unauthenticate(User.self)
}
return response
}
}
}
}
/// Models conforming to this protocol can have their authentication
/// status cached using `SessionAuthenticator`.
public protocol SessionAuthenticatable: Authenticatable {
/// Session identifier type.
associatedtype SessionID: LosslessStringConvertible
/// Unique session identifier.
var sessionID: SessionID { get }
}
private extension SessionAuthenticatable {
static var sessionName: String {
return "\(Self.self)"
}
}
extension Session {
/// Authenticates the model into the session.
public func authenticate<A>(_ a: A)
where A: SessionAuthenticatable
{
self.data["_" + A.sessionName + "Session"] = a.sessionID.description
}
/// Un-authenticates the model from the session.
public func unauthenticate<A>(_ a: A.Type)
where A: SessionAuthenticatable
{
self.data["_" + A.sessionName + "Session"] = nil
}
/// Returns the authenticatable type's ID if it exists
/// in the session data.
public func authenticated<A>(_ a: A.Type) -> A.SessionID?
where A: SessionAuthenticatable
{
self.data["_" + A.sessionName + "Session"]
.flatMap { A.SessionID.init($0) }
}
}
================================================
FILE: Sources/Vapor/Bcrypt/Bcrypt.swift
================================================
import Foundation
import CVaporBcrypt
// MARK: BCrypt
/// Creates and verifies BCrypt hashes.
///
/// Use BCrypt to create hashes for sensitive information like passwords.
///
/// try BCrypt.hash("vapor", cost: 4)
///
/// BCrypt uses a random salt each time it creates a hash. To verify hashes, use the `verify(_:matches)` method.
///
/// let hash = try BCrypt.hash("vapor", cost: 4)
/// try BCrypt.verify("vapor", created: hash) // true
///
/// https://en.wikipedia.org/wiki/Bcrypt
public var Bcrypt: BCryptDigest {
return .init()
}
/// Creates and verifies BCrypt hashes. Normally you will not need to initialize one of these classes and you will
/// use the global `BCrypt` convenience instead.
///
/// try BCrypt.hash("vapor", cost: 4)
///
/// See `BCrypt` for more information.
public final class BCryptDigest {
/// Creates a new `BCryptDigest`. Use the global `BCrypt` convenience variable.
public init() { }
/// Creates a new BCrypt hash with a randomly generated salt.
/// The result can be stored in a database.
public func hash(_ plaintext: String, cost: Int = 12) throws -> String {
guard cost >= BCRYPT_MINLOGROUNDS && cost <= 31 else {
throw BcryptError.invalidCost
}
return try self.hash(plaintext, salt: self.generateSalt(cost: cost))
}
public func hash(_ plaintext: String, salt: String) throws -> String {
guard isSaltValid(salt) else {
throw BcryptError.invalidSalt
}
let originalAlgorithm: Algorithm
if salt.count == Algorithm.saltCount {
// user provided salt
originalAlgorithm = ._2b
} else {
// full salt, not user provided
let revisionString = String(salt.prefix(4))
if let parsedRevision = Algorithm(rawValue: revisionString) {
originalAlgorithm = parsedRevision
} else {
throw BcryptError.invalidSalt
}
}
// OpenBSD doesn't support 2y revision.
let normalizedSalt: String
if originalAlgorithm == Algorithm._2y {
// Replace with 2b.
normalizedSalt = Algorithm._2b.rawValue + salt.dropFirst(originalAlgorithm.revisionCount)
} else {
normalizedSalt = salt
}
let hashedBytes = UnsafeMutablePointer<Int8>.allocate(capacity: 128)
defer { hashedBytes.deallocate() }
let hashingResult = vapor_bcrypt_hashpass(
plaintext,
normalizedSalt,
hashedBytes,
128
)
guard hashingResult == 0 else {
throw BcryptError.hashFailure
}
return originalAlgorithm.rawValue
+ String(cString: hashedBytes)
.dropFirst(originalAlgorithm.revisionCount)
}
/// Verifies an existing BCrypt hash matches the supplied plaintext value. Verification works by parsing the salt and version from
/// the existing digest and using that information to hash the plaintext data. If hash digests match, this method returns `true`.
///
/// let hash = try BCrypt.hash("vapor", cost: 4)
/// try BCrypt.verify("vapor", created: hash) // true
/// try BCrypt.verify("foo", created: hash) // false
///
/// - parameters:
/// - plaintext: Plaintext data to digest and verify.
/// - hash: Existing BCrypt hash to parse version, salt, and existing digest from.
/// - throws: `CryptoError` if hashing fails or if data conversion fails.
/// - returns: `true` if the hash was created from the supplied plaintext data.
public func verify(_ plaintext: String, created hash: String) throws -> Bool {
guard let hashVersion = Algorithm(rawValue: String(hash.prefix(4))) else {
throw BcryptError.invalidHash
}
let hashSalt = String(hash.prefix(hashVersion.fullSaltCount))
guard !hashSalt.isEmpty, hashSalt.count == hashVersion.fullSaltCount else {
throw BcryptError.invalidHash
}
let hashChecksum = String(hash.suffix(hashVersion.checksumCount))
guard !hashChecksum.isEmpty, hashChecksum.count == hashVersion.checksumCount else {
throw BcryptError.invalidHash
}
let messageHash = try self.hash(plaintext, salt: hashSalt)
let messageHashChecksum = String(messageHash.suffix(hashVersion.checksumCount))
return messageHashChecksum.secureCompare(to: hashChecksum)
}
// MARK: Private
/// Generates string (29 chars total) containing the algorithm information + the cost + base-64 encoded 22 character salt
///
/// E.g: $2b$05$J/dtt5ybYUTCJ/dtt5ybYO
/// $AA$ => Algorithm
/// $CC$ => Cost
/// SSSSSSSSSSSSSSSSSSSSSS => Salt
///
/// Allowed charset for the salt: [./A-Za-z0-9]
///
/// - parameters:
/// - cost: Desired complexity. Larger `cost` values take longer to hash and verify.
/// - algorithm: Revision to use (2b by default)
/// - seed: Salt (without revision data). Generated if not provided. Must be 16 chars long.
/// - returns: Complete salt
private func generateSalt(cost: Int, algorithm: Algorithm = ._2b, seed: [UInt8]? = nil) -> String {
let randomData: [UInt8]
if let seed = seed {
randomData = seed
} else {
randomData = [UInt8].random(count: 16)
}
let encodedSalt = base64Encode(randomData)
return
algorithm.rawValue +
(cost < 10 ? "0\(cost)" : "\(cost)" ) + // 0 padded
"$" +
encodedSalt
}
/// Checks whether the provided salt is valid or not
///
/// - parameters:
/// - salt: Salt to be checked
/// - returns: True if the provided salt is valid
private func isSaltValid(_ salt: String) -> Bool {
// Includes revision and cost info (count should be 29)
let revisionString = String(salt.prefix(4))
if let algorithm = Algorithm(rawValue: revisionString) {
return salt.count == algorithm.fullSaltCount
} else {
// Does not include revision and cost info (count should be 22)
return salt.count == Algorithm.saltCount
}
}
/// Encodes the provided plaintext using OpenBSD's custom base-64 encoding (Radix-64)
///
/// - parameters:
/// - data: Data to be base64 encoded.
/// - returns: Base 64 encoded plaintext
private func base64Encode(_ data: [UInt8]) -> String {
let encodedBytes = UnsafeMutablePointer<Int8>.allocate(capacity: 25)
defer { encodedBytes.deallocate() }
let res = data.withUnsafeBytes { bytes in
vapor_encode_base64(encodedBytes, bytes.baseAddress?.assumingMemoryBound(to: UInt8.self), bytes.count)
}
assert(res == 0, "base64 convert failed")
return String(cString: encodedBytes)
}
/// Specific BCrypt algorithm.
private enum Algorithm: String, RawRepresentable {
/// older version
case _2a = "$2a$"
/// format specific to the crypt_blowfish BCrypt implementation, identical to `2b` in all but name.
case _2y = "$2y$"
/// latest revision of the official BCrypt algorithm, current default
case _2b = "$2b$"
/// Revision's length, including the `$` symbols
var revisionCount: Int {
return 4
}
/// Salt's length (includes revision and cost info)
var fullSaltCount: Int {
return 29
}
/// Checksum's length
var checksumCount: Int {
return 31
}
/// Salt's length (does NOT include neither revision nor cost info)
static var saltCount: Int {
return 22
}
}
}
public enum BcryptError: Swift.Error, CustomStringConvertible, LocalizedError {
case invalidCost
case invalidSalt
case hashFailure
case invalidHash
public var errorDescription: String? {
return self.description
}
public var description: String {
return "Bcrypt error: \(self.reason)"
}
var reason: String {
switch self {
case .invalidCost:
return "Cost should be between 4 and 31"
case .invalidSalt:
return "Provided salt has the incorrect format"
case .hashFailure:
return "Unable to compute hash"
case .invalidHash:
return "Invalid hash formatting"
}
}
}
================================================
FILE: Sources/Vapor/Cache/Application+Cache.swift
================================================
import NIOConcurrencyHelpers
extension Application {
/// Controls application's configured caches.
///
/// app.caches.use(.memory)
///
public var caches: Caches {
.init(application: self)
}
/// Current application cache. See `Request.cache` for caching in request handlers.
public var cache: Cache {
guard let makeCache = self.caches.storage.makeCache.withLockedValue({ $0.factory }) else {
fatalError("No cache configured. Configure with app.caches.use(...)")
}
return makeCache(self)
}
public struct Caches: Sendable {
public struct Provider: Sendable {
let run: @Sendable (Application) -> ()
@preconcurrency public init(_ run: @Sendable @escaping (Application) -> ()) {
self.run = run
}
}
final class Storage: Sendable {
struct CacheFactory {
let factory: (@Sendable (Application) -> Cache)?
}
let makeCache: NIOLockedValueBox<CacheFactory>
init() {
self.makeCache = .init(.init(factory: nil))
}
}
struct Key: StorageKey, Sendable {
typealias Value = Storage
}
public let application: Application
public func use(_ provider: Provider) {
provider.run(self.application)
}
@preconcurrency public func use(_ makeCache: @Sendable @escaping (Application) -> (Cache)) {
self.storage.makeCache.withLockedValue { $0 = .init(factory: makeCache) }
}
func initialize() {
self.application.storage[Key.self] = .init()
self.use(.memory)
}
var storage: Storage {
guard let storage = self.application.storage[Key.self] else {
fatalError("Caches not configured. Configure with app.caches.initialize()")
}
return storage
}
}
}
================================================
FILE: Sources/Vapor/Cache/Cache.swift
================================================
import NIOCore
/// Codable key-value pair cache.
public protocol Cache {
/// Gets a decodable value from the cache. Returns `nil` if not found.
func get<T>(_ key: String, as type: T.Type) -> EventLoopFuture<T?>
where T: Decodable
/// Sets an encodable value into the cache. Existing values are replaced. If `nil`, removes value.
func set<T>(_ key: String, to value: T?) -> EventLoopFuture<Void>
where T: Encodable
/// Sets an encodable value into the cache with an expiry time. Existing values are replaced. If `nil`, removes value.
func set<T>(_ key: String, to value: T?, expiresIn expirationTime: CacheExpirationTime?) -> EventLoopFuture<Void>
where T: Encodable
/// Creates a request-specific cache instance.
func `for`(_ request: Request) -> Self
}
extension Cache {
/// Sets an encodable value into the cache with an expiry time. Existing values are replaced. If `nil`, removes value.
public func set<T>(_ key: String, to value: T?, expiresIn expirationTime: CacheExpirationTime?) -> EventLoopFuture<Void>
where T: Encodable
{
return self.set(key, to: value)
}
public func delete(_ key: String) -> EventLoopFuture<Void>
{
return self.set(key, to: String?.none)
}
/// Gets a decodable value from the cache. Returns `nil` if not found.
public func get<T>(_ key: String) -> EventLoopFuture<T?>
where T: Decodable
{
return self.get(key, as: T.self)
}
}
================================================
FILE: Sources/Vapor/Cache/CacheExpirationTime.swift
================================================
/// Defines the lifetime of an entry in a cache.
public enum CacheExpirationTime: Sendable {
case seconds(Int)
case minutes(Int)
case hours(Int)
case days(Int)
/// Returns the amount of time in seconds.
public var seconds: Int {
switch self {
case let .seconds(seconds):
return seconds
case let .minutes(minutes):
return minutes * 60
case let .hours(hours):
return hours * 60 * 60
case let .days(days):
return days * 24 * 60 * 60
}
}
}
================================================
FILE: Sources/Vapor/Cache/MemoryCache.swift
================================================
import Foundation
import NIOCore
import NIOConcurrencyHelpers
extension Application.Caches {
/// In-memory cache. Thread safe.
/// Not shared between multiple instances of your application.
public var memory: Cache {
MemoryCache(storage: self.memoryStorage, on: self.application.eventLoopGroup.next())
}
private var memoryStorage: MemoryCacheStorage {
let lock = self.application.locks.lock(for: MemoryCacheKey.self)
lock.lock()
defer { lock.unlock() }
if let existing = self.application.storage.get(MemoryCacheKey.self) {
return existing
} else {
let new = MemoryCacheStorage()
self.application.storage.set(MemoryCacheKey.self, to: new)
return new
}
}
}
extension Application.Caches.Provider {
/// In-memory cache. Thread safe.
/// Not shared between multiple instances of your application.
public static var memory: Self {
.init {
$0.caches.use { $0.caches.memory }
}
}
}
private struct MemoryCacheKey: LockKey, StorageKey {
typealias Value = MemoryCacheStorage
}
private actor MemoryCacheStorage: Sendable {
struct CacheEntryBox<T> {
var expiresAt: Date?
var value: T
init(_ value: T) {
self.expiresAt = nil
self.value = value
}
}
private var storage: [String: Any]
private var lock: NIOLock
init() {
self.storage = [:]
self.lock = .init()
}
func get<T>(_ key: String) -> T?
where T: Decodable
{
self.lock.lock()
defer { self.lock.unlock() }
guard let box = self.storage[key] as? CacheEntryBox<T> else { return nil }
if let expiresAt = box.expiresAt, expiresAt < Date() {
self.storage.removeValue(forKey: key)
return nil
}
return box.value
}
func set<T>(_ key: String, to value: T?, expiresIn expirationTime: CacheExpirationTime?)
where T: Encodable
{
self.lock.lock()
defer { self.lock.unlock() }
if let value = value {
var box = CacheEntryBox(value)
if let expirationTime = expirationTime {
box.expiresAt = Date().addingTimeInterval(TimeInterval(expirationTime.seconds))
}
self.storage[key] = box
} else {
self.storage.removeValue(forKey: key)
}
}
}
private struct MemoryCache: Cache {
let storage: MemoryCacheStorage
let eventLoop: EventLoop
init(storage: MemoryCacheStorage, on eventLoop: EventLoop) {
self.storage = storage
self.eventLoop = eventLoop
}
func get<T>(_ key: String, as type: T.Type) -> EventLoopFuture<T?>
where T: Decodable & Sendable
{
self.eventLoop.makeFutureWithTask {
await self.storage.get(key)
}
}
func set<T>(_ key: String, to value: T?) -> EventLoopFuture<Void>
where T: Encodable & Sendable
{
self.set(key, to: value, expiresIn: nil)
}
func set<T>(_ key: String, to value: T?, expiresIn expirationTime: CacheExpirationTime?) -> EventLoopFuture<Void>
where T: Encodable & Sendable
{
self.eventLoop.makeFutureWithTask {
await self.storage.set(key, to: value, expiresIn: expirationTime)
}
}
func `for`(_ request: Request) -> MemoryCache {
.init(storage: self.storage, on: request.eventLoop)
}
}
================================================
FILE: Sources/Vapor/Cache/Request+Cache.swift
================================================
extension Request {
public var cache: Cache {
self.application.cache.for(self)
}
}
================================================
FILE: Sources/Vapor/Client/Application+Clients.swift
================================================
import NIOConcurrencyHelpers
extension Application {
public var clients: Clients {
.init(application: self)
}
public var client: Client {
guard let makeClient = self.clients.storage.makeClient.withLockedValue({ $0.factory }) else {
fatalError("No client configured. Configure with app.clients.use(...)")
}
return makeClient(self)
}
public struct Clients: Sendable {
public struct Provider {
let run: @Sendable (Application) -> ()
@preconcurrency public init(_ run: @Sendable @escaping (Application) -> ()) {
self.run = run
}
}
final class Storage: Sendable {
struct ClientFactory {
let factory: (@Sendable (Application) -> Client)?
}
let makeClient: NIOLockedValueBox<ClientFactory>
init() {
self.makeClient = .init(.init(factory: nil))
}
}
struct Key: StorageKey, Sendable {
typealias Value = Storage
}
func initialize() {
self.application.storage[Key.self] = .init()
}
public func use(_ provider: Provider) {
provider.run(self.application)
}
@preconcurrency public func use(_ makeClient: @Sendable @escaping (Application) -> (Client)) {
self.storage.makeClient.withLockedValue { $0 = .init(factory: makeClient) }
}
public let application: Application
var storage: Storage {
guard let storage = self.application.storage[Key.self] else {
fatalError("Clients not initialized. Initialize with app.clients.initialize()")
}
return storage
}
}
}
================================================
FILE: Sources/Vapor/Client/Client.swift
================================================
import NIOCore
import Logging
import NIOHTTP1
public protocol Client: Sendable {
var eventLoop: EventLoop { get }
var byteBufferAllocator: ByteBufferAllocator { get }
func delegating(to eventLoop: EventLoop) -> Client
func logging(to logger: Logger) -> Client
func allocating(to byteBufferAllocator: ByteBufferAllocator) -> Client
func send(_ request: ClientRequest) -> EventLoopFuture<ClientResponse>
}
extension Client {
public func logging(to logger: Logger) -> Client {
return self
}
public func allocating(to byteBufferAllocator: ByteBufferAllocator) -> Client {
return self
}
public var byteBufferAllocator: ByteBufferAllocator {
return ByteBufferAllocator()
}
}
extension Client {
public func get(_ url: URI, headers: HTTPHeaders = [:], beforeSend: (inout ClientRequest) throws -> () = { _ in }) -> EventLoopFuture<ClientResponse> {
return self.send(.GET, headers: headers, to: url, beforeSend: beforeSend)
}
public func post(_ url: URI, headers: HTTPHeaders = [:], beforeSend: (inout ClientRequest) throws -> () = { _ in }) -> EventLoopFuture<ClientResponse> {
return self.send(.POST, headers: headers, to: url, beforeSend: beforeSend)
}
public func patch(_ url: URI, headers: HTTPHeaders = [:], beforeSend: (inout ClientRequest) throws -> () = { _ in }) -> EventLoopFuture<ClientResponse> {
return self.send(.PATCH, headers: headers, to: url, beforeSend: beforeSend)
}
public func put(_ url: URI, headers: HTTPHeaders = [:], beforeSend: (inout ClientRequest) throws -> () = { _ in }) -> EventLoopFuture<ClientResponse> {
return self.send(.PUT, headers: headers, to: url, beforeSend: beforeSend)
}
public func delete(_ url: URI, headers: HTTPHeaders = [:], beforeSend: (inout ClientRequest) throws -> () = { _ in }) -> EventLoopFuture<ClientResponse> {
return self.send(.DELETE, headers: headers, to: url, beforeSend: beforeSend)
}
public func post<T>(_ url: URI, headers: HTTPHeaders = [:], content: T) -> EventLoopFuture<ClientResponse> where T: Content {
return self.post(url, headers: headers, beforeSend: { try $0.content.encode(content) })
}
public func patch<T>(_ url: URI, headers: HTTPHeaders = [:], content: T) -> EventLoopFuture<ClientResponse> where T: Content {
return self.patch(url, headers: headers, beforeSend: { try $0.content.encode(content) })
}
public func put<T>(_ url: URI, headers: HTTPHeaders = [:], content: T) -> EventLoopFuture<ClientResponse> where T: Content {
return self.put(url, headers: headers, beforeSend: { try $0.content.encode(content) })
}
public func send(
_ method: HTTPMethod,
headers: HTTPHeaders = [:],
to url: URI,
beforeSend: (inout ClientRequest) throws -> () = { _ in }
) -> EventLoopFuture<ClientResponse> {
var request = ClientRequest(method: method, url: url, headers: headers, body: nil, byteBufferAllocator: self.byteBufferAllocator)
do {
try beforeSend(&request)
} catch {
return self.eventLoop.makeFailedFuture(error)
}
return self.send(request)
}
}
================================================
FILE: Sources/Vapor/Client/ClientRequest.swift
================================================
import NIOCore
import NIOHTTP1
import Foundation
public struct ClientRequest: Sendable {
public var method: HTTPMethod
public var url: URI
public var headers: HTTPHeaders
public var body: ByteBuffer?
public var timeout: TimeAmount?
private let byteBufferAllocator: ByteBufferAllocator
public init(
method: HTTPMethod = .GET,
url: URI = "/",
headers: HTTPHeaders = [:],
body: ByteBuffer? = nil,
timeout: TimeAmount?,
byteBufferAllocator: ByteBufferAllocator = ByteBufferAllocator()
) {
self.method = method
self.url = url
self.headers = headers
self.body = body
self.timeout = timeout
self.byteBufferAllocator = byteBufferAllocator
}
public init(
method: HTTPMethod = .GET,
url: URI = "/",
headers: HTTPHeaders = [:],
body: ByteBuffer? = nil,
byteBufferAllocator: ByteBufferAllocator = ByteBufferAllocator()
) {
self.init(method: method,
url: url,
headers: headers,
body: body,
timeout: nil,
byteBufferAllocator: byteBufferAllocator)
}
}
extension ClientRequest {
private struct _URLQueryContainer: URLQueryContainer {
var url: URI
func decode<D>(_ decodable: D.Type, using decoder: URLQueryDecoder) throws -> D
where D: Decodable
{
return try decoder.decode(D.self, from: self.url)
}
mutating func encode<E>(_ encodable: E, using encoder: URLQueryEncoder) throws
where E: Encodable
{
try encoder.encode(encodable, to: &self.url)
}
}
public var query: URLQueryContainer {
get {
return _URLQueryContainer(url: self.url)
}
set {
self.url = (newValue as! _URLQueryContainer).url
}
}
private struct _ContentContainer: ContentContainer {
var body: ByteBuffer?
var headers: HTTPHeaders
let byteBufferAllocator: ByteBufferAllocator
var contentType: HTTPMediaType? {
return self.headers.contentType
}
mutating func encode<E>(_ encodable: E, using encoder: ContentEncoder) throws where E : Encodable {
var body = self.byteBufferAllocator.buffer(capacity: 0)
try encoder.encode(encodable, to: &body, headers: &self.headers)
self.body = body
}
func decode<D>(_ decodable: D.Type, using decoder: ContentDecoder) throws -> D where D : Decodable {
guard let body = self.body else {
throw Abort(.lengthRequired)
}
return try decoder.decode(D.self, from: body, headers: self.headers)
}
mutating func encode<C>(_ content: C, using encoder: ContentEncoder) throws where C : Content {
var content = content
try content.beforeEncode()
var body = self.byteBufferAllocator.buffer(capacity: 0)
try encoder.encode(content, to: &body, headers: &self.headers)
self.body = body
}
func decode<C>(_ content: C.Type, using decoder: ContentDecoder) throws -> C where C : Content {
guard let body = self.body else {
throw Abort(.lengthRequired)
}
var decoded = try decoder.decode(C.self, from: body, headers: self.headers)
try decoded.afterDecode()
return decoded
}
}
public var content: ContentContainer {
get {
return _ContentContainer(body: self.body, headers: self.headers, byteBufferAllocator: self.byteBufferAllocator)
}
set {
let container = (newValue as! _ContentContainer)
self.body = container.body
self.headers = container.headers
}
}
}
================================================
FILE: Sources/Vapor/Client/ClientResponse.swift
================================================
import NIOCore
import NIOHTTP1
import Foundation
public struct ClientResponse: Sendable {
public var status: HTTPStatus
public var headers: HTTPHeaders
public var body: ByteBuffer?
private let byteBufferAllocator: ByteBufferAllocator
public init(status: HTTPStatus = .ok, headers: HTTPHeaders = [:], body: ByteBuffer? = nil, byteBufferAllocator: ByteBufferAllocator = ByteBufferAllocator()) {
self.status = status
self.headers = headers
self.body = body
self.byteBufferAllocator = byteBufferAllocator
}
}
extension ClientResponse {
private struct _ContentContainer: ContentContainer {
var body: ByteBuffer?
var headers: HTTPHeaders
let allocator: ByteBufferAllocator
var contentType: HTTPMediaType? {
return self.headers.contentType
}
mutating func encode<E>(_ encodable: E, using encoder: ContentEncoder) throws where E : Encodable {
var body = self.allocator.buffer(capacity: 0)
try encoder.encode(encodable, to: &body, headers: &self.headers)
self.body = body
}
func decode<D>(_ decodable: D.Type, using decoder: ContentDecoder) throws -> D where D : Decodable {
guard let body = self.body else {
throw Abort(.lengthRequired)
}
return try decoder.decode(D.self, from: body, headers: self.headers)
}
mutating func encode<C>(_ content: C, using encoder: ContentEncoder) throws where C : Content {
var body = self.allocator.buffer(capacity: 0)
var content = content
try content.beforeEncode()
try encoder.encode(content, to: &body, headers: &self.headers)
self.body = body
}
func decode<C>(_ content: C.Type, using decoder: ContentDecoder) throws -> C where C : Content {
guard let body = self.body else {
throw Abort(.lengthRequired)
}
var decoded = try decoder.decode(C.self, from: body, headers: self.headers)
try decoded.afterDecode()
return decoded
}
}
public var content: ContentContainer {
get {
return _ContentContainer(body: self.body, headers: self.headers, allocator: self.byteBufferAllocator)
}
set {
let container = (newValue as! _ContentContainer)
self.body = container.body
self.headers = container.headers
}
}
}
extension ClientResponse: CustomStringConvertible {
public var description: String {
var desc = ["HTTP/1.1 \(status.code) \(status.reasonPhrase)"]
desc += self.headers.map { "\($0.name): \($0.value)" }
if var body = self.body {
let string = body.readString(length: body.readableBytes) ?? ""
desc += ["", string]
}
return desc.joined(separator: "\n")
}
}
extension ClientResponse: ResponseEncodable {
public func encodeResponse(for request: Request) -> EventLoopFuture<Response> {
let body: Response.Body
if let buffer = self.body {
body = .init(buffer: buffer, byteBufferAllocator: request.byteBufferAllocator)
} else {
body = .empty
}
let response = Response(
status: self.status,
headers: self.headers,
body: body
)
return request.eventLoop.makeSucceededFuture(response)
}
}
extension ClientResponse: Codable {
private enum CodingKeys: String, CodingKey {
case status = "status"
case headers = "headers"
case body = "body"
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.status = try container.decode(HTTPStatus.self, forKey: .status)
self.headers = try container.decode(HTTPHeaders.self, forKey: .headers)
let bodyString = try container.decode(String?.self, forKey: .body)
guard let s = bodyString, let bodyData = [UInt8].init(decodingBase64: s) else {
throw Abort(.internalServerError, reason: "Could not decode client response body from base64 string")
}
self.byteBufferAllocator = ByteBufferAllocator()
self.body = self.byteBufferAllocator.buffer(bytes: bodyData)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.status, forKey: .status)
try container.encode(self.headers, forKey: .headers)
if let body = self.body {
let string = body.readableBytesView.base64String()
try container.encode(string, forKey: .body)
} else {
try container.encodeNil(forKey: .body)
}
}
}
extension ClientResponse: Equatable {
public static func == (lhs: Self, rhs: Self) -> Bool {
return lhs.status == rhs.status && lhs.headers == rhs.headers && lhs.body == rhs.body
}
}
================================================
FILE: Sources/Vapor/Client/Request+Client.swift
================================================
extension Request {
public var client: Client {
self.application.client.delegating(to: self.eventLoop).logging(to: self.logger).allocating(to: self.byteBufferAllocator)
}
}
================================================
FILE: Sources/Vapor/Commands/BootCommand.swift
================================================
import ConsoleKit
/// Boots the `Application` then exits successfully.
///
/// $ swift run Run boot
/// Done.
///
public final class BootCommand: AsyncCommand {
// See `AsyncCommand`.
public struct Signature: CommandSignature {
public init() { }
}
// See `AsyncCommand`.
public var help: String {
return "Boots the application's providers."
}
/// Create a new `BootCommand`.
public init() { }
// See `AsyncCommand`.
public func run(using context: ConsoleKitCommands.CommandContext, signature: Signature) async throws {
context.console.success("Done.")
}
}
================================================
FILE: Sources/Vapor/Commands/CommandContext+Application.swift
================================================
import ConsoleKit
extension CommandContext {
public var application: Application {
get {
guard let application = self.userInfo["application"] as? Application else {
fatalError("Application not set on context")
}
return application
}
set {
self.userInfo["application"] = newValue
}
}
}
================================================
FILE: Sources/Vapor/Commands/RoutesCommand.swift
================================================
import ConsoleKit
import RoutingKit
/// Displays all routes registered to the `Application`'s `Router` in an ASCII-formatted table.
///
/// $ swift run Run routes
/// +------+------------------+
/// | GET | /search |
/// +------+------------------+
/// | GET | /hash/:string |
/// +------+------------------+
///
/// A colon preceding a path component indicates a variable parameter. A colon with no text following
/// is a parameter whose result will be discarded.
///
/// The path will be displayed with the same syntax that is used to register a route.
public final class RoutesCommand: AsyncCommand {
public struct Signature: CommandSignature {
public init() { }
}
public var help: String {
return "Displays all registered routes."
}
init() { }
public func run(using context: ConsoleKitCommands.CommandContext, signature: Signature) async throws {
let routes = context.application.routes
let includeDescription = !routes.all.filter { $0.userInfo["description"] != nil }.isEmpty
let pathSeparator = "/".consoleText()
context.console.outputASCIITable(routes.all.map { route -> [ConsoleText] in
var column = [route.method.rawValue.consoleText()]
if route.path.isEmpty {
column.append(pathSeparator)
} else {
column.append(route.path
.map { pathSeparator + $0.consoleText() }
.reduce("".consoleText(), +)
)
}
if includeDescription {
let desc = route.userInfo["description"]
.flatMap { $0 as? String }
.flatMap { $0.consoleText() } ?? ""
column.append(desc)
}
return column
})
}
}
extension PathComponent {
func consoleText() -> ConsoleText {
switch self {
case .constant:
return description.consoleText()
default:
return description.consoleText(.info)
}
}
}
extension Console {
func outputASCIITable(_ rows: [[ConsoleText]]) {
var columnWidths: [Int] = []
// calculate longest columns
for row in rows {
for (i, column) in row.enumerated() {
if columnWidths.count <= i {
columnWidths.append(0)
}
if column.description.count > columnWidths[i] {
columnWidths[i] = column.description.count
}
}
}
func hr() {
var text: ConsoleText = ""
for columnWidth in columnWidths {
text += "+"
text += "-"
for _ in 0..<columnWidth {
text += "-"
}
text += "-"
}
text += "+"
self.output(text)
}
for row in rows {
hr()
var text: ConsoleText = ""
for (i, column) in row.enumerated() {
text += "| "
text += column
for _ in 0..<(columnWidths[i] - column.description.count) {
text += " "
}
text += " "
}
text += "|"
self.output(text)
}
hr()
}
}
================================================
FILE: Sources/Vapor/Commands/ServeCommand.swift
================================================
@preconcurrency import Dispatch
import Foundation
import ConsoleKit
import NIOConcurrencyHelpers
/// Boots the application's server. Listens for `SIGINT` and `SIGTERM` for graceful shutdown.
///
/// $ swift run Run serve
/// Server starting on http://localhost:8080
///
public final class ServeCommand: AsyncCommand, Sendable {
public struct Signature: CommandSignature, Sendable {
@Option(name: "hostname", short: "H", help: "Set the hostname the server will run on.")
var hostname: String?
@Option(name: "port", short: "p", help: "Set the port the server will run on.")
var port: Int?
@Option(name: "bind", short: "b", help: "Convenience for setting hostname and port together.")
var bind: String?
@Option(name: "unix-socket", short: nil, help: "Set the path for the unix domain socket file the server will bind to.")
var socketPath: String?
public init() { }
}
/// Errors that may be thrown when serving a server
public enum Error: Swift.Error {
/// Incompatible flags were used together (for instance, specifying a socket path along with a port)
case incompatibleFlags
}
// See `AsyncCommand`.
public let signature = Signature()
// See `AsyncCommand`.
public var help: String {
return "Begins serving the app over HTTP."
}
struct SendableBox: Sendable {
var didShutdown: Bool
var running: Application.Running?
var signalSources: [DispatchSourceSignal]
var server: Server?
}
private let box: NIOLockedValueBox<SendableBox>
/// Create a new `ServeCommand`.
init() {
let box = SendableBox(didShutdown: false, signalSources: [])
self.box = .init(box)
}
// See `AsyncCommand`.
public func run(using context: CommandContext, signature: Signature) async throws {
switch (signature.hostname, signature.port, signature.bind, signature.socketPath) {
case (.none, .none, .none, .none): // use defaults
try await context.application.server.start(address: nil)
case (.none, .none, .none, .some(let socketPath)): // unix socket
try await context.application.server.start(address: .unixDomainSocket(path: socketPath))
case (.none, .none, .some(let address), .none): // bind ("hostname:port")
let hostname = address.split(separator: ":").first.flatMap(String.init)
let port = address.split(separator: ":").last.flatMap(String.init).flatMap(Int.init)
try await context.application.server.start(address: .hostname(hostname, port: port))
case (let hostname, let port, .none, .none): // hostname / port
try await context.application.server.start(address: .hostname(hostname, port: port))
default: throw Error.incompatibleFlags
}
var box = self.box.withLockedValue { $0 }
box.server = context.application.server
// allow the server to be stopped or waited for
let promise = context.application.eventLoopGroup.next().makePromise(of: Void.self)
context.application.running = .start(using: promise)
box.running = context.application.running
// setup signal sources for shutdown
let signalQueue = DispatchQueue(label: "codes.vapor.server.shutdown")
func makeSignalSource(_ code: Int32) {
#if canImport(Darwin)
/// https://github.com/swift-server/swift-service-lifecycle/blob/main/Sources/UnixSignals/UnixSignalsSequence.swift#L77-L82
signal(code, SIG_IGN)
#endif
let source = DispatchSource.makeSignalSource(signal: code, queue: signalQueue)
source.setEventHandler {
print() // clear ^C
promise.succeed(())
}
source.resume()
box.signalSources.append(source)
}
makeSignalSource(SIGTERM)
makeSignalSource(SIGINT)
self.box.withLockedValue { $0 = box }
}
@available(*, noasync, message: "Use the async asyncShutdown() method instead.")
func shutdown() {
var box = self.box.withLockedValue { $0 }
box.didShutdown = true
box.running?.stop()
if let server = box.server {
server.shutdown()
}
box.signalSources.forEach { $0.cancel() } // clear refs
box.signalSources = []
self.box.withLockedValue { $0 = box }
}
func asyncShutdown() async {
var box = self.box.withLockedValue { $0 }
box.didShutdown = true
box.running?.stop()
await box.server?.shutdown()
box.signalSources.forEach { $0.cancel() } // clear refs
box.signalSources = []
self.box.withLockedValue { $0 = box }
}
deinit {
assert(self.box.withLockedValue({ $0.didShutdown }), "ServeCommand did not shutdown before deinit")
}
}
================================================
FILE: Sources/Vapor/Concurrency/AnyResponse+Concurrency.swift
================================================
import NIOCore
/// A type erased response useful for routes that can return more than one type.
///
/// router.get("foo") { req -> AnyAsyncResponse in
/// if /* something */ {
/// return AnyAsyncResponse(42)
/// } else {
/// return AnyAsyncResponse("string")
/// }
/// }
///
/// This can also be done using a `AsyncResponseEncodable` enum.
///
/// enum IntOrString: AsyncResponseEncodable {
/// case int(Int)
/// case string(String)
///
/// func encode(for req: Request) throws -> EventLoopFuture<Response> {
/// switch self {
/// case .int(let i): return try i.encode(for: req)
/// case .string(let s): return try s.encode(for: req)
/// }
/// }
/// }
///
/// router.get("foo") { req -> IntOrString in
/// if /* something */ {
/// return .int(42)
/// } else {
/// return .string("string")
/// }
/// }
///
public struct AnyAsyncResponse: AsyncResponseEncodable {
/// The wrapped `AsyncResponseEncodable` type.
private let encodable: AsyncResponseEncodable
/// Creates a new `AnyAsyncResponse`.
///
/// - parameters:
/// - encodable: Something `AsyncResponseEncodable`.
public init(_ encodable: AsyncResponseEncodable) {
self.encodable = encodable
}
public func encodeResponse(for request: Request) async throws -> Response {
return try await self.encodable.encodeResponse(for: request)
}
}
================================================
FILE: Sources/Vapor/Concurrency/AsyncBasicResponder.swift
================================================
import NIOCore
/// A basic, async closure-based `Responder`.
public struct AsyncBasicResponder: AsyncResponder {
/// The stored responder closure.
private let closure: @Sendable (Request) async throws -> Response
/// Create a new `BasicResponder`.
///
/// let notFound: Responder = BasicResponder { req in
/// let res = req.response(http: .init(status: .notFound))
/// return req.eventLoop.newSucceededFuture(result: res)
/// }
///
/// - parameters:
/// - closure: Responder closure.
public init(
closure: @Sendable @escaping (Request) async throws -> Response
) {
self.closure = closure
}
public func respond(to request: Request) async throws -> Response {
return try await closure(request)
}
}
================================================
FILE: Sources/Vapor/Concurrency/AsyncMiddleware.swift
================================================
import NIOCore
/// `AsyncMiddleware` is placed between the server and your router. It is capable of
/// mutating both incoming requests and outgoing responses. `AsyncMiddleware` can choose
/// to pass requests on to the next `AsyncMiddleware` in a chain, or they can short circuit and
/// return a custom `Response` if desired.
///
/// This is an async version of `Middleware`
public protocol AsyncMiddleware: Middleware {
/// Called with each `Request` that passes through this middleware.
/// - parameters:
/// - request: The incoming `Request`.
/// - next: Next `Responder` in the chain, potentially another middleware or the main router.
/// - returns: An asynchronous `Response`.
func respond(to request: Request, chainingTo next: AsyncResponder) async throws -> Response
}
extension AsyncMiddleware {
public func respond(to request: Request, chainingTo next: Responder) -> EventLoopFuture<Response> {
let promise = request.eventLoop.makePromise(of: Response.self)
promise.completeWithTask {
let asyncResponder = AsyncBasicResponder { req in
return try await next.respond(to: req).get()
}
return try await respond(to: request, chainingTo: asyncResponder)
}
return promise.futureResult
}
}
================================================
FILE: Sources/Vapor/Concurrency/AsyncPasswordHasher+Concurrency.swift
================================================
import NIOCore
import Foundation
extension AsyncPasswordHasher {
public func hash<Password>(_ password: Password) async throws -> [UInt8]
where Password: DataProtocol & Sendable
{
try await self.hash(password).get()
}
public func verify<Password, Digest>(
_ password: Password,
created digest: Digest
) async throws -> Bool
where Password: DataProtocol & Sendable, Digest: DataProtocol & Sendable
{
try await self.verify(password, created: digest).get()
}
public func hash(_ password: String) async throws -> String {
try await self.hash(password).get()
}
public func verify(_ password: String, created digest: String) async throws -> Bool {
try await self.verify(password, created: digest).get()
}
}
================================================
FILE: Sources/Vapor/Concurrency/AsyncSessionDriver.swift
================================================
import NIOCore
/// Capable of managing CRUD operations for `Session`s.
///
/// This is an async version of `SessionDriver`
public protocol AsyncSessionDriver: SessionDriver {
func createSession(_ data: SessionData, for request: Request) async throws -> SessionID
func readSession(_ sessionID: SessionID, for request: Request) async throws -> SessionData?
func updateSession(_ sessionID: SessionID, to data: SessionData, for request: Request) async throws -> SessionID
func deleteSession(_ sessionID: SessionID, for request: Request) async throws
}
extension AsyncSessionDriver {
public func createSession(_ data: SessionData, for request: Request) -> EventLoopFuture<SessionID> {
let promise = request.eventLoop.makePromise(of: SessionID.self)
promise.completeWithTask {
try await self.createSession(data, for: request)
}
return promise.futureResult
}
public func readSession(_ sessionID: SessionID, for request: Request) -> EventLoopFuture<SessionData?> {
let promise = request.eventLoop.makePromise(of: SessionData?.self)
promise.completeWithTask {
try await self.readSession(sessionID, for: request)
}
return promise.futureResult
}
public func updateSession(_ sessionID: SessionID, to data: SessionData, for request: Request) -> EventLoopFuture<SessionID> {
let promise = request.eventLoop.makePromise(of: SessionID.self)
promise.completeWithTask {
try await self.updateSession(sessionID, to: data, for: request)
}
return promise.futureResult
}
public func deleteSession(_ sessionID: SessionID, for request: Request) -> EventLoopFuture<Void> {
let promise = request.eventLoop.makePromise(of: Void.self)
promise.completeWithTask {
try await self.deleteSession(sessionID, for: request)
}
return promise.futureResult
}
}
================================================
FILE: Sources/Vapor/Concurrency/Authentication+Concurrency.swift
================================================
import NIOCore
/// Helper for creating authentication middleware.
///
/// See `AsyncRequestAuthenticator` and `AsyncSessionAuthenticator` for more information.
///
/// This is an async version of `Authenticator`
public protocol AsyncAuthenticator: AsyncMiddleware { }
/// Help for creating authentication middleware based on `Request`.
///
/// `Authenticator`'s use the incoming request to check for authentication information.
/// If valid authentication credentials are present, the authenticated user is added to `req.auth`.
///
/// This is an async version of `RequestAuthenticator`
public protocol AsyncRequestAuthenticator: AsyncAuthenticator {
func authenticate(request: Request) async throws
}
extension AsyncRequestAuthenticator {
public func respond(to request: Request, chainingTo next: AsyncResponder) async throws -> Response {
try await self.authenticate(request: request)
return try await next.respond(to: request)
}
}
// MARK: Basic
/// Helper for creating authentication middleware using the Basic authorization header.
///
/// This is an async version of `BasicAuthenticator`
public protocol AsyncBasicAuthenticator: AsyncRequestAuthenticator {
func authenticate(basic: BasicAuthorization, for request: Request) async throws
}
extension AsyncBasicAuthenticator {
public func authenticate(request: Request) async throws {
guard let basicAuthorization = request.headers.basicAuthorization else {
return
}
return try await self.authenticate(basic: basicAuthorization, for: request)
}
}
// MARK: Bearer
/// Helper for creating authentication middleware using the Bearer authorization header.
///
/// This is an async version of `BearerAuthenticator`
public protocol AsyncBearerAuthenticator: AsyncRequestAuthenticator {
func authenticate(bearer: BearerAuthorization, for request: Request) async throws
}
extension AsyncBearerAuthenticator {
public func authenticate(request: Request) async throws {
guard let bearerAuthorization = request.headers.bearerAuthorization else {
return
}
return try await self.authenticate(bearer: bearerAuthorization, for: request)
}
}
// MARK: Credentials
/// Helper for creating authentication middleware using request body contents.
///
/// This is an async version of `CredentialsAuthenticator`
public protocol AsyncCredentialsAuthenticator: AsyncRequestAuthenticator {
associatedtype Credentials: Content
func authenticate(credentials: Credentials, for request: Request) async throws
}
extension AsyncCredentialsAuthenticator {
public func authenticate(request: Request) async throws {
let credentials: Credentials
do {
credentials = try request.content.decode(Credentials.self)
} catch {
return
}
return try await self.authenticate(credentials: credentials, for: request)
}
}
/// Helper for creating authentication middleware in conjunction with `SessionsMiddleware`.
///
/// This is an async version of `SessionAuthenticator`
public protocol AsyncSessionAuthenticator: AsyncAuthenticator {
associatedtype User: SessionAuthenticatable
/// Authenticate a model with the supplied ID.
func authenticate(sessionID: User.SessionID, for request: Request) async throws
}
extension AsyncSessionAuthenticator {
public func respond(to request: Request, chainingTo next: AsyncResponder) async throws -> Response {
// if the user has already been authenticated
// by a previous middleware, continue
if request.auth.has(User.self) {
return try await next.respond(to: request)
}
if request.hasSession, let aID = request.session.authenticated(User.self) {
// try to find user with id from session
try await self.authenticate(sessionID: aID, for: request)
}
// respond to the request
let response = try await next.respond(to: request)
if let user = request.auth.get(User.self) {
// if a user has been authed (or is still authed), store in the session
request.session.authenticate(user)
} else if request.hasSession {
// if no user is authed, it's possible they've been unauthed.
// remove from session.
request.session.unauthenticate(User.self)
}
return response
}
}
================================================
FILE: Sources/Vapor/Concurrency/Cache+Concurrency.swift
================================================
import NIOCore
public extension Cache {
/// Gets a decodable value from the cache. Returns `nil` if not found.
func get<T>(_ key: String, as type: T.Type) async throws -> T? where T: Decodable & Sendable {
try await self.get(key, as: type).get()
}
/// Sets an encodable value into the cache. Existing values are replaced. If `nil`, removes value.
func set<T>(_ key: String, to value: T?) async throws where T: Encodable & Sendable {
try await self.set(key, to: value).get()
}
/// Sets an encodable value into the cache with an expiry time. Existing values are replaced. If `nil`, removes value.
func set<T>(_ key: String, to value: T?, expiresIn expirationTime: CacheExpirationTime?) async throws where T: Encodable & Sendable {
try await self.set(key, to: value, expiresIn: expirationTime).get()
}
func delete(_ key: String) async throws {
try await self.delete(key).get()
}
/// Gets a decodable value from the cache. Returns `nil` if not found.
func get<T>(_ key: String) async throws -> T? where T: Decodable & Sendable {
try await self.get(key).get()
}
}
================================================
FILE: Sources/Vapor/Concurrency/Client+Concurrency.swift
================================================
import NIOCore
import NIOHTTP1
extension Client {
public func get(_ url: URI, headers: HTTPHeaders = [:], beforeSend: (inout ClientRequest) throws -> () = { _ in }) async throws -> ClientResponse {
return try await self.send(.GET, headers: headers, to: url, beforeSend: beforeSend).get()
}
public func post(_ url: URI, headers: HTTPHeaders = [:], beforeSend: (inout ClientRequest) throws -> () = { _ in }) async throws -> ClientResponse {
return try await self.send(.POST, headers: headers, to: url, beforeSend: beforeSend).get()
}
public func patch(_ url: URI, headers: HTTPHeaders = [:], beforeSend: (inout ClientRequest) throws -> () = { _ in }) async throws -> ClientResponse {
return try await self.send(.PATCH, headers: headers, to: url, beforeSend: beforeSend).get()
}
public func put(_ url: URI, headers: HTTPHeaders = [:], beforeSend: (inout ClientRequest) throws -> () = { _ in }) async throws -> ClientResponse {
return try await self.send(.PUT, headers: headers, to: url, beforeSend: beforeSend).get()
}
public func delete(_ url: URI, headers: HTTPHeaders = [:], beforeSend: (inout ClientRequest) throws -> () = { _ in }) async throws -> ClientResponse {
return try await self.send(.DELETE, headers: headers, to: url, beforeSend: beforeSend).get()
}
public func post<T>(_ url: URI, headers: HTTPHeaders = [:], content: T) async throws -> ClientResponse where T: Content {
return try await self.post(url, headers: headers, beforeSend: { try $0.content.encode(content) })
}
public func patch<T>(_ url: URI, headers: HTTPHeaders = [:], content: T) async throws -> ClientResponse where T: Content {
return try await self.patch(url, headers: headers, beforeSend: { try $0.content.encode(content) })
}
public func put<T>(_ url: URI, headers: HTTPHeaders = [:], content: T) async throws -> ClientResponse where T: Content {
return try await self.put(url, headers: headers, beforeSend: { try $0.content.encode(content) })
}
public func send(
_ method: HTTPMethod,
headers: HTTPHeaders = [:],
to url: URI,
beforeSend: (inout ClientRequest) throws -> () = { _ in }
) async throws -> ClientResponse {
var request = ClientRequest(method: method, url: url, headers: headers, body: nil, byteBufferAllocator: self.byteBufferAllocator)
try beforeSend(&request)
return try await self.send(request).get()
}
public func send(_ request: ClientRequest) async throws -> ClientResponse {
return try await self.send(request).get()
}
}
================================================
FILE: Sources/Vapor/Concurrency/RequestBody+Concurrency.swift
================================================
import NIOCore
import NIOConcurrencyHelpers
// MARK: - Request.Body.AsyncSequenceDelegate
extension Request.Body {
/// `Request.Body.AsyncSequenceDelegate` bridges between EventLoop
/// and AsyncSequence. Crucially, this type handles backpressure
/// by synchronizing bytes on the `EventLoop`
///
/// `AsyncSequenceDelegate` can be created and **must be retained**
/// in `Request.Body/makeAsyncIterator()` method.
fileprivate final class AsyncSequenceDelegate: @unchecked Sendable, NIOAsyncSequenceProducerDelegate {
private enum State {
case notCalledYet
case noSignalReceived
case waitingForSignalFromConsumer(EventLoopPromise<Void>)
}
private var _state: State = .notCalledYet
private let eventLoop: any EventLoop
init(eventLoop: any EventLoop) {
self.eventLoop = eventLoop
}
private func produceMore0() {
self.eventLoop.preconditionInEventLoop()
switch self._state {
case .notCalledYet:
// We can just return here to sign to the producer that we want more data
break
case .noSignalReceived:
preconditionFailure()
case .waitingForSignalFromConsumer(let promise):
self._state = .noSignalReceived
promise.succeed(())
}
}
private func didTerminate0() {
self.eventLoop.preconditionInEventLoop()
switch self._state {
case .notCalledYet:
// Means didn't hit the backpressure limits, so just return
break
case .noSignalReceived:
// we will inform the producer, since the next write will fail.
break
case .waitingForSignalFromConsumer(let promise):
self._state = .noSignalReceived
promise.fail(CancellationError())
}
}
func registerBackpressurePromise(_ promise: EventLoopPromise<Void>) {
self.eventLoop.preconditionInEventLoop()
switch self._state {
case .noSignalReceived, .notCalledYet:
self._state = .waitingForSignalFromConsumer(promise)
case .waitingForSignalFromConsumer:
preconditionFailure()
}
}
func didTerminate() {
self.eventLoop.execute { self.didTerminate0() }
}
func produceMore() {
self.eventLoop.execute { self.produceMore0() }
}
}
}
// MARK: - Request.Body.AsyncSequence
extension Request.Body: AsyncSequence {
public typealias Element = ByteBuffer
/// This wrapper generalizes our implementation.
/// `RequestBody.AsyncIterator` is the override point for
/// using another implementation
public struct AsyncIterator: AsyncIteratorProtocol {
public typealias Element = ByteBuffer
fileprivate typealias Underlying = NIOThrowingAsyncSequenceProducer<ByteBuffer, any Error, NIOAsyncSequenceProducerBackPressureStrategies.HighLowWatermark, Request.Body.AsyncSequenceDelegate>.AsyncIterator
private var underlying: Underlying
fileprivate init(underlying: Underlying) {
self.underlying = underlying
}
public mutating func next() async throws -> ByteBuffer? {
return try await self.underlying.next()
}
}
/// Checks that the request has a body suitable for an AsyncSequence
///
/// AsyncSequence streaming should use a body of type .stream().
/// Using `.collected(_)` will load the entire request into memory
/// which should be avoided for large file uploads.
///
/// Example: app.on(.POST, "/upload", body: .stream) { ... }
private func checkBodyStorage() {
switch request.bodyStorage.withLockedValue({ $0 }) {
case .stream(_):
break
case .collected(_):
break
default:
preconditionFailure("""
AsyncSequence streaming should use a body of type .stream()
Example: app.on(.POST, "/upload", body: .stream) { ... }
""")
}
}
/// Generates an `AsyncIterator` to stream the body’s content as
/// `ByteBuffer` sequences. This implementation supports backpressure using
/// `NIOAsyncSequenceProducerBackPressureStrategies`
/// - Returns: `AsyncIterator` containing the `Request.Body` as a
/// `ByteBuffer` sequence
public func makeAsyncIterator() -> AsyncIterator {
let delegate = AsyncSequenceDelegate(eventLoop: request.eventLoop)
let producer = NIOThrowingAsyncSequenceProducer.makeSequence(
elementType: ByteBuffer.self,
failureType: Error.self,
backPressureStrategy: NIOAsyncSequenceProducerBackPressureStrategies
.HighLowWatermark(lowWatermark: 5, highWatermark: 20),
finishOnDeinit: true,
delegate: delegate
)
let source = producer.source
self.drain { streamResult in
switch streamResult {
case .buffer(let buffer):
// Send the buffer to the async sequence
let result = source.yield(buffer)
// Inspect the source view and handle outcomes
switch result {
case .dropped:
// The consumer dropped the sequence.
// Inform the producer that we don't want more data
// by returning an error in the future.
delegate.didTerminate()
return request.eventLoop.makeFailedFuture(CancellationError())
case .stopProducing:
// The consumer is too slow.
// We need to create a promise that w
gitextract_n1aicf92/
├── .github/
│ ├── CODEOWNERS
│ ├── contributing.md
│ ├── dependabot.yml
│ ├── maintainers.md
│ └── workflows/
│ ├── api-docs.yml
│ ├── sponsors.yml
│ └── test.yml
├── .gitignore
├── .spi.yml
├── AGENTS.md
├── LICENSE
├── NOTICES.txt
├── Package.swift
├── README.md
├── Sources/
│ ├── CVaporBcrypt/
│ │ ├── bcrypt.c
│ │ ├── bcrypt.h
│ │ ├── blf.c
│ │ ├── blf.h
│ │ └── include/
│ │ └── module.modulemap
│ ├── Development/
│ │ ├── Resources/
│ │ │ └── fileio.txt
│ │ ├── configure.swift
│ │ ├── entrypoint.swift
│ │ └── routes.swift
│ ├── Vapor/
│ │ ├── Application.swift
│ │ ├── Authentication/
│ │ │ ├── AuthenticationCache.swift
│ │ │ ├── Authenticator.swift
│ │ │ ├── BasicAuthorization.swift
│ │ │ ├── BearerAuthorization.swift
│ │ │ ├── GuardMiddleware.swift
│ │ │ ├── RedirectMiddleware.swift
│ │ │ └── SessionAuthenticatable.swift
│ │ ├── Bcrypt/
│ │ │ └── Bcrypt.swift
│ │ ├── Cache/
│ │ │ ├── Application+Cache.swift
│ │ │ ├── Cache.swift
│ │ │ ├── CacheExpirationTime.swift
│ │ │ ├── MemoryCache.swift
│ │ │ └── Request+Cache.swift
│ │ ├── Client/
│ │ │ ├── Application+Clients.swift
│ │ │ ├── Client.swift
│ │ │ ├── ClientRequest.swift
│ │ │ ├── ClientResponse.swift
│ │ │ └── Request+Client.swift
│ │ ├── Commands/
│ │ │ ├── BootCommand.swift
│ │ │ ├── CommandContext+Application.swift
│ │ │ ├── RoutesCommand.swift
│ │ │ └── ServeCommand.swift
│ │ ├── Concurrency/
│ │ │ ├── AnyResponse+Concurrency.swift
│ │ │ ├── AsyncBasicResponder.swift
│ │ │ ├── AsyncMiddleware.swift
│ │ │ ├── AsyncPasswordHasher+Concurrency.swift
│ │ │ ├── AsyncSessionDriver.swift
│ │ │ ├── Authentication+Concurrency.swift
│ │ │ ├── Cache+Concurrency.swift
│ │ │ ├── Client+Concurrency.swift
│ │ │ ├── RequestBody+Concurrency.swift
│ │ │ ├── Responder+Concurrency.swift
│ │ │ ├── ResponseCodable+Concurrency.swift
│ │ │ ├── RoutesBuilder+Concurrency.swift
│ │ │ ├── ViewRenderer+Concurrency.swift
│ │ │ └── WebSocket+Concurrency.swift
│ │ ├── Content/
│ │ │ ├── ContainerGetPathExecutor.swift
│ │ │ ├── Content.swift
│ │ │ ├── ContentCoders.swift
│ │ │ ├── ContentConfiguration.swift
│ │ │ ├── ContentContainer.swift
│ │ │ ├── JSONCoder+Custom.swift
│ │ │ ├── JSONCoders+Content.swift
│ │ │ ├── PlaintextDecoder.swift
│ │ │ ├── PlaintextEncoder.swift
│ │ │ ├── URLQueryCoders.swift
│ │ │ └── URLQueryContainer.swift
│ │ ├── Core/
│ │ │ ├── Core.swift
│ │ │ └── Running.swift
│ │ ├── Deprecations/
│ │ │ ├── CORSMiddleware+AllowOriginSetting.swift
│ │ │ ├── CORSMiddleware+Configuration+exposedHeaders.swift
│ │ │ ├── DotEnvFile+load.swift
│ │ │ ├── Routes+caseInsenstive.swift
│ │ │ └── Validatable+validate.swift
│ │ ├── Docs.docc/
│ │ │ └── index.md
│ │ ├── Environment/
│ │ │ ├── Environment+Process.swift
│ │ │ ├── Environment+Secret.swift
│ │ │ └── Environment.swift
│ │ ├── Error/
│ │ │ ├── Abort.swift
│ │ │ ├── AbortError.swift
│ │ │ ├── DebuggableError.swift
│ │ │ ├── Demangler.swift
│ │ │ ├── ErrorSource.swift
│ │ │ └── StackTrace.swift
│ │ ├── Exports.swift
│ │ ├── HTTP/
│ │ │ ├── Application+HTTP.swift
│ │ │ ├── BasicResponder.swift
│ │ │ ├── BodyStream.swift
│ │ │ ├── Client/
│ │ │ │ ├── Application+HTTP+Client.swift
│ │ │ │ └── EventLoopHTTPClient.swift
│ │ │ ├── EndpointCache.swift
│ │ │ ├── HTTPMethod+String.swift
│ │ │ ├── HTTPStatus.swift
│ │ │ ├── Headers/
│ │ │ │ ├── HTTPCookies.swift
│ │ │ │ ├── HTTPHeaderCacheControl.swift
│ │ │ │ ├── HTTPHeaderExpires.swift
│ │ │ │ ├── HTTPHeaderLastModified.swift
│ │ │ │ ├── HTTPHeaders+Cache.swift
│ │ │ │ ├── HTTPHeaders+Connection.swift
│ │ │ │ ├── HTTPHeaders+ContentDisposition.swift
│ │ │ │ ├── HTTPHeaders+ContentRange.swift
│ │ │ │ ├── HTTPHeaders+Directive.swift
│ │ │ │ ├── HTTPHeaders+Forwarded.swift
│ │ │ │ ├── HTTPHeaders+Link.swift
│ │ │ │ ├── HTTPHeaders+Name.swift
│ │ │ │ ├── HTTPHeaders+ResponseCompression.swift
│ │ │ │ ├── HTTPHeaders.swift
│ │ │ │ ├── HTTPMediaType.swift
│ │ │ │ └── HTTPMediaTypePreference.swift
│ │ │ ├── Responder.swift
│ │ │ └── Server/
│ │ │ ├── Application+HTTP+Server.swift
│ │ │ ├── HTTPServer.swift
│ │ │ ├── HTTPServerConfiguration+RequestDecompressionConfiguration.swift
│ │ │ ├── HTTPServerConfiguration+ResponseCompressionConfiguration.swift
│ │ │ ├── HTTPServerHandler.swift
│ │ │ ├── HTTPServerRequestDecoder.swift
│ │ │ ├── HTTPServerResponseEncoder.swift
│ │ │ └── HTTPServerUpgradeHandler.swift
│ │ ├── Logging/
│ │ │ ├── Logger+Report.swift
│ │ │ └── LoggingSystem+Environment.swift
│ │ ├── Middleware/
│ │ │ ├── Application+Middleware.swift
│ │ │ ├── CORSMiddleware.swift
│ │ │ ├── ErrorMiddleware.swift
│ │ │ ├── FileMiddleware.swift
│ │ │ ├── Middleware.swift
│ │ │ ├── MiddlewareConfiguration.swift
│ │ │ ├── ResponseCompressionMiddleware.swift
│ │ │ ├── RouteLoggingMiddleware.swift
│ │ │ └── TracingMiddleware.swift
│ │ ├── Multipart/
│ │ │ ├── File+Multipart.swift
│ │ │ ├── FormDataDecoder+Content.swift
│ │ │ └── FormDataEncoder+Content.swift
│ │ ├── Passwords/
│ │ │ ├── Application+Password.swift
│ │ │ ├── Application+Passwords.swift
│ │ │ ├── AsyncPasswordHasher.swift
│ │ │ ├── BcryptHasher.swift
│ │ │ ├── PasswordHasher.swift
│ │ │ ├── PlaintextHasher.swift
│ │ │ └── Request+Password.swift
│ │ ├── Request/
│ │ │ ├── Redirect.swift
│ │ │ ├── Request+Body.swift
│ │ │ ├── Request+BodyStream.swift
│ │ │ └── Request.swift
│ │ ├── Responder/
│ │ │ ├── Application+Responder.swift
│ │ │ └── DefaultResponder.swift
│ │ ├── Response/
│ │ │ ├── Response+Body.swift
│ │ │ ├── Response.swift
│ │ │ └── ResponseCodable.swift
│ │ ├── Routing/
│ │ │ ├── Application+Routes.swift
│ │ │ ├── Parameters+Require.swift
│ │ │ ├── Request+WebSocket.swift
│ │ │ ├── Route.swift
│ │ │ ├── RouteCollection.swift
│ │ │ ├── Routes.swift
│ │ │ ├── RoutesBuilder+Group.swift
│ │ │ ├── RoutesBuilder+Method.swift
│ │ │ ├── RoutesBuilder+Middleware.swift
│ │ │ ├── RoutesBuilder+WebSocket.swift
│ │ │ └── RoutesBuilder.swift
│ │ ├── Security/
│ │ │ ├── OTP.swift
│ │ │ └── ValidatedCertificateChain.swift
│ │ ├── Server/
│ │ │ ├── Application+Servers.swift
│ │ │ └── Server.swift
│ │ ├── Services/
│ │ │ ├── App+Service.swift
│ │ │ ├── Req+Service.swift
│ │ │ └── Service.swift
│ │ ├── Sessions/
│ │ │ ├── Application+Sessions.swift
│ │ │ ├── MemorySessions.swift
│ │ │ ├── Request+Session.swift
│ │ │ ├── Session.swift
│ │ │ ├── SessionCache.swift
│ │ │ ├── SessionData.swift
│ │ │ ├── SessionDriver.swift
│ │ │ ├── SessionsConfiguration.swift
│ │ │ └── SessionsMiddleware.swift
│ │ ├── URLEncodedForm/
│ │ │ ├── URLEncodedFormData.swift
│ │ │ ├── URLEncodedFormDecoder.swift
│ │ │ ├── URLEncodedFormEncoder.swift
│ │ │ ├── URLEncodedFormError.swift
│ │ │ ├── URLEncodedFormParser.swift
│ │ │ ├── URLEncodedFormSerializer.swift
│ │ │ └── URLQueryFragmentConvertible.swift
│ │ ├── Utilities/
│ │ │ ├── AnyResponse.swift
│ │ │ ├── Array+Random.swift
│ │ │ ├── Base32.swift
│ │ │ ├── Base64.swift
│ │ │ ├── BaseN.swift
│ │ │ ├── BasicCodingKey.swift
│ │ │ ├── ByteCount.swift
│ │ │ ├── Bytes+Hex.swift
│ │ │ ├── Bytes+SecureCompare.swift
│ │ │ ├── Collection+Safe.swift
│ │ │ ├── DataProtocol+Copy.swift
│ │ │ ├── DecoderUnwrapper.swift
│ │ │ ├── DirectoryConfiguration.swift
│ │ │ ├── DotEnv.swift
│ │ │ ├── Extendable.swift
│ │ │ ├── File.swift
│ │ │ ├── FileIO.swift
│ │ │ ├── LifecycleHandler.swift
│ │ │ ├── OptionalType.swift
│ │ │ ├── RFC1123.swift
│ │ │ ├── SocketAddress+Hostname.swift
│ │ │ ├── Storage.swift
│ │ │ ├── String+IsIPAddress.swift
│ │ │ ├── Thread.swift
│ │ │ ├── URI.swift
│ │ │ └── VaporSendableMetadataType.swift
│ │ ├── Validation/
│ │ │ ├── RangeResult.swift
│ │ │ ├── Validatable.swift
│ │ │ ├── Validation.swift
│ │ │ ├── ValidationKey.swift
│ │ │ ├── Validations.swift
│ │ │ ├── ValidationsError.swift
│ │ │ ├── Validator.swift
│ │ │ ├── ValidatorResult.swift
│ │ │ └── Validators/
│ │ │ ├── And.swift
│ │ │ ├── Case.swift
│ │ │ ├── CharacterSet.swift
│ │ │ ├── Count.swift
│ │ │ ├── Custom.swift
│ │ │ ├── Email.swift
│ │ │ ├── Empty.swift
│ │ │ ├── In.swift
│ │ │ ├── Nil.swift
│ │ │ ├── NilIgnoring.swift
│ │ │ ├── Not.swift
│ │ │ ├── Or.swift
│ │ │ ├── Pattern.swift
│ │ │ ├── Range.swift
│ │ │ ├── URL.swift
│ │ │ └── Valid.swift
│ │ ├── View/
│ │ │ ├── Application+Views.swift
│ │ │ ├── PlaintextRenderer.swift
│ │ │ ├── Request+View.swift
│ │ │ ├── View.swift
│ │ │ └── ViewRenderer.swift
│ │ └── _Deprecations.swift
│ ├── VaporTestUtils/
│ │ ├── TestingApplication.swift
│ │ ├── TestingHTTPRequest.swift
│ │ ├── TestingHTTPResponse.swift
│ │ └── Utilities.swift
│ ├── VaporTesting/
│ │ ├── +TestingHTTPResponse.swift
│ │ ├── Docs.docc/
│ │ │ └── index.md
│ │ ├── Exports.swift
│ │ ├── TestingApplicationTester.swift
│ │ ├── VaporTestingContext.swift
│ │ ├── XCTest+Migration.swift
│ │ └── withApp.swift
│ └── XCTVapor/
│ ├── +Application.swift
│ ├── +XCTHTTPResponse.swift
│ ├── Docs.docc/
│ │ └── index.md
│ ├── Exports.swift
│ ├── XCTApplicationTester.swift
│ ├── XCTVaporContext.swift
│ ├── XCTVaporTests.swift
│ └── typealiases.swift
└── Tests/
└── VaporTests/
├── ApplicationCreationTests.swift
├── ApplicationTests.swift
├── AsyncAuthTests.swift
├── AsyncCacheTests.swift
├── AsyncClientTests.swift
├── AsyncCommandsTests.swift
├── AsyncEnvironmentTests.swift
├── AsyncFileTests.swift
├── AsyncMiddlewareTests.swift
├── AsyncPasswordTests.swift
├── AsyncRequestTests.swift
├── AsyncRouteTests.swift
├── AsyncSessionTests.swift
├── AsyncWebSocketTests.swift
├── AuthenticationTests.swift
├── BaseNTests.swift
├── BcryptTests.swift
├── BodyStreamStateTests.swift
├── CacheTests.swift
├── ClientTests.swift
├── ConditionalResponseCompressionTests.swift
├── ContentTests.swift
├── DotEnvTests.swift
├── EndpointCacheTests.swift
├── EnvironmentSecretTests.swift
├── ErrorTests.swift
├── FileTests.swift
├── HTTPCacheTests.swift
├── HTTPHeaderTests.swift
├── HTTPMediaTypeSetTests.swift
├── LoggingTests.swift
├── MetricsTests.swift
├── MiddlewareTests.swift
├── PasswordTests.swift
├── PipelineTests.swift
├── QueryTests.swift
├── RequestTests.swift
├── RouteTests.swift
├── SecurityTests.swift
├── ServerTests.swift
├── ServiceTests.swift
├── SessionTests.swift
├── URITests.swift
├── URLEncodedFormTests.swift
├── Utilities/
│ ├── ByteBuffer+Helpers.swift
│ ├── CapturingMetricsSystem.swift
│ ├── ResponderClient.swift
│ ├── SubUtilities/
│ │ └── index.html
│ ├── TestLogging.swift
│ ├── TestTracer.swift
│ ├── ThreadSafe.swift
│ ├── expired.crt
│ ├── expired.key
│ ├── foo bar.html
│ ├── foo.txt
│ ├── index.html
│ ├── long-test-file.txt
│ ├── my-secret-env-content
│ └── test.env
├── UtilityTests.swift
├── ValidationTests.swift
├── VaporTesting.swift
├── ViewTests.swift
└── WebSocketTests.swift
SYMBOL INDEX (21 symbols across 4 files)
FILE: Sources/CVaporBcrypt/bcrypt.c
function vapor_bcrypt_hashpass (line 50) | int
function decode_base64 (line 187) | static int
function vapor_encode_base64 (line 230) | int
FILE: Sources/CVaporBcrypt/bcrypt.h
type u_int8_t (line 7) | typedef uint8_t u_int8_t;
type u_int16_t (line 9) | typedef uint16_t u_int16_t;
type u_int32_t (line 11) | typedef uint32_t u_int32_t;
type u_int64_t (line 13) | typedef uint64_t u_int64_t;
FILE: Sources/CVaporBcrypt/blf.c
function Vapor_Blowfish_encipher (line 63) | void
function Vapor_Blowfish_decipher (line 88) | void
function Vapor_Blowfish_initstate (line 113) | void
function u_int32_t (line 393) | u_int32_t
function Vapor_Blowfish_expand0state (line 414) | void
function Vapor_Blowfish_expandstate (line 451) | void
function vapor_blf_key (line 493) | void
function vapor_blf_enc (line 503) | void
function vapor_blf_dec (line 516) | void
function vapor_blf_ecb_encrypt (line 529) | void
function vapor_blf_ecb_decrypt (line 555) | void
function vapor_blf_cbc_encrypt (line 581) | void
function vapor_blf_cbc_decrypt (line 610) | void
FILE: Sources/CVaporBcrypt/blf.h
type blf_ctx (line 52) | typedef struct BlowfishContext {
Condensed preview — 325 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,839K chars).
[
{
"path": ".github/CODEOWNERS",
"chars": 17,
"preview": "* @0xTim @gwynne\n"
},
{
"path": ".github/contributing.md",
"chars": 7084,
"preview": "# Contributing to Vapor\n\n👋 Welcome to the Vapor team! \n\n## Testing\n\nOnce in Xcode, select the `vapor-Package` scheme and"
},
{
"path": ".github/dependabot.yml",
"chars": 417,
"preview": "version: 2\nupdates:\n - package-ecosystem: \"github-actions\"\n directory: \"/\"\n schedule:\n interval: \"daily\"\n "
},
{
"path": ".github/maintainers.md",
"chars": 1259,
"preview": "# Being a Maintainer\n\nIf you would like to volunteer to be a maintainer for this repo, or any other repo under the Vapor"
},
{
"path": ".github/workflows/api-docs.yml",
"chars": 387,
"preview": "name: deploy-api-docs\non:\n push:\n branches:\n - main\npermissions:\n contents: read\n id-token: write\n\njobs:\n bu"
},
{
"path": ".github/workflows/sponsors.yml",
"chars": 1300,
"preview": "name: Generate Sponsors README\non: workflow_dispatch\npermissions:\n id-token: write\n contents: read\n\njobs:\n deploy:\n "
},
{
"path": ".github/workflows/test.yml",
"chars": 1833,
"preview": "name: test\nconcurrency:\n group: ${{ github.workflow }}-${{ github.ref }}\n cancel-in-progress: true\non:\n pull_request:"
},
{
"path": ".gitignore",
"chars": 225,
"preview": "Packages\n.build\n.index-build\n.DS_Store\n*.xcodeproj\nDerivedData/\nPackage.resolved\n.swiftpm\nTests/LinuxMain.swift\n.vscode\n"
},
{
"path": ".spi.yml",
"chars": 184,
"preview": "version: 1\nmetadata:\n authors: \"Maintained by the Vapor Core Team with hundreds of contributions from the Vapor Communi"
},
{
"path": "AGENTS.md",
"chars": 3123,
"preview": "# AI & Automated Agent Policy\n\nThis document outlines our policy on AI-assisted contributions and automated agents inter"
},
{
"path": "LICENSE",
"chars": 1080,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2020 Qutheory, LLC\n\nPermission is hereby granted, free of charge, to any person obt"
},
{
"path": "NOTICES.txt",
"chars": 871,
"preview": "\n//===----------------------------------------------------------------------===//\n//\n// This source file is part of the "
},
{
"path": "Package.swift",
"chars": 7577,
"preview": "// swift-tools-version:6.0\nimport PackageDescription\n\nlet package = Package(\n name: \"vapor\",\n platforms: [\n "
},
{
"path": "README.md",
"chars": 15953,
"preview": "<a href=\"https://discord.gg/vapor\">\n\n 2014 Ted Unangst <tedu@ope"
},
{
"path": "Sources/CVaporBcrypt/bcrypt.h",
"chars": 1110,
"preview": "#include <sys/types.h>\n#include <string.h>\n#include <stdio.h>\n\n#if defined(_WIN32)\ntypedef unsigned char uint8_t;\ntypede"
},
{
"path": "Sources/CVaporBcrypt/blf.c",
"chars": 26238,
"preview": "/* $OpenBSD: blf.c,v 1.7 2007/11/26 09:28:34 martynas Exp $ */\n\n/*\n * Blowfish block cipher for OpenBSD\n * Copyrig"
},
{
"path": "Sources/CVaporBcrypt/blf.h",
"chars": 3559,
"preview": "/* $OpenBSD: blf.h,v 1.6 2007/02/21 19:25:40 grunk Exp $ */\n\n/*\n * Blowfish - a fast block cipher designed by Bruc"
},
{
"path": "Sources/CVaporBcrypt/include/module.modulemap",
"chars": 75,
"preview": "module CVaporBcrypt [system][extern_c] {\n header \"../bcrypt.h\"\n export *\n}\n"
},
{
"path": "Sources/Development/Resources/fileio.txt",
"chars": 4104,
"preview": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc ut eleifend turpis, quis vestibulum odio. Sed vestibulum, "
},
{
"path": "Sources/Development/configure.swift",
"chars": 4369,
"preview": "import Vapor\nimport NIOConcurrencyHelpers\nimport NIOSSL\n\npublic func configure(_ app: Application) throws {\n app.logg"
},
{
"path": "Sources/Development/entrypoint.swift",
"chars": 461,
"preview": "import Vapor\nimport Logging\n\n@main\nstruct Entrypoint {\n static func main() async throws {\n var env = try Envir"
},
{
"path": "Sources/Development/routes.swift",
"chars": 12195,
"preview": "import class Foundation.Bundle\nimport Vapor\nimport NIOCore\nimport NIOHTTP1\nimport NIOConcurrencyHelpers\nimport _NIOFileS"
},
{
"path": "Sources/Vapor/Application.swift",
"chars": 14182,
"preview": "import ConsoleKit\nimport Logging\nimport NIOConcurrencyHelpers\nimport NIOCore\nimport NIOPosix\n\n/// Core type representing"
},
{
"path": "Sources/Vapor/Authentication/AuthenticationCache.swift",
"chars": 3761,
"preview": "import NIOConcurrencyHelpers\n\nextension Request {\n /// Helper for accessing authenticated objects.\n /// See `Authe"
},
{
"path": "Sources/Vapor/Authentication/Authenticator.swift",
"chars": 2952,
"preview": "import NIOCore\n\n/// Capable of being authenticated.\npublic protocol Authenticatable { }\n\n/// Helper for creating authent"
},
{
"path": "Sources/Vapor/Authentication/BasicAuthorization.swift",
"chars": 1778,
"preview": "import Foundation\nimport NIOHTTP1\n\n/// A basic username and password.\npublic struct BasicAuthorization: Sendable {\n /"
},
{
"path": "Sources/Vapor/Authentication/BearerAuthorization.swift",
"chars": 1081,
"preview": "import NIOHTTP1\n\n/// A bearer token.\npublic struct BearerAuthorization: Sendable {\n /// The plaintext token\n publi"
},
{
"path": "Sources/Vapor/Authentication/GuardMiddleware.swift",
"chars": 2016,
"preview": "import NIOCore\n\nextension Authenticatable {\n /// This middleware ensures that an `Authenticatable` type `A` has been "
},
{
"path": "Sources/Vapor/Authentication/RedirectMiddleware.swift",
"chars": 1416,
"preview": "import NIOCore\n\nextension Authenticatable {\n /// Basic middleware to redirect unauthenticated requests to the supplie"
},
{
"path": "Sources/Vapor/Authentication/SessionAuthenticatable.swift",
"chars": 2991,
"preview": "import NIOCore\n\n/// Helper for creating authentication middleware in conjunction with `SessionsMiddleware`.\npublic proto"
},
{
"path": "Sources/Vapor/Bcrypt/Bcrypt.swift",
"chars": 8603,
"preview": "import Foundation\nimport CVaporBcrypt\n\n// MARK: BCrypt\n\n/// Creates and verifies BCrypt hashes.\n///\n/// Use BCrypt to cr"
},
{
"path": "Sources/Vapor/Cache/Application+Cache.swift",
"chars": 1998,
"preview": "import NIOConcurrencyHelpers\n\nextension Application {\n /// Controls application's configured caches.\n ///\n /// "
},
{
"path": "Sources/Vapor/Cache/Cache.swift",
"chars": 1525,
"preview": "import NIOCore\n/// Codable key-value pair cache.\npublic protocol Cache {\n /// Gets a decodable value from the cache. "
},
{
"path": "Sources/Vapor/Cache/CacheExpirationTime.swift",
"chars": 565,
"preview": "/// Defines the lifetime of an entry in a cache.\npublic enum CacheExpirationTime: Sendable {\n case seconds(Int)\n c"
},
{
"path": "Sources/Vapor/Cache/MemoryCache.swift",
"chars": 3575,
"preview": "import Foundation\nimport NIOCore\nimport NIOConcurrencyHelpers\n\nextension Application.Caches {\n /// In-memory cache. T"
},
{
"path": "Sources/Vapor/Cache/Request+Cache.swift",
"chars": 99,
"preview": "extension Request {\n public var cache: Cache {\n self.application.cache.for(self)\n }\n}\n"
},
{
"path": "Sources/Vapor/Client/Application+Clients.swift",
"chars": 1820,
"preview": "import NIOConcurrencyHelpers\n\nextension Application {\n public var clients: Clients {\n .init(application: self)"
},
{
"path": "Sources/Vapor/Client/Client.swift",
"chars": 3239,
"preview": "import NIOCore\nimport Logging\nimport NIOHTTP1\n\npublic protocol Client: Sendable {\n var eventLoop: EventLoop { get }\n "
},
{
"path": "Sources/Vapor/Client/ClientRequest.swift",
"chars": 3923,
"preview": "import NIOCore\nimport NIOHTTP1\nimport Foundation\n\npublic struct ClientRequest: Sendable {\n public var method: HTTPMet"
},
{
"path": "Sources/Vapor/Client/ClientResponse.swift",
"chars": 5065,
"preview": "import NIOCore\nimport NIOHTTP1\nimport Foundation\n\npublic struct ClientResponse: Sendable {\n public var status: HTTPSt"
},
{
"path": "Sources/Vapor/Client/Request+Client.swift",
"chars": 189,
"preview": "extension Request {\n public var client: Client {\n self.application.client.delegating(to: self.eventLoop).loggi"
},
{
"path": "Sources/Vapor/Commands/BootCommand.swift",
"chars": 636,
"preview": "import ConsoleKit\n\n/// Boots the `Application` then exits successfully.\n///\n/// $ swift run Run boot\n/// Done.\n/"
},
{
"path": "Sources/Vapor/Commands/CommandContext+Application.swift",
"chars": 390,
"preview": "import ConsoleKit\n\nextension CommandContext {\n public var application: Application {\n get {\n guard "
},
{
"path": "Sources/Vapor/Commands/RoutesCommand.swift",
"chars": 3429,
"preview": "import ConsoleKit\nimport RoutingKit\n\n/// Displays all routes registered to the `Application`'s `Router` in an ASCII-form"
},
{
"path": "Sources/Vapor/Commands/ServeCommand.swift",
"chars": 5057,
"preview": "@preconcurrency import Dispatch\nimport Foundation\nimport ConsoleKit\nimport NIOConcurrencyHelpers\n\n/// Boots the applicat"
},
{
"path": "Sources/Vapor/Concurrency/AnyResponse+Concurrency.swift",
"chars": 1553,
"preview": "import NIOCore\n\n/// A type erased response useful for routes that can return more than one type.\n///\n/// router.get("
},
{
"path": "Sources/Vapor/Concurrency/AsyncBasicResponder.swift",
"chars": 815,
"preview": "import NIOCore\n\n/// A basic, async closure-based `Responder`.\npublic struct AsyncBasicResponder: AsyncResponder {\n //"
},
{
"path": "Sources/Vapor/Concurrency/AsyncMiddleware.swift",
"chars": 1320,
"preview": "import NIOCore\n\n/// `AsyncMiddleware` is placed between the server and your router. It is capable of\n/// mutating both i"
},
{
"path": "Sources/Vapor/Concurrency/AsyncPasswordHasher+Concurrency.swift",
"chars": 813,
"preview": "import NIOCore\nimport Foundation\n\nextension AsyncPasswordHasher {\n public func hash<Password>(_ password: Password) a"
},
{
"path": "Sources/Vapor/Concurrency/AsyncSessionDriver.swift",
"chars": 1962,
"preview": "import NIOCore\n\n/// Capable of managing CRUD operations for `Session`s.\n///\n/// This is an async version of `SessionDriv"
},
{
"path": "Sources/Vapor/Concurrency/Authentication+Concurrency.swift",
"chars": 4441,
"preview": "import NIOCore\n\n/// Helper for creating authentication middleware.\n///\n/// See `AsyncRequestAuthenticator` and `AsyncSes"
},
{
"path": "Sources/Vapor/Concurrency/Cache+Concurrency.swift",
"chars": 1165,
"preview": "import NIOCore\n\npublic extension Cache {\n\n /// Gets a decodable value from the cache. Returns `nil` if not found.\n "
},
{
"path": "Sources/Vapor/Concurrency/Client+Concurrency.swift",
"chars": 2665,
"preview": "import NIOCore\nimport NIOHTTP1\n\nextension Client {\n public func get(_ url: URI, headers: HTTPHeaders = [:], beforeSen"
},
{
"path": "Sources/Vapor/Concurrency/RequestBody+Concurrency.swift",
"chars": 6965,
"preview": "import NIOCore\nimport NIOConcurrencyHelpers\n\n// MARK: - Request.Body.AsyncSequenceDelegate\nextension Request.Body {\n "
},
{
"path": "Sources/Vapor/Concurrency/Responder+Concurrency.swift",
"chars": 437,
"preview": "import NIOCore\n\npublic protocol AsyncResponder: Responder {\n func respond(to request: Request) async throws -> Respon"
},
{
"path": "Sources/Vapor/Concurrency/ResponseCodable+Concurrency.swift",
"chars": 4023,
"preview": "import NIOCore\nimport NIOHTTP1\n\n/// Can convert `self` to a `Response`.\n///\n/// Types that conform to this protocol can "
},
{
"path": "Sources/Vapor/Concurrency/RoutesBuilder+Concurrency.swift",
"chars": 4936,
"preview": "import NIOCore\nimport NIOHTTP1\nimport RoutingKit\n\nextension RoutesBuilder {\n @discardableResult\n @preconcurrency\n "
},
{
"path": "Sources/Vapor/Concurrency/ViewRenderer+Concurrency.swift",
"chars": 646,
"preview": "import NIOCore\n\npublic extension ViewRenderer {\n func render<E>(_ name: String, _ context: E) async throws -> View wh"
},
{
"path": "Sources/Vapor/Concurrency/WebSocket+Concurrency.swift",
"chars": 5427,
"preview": "import NIOCore\nimport NIOHTTP1\nimport WebSocketKit\nimport RoutingKit\nimport Foundation\n\nextension Request {\n\n /// Upg"
},
{
"path": "Sources/Vapor/Content/ContainerGetPathExecutor.swift",
"chars": 3644,
"preview": "/// Decodes nested single values from data at a key path.\ninternal struct ContainerGetPathExecutor<D: Decodable>: Decoda"
},
{
"path": "Sources/Vapor/Content/Content.swift",
"chars": 4551,
"preview": "import NIOCore\n\n/// Convertible to / from content in an HTTP message.\n///\n/// Conformance to this protocol consists of:\n"
},
{
"path": "Sources/Vapor/Content/ContentCoders.swift",
"chars": 3640,
"preview": "import Foundation\nimport NIOCore\nimport NIOHTTP1\n\n/// Conform a type to this protocol to make it usable for encoding dat"
},
{
"path": "Sources/Vapor/Content/ContentConfiguration.swift",
"chars": 5977,
"preview": "import Foundation\nimport MultipartKit\nimport NIOConcurrencyHelpers\n\n/// Configures which ``Encoder``s and ``Decoder``s t"
},
{
"path": "Sources/Vapor/Content/ContentContainer.swift",
"chars": 6410,
"preview": "import NIOHTTP1\nimport NIOCore\n\npublic protocol ContentContainer {\n /// The type of data stored in the container.\n "
},
{
"path": "Sources/Vapor/Content/JSONCoder+Custom.swift",
"chars": 3212,
"preview": "import Foundation\n\nextension JSONEncoder {\n /// Convenience for creating a customized ``Foundation/JSONEncoder``.\n "
},
{
"path": "Sources/Vapor/Content/JSONCoders+Content.swift",
"chars": 3225,
"preview": "import Foundation\nimport NIOCore\nimport NIOFoundationCompat\nimport NIOHTTP1\n\n#if canImport(Darwin)\nextension JSONEncoder"
},
{
"path": "Sources/Vapor/Content/PlaintextDecoder.swift",
"chars": 4041,
"preview": "import NIOCore\nimport NIOHTTP1\n\n/// Decodes data as plaintext, utf8.\npublic struct PlaintextDecoder: ContentDecoder {\n "
},
{
"path": "Sources/Vapor/Content/PlaintextEncoder.swift",
"chars": 5525,
"preview": "import Foundation\nimport NIOCore\nimport NIOHTTP1\n\n/// Encodes data as plaintext, utf8.\npublic struct PlaintextEncoder: C"
},
{
"path": "Sources/Vapor/Content/URLQueryCoders.swift",
"chars": 996,
"preview": "public protocol URLQueryDecoder: Sendable {\n func decode<D>(_ decodable: D.Type, from url: URI) throws -> D\n w"
},
{
"path": "Sources/Vapor/Content/URLQueryContainer.swift",
"chars": 4363,
"preview": "import NIOHTTP1\nimport NIOCore\n\n/// Helper for encoding and decoding data from an HTTP request query string.\n///\n/// See"
},
{
"path": "Sources/Vapor/Core/Core.swift",
"chars": 6226,
"preview": "import ConsoleKit\nimport NIOCore\nimport NIOPosix\nimport NIOConcurrencyHelpers\n\nextension Application {\n public var co"
},
{
"path": "Sources/Vapor/Core/Running.swift",
"chars": 672,
"preview": "import NIOCore\nimport NIOConcurrencyHelpers\n\nextension Application {\n public struct Running: Sendable {\n final"
},
{
"path": "Sources/Vapor/Deprecations/CORSMiddleware+AllowOriginSetting.swift",
"chars": 186,
"preview": "extension CORSMiddleware.AllowOriginSetting {\n @available(*, deprecated, renamed: \"any\")\n public static func white"
},
{
"path": "Sources/Vapor/Deprecations/CORSMiddleware+Configuration+exposedHeaders.swift",
"chars": 1464,
"preview": "import NIOHTTP1\n\nextension CORSMiddleware.Configuration {\n /// Instantiate a CORSConfiguration struct that can be use"
},
{
"path": "Sources/Vapor/Deprecations/DotEnvFile+load.swift",
"chars": 2802,
"preview": "import Logging\nimport NIOCore\nimport NIOPosix\n\nextension DotEnvFile {\n /// Reads the dotenv files relevant to the env"
},
{
"path": "Sources/Vapor/Deprecations/Routes+caseInsenstive.swift",
"chars": 239,
"preview": "extension Routes {\n @available(*, deprecated, renamed: \"caseInsensitive\")\n public var caseInsenstive: Bool {\n "
},
{
"path": "Sources/Vapor/Deprecations/Validatable+validate.swift",
"chars": 221,
"preview": "extension Validatable {\n @available(*, deprecated, renamed: \"validate(content:)\")\n public static func validate(_ r"
},
{
"path": "Sources/Vapor/Docs.docc/index.md",
"chars": 354,
"preview": "# ``Vapor``\n\nVapor is a framework for building server applications, APIs and websites in Swift. It provides a safe, perf"
},
{
"path": "Sources/Vapor/Environment/Environment+Process.swift",
"chars": 1863,
"preview": "import Foundation\n\nextension Environment { \n /// The process information of an environment. Wraps `ProcessInto.pro"
},
{
"path": "Sources/Vapor/Environment/Environment+Secret.swift",
"chars": 4477,
"preview": "import NIOCore\nimport NIOPosix\nimport AsyncKit\nimport _NIOFileSystem\n\nextension Environment {\n /// Reads a file's con"
},
{
"path": "Sources/Vapor/Environment/Environment.swift",
"chars": 7254,
"preview": "import Foundation\nimport ConsoleKit\n\n/// The environment the application is running in, i.e., production, dev, etc. All "
},
{
"path": "Sources/Vapor/Error/Abort.swift",
"chars": 4110,
"preview": "import NIOHTTP1\n\n/// Default implementation of `AbortError`. You can use this as a convenient method for throwing\n/// `A"
},
{
"path": "Sources/Vapor/Error/AbortError.swift",
"chars": 3392,
"preview": "import NIOHTTP1\n\n/// Errors conforming to this protocol will always be displayed by\n/// Vapor to the end-user (even in p"
},
{
"path": "Sources/Vapor/Error/DebuggableError.swift",
"chars": 7058,
"preview": "import Foundation\nimport Logging\n\n/// `Debuggable` provides an interface that allows a type\n/// to be more easily debugg"
},
{
"path": "Sources/Vapor/Error/Demangler.swift",
"chars": 1042,
"preview": "import Foundation\n\n/// Here be dragons! _stdlib_demangleImpl is linked into the stdlib. Use at your own risk!\n@_silgen_n"
},
{
"path": "Sources/Vapor/Error/ErrorSource.swift",
"chars": 1286,
"preview": "/// A source-code location.\npublic struct ErrorSource: Sendable {\n /// File in which this location exists.\n public"
},
{
"path": "Sources/Vapor/Error/StackTrace.swift",
"chars": 1388,
"preview": "import Foundation\nimport NIOConcurrencyHelpers\n\n@available(*, deprecated, message: \"Captured stack traces are no longer "
},
{
"path": "Sources/Vapor/Exports.swift",
"chars": 2461,
"preview": "@_documentation(visibility: internal) @_exported import AsyncKit\n@_documentation(visibility: internal) @_exported import"
},
{
"path": "Sources/Vapor/HTTP/Application+HTTP.swift",
"chars": 169,
"preview": "extension Application {\n public var http: HTTP {\n .init(application: self)\n }\n\n public struct HTTP {\n "
},
{
"path": "Sources/Vapor/HTTP/BasicResponder.swift",
"chars": 1036,
"preview": "import NIOCore\n\n/// A basic, closure-based `Responder`.\npublic struct BasicResponder: Responder {\n /// The stored res"
},
{
"path": "Sources/Vapor/HTTP/BodyStream.swift",
"chars": 2936,
"preview": "import NIOCore\n\npublic enum BodyStreamResult: Sendable {\n /// A normal data chunk.\n /// There will be 0 or more of"
},
{
"path": "Sources/Vapor/HTTP/Client/Application+HTTP+Client.swift",
"chars": 1969,
"preview": "import AsyncHTTPClient\n\nextension Application.Clients.Provider {\n public static var http: Self {\n .init {\n "
},
{
"path": "Sources/Vapor/HTTP/Client/EventLoopHTTPClient.swift",
"chars": 2471,
"preview": "import NIOCore\nimport AsyncHTTPClient\nimport Logging\nimport Foundation\n\nextension HTTPClient {\n func delegating(to ev"
},
{
"path": "Sources/Vapor/HTTP/EndpointCache.swift",
"chars": 7845,
"preview": "import Foundation\nimport NIOConcurrencyHelpers\nimport NIOCore\nimport Logging\nimport NIOHTTP1\n\npublic enum EndpointCacheE"
},
{
"path": "Sources/Vapor/HTTP/HTTPMethod+String.swift",
"chars": 207,
"preview": "import NIOHTTP1\n\nextension HTTPMethod {\n /// `String` representation of this `HTTPMethod`.\n @available(*, deprecat"
},
{
"path": "Sources/Vapor/HTTP/HTTPStatus.swift",
"chars": 853,
"preview": "import NIOHTTP1\nimport NIOCore\n\n/// Less verbose typealias for `HTTPResponseStatus`.\npublic typealias HTTPStatus = HTTPR"
},
{
"path": "Sources/Vapor/HTTP/Headers/HTTPCookies.swift",
"chars": 9738,
"preview": "import Foundation\nimport NIOHTTP1\n\nextension HTTPHeaders {\n /// Get and set `HTTPCookies` for an HTTP request\n ///"
},
{
"path": "Sources/Vapor/HTTP/Headers/HTTPHeaderCacheControl.swift",
"chars": 9610,
"preview": "import Foundation\nimport NIOHTTP1\n\n// Comments on these properties are copied from the mozilla doc URL shown below.\nexte"
},
{
"path": "Sources/Vapor/HTTP/Headers/HTTPHeaderExpires.swift",
"chars": 2044,
"preview": "import Foundation\nimport NIOHTTP1\n\nextension HTTPHeaders {\n public struct Expires {\n /// The date represented "
},
{
"path": "Sources/Vapor/HTTP/Headers/HTTPHeaderLastModified.swift",
"chars": 1580,
"preview": "import Foundation\nimport NIOHTTP1\n\nextension HTTPHeaders {\n /// Represents the HTTP `Last-Modified` header.\n /// -"
},
{
"path": "Sources/Vapor/HTTP/Headers/HTTPHeaders+Cache.swift",
"chars": 1306,
"preview": "import Foundation\nimport NIOHTTP1\n\nextension HTTPHeaders {\n /// Determines when the cached data should be expired.\n "
},
{
"path": "Sources/Vapor/HTTP/Headers/HTTPHeaders+Connection.swift",
"chars": 809,
"preview": "import NIOHTTP1\n\nextension HTTPHeaders {\n public struct Connection: ExpressibleByStringLiteral, Equatable, Sendable {"
},
{
"path": "Sources/Vapor/HTTP/Headers/HTTPHeaders+ContentDisposition.swift",
"chars": 2621,
"preview": "import NIOHTTP1\n\nextension HTTPHeaders {\n /// Convenience for accessing the Content-Disposition header.\n ///\n /"
},
{
"path": "Sources/Vapor/HTTP/Headers/HTTPHeaders+ContentRange.swift",
"chars": 10569,
"preview": "import Foundation\nimport NIOHTTP1\n\nextension HTTPHeaders {\n \n /// The unit in which `ContentRange`s and `Range`s a"
},
{
"path": "Sources/Vapor/HTTP/Headers/HTTPHeaders+Directive.swift",
"chars": 10344,
"preview": "import NIOHTTP1\n\nextension HTTPHeaders {\n struct Directive: Equatable, CustomStringConvertible {\n var value: S"
},
{
"path": "Sources/Vapor/HTTP/Headers/HTTPHeaders+Forwarded.swift",
"chars": 3653,
"preview": "import NIOHTTP1\n\nextension HTTPHeaders {\n /// Convenience for accessing the Forwarded header. This header is added by"
},
{
"path": "Sources/Vapor/HTTP/Headers/HTTPHeaders+Link.swift",
"chars": 6084,
"preview": "import NIOHTTP1\n\nextension HTTPHeaders {\n /// Convenience for accessing the Link header as an array of provided links"
},
{
"path": "Sources/Vapor/HTTP/Headers/HTTPHeaders+Name.swift",
"chars": 23602,
"preview": "import NIOHTTP1\n\nextension HTTPHeaders {\n /// Type used for the name of a HTTP header in the `HTTPHeaders` storage.\n "
},
{
"path": "Sources/Vapor/HTTP/Headers/HTTPHeaders+ResponseCompression.swift",
"chars": 1873,
"preview": "import Foundation\nimport NIOHTTP1\n\nextension HTTPHeaders {\n /// A marker header internal to vapor that explicitly all"
},
{
"path": "Sources/Vapor/HTTP/Headers/HTTPHeaders.swift",
"chars": 2600,
"preview": "import NIOHTTP1\n\nextension HTTPHeaders {\n /// `MediaType` specified by this message's `\"Content-Type\"` header.\n pu"
},
{
"path": "Sources/Vapor/HTTP/Headers/HTTPMediaType.swift",
"chars": 53510,
"preview": "import NIOHTTP1\n\n/// Represents an encoded data-format, used in HTTP, HTML, email, and elsewhere.\n///\n/// text/plain"
},
{
"path": "Sources/Vapor/HTTP/Headers/HTTPMediaTypePreference.swift",
"chars": 2182,
"preview": "import Foundation\nimport NIOHTTP1\n\n/// Represents a `MediaType` and its associated preference, `q`.\npublic struct HTTPMe"
},
{
"path": "Sources/Vapor/HTTP/Responder.swift",
"chars": 123,
"preview": "import NIOCore\n\npublic protocol Responder: Sendable {\n func respond(to request: Request) -> EventLoopFuture<Response>"
},
{
"path": "Sources/Vapor/HTTP/Server/Application+HTTP+Server.swift",
"chars": 2625,
"preview": "extension Application.Servers.Provider {\n public static var http: Self {\n .init {\n $0.servers.use {"
},
{
"path": "Sources/Vapor/HTTP/Server/HTTPServer.swift",
"chars": 36655,
"preview": "import NIOCore\nimport NIOExtras\nimport NIOHTTP1\nimport NIOHTTP2\nimport NIOHTTPCompression\nimport NIOSSL\nimport Logging\ni"
},
{
"path": "Sources/Vapor/HTTP/Server/HTTPServerConfiguration+RequestDecompressionConfiguration.swift",
"chars": 1101,
"preview": "extension HTTPServer.Configuration {\n /// Supported HTTP decompression options.\n public struct RequestDecompressio"
},
{
"path": "Sources/Vapor/HTTP/Server/HTTPServerConfiguration+ResponseCompressionConfiguration.swift",
"chars": 8743,
"preview": "extension HTTPServer.Configuration {\n /// Supported HTTP response compression options.\n public struct ResponseComp"
},
{
"path": "Sources/Vapor/HTTP/Server/HTTPServerHandler.swift",
"chars": 3776,
"preview": "import NIOCore\nimport Logging\n\nfinal class HTTPServerHandler: ChannelInboundHandler, RemovableChannelHandler {\n typea"
},
{
"path": "Sources/Vapor/HTTP/Server/HTTPServerRequestDecoder.swift",
"chars": 15049,
"preview": "import Logging\nimport NIOCore\nimport NIOHTTP1\nimport Foundation\nimport X509\n\nfinal class HTTPServerRequestDecoder: Chann"
},
{
"path": "Sources/Vapor/HTTP/Server/HTTPServerResponseEncoder.swift",
"chars": 10004,
"preview": "import NIOCore\nimport NIOHTTP1\nimport NIOConcurrencyHelpers\n\nfinal class HTTPServerResponseEncoder: ChannelOutboundHandl"
},
{
"path": "Sources/Vapor/HTTP/Server/HTTPServerUpgradeHandler.swift",
"chars": 6420,
"preview": "import NIOCore\nimport NIOHTTP1\nimport NIOWebSocket\nimport WebSocketKit\n\nfinal class HTTPServerUpgradeHandler: ChannelDup"
},
{
"path": "Sources/Vapor/Logging/Logger+Report.swift",
"chars": 1365,
"preview": "import Foundation\nimport Logging\n\nextension Logger {\n /// Reports an `Error` to this `Logger`.\n ///\n /// - para"
},
{
"path": "Sources/Vapor/Logging/LoggingSystem+Environment.swift",
"chars": 1302,
"preview": "import Logging\nimport ConsoleKit\n\nextension LoggingSystem {\n @preconcurrency\n public static func bootstrap(from en"
},
{
"path": "Sources/Vapor/Middleware/Application+Middleware.swift",
"chars": 672,
"preview": "extension Application {\n public var middleware: Middlewares {\n get {\n if let existing = self.storag"
},
{
"path": "Sources/Vapor/Middleware/CORSMiddleware.swift",
"chars": 7964,
"preview": "import NIOHTTP1\nimport NIOCore\n\n/// Middleware that adds support for CORS settings in request responses.\n/// For configu"
},
{
"path": "Sources/Vapor/Middleware/ErrorMiddleware.swift",
"chars": 4098,
"preview": "import Foundation\nimport NIOCore\nimport NIOHTTP1\n\n/// Captures all errors and transforms them into an internal server er"
},
{
"path": "Sources/Vapor/Middleware/FileMiddleware.swift",
"chars": 10961,
"preview": "import Foundation\nimport NIOCore\nimport _NIOFileSystem\n\n/// Serves static files from a public directory.\n///\n/// `FileMi"
},
{
"path": "Sources/Vapor/Middleware/Middleware.swift",
"chars": 2179,
"preview": "import NIOCore\n\n/// `Middleware` is placed between the server and your router. It is capable of\n/// mutating both incomi"
},
{
"path": "Sources/Vapor/Middleware/MiddlewareConfiguration.swift",
"chars": 1129,
"preview": "/// Configures an application's active `Middleware`.\n/// Middleware will be used in the order they are added.\npublic str"
},
{
"path": "Sources/Vapor/Middleware/ResponseCompressionMiddleware.swift",
"chars": 4530,
"preview": "/// Overrides the response compression settings for a route.\n///\n/// This is useful when a set of static routes does not"
},
{
"path": "Sources/Vapor/Middleware/RouteLoggingMiddleware.swift",
"chars": 649,
"preview": "import NIOCore\nimport Logging\n\n/// Emits a log message containing the request method and path to a `Request`'s logger.\n/"
},
{
"path": "Sources/Vapor/Middleware/TracingMiddleware.swift",
"chars": 4819,
"preview": "import Tracing\n\n/// Creates a trace and metadata for every request\n///\n/// See https://opentelemetry.io/docs/specs/semco"
},
{
"path": "Sources/Vapor/Multipart/File+Multipart.swift",
"chars": 1631,
"preview": "import MultipartKit\nimport NIOHTTP1\n\nextension File: MultipartPartConvertible {\n public var multipart: MultipartPart?"
},
{
"path": "Sources/Vapor/Multipart/FormDataDecoder+Content.swift",
"chars": 1309,
"preview": "import MultipartKit\nimport NIOHTTP1\nimport NIOCore\n\nextension FormDataDecoder: ContentDecoder {\n public func decode<D"
},
{
"path": "Sources/Vapor/Multipart/FormDataEncoder+Content.swift",
"chars": 1245,
"preview": "import MultipartKit\nimport NIOHTTP1\nimport NIOCore\n\nextension FormDataEncoder: ContentEncoder {\n public func encode<E"
},
{
"path": "Sources/Vapor/Passwords/Application+Password.swift",
"chars": 1226,
"preview": "import Foundation\n\nextension Application {\n public var password: Password {\n .init(application: self)\n }\n\n "
},
{
"path": "Sources/Vapor/Passwords/Application+Passwords.swift",
"chars": 1517,
"preview": "import NIOConcurrencyHelpers\n\nextension Application {\n public var passwords: Passwords {\n .init(application: s"
},
{
"path": "Sources/Vapor/Passwords/AsyncPasswordHasher.swift",
"chars": 1722,
"preview": "import NIOCore\nimport NIOPosix\nimport Foundation\n\nextension PasswordHasher {\n public func async(\n on threadPoo"
},
{
"path": "Sources/Vapor/Passwords/BcryptHasher.swift",
"chars": 1023,
"preview": "import Foundation\n\nextension Application.Passwords.Provider {\n public static var bcrypt: Self {\n .bcrypt(cost:"
},
{
"path": "Sources/Vapor/Passwords/PasswordHasher.swift",
"chars": 721,
"preview": "import Foundation\n\npublic protocol PasswordHasher: Sendable {\n func hash<Password>(_ password: Password) throws -> [U"
},
{
"path": "Sources/Vapor/Passwords/PlaintextHasher.swift",
"chars": 654,
"preview": "import Foundation\n\nextension Application.Passwords.Provider {\n public static var plaintext: Self {\n .init {\n "
},
{
"path": "Sources/Vapor/Passwords/Request+Password.swift",
"chars": 1029,
"preview": "import Foundation\n\nextension Request {\n public var password: Password {\n .init(request: self)\n }\n \n p"
},
{
"path": "Sources/Vapor/Request/Redirect.swift",
"chars": 4132,
"preview": "extension Request {\n /// Creates a redirect `Response`.\n ///\n /// router.get(\"redirect\") { req in\n /// "
},
{
"path": "Sources/Vapor/Request/Request+Body.swift",
"chars": 2244,
"preview": "import NIOCore\n\nextension Request {\n public struct Body: CustomStringConvertible, Sendable {\n let request: Req"
},
{
"path": "Sources/Vapor/Request/Request+BodyStream.swift",
"chars": 4586,
"preview": "import NIOCore\nimport NIOConcurrencyHelpers\n\nextension Request {\n final class BodyStream: BodyStreamWriter, AsyncBody"
},
{
"path": "Sources/Vapor/Request/Request.swift",
"chars": 15820,
"preview": "import Foundation\nimport NIOCore\nimport NIOHTTP1\nimport Logging\nimport RoutingKit\nimport NIOConcurrencyHelpers\nimport Se"
},
{
"path": "Sources/Vapor/Responder/Application+Responder.swift",
"chars": 2496,
"preview": "import NIOCore\nimport NIOConcurrencyHelpers\n\nextension Application {\n public var responder: Responder {\n .init"
},
{
"path": "Sources/Vapor/Responder/DefaultResponder.swift",
"chars": 5609,
"preview": "import Foundation\nimport Metrics\n@preconcurrency import RoutingKit\nimport NIOCore\nimport NIOHTTP1\nimport Logging\n\n/// Va"
},
{
"path": "Sources/Vapor/Response/Response+Body.swift",
"chars": 13565,
"preview": "@preconcurrency import Dispatch\nimport Foundation\nimport NIOCore\nimport NIOFoundationCompat\nimport NIOConcurrencyHelpers"
},
{
"path": "Sources/Vapor/Response/Response.swift",
"chars": 8276,
"preview": "import NIOCore\nimport NIOHTTP1\nimport NIOFoundationCompat\nimport NIOConcurrencyHelpers\n\n/// An HTTP response from a serv"
},
{
"path": "Sources/Vapor/Response/ResponseCodable.swift",
"chars": 3628,
"preview": "import NIOCore\nimport NIOHTTP1\n\n/// Can convert `self` to a `Response`.\n///\n/// Types that conform to this protocol can "
},
{
"path": "Sources/Vapor/Routing/Application+Routes.swift",
"chars": 360,
"preview": "extension Application {\n public var routes: Routes {\n if let existing = self.storage[RoutesKey.self] {\n "
},
{
"path": "Sources/Vapor/Routing/Parameters+Require.swift",
"chars": 1601,
"preview": "import RoutingKit\n\nextension Parameters {\n /// Grabs the named parameter from the parameter bag.\n /// If the param"
},
{
"path": "Sources/Vapor/Routing/Request+WebSocket.swift",
"chars": 702,
"preview": "import NIOCore\nimport WebSocketKit\nimport NIOHTTP1\n\nextension Request {\n @preconcurrency public func webSocket(\n "
},
{
"path": "Sources/Vapor/Routing/Route.swift",
"chars": 2679,
"preview": "import NIOHTTP1\nimport RoutingKit\nimport NIOConcurrencyHelpers\n\npublic final class Route: CustomStringConvertible, Senda"
},
{
"path": "Sources/Vapor/Routing/RouteCollection.swift",
"chars": 586,
"preview": "/// Groups collections of routes together for adding to a router.\npublic protocol RouteCollection {\n /// Registers ro"
},
{
"path": "Sources/Vapor/Routing/Routes.swift",
"chars": 1863,
"preview": "import NIOConcurrencyHelpers\n\npublic final class Routes: RoutesBuilder, CustomStringConvertible, Sendable {\n public v"
},
{
"path": "Sources/Vapor/Routing/RoutesBuilder+Group.swift",
"chars": 3145,
"preview": "import RoutingKit\n\nextension RoutesBuilder {\n // MARK: Path\n \n /// Creates a new `Router` that will automatical"
},
{
"path": "Sources/Vapor/Routing/RoutesBuilder+Method.swift",
"chars": 6221,
"preview": "import RoutingKit\nimport NIOHTTP1\n\n/// Determines how an incoming HTTP request's body is collected.\npublic enum HTTPBody"
},
{
"path": "Sources/Vapor/Routing/RoutesBuilder+Middleware.swift",
"chars": 3097,
"preview": "extension RoutesBuilder {\n // MARK: Middleware\n\n /// Creates a new `Router` wrapped in the supplied variadic `Midd"
},
{
"path": "Sources/Vapor/Routing/RoutesBuilder+WebSocket.swift",
"chars": 2877,
"preview": "import RoutingKit\nimport WebSocketKit\nimport NIOCore\nimport NIOHTTP1\n\npublic struct WebSocketMaxFrameSize: Sendable, Exp"
},
{
"path": "Sources/Vapor/Routing/RoutesBuilder.swift",
"chars": 237,
"preview": "import Foundation\n\npublic protocol RoutesBuilder {\n func add(_ route: Route)\n}\n\nextension Foundation.UUID: Swift.Loss"
},
{
"path": "Sources/Vapor/Security/OTP.swift",
"chars": 10634,
"preview": "import Foundation\n#if canImport(Darwin) && compiler(>=6.1)\nimport Crypto\n#else\n@preconcurrency import Crypto\n#endif\n\n///"
},
{
"path": "Sources/Vapor/Security/ValidatedCertificateChain.swift",
"chars": 906,
"preview": "import X509\nimport NIOSSL\nimport SwiftASN1\n\nextension NIOSSLCertificate {\n // Convert NIOSSL certificate to X509 cert"
},
{
"path": "Sources/Vapor/Server/Application+Servers.swift",
"chars": 2865,
"preview": "import NIOConcurrencyHelpers\n\nextension Application {\n public var servers: Servers {\n .init(application: self)"
},
{
"path": "Sources/Vapor/Server/Server.swift",
"chars": 4465,
"preview": "import NIOCore\n\n// TODO: Remove these deprecated methods along with ServerStartError in the major release.\npublic protoc"
},
{
"path": "Sources/Vapor/Services/App+Service.swift",
"chars": 181,
"preview": "extension Application {\n public struct Services {\n public let application: Application\n }\n\n public var s"
},
{
"path": "Sources/Vapor/Services/Req+Service.swift",
"chars": 246,
"preview": "extension Request {\n public struct Services {\n public let request: Request\n init(request: Request) {\n "
},
{
"path": "Sources/Vapor/Services/Service.swift",
"chars": 2683,
"preview": "import NIOConcurrencyHelpers\n\nextension Application {\n public struct Service<ServiceType> {\n\n let application:"
},
{
"path": "Sources/Vapor/Sessions/Application+Sessions.swift",
"chars": 2792,
"preview": "import NIOConcurrencyHelpers\n\nextension Application {\n public var sessions: Sessions {\n .init(application: sel"
},
{
"path": "Sources/Vapor/Sessions/MemorySessions.swift",
"chars": 2175,
"preview": "import Foundation\nimport NIOCore\nimport NIOConcurrencyHelpers\n\n/// Simple in-memory sessions implementation.\npublic stru"
},
{
"path": "Sources/Vapor/Sessions/Request+Session.swift",
"chars": 1607,
"preview": "extension Request {\n /// Returns the current `Session` or creates one.\n ///\n /// router.get(\"session\") { re"
},
{
"path": "Sources/Vapor/Sessions/Session.swift",
"chars": 2061,
"preview": "import NIOConcurrencyHelpers\n\n/// Sessions are a method for associating data with a client accessing your app.\n///\n/// E"
},
{
"path": "Sources/Vapor/Sessions/SessionCache.swift",
"chars": 503,
"preview": "import NIOConcurrencyHelpers\n\n/// Singleton service cache for a `Session`. Used with a message's private container.\ninte"
},
{
"path": "Sources/Vapor/Sessions/SessionData.swift",
"chars": 2092,
"preview": "/// A container for storing data associated with a given `SessionID`.\n///\n/// You can add data to an instance of `Sessio"
},
{
"path": "Sources/Vapor/Sessions/SessionDriver.swift",
"chars": 638,
"preview": "import NIOCore\n\n/// Capable of managing CRUD operations for `Session`s.\npublic protocol SessionDriver: Sendable {\n fu"
},
{
"path": "Sources/Vapor/Sessions/SessionsConfiguration.swift",
"chars": 1619,
"preview": "import Foundation\n\n/// Configuration options for sessions.\npublic struct SessionsConfiguration: Sendable {\n /// Creat"
},
{
"path": "Sources/Vapor/Sessions/SessionsMiddleware.swift",
"chars": 4371,
"preview": "import NIOCore\n\n/// Uses HTTP cookies to save and restore sessions for connecting clients.\n///\n/// If a cookie matching "
},
{
"path": "Sources/Vapor/URLEncodedForm/URLEncodedFormData.swift",
"chars": 3738,
"preview": "import Logging\n\n/// Keeps track if the string was percent encoded or not.\n/// Prevents double encoding/double decoding\ne"
},
{
"path": "Sources/Vapor/URLEncodedForm/URLEncodedFormDecoder.swift",
"chars": 24011,
"preview": "import NIOCore\nimport Foundation\nimport NIOHTTP1\n\n/// Decodes instances of `Decodable` types from `application/x-www-for"
},
{
"path": "Sources/Vapor/URLEncodedForm/URLEncodedFormEncoder.swift",
"chars": 18378,
"preview": "import Foundation\nimport NIOHTTP1\nimport NIOCore\n\n/// Encodes `Encodable` instances to `application/x-www-form-urlencode"
},
{
"path": "Sources/Vapor/URLEncodedForm/URLEncodedFormError.swift",
"chars": 576,
"preview": "import NIOHTTP1\n\n/// Errors thrown while encoding/decoding `application/x-www-form-urlencoded` data.\nenum URLEncodedForm"
},
{
"path": "Sources/Vapor/URLEncodedForm/URLEncodedFormParser.swift",
"chars": 1711,
"preview": "/// Parses a URL Query `single=value&arr=1&arr=2&obj[key]=objValue` into\ninternal struct URLEncodedFormParser {\n init"
},
{
"path": "Sources/Vapor/URLEncodedForm/URLEncodedFormSerializer.swift",
"chars": 2671,
"preview": "import struct Foundation.CharacterSet\n\nstruct URLEncodedFormSerializer: Sendable {\n let splitVariablesOn: Character\n "
},
{
"path": "Sources/Vapor/URLEncodedForm/URLQueryFragmentConvertible.swift",
"chars": 4092,
"preview": "import Foundation\n\n/// Capable of converting to / from `URLQueryFragment`.\nprotocol URLQueryFragmentConvertible {\n //"
},
{
"path": "Sources/Vapor/Utilities/AnyResponse.swift",
"chars": 1486,
"preview": "import NIOCore\n\n/// A type erased response useful for routes that can return more than one type.\n///\n/// router.get("
},
{
"path": "Sources/Vapor/Utilities/Array+Random.swift",
"chars": 985,
"preview": "extension FixedWidthInteger {\n public static func random() -> Self {\n return Self.random(in: .min ... .max)\n "
},
{
"path": "Sources/Vapor/Utilities/Base32.swift",
"chars": 7320,
"preview": "/// IMPORTANT:\n///\n/// These APIs are `internal` rather than `public` on purpose - specifically due to the high risk of "
},
{
"path": "Sources/Vapor/Utilities/Base64.swift",
"chars": 8672,
"preview": "/// IMPORTANT:\n///\n/// These APIs are `internal` rather than `public` on purpose - specifically due to the high risk of "
},
{
"path": "Sources/Vapor/Utilities/BaseN.swift",
"chars": 9294,
"preview": "import Algorithms\n\n/// IMPORTANT:\n///\n/// These APIs are `internal` rather than `public` on purpose - partially because "
},
{
"path": "Sources/Vapor/Utilities/BasicCodingKey.swift",
"chars": 2738,
"preview": "#if os(Linux)\npublic typealias CodingKeyRepresentable = Swift.CodingKeyRepresentable\n#else\n/// This is an unwelcome stan"
},
{
"path": "Sources/Vapor/Utilities/ByteCount.swift",
"chars": 1994,
"preview": "import Foundation\n\n/// Represents a number of bytes:\n///\n/// let bytes: ByteCount = \"1mb\"\n/// print(bytes.value) // 1048"
},
{
"path": "Sources/Vapor/Utilities/Bytes+Hex.swift",
"chars": 1684,
"preview": "extension Sequence where Element == UInt8 {\n public var hex: String {\n self.hexEncodedString()\n }\n\n publ"
},
{
"path": "Sources/Vapor/Utilities/Bytes+SecureCompare.swift",
"chars": 1355,
"preview": "extension Collection where Element: Equatable {\n /// Performs a full-comparison of all elements in two collections. I"
},
{
"path": "Sources/Vapor/Utilities/Collection+Safe.swift",
"chars": 226,
"preview": "extension Collection {\n /// Returns the element at the specified index if it is within bounds, otherwise nil.\n sub"
},
{
"path": "Sources/Vapor/Utilities/DataProtocol+Copy.swift",
"chars": 106,
"preview": "import Foundation\n\nextension DataProtocol {\n func copyBytes() -> [UInt8] {\n Array(self)\n }\n}\n"
},
{
"path": "Sources/Vapor/Utilities/DecoderUnwrapper.swift",
"chars": 255,
"preview": "@available(*, deprecated, message: \"This type violates Codable invariants; using it is not safe.\")\npublic struct Decoder"
},
{
"path": "Sources/Vapor/Utilities/DirectoryConfiguration.swift",
"chars": 2398,
"preview": "#if canImport(Glibc)\nimport Glibc\n#elseif canImport(Musl)\nimport Musl\n#elseif canImport(Android)\nimport Android\n#else\nim"
},
{
"path": "Sources/Vapor/Utilities/DotEnv.swift",
"chars": 17296,
"preview": "#if canImport(Glibc)\nimport Glibc\n#elseif canImport(Musl)\nimport Musl\n#elseif canImport(Android)\nimport Android\n#else\nim"
}
]
// ... and 125 more files (download for full content)
About this extraction
This page contains the full source code of the vapor/vapor GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 325 files (1.7 MB), approximately 428.1k tokens, and a symbol index with 21 extracted functions, classes, methods, constants, and types. 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.