diff --git a/.azure/pipelines/ci.yml b/.azure/pipelines/ci.yml index 6ba45c92bb..e356ab3d42 100644 --- a/.azure/pipelines/ci.yml +++ b/.azure/pipelines/ci.yml @@ -7,9 +7,9 @@ trigger: batch: true branches: include: + - blazor-wasm - master - release/* - - internal/release/3.* # Run PR validation on all branches pr: @@ -32,6 +32,8 @@ variables: - name: _DotNetValidationArtifactsCategory value: .NETCORE - ${{ if ne(variables['System.TeamProject'], 'internal') }}: + - name: _UseHelixOpenQueues + value: 'true' - name: _BuildArgs value: '' - name: _PublishArgs @@ -51,7 +53,9 @@ variables: # to have it in two different forms - name: _InternalRuntimeDownloadCodeSignArgs value: /p:DotNetRuntimeSourceFeed=https://dotnetclimsrc.blob.core.windows.net/dotnet /p:DotNetRuntimeSourceFeedKey=$(dotnetclimsrc-read-sas-token-base64) -- ${{ if eq(variables['System.TeamProject'], 'internal') }}: + - group: DotNet-HelixApi-Access + - name: _UseHelixOpenQueues + value: 'false' - ${{ if notin(variables['Build.Reason'], 'PullRequest') }}: # DotNet-Blob-Feed provides: dotnetfeed-storage-access-key-1 # Publish-Build-Assets provides: MaestroAccessToken, BotAccount-dotnet-maestro-bot-PAT @@ -150,8 +154,7 @@ stages: displayName: Build x64 # Build the x86 shared framework - # TODO: make it possible to build for one Windows architecture at a time - # This is going to actually build x86 native assets. See https://github.com/aspnet/AspNetCore/issues/7196 + # This is going to actually build x86 native assets. - script: ./build.cmd -ci -arch x86 @@ -172,6 +175,8 @@ stages: -noBuildDeps $(_BuildArgs) $(_InternalRuntimeDownloadArgs) + # Disabled until 3.1.3 is released + condition: false displayName: Build SiteExtension # This runs code-signing on all packages, zips, and jar files as defined in build/CodeSign.targets. If https://github.com/dotnet/arcade/issues/1957 is resolved, @@ -249,6 +254,38 @@ stages: - name: Windows_arm_Packages path: artifacts/packages/ + # Build Windows ARM64 + - template: jobs/default-build.yml + parameters: + codeSign: true + jobName: Windows_64_build + jobDisplayName: "Build: Windows ARM64" + agentOs: Windows + buildArgs: + -arch arm64 + -sign + -pack + -noBuildNodeJS + -noBuildJava + /bl:artifacts/log/build.win-arm64.binlog + /p:DotNetSignType=$(_SignType) + /p:OnlyPackPlatformSpecificPackages=true + /p:AssetManifestFileName=aspnetcore-win-arm64.xml + $(_BuildArgs) + $(_PublishArgs) + $(_InternalRuntimeDownloadArgs) + installNodeJs: false + installJdk: false + artifacts: + - name: Windows_arm64_Logs + path: artifacts/log/ + publishOnError: true + includeForks: true + - name: Windows_arm64_Packages + path: artifacts/packages/ + - name: Windows_arm64_Installers + path: artifacts/installers/ + # Build MacOS - template: jobs/default-build.yml parameters: @@ -489,25 +526,25 @@ stages: jobDisplayName: "Test: Windows Server 2016 x64" agentOs: Windows isTestingJob: true - buildArgs: -all -pack -test -BuildNative "/p:SkipIISNewHandlerTests=true /p:SkipIISTests=true /p:SkipIISExpressTests=true /p:SkipIISNewShimTests=true /p:RunTemplateTests=false" $(_InternalRuntimeDownloadArgs) + buildArgs: -all -pack -test -BuildNative "/p:SkipHelixReadyTests=true /p:SkipIISNewHandlerTests=true /p:SkipIISTests=true /p:SkipIISExpressTests=true /p:SkipIISNewShimTests=true /p:RunTemplateTests=false" $(_InternalRuntimeDownloadArgs) beforeBuild: - powershell: "& ./src/Servers/IIS/tools/UpdateIISExpressCertificate.ps1; & ./src/Servers/IIS/tools/update_schema.ps1" displayName: Setup IISExpress test certificates and schema afterBuild: - - powershell: "& ./build.ps1 -CI -NoBuild -Test /p:RunFlakyTests=true" - displayName: Run Flaky Tests + - powershell: "& ./build.ps1 -CI -NoBuild -Test /p:RunQuarantinedTests=true" + displayName: Run Quarantined Tests continueOnError: true - task: PublishTestResults@2 - displayName: Publish Flaky Test Results + displayName: Publish Quarantined Test Results inputs: testResultsFormat: 'xUnit' testResultsFiles: '*.xml' - searchFolder: '$(Build.SourcesDirectory)/artifacts/TestResults/$(_BuildConfig)/Flaky' + searchFolder: '$(Build.SourcesDirectory)/artifacts/TestResults/$(_BuildConfig)/Quarantined' artifacts: - name: Windows_Test_Dumps path: artifacts/dumps/ publishOnError: true - includeForks: false + includeForks: true - name: Windows_Test_Logs path: artifacts/log/ publishOnError: true @@ -543,7 +580,7 @@ stages: - name: Windows_Test_Templates_Dumps path: artifacts/dumps/ publishOnError: true - includeForks: false + includeForks: true - name: Windows_Test_Templates_Logs path: artifacts/log/ publishOnError: true @@ -560,7 +597,7 @@ stages: jobDisplayName: "Test: macOS 10.13" agentOs: macOS isTestingJob: true - buildArgs: --all --test "/p:RunTemplateTests=false" $(_InternalRuntimeDownloadArgs) + buildArgs: --all --test "/p:RunTemplateTests=false /p:SkipHelixReadyTests=true" $(_InternalRuntimeDownloadArgs) beforeBuild: - bash: "./eng/scripts/install-nginx-mac.sh" displayName: Installing Nginx @@ -569,15 +606,15 @@ stages: displayName: Pack Packages (for Template tests) - bash: ./src/ProjectTemplates/build.sh --ci --pack --no-restore --no-build-deps "/bl:artifacts/log/template.pack.binlog" displayName: Pack Templates (for Template tests) - - bash: ./build.sh --no-build --ci --test -p:RunFlakyTests=true - displayName: Run Flaky Tests + - bash: ./build.sh --no-build --ci --test -p:RunQuarantinedTests=true + displayName: Run Quarantined Tests continueOnError: true - task: PublishTestResults@2 - displayName: Publish Flaky Test Results + displayName: Publish Quarantined Test Results inputs: testResultsFormat: 'xUnit' testResultsFiles: '*.xml' - searchFolder: '$(Build.SourcesDirectory)/artifacts/TestResults/$(_BuildConfig)/Flaky' + searchFolder: '$(Build.SourcesDirectory)/artifacts/TestResults/$(_BuildConfig)/Quarantined' artifacts: - name: MacOS_Test_Logs path: artifacts/log/ @@ -595,7 +632,7 @@ stages: jobDisplayName: "Test: Ubuntu 16.04 x64" agentOs: Linux isTestingJob: true - buildArgs: --all --test "/p:RunTemplateTests=false" $(_InternalRuntimeDownloadArgs) + buildArgs: --all --test "/p:RunTemplateTests=false /p:SkipHelixReadyTests=true" $(_InternalRuntimeDownloadArgs) beforeBuild: - bash: "./eng/scripts/install-nginx-linux.sh" displayName: Installing Nginx @@ -606,15 +643,15 @@ stages: displayName: Pack Packages (for Template tests) - bash: ./src/ProjectTemplates/build.sh --ci --pack --no-restore --no-build-deps "/bl:artifacts/log/template.pack.binlog" displayName: Pack Templates (for Template tests) - - bash: ./build.sh --no-build --ci --test -p:RunFlakyTests=true - displayName: Run Flaky Tests + - bash: ./build.sh --no-build --ci --test -p:RunQuarantinedTests=true + displayName: Run Quarantined Tests continueOnError: true - task: PublishTestResults@2 - displayName: Publish Flaky Test Results + displayName: Publish Quarantined Test Results inputs: testResultsFormat: 'xUnit' testResultsFiles: '*.xml' - searchFolder: '$(Build.SourcesDirectory)/artifacts/TestResults/$(_BuildConfig)/Flaky' + searchFolder: '$(Build.SourcesDirectory)/artifacts/TestResults/$(_BuildConfig)/Quarantined' artifacts: - name: Linux_Test_Logs path: artifacts/log/ @@ -625,6 +662,80 @@ stages: publishOnError: true includeForks: true + # Helix x64 + - template: jobs/default-build.yml + parameters: + condition: in(variables['Build.Reason'], 'PullRequest') + jobName: Helix_x64 + jobDisplayName: 'Tests: Helix x64' + agentOs: Windows + timeoutInMinutes: 180 + steps: + # Build the shared framework + - script: ./build.cmd -ci -all -pack -arch x64 -buildNative /p:ASPNETCORE_TEST_LOG_DIR=artifacts/log /bl:artifacts/log/helix.build.x64.binlog + displayName: Build shared fx + - script: .\restore.cmd -ci /p:BuildInteropProjects=true + displayName: Restore + - script: .\build.cmd -ci -NoRestore -test -projects eng\helix\helix.proj /p:IsRequiredCheck=true /p:IsHelixJob=true /p:BuildAllProjects=true /p:BuildInteropProjects=true /p:BuildNative=true /p:RunTemplateTests=true /p:ASPNETCORE_TEST_LOG_DIR=artifacts/log -bl + displayName: Run build.cmd helix target + env: + HelixApiAccessToken: $(HelixApiAccessToken) # Needed for internal queues + SYSTEM_ACCESSTOKEN: $(System.AccessToken) # We need to set this env var to publish helix results to Azure Dev Ops + artifacts: + - name: Helix_logs + path: artifacts/log/ + publishOnError: true + includeForks: true + + - template: jobs/default-build.yml + parameters: + condition: notin(variables['Build.Reason'], 'PullRequest') + jobName: Helix_x64_daily + jobDisplayName: 'Tests: Helix x64 Daily' + agentOs: Windows + timeoutInMinutes: 180 + steps: + # Build the shared framework + - script: ./build.cmd -ci -all -pack -arch x64 -buildNative /p:ASPNETCORE_TEST_LOG_DIR=artifacts/log /bl:artifacts/log/helix.daily.build.x64.binlog + displayName: Build shared fx + # Build the x86 shared framework + - script: .\restore.cmd -ci /p:BuildInteropProjects=true + displayName: Restore + - script: .\build.cmd -ci -NoRestore -test -projects eng\helix\helix.proj /p:IsHelixJob=true /p:IsHelixDaily=true /p:BuildAllProjects=true /p:BuildInteropProjects=true /p:BuildNative=true /p:RunTemplateTests=true /p:ASPNETCORE_TEST_LOG_DIR=artifacts/log -bl + displayName: Run build.cmd helix target + env: + HelixApiAccessToken: $(HelixApiAccessToken) # Needed for internal queues + SYSTEM_ACCESSTOKEN: $(System.AccessToken) # We need to set this env var to publish helix results to Azure Dev Ops + artifacts: + - name: Helix_logs + path: artifacts/log/ + publishOnError: true + includeForks: true + + # Helix ARM64 + - template: jobs/default-build.yml + parameters: + condition: and(eq(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) + jobName: Helix_arm64_daily + jobDisplayName: "Tests: Helix ARM64 Daily" + agentOs: Linux + timeoutInMinutes: 180 + steps: + # Build the shared framework + - script: ./restore.sh -ci + displayName: Restore + - script: ./build.sh -ci --arch arm64 -test --no-build-nodejs -projects $(Build.SourcesDirectory)/eng/helix/helix.proj /p:IsHelixJob=true /p:IsHelixDaily=true /p:BuildAllProjects=true /p:BuildNative=true /p:ASPNETCORE_TEST_LOG_DIR=artifacts/log -bl + displayName: Run build.sh helix arm64 target + env: + HelixApiAccessToken: $(HelixApiAccessToken) # Needed for internal queues + SYSTEM_ACCESSTOKEN: $(System.AccessToken) # We need to set this env var to publish helix results to Azure Dev Ops + installNodeJs: false + artifacts: + - name: Helix_arm64_logs + path: artifacts/log/ + publishOnError: true + includeForks: true + # Source build - job: Source_Build displayName: 'Test: Linux Source Build' @@ -642,17 +753,6 @@ stages: chmod +x $HOME/bin/jq echo "##vso[task.prependpath]$HOME/bin" displayName: Install jq - - task: UseDotNet@2 - displayName: 'Use .NET Core sdk' - inputs: - packageType: sdk - # The SDK version selected here is intentionally supposed to use the latest release - # For the purpose of building Linux distros, we can't depend on features of the SDK - # which may not exist in pre-built versions of the SDK - # Pinning to preview 8 since preview 9 has breaking changes - version: 3.1.100 - installationPath: $(DotNetCoreSdkDir) - includePreviewVersions: true - ${{ if ne(variables['System.TeamProject'], 'public') }}: - task: Bash@3 displayName: Setup Private Feeds Credentials diff --git a/.azure/pipelines/devBuilds.yml b/.azure/pipelines/devBuilds.yml new file mode 100644 index 0000000000..61cb699a4e --- /dev/null +++ b/.azure/pipelines/devBuilds.yml @@ -0,0 +1,118 @@ +# +# See https://docs.microsoft.com/en-us/vsts/pipelines/yaml-schema for details on this file. +# + +# Configure which branches trigger builds +trigger: none + +# no PR builds +pr: none + +# Schedule this pipeline to run every midnight +schedules: +- cron: "0 8 * * *" + displayName: Daily midnight Dev builds + branches: + include: + - master + always: true + +stages: +- stage: build_components + displayName: Build Components + jobs: + # Build components on Windows (x64) + - template: jobs/default-build.yml + parameters: + codeSign: false + jobName: Windows_build + jobDisplayName: "Build: Components" + agentOs: Windows + steps: + - script: git submodule init + - script: git submodule update --recursive + - script: cd ./src/Components + - script: ./build.cmd + -ci + -arch x64 + /bl:artifacts/log/build.components.x64.binlog + displayName: Build x64 + artifacts: + - name: Windows_Logs + path: artifacts/log/ + publishOnError: true + includeForks: true + +- stage: build_servers + displayName: Build Servers + jobs: + # Build servers on Windows (x64) + - template: jobs/default-build.yml + parameters: + codeSign: false + jobName: Windows_build + jobDisplayName: "Build: Servers" + agentOs: Windows + steps: + - script: git submodule init + - script: git submodule update --recursive + - script: cd ./src/Servers + - script: ./build.cmd + -ci + -arch x64 + /bl:artifacts/log/build.servers.x64.binlog + displayName: Build x64 + artifacts: + - name: Windows_Logs + path: artifacts/log/ + publishOnError: true + includeForks: true + +- stage: build_project_templates + displayName: Build Project Templates + jobs: + # Build servers on Windows (x64) + - template: jobs/default-build.yml + parameters: + codeSign: false + jobName: Windows_build + jobDisplayName: "Build: Project Templates" + agentOs: Windows + steps: + - script: git submodule init + - script: git submodule update --recursive + - script: cd ./src/ProjectTemplates + - script: ./build.cmd + -ci + -arch x64 + /bl:artifacts/log/build.projectTemplates.x64.binlog + displayName: Build x64 + artifacts: + - name: Windows_Logs + path: artifacts/log/ + publishOnError: true + includeForks: true + +- stage: build_all + displayName: Build Everything + jobs: + # Build servers on Windows (x64) + - template: jobs/default-build.yml + parameters: + codeSign: false + jobName: Windows_build + jobDisplayName: "Build: Everything" + agentOs: Windows + steps: + - script: git submodule init + - script: git submodule update --recursive + - script: ./build.cmd + -ci + -arch x64 + /bl:artifacts/log/build.all.x64.binlog + displayName: Build x64 + artifacts: + - name: Windows_Logs + path: artifacts/log/ + publishOnError: true + includeForks: true diff --git a/.azure/pipelines/helix-test.yml b/.azure/pipelines/helix-test.yml deleted file mode 100644 index ad17b9963e..0000000000 --- a/.azure/pipelines/helix-test.yml +++ /dev/null @@ -1,27 +0,0 @@ -# Don't run CI for this config yet. We're not ready to move official builds on to Azure Pipelines -trigger: none - -# Run PR validation on all branches -pr: - branches: - include: - - '*' - -jobs: -- template: jobs/default-build.yml - parameters: - jobName: Helix_x64 - jobDisplayName: 'Tests: Helix x64' - agentOs: Windows - timeoutInMinutes: 240 - steps: - - script: .\restore.cmd -ci - displayName: Restore - - script: .\build.cmd -ci -NoRestore -test -projects eng\helix\helix.proj /p:IsHelixJob=true /p:BuildAllProjects=true /p:BuildNative=true -bl - displayName: Run build.cmd helix target - env: - SYSTEM_ACCESSTOKEN: $(System.AccessToken) # We need to set this env var to publish helix results to Azure Dev Ops - artifacts: - - name: Helix_logs - path: artifacts/log/ - publishOnError: true diff --git a/.azure/pipelines/jobs/default-build.yml b/.azure/pipelines/jobs/default-build.yml index 700b0cd7d0..f1596a7b10 100644 --- a/.azure/pipelines/jobs/default-build.yml +++ b/.azure/pipelines/jobs/default-build.yml @@ -81,7 +81,7 @@ jobs: enablePublishUsingPipelines: ${{ variables._PublishUsingPipelines }} enablePublishTestResults: true # publish test results to AzDO (populates AzDO Tests tab) enableTelemetry: true - helixRepo: aspnet/AspNetCore + helixRepo: dotnet/aspnetcore helixType: build.product/ workspace: clean: all @@ -251,6 +251,7 @@ jobs: condition: always() inputs: testRunner: junit - testResultsFiles: '**/TEST-com.microsoft.signalr*.xml' + testResultsFiles: '**/TEST-junit-jupiter.xml' buildConfiguration: $(BuildConfiguration) buildPlatform: $(AgentOsName) + mergeTestResults: true diff --git a/.azure/pipelines/quarantined-tests.yml b/.azure/pipelines/quarantined-tests.yml new file mode 100644 index 0000000000..3666d8fd8c --- /dev/null +++ b/.azure/pipelines/quarantined-tests.yml @@ -0,0 +1,46 @@ +# We want to run quarantined tests on master as well as on PRs +trigger: + batch: true + branches: + include: + - master + +schedules: +- cron: "0 */4 * * *" + displayName: Every 4 hours test run + branches: + include: + - master + always: true + +variables: +- ${{ if ne(variables['System.TeamProject'], 'internal') }}: + - name: _UseHelixOpenQueues + value: 'true' +- ${{ if eq(variables['System.TeamProject'], 'internal') }}: + - group: DotNet-HelixApi-Access + - name: _UseHelixOpenQueues + value: 'false' + +jobs: +- template: jobs/default-build.yml + parameters: + jobName: Helix_quarantine_x64 + jobDisplayName: 'Tests: Helix Quarantine x64' + agentOs: Windows + timeoutInMinutes: 240 + steps: + # Build the shared framework + - script: ./build.cmd -ci -all -pack -arch x64 -buildNative /p:ASPNETCORE_TEST_LOG_DIR=artifacts/log /bl:artifacts/log/helix.build.x64.binlog + displayName: Build shared fx + - script: .\restore.cmd -ci /p:BuildInteropProjects=true + displayName: Restore + - script: .\build.cmd -ci -NoRestore -test -noBuildJava -projects eng\helix\helix.proj /p:RunQuarantinedTests=true /p:IsRequiredCheck=true /p:IsHelixJob=true /p:BuildAllProjects=true /p:BuildInteropProjects=true /p:BuildNative=true /p:RunTemplateTests=true /p:ASPNETCORE_TEST_LOG_DIR=artifacts/log -bl + displayName: Run build.cmd helix target + env: + HelixApiAccessToken: $(HelixApiAccessToken) # Needed for internal queues + SYSTEM_ACCESSTOKEN: $(System.AccessToken) # We need to set this env var to publish helix results to Azure Dev Ops + artifacts: + - name: Helix_logs + path: artifacts/log/ + publishOnError: true diff --git a/.config/CredScanSuppressions.json b/.config/CredScanSuppressions.json index 2f6299934d..4c68f893d7 100644 --- a/.config/CredScanSuppressions.json +++ b/.config/CredScanSuppressions.json @@ -20,6 +20,10 @@ { "placeholder": "1qaz@WSX", "_justification": "This is a fake password used in test code." + }, + { + "file": "\\src\\Servers\\Kestrel\\shared\\test\\TestCertificates\\testCert.pfx", + "_justification": "Legitimate UT certificate file with private key" } ] } diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json new file mode 100644 index 0000000000..be95a01fc5 --- /dev/null +++ b/.config/dotnet-tools.json @@ -0,0 +1,12 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "dotnet-serve": { + "version": "1.5.0", + "commands": [ + "dotnet-serve" + ] + } + } +} \ No newline at end of file diff --git a/.gitattributes b/.gitattributes index ff67a9158f..3225eae5e0 100644 --- a/.gitattributes +++ b/.gitattributes @@ -8,6 +8,11 @@ ############################################################################### *.sh eol=lf +############################################################################### +# Make gradlew always have LF as line endings +############################################################################### +gradlew eol=lf + ############################################################################### # Set default behavior for command prompt diff. # diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 103c64248f..ce75c6821d 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -14,9 +14,11 @@ /src/Hosting/ @tratcher @anurse /src/Http/ @tratcher @jkotalik @anurse /src/Middleware/ @tratcher @anurse -/src/ProjectTemplates/ @ryanbrandenburg +/src/Middleware/HttpsPolicy/ @jkotalik @anurse +/src/Middleware/Rewrite/ @jkotalik @anurse +# /src/ProjectTemplates/ @ryanbrandenburg /src/Security/ @tratcher @anurse /src/Servers/ @tratcher @jkotalik @anurse @halter73 -/src/Middleware/Rewrite @jkotalik @anurse -/src/Middleware/HttpsPolicy @jkotalik @anurse +/src/Shared/runtime/ @dotnet/http +/src/Shared/test/Shared.Tests/runtime/ @dotnet/http /src/SignalR/ @BrennanConroy @halter73 @anurse diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index ed23a80be2..86f53b9a8a 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -3,24 +3,34 @@ name: Bug report about: Create a report about something that is not working --- -### If you believe you have an issue that affects the security of the platform please do NOT create an issue and instead email your issue details to secure@microsoft.com. Your report may be eligible for our [bug bounty](https://technet.microsoft.com/en-us/mt764065.aspx) but ONLY if it is reported through email. + ### Describe the bug A clear and concise description of what the bug is. ### To Reproduce -Steps to reproduce the behavior: -1. Using this version of ASP.NET Core '...' -2. Run this code '....' -3. With these arguments '....' -4. See error + + +### Further technical details +- ASP.NET Core version +- Include the output of `dotnet --info` +- The IDE (VS / VS Code/ VS4Mac) you're running on, and it's version diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index d2dac5f2da..d7991eb1e0 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -3,6 +3,12 @@ name: Feature request about: Suggest an idea for this project --- + + ### Is your feature request related to a problem? Please describe. A clear and concise description of what the problem is. Example: I am trying to do [...] but [...] diff --git a/.github/workflows/ReportDiff.ps1 b/.github/workflows/ReportDiff.ps1 new file mode 100644 index 0000000000..2093a9dd86 --- /dev/null +++ b/.github/workflows/ReportDiff.ps1 @@ -0,0 +1,29 @@ +# Check the code is in sync +$changed = (select-string "nothing to commit" artifacts\status.txt).count -eq 0 +if (-not $changed) { return $changed } +# Check if tracking issue is open/closed +$Headers = @{ Authorization = 'token {0}' -f $ENV:GITHUB_TOKEN; }; +$result = Invoke-RestMethod -Uri $issue +if ($result.state -eq "closed") { + $json = "{ `"state`": `"open`" }" + $result = Invoke-RestMethod -Method PATCH -Headers $Headers -Uri $issue -Body $json +} +# Add a comment +$status = [IO.File]::ReadAllText("artifacts\status.txt") +$diff = [IO.File]::ReadAllText("artifacts\diff.txt") +$body = @" +The shared code is out of sync. +
+ The Diff + +`````` +$status +$diff +`````` + +
+"@ +$json = ConvertTo-Json -InputObject @{ 'body' = $body } +$issue = $issue + '/comments' +$result = Invoke-RestMethod -Method POST -Headers $Headers -Uri $issue -Body $json +return $changed \ No newline at end of file diff --git a/.github/workflows/runtime-sync.yml b/.github/workflows/runtime-sync.yml new file mode 100644 index 0000000000..b7cd3e4c31 --- /dev/null +++ b/.github/workflows/runtime-sync.yml @@ -0,0 +1,69 @@ +name: AspNetCore-Runtime Code Sync +on: + # Test this script using on: push + # push: + schedule: + # * is a special character in YAML so you have to quote this string + # https://help.github.com/en/actions/automating-your-workflow-with-github-actions/events-that-trigger-workflows#scheduled-events-schedule + # Once per day at midnight PST (8 UTC) + - cron: '0 8 * * *' + +jobs: + compare_repos: + # Comment out this line to test the scripts in a fork + if: github.repository == 'dotnet/aspnetcore' + name: Compare the shared code in the AspNetCore and Runtime repos and notify if they're out of sync. + runs-on: windows-latest + steps: + - name: Checkout aspnetcore + uses: actions/checkout@v2.0.0 + with: + # Test this script using changes in a fork + repository: 'dotnet/aspnetcore' + path: aspnetcore + - name: Checkout runtime + uses: actions/checkout@v2.0.0 + with: + # Test this script using changes in a fork + repository: 'dotnet/runtime' + path: runtime + - name: Copy + shell: cmd + working-directory: .\runtime\src\libraries\Common\src\System\Net\Http\aspnetcore\ + env: + ASPNETCORE_REPO: d:\a\aspnetcore\aspnetcore\aspnetcore\ + run: CopyToAspNetCore.cmd + - name: Diff + shell: cmd + working-directory: .\aspnetcore\ + run: | + mkdir ..\artifacts + git status > ..\artifacts\status.txt + git diff > ..\artifacts\diff.txt + - uses: actions/upload-artifact@v1 + with: + name: results + path: artifacts + - name: Check and Notify + id: check + shell: pwsh + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + # Test this script using an issue in the local forked repo + $issue = 'https://api.github.com/repos/dotnet/aspnetcore/issues/18943' + $changed = .\aspnetcore\.github\workflows\ReportDiff.ps1 + echo "::set-output name=changed::$changed" + - name: Send PR + if: steps.check.outputs.changed == 'true' + # https://github.com/marketplace/actions/create-pull-request + uses: peter-evans/create-pull-request@v2 + with: + token: ${{ secrets.GITHUB_TOKEN }} + path: .\aspnetcore + commit-message: 'Sync shared code from runtime' + title: 'Sync shared code from runtime' + body: 'This PR was automatically generated to sync shared code changes from runtime. Fixes #18943' + labels: area-servers + branch: github-action/sync-runtime + branch-suffix: timestamp diff --git a/.gitignore b/.gitignore index 8a2385174b..99e05dccb9 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ BenchmarkDotNet.Artifacts/ .gradle/ src/SignalR/clients/**/dist/ modules/ +.ionide/ # File extensions *.aps @@ -29,6 +30,7 @@ modules/ *.psess *.res *.snk +*.so *.suo *.tlog *.user @@ -40,3 +42,4 @@ launchSettings.json msbuild.ProjectImports.zip StyleCop.Cache UpgradeLog.htm +.idea diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 222522b092..238c74186d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,16 +3,16 @@ One of the easiest ways to contribute is to participate in discussions on GitHub issues. You can also contribute by submitting pull requests with code changes. ## General feedback and discussions? -Start a discussion on the [repository issue tracker](https://github.com/aspnet/AspNetCore/issues). +Start a discussion on the [repository issue tracker](https://github.com/dotnet/aspnetcore/issues). ## Bugs and feature requests? For non-security related bugs, log a new issue in the appropriate GitHub repository. Here are some of the most common repositories: * [Docs](https://github.com/aspnet/Docs) -* [AspNetCore](https://github.com/aspnet/AspNetCore) -* [Entity Framework Core](https://github.com/aspnet/EntityFrameworkCore) +* [AspNetCore](https://github.com/dotnet/aspnetcore) +* [Entity Framework Core](https://github.com/dotnet/efcore) * [Tooling](https://github.com/aspnet/Tooling) -* [Extensions](https://github.com/aspnet/Extensions) +* [Extensions](https://github.com/dotnet/extensions) Or browse the full list of repositories in the [aspnet](https://github.com/aspnet/) organization. @@ -31,8 +31,8 @@ Our team members also monitor several other discussion forums: We accept fixes and features! Here are some resources to help you get started on how to contribute code or new content. * Look at the [Contributor documentation](/docs/) to get started on building the source code on your own. -* ["Help wanted" issues](https://github.com/aspnet/AspNetCore/labels/help%20wanted) - these issues are up for grabs. Comment on an issue if you want to create a fix. -* ["Good first issue" issues](https://github.com/aspnet/AspNetCore/labels/good%20first%20issue) - we think these are a good for newcomers. +* ["Help wanted" issues](https://github.com/dotnet/aspnetcore/labels/help%20wanted) - these issues are up for grabs. Comment on an issue if you want to create a fix. +* ["Good first issue" issues](https://github.com/dotnet/aspnetcore/labels/good%20first%20issue) - we think these are a good for newcomers. ### Identifying the scale @@ -42,7 +42,7 @@ If you would like to contribute to one of our repositories, first identify the s You will need to sign a [Contributor License Agreement](https://cla.dotnetfoundation.org/) when submitting your pull request. To complete the Contributor License Agreement (CLA), you will need to follow the instructions provided by the CLA bot when you send the pull request. This needs to only be done once for any .NET Foundation OSS project. -If you don't know what a pull request is read this article: https://help.github.com/articles/using-pull-requests. Make sure the repository can build and all tests pass. Familiarize yourself with the project workflow and our coding conventions. The coding, style, and general engineering guidelines are published on the [Engineering guidelines](https://github.com/aspnet/AspNetCore/wiki/Engineering-guidelines) page. +If you don't know what a pull request is read this article: https://help.github.com/articles/using-pull-requests. Make sure the repository can build and all tests pass. Familiarize yourself with the project workflow and our coding conventions. The coding, style, and general engineering guidelines are published on the [Engineering guidelines](https://github.com/dotnet/aspnetcore/wiki/Engineering-guidelines) page. ### Tests diff --git a/Directory.Build.props b/Directory.Build.props index 4b9357c0e7..c40d203441 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,7 +1,7 @@  $(MSBuildThisFileDirectory) - https://github.com/aspnet/AspNetCore + https://github.com/dotnet/aspnetcore git @@ -30,11 +30,14 @@ --> false true + + + true - + - + Microsoft ASP.NET Core @@ -51,7 +54,7 @@ true - netcoreapp3.1 + netcoreapp5.0 @@ -61,6 +64,10 @@ $(WarningsNotAsErrors);CS1591 $(WarningsNotAsErrors);xUnit1004 + + $(NoWarn);NU5131 + + $(NoWarn);NU5048 @@ -84,7 +91,7 @@ aspnetcore-runtime aspnetcore-targeting-pack - + false false @@ -107,6 +114,7 @@ win osx linux + $([System.Runtime.InteropServices.RuntimeInformation]::ProcessArchitecture.ToString().ToLowerInvariant()) x64 $(TargetOsName)-$(TargetArchitecture) @@ -115,6 +123,7 @@ win-x64; win-x86; win-arm; + win-arm64; osx-x64; linux-musl-x64; linux-musl-arm64; @@ -182,5 +191,6 @@ + diff --git a/Directory.Build.targets b/Directory.Build.targets index 94595c4b88..5c0c27b516 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -102,6 +102,7 @@ false true + true @@ -116,8 +117,9 @@ - + + $(AspNetCoreMajorVersion).$(AspNetCoreMinorVersion).0.0 @@ -140,10 +142,6 @@ $(SharedFxVersion) - - $(NETStandardLibraryRefPackageVersion) - - + + + diff --git a/NuGet.config b/NuGet.config index 18e9d0cf68..618ac88e70 100644 --- a/NuGet.config +++ b/NuGet.config @@ -2,16 +2,14 @@ - - - - - - - - + + + + + + diff --git a/README.md b/README.md index 4b954c6076..4c2e975c42 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ ASP.NET Core ============ -ASP.NET Core is an open-source and cross-platform framework for building modern cloud based internet connected applications, such as web apps, IoT apps and mobile backends. ASP.NET Core apps can run on .NET Core or on the full .NET Framework. It was architected to provide an optimized development framework for apps that are deployed to the cloud or run on-premises. It consists of modular components with minimal overhead, so you retain flexibility while constructing your solutions. You can develop and run your ASP.NET Core apps cross-platform on Windows, Mac and Linux. [Learn more about ASP.NET Core](https://docs.microsoft.com/aspnet/core/). +ASP.NET Core is an open-source and cross-platform framework for building modern cloud based internet connected applications, such as web apps, IoT apps and mobile backends. ASP.NET Core apps run on [.NET Core](https://dot.net), a free, cross-platform and open-source application runtime. It was architected to provide an optimized development framework for apps that are deployed to the cloud or run on-premises. It consists of modular components with minimal overhead, so you retain flexibility while constructing your solutions. You can develop and run your ASP.NET Core apps cross-platform on Windows, Mac and Linux. [Learn more about ASP.NET Core](https://docs.microsoft.com/aspnet/core/). ## Get Started @@ -9,6 +9,8 @@ Follow the [Getting Started](https://docs.microsoft.com/aspnet/core/getting-star Also check out the [.NET Homepage](https://www.microsoft.com/net) for released versions of .NET, getting started guides, and learning resources. +See the [Issue Management Policies](https://github.com/dotnet/aspnetcore/blob/anurse/issue-policies/docs/IssueManagementPolicies.md) document for more information on how we handle incoming issues. + ## How to Engage, Contribute, and Give Feedback Some of the best ways to contribute are to try things out, file issues, join in design conversations, @@ -17,7 +19,7 @@ and make pull-requests. * [Download our latest daily builds](./docs/DailyBuilds.md) * Follow along with the development of ASP.NET Core: * [Community Standup](https://live.asp.net): The community standup is held every week and streamed live to YouTube. You can view past standups in the linked playlist. - * [Roadmap](https://github.com/aspnet/AspNetCore/wiki/Roadmap): The schedule and milestone themes for ASP.NET Core. + * [Roadmap](https://github.com/dotnet/aspnetcore/wiki/Roadmap): The schedule and milestone themes for ASP.NET Core. * [Build ASP.NET Core source code](./docs/BuildFromSource.md) * Check out the [contributing](CONTRIBUTING.md) page to see the best places to log issues and start discussions. @@ -30,8 +32,8 @@ Security issues and bugs should be reported privately, via email, to the Microso These are some other repos for related projects: * [Documentation](https://github.com/aspnet/Docs) - documentation sources for https://docs.microsoft.com/aspnet/core/ -* [Entity Framework Core](https://github.com/aspnet/EntityFrameworkCore) - data access technology -* [Extensions](https://github.com/aspnet/Extensions) - Logging, configuration, dependency injection, and more. +* [Entity Framework Core](https://github.com/dotnet/efcore) - data access technology +* [Extensions](https://github.com/dotnet/extensions) - Logging, configuration, dependency injection, and more. ## Code of conduct diff --git a/SECURITY.md b/SECURITY.md index 92d052767f..5a9569ce1f 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -6,9 +6,9 @@ The .NET Core and ASP.NET Core support policy, including supported versions can ## Reporting a Vulnerability -Security issues and bugs should be reported privately, via email, to the Microsoft Security Response Center (MSRC) secure@microsoft.com. +Security issues and bugs should be reported privately, via email, to the Microsoft Security Response Center (MSRC) through https://msrc.microsoft.com or by emailing secure@microsoft.com. You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your -original message. Further information, including the MSRC PGP key, can be found in the [Security TechCenter](https://technet.microsoft.com/en-us/security/ff852094.aspx). +original message. Further information, including the MSRC PGP key, can be found in the [MSRC Report an Issue FAQ](https://www.microsoft.com/en-us/msrc/faqs-report-an-issue). Reports via MSRC may qualify for the .NET Core Bug Bounty. Details of the .NET Core Bug Bounty including terms and conditions are at [https://aka.ms/corebounty](https://aka.ms/corebounty). diff --git a/THIRD-PARTY-NOTICES.txt b/THIRD-PARTY-NOTICES.txt index 81fadeae22..99d1e37721 100644 --- a/THIRD-PARTY-NOTICES.txt +++ b/THIRD-PARTY-NOTICES.txt @@ -189,3 +189,31 @@ 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. + +License notice for corefx + +------------------------------------------------ + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +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. diff --git a/build.ps1 b/build.ps1 index cf854f54b4..f371d26673 100644 --- a/build.ps1 +++ b/build.ps1 @@ -99,7 +99,7 @@ Running tests. build.ps1 -test .LINK -Online version: https://github.com/aspnet/AspNetCore/blob/master/docs/BuildFromSource.md +Online version: https://github.com/dotnet/aspnetcore/blob/master/docs/BuildFromSource.md #> [CmdletBinding(PositionalBinding = $false, DefaultParameterSetName='Groups')] param( @@ -118,7 +118,7 @@ param( [ValidateSet('Debug', 'Release')] $Configuration, - [ValidateSet('x64', 'x86', 'arm')] + [ValidateSet('x64', 'x86', 'arm', 'arm64')] $Architecture = 'x64', # A list of projects which should be built. @@ -157,7 +157,7 @@ param( # Other lifecycle targets [switch]$Help, # Show help - + # Optional arguments that enable downloading an internal # runtime or runtime from a non-default location [string]$DotNetRuntimeSourceFeed, @@ -336,7 +336,7 @@ $env:MSBUILDDISABLENODEREUSE=1 # Our build often has warnings that we can't fix, like "MSB3026: Could not copy" due to race # conditions in building C++ -# Fixing this is tracked by https://github.com/aspnet/AspNetCore-Internal/issues/601 +# Fixing this is tracked by https://github.com/dotnet/aspnetcore-internal/issues/601 $warnAsError = $false if ($ForceCoreMsbuild) { diff --git a/build.sh b/build.sh index b97417c4fa..92ef856d47 100755 --- a/build.sh +++ b/build.sh @@ -304,7 +304,7 @@ nodeReuse=false export MSBUILDDISABLENODEREUSE=1 # Our build often has warnings that we can't fix -# Fixing this is tracked by https://github.com/aspnet/AspNetCore-Internal/issues/601 +# Fixing this is tracked by https://github.com/dotnet/aspnetcore-internal/issues/601 warn_as_error=false # Workaround Arcade check which asserts BinaryLog is true on CI. diff --git a/docs/APIReviewProcess.md b/docs/APIReviewProcess.md new file mode 100644 index 0000000000..2340d8097b --- /dev/null +++ b/docs/APIReviewProcess.md @@ -0,0 +1,71 @@ +## Description +Starting in 5.0, certain areas within the dotnet/aspnetcore and dotnet/extensions repos will require formal *incremental* API reviews before any PRs that change APIs are merged. + +API changes to the following areas are required to go follow this process: + +* area-azure +* area-hosting +* area-installers +* area-middleware +* area-mvc + * feature-model-binding + * feature-razor-pages + * feature-JSONPatch + * feature-discovery + * feature-formatters + * feature-api-explorer + * feature-tag-helpers +* area-security +* area-servers +* area-signalr +* area-websockets + +## Process +The goal of the API Review process is to ensure that the new APIs are following common patterns and the best practices. +Also, it's aimed to help and guide engineers towards better API design decisions. People should feel empowered to submit their APIs for review as besides all the benefits it's also a learning and knowledge sharing experience. + +The process is visualized in the below diagram: +![A sequence diagram illustrating the same process described below.](https://user-images.githubusercontent.com/34246760/66542496-95052c80-eae7-11e9-9c7c-549b82a8d492.png) + + +1. API review process kicks in after the owner for the issue identifies that the work required for the issue will need an API change or addition. In such cases, the issue owner will handle (either himself/herself, or with the community member who has expressed interest in handling the work) driving a design proposal. When working with a community member, the issue owner is responsible for guiding them to an acceptable design. +1. If the proposed design adds new APIs, mark those issues with the `api-suggestion` label +1. When the issue owner thinks the proposal is in a good shape, he/she marks the issue with `api-ready-for-review` label. Also, the @asp-net-api-reviews team should be notified on the issue. +1. The `asp-net-api-reviews` team will host a weekly API review meeting and will review your proposed API change during the next meeting. If you have an API scheduled for review, you must have a representative in the meeting. +1. Some API reviews can happen through a shorter process. For these situations, simply ping the API review crew for a quicker review, so that it can happen as a conversation. +1. When an API change/suggestion gets approved, the `api-approved` label should be added to the issue. +1. The owner of the issue is now free to work on the implementation of the proposed API. +1. In case during implementation changes to the original proposal are required, the review should become obsolete and the process should start from the beginning. + +## What Makes an issue/PR "ready-for-review"? + +Before marking an issue as `api-ready-for-review`, make sure that the issue has the following: + +- A short description that will help reviewers not familiar with this area. +- The API changes in ref-assembly format. It's fine to link this to the generated ref-assembly-code in the PR. If the changes are to an area that does produce ref-assemblies, please write out what it would look like in ref-assembly format for us to review. + +```txt +Good: This is the API for the widget factory, users use it in startup code to +configure how their widgets work. We have an overload that accepts URI, but +not one that accepts string, so we're adding it for convenience. + +Bad: Adding a string overload for Widget.ConfigureFactory. +``` + +Note: Ideally all of the following would be in the top comment on an issue, but that's not always possible when the issue was opened by a user. As a rule, we don't edit or replace user comments except for formatting, or if they break the rules. In this case it's fine to post a new comment on the issue, OR to edit the top post and insert a link. If you edit an external contributor's post to add a link make sure you explain why it was done! + +In general, larger changes should have more explanation and context provided, and small changes need less explanation. A really large change or feature-area design should probably come with a lot of explanation: [example](https://github.com/dotnet/aspnetcore/issues/17160) + +### Why do we do this? + +Putting this information in an issue with all of the context makes it possible for discussion to take place before the api-review meeting. Writing things down and posting them online enables remote work as well as our community to give feedback on designs as well. We want to provide enough context for people *working outside that feature area* to understand what the change is about and give meaningful feedback. If you're ready to present an change in the meeting, then you should definitely be ready to explain why it matters. + +We use the ref-assembly format because it's more readable and useful for the kinds of things that come up in api-review discussions. Using a more compact format (without docs and implementations) makes it easier to notice patterns. In the rare case that you have to manually transcribe this format, think of this as you spending a little time to save a lot of others time in the meeting. + +## If you are the "champion" for a community-submitted change + +If you are assigned a community-submitted change to *champion* in our API-review then just put on your pretend pajamas and pretend that it was your change to begin with. Come to the meeting ready to explain why this addition is needed, and why it's the best approach. + +## API Review Meeting + +The API Review meeting should be open to any member of the ASP.NET Core team. And invite will be sent to all the team with pre-booked meeting room and time-slot for these meetings to be hosted. Each API review should include the area owners as mandatory attendees. diff --git a/docs/BuildFromSource.md b/docs/BuildFromSource.md index 5cc74a5f9d..3fc873b939 100644 --- a/docs/BuildFromSource.md +++ b/docs/BuildFromSource.md @@ -1,11 +1,10 @@ -Build ASP.NET Core from Source -============================== +# Build ASP.NET Core from Source -Building ASP.NET Core from source allows you tweak and customize ASP.NET Core, and to contribute your improvements back to the project. +Building ASP.NET Core from source allows you to tweak and customize ASP.NET Core, and to contribute your improvements back to the project. -See https://github.com/aspnet/AspNetCore/labels/area-infrastructure for known issues and to track ongoing work. +See for known issues and to track ongoing work. -## Install pre-requistes +## Install pre-requisites ### Windows @@ -14,20 +13,27 @@ Building ASP.NET Core on Windows requires: * Windows 10, version 1803 or newer * At least 10 GB of disk space and a good internet connection (our build scripts download a lot of tools and dependencies) * Visual Studio 2019. - * To install the exact required components, run [eng/scripts/InstallVisualStudio.ps1](/eng/scripts/InstallVisualStudio.ps1). - ```ps1 - PS> ./eng/scripts/InstallVisualStudio.ps1 - ``` + * To install the exact required components, run [eng/scripts/InstallVisualStudio.ps1](/eng/scripts/InstallVisualStudio.ps1). + + ```ps1 + PS> ./eng/scripts/InstallVisualStudio.ps1 + ``` + + However, any Visual Studio 2019 instance that meets the requirements should be fine. See [global.json](/global.json) + and [eng/scripts/vs.json](/eng/scripts/vs.json) for those requirements. By default, the script will install Visual Studio Enterprise Edition, however you can use a different edition by passing the `-Edition` flag. * Git. * NodeJS. LTS version of 10.14.2 or newer * Java Development Kit 11 or newer. Either: - * OpenJDK - * Oracle's JDK - * To install a version of the JDK that will only be used by this repo, run [eng/scripts/InstallJdk.ps1](/eng/scripts/InstallJdk.ps1) - ```ps1 - PS> ./eng/scripts/InstallJdk.ps1 - ``` -* Chrome - Selenium-based tests require a version of Chrome to be installed. Download and install it from [https://www.google.com/chrome] + * OpenJDK + * Oracle's JDK + * To install a version of the JDK that will only be used by this repo, run [eng/scripts/InstallJdk.ps1](/eng/scripts/InstallJdk.ps1) + + ```ps1 + PS> ./eng/scripts/InstallJdk.ps1 + ``` + + However, the build should find any JDK 11 or newer installation on the machine. +* Chrome - Selenium-based tests require a version of Chrome to be installed. Download and install it from ### macOS/Linux @@ -39,20 +45,22 @@ Building ASP.NET Core on macOS or Linux requires: * Git * NodeJS. LTS version of 10.14.2 or newer * Java Development Kit 11 or newer. Either: - * OpenJDK - * Oracle's JDK + * OpenJDK + * Oracle's JDK ## Clone the source code -ASP.NET Core uses git submodules to include source from a few other projects. +ASP.NET Core uses git submodules to include the source from a few other projects. For a new copy of the project, run: -``` -git clone --recursive https://github.com/aspnet/AspNetCore + +```ps1 +git clone --recursive https://github.com/dotnet/aspnetcore ``` To update an existing copy, run: -``` + +```ps1 git submodule update --init --recursive ``` @@ -61,22 +69,24 @@ git submodule update --init --recursive Before opening our .sln files in Visual Studio or VS Code, you need to perform the following actions. 1. Executing the following on command-line: - ``` + + ```ps1 .\restore.cmd ``` - This will download required tools and build the entire repository once. At that point, you should be able to open .sln files to work on the projects you care about. + + This will download the required tools and build the entire repository once. At that point, you should be able to open .sln files to work on the projects you care about. > :bulb: Pro tip: you will also want to run this command after pulling large sets of changes. On the master branch, we regularly update the versions of .NET Core SDK required to build the repo. > You will need to restart Visual Studio every time we update the .NET Core SDK. -2. Use the `startvs.cmd` script to open Visual Studio .sln files. This script first sets required environment variables. +2. Use the `startvs.cmd` script to open Visual Studio .sln files. This script first sets the required environment variables. ### Solution files We don't have a single .sln file for all of ASP.NET Core because Visual Studio doesn't currently handle projects of this scale. -Instead, we have many .sln files which include a sub-set of projects. These principles guide how we create and manage .slns: +Instead, we have many .sln files which include a sub-set of projects. These principles guide how we create and manage .sln files: -1. Solution files are not used by CI or command line build scripts. They are for meant for use by developers only. +1. Solution files are not used by CI or command line build scripts. They are meant for use by developers only. 2. Solution files group together projects which are frequently edited at the same time. 3. Can't find a solution that has the projects you care about? Feel free to make a PR to add a new .sln file. @@ -90,10 +100,12 @@ Opening solution files and building may produce an error code CS0006 with a mess The cause of this problem is that the solution you are using does not include the project that produces this .dll. This most often occurs after we have added new projects to the repo, but failed to update our .sln files to include the new project. In some cases, it is sometimes the intended behavior of the .sln which has been crafted to only include a subset of projects. -**You can fix this in one of two ways** -1. Build the project on command line. In most cases, running `build.cmd` on command line solve this problem. +#### You can fix this in one of two ways + +1. Build the project on command line. In most cases, running `build.cmd` on command line solves this problem. 2. Update the solution to include the missing project. You can either do this one by one using `dotnet sln` - ``` + + ```ps1 dotnet sln add C:\src\AspNetCore\src\Hosting\Abstractions\src\Microsoft.AspNetCore.Hosting.Abstractions.csproj ``` @@ -112,6 +124,7 @@ Using Visual Studio Code with this repo requires setting environment variables o Use these command to launch VS Code with the right settings. On Windows (requires PowerShell): + ```ps1 # The extra dot at the beginning is required to 'dot source' this file into the right scope. @@ -120,26 +133,34 @@ code . ``` On macOS/Linux: -``` + +```bash source activate.sh code . ``` +Note that if you are using the "Remote-WSL" extension in VSCode, the environment is not supplied +to the process in WSL. You can workaround this by explicitly setting the environment variables +in `~/.vscode-server/server-env-setup`. +See https://code.visualstudio.com/docs/remote/wsl#_advanced-environment-setup-script for details. + ## Building on command-line You can also build the entire project on command line with the `build.cmd`/`.sh` scripts. On Windows: -``` + +```ps1 .\build.cmd ``` On macOS/Linux: -``` + +```bash ./build.sh ``` -By default, all of the C# projects are built. Some C# projects requires NodeJS to be installed to compile JavaScript assets which are then checked in as source. If NodeJS is detected on the path, the NodeJS projects will be compiled as part of building C# projects. If NodeJS is not detected on the path, the JavaScript assets checked in previously will be used instead. To disable building NodeJS projects, specify /p:BuildNodeJs=false on the command line. +By default, all of the C# projects are built. Some C# projects require NodeJS to be installed to compile JavaScript assets which are then checked in as source. If NodeJS is detected on the path, the NodeJS projects will be compiled as part of building C# projects. If NodeJS is not detected on the path, the JavaScript assets checked in previously will be used instead. To disable building NodeJS projects, specify /p:BuildNodeJs=false on the command line. ### Using `dotnet` on command line in this repo @@ -155,6 +176,7 @@ On Windows (requires PowerShell): ``` On macOS/Linux: + ```bash source ./activate.sh ``` @@ -164,12 +186,14 @@ source ./activate.sh Tests are not run by default. Use the `-test` option to run tests in addition to building. On Windows: -``` + +```ps1 .\build.cmd -test ``` On macOS/Linux: -``` + +```bash ./build.sh --test ``` @@ -182,7 +206,8 @@ Furthermore, you can use flags on `build.cmd`/`.sh` to build subsets based on la ## Build properties Additional properties can be added as an argument in the form `/property:$name=$value`, or `/p:$name=$value` for short. For example: -``` + +```ps1 .\build.cmd /p:Configuration=Release ``` @@ -199,8 +224,8 @@ TargetOsName | The base runtime identifier to build for (win, linux, After building ASP.NET Core from source, you will need to install and use your local version of ASP.NET Core. See ["Artifacts"](./Artifacts.md) for more explanation of the different folders produced by a build. -- Run the installers produced in `artifacts/installers/{Debug, Release}/` for your platform. -- Add a NuGet.Config to your project directory with the following content: +* Run the installers produced in `artifacts/installers/{Debug, Release}/` for your platform. +* Add a NuGet.Config to your project directory with the following content: ```xml @@ -215,7 +240,8 @@ See ["Artifacts"](./Artifacts.md) for more explanation of the different folders *NOTE: This NuGet.Config should be with your application unless you want nightly packages to potentially start being restored for other apps on the machine.* -- Update the versions on `PackageReference` items in your .csproj project file to point to the version from your local build. +* Update the versions on `PackageReference` items in your .csproj project file to point to the version from your local build. + ```xml diff --git a/docs/DailyBuilds.md b/docs/DailyBuilds.md index 7e727df9e4..6c0c734bf8 100644 --- a/docs/DailyBuilds.md +++ b/docs/DailyBuilds.md @@ -13,13 +13,7 @@ If you want to download the latest daily build and use it in a project, then you - - - - - - - + diff --git a/docs/Helix.md b/docs/Helix.md index e8261acc85..a7e759cb42 100644 --- a/docs/Helix.md +++ b/docs/Helix.md @@ -12,17 +12,10 @@ For more info about helix see: [SDK](https://github.com/dotnet/arcade/blob/maste To run Helix tests for one particular test project: ``` -cd src/MyCode/test -dotnet msbuild /t:Helix +.\eng\scripts\RunHelix.ps1 -Project path\mytestproject.csproj ``` -To run tests for the entire repo, run: - -``` -.\eng\scripts\TestHelix.ps1 -``` - -This will restore, and then publish all of the test projects including some bootstrapping scripts that will install the correct dotnet runtime/sdk before running the test assemblies on the helix machine, and upload the job to helix. +This will restore, and then publish all the test project including some bootstrapping scripts that will install the correct dotnet runtime/sdk before running the test assembly on the helix machine(s), and upload the job to helix. ## How do I look at the results of a helix run on Azure Pipelines? diff --git a/docs/IssueManagementPolicies.md b/docs/IssueManagementPolicies.md new file mode 100644 index 0000000000..e71c969938 --- /dev/null +++ b/docs/IssueManagementPolicies.md @@ -0,0 +1,27 @@ +# Issue Management Policies + +We have a lot of issue traffic to manage, so we have a few policies in place to help us do that. This is a brief summary of some of the policies we have in place and the justification for them. + +## Commenting on closed issues + +In general, we recommend you open a new issue if you have a bug, feature request, or question to discuss. If you find a closed issue that is related, open a *new issue* and link to the closed issue rather than posting on the closed issue. Closed issues don't appear in our triage process, so only the people who have been active on the original thread will be notified of your comment. A new issue will get more attention from the team. + +*In general* we don't mind getting duplicate issues. It's easier for us to close duplicate issues than to discuss multiple root causes on a single issue! We may close your issue as a duplicate if we determine it has the same root cause as another. Don't worry! It's not a waste of our time! + +## Needs Author Feedback + +If a contributor reviews an issue and determines that more information is needed from the author, they will post a comment requesting that information and apply the `Needs: Author Feedback` label. This label indicates that the author needs to post a response in order for us to continue investigating the issue. + +If the author does not post a response within **7 days**, the issue will be automatically closed. If the author responds within **7 days** after the issue is closed, the issue will be automatically re-opened. We recognize that you may not be able to respond immediately to our requests, we're happy to hear from you whenever you're able to provide the new information. + +## Duplicate issues + +If we determine that the issue is a duplicate of another, we will label it with the `Resolution: Duplicate` label. The issue will be automatically closed in 1 day of inactivity. + +## Answered questions + +If we determine that the issue is a question and have posted an answer, we will label it with the `Resolution: Answered` label. The issue will be automatically closed in 1 day of inactivity. + +## Locking closed issues + +After an issue has been closed and had no activity for **30 days** it will be automatically locked as *resolved*. This is done in order to reduce confusion as to where to post new comments. If you are still encountering the problem reported in an issue, or have a related question or bug report, feel free to open a *new issue* and link to the original (now locked) issue! diff --git a/docs/ReferenceAssemblies.md b/docs/ReferenceAssemblies.md index 1ef20699e6..593682a29e 100644 --- a/docs/ReferenceAssemblies.md +++ b/docs/ReferenceAssemblies.md @@ -22,4 +22,4 @@ Set `false` in the implementation ( ### Regenerate reference assemblies for all projects -Run `.\eng\scripts\GenerateReferenceAssemblies.ps1` from repository root. +Run `.\eng\scripts\GenerateReferenceAssemblies.ps1` from repository root. Make sure you've run `.\restore.cmd` first. diff --git a/docs/ReferenceResolution.md b/docs/ReferenceResolution.md index 043082f4ec..d84215a6d3 100644 --- a/docs/ReferenceResolution.md +++ b/docs/ReferenceResolution.md @@ -68,7 +68,7 @@ Steps for adding a new package dependency to an existing project. Let's say I'm If you don't know the commit hash of the source code used to produce "0.0.1-beta-1", you can use `000000` as a placeholder for `Sha` as its value is unimportant and will be updated the next time the bot runs. - If the new dependency comes from dotnet/CoreFx, dotnet/code-setup or aspnet/Extensions, add a + If the new dependency comes from dotnet/CoreFx, dotnet/code-setup or dotnet/extensions, add a `CoherentParentDependency` attribute to the `` element as shown below. This example indicates the dotnet/CoreFx dependency version should be determined based on the build that produced the chosen Microsoft.NETCore.App. That is, the dotnet/CoreFx dependency and Microsoft.NETCore.App should be @@ -81,7 +81,7 @@ Steps for adding a new package dependency to an existing project. Let's say I'm ``` The attribute value should be `"Microsoft.Extensions.Logging"` for dotnet/core-setup dependencies and - `"Microsoft.CodeAnalysis.Razor"` for aspnet/Extensions dependencies. + `"Microsoft.CodeAnalysis.Razor"` for dotnet/extensions dependencies. ## Example: make a breaking change to references diff --git a/docs/Servicing.md b/docs/Servicing.md new file mode 100644 index 0000000000..f257322fab --- /dev/null +++ b/docs/Servicing.md @@ -0,0 +1,80 @@ +# Servicing Process + +We maintain several on-going releases at once and produce patches for them. An essential part of our support committment to users is that we build high-quality patches that avoid breaking applications. This means we have to be extremely cautious with taking changes in patch releases. This document describes the "bar" (criteria for accepting servicing fixes) and the process for managing these changes. + +See the [.NET Core release lifecycle](https://dotnet.microsoft.com/platform/support/policy/dotnet-core#lifecycle) for more details on the currently-supported .NET releases. + +The status of current servicing fixes can be found on the [Servicing Status](https://github.com/dotnet/aspnetcore/projects/11) GitHub project. + +## Servicing Bar + +The servicing bar is defined as any fixes the .NET "Shiproom" (see below) approves. We use certain criteria to evaluate fixes (described below) but still reserve the right to accept/reject bugs despite this criteria in certain circumstances. + +A fix is generally suitable for accepting in a servicing release if **all** of the below are true: + +* It impacts a "significant" number of users. There's no formal definition here, but generally means multiple users have reported the issue, or the team is confident that a large number of users would be affected. +* It has no suitable workaround. Since any change comes with risk, having users apply a workaround is generally preferable to shipping an update that may cause more issues. +* It does not change public API (removing/adding/changing APIs). Applications should be binary-compatible with **all** patches for a given major.minor version, so API changes cannot be made in patches. +* Any behavioral changes are fixing unexpected exceptions/failures/crashes/etc. or are behind opt-in configuration. In rare cases, where the value is high, we will take changes that are not opt-in, but will provide opt-out configuration to disable them and restore previous behavior. + +In addition, the following factors make a particular servicing fix *more likely* to be accepted: + +* It fixes a regression introduced in a previous release +* It is necessary to meet key "tenants" (Security, Compliance, Geopolitical issues, etc.) +* It is required to support new OS distributions +* If the issue is reported through [Microsoft Product Support](https://dotnet.microsoft.com/platform/support). + +Finally, infrastructure and test-only fixes are generally acceptable since they do not impact the customer use of the product. However, these should generally be focused on fixes that improve the *reliability* of building/testing the product. + +### Long-Term Support Releases + +In general, Long-Term Support releases are very risk-averse. Users choose these releases over the "Current" releases because their applications can't take the risks involved in frequent updates. We want users to feel very confident installing patches. + +As a result, in general, requests for servicing fixes in Long-Term Support releases should come through [Microsoft Support](https://dotnet.microsoft.com/platform/support). + +## Submitting a fix to Shiproom + +**External Contributors**: In general, this will be done by a team member. Reach out to the team members reviewing your change to ask for help with this process. + +To request Shiproom approval for a fix, open a **Pull Request** to the target `release/` branch (for example `release/3.1` for 3.1.x). Prior to submitting to shiproom, ensure all of the following: + +* The PR is "ready-to-merge" (Has at least one review approval, passing builds, is not a draft) +* The PR description contains the following template: + +``` +#### Description + + + +#### Customer Impact + + + +#### Regression? + + + +#### Risk + + +``` + +Once the above conditions are met, apply the `servicing-consider` label. + +## Shiproom + +The .NET Shiproom meets regularly (approximately twice a week) and reviews PRs labelled `servicing-consider`. The Shiproom attendees include stakeholders from across the stack (runtime, libraries, app models, sdk, etc.). Any PR with this label will be considered. Having a fully-complete template is important to ensuring the PR can be properly reviewed. Generally, someone familiar with the PR should be present at the meeting, but having the template filled out helps ensure that if that person is unavailable, the bug is well-represented. + +After reviewing a PR, Shiproom will take one of the following actions: + +* Apply the `servicing-approved` label and place it in the appropriate milestone based on the target patch release. The change is approved and can be merged when branches are open for the target patch. +* Apply the `servicing-more-info` label (or just leave `servicing-consider`) and request additional information or better representation at a subsequent meeting for approval. +* Apply the `servicing-rejected` label. The change has been declined and should not be merged. It can be resubmitted if there is new information to consider. + +## Merging + +Only a repository admin can merge changes to `release/*` branches. Once branches open for a particular patch release, the admins will go through and merge PRs labelled `servicing-approved` and targeting that patch release. Sometimes we are tracking multiple patch releases at once (rare, but it happens) so it's possible that only some approved PRs will be merged at the same time. diff --git a/docs/Submodules.md b/docs/Submodules.md index 8d24c40ff5..18e0a87caf 100644 --- a/docs/Submodules.md +++ b/docs/Submodules.md @@ -9,7 +9,7 @@ For full information, see the [official docs for git submodules](https://git-scm ## Fundamental concept -The parent repo (aspnet/AspNetCore) stores two pieces of info about each submodule. +The parent repo (dotnet/aspnetcore) stores two pieces of info about each submodule. 1. Where to clone the submodule from. This is stored in the .gitmodules file 2. The commit hash of the submodule to use. @@ -22,7 +22,7 @@ Other info may appear in the .gitmodules file, but it is only used when attempti By default, submodules will not be present. Use `--recursive` to clone all submodules. - git clone https://github.com/aspnet/AspNetCore.git --recursive + git clone https://github.com/dotnet/aspnetcore.git --recursive If you have already cloned, run this to initialize all submodules. @@ -72,7 +72,7 @@ that contains the new commit. git add modules/KestrelhttpServer/ git commit -m "Update Kestrel to latest version" -## PowerShell is slow in aspnet/AspNetCore +## PowerShell is slow in dotnet/aspnetcore Many users have post-git, and extension that shows git status on the prompt line. Because `git status` with submodules on Windows is very slow, it can make PowerShell unbearable to use. diff --git a/docs/area-owners.md b/docs/area-owners.md new file mode 100644 index 0000000000..e4d6e72071 --- /dev/null +++ b/docs/area-owners.md @@ -0,0 +1,23 @@ +The below table lists all the `area-`labels used in the `aspnetcore` repository and their owners: + +| Area label | Owner | Description| +|--- | ---| --- | +| area-azure | anurse | | +| area-blazor | mkArtakMSFT | Blazor server and Blazor WASM related | +| area-commandlinetools | anurse, mkArtakMSFT | dev certs, dotnet watch, | +| area-dataprotection | anurse | | +| area-grpc | shirhatti | | +| area-healthchecks | rynowak | | +| area-hosting | anurse | | +| area-httpclientfactory | anurse | | +| area-identity | blowdart | | +| area-infrastructure | dougbu | | +| area-installers | anurse | | +| area-middleware | anurse | | +| area-mvc | mkArtakMSFT | | +| area-perf | anurse | | +| area-platform | anurse | | +| area-security | blowdart | | +| area-servers | anurse | | +| area-signalr | anurse | | +| area-websockets | anurse | | diff --git a/eng/Baseline.xml b/eng/Baseline.xml index 8ae05ac449..b4192011c4 100644 --- a/eng/Baseline.xml +++ b/eng/Baseline.xml @@ -86,4 +86,4 @@ Update this list when preparing for a new patch. - \ No newline at end of file + diff --git a/eng/Build.props b/eng/Build.props index 42a4992e7d..6f1b3f1908 100644 --- a/eng/Build.props +++ b/eng/Build.props @@ -34,7 +34,8 @@ $(RepoRoot)src\Installers\**\*.*proj; $(RepoRoot)src\SignalR\clients\ts\**\node_modules\**\*.*proj; $(RepoRoot)src\Components\Web.JS\node_modules\**\*.*proj; - $(RepoRoot)src\Components\Blazor\Templates\src\content\**\*.*proj; + $(RepoRoot)src\Components\Blazor\Build\testassets\**\*.*proj; + $(RepoRoot)src\ProjectTemplates\BlazorWasm.ProjectTemplates\content\**\*.csproj; $(RepoRoot)src\ProjectTemplates\Web.ProjectTemplates\content\**\*.csproj; $(RepoRoot)src\ProjectTemplates\Web.ProjectTemplates\content\**\*.fsproj; $(RepoRoot)src\ProjectTemplates\Web.Spa.ProjectTemplates\content\**\*.csproj; @@ -42,7 +43,7 @@ @@ -121,6 +123,13 @@ + + + + + + + @@ -120,9 +108,10 @@ and are generated based on the last package release. - + + - + @@ -143,6 +132,7 @@ and are generated based on the last package release. + @@ -164,8 +154,13 @@ and are generated based on the last package release. + - + + + + + @@ -173,12 +168,8 @@ and are generated based on the last package release. - - - - + - diff --git a/eng/FlakyTests.AfterArcade.props b/eng/FlakyTests.AfterArcade.props deleted file mode 100644 index 12f631127e..0000000000 --- a/eng/FlakyTests.AfterArcade.props +++ /dev/null @@ -1,7 +0,0 @@ - - - - $(ArtifactsDir)log\$(Configuration)\Flaky\ - $(ArtifactsDir)TestResults\$(Configuration)\Flaky\ - - diff --git a/eng/FlakyTests.BeforeArcade.props b/eng/FlakyTests.BeforeArcade.props deleted file mode 100644 index 02f9fb6c59..0000000000 --- a/eng/FlakyTests.BeforeArcade.props +++ /dev/null @@ -1,18 +0,0 @@ - - - - <_FlakyRunAdditionalArgs>-trait "Flaky:All=true" - <_NonFlakyRunAdditionalArgs>-notrait "Flaky:All=true" - - - - - <_FlakyRunAdditionalArgs>$(_FlakyRunAdditionalArgs) -trait "Flaky:AzP:All=true" -trait "Flaky:AzP:OS:$(AGENT_OS)=true" - <_NonFlakyRunAdditionalArgs>$(_NonFlakyRunAdditionalArgs) -notrait "Flaky:AzP:All=true" -notrait "Flaky:AzP:OS:$(AGENT_OS)=true" - - - - $(_NonFlakyRunAdditionalArgs) $(TestRunnerAdditionalArguments) - $(_FlakyRunAdditionalArgs) $(TestRunnerAdditionalArguments) - - diff --git a/eng/GenAPI.exclusions.txt b/eng/GenAPI.exclusions.txt index 810c09e52c..ebabc0ac7d 100644 --- a/eng/GenAPI.exclusions.txt +++ b/eng/GenAPI.exclusions.txt @@ -2,24 +2,4 @@ T:Microsoft.AspNetCore.Components.RenderTree.RenderTreeFrame # Manually implemented - https://github.com/dotnet/arcade/issues/2066 T:Microsoft.AspNetCore.Mvc.ApplicationModels.PageParameterModel -T:Microsoft.AspNetCore.Mvc.ApplicationModels.PagePropertyModel -# Manually implemented - Need to include internal setter -P:Microsoft.AspNetCore.Mvc.Razor.Infrastructure.TagHelperMemoryCacheProvider.Cache -P:Microsoft.AspNetCore.Mvc.RazorPages.RazorPagesOptions.Conventions -P:Microsoft.AspNetCore.Routing.Matching.CandidateState.Values -P:Microsoft.AspNetCore.Server.Kestrel.Core.ListenOptions.KestrelServerOptions -# public structs with public fields that GenAPI doesn't handle -T:Microsoft.AspNetCore.Components.EventCallback -T:Microsoft.AspNetCore.Components.EventCallback`1 -# Break GenAPI - https://github.com/dotnet/arcade/issues/4488 -T:Microsoft.AspNetCore.Components.Rendering.HtmlRenderer.d__17 -T:Microsoft.AspNetCore.Components.Rendering.HtmlRenderer.d__8 -T:Microsoft.AspNetCore.Mvc.ApplicationModels.ActionAttributeRouteModel.<>c -T:Microsoft.AspNetCore.Mvc.ApplicationModels.ActionAttributeRouteModel.d__3 -T:Microsoft.AspNetCore.Mvc.ControllerBase.d__181`1 -T:Microsoft.AspNetCore.Mvc.ControllerBase.d__183`1 -T:Microsoft.AspNetCore.Mvc.ControllerBase.d__184`1 -T:Microsoft.AspNetCore.Mvc.ControllerBase.d__187 -T:Microsoft.AspNetCore.Mvc.Controllers.ControllerBinderDelegateProvider.<>c__DisplayClass0_0 -T:Microsoft.AspNetCore.Mvc.ModelBinding.CompositeValueProvider.d__4 -T:Microsoft.AspNetCore.Mvc.Routing.ConsumesMatcherPolicy.<>c +T:Microsoft.AspNetCore.Mvc.ApplicationModels.PagePropertyModel \ No newline at end of file diff --git a/eng/PlatformManifest.txt b/eng/PlatformManifest.txt deleted file mode 100644 index 1d5a81f517..0000000000 --- a/eng/PlatformManifest.txt +++ /dev/null @@ -1,131 +0,0 @@ -aspnetcorev2_inprocess.dll|Microsoft.AspNetCore.App.Ref||13.1.19320.0 -Microsoft.AspNetCore.Antiforgery.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56601 -Microsoft.AspNetCore.Authentication.Abstractions.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56601 -Microsoft.AspNetCore.Authentication.Cookies.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56601 -Microsoft.AspNetCore.Authentication.Core.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56601 -Microsoft.AspNetCore.Authentication.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56601 -Microsoft.AspNetCore.Authentication.OAuth.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56601 -Microsoft.AspNetCore.Authorization.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56601 -Microsoft.AspNetCore.Authorization.Policy.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56601 -Microsoft.AspNetCore.Components.Authorization.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56601 -Microsoft.AspNetCore.Components.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56601 -Microsoft.AspNetCore.Components.Forms.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56601 -Microsoft.AspNetCore.Components.Server.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56601 -Microsoft.AspNetCore.Components.Web.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56601 -Microsoft.AspNetCore.Connections.Abstractions.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56601 -Microsoft.AspNetCore.CookiePolicy.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56601 -Microsoft.AspNetCore.Cors.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56601 -Microsoft.AspNetCore.Cryptography.Internal.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56601 -Microsoft.AspNetCore.Cryptography.KeyDerivation.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56601 -Microsoft.AspNetCore.DataProtection.Abstractions.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56601 -Microsoft.AspNetCore.DataProtection.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56601 -Microsoft.AspNetCore.DataProtection.Extensions.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56601 -Microsoft.AspNetCore.Diagnostics.Abstractions.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56601 -Microsoft.AspNetCore.Diagnostics.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56601 -Microsoft.AspNetCore.Diagnostics.HealthChecks.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56601 -Microsoft.AspNetCore.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56601 -Microsoft.AspNetCore.HostFiltering.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56601 -Microsoft.AspNetCore.Hosting.Abstractions.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56601 -Microsoft.AspNetCore.Hosting.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56601 -Microsoft.AspNetCore.Hosting.Server.Abstractions.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56601 -Microsoft.AspNetCore.Html.Abstractions.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56601 -Microsoft.AspNetCore.Http.Abstractions.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56601 -Microsoft.AspNetCore.Http.Connections.Common.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56601 -Microsoft.AspNetCore.Http.Connections.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56601 -Microsoft.AspNetCore.Http.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56601 -Microsoft.AspNetCore.Http.Extensions.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56601 -Microsoft.AspNetCore.Http.Features.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56601 -Microsoft.AspNetCore.HttpOverrides.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56601 -Microsoft.AspNetCore.HttpsPolicy.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56601 -Microsoft.AspNetCore.Identity.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56601 -Microsoft.AspNetCore.Localization.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56601 -Microsoft.AspNetCore.Localization.Routing.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56601 -Microsoft.AspNetCore.Metadata.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56601 -Microsoft.AspNetCore.Mvc.Abstractions.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56601 -Microsoft.AspNetCore.Mvc.ApiExplorer.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56601 -Microsoft.AspNetCore.Mvc.Core.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56601 -Microsoft.AspNetCore.Mvc.Cors.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56601 -Microsoft.AspNetCore.Mvc.DataAnnotations.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56601 -Microsoft.AspNetCore.Mvc.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56601 -Microsoft.AspNetCore.Mvc.Formatters.Json.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56601 -Microsoft.AspNetCore.Mvc.Formatters.Xml.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56601 -Microsoft.AspNetCore.Mvc.Localization.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56601 -Microsoft.AspNetCore.Mvc.Razor.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56601 -Microsoft.AspNetCore.Mvc.RazorPages.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56601 -Microsoft.AspNetCore.Mvc.TagHelpers.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56601 -Microsoft.AspNetCore.Mvc.ViewFeatures.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56601 -Microsoft.AspNetCore.Razor.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56601 -Microsoft.AspNetCore.Razor.Runtime.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56601 -Microsoft.AspNetCore.ResponseCaching.Abstractions.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56601 -Microsoft.AspNetCore.ResponseCaching.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56601 -Microsoft.AspNetCore.ResponseCompression.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56601 -Microsoft.AspNetCore.Rewrite.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56601 -Microsoft.AspNetCore.Routing.Abstractions.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56601 -Microsoft.AspNetCore.Routing.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56601 -Microsoft.AspNetCore.Server.HttpSys.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56601 -Microsoft.AspNetCore.Server.IIS.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56601 -Microsoft.AspNetCore.Server.IISIntegration.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56601 -Microsoft.AspNetCore.Server.Kestrel.Core.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56601 -Microsoft.AspNetCore.Server.Kestrel.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56601 -Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56601 -Microsoft.AspNetCore.Session.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56601 -Microsoft.AspNetCore.SignalR.Common.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56601 -Microsoft.AspNetCore.SignalR.Core.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56601 -Microsoft.AspNetCore.SignalR.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56601 -Microsoft.AspNetCore.SignalR.Protocols.Json.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56601 -Microsoft.AspNetCore.StaticFiles.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56601 -Microsoft.AspNetCore.WebSockets.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56601 -Microsoft.AspNetCore.WebUtilities.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56601 -Microsoft.Extensions.Caching.Abstractions.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56504 -Microsoft.Extensions.Caching.Memory.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56504 -Microsoft.Extensions.Configuration.Abstractions.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56504 -Microsoft.Extensions.Configuration.Binder.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56504 -Microsoft.Extensions.Configuration.CommandLine.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56504 -Microsoft.Extensions.Configuration.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56504 -Microsoft.Extensions.Configuration.EnvironmentVariables.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56504 -Microsoft.Extensions.Configuration.FileExtensions.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56504 -Microsoft.Extensions.Configuration.Ini.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56504 -Microsoft.Extensions.Configuration.Json.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56504 -Microsoft.Extensions.Configuration.KeyPerFile.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56504 -Microsoft.Extensions.Configuration.UserSecrets.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56504 -Microsoft.Extensions.Configuration.Xml.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56504 -Microsoft.Extensions.DependencyInjection.Abstractions.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56504 -Microsoft.Extensions.DependencyInjection.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56504 -Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56504 -Microsoft.Extensions.Diagnostics.HealthChecks.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56504 -Microsoft.Extensions.FileProviders.Abstractions.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56504 -Microsoft.Extensions.FileProviders.Composite.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56504 -Microsoft.Extensions.FileProviders.Embedded.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56504 -Microsoft.Extensions.FileProviders.Physical.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56504 -Microsoft.Extensions.FileSystemGlobbing.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56504 -Microsoft.Extensions.Hosting.Abstractions.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56504 -Microsoft.Extensions.Hosting.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56504 -Microsoft.Extensions.Http.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56504 -Microsoft.Extensions.Identity.Core.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56601 -Microsoft.Extensions.Identity.Stores.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56601 -Microsoft.Extensions.Localization.Abstractions.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56504 -Microsoft.Extensions.Localization.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56504 -Microsoft.Extensions.Logging.Abstractions.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56504 -Microsoft.Extensions.Logging.Configuration.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56504 -Microsoft.Extensions.Logging.Console.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56504 -Microsoft.Extensions.Logging.Debug.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56504 -Microsoft.Extensions.Logging.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56504 -Microsoft.Extensions.Logging.EventLog.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56504 -Microsoft.Extensions.Logging.EventSource.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56504 -Microsoft.Extensions.Logging.TraceSource.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56504 -Microsoft.Extensions.ObjectPool.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56504 -Microsoft.Extensions.Options.ConfigurationExtensions.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56504 -Microsoft.Extensions.Options.DataAnnotations.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56504 -Microsoft.Extensions.Options.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56504 -Microsoft.Extensions.Primitives.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56504 -Microsoft.Extensions.WebEncoders.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56504 -Microsoft.JSInterop.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56504 -Microsoft.Net.Http.Headers.dll|Microsoft.AspNetCore.App.Ref|3.1.0.0|3.100.19.56601 -Microsoft.Win32.SystemEvents.dll|Microsoft.AspNetCore.App.Ref|4.0.2.0|4.700.19.56404 -System.Diagnostics.EventLog.dll|Microsoft.AspNetCore.App.Ref|4.0.2.0|4.700.19.56404 -System.Drawing.Common.dll|Microsoft.AspNetCore.App.Ref|4.0.2.0|4.700.19.56404 -System.IO.Pipelines.dll|Microsoft.AspNetCore.App.Ref|4.0.2.0|4.700.19.56404 -System.Security.Cryptography.Pkcs.dll|Microsoft.AspNetCore.App.Ref|4.1.1.0|4.700.19.56404 -System.Security.Cryptography.Xml.dll|Microsoft.AspNetCore.App.Ref|4.0.3.0|4.700.19.56404 -System.Security.Permissions.dll|Microsoft.AspNetCore.App.Ref|4.0.3.0|4.700.19.56404 -System.Windows.Extensions.dll|Microsoft.AspNetCore.App.Ref|4.0.1.0|4.700.19.56404 \ No newline at end of file diff --git a/eng/ProjectReferences.props b/eng/ProjectReferences.props index dedf038e0b..dda9b06ce8 100644 --- a/eng/ProjectReferences.props +++ b/eng/ProjectReferences.props @@ -6,8 +6,6 @@ - - @@ -18,6 +16,7 @@ + @@ -58,9 +57,11 @@ + + @@ -141,5 +142,14 @@ + + + + + + + + + diff --git a/eng/Publishing.props b/eng/Publishing.props index 74434eeea5..ab7456c178 100644 --- a/eng/Publishing.props +++ b/eng/Publishing.props @@ -34,6 +34,7 @@ + + + $(ArtifactsDir)log\$(Configuration)\Quarantined\ + $(ArtifactsDir)TestResults\$(Configuration)\Quarantined\ + + diff --git a/eng/QuarantinedTests.BeforeArcade.props b/eng/QuarantinedTests.BeforeArcade.props new file mode 100644 index 0000000000..5fa5fdcaf6 --- /dev/null +++ b/eng/QuarantinedTests.BeforeArcade.props @@ -0,0 +1,11 @@ + + + <_QuarantinedTestRunAdditionalArgs>-trait "Quarantined=true" + <_NonQuarantinedTestRunAdditionalArgs>-notrait "Quarantined=true" + + + + $(_NonQuarantinedTestRunAdditionalArgs) $(TestRunnerAdditionalArguments) + $(_QuarantinedTestRunAdditionalArgs) $(TestRunnerAdditionalArguments) + + diff --git a/eng/SharedFramework.External.props b/eng/SharedFramework.External.props index e46f5c1120..9812152edd 100644 --- a/eng/SharedFramework.External.props +++ b/eng/SharedFramework.External.props @@ -7,7 +7,7 @@ - + $(SystemIOPipelinesPackageVersion.Split('.')[0]).$(SystemIOPipelinesPackageVersion.Split('.')[1]).0 $(SystemSecurityCryptographyXmlPackageVersion.Split('.')[0]).$(SystemSecurityCryptographyXmlPackageVersion.Split('.')[1]).0 $(MicrosoftWin32SystemEventsPackageVersion.Split('.')[0]).$(MicrosoftWin32SystemEventsPackageVersion.Split('.')[1]).0 @@ -20,7 +20,7 @@ - + @@ -30,24 +30,18 @@ - - - - - - @@ -56,13 +50,10 @@ - - - diff --git a/eng/SharedFramework.Local.props b/eng/SharedFramework.Local.props index ddfe60ecfb..49d28f78f0 100644 --- a/eng/SharedFramework.Local.props +++ b/eng/SharedFramework.Local.props @@ -26,6 +26,15 @@ + + + + + + + + + diff --git a/eng/Signing.props b/eng/Signing.props index 3b61e9205f..024a5c8a0a 100644 --- a/eng/Signing.props +++ b/eng/Signing.props @@ -93,9 +93,11 @@ <_DotNetFilesToExclude Include="$(BaseRedistNetCorePath)win-x64\shared\Microsoft.NETCore.App\**\*.dll" CertificateName="None" /> <_DotNetFilesToExclude Include="$(BaseRedistNetCorePath)win-x86\shared\Microsoft.NETCore.App\**\*.dll" CertificateName="None" /> <_DotNetFilesToExclude Include="$(BaseRedistNetCorePath)win-arm\shared\Microsoft.NETCore.App\**\*.dll" CertificateName="None" /> + <_DotNetFilesToExclude Include="$(BaseRedistNetCorePath)win-arm64\shared\Microsoft.NETCore.App\**\*.dll" CertificateName="None" /> <_DotNetFilesToExclude Include="$(BaseRedistNetCorePath)win-x64\host\**\*.dll" CertificateName="None" /> <_DotNetFilesToExclude Include="$(BaseRedistNetCorePath)win-x86\host\**\*.dll" CertificateName="None" /> <_DotNetFilesToExclude Include="$(BaseRedistNetCorePath)win-arm\host\**\*.dll" CertificateName="None" /> + <_DotNetFilesToExclude Include="$(BaseRedistNetCorePath)win-arm64\host\**\*.dll" CertificateName="None" /> <_DotNetFilesToExclude Include="$(RedistNetCorePath)dotnet.exe" CertificateName="None" /> diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index ff722eff07..b118c4e3c9 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -9,429 +9,340 @@ --> - - https://github.com/aspnet/Blazor - 7868699de745fd30a654c798a99dc541b77b95c0 + + https://github.com/dotnet/blazor + dd7fb4d3931d556458f62642c2edfc59f6295bfb - + https://github.com/dotnet/aspnetcore-tooling - 5ecfad7e0515ee580f7e1b51d1558fc2a1d27ee5 + 191f37daed4cca323ad5bee7b4ee30a0b93d941b - + https://github.com/dotnet/aspnetcore-tooling - 5ecfad7e0515ee580f7e1b51d1558fc2a1d27ee5 + 191f37daed4cca323ad5bee7b4ee30a0b93d941b - + https://github.com/dotnet/aspnetcore-tooling - 5ecfad7e0515ee580f7e1b51d1558fc2a1d27ee5 + 191f37daed4cca323ad5bee7b4ee30a0b93d941b - + https://github.com/dotnet/aspnetcore-tooling - 5ecfad7e0515ee580f7e1b51d1558fc2a1d27ee5 + 191f37daed4cca323ad5bee7b4ee30a0b93d941b - + https://github.com/dotnet/efcore - 00f57950901b46a4ee065edd6fe8594f1caa7b1f + 8bd99a9e1ac43751be398e82fea48c96794dc9ff - + https://github.com/dotnet/efcore - 00f57950901b46a4ee065edd6fe8594f1caa7b1f + 8bd99a9e1ac43751be398e82fea48c96794dc9ff - + https://github.com/dotnet/efcore - 00f57950901b46a4ee065edd6fe8594f1caa7b1f + 8bd99a9e1ac43751be398e82fea48c96794dc9ff - + https://github.com/dotnet/efcore - 00f57950901b46a4ee065edd6fe8594f1caa7b1f + 8bd99a9e1ac43751be398e82fea48c96794dc9ff - + https://github.com/dotnet/efcore - 00f57950901b46a4ee065edd6fe8594f1caa7b1f + 8bd99a9e1ac43751be398e82fea48c96794dc9ff - + https://github.com/dotnet/efcore - 00f57950901b46a4ee065edd6fe8594f1caa7b1f + 8bd99a9e1ac43751be398e82fea48c96794dc9ff - + https://github.com/dotnet/efcore - 00f57950901b46a4ee065edd6fe8594f1caa7b1f + 8bd99a9e1ac43751be398e82fea48c96794dc9ff - + https://github.com/dotnet/extensions - 6ea53eeff45bed83b1df5a19433b20e4177fbb8c + 6d1dad26c4622fdb8b5fc226598748c06f5dd21f - + https://github.com/dotnet/extensions - 6ea53eeff45bed83b1df5a19433b20e4177fbb8c + 6d1dad26c4622fdb8b5fc226598748c06f5dd21f - + https://github.com/dotnet/extensions - 6ea53eeff45bed83b1df5a19433b20e4177fbb8c + 6d1dad26c4622fdb8b5fc226598748c06f5dd21f - + https://github.com/dotnet/extensions - 6ea53eeff45bed83b1df5a19433b20e4177fbb8c + 6d1dad26c4622fdb8b5fc226598748c06f5dd21f - + https://github.com/dotnet/extensions - 6ea53eeff45bed83b1df5a19433b20e4177fbb8c + 6d1dad26c4622fdb8b5fc226598748c06f5dd21f - + https://github.com/dotnet/extensions - 6ea53eeff45bed83b1df5a19433b20e4177fbb8c + 6d1dad26c4622fdb8b5fc226598748c06f5dd21f - + https://github.com/dotnet/extensions - 6ea53eeff45bed83b1df5a19433b20e4177fbb8c + 6d1dad26c4622fdb8b5fc226598748c06f5dd21f - + https://github.com/dotnet/extensions - 6ea53eeff45bed83b1df5a19433b20e4177fbb8c + 6d1dad26c4622fdb8b5fc226598748c06f5dd21f - + https://github.com/dotnet/extensions - 6ea53eeff45bed83b1df5a19433b20e4177fbb8c + 6d1dad26c4622fdb8b5fc226598748c06f5dd21f - + https://github.com/dotnet/extensions - 6ea53eeff45bed83b1df5a19433b20e4177fbb8c + 6d1dad26c4622fdb8b5fc226598748c06f5dd21f - + https://github.com/dotnet/extensions - 6ea53eeff45bed83b1df5a19433b20e4177fbb8c + 6d1dad26c4622fdb8b5fc226598748c06f5dd21f - + https://github.com/dotnet/extensions - 6ea53eeff45bed83b1df5a19433b20e4177fbb8c + 6d1dad26c4622fdb8b5fc226598748c06f5dd21f - + https://github.com/dotnet/extensions - 6ea53eeff45bed83b1df5a19433b20e4177fbb8c + 6d1dad26c4622fdb8b5fc226598748c06f5dd21f - + https://github.com/dotnet/extensions - 6ea53eeff45bed83b1df5a19433b20e4177fbb8c + 6d1dad26c4622fdb8b5fc226598748c06f5dd21f - + https://github.com/dotnet/extensions - 6ea53eeff45bed83b1df5a19433b20e4177fbb8c + 6d1dad26c4622fdb8b5fc226598748c06f5dd21f - + https://github.com/dotnet/extensions - 6ea53eeff45bed83b1df5a19433b20e4177fbb8c + 6d1dad26c4622fdb8b5fc226598748c06f5dd21f - + https://github.com/dotnet/extensions - 6ea53eeff45bed83b1df5a19433b20e4177fbb8c + 6d1dad26c4622fdb8b5fc226598748c06f5dd21f - + https://github.com/dotnet/extensions - 6ea53eeff45bed83b1df5a19433b20e4177fbb8c + 6d1dad26c4622fdb8b5fc226598748c06f5dd21f - + https://github.com/dotnet/extensions - 6ea53eeff45bed83b1df5a19433b20e4177fbb8c + 6d1dad26c4622fdb8b5fc226598748c06f5dd21f - + https://github.com/dotnet/extensions - 6ea53eeff45bed83b1df5a19433b20e4177fbb8c + 6d1dad26c4622fdb8b5fc226598748c06f5dd21f - + https://github.com/dotnet/extensions - 6ea53eeff45bed83b1df5a19433b20e4177fbb8c + 6d1dad26c4622fdb8b5fc226598748c06f5dd21f - + https://github.com/dotnet/extensions - 6ea53eeff45bed83b1df5a19433b20e4177fbb8c + 6d1dad26c4622fdb8b5fc226598748c06f5dd21f - + https://github.com/dotnet/extensions - 6ea53eeff45bed83b1df5a19433b20e4177fbb8c + 6d1dad26c4622fdb8b5fc226598748c06f5dd21f - + https://github.com/dotnet/extensions - 6ea53eeff45bed83b1df5a19433b20e4177fbb8c + 6d1dad26c4622fdb8b5fc226598748c06f5dd21f - + https://github.com/dotnet/extensions - 6ea53eeff45bed83b1df5a19433b20e4177fbb8c + 6d1dad26c4622fdb8b5fc226598748c06f5dd21f - + https://github.com/dotnet/extensions - 6ea53eeff45bed83b1df5a19433b20e4177fbb8c + 6d1dad26c4622fdb8b5fc226598748c06f5dd21f - + https://github.com/dotnet/extensions - 6ea53eeff45bed83b1df5a19433b20e4177fbb8c + 6d1dad26c4622fdb8b5fc226598748c06f5dd21f - + https://github.com/dotnet/extensions - 6ea53eeff45bed83b1df5a19433b20e4177fbb8c + 6d1dad26c4622fdb8b5fc226598748c06f5dd21f - + https://github.com/dotnet/extensions - 6ea53eeff45bed83b1df5a19433b20e4177fbb8c + 6d1dad26c4622fdb8b5fc226598748c06f5dd21f - + https://github.com/dotnet/extensions - 6ea53eeff45bed83b1df5a19433b20e4177fbb8c + 6d1dad26c4622fdb8b5fc226598748c06f5dd21f - + https://github.com/dotnet/extensions - 6ea53eeff45bed83b1df5a19433b20e4177fbb8c + 6d1dad26c4622fdb8b5fc226598748c06f5dd21f - + https://github.com/dotnet/extensions - 6ea53eeff45bed83b1df5a19433b20e4177fbb8c + 6d1dad26c4622fdb8b5fc226598748c06f5dd21f - + https://github.com/dotnet/extensions - 6ea53eeff45bed83b1df5a19433b20e4177fbb8c + 6d1dad26c4622fdb8b5fc226598748c06f5dd21f - + https://github.com/dotnet/extensions - 6ea53eeff45bed83b1df5a19433b20e4177fbb8c + 6d1dad26c4622fdb8b5fc226598748c06f5dd21f - + https://github.com/dotnet/extensions - 6ea53eeff45bed83b1df5a19433b20e4177fbb8c + 6d1dad26c4622fdb8b5fc226598748c06f5dd21f - + https://github.com/dotnet/extensions - 6ea53eeff45bed83b1df5a19433b20e4177fbb8c + 6d1dad26c4622fdb8b5fc226598748c06f5dd21f - + https://github.com/dotnet/extensions - 6ea53eeff45bed83b1df5a19433b20e4177fbb8c + 6d1dad26c4622fdb8b5fc226598748c06f5dd21f - + https://github.com/dotnet/extensions - 6ea53eeff45bed83b1df5a19433b20e4177fbb8c + 6d1dad26c4622fdb8b5fc226598748c06f5dd21f - + https://github.com/dotnet/extensions - 6ea53eeff45bed83b1df5a19433b20e4177fbb8c + 6d1dad26c4622fdb8b5fc226598748c06f5dd21f - + https://github.com/dotnet/extensions - 6ea53eeff45bed83b1df5a19433b20e4177fbb8c + 6d1dad26c4622fdb8b5fc226598748c06f5dd21f - - https://github.com/dotnet/extensions - 6ea53eeff45bed83b1df5a19433b20e4177fbb8c + + https://github.com/dotnet/runtime + c39af4fc45c05e862815c34239b33de3b0259705 - - https://github.com/dotnet/extensions - 6ea53eeff45bed83b1df5a19433b20e4177fbb8c + + https://github.com/dotnet/runtime + c39af4fc45c05e862815c34239b33de3b0259705 - - https://github.com/dotnet/extensions - 6ea53eeff45bed83b1df5a19433b20e4177fbb8c + + https://github.com/dotnet/runtime + c39af4fc45c05e862815c34239b33de3b0259705 - - https://github.com/dotnet/extensions - 6ea53eeff45bed83b1df5a19433b20e4177fbb8c + + https://github.com/dotnet/runtime + c39af4fc45c05e862815c34239b33de3b0259705 - - https://github.com/dotnet/extensions - 6ea53eeff45bed83b1df5a19433b20e4177fbb8c + + https://github.com/dotnet/runtime + c39af4fc45c05e862815c34239b33de3b0259705 - - https://github.com/dotnet/extensions - 6ea53eeff45bed83b1df5a19433b20e4177fbb8c + + https://github.com/dotnet/runtime + c39af4fc45c05e862815c34239b33de3b0259705 - - https://github.com/dotnet/extensions - 6ea53eeff45bed83b1df5a19433b20e4177fbb8c + + https://github.com/dotnet/runtime + c39af4fc45c05e862815c34239b33de3b0259705 - - https://github.com/dotnet/extensions - 6ea53eeff45bed83b1df5a19433b20e4177fbb8c + + https://github.com/dotnet/runtime + c39af4fc45c05e862815c34239b33de3b0259705 - - https://github.com/dotnet/extensions - 6ea53eeff45bed83b1df5a19433b20e4177fbb8c + + https://github.com/dotnet/runtime + c39af4fc45c05e862815c34239b33de3b0259705 - - https://github.com/dotnet/extensions - 6ea53eeff45bed83b1df5a19433b20e4177fbb8c + + https://github.com/dotnet/runtime + c39af4fc45c05e862815c34239b33de3b0259705 - - https://github.com/dotnet/extensions - 6ea53eeff45bed83b1df5a19433b20e4177fbb8c + + https://github.com/dotnet/runtime + c39af4fc45c05e862815c34239b33de3b0259705 - - https://github.com/dotnet/extensions - 6ea53eeff45bed83b1df5a19433b20e4177fbb8c + + https://github.com/dotnet/runtime + c39af4fc45c05e862815c34239b33de3b0259705 - - https://github.com/dotnet/extensions - 6ea53eeff45bed83b1df5a19433b20e4177fbb8c + + https://github.com/dotnet/runtime + c39af4fc45c05e862815c34239b33de3b0259705 - - https://github.com/dotnet/extensions - 6ea53eeff45bed83b1df5a19433b20e4177fbb8c + + https://github.com/dotnet/runtime + c39af4fc45c05e862815c34239b33de3b0259705 - - https://github.com/dotnet/extensions - 6ea53eeff45bed83b1df5a19433b20e4177fbb8c + + https://github.com/dotnet/runtime + c39af4fc45c05e862815c34239b33de3b0259705 - - https://github.com/dotnet/extensions - 6ea53eeff45bed83b1df5a19433b20e4177fbb8c + + https://github.com/dotnet/runtime + c39af4fc45c05e862815c34239b33de3b0259705 - - https://github.com/dotnet/extensions - 6ea53eeff45bed83b1df5a19433b20e4177fbb8c + + https://github.com/dotnet/runtime + c39af4fc45c05e862815c34239b33de3b0259705 - - https://github.com/dotnet/corefx - 0f7f38c4fd323b26da10cce95f857f77f0f09b48 + + https://github.com/dotnet/runtime + c39af4fc45c05e862815c34239b33de3b0259705 - - https://github.com/dotnet/corefx - 0f7f38c4fd323b26da10cce95f857f77f0f09b48 + + https://github.com/dotnet/runtime + c39af4fc45c05e862815c34239b33de3b0259705 - - https://github.com/dotnet/corefx - 0f7f38c4fd323b26da10cce95f857f77f0f09b48 + + https://github.com/dotnet/runtime + c39af4fc45c05e862815c34239b33de3b0259705 - - https://github.com/dotnet/corefx - 0f7f38c4fd323b26da10cce95f857f77f0f09b48 + + https://github.com/dotnet/runtime + c39af4fc45c05e862815c34239b33de3b0259705 - - https://github.com/dotnet/corefx - 0f7f38c4fd323b26da10cce95f857f77f0f09b48 - - - https://github.com/dotnet/corefx - 0f7f38c4fd323b26da10cce95f857f77f0f09b48 - - - https://github.com/dotnet/corefx - 0f7f38c4fd323b26da10cce95f857f77f0f09b48 - - - https://github.com/dotnet/corefx - 0f7f38c4fd323b26da10cce95f857f77f0f09b48 - - - https://github.com/dotnet/corefx - 0f7f38c4fd323b26da10cce95f857f77f0f09b48 - - - https://github.com/dotnet/corefx - 0f7f38c4fd323b26da10cce95f857f77f0f09b48 - - - https://github.com/dotnet/corefx - 0f7f38c4fd323b26da10cce95f857f77f0f09b48 - - - https://github.com/dotnet/corefx - 0f7f38c4fd323b26da10cce95f857f77f0f09b48 - - - https://github.com/dotnet/corefx - 0f7f38c4fd323b26da10cce95f857f77f0f09b48 - - - https://github.com/dotnet/corefx - 0f7f38c4fd323b26da10cce95f857f77f0f09b48 - - - https://github.com/dotnet/corefx - 0f7f38c4fd323b26da10cce95f857f77f0f09b48 - - - https://github.com/dotnet/corefx - 0f7f38c4fd323b26da10cce95f857f77f0f09b48 - - - https://github.com/dotnet/corefx - 0f7f38c4fd323b26da10cce95f857f77f0f09b48 - - - https://github.com/dotnet/corefx - 0f7f38c4fd323b26da10cce95f857f77f0f09b48 - - - https://github.com/dotnet/corefx - 0f7f38c4fd323b26da10cce95f857f77f0f09b48 - - - https://github.com/dotnet/corefx - e946cebe43a510e8c6476bbc8185d1445df33a1a - - - https://github.com/dotnet/corefx - 0f7f38c4fd323b26da10cce95f857f77f0f09b48 - - - https://github.com/dotnet/corefx - 0f7f38c4fd323b26da10cce95f857f77f0f09b48 - - - https://github.com/dotnet/core-setup - 866f89c19bfb6bcb3d861cc424643765a441d396 + + https://github.com/dotnet/runtime + c39af4fc45c05e862815c34239b33de3b0259705 - - https://github.com/dotnet/core-setup - 866f89c19bfb6bcb3d861cc424643765a441d396 + + https://github.com/dotnet/runtime + c39af4fc45c05e862815c34239b33de3b0259705 - - https://github.com/dotnet/core-setup - 7d57652f33493fa022125b7f63aad0d70c52d810 - - - https://github.com/dotnet/core-setup - 866f89c19bfb6bcb3d861cc424643765a441d396 - - - - https://github.com/dotnet/core-setup - 65f04fb6db7a5e198d05dbebd5c4ad21eb018f89 - - - https://github.com/aspnet/Extensions - 4e1be2fb546751c773968d7b40ff7f4b62887153 + + https://github.com/dotnet/runtime + c39af4fc45c05e862815c34239b33de3b0259705 - - https://github.com/dotnet/corefx - c5f41f1b6bec47ee8c1a4daba911b65723540da5 + + https://github.com/dotnet/runtime + c39af4fc45c05e862815c34239b33de3b0259705 - + https://github.com/dotnet/extensions - 6ea53eeff45bed83b1df5a19433b20e4177fbb8c + 6d1dad26c4622fdb8b5fc226598748c06f5dd21f - + https://github.com/dotnet/arcade - 15f00efd583eab4372b2e9ca25bd80ace5b119ad + 7dbc907fa03eacf4c57f827cb4235d77b7ed4fcd - + https://github.com/dotnet/arcade - 15f00efd583eab4372b2e9ca25bd80ace5b119ad + 7dbc907fa03eacf4c57f827cb4235d77b7ed4fcd - + https://github.com/dotnet/arcade - 15f00efd583eab4372b2e9ca25bd80ace5b119ad + 7dbc907fa03eacf4c57f827cb4235d77b7ed4fcd - - https://github.com/dotnet/extensions - 6ea53eeff45bed83b1df5a19433b20e4177fbb8c - - + https://github.com/dotnet/roslyn - d8180a5ecafb92adcfbfe8cf9199eb23be1a1ccf + 9250a5196fc3dbd251768a9c6e3e9b9489ac4993 - + \ No newline at end of file diff --git a/eng/Versions.props b/eng/Versions.props index 4cbf2c2548..33ba27d493 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -6,34 +6,34 @@ --> - 3 - 1 - 4 - 0 + 5 + 0 + 0 + 3 - true + false release + preview + Preview $(PreReleaseVersionIteration) true false - servicing - Servicing - - 4 - preview$(BlazorClientPreReleasePreviewNumber) $(AspNetCoreMajorVersion).$(AspNetCoreMinorVersion) - - 3.1.0 false - - true + + true + + true $(AspNetCoreMajorVersion).$(AspNetCoreMinorVersion).$(AspNetCorePatchVersion) $(VersionPrefix) - $(AspNetCoreMajorVersion).$(AspNetCoreMinorVersion).3 + $(AspNetCoreMajorVersion).$(AspNetCoreMinorVersion).0 0.3.$(AspNetCorePatchVersion) $([MSBuild]::Add(10, $(AspNetCoreMajorVersion))) @@ -62,116 +62,94 @@ --> - 1.0.0-beta.20113.5 + 5.0.0-beta.20171.1 - 3.4.1-beta4-20127-10 + 3.6.0-3.20170.19 - 3.1.4-servicing.20176.1 - 3.1.4-servicing.20176.1 - 3.1.0 - 3.1.4-servicing.20176.1 - 2.1.0 + 5.0.0-preview.3-runtime.20174.7 + 5.0.0-preview.3.20174.7 + 5.0.0-preview.3.20174.7 + 5.0.0-preview.3.20174.7 - 1.1.0 - 4.7.0 - 4.7.0 - 4.7.0 - 4.7.0 - 4.7.0 - 4.7.0 - 4.7.0 - 4.7.0 - 4.7.0 - 1.8.0 - 4.7.0 - 4.7.0 - 4.7.0 - 4.7.0 - 4.7.0 - 4.7.0 - 4.7.0 - 4.7.0 - 4.7.1 - 4.7.0 - 4.7.0 + 5.0.0-preview.3.20174.7 + 5.0.0-preview.3.20174.7 + 5.0.0-preview.3.20174.7 + 5.0.0-preview.3.20174.7 + 5.0.0-preview.3.20174.7 + 5.0.0-preview.3.20174.7 + 5.0.0-preview.3.20174.7 + 5.0.0-preview.3.20174.7 + 5.0.0-preview.3.20174.7 + 5.0.0-preview.3.20174.7 + 5.0.0-preview.3.20174.7 + 5.0.0-preview.3.20174.7 + 5.0.0-preview.3.20174.7 + 5.0.0-preview.3.20174.7 + 5.0.0-preview.3.20174.7 + 5.0.0-preview.3.20174.7 + 5.0.0-preview.3.20174.7 + 5.0.0-preview.3.20174.7 + 5.0.0-preview.3.20174.7 + 5.0.0-preview.3.20174.7 - 3.1.1-servicing.20176.2 - - 3.1.0-preview4.19605.1 - - 3.1.4-servicing.20176.6 - 3.1.4-servicing.20176.6 - 3.1.4-servicing.20176.6 - 3.1.4-servicing.20176.6 - 3.1.4-servicing.20176.6 - 3.1.4 - 3.1.4 - 3.1.4 - 3.1.4 - 3.1.4-servicing.20176.6 - 3.1.4 - 3.1.4 - 3.1.4 - 3.1.4 - 3.1.4 - 3.1.4 - 3.1.4 - 3.1.4 - 3.1.4 - 3.1.4 - 3.1.4 - 3.1.4 - 3.1.4 - 3.1.4 - 3.1.4 - 3.1.4 - 3.1.4 - 3.1.4 - 3.1.4 - 3.1.4 - 3.1.4 - 3.1.4 - 3.1.4-servicing.20176.6 - 3.1.4 - 3.1.4 - 3.1.4-servicing.20176.6 - 3.1.4 - 3.1.4 - 3.1.4 - 3.1.4 - 3.1.4 - 3.1.4 - 3.1.4 - 3.1.4 - 3.1.4 - 3.1.4 - 3.1.4 - 3.1.4-servicing.20176.6 - 3.1.4 - 3.1.4 - 3.1.4 - 3.1.4 - 3.1.4 - 3.1.4-servicing.20176.6 - 3.1.4 - 3.1.4-servicing.20176.6 - 3.1.4-servicing.20176.6 - 3.1.4 - 3.1.0-rtm.19565.4 - 3.1.4 - - 3.1.4 - 3.1.4 - 3.1.4 - 3.1.4 - 3.1.4 - 3.1.4 - 3.1.4 - - 3.1.3 - 3.1.3 - 3.1.3 - 3.1.3 + 5.0.0-preview.3.20174.7 + + 3.2.0-preview1.20067.1 + + 5.0.0-preview.3.20175.7 + 5.0.0-preview.3.20175.7 + 5.0.0-preview.3.20175.7 + 5.0.0-preview.3.20175.7 + 5.0.0-preview.3.20175.7 + 5.0.0-preview.3.20175.7 + 5.0.0-preview.3.20175.7 + 5.0.0-preview.3.20175.7 + 5.0.0-preview.3.20175.7 + 5.0.0-preview.3.20175.7 + 5.0.0-preview.3.20175.7 + 5.0.0-preview.3.20175.7 + 5.0.0-preview.3.20175.7 + 5.0.0-preview.3.20175.7 + 5.0.0-preview.3.20175.7 + 5.0.0-preview.3.20175.7 + 5.0.0-preview.3.20175.7 + 5.0.0-preview.3.20175.7 + 5.0.0-preview.3.20175.7 + 5.0.0-preview.3.20175.7 + 5.0.0-preview.3.20175.7 + 5.0.0-preview.3.20175.7 + 5.0.0-preview.3.20175.7 + 5.0.0-preview.3.20175.7 + 5.0.0-preview.3.20175.7 + 5.0.0-preview.3.20175.7 + 5.0.0-preview.3.20175.7 + 5.0.0-preview.3.20175.7 + 5.0.0-preview.3.20175.7 + 5.0.0-preview.3.20175.7 + 5.0.0-preview.3.20175.7 + 5.0.0-preview.3.20175.7 + 5.0.0-preview.3.20175.7 + 5.0.0-preview.3.20175.7 + 5.0.0-preview.3.20175.7 + 5.0.0-preview.3.20175.7 + 5.0.0-preview.3.20175.7 + 5.0.0-preview.3.20175.7 + 5.0.0-preview.3.20175.7 + 5.0.0-preview.3.20175.7 + 5.0.0-preview.3.20175.7 + + 5.0.0-preview.3.20176.7 + 5.0.0-preview.3.20176.7 + 5.0.0-preview.3.20176.7 + 5.0.0-preview.3.20176.7 + 5.0.0-preview.3.20176.7 + 5.0.0-preview.3.20176.7 + 5.0.0-preview.3.20176.7 + + 5.0.0-preview.3.20176.1 + 5.0.0-preview.3.20176.1 + 5.0.0-preview.3.20176.1 + 5.0.0-preview.3.20176.1 + 4.7.0 + 2.0.3 4.5.0 4.4.0 0.3.0-alpha.19317.1 4.3.0 4.3.2 - 4.5.2 + 4.3.0 + 4.5.3 + 4.5.0 1.10.0 5.2.6 3.1.1-preview4.19614.4 - 2.3.2 - 10.0.1 + 1.0.0 15.8.166 + 1.2.0 15.8.166 1.2.6 15.8.166 3.0.0 3.0.0 3.0.0 - 5.8.4 - 5.8.4 3.19.8 5.5.0 5.5.0 @@ -231,19 +211,24 @@ 2.1.1 2.2.0 + 3.1.3 0.9.9 - 0.10.13 + 0.12.0 4.2.1 + 2.3.0 4.2.1 - 3.8.0 + 3.10.0 2.27.0 + 2.27.0 + 2.27.0 + 2.27.0 3.0.0 3.0.0 3.0.0 3.0.0 3.0.0 - 1.7.3.7 + 2.1.90 4.10.0 0.10.1 1.0.2 diff --git a/eng/Workarounds.props b/eng/Workarounds.props index 3ed1473baf..eeb9004c6d 100644 --- a/eng/Workarounds.props +++ b/eng/Workarounds.props @@ -21,7 +21,7 @@ $(NoWarn);NU5131 - + $(NoWarn);NU5048 diff --git a/eng/Workarounds.targets b/eng/Workarounds.targets index 8f51713dc0..0e2159fd3c 100644 --- a/eng/Workarounds.targets +++ b/eng/Workarounds.targets @@ -1,8 +1,8 @@ - + - 3.1 + 5.0 @@ -16,7 +16,7 @@ @@ -25,12 +25,7 @@ - - - $(MicrosoftNETCorePlatformsPackageVersion) - - - + + + + + + + diff --git a/eng/common/CheckSymbols.ps1 b/eng/common/CheckSymbols.ps1 deleted file mode 100644 index b8d84607b8..0000000000 --- a/eng/common/CheckSymbols.ps1 +++ /dev/null @@ -1,158 +0,0 @@ -param( - [Parameter(Mandatory=$true)][string] $InputPath, # Full path to directory where NuGet packages to be checked are stored - [Parameter(Mandatory=$true)][string] $ExtractPath, # Full path to directory where the packages will be extracted during validation - [Parameter(Mandatory=$true)][string] $SymbolToolPath # Full path to directory where dotnet symbol-tool was installed -) - -Add-Type -AssemblyName System.IO.Compression.FileSystem - -function FirstMatchingSymbolDescriptionOrDefault { - param( - [string] $FullPath, # Full path to the module that has to be checked - [string] $TargetServerParam, # Parameter to pass to `Symbol Tool` indicating the server to lookup for symbols - [string] $SymbolsPath - ) - - $FileName = [System.IO.Path]::GetFileName($FullPath) - $Extension = [System.IO.Path]::GetExtension($FullPath) - - # Those below are potential symbol files that the `dotnet symbol` might - # return. Which one will be returned depend on the type of file we are - # checking and which type of file was uploaded. - - # The file itself is returned - $SymbolPath = $SymbolsPath + "\" + $FileName - - # PDB file for the module - $PdbPath = $SymbolPath.Replace($Extension, ".pdb") - - # PDB file for R2R module (created by crossgen) - $NGenPdb = $SymbolPath.Replace($Extension, ".ni.pdb") - - # DBG file for a .so library - $SODbg = $SymbolPath.Replace($Extension, ".so.dbg") - - # DWARF file for a .dylib - $DylibDwarf = $SymbolPath.Replace($Extension, ".dylib.dwarf") - - .\dotnet-symbol.exe --symbols --modules --windows-pdbs $TargetServerParam $FullPath -o $SymbolsPath | Out-Null - - if (Test-Path $PdbPath) { - return "PDB" - } - elseif (Test-Path $NGenPdb) { - return "NGen PDB" - } - elseif (Test-Path $SODbg) { - return "DBG for SO" - } - elseif (Test-Path $DylibDwarf) { - return "Dwarf for Dylib" - } - elseif (Test-Path $SymbolPath) { - return "Module" - } - else { - return $null - } -} - -function CountMissingSymbols { - param( - [string] $PackagePath # Path to a NuGet package - ) - - # Ensure input file exist - if (!(Test-Path $PackagePath)) { - throw "Input file does not exist: $PackagePath" - } - - # Extensions for which we'll look for symbols - $RelevantExtensions = @(".dll", ".exe", ".so", ".dylib") - - # How many files are missing symbol information - $MissingSymbols = 0 - - $PackageId = [System.IO.Path]::GetFileNameWithoutExtension($PackagePath) - $PackageGuid = New-Guid - $ExtractPath = Join-Path -Path $ExtractPath -ChildPath $PackageGuid - $SymbolsPath = Join-Path -Path $ExtractPath -ChildPath "Symbols" - - [System.IO.Compression.ZipFile]::ExtractToDirectory($PackagePath, $ExtractPath) - - # Makes easier to reference `symbol tool` - Push-Location $SymbolToolPath - - Get-ChildItem -Recurse $ExtractPath | - Where-Object {$RelevantExtensions -contains $_.Extension} | - ForEach-Object { - if ($_.FullName -Match "\\ref\\") { - Write-Host "`t Ignoring reference assembly file" $_.FullName - return - } - - $SymbolsOnMSDL = FirstMatchingSymbolDescriptionOrDefault $_.FullName "--microsoft-symbol-server" $SymbolsPath - $SymbolsOnSymWeb = FirstMatchingSymbolDescriptionOrDefault $_.FullName "--internal-server" $SymbolsPath - - Write-Host -NoNewLine "`t Checking file" $_.FullName "... " - - if ($SymbolsOnMSDL -ne $null -and $SymbolsOnSymWeb -ne $null) { - Write-Host "Symbols found on MSDL (" $SymbolsOnMSDL ") and SymWeb (" $SymbolsOnSymWeb ")" - } - else { - $MissingSymbols++ - - if ($SymbolsOnMSDL -eq $null -and $SymbolsOnSymWeb -eq $null) { - Write-Host "No symbols found on MSDL or SymWeb!" - } - else { - if ($SymbolsOnMSDL -eq $null) { - Write-Host "No symbols found on MSDL!" - } - else { - Write-Host "No symbols found on SymWeb!" - } - } - } - } - - Pop-Location - - return $MissingSymbols -} - -function CheckSymbolsAvailable { - if (Test-Path $ExtractPath) { - Remove-Item $ExtractPath -Force -Recurse -ErrorAction SilentlyContinue - } - - Get-ChildItem "$InputPath\*.nupkg" | - ForEach-Object { - $FileName = $_.Name - - # These packages from Arcade-Services include some native libraries that - # our current symbol uploader can't handle. Below is a workaround until - # we get issue: https://github.com/dotnet/arcade/issues/2457 sorted. - if ($FileName -Match "Microsoft\.DotNet\.Darc\.") { - Write-Host "Ignoring Arcade-services file: $FileName" - Write-Host - return - } - elseif ($FileName -Match "Microsoft\.DotNet\.Maestro\.Tasks\.") { - Write-Host "Ignoring Arcade-services file: $FileName" - Write-Host - return - } - - Write-Host "Validating $FileName " - $Status = CountMissingSymbols "$InputPath\$FileName" - - if ($Status -ne 0) { - Write-Error "Missing symbols for $Status modules in the package $FileName" - } - - Write-Host - } -} - -CheckSymbolsAvailable diff --git a/eng/common/PublishToPackageFeed.proj b/eng/common/PublishToPackageFeed.proj deleted file mode 100644 index a1b1333723..0000000000 --- a/eng/common/PublishToPackageFeed.proj +++ /dev/null @@ -1,83 +0,0 @@ - - - - - - netcoreapp2.1 - - - - - - - - - - - - - - - - - - - - - - - - - https://dotnetfeed.blob.core.windows.net/dotnet-core/index.json - https://dotnetfeed.blob.core.windows.net/arcade-validation/index.json - https://dotnetfeed.blob.core.windows.net/aspnet-aspnetcore/index.json - https://dotnetfeed.blob.core.windows.net/aspnet-aspnetcore-tooling/index.json - https://dotnetfeed.blob.core.windows.net/aspnet-entityframeworkcore/index.json - https://dotnetfeed.blob.core.windows.net/aspnet-extensions/index.json - https://dotnetfeed.blob.core.windows.net/dotnet-coreclr/index.json - https://dotnetfeed.blob.core.windows.net/dotnet-sdk/index.json - https://dotnetfeed.blob.core.windows.net/dotnet-tools-internal/index.json - https://dotnetfeed.blob.core.windows.net/dotnet-toolset/index.json - https://dotnetfeed.blob.core.windows.net/dotnet-windowsdesktop/index.json - https://dotnetfeed.blob.core.windows.net/nuget-nugetclient/index.json - https://dotnetfeed.blob.core.windows.net/aspnet-entityframework6/index.json - https://dotnetfeed.blob.core.windows.net/aspnet-blazor/index.json - - - - - - - - - - - - diff --git a/eng/common/PublishToSymbolServers.proj b/eng/common/PublishToSymbolServers.proj deleted file mode 100644 index 5d55e312b0..0000000000 --- a/eng/common/PublishToSymbolServers.proj +++ /dev/null @@ -1,82 +0,0 @@ - - - - - - netcoreapp2.1 - - - - - - - - - - - - - - - - 3650 - true - false - - - - - - - - - - - - - - - - - diff --git a/eng/common/SetupNugetSources.ps1 b/eng/common/SetupNugetSources.ps1 index a8b5280d9d..a5a1e711d7 100644 --- a/eng/common/SetupNugetSources.ps1 +++ b/eng/common/SetupNugetSources.ps1 @@ -83,7 +83,7 @@ function AddCredential($creds, $source, $username, $password) { $passwordElement.SetAttribute("value", $Password) } -function InsertMaestroPrivateFeedCredentials($Sources, $Creds, $Username, $Password) { +function InsertMaestroPrivateFeedCredentials($Sources, $Creds, $Password) { $maestroPrivateSources = $Sources.SelectNodes("add[contains(@key,'darc-int')]") Write-Host "Inserting credentials for $($maestroPrivateSources.Count) Maestro's private feeds." @@ -123,21 +123,19 @@ if ($creds -eq $null) { $doc.DocumentElement.AppendChild($creds) | Out-Null } -$userName = "dn-bot" - # Insert credential nodes for Maestro's private feeds -InsertMaestroPrivateFeedCredentials -Sources $sources -Creds $creds -Username $userName -Password $Password +InsertMaestroPrivateFeedCredentials -Sources $sources -Creds $creds -Password $Password $dotnet3Source = $sources.SelectSingleNode("add[@key='dotnet3']") if ($dotnet3Source -ne $null) { - AddPackageSource -Sources $sources -SourceName "dotnet3-internal" -SourceEndPoint "https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3-internal/nuget/v2" -Creds $creds -Username $userName -Password $Password - AddPackageSource -Sources $sources -SourceName "dotnet3-internal-transport" -SourceEndPoint "https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3-internal-transport/nuget/v2" -Creds $creds -Username $userName -Password $Password + AddPackageSource -Sources $sources -SourceName "dotnet3-internal" -SourceEndPoint "https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3-internal/nuget/v2" -Creds $creds -Username "dn-bot" -Password $Password + AddPackageSource -Sources $sources -SourceName "dotnet3-internal-transport" -SourceEndPoint "https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3-internal-transport/nuget/v2" -Creds $creds -Username "dn-bot" -Password $Password } $dotnet31Source = $sources.SelectSingleNode("add[@key='dotnet3.1']") if ($dotnet31Source -ne $null) { - AddPackageSource -Sources $sources -SourceName "dotnet3.1-internal" -SourceEndPoint "https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal/nuget/v2" -Creds $creds -Username $userName -Password $Password - AddPackageSource -Sources $sources -SourceName "dotnet3.1-internal-transport" -SourceEndPoint "https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal-transport/nuget/v2" -Creds $creds -Username $userName -Password $Password + AddPackageSource -Sources $sources -SourceName "dotnet3.1-internal" -SourceEndPoint "https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal/nuget/v2" -Creds $creds -Username "dn-bot" -Password $Password + AddPackageSource -Sources $sources -SourceName "dotnet3.1-internal-transport" -SourceEndPoint "https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal-transport/nuget/v2" -Creds $creds -Username "dn-bot" -Password $Password } -$doc.Save($filename) \ No newline at end of file +$doc.Save($filename) diff --git a/eng/common/SetupNugetSources.sh b/eng/common/SetupNugetSources.sh index 4ebb1e5a44..7d6fef27fe 100644 --- a/eng/common/SetupNugetSources.sh +++ b/eng/common/SetupNugetSources.sh @@ -146,4 +146,4 @@ for FeedName in ${PackageSources[@]} ; do sed -i.bak "s|$PackageSourceCredentialsNodeFooter|$NewCredential${NL}$PackageSourceCredentialsNodeFooter|" $ConfigFile fi -done \ No newline at end of file +done diff --git a/eng/common/SigningValidation.proj b/eng/common/SigningValidation.proj deleted file mode 100644 index 3d0ac80af3..0000000000 --- a/eng/common/SigningValidation.proj +++ /dev/null @@ -1,83 +0,0 @@ - - - - - - netcoreapp2.1 - - - - - - - - $(NuGetPackageRoot)Microsoft.DotNet.SignCheck\$(SignCheckVersion)\tools\Microsoft.DotNet.SignCheck.exe - - $(PackageBasePath) - signcheck.log - signcheck.errors.log - signcheck.exclusions.txt - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/eng/common/SourceLinkValidation.ps1 b/eng/common/SourceLinkValidation.ps1 deleted file mode 100644 index cb2d28cb99..0000000000 --- a/eng/common/SourceLinkValidation.ps1 +++ /dev/null @@ -1,184 +0,0 @@ -param( - [Parameter(Mandatory=$true)][string] $InputPath, # Full path to directory where Symbols.NuGet packages to be checked are stored - [Parameter(Mandatory=$true)][string] $ExtractPath, # Full path to directory where the packages will be extracted during validation - [Parameter(Mandatory=$true)][string] $SourceLinkToolPath, # Full path to directory where dotnet SourceLink CLI was installed - [Parameter(Mandatory=$true)][string] $GHRepoName, # GitHub name of the repo including the Org. E.g., dotnet/arcade - [Parameter(Mandatory=$true)][string] $GHCommit # GitHub commit SHA used to build the packages -) - -# Cache/HashMap (File -> Exist flag) used to consult whether a file exist -# in the repository at a specific commit point. This is populated by inserting -# all files present in the repo at a specific commit point. -$global:RepoFiles = @{} - -$ValidatePackage = { - param( - [string] $PackagePath # Full path to a Symbols.NuGet package - ) - - # Ensure input file exist - if (!(Test-Path $PackagePath)) { - throw "Input file does not exist: $PackagePath" - } - - # Extensions for which we'll look for SourceLink information - # For now we'll only care about Portable & Embedded PDBs - $RelevantExtensions = @(".dll", ".exe", ".pdb") - - Write-Host -NoNewLine "Validating" ([System.IO.Path]::GetFileName($PackagePath)) "... " - - $PackageId = [System.IO.Path]::GetFileNameWithoutExtension($PackagePath) - $ExtractPath = Join-Path -Path $using:ExtractPath -ChildPath $PackageId - $FailedFiles = 0 - - Add-Type -AssemblyName System.IO.Compression.FileSystem - - [System.IO.Directory]::CreateDirectory($ExtractPath); - - $zip = [System.IO.Compression.ZipFile]::OpenRead($PackagePath) - - $zip.Entries | - Where-Object {$RelevantExtensions -contains [System.IO.Path]::GetExtension($_.Name)} | - ForEach-Object { - $FileName = $_.FullName - $Extension = [System.IO.Path]::GetExtension($_.Name) - $FakeName = -Join((New-Guid), $Extension) - $TargetFile = Join-Path -Path $ExtractPath -ChildPath $FakeName - - # We ignore resource DLLs - if ($FileName.EndsWith(".resources.dll")) { - return - } - - [System.IO.Compression.ZipFileExtensions]::ExtractToFile($_, $TargetFile, $true) - - $ValidateFile = { - param( - [string] $FullPath, # Full path to the module that has to be checked - [string] $RealPath, - [ref] $FailedFiles - ) - - # Makes easier to reference `sourcelink cli` - Push-Location $using:SourceLinkToolPath - - $SourceLinkInfos = .\sourcelink.exe print-urls $FullPath | Out-String - - if ($LASTEXITCODE -eq 0 -and -not ([string]::IsNullOrEmpty($SourceLinkInfos))) { - $NumFailedLinks = 0 - - # We only care about Http addresses - $Matches = (Select-String '(http[s]?)(:\/\/)([^\s,]+)' -Input $SourceLinkInfos -AllMatches).Matches - - if ($Matches.Count -ne 0) { - $Matches.Value | - ForEach-Object { - $Link = $_ - $CommitUrl = -Join("https://raw.githubusercontent.com/", $using:GHRepoName, "/", $using:GHCommit, "/") - $FilePath = $Link.Replace($CommitUrl, "") - $Status = 200 - $Cache = $using:RepoFiles - - if ( !($Cache.ContainsKey($FilePath)) ) { - try { - $Uri = $Link -as [System.URI] - - # Only GitHub links are valid - if ($Uri.AbsoluteURI -ne $null -and $Uri.Host -match "github") { - $Status = (Invoke-WebRequest -Uri $Link -UseBasicParsing -Method HEAD -TimeoutSec 5).StatusCode - } - else { - $Status = 0 - } - } - catch { - $Status = 0 - } - } - - if ($Status -ne 200) { - if ($NumFailedLinks -eq 0) { - if ($FailedFiles.Value -eq 0) { - Write-Host - } - - Write-Host "`tFile $RealPath has broken links:" - } - - Write-Host "`t`tFailed to retrieve $Link" - - $NumFailedLinks++ - } - } - } - - if ($NumFailedLinks -ne 0) { - $FailedFiles.value++ - $global:LASTEXITCODE = 1 - } - } - - Pop-Location - } - - &$ValidateFile $TargetFile $FileName ([ref]$FailedFiles) - } - - $zip.Dispose() - - if ($FailedFiles -eq 0) { - Write-Host "Passed." - } -} - -function ValidateSourceLinkLinks { - if (!($GHRepoName -Match "^[^\s\/]+/[^\s\/]+$")) { - Write-Host "GHRepoName should be in the format /" - $global:LASTEXITCODE = 1 - return - } - - if (!($GHCommit -Match "^[0-9a-fA-F]{40}$")) { - Write-Host "GHCommit should be a 40 chars hexadecimal string" - $global:LASTEXITCODE = 1 - return - } - - $RepoTreeURL = -Join("https://api.github.com/repos/", $GHRepoName, "/git/trees/", $GHCommit, "?recursive=1") - $CodeExtensions = @(".cs", ".vb", ".fs", ".fsi", ".fsx", ".fsscript") - - try { - # Retrieve the list of files in the repo at that particular commit point and store them in the RepoFiles hash - $Data = Invoke-WebRequest $RepoTreeURL | ConvertFrom-Json | Select-Object -ExpandProperty tree - - foreach ($file in $Data) { - $Extension = [System.IO.Path]::GetExtension($file.path) - - if ($CodeExtensions.Contains($Extension)) { - $RepoFiles[$file.path] = 1 - } - } - } - catch { - Write-Host "Problems downloading the list of files from the repo. Url used: $RepoTreeURL" - $global:LASTEXITCODE = 1 - return - } - - if (Test-Path $ExtractPath) { - Remove-Item $ExtractPath -Force -Recurse -ErrorAction SilentlyContinue - } - - # Process each NuGet package in parallel - $Jobs = @() - Get-ChildItem "$InputPath\*.symbols.nupkg" | - ForEach-Object { - $Jobs += Start-Job -ScriptBlock $ValidatePackage -ArgumentList $_.FullName - } - - foreach ($Job in $Jobs) { - Wait-Job -Id $Job.Id | Receive-Job - } -} - -Measure-Command { ValidateSourceLinkLinks } diff --git a/eng/common/build.ps1 b/eng/common/build.ps1 index e001ccb481..813d440d2a 100644 --- a/eng/common/build.ps1 +++ b/eng/common/build.ps1 @@ -18,6 +18,7 @@ Param( [switch] $sign, [switch] $pack, [switch] $publish, + [switch] $clean, [switch][Alias('bl')]$binaryLog, [switch] $ci, [switch] $prepareMachine, @@ -25,49 +26,55 @@ Param( [Parameter(ValueFromRemainingArguments=$true)][String[]]$properties ) -. $PSScriptRoot\tools.ps1 - -function Print-Usage() { - Write-Host "Common settings:" - Write-Host " -configuration Build configuration: 'Debug' or 'Release' (short: -c)" - Write-Host " -platform Platform configuration: 'x86', 'x64' or any valid Platform value to pass to msbuild" - Write-Host " -verbosity Msbuild verbosity: q[uiet], m[inimal], n[ormal], d[etailed], and diag[nostic] (short: -v)" - Write-Host " -binaryLog Output binary log (short: -bl)" - Write-Host " -help Print help and exit" - Write-Host "" - - Write-Host "Actions:" - Write-Host " -restore Restore dependencies (short: -r)" - Write-Host " -build Build solution (short: -b)" - Write-Host " -rebuild Rebuild solution" - Write-Host " -deploy Deploy built VSIXes" - Write-Host " -deployDeps Deploy dependencies (e.g. VSIXes for integration tests)" - Write-Host " -test Run all unit tests in the solution (short: -t)" - Write-Host " -integrationTest Run all integration tests in the solution" - Write-Host " -performanceTest Run all performance tests in the solution" - Write-Host " -pack Package build outputs into NuGet packages and Willow components" - Write-Host " -sign Sign build outputs" - Write-Host " -publish Publish artifacts (e.g. symbols)" - Write-Host "" - - Write-Host "Advanced settings:" - Write-Host " -projects Semi-colon delimited list of sln/proj's to build. Globbing is supported (*.sln)" - Write-Host " -ci Set when running on CI server" - Write-Host " -prepareMachine Prepare machine for CI run, clean up processes after build" - Write-Host " -warnAsError Sets warnaserror msbuild parameter ('true' or 'false')" - Write-Host " -msbuildEngine Msbuild engine to use to run build ('dotnet', 'vs', or unspecified)." - Write-Host "" - - Write-Host "Command line arguments not listed above are passed thru to msbuild." - Write-Host "The above arguments can be shortened as much as to be unambiguous (e.g. -co for configuration, -t for test, etc.)." +# Unset 'Platform' environment variable to avoid unwanted collision in InstallDotNetCore.targets file +# some computer has this env var defined (e.g. Some HP) +if($env:Platform) { + $env:Platform="" } +function Print-Usage() { + Write-Host "Common settings:" + Write-Host " -configuration Build configuration: 'Debug' or 'Release' (short: -c)" + Write-Host " -platform Platform configuration: 'x86', 'x64' or any valid Platform value to pass to msbuild" + Write-Host " -verbosity Msbuild verbosity: q[uiet], m[inimal], n[ormal], d[etailed], and diag[nostic] (short: -v)" + Write-Host " -binaryLog Output binary log (short: -bl)" + Write-Host " -help Print help and exit" + Write-Host "" + + Write-Host "Actions:" + Write-Host " -restore Restore dependencies (short: -r)" + Write-Host " -build Build solution (short: -b)" + Write-Host " -rebuild Rebuild solution" + Write-Host " -deploy Deploy built VSIXes" + Write-Host " -deployDeps Deploy dependencies (e.g. VSIXes for integration tests)" + Write-Host " -test Run all unit tests in the solution (short: -t)" + Write-Host " -integrationTest Run all integration tests in the solution" + Write-Host " -performanceTest Run all performance tests in the solution" + Write-Host " -pack Package build outputs into NuGet packages and Willow components" + Write-Host " -sign Sign build outputs" + Write-Host " -publish Publish artifacts (e.g. symbols)" + Write-Host " -clean Clean the solution" + Write-Host "" + + Write-Host "Advanced settings:" + Write-Host " -projects Semi-colon delimited list of sln/proj's to build. Globbing is supported (*.sln)" + Write-Host " -ci Set when running on CI server" + Write-Host " -prepareMachine Prepare machine for CI run, clean up processes after build" + Write-Host " -warnAsError Sets warnaserror msbuild parameter ('true' or 'false')" + Write-Host " -msbuildEngine Msbuild engine to use to run build ('dotnet', 'vs', or unspecified)." + Write-Host "" + + Write-Host "Command line arguments not listed above are passed thru to msbuild." + Write-Host "The above arguments can be shortened as much as to be unambiguous (e.g. -co for configuration, -t for test, etc.)." +} + +. $PSScriptRoot\tools.ps1 function InitializeCustomToolset { if (-not $restore) { return } - $script = Join-Path $EngRoot "restore-toolset.ps1" + $script = Join-Path $EngRoot 'restore-toolset.ps1' if (Test-Path $script) { . $script @@ -78,8 +85,8 @@ function Build { $toolsetBuildProj = InitializeToolset InitializeCustomToolset - $bl = if ($binaryLog) { "/bl:" + (Join-Path $LogDir "Build.binlog") } else { "" } - $platformArg = if ($platform) { "/p:Platform=$platform" } else { "" } + $bl = if ($binaryLog) { '/bl:' + (Join-Path $LogDir 'Build.binlog') } else { '' } + $platformArg = if ($platform) { "/p:Platform=$platform" } else { '' } if ($projects) { # Re-assign properties to a new variable because PowerShell doesn't let us append properties directly for unclear reasons. @@ -113,7 +120,15 @@ function Build { } try { - if ($help -or (($null -ne $properties) -and ($properties.Contains("/help") -or $properties.Contains("/?")))) { + if ($clean) { + if (Test-Path $ArtifactsDir) { + Remove-Item -Recurse -Force $ArtifactsDir + Write-Host 'Artifacts directory deleted.' + } + exit 0 + } + + if ($help -or (($null -ne $properties) -and ($properties.Contains('/help') -or $properties.Contains('/?')))) { Print-Usage exit 0 } @@ -123,14 +138,7 @@ try { $nodeReuse = $false } - # Import custom tools configuration, if present in the repo. - # Note: Import in global scope so that the script set top-level variables without qualification. - $configureToolsetScript = Join-Path $EngRoot "configure-toolset.ps1" - if (Test-Path $configureToolsetScript) { - . $configureToolsetScript - } - - if (($restore) -and ($null -eq $env:DisableNativeToolsetInstalls)) { + if ($restore) { InitializeNativeTools } @@ -138,7 +146,7 @@ try { } catch { Write-Host $_.ScriptStackTrace - Write-PipelineTelemetryError -Category "InitializeToolset" -Message $_ + Write-PipelineTelemetryError -Category 'InitializeToolset' -Message $_ ExitWithExitCode 1 } diff --git a/eng/common/build.sh b/eng/common/build.sh index 6236fc4d38..36f9aa0462 100755 --- a/eng/common/build.sh +++ b/eng/common/build.sh @@ -26,6 +26,7 @@ usage() echo " --pack Package build outputs into NuGet packages and Willow components" echo " --sign Sign build outputs" echo " --publish Publish artifacts (e.g. symbols)" + echo " --clean Clean the solution" echo "" echo "Advanced settings:" @@ -62,6 +63,7 @@ publish=false sign=false public=false ci=false +clean=false warn_as_error=true node_reuse=true @@ -82,6 +84,9 @@ while [[ $# > 0 ]]; do usage exit 0 ;; + -clean) + clean=true + ;; -configuration|-c) configuration=$2 shift @@ -196,20 +201,15 @@ function Build { ExitWithExitCode 0 } -# Import custom tools configuration, if present in the repo. -configure_toolset_script="$eng_root/configure-toolset.sh" -if [[ -a "$configure_toolset_script" ]]; then - . "$configure_toolset_script" +if [[ "$clean" == true ]]; then + if [ -d "$artifacts_dir" ]; then + rm -rf $artifacts_dir + echo "Artifacts directory deleted." + fi + exit 0 fi -# TODO: https://github.com/dotnet/arcade/issues/1468 -# Temporary workaround to avoid breaking change. -# Remove once repos are updated. -if [[ -n "${useInstalledDotNetCli:-}" ]]; then - use_installed_dotnet_cli="$useInstalledDotNetCli" -fi - -if [[ "$restore" == true && -z ${DisableNativeToolsetInstalls:-} ]]; then +if [[ "$restore" == true ]]; then InitializeNativeTools fi diff --git a/eng/common/cross/android/arm/toolchain.cmake b/eng/common/cross/android/arm/toolchain.cmake deleted file mode 100644 index a7e1c73501..0000000000 --- a/eng/common/cross/android/arm/toolchain.cmake +++ /dev/null @@ -1,41 +0,0 @@ -set(CROSS_NDK_TOOLCHAIN $ENV{ROOTFS_DIR}/../) -set(CROSS_ROOTFS ${CROSS_NDK_TOOLCHAIN}/sysroot) -set(CLR_CMAKE_PLATFORM_ANDROID "Android") - -set(CMAKE_SYSTEM_NAME Linux) -set(CMAKE_SYSTEM_VERSION 1) -set(CMAKE_SYSTEM_PROCESSOR arm) - -## Specify the toolchain -set(TOOLCHAIN "arm-linux-androideabi") -set(CMAKE_PREFIX_PATH ${CROSS_NDK_TOOLCHAIN}) -set(TOOLCHAIN_PREFIX ${TOOLCHAIN}-) - -find_program(CMAKE_C_COMPILER ${TOOLCHAIN_PREFIX}clang) -find_program(CMAKE_CXX_COMPILER ${TOOLCHAIN_PREFIX}clang++) -find_program(CMAKE_ASM_COMPILER ${TOOLCHAIN_PREFIX}clang) -find_program(CMAKE_AR ${TOOLCHAIN_PREFIX}ar) -find_program(CMAKE_LD ${TOOLCHAIN_PREFIX}ar) -find_program(CMAKE_OBJCOPY ${TOOLCHAIN_PREFIX}objcopy) -find_program(CMAKE_OBJDUMP ${TOOLCHAIN_PREFIX}objdump) - -add_compile_options(--sysroot=${CROSS_ROOTFS}) -add_compile_options(-fPIE) -add_compile_options(-mfloat-abi=soft) -include_directories(SYSTEM ${CROSS_NDK_TOOLCHAIN}/include/c++/4.9.x/) -include_directories(SYSTEM ${CROSS_NDK_TOOLCHAIN}/include/c++/4.9.x/arm-linux-androideabi/) - -set(CROSS_LINK_FLAGS "${CROSS_LINK_FLAGS} -B ${CROSS_ROOTFS}/usr/lib/gcc/${TOOLCHAIN}") -set(CROSS_LINK_FLAGS "${CROSS_LINK_FLAGS} -L${CROSS_ROOTFS}/lib/${TOOLCHAIN}") -set(CROSS_LINK_FLAGS "${CROSS_LINK_FLAGS} --sysroot=${CROSS_ROOTFS}") -set(CROSS_LINK_FLAGS "${CROSS_LINK_FLAGS} -fPIE -pie") - -set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${CROSS_LINK_FLAGS}" CACHE STRING "" FORCE) -set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${CROSS_LINK_FLAGS}" CACHE STRING "" FORCE) -set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} ${CROSS_LINK_FLAGS}" CACHE STRING "" FORCE) - -set(CMAKE_FIND_ROOT_PATH "${CROSS_ROOTFS}") -set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) -set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) -set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) -set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) diff --git a/eng/common/cross/android/arm64/toolchain.cmake b/eng/common/cross/android/arm64/toolchain.cmake deleted file mode 100644 index 29415899c1..0000000000 --- a/eng/common/cross/android/arm64/toolchain.cmake +++ /dev/null @@ -1,42 +0,0 @@ -set(CROSS_NDK_TOOLCHAIN $ENV{ROOTFS_DIR}/../) -set(CROSS_ROOTFS ${CROSS_NDK_TOOLCHAIN}/sysroot) -set(CLR_CMAKE_PLATFORM_ANDROID "Android") - -set(CMAKE_SYSTEM_NAME Linux) -set(CMAKE_SYSTEM_VERSION 1) -set(CMAKE_SYSTEM_PROCESSOR aarch64) - -## Specify the toolchain -set(TOOLCHAIN "aarch64-linux-android") -set(CMAKE_PREFIX_PATH ${CROSS_NDK_TOOLCHAIN}) -set(TOOLCHAIN_PREFIX ${TOOLCHAIN}-) - -find_program(CMAKE_C_COMPILER ${TOOLCHAIN_PREFIX}clang) -find_program(CMAKE_CXX_COMPILER ${TOOLCHAIN_PREFIX}clang++) -find_program(CMAKE_ASM_COMPILER ${TOOLCHAIN_PREFIX}clang) -find_program(CMAKE_AR ${TOOLCHAIN_PREFIX}ar) -find_program(CMAKE_LD ${TOOLCHAIN_PREFIX}ar) -find_program(CMAKE_OBJCOPY ${TOOLCHAIN_PREFIX}objcopy) -find_program(CMAKE_OBJDUMP ${TOOLCHAIN_PREFIX}objdump) - -add_compile_options(--sysroot=${CROSS_ROOTFS}) -add_compile_options(-fPIE) - -## Needed for Android or bionic specific conditionals -add_compile_options(-D__ANDROID__) -add_compile_options(-D__BIONIC__) - -set(CROSS_LINK_FLAGS "${CROSS_LINK_FLAGS} -B ${CROSS_ROOTFS}/usr/lib/gcc/${TOOLCHAIN}") -set(CROSS_LINK_FLAGS "${CROSS_LINK_FLAGS} -L${CROSS_ROOTFS}/lib/${TOOLCHAIN}") -set(CROSS_LINK_FLAGS "${CROSS_LINK_FLAGS} --sysroot=${CROSS_ROOTFS}") -set(CROSS_LINK_FLAGS "${CROSS_LINK_FLAGS} -fPIE -pie") - -set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${CROSS_LINK_FLAGS}" CACHE STRING "" FORCE) -set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${CROSS_LINK_FLAGS}" CACHE STRING "" FORCE) -set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} ${CROSS_LINK_FLAGS}" CACHE STRING "" FORCE) - -set(CMAKE_FIND_ROOT_PATH "${CROSS_ROOTFS}") -set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) -set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) -set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) -set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) diff --git a/eng/common/cross/build-android-rootfs.sh b/eng/common/cross/build-android-rootfs.sh index adceda877a..e7f12edb56 100755 --- a/eng/common/cross/build-android-rootfs.sh +++ b/eng/common/cross/build-android-rootfs.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash set -e -__NDK_Version=r14 +__NDK_Version=r21 usage() { @@ -16,11 +16,11 @@ usage() echo. echo "By default, the NDK will be downloaded into the cross/android-rootfs/android-ndk-$__NDK_Version directory. If you already have an NDK installation," echo "you can set the NDK_DIR environment variable to have this script use that installation of the NDK." - echo "By default, this script will generate a file, android_platform, in the root of the ROOTFS_DIR directory that contains the RID for the supported and tested Android build: android.21-arm64. This file is to replace '/etc/os-release', which is not available for Android." + echo "By default, this script will generate a file, android_platform, in the root of the ROOTFS_DIR directory that contains the RID for the supported and tested Android build: android.28-arm64. This file is to replace '/etc/os-release', which is not available for Android." exit 1 } -__ApiLevel=21 # The minimum platform for arm64 is API level 21 +__ApiLevel=28 # The minimum platform for arm64 is API level 21 but the minimum version that support glob(3) is 28. See $ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/include/glob.h __BuildArch=arm64 __AndroidArch=aarch64 __AndroidToolchain=aarch64-linux-android @@ -53,13 +53,20 @@ for i in "$@" done # Obtain the location of the bash script to figure out where the root of the repo is. -__CrossDir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +__ScriptBaseDir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -__Android_Cross_Dir="$__CrossDir/android-rootfs" -__NDK_Dir="$__Android_Cross_Dir/android-ndk-$__NDK_Version" -__libunwind_Dir="$__Android_Cross_Dir/libunwind" -__lldb_Dir="$__Android_Cross_Dir/lldb" -__ToolchainDir="$__Android_Cross_Dir/toolchain/$__BuildArch" +__CrossDir="$__ScriptBaseDir/../../../.tools/android-rootfs" + +if [[ ! -f "$__CrossDir" ]]; then + mkdir -p "$__CrossDir" +fi + +# Resolve absolute path to avoid `../` in build logs +__CrossDir="$( cd "$__CrossDir" && pwd )" + +__NDK_Dir="$__CrossDir/android-ndk-$__NDK_Version" +__lldb_Dir="$__CrossDir/lldb" +__ToolchainDir="$__CrossDir/android-ndk-$__NDK_Version" if [[ -n "$TOOLCHAIN_DIR" ]]; then __ToolchainDir=$TOOLCHAIN_DIR @@ -78,60 +85,47 @@ echo "Target Toolchain location: $__ToolchainDir" if [ ! -d $__NDK_Dir ]; then echo Downloading the NDK into $__NDK_Dir mkdir -p $__NDK_Dir - wget -nv -nc --show-progress https://dl.google.com/android/repository/android-ndk-$__NDK_Version-linux-x86_64.zip -O $__Android_Cross_Dir/android-ndk-$__NDK_Version-linux-x86_64.zip - unzip -q $__Android_Cross_Dir/android-ndk-$__NDK_Version-linux-x86_64.zip -d $__Android_Cross_Dir + wget -q --progress=bar:force:noscroll --show-progress https://dl.google.com/android/repository/android-ndk-$__NDK_Version-linux-x86_64.zip -O $__CrossDir/android-ndk-$__NDK_Version-linux-x86_64.zip + unzip -q $__CrossDir/android-ndk-$__NDK_Version-linux-x86_64.zip -d $__CrossDir fi if [ ! -d $__lldb_Dir ]; then mkdir -p $__lldb_Dir echo Downloading LLDB into $__lldb_Dir - wget -nv -nc --show-progress https://dl.google.com/android/repository/lldb-2.3.3614996-linux-x86_64.zip -O $__Android_Cross_Dir/lldb-2.3.3614996-linux-x86_64.zip - unzip -q $__Android_Cross_Dir/lldb-2.3.3614996-linux-x86_64.zip -d $__lldb_Dir + wget -q --progress=bar:force:noscroll --show-progress https://dl.google.com/android/repository/lldb-2.3.3614996-linux-x86_64.zip -O $__CrossDir/lldb-2.3.3614996-linux-x86_64.zip + unzip -q $__CrossDir/lldb-2.3.3614996-linux-x86_64.zip -d $__lldb_Dir fi -# Create the RootFS for both arm64 as well as aarch -rm -rf $__Android_Cross_Dir/toolchain +echo "Download dependencies..." +__TmpDir=$__CrossDir/tmp/$__BuildArch/ +mkdir -p "$__TmpDir" -echo Generating the $__BuildArch toolchain -$__NDK_Dir/build/tools/make_standalone_toolchain.py --arch $__BuildArch --api $__ApiLevel --install-dir $__ToolchainDir +# combined dependencies for coreclr, installer and libraries +__AndroidPackages="libicu" +__AndroidPackages+=" libandroid-glob" +__AndroidPackages+=" liblzma" +__AndroidPackages+=" krb5" +__AndroidPackages+=" openssl" -# Install the required packages into the toolchain -# TODO: Add logic to get latest pkg version instead of specific version number -rm -rf $__Android_Cross_Dir/deb/ -rm -rf $__Android_Cross_Dir/tmp +for path in $(wget -qO- http://termux.net/dists/stable/main/binary-$__AndroidArch/Packages |\ + grep -A15 "Package: \(${__AndroidPackages// /\\|}\)" | grep -v "static\|tool" | grep Filename); do -mkdir -p $__Android_Cross_Dir/deb/ -mkdir -p $__Android_Cross_Dir/tmp/$arch/ -wget -nv -nc http://termux.net/dists/stable/main/binary-$__AndroidArch/libicu_60.2_$__AndroidArch.deb -O $__Android_Cross_Dir/deb/libicu_60.2_$__AndroidArch.deb -wget -nv -nc http://termux.net/dists/stable/main/binary-$__AndroidArch/libicu-dev_60.2_$__AndroidArch.deb -O $__Android_Cross_Dir/deb/libicu-dev_60.2_$__AndroidArch.deb + if [[ "$path" != "Filename:" ]]; then + echo "Working on: $path" + wget -qO- http://termux.net/$path | dpkg -x - "$__TmpDir" + fi +done -wget -nv -nc http://termux.net/dists/stable/main/binary-$__AndroidArch/libandroid-glob-dev_0.4_$__AndroidArch.deb -O $__Android_Cross_Dir/deb/libandroid-glob-dev_0.4_$__AndroidArch.deb -wget -nv -nc http://termux.net/dists/stable/main/binary-$__AndroidArch/libandroid-glob_0.4_$__AndroidArch.deb -O $__Android_Cross_Dir/deb/libandroid-glob_0.4_$__AndroidArch.deb -wget -nv -nc http://termux.net/dists/stable/main/binary-$__AndroidArch/libandroid-support-dev_22_$__AndroidArch.deb -O $__Android_Cross_Dir/deb/libandroid-support-dev_22_$__AndroidArch.deb -wget -nv -nc http://termux.net/dists/stable/main/binary-$__AndroidArch/libandroid-support_22_$__AndroidArch.deb -O $__Android_Cross_Dir/deb/libandroid-support_22_$__AndroidArch.deb -wget -nv -nc http://termux.net/dists/stable/main/binary-$__AndroidArch/liblzma-dev_5.2.3_$__AndroidArch.deb -O $__Android_Cross_Dir/deb/liblzma-dev_5.2.3_$__AndroidArch.deb -wget -nv -nc http://termux.net/dists/stable/main/binary-$__AndroidArch/liblzma_5.2.3_$__AndroidArch.deb -O $__Android_Cross_Dir/deb/liblzma_5.2.3_$__AndroidArch.deb -wget -nv -nc http://termux.net/dists/stable/main/binary-$__AndroidArch/libunwind-dev_1.2.20170304_$__AndroidArch.deb -O $__Android_Cross_Dir/deb/libunwind-dev_1.2.20170304_$__AndroidArch.deb -wget -nv -nc http://termux.net/dists/stable/main/binary-$__AndroidArch/libunwind_1.2.20170304_$__AndroidArch.deb -O $__Android_Cross_Dir/deb/libunwind_1.2.20170304_$__AndroidArch.deb - -echo Unpacking Termux packages -dpkg -x $__Android_Cross_Dir/deb/libicu_60.2_$__AndroidArch.deb $__Android_Cross_Dir/tmp/$__AndroidArch/ -dpkg -x $__Android_Cross_Dir/deb/libicu-dev_60.2_$__AndroidArch.deb $__Android_Cross_Dir/tmp/$__AndroidArch/ -dpkg -x $__Android_Cross_Dir/deb/libandroid-glob-dev_0.4_$__AndroidArch.deb $__Android_Cross_Dir/tmp/$__AndroidArch/ -dpkg -x $__Android_Cross_Dir/deb/libandroid-glob_0.4_$__AndroidArch.deb $__Android_Cross_Dir/tmp/$__AndroidArch/ -dpkg -x $__Android_Cross_Dir/deb/libandroid-support-dev_22_$__AndroidArch.deb $__Android_Cross_Dir/tmp/$__AndroidArch/ -dpkg -x $__Android_Cross_Dir/deb/libandroid-support_22_$__AndroidArch.deb $__Android_Cross_Dir/tmp/$__AndroidArch/ -dpkg -x $__Android_Cross_Dir/deb/liblzma-dev_5.2.3_$__AndroidArch.deb $__Android_Cross_Dir/tmp/$__AndroidArch/ -dpkg -x $__Android_Cross_Dir/deb/liblzma_5.2.3_$__AndroidArch.deb $__Android_Cross_Dir/tmp/$__AndroidArch/ -dpkg -x $__Android_Cross_Dir/deb/libunwind-dev_1.2.20170304_$__AndroidArch.deb $__Android_Cross_Dir/tmp/$__AndroidArch/ -dpkg -x $__Android_Cross_Dir/deb/libunwind_1.2.20170304_$__AndroidArch.deb $__Android_Cross_Dir/tmp/$__AndroidArch/ - -cp -R $__Android_Cross_Dir/tmp/$__AndroidArch/data/data/com.termux/files/usr/* $__ToolchainDir/sysroot/usr/ +cp -R "$__TmpDir/data/data/com.termux/files/usr/"* "$__ToolchainDir/sysroot/usr/" # Generate platform file for build.sh script to assign to __DistroRid echo "Generating platform file..." +echo "RID=android.${__ApiLevel}-${__BuildArch}" > $__ToolchainDir/sysroot/android_platform -echo "RID=android.21-arm64" > $__ToolchainDir/sysroot/android_platform -echo Now run: -echo CONFIG_DIR=\`realpath cross/android/$__BuildArch\` ROOTFS_DIR=\`realpath $__ToolchainDir/sysroot\` ./build.sh cross $__BuildArch skipgenerateversion skipnuget cmakeargs -DENABLE_LLDBPLUGIN=0 - +echo "Now to build coreclr, libraries and installers; run:" +echo ROOTFS_DIR=\$\(realpath $__ToolchainDir/sysroot\) ./build.sh --cross --arch $__BuildArch \ + --subsetCategory coreclr +echo ROOTFS_DIR=\$\(realpath $__ToolchainDir/sysroot\) ./build.sh --cross --arch $__BuildArch \ + --subsetCategory libraries +echo ROOTFS_DIR=\$\(realpath $__ToolchainDir/sysroot\) ./build.sh --cross --arch $__BuildArch \ + --subsetCategory installer diff --git a/eng/common/cross/build-rootfs.sh b/eng/common/cross/build-rootfs.sh index d7d5d7d5f4..a23f895ba1 100755 --- a/eng/common/cross/build-rootfs.sh +++ b/eng/common/cross/build-rootfs.sh @@ -25,8 +25,9 @@ __UbuntuPackages="build-essential" __AlpinePackages="alpine-base" __AlpinePackages+=" build-base" __AlpinePackages+=" linux-headers" -__AlpinePackages+=" lldb-dev" -__AlpinePackages+=" llvm-dev" +__AlpinePackagesEdgeTesting=" lldb-dev" +__AlpinePackagesEdgeMain=" llvm9-libs" +__AlpinePackagesEdgeMain+=" python3" # symlinks fixer __UbuntuPackages+=" symlinks" @@ -193,19 +194,29 @@ fi if [[ "$__LinuxCodeName" == "alpine" ]]; then __ApkToolsVersion=2.9.1 - __AlpineVersion=3.7 + __AlpineVersion=3.9 __ApkToolsDir=$(mktemp -d) wget https://github.com/alpinelinux/apk-tools/releases/download/v$__ApkToolsVersion/apk-tools-$__ApkToolsVersion-x86_64-linux.tar.gz -P $__ApkToolsDir tar -xf $__ApkToolsDir/apk-tools-$__ApkToolsVersion-x86_64-linux.tar.gz -C $__ApkToolsDir mkdir -p $__RootfsDir/usr/bin cp -v /usr/bin/qemu-$__QEMUArch-static $__RootfsDir/usr/bin + $__ApkToolsDir/apk-tools-$__ApkToolsVersion/apk \ -X http://dl-cdn.alpinelinux.org/alpine/v$__AlpineVersion/main \ -X http://dl-cdn.alpinelinux.org/alpine/v$__AlpineVersion/community \ - -X http://dl-cdn.alpinelinux.org/alpine/edge/testing \ - -X http://dl-cdn.alpinelinux.org/alpine/edge/main \ -U --allow-untrusted --root $__RootfsDir --arch $__AlpineArch --initdb \ add $__AlpinePackages + + $__ApkToolsDir/apk-tools-$__ApkToolsVersion/apk \ + -X http://dl-cdn.alpinelinux.org/alpine/edge/main \ + -U --allow-untrusted --root $__RootfsDir --arch $__AlpineArch --initdb \ + add $__AlpinePackagesEdgeMain + + $__ApkToolsDir/apk-tools-$__ApkToolsVersion/apk \ + -X http://dl-cdn.alpinelinux.org/alpine/edge/testing \ + -U --allow-untrusted --root $__RootfsDir --arch $__AlpineArch --initdb \ + add $__AlpinePackagesEdgeTesting + rm -r $__ApkToolsDir elif [[ -n $__LinuxCodeName ]]; then qemu-debootstrap --arch $__UbuntuArch $__LinuxCodeName $__RootfsDir $__UbuntuRepo diff --git a/eng/common/cross/toolchain.cmake b/eng/common/cross/toolchain.cmake index 071d411241..1823804da4 100644 --- a/eng/common/cross/toolchain.cmake +++ b/eng/common/cross/toolchain.cmake @@ -31,6 +31,10 @@ else() message(FATAL_ERROR "Arch is ${TARGET_ARCH_NAME}. Only armel, arm, arm64 and x86 are supported!") endif() +if(DEFINED ENV{TOOLCHAIN}) + set(TOOLCHAIN $ENV{TOOLCHAIN}) +endif() + # Specify include paths if(TARGET_ARCH_NAME STREQUAL "armel") if(DEFINED TIZEN_TOOLCHAIN) @@ -39,50 +43,47 @@ if(TARGET_ARCH_NAME STREQUAL "armel") endif() endif() -# add_compile_param - adds only new options without duplicates. -# arg0 - list with result options, arg1 - list with new options. -# arg2 - optional argument, quick summary string for optional using CACHE FORCE mode. -macro(add_compile_param) - if(NOT ${ARGC} MATCHES "^(2|3)$") - message(FATAL_ERROR "Wrong using add_compile_param! Two or three parameters must be given! See add_compile_param description.") - endif() - foreach(OPTION ${ARGV1}) - if(NOT ${ARGV0} MATCHES "${OPTION}($| )") - set(${ARGV0} "${${ARGV0}} ${OPTION}") - if(${ARGC} EQUAL "3") # CACHE FORCE mode - set(${ARGV0} "${${ARGV0}}" CACHE STRING "${ARGV2}" FORCE) - endif() +if("$ENV{__DistroRid}" MATCHES "android.*") + if(TARGET_ARCH_NAME STREQUAL "arm") + set(ANDROID_ABI armeabi-v7a) + elseif(TARGET_ARCH_NAME STREQUAL "arm64") + set(ANDROID_ABI arm64-v8a) endif() - endforeach() -endmacro() + + # extract platform number required by the NDK's toolchain + string(REGEX REPLACE ".*\\.([0-9]+)-.*" "\\1" ANDROID_PLATFORM "$ENV{__DistroRid}") + + set(ANDROID_TOOLCHAIN clang) + set(FEATURE_EVENT_TRACE 0) # disable event trace as there is no lttng-ust package in termux repository + set(CMAKE_SYSTEM_LIBRARY_PATH "${CROSS_ROOTFS}/usr/lib") + set(CMAKE_SYSTEM_INCLUDE_PATH "${CROSS_ROOTFS}/usr/include") + + # include official NDK toolchain script + include(${CROSS_ROOTFS}/../build/cmake/android.toolchain.cmake) +else() + set(CMAKE_SYSROOT "${CROSS_ROOTFS}") + + set(CMAKE_C_COMPILER_EXTERNAL_TOOLCHAIN "${CROSS_ROOTFS}/usr") + set(CMAKE_CXX_COMPILER_EXTERNAL_TOOLCHAIN "${CROSS_ROOTFS}/usr") + set(CMAKE_ASM_COMPILER_EXTERNAL_TOOLCHAIN "${CROSS_ROOTFS}/usr") +endif() # Specify link flags -add_compile_param(CROSS_LINK_FLAGS "--sysroot=${CROSS_ROOTFS}") -add_compile_param(CROSS_LINK_FLAGS "--gcc-toolchain=${CROSS_ROOTFS}/usr") -add_compile_param(CROSS_LINK_FLAGS "--target=${TOOLCHAIN}") -add_compile_param(CROSS_LINK_FLAGS "-fuse-ld=gold") if(TARGET_ARCH_NAME STREQUAL "armel") if(DEFINED TIZEN_TOOLCHAIN) # For Tizen only - add_compile_param(CROSS_LINK_FLAGS "-B${CROSS_ROOTFS}/usr/lib/gcc/${TIZEN_TOOLCHAIN}") - add_compile_param(CROSS_LINK_FLAGS "-L${CROSS_ROOTFS}/lib") - add_compile_param(CROSS_LINK_FLAGS "-L${CROSS_ROOTFS}/usr/lib") - add_compile_param(CROSS_LINK_FLAGS "-L${CROSS_ROOTFS}/usr/lib/gcc/${TIZEN_TOOLCHAIN}") + add_link_options("-B${CROSS_ROOTFS}/usr/lib/gcc/${TIZEN_TOOLCHAIN}") + add_link_options("-L${CROSS_ROOTFS}/lib") + add_link_options("-L${CROSS_ROOTFS}/usr/lib") + add_link_options("-L${CROSS_ROOTFS}/usr/lib/gcc/${TIZEN_TOOLCHAIN}") endif() elseif(TARGET_ARCH_NAME STREQUAL "x86") - add_compile_param(CROSS_LINK_FLAGS "-m32") + add_link_options(-m32) endif() -add_compile_param(CMAKE_EXE_LINKER_FLAGS "${CROSS_LINK_FLAGS}" "TOOLCHAIN_EXE_LINKER_FLAGS") -add_compile_param(CMAKE_SHARED_LINKER_FLAGS "${CROSS_LINK_FLAGS}" "TOOLCHAIN_EXE_LINKER_FLAGS") -add_compile_param(CMAKE_MODULE_LINKER_FLAGS "${CROSS_LINK_FLAGS}" "TOOLCHAIN_EXE_LINKER_FLAGS") - # Specify compile options -add_compile_options("--sysroot=${CROSS_ROOTFS}") -add_compile_options("--target=${TOOLCHAIN}") -add_compile_options("--gcc-toolchain=${CROSS_ROOTFS}/usr") -if(TARGET_ARCH_NAME MATCHES "^(arm|armel|arm64)$") +if(TARGET_ARCH_NAME MATCHES "^(arm|armel|arm64)$" AND NOT "$ENV{__DistroRid}" MATCHES "android.*") set(CMAKE_C_COMPILER_TARGET ${TOOLCHAIN}) set(CMAKE_CXX_COMPILER_TARGET ${TOOLCHAIN}) set(CMAKE_ASM_COMPILER_TARGET ${TOOLCHAIN}) @@ -90,7 +91,17 @@ endif() if(TARGET_ARCH_NAME MATCHES "^(arm|armel)$") add_compile_options(-mthumb) - add_compile_options(-mfpu=vfpv3) + if (NOT DEFINED CLR_ARM_FPU_TYPE) + set (CLR_ARM_FPU_TYPE vfpv3) + endif (NOT DEFINED CLR_ARM_FPU_TYPE) + + add_compile_options (-mfpu=${CLR_ARM_FPU_TYPE}) + if (NOT DEFINED CLR_ARM_FPU_CAPABILITY) + set (CLR_ARM_FPU_CAPABILITY 0x7) + endif (NOT DEFINED CLR_ARM_FPU_CAPABILITY) + + add_definitions (-DCLR_ARM_FPU_CAPABILITY=${CLR_ARM_FPU_CAPABILITY}) + if(TARGET_ARCH_NAME STREQUAL "armel") add_compile_options(-mfloat-abi=softfp) if(DEFINED TIZEN_TOOLCHAIN) @@ -103,7 +114,7 @@ elseif(TARGET_ARCH_NAME STREQUAL "x86") add_compile_options(-Wno-error=unused-command-line-argument) endif() -# Set LLDB include and library paths +# Set LLDB include and library paths for builds that need lldb. if(TARGET_ARCH_NAME MATCHES "^(arm|armel|x86)$") if(TARGET_ARCH_NAME STREQUAL "x86") set(LLVM_CROSS_DIR "$ENV{LLVM_CROSS_HOME}") @@ -131,7 +142,7 @@ if(TARGET_ARCH_NAME MATCHES "^(arm|armel|x86)$") endif() endif() -set(CMAKE_FIND_ROOT_PATH "${CROSS_ROOTFS}") + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) diff --git a/eng/common/darc-init.ps1 b/eng/common/darc-init.ps1 index b94c2f4e41..435e764134 100644 --- a/eng/common/darc-init.ps1 +++ b/eng/common/darc-init.ps1 @@ -1,13 +1,14 @@ param ( $darcVersion = $null, - $versionEndpoint = "https://maestro-prod.westus2.cloudapp.azure.com/api/assets/darc-version?api-version=2019-01-16", - $verbosity = "m" + $versionEndpoint = 'https://maestro-prod.westus2.cloudapp.azure.com/api/assets/darc-version?api-version=2019-01-16', + $verbosity = 'minimal', + $toolpath = $null ) . $PSScriptRoot\tools.ps1 -function InstallDarcCli ($darcVersion) { - $darcCliPackageName = "microsoft.dotnet.darc" +function InstallDarcCli ($darcVersion, $toolpath) { + $darcCliPackageName = 'microsoft.dotnet.darc' $dotnetRoot = InitializeDotNetCli -install:$true $dotnet = "$dotnetRoot\dotnet.exe" @@ -23,11 +24,24 @@ function InstallDarcCli ($darcVersion) { $darcVersion = $(Invoke-WebRequest -Uri $versionEndpoint -UseBasicParsing).Content } - $arcadeServicesSource = 'https://dotnetfeed.blob.core.windows.net/dotnet-core/index.json' + $arcadeServicesSource = 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json' Write-Host "Installing Darc CLI version $darcVersion..." - Write-Host "You may need to restart your command window if this is the first dotnet tool you have installed." - & "$dotnet" tool install $darcCliPackageName --version $darcVersion --add-source "$arcadeServicesSource" -v $verbosity -g --framework netcoreapp2.1 + Write-Host 'You may need to restart your command window if this is the first dotnet tool you have installed.' + if (-not $toolpath) { + Write-Host "'$dotnet' tool install $darcCliPackageName --version $darcVersion --add-source '$arcadeServicesSource' -v $verbosity -g" + & "$dotnet" tool install $darcCliPackageName --version $darcVersion --add-source "$arcadeServicesSource" -v $verbosity -g + }else { + Write-Host "'$dotnet' tool install $darcCliPackageName --version $darcVersion --add-source '$arcadeServicesSource' -v $verbosity --tool-path '$toolpath'" + & "$dotnet" tool install $darcCliPackageName --version $darcVersion --add-source "$arcadeServicesSource" -v $verbosity --tool-path "$toolpath" + } } -InstallDarcCli $darcVersion +try { + InstallDarcCli $darcVersion $toolpath +} +catch { + Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Category 'Darc' -Message $_ + ExitWithExitCode 1 +} \ No newline at end of file diff --git a/eng/common/darc-init.sh b/eng/common/darc-init.sh index 242429bca6..d981d7bbf3 100755 --- a/eng/common/darc-init.sh +++ b/eng/common/darc-init.sh @@ -2,8 +2,8 @@ source="${BASH_SOURCE[0]}" darcVersion='' -versionEndpoint="https://maestro-prod.westus2.cloudapp.azure.com/api/assets/darc-version?api-version=2019-01-16" -verbosity=m +versionEndpoint='https://maestro-prod.westus2.cloudapp.azure.com/api/assets/darc-version?api-version=2019-01-16' +verbosity='minimal' while [[ $# > 0 ]]; do opt="$(echo "$1" | awk '{print tolower($0)}')" @@ -20,6 +20,10 @@ while [[ $# > 0 ]]; do verbosity=$2 shift ;; + --toolpath) + toolpath=$2 + shift + ;; *) echo "Invalid argument: $1" usage @@ -52,17 +56,27 @@ function InstallDarcCli { InitializeDotNetCli local dotnet_root=$_InitializeDotNetCli - local uninstall_command=`$dotnet_root/dotnet tool uninstall $darc_cli_package_name -g` - local tool_list=$($dotnet_root/dotnet tool list -g) - if [[ $tool_list = *$darc_cli_package_name* ]]; then - echo $($dotnet_root/dotnet tool uninstall $darc_cli_package_name -g) + if [ -z "$toolpath" ]; then + local tool_list=$($dotnet_root/dotnet tool list -g) + if [[ $tool_list = *$darc_cli_package_name* ]]; then + echo $($dotnet_root/dotnet tool uninstall $darc_cli_package_name -g) + fi + else + local tool_list=$($dotnet_root/dotnet tool list --tool-path "$toolpath") + if [[ $tool_list = *$darc_cli_package_name* ]]; then + echo $($dotnet_root/dotnet tool uninstall $darc_cli_package_name --tool-path "$toolpath") + fi fi - local arcadeServicesSource="https://dotnetfeed.blob.core.windows.net/dotnet-core/index.json" + local arcadeServicesSource="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json" echo "Installing Darc CLI version $darcVersion..." echo "You may need to restart your command shell if this is the first dotnet tool you have installed." - echo $($dotnet_root/dotnet tool install $darc_cli_package_name --version $darcVersion --add-source "$arcadeServicesSource" -v $verbosity -g) + if [ -z "$toolpath" ]; then + echo $($dotnet_root/dotnet tool install $darc_cli_package_name --version $darcVersion --add-source "$arcadeServicesSource" -v $verbosity -g) + else + echo $($dotnet_root/dotnet tool install $darc_cli_package_name --version $darcVersion --add-source "$arcadeServicesSource" -v $verbosity --tool-path "$toolpath") + fi } InstallDarcCli diff --git a/eng/common/dotnet-install.ps1 b/eng/common/dotnet-install.ps1 index ec3e739fe8..811f0f717f 100644 --- a/eng/common/dotnet-install.ps1 +++ b/eng/common/dotnet-install.ps1 @@ -1,28 +1,27 @@ [CmdletBinding(PositionalBinding=$false)] Param( - [string] $verbosity = "minimal", - [string] $architecture = "", - [string] $version = "Latest", - [string] $runtime = "dotnet", - [string] $RuntimeSourceFeed = "", - [string] $RuntimeSourceFeedKey = "" + [string] $verbosity = 'minimal', + [string] $architecture = '', + [string] $version = 'Latest', + [string] $runtime = 'dotnet', + [string] $RuntimeSourceFeed = '', + [string] $RuntimeSourceFeedKey = '' ) . $PSScriptRoot\tools.ps1 -$dotnetRoot = Join-Path $RepoRoot ".dotnet" +$dotnetRoot = Join-Path $RepoRoot '.dotnet' $installdir = $dotnetRoot try { - if ($architecture -and $architecture.Trim() -eq "x86") { - $installdir = Join-Path $installdir "x86" + if ($architecture -and $architecture.Trim() -eq 'x86') { + $installdir = Join-Path $installdir 'x86' } - InstallDotNet $installdir $version $architecture $runtime $true -RuntimeSourceFeed $RuntimeSourceFeed -RuntimeSourceFeedKey $RuntimeSourceFeedKey -} + InstallDotNet $installdir $version $architecture $runtime $true -RuntimeSourceFeed $RuntimeSourceFeed -RuntimeSourceFeedKey $RuntimeSourceFeedKey +} catch { - Write-Host $_ - Write-Host $_.Exception Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Category 'InitializeToolset' -Message $_ ExitWithExitCode 1 } diff --git a/eng/common/dotnet-install.sh b/eng/common/dotnet-install.sh index d259a274c7..ead6a1d9a2 100755 --- a/eng/common/dotnet-install.sh +++ b/eng/common/dotnet-install.sh @@ -11,6 +11,8 @@ while [[ -h "$source" ]]; do done scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" +. "$scriptroot/tools.sh" + version='Latest' architecture='' runtime='dotnet' @@ -40,18 +42,47 @@ while [[ $# > 0 ]]; do runtimeSourceFeedKey="$1" ;; *) - echo "Invalid argument: $1" + Write-PipelineTelemetryError -Category 'Build' -Message "Invalid argument: $1" exit 1 ;; esac shift done -. "$scriptroot/tools.sh" +# Use uname to determine what the CPU is. +cpuname=$(uname -p) +# Some Linux platforms report unknown for platform, but the arch for machine. +if [[ "$cpuname" == "unknown" ]]; then + cpuname=$(uname -m) +fi + +case $cpuname in + aarch64) + buildarch=arm64 + ;; + amd64|x86_64) + buildarch=x64 + ;; + armv*l) + buildarch=arm + ;; + i686) + buildarch=x86 + ;; + *) + echo "Unknown CPU $cpuname detected, treating it as x64" + buildarch=x64 + ;; +esac + dotnetRoot="$repo_root/.dotnet" +if [[ $architecture != "" ]] && [[ $architecture != $buildarch ]]; then + dotnetRoot="$dotnetRoot/$architecture" +fi + InstallDotNet $dotnetRoot $version "$architecture" $runtime true $runtimeSourceFeed $runtimeSourceFeedKey || { local exit_code=$? - echo "dotnet-install.sh failed (exit code '$exit_code')." >&2 + Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "dotnet-install.sh failed (exit code '$exit_code')." >&2 ExitWithExitCode $exit_code } diff --git a/eng/common/enable-cross-org-publishing.ps1 b/eng/common/enable-cross-org-publishing.ps1 index eccbf9f1b1..da09da4f1f 100644 --- a/eng/common/enable-cross-org-publishing.ps1 +++ b/eng/common/enable-cross-org-publishing.ps1 @@ -2,5 +2,12 @@ param( [string] $token ) -Write-Host "##vso[task.setvariable variable=VSS_NUGET_ACCESSTOKEN]$token" -Write-Host "##vso[task.setvariable variable=VSS_NUGET_URI_PREFIXES]https://dnceng.pkgs.visualstudio.com/;https://pkgs.dev.azure.com/dnceng/;https://devdiv.pkgs.visualstudio.com/;https://pkgs.dev.azure.com/devdiv/" + +. $PSScriptRoot\pipeline-logging-functions.ps1 + +# Write-PipelineSetVariable will no-op if a variable named $ci is not defined +# Since this script is only ever called in AzDO builds, just universally set it +$ci = $true + +Write-PipelineSetVariable -Name 'VSS_NUGET_ACCESSTOKEN' -Value $token -IsMultiJobVariable $false +Write-PipelineSetVariable -Name 'VSS_NUGET_URI_PREFIXES' -Value 'https://dnceng.pkgs.visualstudio.com/;https://pkgs.dev.azure.com/dnceng/;https://devdiv.pkgs.visualstudio.com/;https://pkgs.dev.azure.com/devdiv/' -IsMultiJobVariable $false diff --git a/eng/common/generate-graph-files.ps1 b/eng/common/generate-graph-files.ps1 index b056e4c1ac..0728b1a8b5 100644 --- a/eng/common/generate-graph-files.ps1 +++ b/eng/common/generate-graph-files.ps1 @@ -3,39 +3,39 @@ Param( [Parameter(Mandatory=$true)][string] $gitHubPat, # GitHub personal access token from https://github.com/settings/tokens (no auth scopes needed) [Parameter(Mandatory=$true)][string] $azdoPat, # Azure Dev Ops tokens from https://dev.azure.com/dnceng/_details/security/tokens (code read scope needed) [Parameter(Mandatory=$true)][string] $outputFolder, # Where the graphviz.txt file will be created - [string] $darcVersion = '1.1.0-beta.19175.6', # darc's version + [string] $darcVersion, # darc's version [string] $graphvizVersion = '2.38', # GraphViz version [switch] $includeToolset # Whether the graph should include toolset dependencies or not. i.e. arcade, optimization. For more about # toolset dependencies see https://github.com/dotnet/arcade/blob/master/Documentation/Darc.md#toolset-vs-product-dependencies ) -$ErrorActionPreference = "Stop" -. $PSScriptRoot\tools.ps1 - -Import-Module -Name (Join-Path $PSScriptRoot "native\CommonLibrary.psm1") - function CheckExitCode ([string]$stage) { $exitCode = $LASTEXITCODE if ($exitCode -ne 0) { - Write-Host "Something failed in stage: '$stage'. Check for errors above. Exiting now..." + Write-PipelineTelemetryError -Category 'Arcade' -Message "Something failed in stage: '$stage'. Check for errors above. Exiting now..." ExitWithExitCode $exitCode } } try { + $ErrorActionPreference = 'Stop' + . $PSScriptRoot\tools.ps1 + + Import-Module -Name (Join-Path $PSScriptRoot 'native\CommonLibrary.psm1') + Push-Location $PSScriptRoot - Write-Host "Installing darc..." + Write-Host 'Installing darc...' . .\darc-init.ps1 -darcVersion $darcVersion - CheckExitCode "Running darc-init" + CheckExitCode 'Running darc-init' - $engCommonBaseDir = Join-Path $PSScriptRoot "native\" + $engCommonBaseDir = Join-Path $PSScriptRoot 'native\' $graphvizInstallDir = CommonLibrary\Get-NativeInstallDirectory - $nativeToolBaseUri = "https://netcorenativeassets.blob.core.windows.net/resource-packages/external" - $installBin = Join-Path $graphvizInstallDir "bin" + $nativeToolBaseUri = 'https://netcorenativeassets.blob.core.windows.net/resource-packages/external' + $installBin = Join-Path $graphvizInstallDir 'bin' - Write-Host "Installing dot..." + Write-Host 'Installing dot...' .\native\install-tool.ps1 -ToolName graphviz -InstallPath $installBin -BaseUri $nativeToolBaseUri -CommonLibraryDirectory $engCommonBaseDir -Version $graphvizVersion -Verbose $darcExe = "$env:USERPROFILE\.dotnet\tools" @@ -51,37 +51,36 @@ try { $graphVizImageFilePath = "$outputFolder\graph.png" $normalGraphFilePath = "$outputFolder\graph-full.txt" $flatGraphFilePath = "$outputFolder\graph-flat.txt" - $baseOptions = @( "--github-pat", "$gitHubPat", "--azdev-pat", "$azdoPat", "--password", "$barToken" ) + $baseOptions = @( '--github-pat', "$gitHubPat", '--azdev-pat', "$azdoPat", '--password', "$barToken" ) if ($includeToolset) { - Write-Host "Toolsets will be included in the graph..." - $baseOptions += @( "--include-toolset" ) + Write-Host 'Toolsets will be included in the graph...' + $baseOptions += @( '--include-toolset' ) } - Write-Host "Generating standard dependency graph..." + Write-Host 'Generating standard dependency graph...' & "$darcExe" get-dependency-graph @baseOptions --output-file $normalGraphFilePath - CheckExitCode "Generating normal dependency graph" + CheckExitCode 'Generating normal dependency graph' - Write-Host "Generating flat dependency graph and graphviz file..." + Write-Host 'Generating flat dependency graph and graphviz file...' & "$darcExe" get-dependency-graph @baseOptions --flat --coherency --graphviz $graphVizFilePath --output-file $flatGraphFilePath - CheckExitCode "Generating flat and graphviz dependency graph" + CheckExitCode 'Generating flat and graphviz dependency graph' Write-Host "Generating graph image $graphVizFilePath" $dotFilePath = Join-Path $installBin "graphviz\$graphvizVersion\release\bin\dot.exe" & "$dotFilePath" -Tpng -o"$graphVizImageFilePath" "$graphVizFilePath" - CheckExitCode "Generating graphviz image" + CheckExitCode 'Generating graphviz image' Write-Host "'$graphVizFilePath', '$flatGraphFilePath', '$normalGraphFilePath' and '$graphVizImageFilePath' created!" } catch { if (!$includeToolset) { - Write-Host "This might be a toolset repo which includes only toolset dependencies. " -NoNewline -ForegroundColor Yellow - Write-Host "Since -includeToolset is not set there is no graph to create. Include -includeToolset and try again..." -ForegroundColor Yellow + Write-Host 'This might be a toolset repo which includes only toolset dependencies. ' -NoNewline -ForegroundColor Yellow + Write-Host 'Since -includeToolset is not set there is no graph to create. Include -includeToolset and try again...' -ForegroundColor Yellow } - Write-Host $_ - Write-Host $_.Exception Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Category 'Arcade' -Message $_ ExitWithExitCode 1 } finally { - Pop-Location + Pop-Location } \ No newline at end of file diff --git a/eng/common/init-tools-native.ps1 b/eng/common/init-tools-native.ps1 index 8cf18bcfeb..db830c00a6 100644 --- a/eng/common/init-tools-native.ps1 +++ b/eng/common/init-tools-native.ps1 @@ -35,7 +35,7 @@ File path to global.json file #> [CmdletBinding(PositionalBinding=$false)] Param ( - [string] $BaseUri = "https://netcorenativeassets.blob.core.windows.net/resource-packages/external", + [string] $BaseUri = 'https://netcorenativeassets.blob.core.windows.net/resource-packages/external', [string] $InstallDirectory, [switch] $Clean = $False, [switch] $Force = $False, @@ -45,26 +45,27 @@ Param ( ) if (!$GlobalJsonFile) { - $GlobalJsonFile = Join-Path (Get-Item $PSScriptRoot).Parent.Parent.FullName "global.json" + $GlobalJsonFile = Join-Path (Get-Item $PSScriptRoot).Parent.Parent.FullName 'global.json' } Set-StrictMode -version 2.0 -$ErrorActionPreference="Stop" +$ErrorActionPreference='Stop' -Import-Module -Name (Join-Path $PSScriptRoot "native\CommonLibrary.psm1") +. $PSScriptRoot\pipeline-logging-functions.ps1 +Import-Module -Name (Join-Path $PSScriptRoot 'native\CommonLibrary.psm1') try { # Define verbose switch if undefined - $Verbose = $VerbosePreference -Eq "Continue" + $Verbose = $VerbosePreference -Eq 'Continue' - $EngCommonBaseDir = Join-Path $PSScriptRoot "native\" + $EngCommonBaseDir = Join-Path $PSScriptRoot 'native\' $NativeBaseDir = $InstallDirectory if (!$NativeBaseDir) { $NativeBaseDir = CommonLibrary\Get-NativeInstallDirectory } $Env:CommonLibrary_NativeInstallDir = $NativeBaseDir - $InstallBin = Join-Path $NativeBaseDir "bin" - $InstallerPath = Join-Path $EngCommonBaseDir "install-tool.ps1" + $InstallBin = Join-Path $NativeBaseDir 'bin' + $InstallerPath = Join-Path $EngCommonBaseDir 'install-tool.ps1' # Process tools list Write-Host "Processing $GlobalJsonFile" @@ -74,7 +75,7 @@ try { } $NativeTools = Get-Content($GlobalJsonFile) -Raw | ConvertFrom-Json | - Select-Object -Expand "native-tools" -ErrorAction SilentlyContinue + Select-Object -Expand 'native-tools' -ErrorAction SilentlyContinue if ($NativeTools) { $NativeTools.PSObject.Properties | ForEach-Object { $ToolName = $_.Name @@ -112,18 +113,21 @@ try { } $toolInstallationFailure = $true } else { - Write-Error $errMsg + # We cannot change this to Write-PipelineTelemetryError because of https://github.com/dotnet/arcade/issues/4482 + Write-Host $errMsg exit 1 } } } if ((Get-Variable 'toolInstallationFailure' -ErrorAction 'SilentlyContinue') -and $toolInstallationFailure) { + # We cannot change this to Write-PipelineTelemetryError because of https://github.com/dotnet/arcade/issues/4482 + Write-Host 'Native tools bootstrap failed' exit 1 } } else { - Write-Host "No native tools defined in global.json" + Write-Host 'No native tools defined in global.json' exit 0 } @@ -131,17 +135,18 @@ try { exit 0 } if (Test-Path $InstallBin) { - Write-Host "Native tools are available from" (Convert-Path -Path $InstallBin) + Write-Host 'Native tools are available from ' (Convert-Path -Path $InstallBin) Write-Host "##vso[task.prependpath]$(Convert-Path -Path $InstallBin)" + return $InstallBin } else { - Write-Error "Native tools install directory does not exist, installation failed" + Write-PipelineTelemetryError -Category 'NativeToolsBootstrap' -Message 'Native tools install directory does not exist, installation failed' exit 1 } exit 0 } catch { - Write-Host $_ - Write-Host $_.Exception - exit 1 + Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Category 'NativeToolsBootstrap' -Message $_ + ExitWithExitCode 1 } diff --git a/eng/common/init-tools-native.sh b/eng/common/init-tools-native.sh index 4dafaaca13..29fc5db8ae 100755 --- a/eng/common/init-tools-native.sh +++ b/eng/common/init-tools-native.sh @@ -12,6 +12,7 @@ retry_wait_time_seconds=30 global_json_file="$(dirname "$(dirname "${scriptroot}")")/global.json" declare -A native_assets +. $scriptroot/pipeline-logging-functions.sh . $scriptroot/native/common-library.sh while (($# > 0)); do @@ -33,6 +34,14 @@ while (($# > 0)); do force=true shift 1 ;; + --donotabortonfailure) + donotabortonfailure=true + shift 1 + ;; + --donotdisplaywarnings) + donotdisplaywarnings=true + shift 1 + ;; --downloadretries) download_retries=$2 shift 2 @@ -51,6 +60,8 @@ while (($# > 0)); do echo " - (default) %USERPROFILE%/.netcoreeng/native" echo "" echo " --clean Switch specifying not to install anything, but cleanup native asset folders" + echo " --donotabortonfailure Switch specifiying whether to abort native tools installation on failure" + echo " --donotdisplaywarnings Switch specifiying whether to display warnings during native tools installation on failure" echo " --force Clean and then install tools" echo " --help Print help and exit" echo "" @@ -91,6 +102,7 @@ if [[ -z $install_directory ]]; then fi install_bin="${native_base_dir}/bin" +installed_any=false ReadGlobalJsonNativeTools @@ -102,8 +114,8 @@ else for tool in "${!native_assets[@]}" do tool_version=${native_assets[$tool]} - installer_name="install-$tool.sh" - installer_command="$native_installer_dir/$installer_name" + installer_path="$native_installer_dir/install-$tool.sh" + installer_command="$installer_path" installer_command+=" --baseuri $base_uri" installer_command+=" --installpath $install_bin" installer_command+=" --version $tool_version" @@ -117,11 +129,29 @@ else installer_command+=" --clean" fi - $installer_command - - if [[ $? != 0 ]]; then - echo "Execution Failed" >&2 - exit 1 + if [[ -a $installer_path ]]; then + $installer_command + if [[ $? != 0 ]]; then + if [[ $donotabortonfailure = true ]]; then + if [[ $donotdisplaywarnings != true ]]; then + Write-PipelineTelemetryError -category 'NativeToolsBootstrap' "Execution Failed" + fi + else + Write-PipelineTelemetryError -category 'NativeToolsBootstrap' "Execution Failed" + exit 1 + fi + else + $installed_any = true + fi + else + if [[ $donotabortonfailure == true ]]; then + if [[ $donotdisplaywarnings != true ]]; then + Write-PipelineTelemetryError -category 'NativeToolsBootstrap' "Execution Failed: no install script" + fi + else + Write-PipelineTelemetryError -category 'NativeToolsBootstrap' "Execution Failed: no install script" + exit 1 + fi fi done fi @@ -134,8 +164,10 @@ if [[ -d $install_bin ]]; then echo "Native tools are available from $install_bin" echo "##vso[task.prependpath]$install_bin" else - echo "Native tools install directory does not exist, installation failed" >&2 - exit 1 + if [[ $installed_any = true ]]; then + Write-PipelineTelemetryError -category 'NativeToolsBootstrap' "Native tools install directory does not exist, installation failed" + exit 1 + fi fi exit 0 diff --git a/eng/common/internal-feed-operations.ps1 b/eng/common/internal-feed-operations.ps1 index 8b8bafd6a8..db0baac9a4 100644 --- a/eng/common/internal-feed-operations.ps1 +++ b/eng/common/internal-feed-operations.ps1 @@ -6,9 +6,8 @@ param( [switch] $IsFeedPrivate ) -$ErrorActionPreference = "Stop" +$ErrorActionPreference = 'Stop' Set-StrictMode -Version 2.0 - . $PSScriptRoot\tools.ps1 # Sets VSS_NUGET_EXTERNAL_FEED_ENDPOINTS based on the "darc-int-*" feeds defined in NuGet.config. This is needed @@ -21,7 +20,7 @@ function SetupCredProvider { ) # Install the Cred Provider NuGet plugin - Write-Host "Setting up Cred Provider NuGet plugin in the agent..." + Write-Host 'Setting up Cred Provider NuGet plugin in the agent...' Write-Host "Getting 'installcredprovider.ps1' from 'https://github.com/microsoft/artifacts-credprovider'..." $url = 'https://raw.githubusercontent.com/microsoft/artifacts-credprovider/master/helpers/installcredprovider.ps1' @@ -29,18 +28,18 @@ function SetupCredProvider { Write-Host "Writing the contents of 'installcredprovider.ps1' locally..." Invoke-WebRequest $url -OutFile installcredprovider.ps1 - Write-Host "Installing plugin..." + Write-Host 'Installing plugin...' .\installcredprovider.ps1 -Force Write-Host "Deleting local copy of 'installcredprovider.ps1'..." Remove-Item .\installcredprovider.ps1 if (-Not("$env:USERPROFILE\.nuget\plugins\netcore")) { - Write-Host "CredProvider plugin was not installed correctly!" + Write-PipelineTelemetryError -Category 'Arcade' -Message 'CredProvider plugin was not installed correctly!' ExitWithExitCode 1 } else { - Write-Host "CredProvider plugin was installed correctly!" + Write-Host 'CredProvider plugin was installed correctly!' } # Then, we set the 'VSS_NUGET_EXTERNAL_FEED_ENDPOINTS' environment variable to restore from the stable @@ -49,7 +48,7 @@ function SetupCredProvider { $nugetConfigPath = "$RepoRoot\NuGet.config" if (-Not (Test-Path -Path $nugetConfigPath)) { - Write-Host "NuGet.config file not found in repo's root!" + Write-PipelineTelemetryError -Category 'Build' -Message 'NuGet.config file not found in repo root!' ExitWithExitCode 1 } @@ -81,7 +80,7 @@ function SetupCredProvider { } else { - Write-Host "No internal endpoints found in NuGet.config" + Write-Host 'No internal endpoints found in NuGet.config' } } @@ -99,7 +98,7 @@ function InstallDotNetSdkAndRestoreArcade { & $dotnet restore $restoreProjPath - Write-Host "Arcade SDK restored!" + Write-Host 'Arcade SDK restored!' if (Test-Path -Path $restoreProjPath) { Remove-Item $restoreProjPath @@ -113,23 +112,22 @@ function InstallDotNetSdkAndRestoreArcade { try { Push-Location $PSScriptRoot - if ($Operation -like "setup") { + if ($Operation -like 'setup') { SetupCredProvider $AuthToken } - elseif ($Operation -like "install-restore") { + elseif ($Operation -like 'install-restore') { InstallDotNetSdkAndRestoreArcade } else { - Write-Host "Unknown operation '$Operation'!" + Write-PipelineTelemetryError -Category 'Arcade' -Message "Unknown operation '$Operation'!" ExitWithExitCode 1 } } catch { - Write-Host $_ - Write-Host $_.Exception Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Category 'Arcade' -Message $_ ExitWithExitCode 1 } finally { - Pop-Location + Pop-Location } diff --git a/eng/common/internal-feed-operations.sh b/eng/common/internal-feed-operations.sh index 1ff654d2ff..5941ea2833 100755 --- a/eng/common/internal-feed-operations.sh +++ b/eng/common/internal-feed-operations.sh @@ -30,7 +30,7 @@ function SetupCredProvider { rm installcredprovider.sh if [ ! -d "$HOME/.nuget/plugins" ]; then - echo "CredProvider plugin was not installed correctly!" + Write-PipelineTelemetryError -category 'Build' 'CredProvider plugin was not installed correctly!' ExitWithExitCode 1 else echo "CredProvider plugin was installed correctly!" @@ -42,7 +42,7 @@ function SetupCredProvider { local nugetConfigPath="$repo_root/NuGet.config" if [ ! "$nugetConfigPath" ]; then - echo "NuGet.config file not found in repo's root!" + Write-PipelineTelemetryError -category 'Build' "NuGet.config file not found in repo's root!" ExitWithExitCode 1 fi diff --git a/eng/common/msbuild.ps1 b/eng/common/msbuild.ps1 index b37fd3d5e9..c640123000 100644 --- a/eng/common/msbuild.ps1 +++ b/eng/common/msbuild.ps1 @@ -1,6 +1,6 @@ [CmdletBinding(PositionalBinding=$false)] Param( - [string] $verbosity = "minimal", + [string] $verbosity = 'minimal', [bool] $warnAsError = $true, [bool] $nodeReuse = $true, [switch] $ci, @@ -18,9 +18,8 @@ try { MSBuild @extraArgs } catch { - Write-Host $_ - Write-Host $_.Exception Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Category 'Build' -Message $_ ExitWithExitCode 1 } diff --git a/eng/common/native/common-library.sh b/eng/common/native/common-library.sh index 271bddfac5..bf272dcf55 100755 --- a/eng/common/native/common-library.sh +++ b/eng/common/native/common-library.sh @@ -34,7 +34,7 @@ function ExpandZip { echo "'Force flag enabled, but '$output_directory' exists. Removing directory" rm -rf $output_directory if [[ $? != 0 ]]; then - echo Unable to remove '$output_directory'>&2 + Write-PipelineTelemetryError -category 'NativeToolsBootstrap' "Unable to remove '$output_directory'" return 1 fi fi @@ -45,7 +45,7 @@ function ExpandZip { echo "Extracting archive" tar -xf $zip_path -C $output_directory if [[ $? != 0 ]]; then - echo "Unable to extract '$zip_path'" >&2 + Write-PipelineTelemetryError -category 'NativeToolsBootstrap' "Unable to extract '$zip_path'" return 1 fi @@ -117,7 +117,7 @@ function DownloadAndExtract { # Download file GetFile "$uri" "$temp_tool_path" $force $download_retries $retry_wait_time_seconds if [[ $? != 0 ]]; then - echo "Failed to download '$uri' to '$temp_tool_path'." >&2 + Write-PipelineTelemetryError -category 'NativeToolsBootstrap' "Failed to download '$uri' to '$temp_tool_path'." return 1 fi @@ -125,7 +125,7 @@ function DownloadAndExtract { echo "extracting from $temp_tool_path to $installDir" ExpandZip "$temp_tool_path" "$installDir" $force $download_retries $retry_wait_time_seconds if [[ $? != 0 ]]; then - echo "Failed to extract '$temp_tool_path' to '$installDir'." >&2 + Write-PipelineTelemetryError -category 'NativeToolsBootstrap' "Failed to extract '$temp_tool_path' to '$installDir'." return 1 fi @@ -148,7 +148,7 @@ function NewScriptShim { fi if [[ ! -f $tool_file_path ]]; then - echo "Specified tool file path:'$tool_file_path' does not exist" >&2 + Write-PipelineTelemetryError -category 'NativeToolsBootstrap' "Specified tool file path:'$tool_file_path' does not exist" return 1 fi diff --git a/eng/common/native/install-cmake-test.sh b/eng/common/native/install-cmake-test.sh index 53ddf4e686..12339a4076 100755 --- a/eng/common/native/install-cmake-test.sh +++ b/eng/common/native/install-cmake-test.sh @@ -101,7 +101,7 @@ fi DownloadAndExtract $uri $tool_install_directory $force $download_retries $retry_wait_time_seconds if [[ $? != 0 ]]; then - echo "Installation failed" >&2 + Write-PipelineTelemetryError -category 'NativeToolsBootstrap' 'Installation failed' exit 1 fi @@ -110,7 +110,7 @@ fi NewScriptShim $shim_path $tool_file_path true if [[ $? != 0 ]]; then - echo "Shim generation failed" >&2 + Write-PipelineTelemetryError -category 'NativeToolsBootstrap' 'Shim generation failed' exit 1 fi diff --git a/eng/common/native/install-cmake.sh b/eng/common/native/install-cmake.sh index 5f1a182fa9..18041be876 100755 --- a/eng/common/native/install-cmake.sh +++ b/eng/common/native/install-cmake.sh @@ -101,7 +101,7 @@ fi DownloadAndExtract $uri $tool_install_directory $force $download_retries $retry_wait_time_seconds if [[ $? != 0 ]]; then - echo "Installation failed" >&2 + Write-PipelineTelemetryError -category 'NativeToolsBootstrap' 'Installation failed' exit 1 fi @@ -110,7 +110,7 @@ fi NewScriptShim $shim_path $tool_file_path true if [[ $? != 0 ]]; then - echo "Shim generation failed" >&2 + Write-PipelineTelemetryError -category 'NativeToolsBootstrap' 'Shim generation failed' exit 1 fi diff --git a/eng/common/native/install-tool.ps1 b/eng/common/native/install-tool.ps1 index 635ab3fd41..f397e1c75d 100644 --- a/eng/common/native/install-tool.ps1 +++ b/eng/common/native/install-tool.ps1 @@ -46,6 +46,8 @@ Param ( [int] $RetryWaitTimeInSeconds = 30 ) +. $PSScriptRoot\..\pipeline-logging-functions.ps1 + # Import common library modules Import-Module -Name (Join-Path $CommonLibraryDirectory "CommonLibrary.psm1") @@ -93,7 +95,7 @@ try { -Verbose:$Verbose if ($InstallStatus -Eq $False) { - Write-Error "Installation failed" + Write-PipelineTelemetryError "Installation failed" -Category "NativeToolsetBootstrapping" exit 1 } } @@ -103,7 +105,7 @@ try { Write-Error "There are multiple copies of $ToolName in $($ToolInstallDirectory): `n$(@($ToolFilePath | out-string))" exit 1 } elseif (@($ToolFilePath).Length -Lt 1) { - Write-Error "$ToolName was not found in $ToolFilePath." + Write-Host "$ToolName was not found in $ToolFilePath." exit 1 } @@ -117,14 +119,14 @@ try { -Verbose:$Verbose if ($GenerateShimStatus -Eq $False) { - Write-Error "Generate shim failed" + Write-PipelineTelemetryError "Generate shim failed" -Category "NativeToolsetBootstrapping" return 1 } exit 0 } catch { - Write-Host $_ - Write-Host $_.Exception + Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Category "NativeToolsetBootstrapping" -Message $_ exit 1 } diff --git a/eng/common/performance/perfhelixpublish.proj b/eng/common/performance/perfhelixpublish.proj index e5826b5323..cf5941e1b6 100644 --- a/eng/common/performance/perfhelixpublish.proj +++ b/eng/common/performance/perfhelixpublish.proj @@ -6,7 +6,7 @@ py -3 %HELIX_CORRELATION_PAYLOAD%\Core_Root\CoreRun.exe %HELIX_CORRELATION_PAYLOAD%\Baseline_Core_Root\CoreRun.exe - $(HelixPreCommands);call %HELIX_CORRELATION_PAYLOAD%\performance\tools\machine-setup.cmd + $(HelixPreCommands);call %HELIX_CORRELATION_PAYLOAD%\performance\tools\machine-setup.cmd;set PYTHONPATH=%HELIX_WORKITEM_PAYLOAD%\scripts%3B%HELIX_WORKITEM_PAYLOAD% %HELIX_CORRELATION_PAYLOAD%\artifacts\BenchmarkDotNet.Artifacts %HELIX_CORRELATION_PAYLOAD%\artifacts\BenchmarkDotNet.Artifacts_Baseline %HELIX_CORRELATION_PAYLOAD%\performance\src\tools\ResultsComparer\ResultsComparer.csproj @@ -99,4 +99,23 @@ 4:00 + + + + $(WorkItemDirectory)\ScenarioCorrelation + $(Python) %HELIX_CORRELATION_PAYLOAD%\performance\src\scenarios\crossgen\test.py crossgen --test-name System.Private.Xml.dll --core-root %HELIX_CORRELATION_PAYLOAD%\Core_Root + + + $(WorkItemDirectory)\ScenarioCorrelation + $(Python) %HELIX_CORRELATION_PAYLOAD%\performance\src\scenarios\crossgen\test.py crossgen --test-name System.Linq.Expressions.dll --core-root %HELIX_CORRELATION_PAYLOAD%\Core_Root + + + $(WorkItemDirectory)\ScenarioCorrelation + $(Python) %HELIX_CORRELATION_PAYLOAD%\performance\src\scenarios\crossgen\test.py crossgen --test-name Microsoft.CodeAnalysis.VisualBasic.dll --core-root %HELIX_CORRELATION_PAYLOAD%\Core_Root + + + $(WorkItemDirectory)\ScenarioCorrelation + $(Python) %HELIX_CORRELATION_PAYLOAD%\performance\src\scenarios\crossgen\test.py crossgen --test-name Microsoft.CodeAnalysis.CSharp.dll --core-root %HELIX_CORRELATION_PAYLOAD%\Core_Root + + \ No newline at end of file diff --git a/eng/common/performance/performance-setup.ps1 b/eng/common/performance/performance-setup.ps1 index ec41965fc8..e337669929 100644 --- a/eng/common/performance/performance-setup.ps1 +++ b/eng/common/performance/performance-setup.ps1 @@ -9,12 +9,12 @@ Param( [string] $Branch=$env:BUILD_SOURCEBRANCH, [string] $CommitSha=$env:BUILD_SOURCEVERSION, [string] $BuildNumber=$env:BUILD_BUILDNUMBER, - [string] $RunCategories="coreclr corefx", + [string] $RunCategories="Libraries Runtime", [string] $Csproj="src\benchmarks\micro\MicroBenchmarks.csproj", [string] $Kind="micro", [switch] $Internal, [switch] $Compare, - [string] $Configurations="CompilationMode=$CompilationMode" + [string] $Configurations="CompilationMode=$CompilationMode RunKind=$Kind" ) $RunFromPerformanceRepo = ($Repository -eq "dotnet/performance") -or ($Repository -eq "dotnet-performance") @@ -49,7 +49,8 @@ if ($Internal) { $HelixSourcePrefix = "official" } -$CommonSetupArguments="--frameworks $Framework --queue $Queue --build-number $BuildNumber --build-configs $Configurations" +# FIX ME: This is a workaround until we get this from the actual pipeline +$CommonSetupArguments="--channel master --queue $Queue --build-number $BuildNumber --build-configs $Configurations --architecture $Architecture" $SetupArguments = "--repository https://github.com/$Repository --branch $Branch --get-perf-hash --commit-sha $CommitSha $CommonSetupArguments" if ($RunFromPerformanceRepo) { diff --git a/eng/common/performance/performance-setup.sh b/eng/common/performance/performance-setup.sh index 2f2092166e..94a04e0fe5 100755 --- a/eng/common/performance/performance-setup.sh +++ b/eng/common/performance/performance-setup.sh @@ -13,9 +13,9 @@ build_number=$BUILD_BUILDNUMBER internal=false compare=false kind="micro" -run_categories="coreclr corefx" +run_categories="Libraries Runtime" csproj="src\benchmarks\micro\MicroBenchmarks.csproj" -configurations= +configurations="CompliationMode=$compilation_mode RunKind=$kind" run_from_perf_repo=false use_core_run=true use_baseline_core_run=true @@ -164,7 +164,7 @@ if [[ "$internal" == true ]]; then fi fi -common_setup_arguments="--frameworks $framework --queue $queue --build-number $build_number --build-configs $configurations" +common_setup_arguments="--channel master --queue $queue --build-number $build_number --build-configs $configurations --architecture $architecture" setup_arguments="--repository https://github.com/$repository --branch $branch --get-perf-hash --commit-sha $commit_sha $common_setup_arguments" if [[ "$run_from_perf_repo" = true ]]; then diff --git a/eng/common/pipeline-logging-functions.ps1 b/eng/common/pipeline-logging-functions.ps1 index af5f48aace..5042baebf1 100644 --- a/eng/common/pipeline-logging-functions.ps1 +++ b/eng/common/pipeline-logging-functions.ps1 @@ -12,6 +12,7 @@ $script:loggingCommandEscapeMappings = @( # TODO: WHAT ABOUT "="? WHAT ABOUT "%" # TODO: BUG: Escape % ??? # TODO: Add test to verify don't need to escape "=". +# Specify "-Force" to force pipeline formatted output even if "$ci" is false or not set function Write-PipelineTelemetryError { [CmdletBinding()] param( @@ -25,49 +26,55 @@ function Write-PipelineTelemetryError { [string]$SourcePath, [string]$LineNumber, [string]$ColumnNumber, - [switch]$AsOutput) + [switch]$AsOutput, + [switch]$Force) - $PSBoundParameters.Remove("Category") | Out-Null - - $Message = "(NETCORE_ENGINEERING_TELEMETRY=$Category) $Message" - $PSBoundParameters.Remove("Message") | Out-Null - $PSBoundParameters.Add("Message", $Message) + $PSBoundParameters.Remove('Category') | Out-Null + if($Force -Or ((Test-Path variable:ci) -And $ci)) { + $Message = "(NETCORE_ENGINEERING_TELEMETRY=$Category) $Message" + } + $PSBoundParameters.Remove('Message') | Out-Null + $PSBoundParameters.Add('Message', $Message) Write-PipelineTaskError @PSBoundParameters } +# Specify "-Force" to force pipeline formatted output even if "$ci" is false or not set function Write-PipelineTaskError { [CmdletBinding()] param( - [Parameter(Mandatory = $true)] - [string]$Message, - [Parameter(Mandatory = $false)] - [string]$Type = 'error', - [string]$ErrCode, - [string]$SourcePath, - [string]$LineNumber, - [string]$ColumnNumber, - [switch]$AsOutput) - - if(!$ci) { + [Parameter(Mandatory = $true)] + [string]$Message, + [Parameter(Mandatory = $false)] + [string]$Type = 'error', + [string]$ErrCode, + [string]$SourcePath, + [string]$LineNumber, + [string]$ColumnNumber, + [switch]$AsOutput, + [switch]$Force + ) + + if(!$Force -And (-Not (Test-Path variable:ci) -Or !$ci)) { if($Type -eq 'error') { - Write-Host $Message -ForegroundColor Red - return + Write-Host $Message -ForegroundColor Red + return } elseif ($Type -eq 'warning') { - Write-Host $Message -ForegroundColor Yellow - return + Write-Host $Message -ForegroundColor Yellow + return } - } - - if(($Type -ne 'error') -and ($Type -ne 'warning')) { - Write-Host $Message - return - } - if(-not $PSBoundParameters.ContainsKey('Type')) { - $PSBoundParameters.Add('Type', 'error') - } - Write-LogIssue @PSBoundParameters + } + + if(($Type -ne 'error') -and ($Type -ne 'warning')) { + Write-Host $Message + return + } + $PSBoundParameters.Remove('Force') | Out-Null + if(-not $PSBoundParameters.ContainsKey('Type')) { + $PSBoundParameters.Add('Type', 'error') + } + Write-LogIssue @PSBoundParameters } function Write-PipelineSetVariable { @@ -80,7 +87,7 @@ function Write-PipelineTaskError { [switch]$AsOutput, [bool]$IsMultiJobVariable=$true) - if($ci) { + if((Test-Path variable:ci) -And $ci) { Write-LoggingCommand -Area 'task' -Event 'setvariable' -Data $Value -Properties @{ 'variable' = $Name 'isSecret' = $Secret @@ -95,7 +102,8 @@ function Write-PipelineTaskError { [Parameter(Mandatory=$true)] [string]$Path, [switch]$AsOutput) - if($ci) { + + if((Test-Path variable:ci) -And $ci) { Write-LoggingCommand -Area 'task' -Event 'prependpath' -Data $Path -AsOutput:$AsOutput } } @@ -231,4 +239,4 @@ function Write-LogIssue { } Write-Host $command -ForegroundColor $foregroundColor -BackgroundColor $backgroundColor -} \ No newline at end of file +} diff --git a/eng/common/pipeline-logging-functions.sh b/eng/common/pipeline-logging-functions.sh index 1c560a5061..33c3f0d807 100755 --- a/eng/common/pipeline-logging-functions.sh +++ b/eng/common/pipeline-logging-functions.sh @@ -2,6 +2,7 @@ function Write-PipelineTelemetryError { local telemetry_category='' + local force=false local function_args=() local message='' while [[ $# -gt 0 ]]; do @@ -11,6 +12,9 @@ function Write-PipelineTelemetryError { telemetry_category=$2 shift ;; + -force|-f) + force=true + ;; -*) function_args+=("$1 $2") shift @@ -22,19 +26,22 @@ function Write-PipelineTelemetryError { shift done - if [[ "$ci" != true ]]; then + if [[ $force != true ]] && [[ "$ci" != true ]]; then echo "$message" >&2 return fi message="(NETCORE_ENGINEERING_TELEMETRY=$telemetry_category) $message" function_args+=("$message") + if [[ $force == true ]]; then + function_args+=("-force") + fi Write-PipelineTaskError $function_args } function Write-PipelineTaskError { - if [[ "$ci" != true ]]; then + if [[ $force != true ]] && [[ "$ci" != true ]]; then echo "$@" >&2 return fi diff --git a/eng/common/post-build/promote-build.ps1 b/eng/common/post-build/add-build-to-channel.ps1 similarity index 55% rename from eng/common/post-build/promote-build.ps1 rename to eng/common/post-build/add-build-to-channel.ps1 index e5ae85f251..de2d957922 100644 --- a/eng/common/post-build/promote-build.ps1 +++ b/eng/common/post-build/add-build-to-channel.ps1 @@ -2,26 +2,26 @@ param( [Parameter(Mandatory=$true)][int] $BuildId, [Parameter(Mandatory=$true)][int] $ChannelId, [Parameter(Mandatory=$true)][string] $MaestroApiAccessToken, - [Parameter(Mandatory=$false)][string] $MaestroApiEndPoint = "https://maestro-prod.westus2.cloudapp.azure.com", - [Parameter(Mandatory=$false)][string] $MaestroApiVersion = "2019-01-16" + [Parameter(Mandatory=$false)][string] $MaestroApiEndPoint = 'https://maestro-prod.westus2.cloudapp.azure.com', + [Parameter(Mandatory=$false)][string] $MaestroApiVersion = '2019-01-16' ) -. $PSScriptRoot\post-build-utils.ps1 - try { + . $PSScriptRoot\post-build-utils.ps1 + # Check that the channel we are going to promote the build to exist $channelInfo = Get-MaestroChannel -ChannelId $ChannelId if (!$channelInfo) { - Write-Host "Channel with BAR ID $ChannelId was not found in BAR!" + Write-PipelineTelemetryCategory -Category 'PromoteBuild' -Message "Channel with BAR ID $ChannelId was not found in BAR!" ExitWithExitCode 1 } - # Get info about which channels the build has already been promoted to + # Get info about which channel(s) the build has already been promoted to $buildInfo = Get-MaestroBuild -BuildId $BuildId if (!$buildInfo) { - Write-Host "Build with BAR ID $BuildId was not found in BAR!" + Write-PipelineTelemetryError -Category 'PromoteBuild' -Message "Build with BAR ID $BuildId was not found in BAR!" ExitWithExitCode 1 } @@ -39,10 +39,10 @@ try { Assign-BuildToChannel -BuildId $BuildId -ChannelId $ChannelId - Write-Host "done." + Write-Host 'done.' } catch { - Write-Host "There was an error while trying to promote build '$BuildId' to channel '$ChannelId'" Write-Host $_ - Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Category 'PromoteBuild' -Message "There was an error while trying to promote build '$BuildId' to channel '$ChannelId'" + ExitWithExitCode 1 } diff --git a/eng/common/post-build/check-channel-consistency.ps1 b/eng/common/post-build/check-channel-consistency.ps1 new file mode 100644 index 0000000000..7e6618d64a --- /dev/null +++ b/eng/common/post-build/check-channel-consistency.ps1 @@ -0,0 +1,25 @@ +param( + [Parameter(Mandatory=$true)][string] $PromoteToChannels, # List of channels that the build should be promoted to + [Parameter(Mandatory=$true)][array] $AvailableChannelIds # List of channel IDs available in the YAML implementation +) + +try { + . $PSScriptRoot\post-build-utils.ps1 + + # Check that every channel that Maestro told to promote the build to + # is available in YAML + $PromoteToChannelsIds = $PromoteToChannels -split "\D" | Where-Object { $_ } + + foreach ($id in $PromoteToChannelsIds) { + if (($id -ne 0) -and ($id -notin $AvailableChannelIds)) { + Write-PipelineTaskError -Type 'warning' -Message "Channel $id is not present in the post-build YAML configuration!" + } + } + + Write-Host 'done.' +} +catch { + Write-Host $_ + Write-PipelineTelemetryError -Category 'CheckChannelConsistency' -Message "There was an error while trying to check consistency of Maestro default channels for the build and post-build YAML configuration." + ExitWithExitCode 1 +} diff --git a/eng/common/post-build/darc-gather-drop.ps1 b/eng/common/post-build/darc-gather-drop.ps1 deleted file mode 100644 index 89854d3c1c..0000000000 --- a/eng/common/post-build/darc-gather-drop.ps1 +++ /dev/null @@ -1,45 +0,0 @@ -param( - [Parameter(Mandatory=$true)][int] $BarBuildId, # ID of the build which assets should be downloaded - [Parameter(Mandatory=$true)][string] $DropLocation, # Where the assets should be downloaded to - [Parameter(Mandatory=$true)][string] $MaestroApiAccessToken, # Token used to access Maestro API - [Parameter(Mandatory=$false)][string] $MaestroApiEndPoint = "https://maestro-prod.westus2.cloudapp.azure.com", # Maestro API URL - [Parameter(Mandatory=$false)][string] $MaestroApiVersion = "2019-01-16" # Version of Maestro API to use -) - -. $PSScriptRoot\post-build-utils.ps1 - -try { - Write-Host "Installing DARC ..." - - . $PSScriptRoot\..\darc-init.ps1 - $exitCode = $LASTEXITCODE - - if ($exitCode -ne 0) { - Write-PipelineTaskError "Something failed while running 'darc-init.ps1'. Check for errors above. Exiting now..." - ExitWithExitCode $exitCode - } - - # For now, only use a dry run. - # Ideally we would change darc to enable a quick request that - # would check whether the file exists that you can download it, - # and that it won't conflict with other files. - # https://github.com/dotnet/arcade/issues/3674 - # Right now we can't remove continue-on-error because we ocassionally will have - # dependencies that have no associated builds (e.g. an old dependency). - # We need to add an option to baseline specific dependencies away, or add them manually - # to the BAR. - darc gather-drop --non-shipping ` - --dry-run ` - --continue-on-error ` - --id $BarBuildId ` - --output-dir $DropLocation ` - --bar-uri $MaestroApiEndpoint ` - --password $MaestroApiAccessToken ` - --latest-location -} -catch { - Write-Host $_ - Write-Host $_.Exception - Write-Host $_.ScriptStackTrace - ExitWithExitCode 1 -} diff --git a/eng/common/post-build/nuget-validation.ps1 b/eng/common/post-build/nuget-validation.ps1 index 78ed0d540f..dab3534ab5 100644 --- a/eng/common/post-build/nuget-validation.ps1 +++ b/eng/common/post-build/nuget-validation.ps1 @@ -6,20 +6,19 @@ param( [Parameter(Mandatory=$true)][string] $ToolDestinationPath # Where the validation tool should be downloaded to ) -. $PSScriptRoot\post-build-utils.ps1 - try { - $url = "https://raw.githubusercontent.com/NuGet/NuGetGallery/jver-verify/src/VerifyMicrosoftPackage/verify.ps1" + . $PSScriptRoot\post-build-utils.ps1 - New-Item -ItemType "directory" -Path ${ToolDestinationPath} -Force + $url = 'https://raw.githubusercontent.com/NuGet/NuGetGallery/3e25ad135146676bcab0050a516939d9958bfa5d/src/VerifyMicrosoftPackage/verify.ps1' + + New-Item -ItemType 'directory' -Path ${ToolDestinationPath} -Force Invoke-WebRequest $url -OutFile ${ToolDestinationPath}\verify.ps1 & ${ToolDestinationPath}\verify.ps1 ${PackagesPath}\*.nupkg } catch { - Write-PipelineTaskError "NuGet package validation failed. Please check error logs." - Write-Host $_ Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Category 'NuGetValidation' -Message $_ ExitWithExitCode 1 } diff --git a/eng/common/post-build/post-build-utils.ps1 b/eng/common/post-build/post-build-utils.ps1 index 551ae113f8..7d49744795 100644 --- a/eng/common/post-build/post-build-utils.ps1 +++ b/eng/common/post-build/post-build-utils.ps1 @@ -1,16 +1,17 @@ # Most of the functions in this file require the variables `MaestroApiEndPoint`, # `MaestroApiVersion` and `MaestroApiAccessToken` to be globally available. -$ErrorActionPreference = "Stop" +$ErrorActionPreference = 'Stop' Set-StrictMode -Version 2.0 # `tools.ps1` checks $ci to perform some actions. Since the post-build # scripts don't necessarily execute in the same agent that run the # build.ps1/sh script this variable isn't automatically set. $ci = $true +$disableConfigureToolsetImport = $true . $PSScriptRoot\..\tools.ps1 -function Create-MaestroApiRequestHeaders([string]$ContentType = "application/json") { +function Create-MaestroApiRequestHeaders([string]$ContentType = 'application/json') { Validate-MaestroVars $headers = New-Object 'System.Collections.Generic.Dictionary[[String],[String]]' @@ -50,14 +51,6 @@ function Get-MaestroSubscriptions([string]$SourceRepository, [int]$ChannelId) { return $result } -function Trigger-Subscription([string]$SubscriptionId) { - Validate-MaestroVars - - $apiHeaders = Create-MaestroApiRequestHeaders -AuthToken $MaestroApiAccessToken - $apiEndpoint = "$MaestroApiEndPoint/api/subscriptions/$SubscriptionId/trigger?api-version=$MaestroApiVersion" - Invoke-WebRequest -Uri $apiEndpoint -Headers $apiHeaders -Method Post | Out-Null -} - function Assign-BuildToChannel([int]$BuildId, [int]$ChannelId) { Validate-MaestroVars @@ -66,24 +59,32 @@ function Assign-BuildToChannel([int]$BuildId, [int]$ChannelId) { Invoke-WebRequest -Method Post -Uri $apiEndpoint -Headers $apiHeaders | Out-Null } +function Trigger-Subscription([string]$SubscriptionId) { + Validate-MaestroVars + + $apiHeaders = Create-MaestroApiRequestHeaders -AuthToken $MaestroApiAccessToken + $apiEndpoint = "$MaestroApiEndPoint/api/subscriptions/$SubscriptionId/trigger?api-version=$MaestroApiVersion" + Invoke-WebRequest -Uri $apiEndpoint -Headers $apiHeaders -Method Post | Out-Null +} + function Validate-MaestroVars { try { Get-Variable MaestroApiEndPoint -Scope Global | Out-Null Get-Variable MaestroApiVersion -Scope Global | Out-Null Get-Variable MaestroApiAccessToken -Scope Global | Out-Null - if (!($MaestroApiEndPoint -Match "^http[s]?://maestro-(int|prod).westus2.cloudapp.azure.com$")) { - Write-PipelineTaskError "MaestroApiEndPoint is not a valid Maestro URL. '$MaestroApiEndPoint'" + if (!($MaestroApiEndPoint -Match '^http[s]?://maestro-(int|prod).westus2.cloudapp.azure.com$')) { + Write-PipelineTelemetryError -Category 'MaestroVars' -Message "MaestroApiEndPoint is not a valid Maestro URL. '$MaestroApiEndPoint'" ExitWithExitCode 1 } - if (!($MaestroApiVersion -Match "^[0-9]{4}-[0-9]{2}-[0-9]{2}$")) { - Write-PipelineTaskError "MaestroApiVersion does not match a version string in the format yyyy-MM-DD. '$MaestroApiVersion'" + if (!($MaestroApiVersion -Match '^[0-9]{4}-[0-9]{2}-[0-9]{2}$')) { + Write-PipelineTelemetryError -Category 'MaestroVars' -Message "MaestroApiVersion does not match a version string in the format yyyy-MM-DD. '$MaestroApiVersion'" ExitWithExitCode 1 } } catch { - Write-PipelineTaskError "Error: Variables `MaestroApiEndPoint`, `MaestroApiVersion` and `MaestroApiAccessToken` are required while using this script." + Write-PipelineTelemetryError -Category 'MaestroVars' -Message 'Error: Variables `MaestroApiEndPoint`, `MaestroApiVersion` and `MaestroApiAccessToken` are required while using this script.' Write-Host $_ ExitWithExitCode 1 } diff --git a/eng/common/post-build/setup-maestro-vars.ps1 b/eng/common/post-build/setup-maestro-vars.ps1 deleted file mode 100644 index d7f64dc63c..0000000000 --- a/eng/common/post-build/setup-maestro-vars.ps1 +++ /dev/null @@ -1,26 +0,0 @@ -param( - [Parameter(Mandatory=$true)][string] $ReleaseConfigsPath # Full path to ReleaseConfigs.txt asset -) - -. $PSScriptRoot\post-build-utils.ps1 - -try { - $Content = Get-Content $ReleaseConfigsPath - - $BarId = $Content | Select -Index 0 - - $Channels = "" - $Content | Select -Index 1 | ForEach-Object { $Channels += "$_ ," } - - $IsStableBuild = $Content | Select -Index 2 - - Write-PipelineSetVariable -Name 'BARBuildId' -Value $BarId - Write-PipelineSetVariable -Name 'InitialChannels' -Value "$Channels" - Write-PipelineSetVariable -Name 'IsStableBuild' -Value $IsStableBuild -} -catch { - Write-Host $_ - Write-Host $_.Exception - Write-Host $_.ScriptStackTrace - ExitWithExitCode 1 -} diff --git a/eng/common/post-build/sourcelink-validation.ps1 b/eng/common/post-build/sourcelink-validation.ps1 index bbfdacca13..cc9d059d04 100644 --- a/eng/common/post-build/sourcelink-validation.ps1 +++ b/eng/common/post-build/sourcelink-validation.ps1 @@ -34,9 +34,9 @@ $ValidatePackage = { # Extensions for which we'll look for SourceLink information # For now we'll only care about Portable & Embedded PDBs - $RelevantExtensions = @(".dll", ".exe", ".pdb") + $RelevantExtensions = @('.dll', '.exe', '.pdb') - Write-Host -NoNewLine "Validating" ([System.IO.Path]::GetFileName($PackagePath)) "... " + Write-Host -NoNewLine 'Validating ' ([System.IO.Path]::GetFileName($PackagePath)) '...' $PackageId = [System.IO.Path]::GetFileNameWithoutExtension($PackagePath) $ExtractPath = Join-Path -Path $using:ExtractPath -ChildPath $PackageId @@ -58,7 +58,7 @@ $ValidatePackage = { $TargetFile = Join-Path -Path $ExtractPath -ChildPath $FakeName # We ignore resource DLLs - if ($FileName.EndsWith(".resources.dll")) { + if ($FileName.EndsWith('.resources.dll')) { return } @@ -96,7 +96,7 @@ $ValidatePackage = { $Uri = $Link -as [System.URI] # Only GitHub links are valid - if ($Uri.AbsoluteURI -ne $null -and ($Uri.Host -match "github" -or $Uri.Host -match "githubusercontent")) { + if ($Uri.AbsoluteURI -ne $null -and ($Uri.Host -match 'github' -or $Uri.Host -match 'githubusercontent')) { $Status = (Invoke-WebRequest -Uri $Link -UseBasicParsing -Method HEAD -TimeoutSec 5).StatusCode } else { @@ -143,19 +143,19 @@ $ValidatePackage = { } if ($FailedFiles -eq 0) { - Write-Host "Passed." + Write-Host 'Passed.' return 0 } else { - Write-Host "$PackagePath has broken SourceLink links." + Write-PipelineTelemetryError -Category 'SourceLink' -Message "$PackagePath has broken SourceLink links." return 1 } } function ValidateSourceLinkLinks { - if ($GHRepoName -ne "" -and !($GHRepoName -Match "^[^\s\/]+/[^\s\/]+$")) { - if (!($GHRepoName -Match "^[^\s-]+-[^\s]+$")) { - Write-PipelineTaskError "GHRepoName should be in the format / or -. '$GHRepoName'" + if ($GHRepoName -ne '' -and !($GHRepoName -Match '^[^\s\/]+/[^\s\/]+$')) { + if (!($GHRepoName -Match '^[^\s-]+-[^\s]+$')) { + Write-PipelineTelemetryError -Category 'SourceLink' -Message "GHRepoName should be in the format / or -. '$GHRepoName'" ExitWithExitCode 1 } else { @@ -163,14 +163,14 @@ function ValidateSourceLinkLinks { } } - if ($GHCommit -ne "" -and !($GHCommit -Match "^[0-9a-fA-F]{40}$")) { - Write-PipelineTaskError "GHCommit should be a 40 chars hexadecimal string. '$GHCommit'" + if ($GHCommit -ne '' -and !($GHCommit -Match '^[0-9a-fA-F]{40}$')) { + Write-PipelineTelemetryError -Category 'SourceLink' -Message "GHCommit should be a 40 chars hexadecimal string. '$GHCommit'" ExitWithExitCode 1 } - if ($GHRepoName -ne "" -and $GHCommit -ne "") { - $RepoTreeURL = -Join("http://api.github.com/repos/", $GHRepoName, "/git/trees/", $GHCommit, "?recursive=1") - $CodeExtensions = @(".cs", ".vb", ".fs", ".fsi", ".fsx", ".fsscript") + if ($GHRepoName -ne '' -and $GHCommit -ne '') { + $RepoTreeURL = -Join('http://api.github.com/repos/', $GHRepoName, '/git/trees/', $GHCommit, '?recursive=1') + $CodeExtensions = @('.cs', '.vb', '.fs', '.fsi', '.fsx', '.fsscript') try { # Retrieve the list of files in the repo at that particular commit point and store them in the RepoFiles hash @@ -188,8 +188,8 @@ function ValidateSourceLinkLinks { Write-Host "Problems downloading the list of files from the repo. Url used: $RepoTreeURL . Execution will proceed without caching." } } - elseif ($GHRepoName -ne "" -or $GHCommit -ne "") { - Write-Host "For using the http caching mechanism both GHRepoName and GHCommit should be informed." + elseif ($GHRepoName -ne '' -or $GHCommit -ne '') { + Write-Host 'For using the http caching mechanism both GHRepoName and GHCommit should be informed.' } if (Test-Path $ExtractPath) { @@ -217,18 +217,18 @@ function ValidateSourceLinkLinks { $ValidationFailures = 0 foreach ($Job in @(Get-Job)) { $jobResult = Wait-Job -Id $Job.Id | Receive-Job - if ($jobResult -ne "0") { + if ($jobResult -ne '0') { $ValidationFailures++ } } if ($ValidationFailures -gt 0) { - Write-PipelineTaskError " $ValidationFailures package(s) failed validation." + Write-PipelineTelemetryError -Category 'SourceLink' -Message "$ValidationFailures package(s) failed validation." ExitWithExitCode 1 } } function InstallSourcelinkCli { - $sourcelinkCliPackageName = "sourcelink" + $sourcelinkCliPackageName = 'sourcelink' $dotnetRoot = InitializeDotNetCli -install:$true $dotnet = "$dotnetRoot\dotnet.exe" @@ -239,7 +239,7 @@ function InstallSourcelinkCli { } else { Write-Host "Installing SourceLink CLI version $sourcelinkCliVersion..." - Write-Host "You may need to restart your command window if this is the first dotnet tool you have installed." + Write-Host 'You may need to restart your command window if this is the first dotnet tool you have installed.' & "$dotnet" tool install $sourcelinkCliPackageName --version $sourcelinkCliVersion --verbosity "minimal" --global } } @@ -250,8 +250,8 @@ try { ValidateSourceLinkLinks } catch { - Write-Host $_ Write-Host $_.Exception Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Category 'SourceLink' -Message $_ ExitWithExitCode 1 } diff --git a/eng/common/post-build/symbols-validation.ps1 b/eng/common/post-build/symbols-validation.ps1 index 096ac321d1..f7cfe986dd 100644 --- a/eng/common/post-build/symbols-validation.ps1 +++ b/eng/common/post-build/symbols-validation.ps1 @@ -4,10 +4,6 @@ param( [Parameter(Mandatory=$true)][string] $DotnetSymbolVersion # Version of dotnet symbol to use ) -. $PSScriptRoot\post-build-utils.ps1 - -Add-Type -AssemblyName System.IO.Compression.FileSystem - function FirstMatchingSymbolDescriptionOrDefault { param( [string] $FullPath, # Full path to the module that has to be checked @@ -23,19 +19,19 @@ function FirstMatchingSymbolDescriptionOrDefault { # checking and which type of file was uploaded. # The file itself is returned - $SymbolPath = $SymbolsPath + "\" + $FileName + $SymbolPath = $SymbolsPath + '\' + $FileName # PDB file for the module - $PdbPath = $SymbolPath.Replace($Extension, ".pdb") + $PdbPath = $SymbolPath.Replace($Extension, '.pdb') # PDB file for R2R module (created by crossgen) - $NGenPdb = $SymbolPath.Replace($Extension, ".ni.pdb") + $NGenPdb = $SymbolPath.Replace($Extension, '.ni.pdb') # DBG file for a .so library - $SODbg = $SymbolPath.Replace($Extension, ".so.dbg") + $SODbg = $SymbolPath.Replace($Extension, '.so.dbg') # DWARF file for a .dylib - $DylibDwarf = $SymbolPath.Replace($Extension, ".dylib.dwarf") + $DylibDwarf = $SymbolPath.Replace($Extension, '.dylib.dwarf') $dotnetSymbolExe = "$env:USERPROFILE\.dotnet\tools" $dotnetSymbolExe = Resolve-Path "$dotnetSymbolExe\dotnet-symbol.exe" @@ -43,19 +39,19 @@ function FirstMatchingSymbolDescriptionOrDefault { & $dotnetSymbolExe --symbols --modules --windows-pdbs $TargetServerParam $FullPath -o $SymbolsPath | Out-Null if (Test-Path $PdbPath) { - return "PDB" + return 'PDB' } elseif (Test-Path $NGenPdb) { - return "NGen PDB" + return 'NGen PDB' } elseif (Test-Path $SODbg) { - return "DBG for SO" + return 'DBG for SO' } elseif (Test-Path $DylibDwarf) { - return "Dwarf for Dylib" + return 'Dwarf for Dylib' } elseif (Test-Path $SymbolPath) { - return "Module" + return 'Module' } else { return $null @@ -74,7 +70,7 @@ function CountMissingSymbols { } # Extensions for which we'll look for symbols - $RelevantExtensions = @(".dll", ".exe", ".so", ".dylib") + $RelevantExtensions = @('.dll', '.exe', '.so', '.dylib') # How many files are missing symbol information $MissingSymbols = 0 @@ -82,38 +78,38 @@ function CountMissingSymbols { $PackageId = [System.IO.Path]::GetFileNameWithoutExtension($PackagePath) $PackageGuid = New-Guid $ExtractPath = Join-Path -Path $ExtractPath -ChildPath $PackageGuid - $SymbolsPath = Join-Path -Path $ExtractPath -ChildPath "Symbols" + $SymbolsPath = Join-Path -Path $ExtractPath -ChildPath 'Symbols' [System.IO.Compression.ZipFile]::ExtractToDirectory($PackagePath, $ExtractPath) Get-ChildItem -Recurse $ExtractPath | Where-Object {$RelevantExtensions -contains $_.Extension} | ForEach-Object { - if ($_.FullName -Match "\\ref\\") { - Write-Host "`t Ignoring reference assembly file" $_.FullName + if ($_.FullName -Match '\\ref\\') { + Write-Host "`t Ignoring reference assembly file " $_.FullName return } - $SymbolsOnMSDL = FirstMatchingSymbolDescriptionOrDefault $_.FullName "--microsoft-symbol-server" $SymbolsPath - $SymbolsOnSymWeb = FirstMatchingSymbolDescriptionOrDefault $_.FullName "--internal-server" $SymbolsPath + $SymbolsOnMSDL = FirstMatchingSymbolDescriptionOrDefault $_.FullName '--microsoft-symbol-server' $SymbolsPath + $SymbolsOnSymWeb = FirstMatchingSymbolDescriptionOrDefault $_.FullName '--internal-server' $SymbolsPath - Write-Host -NoNewLine "`t Checking file" $_.FullName "... " + Write-Host -NoNewLine "`t Checking file " $_.FullName "... " if ($SymbolsOnMSDL -ne $null -and $SymbolsOnSymWeb -ne $null) { - Write-Host "Symbols found on MSDL (" $SymbolsOnMSDL ") and SymWeb (" $SymbolsOnSymWeb ")" + Write-Host "Symbols found on MSDL ($SymbolsOnMSDL) and SymWeb ($SymbolsOnSymWeb)" } else { $MissingSymbols++ if ($SymbolsOnMSDL -eq $null -and $SymbolsOnSymWeb -eq $null) { - Write-Host "No symbols found on MSDL or SymWeb!" + Write-Host 'No symbols found on MSDL or SymWeb!' } else { if ($SymbolsOnMSDL -eq $null) { - Write-Host "No symbols found on MSDL!" + Write-Host 'No symbols found on MSDL!' } else { - Write-Host "No symbols found on SymWeb!" + Write-Host 'No symbols found on SymWeb!' } } } @@ -132,27 +128,27 @@ function CheckSymbolsAvailable { Get-ChildItem "$InputPath\*.nupkg" | ForEach-Object { $FileName = $_.Name - + # These packages from Arcade-Services include some native libraries that # our current symbol uploader can't handle. Below is a workaround until # we get issue: https://github.com/dotnet/arcade/issues/2457 sorted. - if ($FileName -Match "Microsoft\.DotNet\.Darc\.") { + if ($FileName -Match 'Microsoft\.DotNet\.Darc\.') { Write-Host "Ignoring Arcade-services file: $FileName" Write-Host return } - elseif ($FileName -Match "Microsoft\.DotNet\.Maestro\.Tasks\.") { + elseif ($FileName -Match 'Microsoft\.DotNet\.Maestro\.Tasks\.') { Write-Host "Ignoring Arcade-services file: $FileName" Write-Host return } - + Write-Host "Validating $FileName " $Status = CountMissingSymbols "$InputPath\$FileName" - + if ($Status -ne 0) { - Write-PipelineTaskError "Missing symbols for $Status modules in the package $FileName" - ExitWithExitCode $exitCode + Write-PipelineTelemetryError -Category 'CheckSymbols' -Message "Missing symbols for $Status modules in the package $FileName" + ExitWithExitCode $exitCode } Write-Host @@ -160,7 +156,7 @@ function CheckSymbolsAvailable { } function InstallDotnetSymbol { - $dotnetSymbolPackageName = "dotnet-symbol" + $dotnetSymbolPackageName = 'dotnet-symbol' $dotnetRoot = InitializeDotNetCli -install:$true $dotnet = "$dotnetRoot\dotnet.exe" @@ -171,19 +167,22 @@ function InstallDotnetSymbol { } else { Write-Host "Installing dotnet-symbol version $dotnetSymbolVersion..." - Write-Host "You may need to restart your command window if this is the first dotnet tool you have installed." + Write-Host 'You may need to restart your command window if this is the first dotnet tool you have installed.' & "$dotnet" tool install $dotnetSymbolPackageName --version $dotnetSymbolVersion --verbosity "minimal" --global } } try { + . $PSScriptRoot\post-build-utils.ps1 + + Add-Type -AssemblyName System.IO.Compression.FileSystem + InstallDotnetSymbol CheckSymbolsAvailable } catch { - Write-Host $_ - Write-Host $_.Exception Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Category 'CheckSymbols' -Message $_ ExitWithExitCode 1 } diff --git a/eng/common/post-build/trigger-subscriptions.ps1 b/eng/common/post-build/trigger-subscriptions.ps1 index 926d5b4551..55dea518ac 100644 --- a/eng/common/post-build/trigger-subscriptions.ps1 +++ b/eng/common/post-build/trigger-subscriptions.ps1 @@ -2,56 +2,63 @@ param( [Parameter(Mandatory=$true)][string] $SourceRepo, [Parameter(Mandatory=$true)][int] $ChannelId, [Parameter(Mandatory=$true)][string] $MaestroApiAccessToken, - [Parameter(Mandatory=$false)][string] $MaestroApiEndPoint = "https://maestro-prod.westus2.cloudapp.azure.com", - [Parameter(Mandatory=$false)][string] $MaestroApiVersion = "2019-01-16" + [Parameter(Mandatory=$false)][string] $MaestroApiEndPoint = 'https://maestro-prod.westus2.cloudapp.azure.com', + [Parameter(Mandatory=$false)][string] $MaestroApiVersion = '2019-01-16' ) -. $PSScriptRoot\post-build-utils.ps1 +try { + . $PSScriptRoot\post-build-utils.ps1 -# Get all the $SourceRepo subscriptions -$normalizedSourceRepo = $SourceRepo.Replace('dnceng@', '') -$subscriptions = Get-MaestroSubscriptions -SourceRepository $normalizedSourceRepo -ChannelId $ChannelId + # Get all the $SourceRepo subscriptions + $normalizedSourceRepo = $SourceRepo.Replace('dnceng@', '') + $subscriptions = Get-MaestroSubscriptions -SourceRepository $normalizedSourceRepo -ChannelId $ChannelId -if (!$subscriptions) { - Write-Host "No subscriptions found for source repo '$normalizedSourceRepo' in channel '$ChannelId'" - ExitWithExitCode 0 -} + if (!$subscriptions) { + Write-PipelineTelemetryError -Category 'TriggerSubscriptions' -Message "No subscriptions found for source repo '$normalizedSourceRepo' in channel '$ChannelId'" + ExitWithExitCode 0 + } -$subscriptionsToTrigger = New-Object System.Collections.Generic.List[string] -$failedTriggeredSubscription = $false + $subscriptionsToTrigger = New-Object System.Collections.Generic.List[string] + $failedTriggeredSubscription = $false -# Get all enabled subscriptions that need dependency flow on 'everyBuild' -foreach ($subscription in $subscriptions) { - if ($subscription.enabled -and $subscription.policy.updateFrequency -like 'everyBuild' -and $subscription.channel.id -eq $ChannelId) { - Write-Host "Should trigger this subscription: $subscription.id" - [void]$subscriptionsToTrigger.Add($subscription.id) + # Get all enabled subscriptions that need dependency flow on 'everyBuild' + foreach ($subscription in $subscriptions) { + if ($subscription.enabled -and $subscription.policy.updateFrequency -like 'everyBuild' -and $subscription.channel.id -eq $ChannelId) { + Write-Host "Should trigger this subscription: ${$subscription.id}" + [void]$subscriptionsToTrigger.Add($subscription.id) + } + } + + foreach ($subscriptionToTrigger in $subscriptionsToTrigger) { + try { + Write-Host "Triggering subscription '$subscriptionToTrigger'." + + Trigger-Subscription -SubscriptionId $subscriptionToTrigger + + Write-Host 'done.' + } + catch + { + Write-Host "There was an error while triggering subscription '$subscriptionToTrigger'" + Write-Host $_ + Write-Host $_.ScriptStackTrace + $failedTriggeredSubscription = $true + } + } + + if ($subscriptionsToTrigger.Count -eq 0) { + Write-Host "No subscription matched source repo '$normalizedSourceRepo' and channel ID '$ChannelId'." + } + elseif ($failedTriggeredSubscription) { + Write-PipelineTelemetryError -Category 'TriggerSubscriptions' -Message 'At least one subscription failed to be triggered...' + ExitWithExitCode 1 + } + else { + Write-Host 'All subscriptions were triggered successfully!' } } - -foreach ($subscriptionToTrigger in $subscriptionsToTrigger) { - try { - Write-Host "Triggering subscription '$subscriptionToTrigger'." - - Trigger-Subscription -SubscriptionId $subscriptionToTrigger - - Write-Host "done." - } - catch - { - Write-Host "There was an error while triggering subscription '$subscriptionToTrigger'" - Write-Host $_ - Write-Host $_.ScriptStackTrace - $failedTriggeredSubscription = $true - } -} - -if ($subscriptionsToTrigger.Count -eq 0) { - Write-Host "No subscription matched source repo '$normalizedSourceRepo' and channel ID '$ChannelId'." -} -elseif ($failedTriggeredSubscription) { - Write-Host "At least one subscription failed to be triggered..." +catch { + Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Category 'TriggerSubscriptions' -Message $_ ExitWithExitCode 1 } -else { - Write-Host "All subscriptions were triggered successfully!" -} diff --git a/eng/common/sdk-task.ps1 b/eng/common/sdk-task.ps1 index d0eec5163e..3872af59b9 100644 --- a/eng/common/sdk-task.ps1 +++ b/eng/common/sdk-task.ps1 @@ -1,8 +1,8 @@ [CmdletBinding(PositionalBinding=$false)] Param( - [string] $configuration = "Debug", + [string] $configuration = 'Debug', [string] $task, - [string] $verbosity = "minimal", + [string] $verbosity = 'minimal', [string] $msbuildEngine = $null, [switch] $restore, [switch] $prepareMachine, @@ -32,7 +32,7 @@ function Print-Usage() { } function Build([string]$target) { - $logSuffix = if ($target -eq "Execute") { "" } else { ".$target" } + $logSuffix = if ($target -eq 'Execute') { '' } else { ".$target" } $log = Join-Path $LogDir "$task$logSuffix.binlog" $outputPath = Join-Path $ToolsetDir "$task\\" @@ -46,33 +46,32 @@ function Build([string]$target) { } try { - if ($help -or (($null -ne $properties) -and ($properties.Contains("/help") -or $properties.Contains("/?")))) { + if ($help -or (($null -ne $properties) -and ($properties.Contains('/help') -or $properties.Contains('/?')))) { Print-Usage exit 0 } if ($task -eq "") { - Write-Host "Missing required parameter '-task '" -ForegroundColor Red + Write-PipelineTelemetryError -Category 'Build' -Message "Missing required parameter '-task '" -ForegroundColor Red Print-Usage ExitWithExitCode 1 } $taskProject = GetSdkTaskProject $task if (!(Test-Path $taskProject)) { - Write-Host "Unknown task: $task" -ForegroundColor Red + Write-PipelineTelemetryError -Category 'Build' -Message "Unknown task: $task" -ForegroundColor Red ExitWithExitCode 1 } if ($restore) { - Build "Restore" + Build 'Restore' } - Build "Execute" + Build 'Execute' } catch { - Write-Host $_ - Write-Host $_.Exception Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Category 'Build' -Message $_ ExitWithExitCode 1 } diff --git a/eng/common/sdl/execute-all-sdl-tools.ps1 b/eng/common/sdl/execute-all-sdl-tools.ps1 index 01799d63ff..9db582f279 100644 --- a/eng/common/sdl/execute-all-sdl-tools.ps1 +++ b/eng/common/sdl/execute-all-sdl-tools.ps1 @@ -1,100 +1,110 @@ Param( - [string] $GuardianPackageName, # Required: the name of guardian CLI package (not needed if GuardianCliLocation is specified) - [string] $NugetPackageDirectory, # Required: directory where NuGet packages are installed (not needed if GuardianCliLocation is specified) - [string] $GuardianCliLocation, # Optional: Direct location of Guardian CLI executable if GuardianPackageName & NugetPackageDirectory are not specified - [string] $Repository=$env:BUILD_REPOSITORY_NAME, # Required: the name of the repository (e.g. dotnet/arcade) - [string] $BranchName=$env:BUILD_SOURCEBRANCH, # Optional: name of branch or version of gdn settings; defaults to master - [string] $SourceDirectory=$env:BUILD_SOURCESDIRECTORY, # Required: the directory where source files are located - [string] $ArtifactsDirectory = (Join-Path $env:BUILD_SOURCESDIRECTORY ("artifacts")), # Required: the directory where build artifacts are located - [string] $AzureDevOpsAccessToken, # Required: access token for dnceng; should be provided via KeyVault - [string[]] $SourceToolsList, # Optional: list of SDL tools to run on source code - [string[]] $ArtifactToolsList, # Optional: list of SDL tools to run on built artifacts - [bool] $TsaPublish=$False, # Optional: true will publish results to TSA; only set to true after onboarding to TSA; TSA is the automated framework used to upload test results as bugs. - [string] $TsaBranchName=$env:BUILD_SOURCEBRANCH, # Optional: required for TSA publish; defaults to $(Build.SourceBranchName); TSA is the automated framework used to upload test results as bugs. - [string] $TsaRepositoryName=$env:BUILD_REPOSITORY_NAME, # Optional: TSA repository name; will be generated automatically if not submitted; TSA is the automated framework used to upload test results as bugs. - [string] $BuildNumber=$env:BUILD_BUILDNUMBER, # Optional: required for TSA publish; defaults to $(Build.BuildNumber) - [bool] $UpdateBaseline=$False, # Optional: if true, will update the baseline in the repository; should only be run after fixing any issues which need to be fixed - [bool] $TsaOnboard=$False, # Optional: if true, will onboard the repository to TSA; should only be run once; TSA is the automated framework used to upload test results as bugs. - [string] $TsaInstanceUrl, # Optional: only needed if TsaOnboard or TsaPublish is true; the instance-url registered with TSA; TSA is the automated framework used to upload test results as bugs. - [string] $TsaCodebaseName, # Optional: only needed if TsaOnboard or TsaPublish is true; the name of the codebase registered with TSA; TSA is the automated framework used to upload test results as bugs. - [string] $TsaProjectName, # Optional: only needed if TsaOnboard or TsaPublish is true; the name of the project registered with TSA; TSA is the automated framework used to upload test results as bugs. - [string] $TsaNotificationEmail, # Optional: only needed if TsaOnboard is true; the email(s) which will receive notifications of TSA bug filings (e.g. alias@microsoft.com); TSA is the automated framework used to upload test results as bugs. - [string] $TsaCodebaseAdmin, # Optional: only needed if TsaOnboard is true; the aliases which are admins of the TSA codebase (e.g. DOMAIN\alias); TSA is the automated framework used to upload test results as bugs. - [string] $TsaBugAreaPath, # Optional: only needed if TsaOnboard is true; the area path where TSA will file bugs in AzDO; TSA is the automated framework used to upload test results as bugs. - [string] $TsaIterationPath, # Optional: only needed if TsaOnboard is true; the iteration path where TSA will file bugs in AzDO; TSA is the automated framework used to upload test results as bugs. - [string] $GuardianLoggerLevel="Standard", # Optional: the logger level for the Guardian CLI; options are Trace, Verbose, Standard, Warning, and Error - [string[]] $CrScanAdditionalRunConfigParams, # Optional: Additional Params to custom build a CredScan run config in the format @("xyz:abc","sdf:1") - [string[]] $PoliCheckAdditionalRunConfigParams # Optional: Additional Params to custom build a Policheck run config in the format @("xyz:abc","sdf:1") + [string] $GuardianPackageName, # Required: the name of guardian CLI package (not needed if GuardianCliLocation is specified) + [string] $NugetPackageDirectory, # Required: directory where NuGet packages are installed (not needed if GuardianCliLocation is specified) + [string] $GuardianCliLocation, # Optional: Direct location of Guardian CLI executable if GuardianPackageName & NugetPackageDirectory are not specified + [string] $Repository=$env:BUILD_REPOSITORY_NAME, # Required: the name of the repository (e.g. dotnet/arcade) + [string] $BranchName=$env:BUILD_SOURCEBRANCH, # Optional: name of branch or version of gdn settings; defaults to master + [string] $SourceDirectory=$env:BUILD_SOURCESDIRECTORY, # Required: the directory where source files are located + [string] $ArtifactsDirectory = (Join-Path $env:BUILD_ARTIFACTSTAGINGDIRECTORY ('artifacts')), # Required: the directory where build artifacts are located + [string] $AzureDevOpsAccessToken, # Required: access token for dnceng; should be provided via KeyVault + [string[]] $SourceToolsList, # Optional: list of SDL tools to run on source code + [string[]] $ArtifactToolsList, # Optional: list of SDL tools to run on built artifacts + [bool] $TsaPublish=$False, # Optional: true will publish results to TSA; only set to true after onboarding to TSA; TSA is the automated framework used to upload test results as bugs. + [string] $TsaBranchName=$env:BUILD_SOURCEBRANCH, # Optional: required for TSA publish; defaults to $(Build.SourceBranchName); TSA is the automated framework used to upload test results as bugs. + [string] $TsaRepositoryName=$env:BUILD_REPOSITORY_NAME, # Optional: TSA repository name; will be generated automatically if not submitted; TSA is the automated framework used to upload test results as bugs. + [string] $BuildNumber=$env:BUILD_BUILDNUMBER, # Optional: required for TSA publish; defaults to $(Build.BuildNumber) + [bool] $UpdateBaseline=$False, # Optional: if true, will update the baseline in the repository; should only be run after fixing any issues which need to be fixed + [bool] $TsaOnboard=$False, # Optional: if true, will onboard the repository to TSA; should only be run once; TSA is the automated framework used to upload test results as bugs. + [string] $TsaInstanceUrl, # Optional: only needed if TsaOnboard or TsaPublish is true; the instance-url registered with TSA; TSA is the automated framework used to upload test results as bugs. + [string] $TsaCodebaseName, # Optional: only needed if TsaOnboard or TsaPublish is true; the name of the codebase registered with TSA; TSA is the automated framework used to upload test results as bugs. + [string] $TsaProjectName, # Optional: only needed if TsaOnboard or TsaPublish is true; the name of the project registered with TSA; TSA is the automated framework used to upload test results as bugs. + [string] $TsaNotificationEmail, # Optional: only needed if TsaOnboard is true; the email(s) which will receive notifications of TSA bug filings (e.g. alias@microsoft.com); TSA is the automated framework used to upload test results as bugs. + [string] $TsaCodebaseAdmin, # Optional: only needed if TsaOnboard is true; the aliases which are admins of the TSA codebase (e.g. DOMAIN\alias); TSA is the automated framework used to upload test results as bugs. + [string] $TsaBugAreaPath, # Optional: only needed if TsaOnboard is true; the area path where TSA will file bugs in AzDO; TSA is the automated framework used to upload test results as bugs. + [string] $TsaIterationPath, # Optional: only needed if TsaOnboard is true; the iteration path where TSA will file bugs in AzDO; TSA is the automated framework used to upload test results as bugs. + [string] $GuardianLoggerLevel='Standard', # Optional: the logger level for the Guardian CLI; options are Trace, Verbose, Standard, Warning, and Error + [string[]] $CrScanAdditionalRunConfigParams, # Optional: Additional Params to custom build a CredScan run config in the format @("xyz:abc","sdf:1") + [string[]] $PoliCheckAdditionalRunConfigParams # Optional: Additional Params to custom build a Policheck run config in the format @("xyz:abc","sdf:1") ) -$ErrorActionPreference = "Stop" -Set-StrictMode -Version 2.0 -$LASTEXITCODE = 0 +try { + $ErrorActionPreference = 'Stop' + Set-StrictMode -Version 2.0 + $disableConfigureToolsetImport = $true + $LASTEXITCODE = 0 -#Replace repo names to the format of org/repo -if (!($Repository.contains('/'))) { - $RepoName = $Repository -replace '(.*?)-(.*)', '$1/$2'; + . $PSScriptRoot\..\tools.ps1 + + #Replace repo names to the format of org/repo + if (!($Repository.contains('/'))) { + $RepoName = $Repository -replace '(.*?)-(.*)', '$1/$2'; + } + else{ + $RepoName = $Repository; + } + + if ($GuardianPackageName) { + $guardianCliLocation = Join-Path $NugetPackageDirectory (Join-Path $GuardianPackageName (Join-Path 'tools' 'guardian.cmd')) + } else { + $guardianCliLocation = $GuardianCliLocation + } + + $workingDirectory = (Split-Path $SourceDirectory -Parent) + $ValidPath = Test-Path $guardianCliLocation + + if ($ValidPath -eq $False) + { + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message 'Invalid Guardian CLI Location.' + ExitWithExitCode 1 + } + + & $(Join-Path $PSScriptRoot 'init-sdl.ps1') -GuardianCliLocation $guardianCliLocation -Repository $RepoName -BranchName $BranchName -WorkingDirectory $workingDirectory -AzureDevOpsAccessToken $AzureDevOpsAccessToken -GuardianLoggerLevel $GuardianLoggerLevel + $gdnFolder = Join-Path $workingDirectory '.gdn' + + if ($TsaOnboard) { + if ($TsaCodebaseName -and $TsaNotificationEmail -and $TsaCodebaseAdmin -and $TsaBugAreaPath) { + Write-Host "$guardianCliLocation tsa-onboard --codebase-name `"$TsaCodebaseName`" --notification-alias `"$TsaNotificationEmail`" --codebase-admin `"$TsaCodebaseAdmin`" --instance-url `"$TsaInstanceUrl`" --project-name `"$TsaProjectName`" --area-path `"$TsaBugAreaPath`" --iteration-path `"$TsaIterationPath`" --working-directory $workingDirectory --logger-level $GuardianLoggerLevel" + & $guardianCliLocation tsa-onboard --codebase-name "$TsaCodebaseName" --notification-alias "$TsaNotificationEmail" --codebase-admin "$TsaCodebaseAdmin" --instance-url "$TsaInstanceUrl" --project-name "$TsaProjectName" --area-path "$TsaBugAreaPath" --iteration-path "$TsaIterationPath" --working-directory $workingDirectory --logger-level $GuardianLoggerLevel + if ($LASTEXITCODE -ne 0) { + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message "Guardian tsa-onboard failed with exit code $LASTEXITCODE." + ExitWithExitCode $LASTEXITCODE + } + } else { + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message 'Could not onboard to TSA -- not all required values ($TsaCodebaseName, $TsaNotificationEmail, $TsaCodebaseAdmin, $TsaBugAreaPath) were specified.' + ExitWithExitCode 1 + } + } + + if ($ArtifactToolsList -and $ArtifactToolsList.Count -gt 0) { + & $(Join-Path $PSScriptRoot 'run-sdl.ps1') -GuardianCliLocation $guardianCliLocation -WorkingDirectory $workingDirectory -TargetDirectory $ArtifactsDirectory -GdnFolder $gdnFolder -ToolsList $ArtifactToolsList -AzureDevOpsAccessToken $AzureDevOpsAccessToken -UpdateBaseline $UpdateBaseline -GuardianLoggerLevel $GuardianLoggerLevel -CrScanAdditionalRunConfigParams $CrScanAdditionalRunConfigParams -PoliCheckAdditionalRunConfigParams $PoliCheckAdditionalRunConfigParams + } + if ($SourceToolsList -and $SourceToolsList.Count -gt 0) { + & $(Join-Path $PSScriptRoot 'run-sdl.ps1') -GuardianCliLocation $guardianCliLocation -WorkingDirectory $workingDirectory -TargetDirectory $SourceDirectory -GdnFolder $gdnFolder -ToolsList $SourceToolsList -AzureDevOpsAccessToken $AzureDevOpsAccessToken -UpdateBaseline $UpdateBaseline -GuardianLoggerLevel $GuardianLoggerLevel -CrScanAdditionalRunConfigParams $CrScanAdditionalRunConfigParams -PoliCheckAdditionalRunConfigParams $PoliCheckAdditionalRunConfigParams + } + + if ($UpdateBaseline) { + & (Join-Path $PSScriptRoot 'push-gdn.ps1') -Repository $RepoName -BranchName $BranchName -GdnFolder $GdnFolder -AzureDevOpsAccessToken $AzureDevOpsAccessToken -PushReason 'Update baseline' + } + + if ($TsaPublish) { + if ($TsaBranchName -and $BuildNumber) { + if (-not $TsaRepositoryName) { + $TsaRepositoryName = "$($Repository)-$($BranchName)" + } + Write-Host "$guardianCliLocation tsa-publish --all-tools --repository-name `"$TsaRepositoryName`" --branch-name `"$TsaBranchName`" --build-number `"$BuildNumber`" --codebase-name `"$TsaCodebaseName`" --notification-alias `"$TsaNotificationEmail`" --codebase-admin `"$TsaCodebaseAdmin`" --instance-url `"$TsaInstanceUrl`" --project-name `"$TsaProjectName`" --area-path `"$TsaBugAreaPath`" --iteration-path `"$TsaIterationPath`" --working-directory $workingDirectory --logger-level $GuardianLoggerLevel" + & $guardianCliLocation tsa-publish --all-tools --repository-name "$TsaRepositoryName" --branch-name "$TsaBranchName" --build-number "$BuildNumber" --onboard $True --codebase-name "$TsaCodebaseName" --notification-alias "$TsaNotificationEmail" --codebase-admin "$TsaCodebaseAdmin" --instance-url "$TsaInstanceUrl" --project-name "$TsaProjectName" --area-path "$TsaBugAreaPath" --iteration-path "$TsaIterationPath" --working-directory $workingDirectory --logger-level $GuardianLoggerLevel + if ($LASTEXITCODE -ne 0) { + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message "Guardian tsa-publish failed with exit code $LASTEXITCODE." + ExitWithExitCode $LASTEXITCODE + } + } else { + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message 'Could not publish to TSA -- not all required values ($TsaBranchName, $BuildNumber) were specified.' + ExitWithExitCode 1 + } + } } -else{ - $RepoName = $Repository; -} - -if ($GuardianPackageName) { - $guardianCliLocation = Join-Path $NugetPackageDirectory (Join-Path $GuardianPackageName (Join-Path "tools" "guardian.cmd")) -} else { - $guardianCliLocation = $GuardianCliLocation -} - -$workingDirectory = (Split-Path $SourceDirectory -Parent) -$ValidPath = Test-Path $guardianCliLocation - -if ($ValidPath -eq $False) -{ - Write-Host "Invalid Guardian CLI Location." +catch { + Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message $_ exit 1 } - -& $(Join-Path $PSScriptRoot "init-sdl.ps1") -GuardianCliLocation $guardianCliLocation -Repository $RepoName -BranchName $BranchName -WorkingDirectory $workingDirectory -AzureDevOpsAccessToken $AzureDevOpsAccessToken -GuardianLoggerLevel $GuardianLoggerLevel -$gdnFolder = Join-Path $workingDirectory ".gdn" - -if ($TsaOnboard) { - if ($TsaCodebaseName -and $TsaNotificationEmail -and $TsaCodebaseAdmin -and $TsaBugAreaPath) { - Write-Host "$guardianCliLocation tsa-onboard --codebase-name `"$TsaCodebaseName`" --notification-alias `"$TsaNotificationEmail`" --codebase-admin `"$TsaCodebaseAdmin`" --instance-url `"$TsaInstanceUrl`" --project-name `"$TsaProjectName`" --area-path `"$TsaBugAreaPath`" --iteration-path `"$TsaIterationPath`" --working-directory $workingDirectory --logger-level $GuardianLoggerLevel" - & $guardianCliLocation tsa-onboard --codebase-name "$TsaCodebaseName" --notification-alias "$TsaNotificationEmail" --codebase-admin "$TsaCodebaseAdmin" --instance-url "$TsaInstanceUrl" --project-name "$TsaProjectName" --area-path "$TsaBugAreaPath" --iteration-path "$TsaIterationPath" --working-directory $workingDirectory --logger-level $GuardianLoggerLevel - if ($LASTEXITCODE -ne 0) { - Write-Host "Guardian tsa-onboard failed with exit code $LASTEXITCODE." - exit $LASTEXITCODE - } - } else { - Write-Host "Could not onboard to TSA -- not all required values ($$TsaCodebaseName, $$TsaNotificationEmail, $$TsaCodebaseAdmin, $$TsaBugAreaPath) were specified." - exit 1 - } -} - -if ($ArtifactToolsList -and $ArtifactToolsList.Count -gt 0) { - & $(Join-Path $PSScriptRoot "run-sdl.ps1") -GuardianCliLocation $guardianCliLocation -WorkingDirectory $workingDirectory -TargetDirectory $ArtifactsDirectory -GdnFolder $gdnFolder -ToolsList $ArtifactToolsList -AzureDevOpsAccessToken $AzureDevOpsAccessToken -UpdateBaseline $UpdateBaseline -GuardianLoggerLevel $GuardianLoggerLevel -CrScanAdditionalRunConfigParams $CrScanAdditionalRunConfigParams -PoliCheckAdditionalRunConfigParams $PoliCheckAdditionalRunConfigParams -} -if ($SourceToolsList -and $SourceToolsList.Count -gt 0) { - & $(Join-Path $PSScriptRoot "run-sdl.ps1") -GuardianCliLocation $guardianCliLocation -WorkingDirectory $workingDirectory -TargetDirectory $SourceDirectory -GdnFolder $gdnFolder -ToolsList $SourceToolsList -AzureDevOpsAccessToken $AzureDevOpsAccessToken -UpdateBaseline $UpdateBaseline -GuardianLoggerLevel $GuardianLoggerLevel -CrScanAdditionalRunConfigParams $CrScanAdditionalRunConfigParams -PoliCheckAdditionalRunConfigParams $PoliCheckAdditionalRunConfigParams -} - -if ($UpdateBaseline) { - & (Join-Path $PSScriptRoot "push-gdn.ps1") -Repository $RepoName -BranchName $BranchName -GdnFolder $GdnFolder -AzureDevOpsAccessToken $AzureDevOpsAccessToken -PushReason "Update baseline" -} - -if ($TsaPublish) { - if ($TsaBranchName -and $BuildNumber) { - if (-not $TsaRepositoryName) { - $TsaRepositoryName = "$($Repository)-$($BranchName)" - } - Write-Host "$guardianCliLocation tsa-publish --all-tools --repository-name `"$TsaRepositoryName`" --branch-name `"$TsaBranchName`" --build-number `"$BuildNumber`" --codebase-name `"$TsaCodebaseName`" --notification-alias `"$TsaNotificationEmail`" --codebase-admin `"$TsaCodebaseAdmin`" --instance-url `"$TsaInstanceUrl`" --project-name `"$TsaProjectName`" --area-path `"$TsaBugAreaPath`" --iteration-path `"$TsaIterationPath`" --working-directory $workingDirectory --logger-level $GuardianLoggerLevel" - & $guardianCliLocation tsa-publish --all-tools --repository-name "$TsaRepositoryName" --branch-name "$TsaBranchName" --build-number "$BuildNumber" --onboard $True --codebase-name "$TsaCodebaseName" --notification-alias "$TsaNotificationEmail" --codebase-admin "$TsaCodebaseAdmin" --instance-url "$TsaInstanceUrl" --project-name "$TsaProjectName" --area-path "$TsaBugAreaPath" --iteration-path "$TsaIterationPath" --working-directory $workingDirectory --logger-level $GuardianLoggerLevel - if ($LASTEXITCODE -ne 0) { - Write-Host "Guardian tsa-publish failed with exit code $LASTEXITCODE." - exit $LASTEXITCODE - } - } else { - Write-Host "Could not publish to TSA -- not all required values ($$TsaBranchName, $$BuildNumber) were specified." - exit 1 - } -} diff --git a/eng/common/sdl/extract-artifact-packages.ps1 b/eng/common/sdl/extract-artifact-packages.ps1 index 6e6825013b..3c9bf10678 100644 --- a/eng/common/sdl/extract-artifact-packages.ps1 +++ b/eng/common/sdl/extract-artifact-packages.ps1 @@ -3,54 +3,16 @@ param( [Parameter(Mandatory=$true)][string] $ExtractPath # Full path to directory where the packages will be extracted ) -$ErrorActionPreference = "Stop" +$ErrorActionPreference = 'Stop' Set-StrictMode -Version 2.0 # `tools.ps1` checks $ci to perform some actions. Since the post-build # scripts don't necessarily execute in the same agent that run the # build.ps1/sh script this variable isn't automatically set. $ci = $true -. $PSScriptRoot\..\tools.ps1 +$disableConfigureToolsetImport = $true -$ExtractPackage = { - param( - [string] $PackagePath # Full path to a NuGet package - ) - - if (!(Test-Path $PackagePath)) { - Write-PipelineTaskError "Input file does not exist: $PackagePath" - ExitWithExitCode 1 - } - - $RelevantExtensions = @(".dll", ".exe", ".pdb") - Write-Host -NoNewLine "Extracting" ([System.IO.Path]::GetFileName($PackagePath)) "... " - - $PackageId = [System.IO.Path]::GetFileNameWithoutExtension($PackagePath) - $ExtractPath = Join-Path -Path $using:ExtractPath -ChildPath $PackageId - - Add-Type -AssemblyName System.IO.Compression.FileSystem - - [System.IO.Directory]::CreateDirectory($ExtractPath); - - try { - $zip = [System.IO.Compression.ZipFile]::OpenRead($PackagePath) - - $zip.Entries | - Where-Object {$RelevantExtensions -contains [System.IO.Path]::GetExtension($_.Name)} | - ForEach-Object { - $TargetFile = Join-Path -Path $ExtractPath -ChildPath $_.Name - - [System.IO.Compression.ZipFileExtensions]::ExtractToFile($_, $TargetFile, $true) - } - } - catch { - - } - finally { - $zip.Dispose() - } - } - function ExtractArtifacts { +function ExtractArtifacts { if (!(Test-Path $InputPath)) { Write-Host "Input Path does not exist: $InputPath" ExitWithExitCode 0 @@ -67,11 +29,52 @@ $ExtractPackage = { } try { + . $PSScriptRoot\..\tools.ps1 + + $ExtractPackage = { + param( + [string] $PackagePath # Full path to a NuGet package + ) + + if (!(Test-Path $PackagePath)) { + Write-PipelineTelemetryError -Category 'Build' -Message "Input file does not exist: $PackagePath" + ExitWithExitCode 1 + } + + $RelevantExtensions = @('.dll', '.exe', '.pdb') + Write-Host -NoNewLine 'Extracting ' ([System.IO.Path]::GetFileName($PackagePath)) '...' + + $PackageId = [System.IO.Path]::GetFileNameWithoutExtension($PackagePath) + $ExtractPath = Join-Path -Path $using:ExtractPath -ChildPath $PackageId + + Add-Type -AssemblyName System.IO.Compression.FileSystem + + [System.IO.Directory]::CreateDirectory($ExtractPath); + + try { + $zip = [System.IO.Compression.ZipFile]::OpenRead($PackagePath) + + $zip.Entries | + Where-Object {$RelevantExtensions -contains [System.IO.Path]::GetExtension($_.Name)} | + ForEach-Object { + $TargetFile = Join-Path -Path $ExtractPath -ChildPath $_.Name + + [System.IO.Compression.ZipFileExtensions]::ExtractToFile($_, $TargetFile, $true) + } + } + catch { + Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message $_ + ExitWithExitCode 1 + } + finally { + $zip.Dispose() + } + } Measure-Command { ExtractArtifacts } } catch { - Write-Host $_ - Write-Host $_.Exception Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message $_ ExitWithExitCode 1 } diff --git a/eng/common/sdl/init-sdl.ps1 b/eng/common/sdl/init-sdl.ps1 index c737eb0e71..285f1ccdb0 100644 --- a/eng/common/sdl/init-sdl.ps1 +++ b/eng/common/sdl/init-sdl.ps1 @@ -1,16 +1,19 @@ Param( [string] $GuardianCliLocation, [string] $Repository, - [string] $BranchName="master", + [string] $BranchName='master', [string] $WorkingDirectory, [string] $AzureDevOpsAccessToken, - [string] $GuardianLoggerLevel="Standard" + [string] $GuardianLoggerLevel='Standard' ) -$ErrorActionPreference = "Stop" +$ErrorActionPreference = 'Stop' Set-StrictMode -Version 2.0 +$disableConfigureToolsetImport = $true $LASTEXITCODE = 0 +. $PSScriptRoot\..\tools.ps1 + # Don't display the console progress UI - it's a huge perf hit $ProgressPreference = 'SilentlyContinue' @@ -21,11 +24,10 @@ $uri = "https://dev.azure.com/dnceng/internal/_apis/git/repositories/sdl-tool-cf $zipFile = "$WorkingDirectory/gdn.zip" Add-Type -AssemblyName System.IO.Compression.FileSystem -$gdnFolder = (Join-Path $WorkingDirectory ".gdn") -Try -{ +$gdnFolder = (Join-Path $WorkingDirectory '.gdn') +try { # We try to download the zip; if the request fails (e.g. the file doesn't exist), we catch it and init guardian instead - Write-Host "Downloading gdn folder from internal config repostiory..." + Write-Host 'Downloading gdn folder from internal config repostiory...' Invoke-WebRequest -Headers @{ "Accept"="application/zip"; "Authorization"="Basic $encodedPat" } -Uri $uri -OutFile $zipFile if (Test-Path $gdnFolder) { # Remove the gdn folder if it exists (it shouldn't unless there's too much caching; this is just in case) @@ -33,19 +35,29 @@ Try } [System.IO.Compression.ZipFile]::ExtractToDirectory($zipFile, $WorkingDirectory) Write-Host $gdnFolder -} Catch [System.Net.WebException] { + ExitWithExitCode 0 +} catch [System.Net.WebException] { } # Catch and ignore webexception +try { # if the folder does not exist, we'll do a guardian init and push it to the remote repository - Write-Host "Initializing Guardian..." + Write-Host 'Initializing Guardian...' Write-Host "$GuardianCliLocation init --working-directory $WorkingDirectory --logger-level $GuardianLoggerLevel" & $GuardianCliLocation init --working-directory $WorkingDirectory --logger-level $GuardianLoggerLevel if ($LASTEXITCODE -ne 0) { - Write-Error "Guardian init failed with exit code $LASTEXITCODE." + Write-PipelineTelemetryError -Force -Category 'Build' -Message "Guardian init failed with exit code $LASTEXITCODE." + ExitWithExitCode $LASTEXITCODE } # We create the mainbaseline so it can be edited later Write-Host "$GuardianCliLocation baseline --working-directory $WorkingDirectory --name mainbaseline" & $GuardianCliLocation baseline --working-directory $WorkingDirectory --name mainbaseline if ($LASTEXITCODE -ne 0) { - Write-Error "Guardian baseline failed with exit code $LASTEXITCODE." + Write-PipelineTelemetryError -Force -Category 'Build' -Message "Guardian baseline failed with exit code $LASTEXITCODE." + ExitWithExitCode $LASTEXITCODE } - & $(Join-Path $PSScriptRoot "push-gdn.ps1") -Repository $Repository -BranchName $BranchName -GdnFolder $gdnFolder -AzureDevOpsAccessToken $AzureDevOpsAccessToken -PushReason "Initialize gdn folder" -} \ No newline at end of file + & $(Join-Path $PSScriptRoot 'push-gdn.ps1') -Repository $Repository -BranchName $BranchName -GdnFolder $gdnFolder -AzureDevOpsAccessToken $AzureDevOpsAccessToken -PushReason 'Initialize gdn folder' + ExitWithExitCode 0 +} +catch { + Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Category 'Sdl' -Message $_ + ExitWithExitCode 1 +} diff --git a/eng/common/sdl/push-gdn.ps1 b/eng/common/sdl/push-gdn.ps1 index 79c707d6d8..79d3d355c7 100644 --- a/eng/common/sdl/push-gdn.ps1 +++ b/eng/common/sdl/push-gdn.ps1 @@ -1,51 +1,65 @@ Param( [string] $Repository, - [string] $BranchName="master", + [string] $BranchName='master', [string] $GdnFolder, [string] $AzureDevOpsAccessToken, [string] $PushReason ) -$ErrorActionPreference = "Stop" +$ErrorActionPreference = 'Stop' Set-StrictMode -Version 2.0 +$disableConfigureToolsetImport = $true $LASTEXITCODE = 0 -# We create the temp directory where we'll store the sdl-config repository -$sdlDir = Join-Path $env:TEMP "sdl" -if (Test-Path $sdlDir) { - Remove-Item -Force -Recurse $sdlDir -} +try { + . $PSScriptRoot\..\tools.ps1 -Write-Host "git clone https://dnceng:`$AzureDevOpsAccessToken@dev.azure.com/dnceng/internal/_git/sdl-tool-cfg $sdlDir" -git clone https://dnceng:$AzureDevOpsAccessToken@dev.azure.com/dnceng/internal/_git/sdl-tool-cfg $sdlDir -if ($LASTEXITCODE -ne 0) { - Write-Error "Git clone failed with exit code $LASTEXITCODE." -} -# We copy the .gdn folder from our local run into the git repository so it can be committed -$sdlRepositoryFolder = Join-Path (Join-Path (Join-Path $sdlDir $Repository) $BranchName) ".gdn" -if (Get-Command Robocopy) { - Robocopy /S $GdnFolder $sdlRepositoryFolder -} else { - rsync -r $GdnFolder $sdlRepositoryFolder -} -# cd to the sdl-config directory so we can run git there -Push-Location $sdlDir -# git add . --> git commit --> git push -Write-Host "git add ." -git add . -if ($LASTEXITCODE -ne 0) { - Write-Error "Git add failed with exit code $LASTEXITCODE." -} -Write-Host "git -c user.email=`"dn-bot@microsoft.com`" -c user.name=`"Dotnet Bot`" commit -m `"$PushReason for $Repository/$BranchName`"" -git -c user.email="dn-bot@microsoft.com" -c user.name="Dotnet Bot" commit -m "$PushReason for $Repository/$BranchName" -if ($LASTEXITCODE -ne 0) { - Write-Error "Git commit failed with exit code $LASTEXITCODE." -} -Write-Host "git push" -git push -if ($LASTEXITCODE -ne 0) { - Write-Error "Git push failed with exit code $LASTEXITCODE." -} + # We create the temp directory where we'll store the sdl-config repository + $sdlDir = Join-Path $env:TEMP 'sdl' + if (Test-Path $sdlDir) { + Remove-Item -Force -Recurse $sdlDir + } -# Return to the original directory -Pop-Location \ No newline at end of file + Write-Host "git clone https://dnceng:`$AzureDevOpsAccessToken@dev.azure.com/dnceng/internal/_git/sdl-tool-cfg $sdlDir" + git clone https://dnceng:$AzureDevOpsAccessToken@dev.azure.com/dnceng/internal/_git/sdl-tool-cfg $sdlDir + if ($LASTEXITCODE -ne 0) { + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message "Git clone failed with exit code $LASTEXITCODE." + ExitWithExitCode $LASTEXITCODE + } + # We copy the .gdn folder from our local run into the git repository so it can be committed + $sdlRepositoryFolder = Join-Path (Join-Path (Join-Path $sdlDir $Repository) $BranchName) '.gdn' + if (Get-Command Robocopy) { + Robocopy /S $GdnFolder $sdlRepositoryFolder + } else { + rsync -r $GdnFolder $sdlRepositoryFolder + } + # cd to the sdl-config directory so we can run git there + Push-Location $sdlDir + # git add . --> git commit --> git push + Write-Host 'git add .' + git add . + if ($LASTEXITCODE -ne 0) { + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message "Git add failed with exit code $LASTEXITCODE." + ExitWithExitCode $LASTEXITCODE + } + Write-Host "git -c user.email=`"dn-bot@microsoft.com`" -c user.name=`"Dotnet Bot`" commit -m `"$PushReason for $Repository/$BranchName`"" + git -c user.email="dn-bot@microsoft.com" -c user.name="Dotnet Bot" commit -m "$PushReason for $Repository/$BranchName" + if ($LASTEXITCODE -ne 0) { + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message "Git commit failed with exit code $LASTEXITCODE." + ExitWithExitCode $LASTEXITCODE + } + Write-Host 'git push' + git push + if ($LASTEXITCODE -ne 0) { + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message "Git push failed with exit code $LASTEXITCODE." + ExitWithExitCode $LASTEXITCODE + } + + # Return to the original directory + Pop-Location +} +catch { + Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Category 'Sdl' -Message $_ + ExitWithExitCode 1 +} \ No newline at end of file diff --git a/eng/common/sdl/run-sdl.ps1 b/eng/common/sdl/run-sdl.ps1 index 9bc25314ae..40a084f796 100644 --- a/eng/common/sdl/run-sdl.ps1 +++ b/eng/common/sdl/run-sdl.ps1 @@ -5,55 +5,65 @@ Param( [string] $GdnFolder, [string[]] $ToolsList, [string] $UpdateBaseline, - [string] $GuardianLoggerLevel="Standard", + [string] $GuardianLoggerLevel='Standard', [string[]] $CrScanAdditionalRunConfigParams, [string[]] $PoliCheckAdditionalRunConfigParams ) -$ErrorActionPreference = "Stop" +$ErrorActionPreference = 'Stop' Set-StrictMode -Version 2.0 +$disableConfigureToolsetImport = $true $LASTEXITCODE = 0 -# We store config files in the r directory of .gdn -Write-Host $ToolsList -$gdnConfigPath = Join-Path $GdnFolder "r" -$ValidPath = Test-Path $GuardianCliLocation +try { + . $PSScriptRoot\..\tools.ps1 -if ($ValidPath -eq $False) -{ - Write-Host "Invalid Guardian CLI Location." - exit 1 -} + # We store config files in the r directory of .gdn + Write-Host $ToolsList + $gdnConfigPath = Join-Path $GdnFolder 'r' + $ValidPath = Test-Path $GuardianCliLocation -$configParam = @("--config") - -foreach ($tool in $ToolsList) { - $gdnConfigFile = Join-Path $gdnConfigPath "$tool-configure.gdnconfig" - Write-Host $tool - # We have to manually configure tools that run on source to look at the source directory only - if ($tool -eq "credscan") { - Write-Host "$GuardianCliLocation configure --working-directory $WorkingDirectory --tool $tool --output-path $gdnConfigFile --logger-level $GuardianLoggerLevel --noninteractive --force --args `" TargetDirectory < $TargetDirectory `" `" OutputType < pre `" $(If ($CrScanAdditionalRunConfigParams) {$CrScanAdditionalRunConfigParams})" - & $GuardianCliLocation configure --working-directory $WorkingDirectory --tool $tool --output-path $gdnConfigFile --logger-level $GuardianLoggerLevel --noninteractive --force --args " TargetDirectory < $TargetDirectory " "OutputType < pre" $(If ($CrScanAdditionalRunConfigParams) {$CrScanAdditionalRunConfigParams}) - if ($LASTEXITCODE -ne 0) { - Write-Host "Guardian configure for $tool failed with exit code $LASTEXITCODE." - exit $LASTEXITCODE - } - } - if ($tool -eq "policheck") { - Write-Host "$GuardianCliLocation configure --working-directory $WorkingDirectory --tool $tool --output-path $gdnConfigFile --logger-level $GuardianLoggerLevel --noninteractive --force --args `" Target < $TargetDirectory `" $(If ($PoliCheckAdditionalRunConfigParams) {$PoliCheckAdditionalRunConfigParams})" - & $GuardianCliLocation configure --working-directory $WorkingDirectory --tool $tool --output-path $gdnConfigFile --logger-level $GuardianLoggerLevel --noninteractive --force --args " Target < $TargetDirectory " $(If ($PoliCheckAdditionalRunConfigParams) {$PoliCheckAdditionalRunConfigParams}) - if ($LASTEXITCODE -ne 0) { - Write-Host "Guardian configure for $tool failed with exit code $LASTEXITCODE." - exit $LASTEXITCODE - } + if ($ValidPath -eq $False) + { + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message "Invalid Guardian CLI Location." + ExitWithExitCode 1 } - $configParam+=$gdnConfigFile -} + $configParam = @('--config') -Write-Host "$GuardianCliLocation run --working-directory $WorkingDirectory --baseline mainbaseline --update-baseline $UpdateBaseline --logger-level $GuardianLoggerLevel $configParam" -& $GuardianCliLocation run --working-directory $WorkingDirectory --tool $tool --baseline mainbaseline --update-baseline $UpdateBaseline --logger-level $GuardianLoggerLevel $configParam -if ($LASTEXITCODE -ne 0) { - Write-Host "Guardian run for $ToolsList using $configParam failed with exit code $LASTEXITCODE." - exit $LASTEXITCODE + foreach ($tool in $ToolsList) { + $gdnConfigFile = Join-Path $gdnConfigPath "$tool-configure.gdnconfig" + Write-Host $tool + # We have to manually configure tools that run on source to look at the source directory only + if ($tool -eq 'credscan') { + Write-Host "$GuardianCliLocation configure --working-directory $WorkingDirectory --tool $tool --output-path $gdnConfigFile --logger-level $GuardianLoggerLevel --noninteractive --force --args `" TargetDirectory < $TargetDirectory `" `" OutputType < pre `" $(If ($CrScanAdditionalRunConfigParams) {$CrScanAdditionalRunConfigParams})" + & $GuardianCliLocation configure --working-directory $WorkingDirectory --tool $tool --output-path $gdnConfigFile --logger-level $GuardianLoggerLevel --noninteractive --force --args " TargetDirectory < $TargetDirectory " "OutputType < pre" $(If ($CrScanAdditionalRunConfigParams) {$CrScanAdditionalRunConfigParams}) + if ($LASTEXITCODE -ne 0) { + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message "Guardian configure for $tool failed with exit code $LASTEXITCODE." + ExitWithExitCode $LASTEXITCODE + } + } + if ($tool -eq 'policheck') { + Write-Host "$GuardianCliLocation configure --working-directory $WorkingDirectory --tool $tool --output-path $gdnConfigFile --logger-level $GuardianLoggerLevel --noninteractive --force --args `" Target < $TargetDirectory `" $(If ($PoliCheckAdditionalRunConfigParams) {$PoliCheckAdditionalRunConfigParams})" + & $GuardianCliLocation configure --working-directory $WorkingDirectory --tool $tool --output-path $gdnConfigFile --logger-level $GuardianLoggerLevel --noninteractive --force --args " Target < $TargetDirectory " $(If ($PoliCheckAdditionalRunConfigParams) {$PoliCheckAdditionalRunConfigParams}) + if ($LASTEXITCODE -ne 0) { + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message "Guardian configure for $tool failed with exit code $LASTEXITCODE." + ExitWithExitCode $LASTEXITCODE + } + } + + $configParam+=$gdnConfigFile + } + + Write-Host "$GuardianCliLocation run --working-directory $WorkingDirectory --baseline mainbaseline --update-baseline $UpdateBaseline --logger-level $GuardianLoggerLevel $configParam" + & $GuardianCliLocation run --working-directory $WorkingDirectory --tool $tool --baseline mainbaseline --update-baseline $UpdateBaseline --logger-level $GuardianLoggerLevel $configParam + if ($LASTEXITCODE -ne 0) { + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message "Guardian run for $ToolsList using $configParam failed with exit code $LASTEXITCODE." + ExitWithExitCode $LASTEXITCODE + } } +catch { + Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Category 'Sdl' -Message $_ + ExitWithExitCode 1 +} \ No newline at end of file diff --git a/eng/common/templates/job/execute-sdl.yml b/eng/common/templates/job/execute-sdl.yml index 52e2ff021d..640f2b04e2 100644 --- a/eng/common/templates/job/execute-sdl.yml +++ b/eng/common/templates/job/execute-sdl.yml @@ -1,4 +1,5 @@ parameters: + enable: 'false' # Whether the SDL validation job should execute or not overrideParameters: '' # Optional: to override values for parameters. additionalParameters: '' # Optional: parameters that need user specific values eg: '-SourceToolsList @("abc","def") -ArtifactToolsList @("ghi","jkl")' # There is some sort of bug (has been reported) in Azure DevOps where if this parameter is named @@ -16,8 +17,15 @@ jobs: - job: Run_SDL dependsOn: ${{ parameters.dependsOn }} displayName: Run SDL tool + condition: eq( ${{ parameters.enable }}, 'true') variables: - group: DotNet-VSTS-Bot + - name: AzDOProjectName + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOProjectName'] ] + - name: AzDOPipelineId + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOPipelineId'] ] + - name: AzDOBuildId + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOBuildId'] ] pool: name: Hosted VS2017 steps: @@ -28,25 +36,33 @@ jobs: - task: DownloadBuildArtifacts@0 displayName: Download Build Artifacts inputs: - buildType: current + buildType: specific + buildVersionToDownload: specific + project: $(AzDOProjectName) + pipeline: $(AzDOPipelineId) + buildId: $(AzDOBuildId) artifactName: ${{ artifactName }} downloadPath: $(Build.ArtifactStagingDirectory)\artifacts - ${{ if eq(parameters.artifactNames, '') }}: - task: DownloadBuildArtifacts@0 displayName: Download Build Artifacts inputs: - buildType: current + buildType: specific + buildVersionToDownload: specific + project: $(AzDOProjectName) + pipeline: $(AzDOPipelineId) + buildId: $(AzDOBuildId) downloadType: specific files itemPattern: "**" downloadPath: $(Build.ArtifactStagingDirectory)\artifacts - powershell: eng/common/sdl/extract-artifact-packages.ps1 - -InputPath $(Build.SourcesDirectory)\artifacts\BlobArtifacts - -ExtractPath $(Build.SourcesDirectory)\artifacts\BlobArtifacts + -InputPath $(Build.ArtifactStagingDirectory)\artifacts\BlobArtifacts + -ExtractPath $(Build.ArtifactStagingDirectory)\artifacts\BlobArtifacts displayName: Extract Blob Artifacts continueOnError: ${{ parameters.sdlContinueOnError }} - powershell: eng/common/sdl/extract-artifact-packages.ps1 - -InputPath $(Build.SourcesDirectory)\artifacts\PackageArtifacts - -ExtractPath $(Build.SourcesDirectory)\artifacts\PackageArtifacts + -InputPath $(Build.ArtifactStagingDirectory)\artifacts\PackageArtifacts + -ExtractPath $(Build.ArtifactStagingDirectory)\artifacts\PackageArtifacts displayName: Extract Package Artifacts continueOnError: ${{ parameters.sdlContinueOnError }} - task: NuGetToolInstaller@1 diff --git a/eng/common/templates/job/job.yml b/eng/common/templates/job/job.yml index ffda80a197..ecebd0f03e 100644 --- a/eng/common/templates/job/job.yml +++ b/eng/common/templates/job/job.yml @@ -1,67 +1,33 @@ +# Internal resources (telemetry, microbuild) can only be accessed from non-public projects, +# and some (Microbuild) should only be applied to non-PR cases for internal builds. + parameters: # Job schema parameters - https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=vsts&tabs=schema#job cancelTimeoutInMinutes: '' - condition: '' - - continueOnError: false - container: '' - + continueOnError: false dependsOn: '' - displayName: '' - - steps: [] - pool: '' - + steps: [] strategy: '' - timeoutInMinutes: '' - variables: [] - workspace: '' - # Job base template specific parameters - # Optional: Enable installing Microbuild plugin - # if 'true', these "variables" must be specified in the variables object or as part of the queue matrix - # _TeamName - the name of your team - # _SignType - 'test' or 'real' +# Job base template specific parameters + # See schema documentation - https://github.com/dotnet/arcade/blob/master/Documentation/AzureDevOps/TemplateSchema.md + artifacts: '' enableMicrobuild: false - - # Optional: Include PublishBuildArtifacts task enablePublishBuildArtifacts: false - - # Optional: Enable publishing to the build asset registry enablePublishBuildAssets: false - - # Optional: Prevent gather/push manifest from executing when using publishing pipelines - enablePublishUsingPipelines: false - - # Optional: Include PublishTestResults task enablePublishTestResults: false - - # Optional: enable sending telemetry - enableTelemetry: false - - # Optional: define the helix repo for telemetry (example: 'dotnet/arcade') - helixRepo: '' - - # Optional: define the helix type for telemetry (example: 'build/product/') - helixType: '' - - # Required: name of the job + enablePublishUsingPipelines: false name: '' - - # Optional: should run as a public build even in the internal project - # if 'true', the build won't run any of the internal only steps, even if it is running in non-public projects. + preSteps: [] runAsPublic: false -# Internal resources (telemetry, microbuild) can only be accessed from non-public projects, -# and some (Microbuild) should only be applied to non-PR cases for internal builds. - jobs: - job: ${{ parameters.name }} @@ -93,7 +59,7 @@ jobs: timeoutInMinutes: ${{ parameters.timeoutInMinutes }} variables: - - ${{ if eq(parameters.enableTelemetry, 'true') }}: + - ${{ if ne(parameters.enableTelemetry, 'false') }}: - name: DOTNET_CLI_TELEMETRY_PROFILE value: '$(Build.Repository.Uri)' - ${{ each variable in parameters.variables }}: @@ -125,21 +91,12 @@ jobs: workspace: ${{ parameters.workspace }} steps: - - ${{ if eq(parameters.enableTelemetry, 'true') }}: - # Telemetry tasks are built from https://github.com/dotnet/arcade-extensions - - task: sendStartTelemetry@0 - displayName: 'Send Helix Start Telemetry' - inputs: - helixRepo: ${{ parameters.helixRepo }} - ${{ if ne(parameters.helixType, '') }}: - helixType: ${{ parameters.helixType }} - buildConfig: $(_BuildConfig) - runAsPublic: ${{ parameters.runAsPublic }} - continueOnError: ${{ parameters.continueOnError }} - condition: always() + - ${{ if ne(parameters.preSteps, '') }}: + - ${{ each preStep in parameters.preSteps }}: + - ${{ preStep }} - - ${{ if eq(parameters.enableMicrobuild, 'true') }}: - - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - ${{ if eq(parameters.enableMicrobuild, 'true') }}: - task: MicroBuildSigningPlugin@2 displayName: Install MicroBuild plugin inputs: @@ -151,9 +108,16 @@ jobs: continueOnError: ${{ parameters.continueOnError }} condition: and(succeeded(), in(variables['_SignType'], 'real', 'test'), eq(variables['Agent.Os'], 'Windows_NT')) - - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: - task: NuGetAuthenticate@0 + - ${{ if or(eq(parameters.artifacts.download, 'true'), ne(parameters.artifacts.download, '')) }}: + - task: DownloadPipelineArtifact@2 + inputs: + buildType: current + artifactName: ${{ coalesce(parameters.artifacts.download.name, 'Artifacts_$(Agent.OS)_$(_BuildConfig)') }} + targetPath: ${{ coalesce(parameters.artifacts.download.path, 'artifacts') }} + itemPattern: ${{ coalesce(parameters.artifacts.download.pattern, '**') }} + - ${{ each step in parameters.steps }}: - ${{ step }} @@ -166,20 +130,60 @@ jobs: env: TeamName: $(_TeamName) - - ${{ if eq(parameters.enableTelemetry, 'true') }}: - # Telemetry tasks are built from https://github.com/dotnet/arcade-extensions - - task: sendEndTelemetry@0 - displayName: 'Send Helix End Telemetry' - continueOnError: ${{ parameters.continueOnError }} - condition: always() + - ${{ if ne(parameters.artifacts.publish, '') }}: + - ${{ if or(eq(parameters.artifacts.publish.artifacts, 'true'), ne(parameters.artifacts.publish.artifacts, '')) }}: + - task: CopyFiles@2 + displayName: Gather binaries for publish to artifacts + inputs: + SourceFolder: 'artifacts/bin' + Contents: '**' + TargetFolder: '$(Build.ArtifactStagingDirectory)/artifacts/bin' + - task: CopyFiles@2 + displayName: Gather packages for publish to artifacts + inputs: + SourceFolder: 'artifacts/packages' + Contents: '**' + TargetFolder: '$(Build.ArtifactStagingDirectory)/artifacts/packages' + - task: PublishBuildArtifacts@1 + displayName: Publish pipeline artifacts + inputs: + PathtoPublish: '$(Build.ArtifactStagingDirectory)/artifacts' + PublishLocation: Container + ArtifactName: ${{ coalesce(parameters.artifacts.publish.artifacts.name , 'Artifacts_$(Agent.Os)_$(_BuildConfig)') }} + continueOnError: true + condition: always() + - ${{ if or(eq(parameters.artifacts.publish.logs, 'true'), ne(parameters.artifacts.publish.logs, '')) }}: + - publish: artifacts/log + artifact: ${{ coalesce(parameters.artifacts.publish.logs.name, 'Logs_Build_$(Agent.Os)_$(_BuildConfig)') }} + displayName: Publish logs + continueOnError: true + condition: always() + - ${{ if or(eq(parameters.artifacts.publish.manifests, 'true'), ne(parameters.artifacts.publish.manifests, '')) }}: + - ${{ if and(ne(parameters.enablePublishUsingPipelines, 'true'), eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - task: CopyFiles@2 + displayName: Gather Asset Manifests + inputs: + SourceFolder: '$(Build.SourcesDirectory)/artifacts/log/$(_BuildConfig)/AssetManifest' + TargetFolder: '$(Build.ArtifactStagingDirectory)/AssetManifests' + continueOnError: ${{ parameters.continueOnError }} + condition: and(succeeded(), eq(variables['_DotNetPublishToBlobFeed'], 'true')) - - ${{ if eq(parameters.enablePublishBuildArtifacts, 'true') }}: + - task: PublishBuildArtifacts@1 + displayName: Push Asset Manifests + inputs: + PathtoPublish: '$(Build.ArtifactStagingDirectory)/AssetManifests' + PublishLocation: Container + ArtifactName: AssetManifests + continueOnError: ${{ parameters.continueOnError }} + condition: and(succeeded(), eq(variables['_DotNetPublishToBlobFeed'], 'true')) + + - ${{ if ne(parameters.enablePublishBuildArtifacts, 'false') }}: - task: PublishBuildArtifacts@1 displayName: Publish Logs inputs: PathtoPublish: '$(Build.SourcesDirectory)/artifacts/log/$(_BuildConfig)' PublishLocation: Container - ArtifactName: $(Agent.Os)_$(Agent.JobName) + ArtifactName: ${{ coalesce(parameters.enablePublishBuildArtifacts.artifactName, '$(Agent.Os)_$(Agent.JobName)' ) }} continueOnError: true condition: always() diff --git a/eng/common/templates/job/publish-build-assets.yml b/eng/common/templates/job/publish-build-assets.yml index b722975f9c..055304ad89 100644 --- a/eng/common/templates/job/publish-build-assets.yml +++ b/eng/common/templates/job/publish-build-assets.yml @@ -37,6 +37,12 @@ jobs: - name: _BuildConfig value: ${{ parameters.configuration }} - group: Publish-Build-Assets + # Skip component governance and codesign validation for SDL. These jobs + # create no content. + - name: skipComponentGovernanceDetection + value: true + - name: runCodesignValidationInjection + value: false steps: - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: diff --git a/eng/common/templates/jobs/jobs.yml b/eng/common/templates/jobs/jobs.yml index 6a2f98c036..c08225a9a9 100644 --- a/eng/common/templates/jobs/jobs.yml +++ b/eng/common/templates/jobs/jobs.yml @@ -1,19 +1,10 @@ parameters: - # Optional: 'true' if failures in job.yml job should not fail the job + # See schema documentation in /Documentation/AzureDevOps/TemplateSchema.md continueOnError: false - # Optional: Enable installing Microbuild plugin - # if 'true', these "variables" must be specified in the variables object or as part of the queue matrix - # _TeamName - the name of your team - # _SignType - 'test' or 'real' - enableMicrobuild: false - # Optional: Include PublishBuildArtifacts task enablePublishBuildArtifacts: false - # Optional: Enable publishing to the build asset registry - enablePublishBuildAssets: false - # Optional: Enable publishing using release pipelines enablePublishUsingPipelines: false @@ -23,19 +14,9 @@ parameters: # Optional: Include toolset dependencies in the generated graph files includeToolset: false - # Optional: Include PublishTestResults task - enablePublishTestResults: false - - # Optional: enable sending telemetry - # if enabled then the 'helixRepo' parameter should also be specified - enableTelemetry: false - # Required: A collection of jobs to run - https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=vsts&tabs=schema#job jobs: [] - # Optional: define the helix repo for telemetry (example: 'dotnet/arcade') - helixRepo: '' - # Optional: Override automatically derived dependsOn value for "publish build assets" job publishBuildAssetsDependsOn: '' @@ -62,29 +43,30 @@ jobs: name: ${{ job.job }} -- ${{ if and(eq(parameters.enablePublishBuildAssets, true), eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: - - template: ../job/publish-build-assets.yml - parameters: - continueOnError: ${{ parameters.continueOnError }} - dependsOn: - - ${{ if ne(parameters.publishBuildAssetsDependsOn, '') }}: - - ${{ each job in parameters.publishBuildAssetsDependsOn }}: - - ${{ job.job }} - - ${{ if eq(parameters.publishBuildAssetsDependsOn, '') }}: - - ${{ each job in parameters.jobs }}: - - ${{ job.job }} - pool: - vmImage: vs2017-win2016 - runAsPublic: ${{ parameters.runAsPublic }} - publishUsingPipelines: ${{ parameters.enablePublishUsingPipelines }} - enablePublishBuildArtifacts: ${{ parameters.enablePublishBuildArtifacts }} - -- ${{ if and(eq(parameters.graphFileGeneration.enabled, true), eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: - - template: ../job/generate-graph-files.yml - parameters: - continueOnError: ${{ parameters.continueOnError }} - includeToolset: ${{ parameters.graphFileGeneration.includeToolset }} - dependsOn: - - Asset_Registry_Publish - pool: - vmImage: vs2017-win2016 +- ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - ${{ if or(eq(parameters.enablePublishBuildAssets, true), eq(parameters.artifacts.publish.manifests, 'true'), ne(parameters.artifacts.publish.manifests, '')) }}: + - template: ../job/publish-build-assets.yml + parameters: + continueOnError: ${{ parameters.continueOnError }} + dependsOn: + - ${{ if ne(parameters.publishBuildAssetsDependsOn, '') }}: + - ${{ each job in parameters.publishBuildAssetsDependsOn }}: + - ${{ job.job }} + - ${{ if eq(parameters.publishBuildAssetsDependsOn, '') }}: + - ${{ each job in parameters.jobs }}: + - ${{ job.job }} + pool: + vmImage: vs2017-win2016 + runAsPublic: ${{ parameters.runAsPublic }} + publishUsingPipelines: ${{ parameters.enablePublishUsingPipelines }} + enablePublishBuildArtifacts: ${{ parameters.enablePublishBuildArtifacts }} + + - ${{ if eq(parameters.graphFileGeneration.enabled, true) }}: + - template: ../job/generate-graph-files.yml + parameters: + continueOnError: ${{ parameters.continueOnError }} + includeToolset: ${{ parameters.graphFileGeneration.includeToolset }} + dependsOn: + - Asset_Registry_Publish + pool: + vmImage: vs2017-win2016 diff --git a/eng/common/templates/post-build/channels/generic-internal-channel.yml b/eng/common/templates/post-build/channels/generic-internal-channel.yml index ad9375f5e5..dde27800c3 100644 --- a/eng/common/templates/post-build/channels/generic-internal-channel.yml +++ b/eng/common/templates/post-build/channels/generic-internal-channel.yml @@ -1,4 +1,7 @@ parameters: + artifactsPublishingAdditionalParameters: '' + dependsOn: + - Validate publishInstallersAndChecksums: false symbolPublishingAdditionalParameters: '' stageName: '' @@ -10,37 +13,54 @@ parameters: stages: - stage: ${{ parameters.stageName }} - dependsOn: validate + dependsOn: ${{ parameters.dependsOn }} variables: - template: ../common-variables.yml displayName: ${{ parameters.channelName }} Publishing jobs: - template: ../setup-maestro-vars.yml - - job: + - job: publish_symbols displayName: Symbol Publishing dependsOn: setupMaestroVars - condition: contains(dependencies.setupMaestroVars.outputs['setReleaseVars.InitialChannels'], format('[{0}]', ${{ parameters.channelId }} )) + condition: or(contains(dependencies.setupMaestroVars.outputs['setReleaseVars.InitialChannels'], format('[{0}]', ${{ parameters.channelId }} )), eq(dependencies.setupMaestroVars.outputs['setReleaseVars.PromoteToMaestroChannelId'], ${{ parameters.channelId }})) variables: - group: DotNet-Symbol-Server-Pats + - name: AzDOProjectName + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOProjectName'] ] + - name: AzDOPipelineId + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOPipelineId'] ] + - name: AzDOBuildId + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOBuildId'] ] pool: vmImage: 'windows-2019' steps: + - task: DownloadBuildArtifacts@0 + displayName: Download Build Assets + continueOnError: true + inputs: + buildType: specific + buildVersionToDownload: specific + project: $(AzDOProjectName) + pipeline: $(AzDOPipelineId) + buildId: $(AzDOBuildId) + downloadType: 'specific' + itemPattern: | + PdbArtifacts/** + BlobArtifacts/** + downloadPath: '$(Build.ArtifactStagingDirectory)' + # This is necessary whenever we want to publish/restore to an AzDO private feed + # Since sdk-task.ps1 tries to restore packages we need to do this authentication here + # otherwise it'll complain about accessing a private feed. - task: NuGetAuthenticate@0 displayName: 'Authenticate to AzDO Feeds' - - task: DownloadBuildArtifacts@0 - displayName: Download Blob Artifacts + - task: PowerShell@2 + displayName: Enable cross-org publishing inputs: - artifactName: 'BlobArtifacts' - continueOnError: true - - - task: DownloadBuildArtifacts@0 - displayName: Download PDB Artifacts - inputs: - artifactName: 'PDBArtifacts' - continueOnError: true + filePath: eng\common\enable-cross-org-publishing.ps1 + arguments: -token $(dn-bot-dnceng-artifact-feeds-rw) - task: PowerShell@2 displayName: Publish @@ -53,39 +73,48 @@ stages: /p:BlobBasePath='$(Build.ArtifactStagingDirectory)/BlobArtifacts/' /p:SymbolPublishingExclusionsFile='$(Build.SourcesDirectory)/eng/SymbolPublishingExclusionsFile.txt' /p:Configuration=Release + /p:PublishToMSDL=false ${{ parameters.symbolPublishingAdditionalParameters }} + - template: ../../steps/publish-logs.yml + parameters: + StageLabel: '${{ parameters.stageName }}' + JobLabel: 'SymbolPublishing' + - job: publish_assets displayName: Publish Assets dependsOn: setupMaestroVars + timeoutInMinutes: 120 variables: - - group: DotNet-Blob-Feed - - group: AzureDevOps-Artifact-Feeds-Pats - name: BARBuildId value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.BARBuildId'] ] - name: IsStableBuild value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.IsStableBuild'] ] - condition: contains(dependencies.setupMaestroVars.outputs['setReleaseVars.InitialChannels'], format('[{0}]', ${{ parameters.channelId }})) + - name: AzDOProjectName + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOProjectName'] ] + - name: AzDOPipelineId + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOPipelineId'] ] + - name: AzDOBuildId + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOBuildId'] ] + condition: or(contains(dependencies.setupMaestroVars.outputs['setReleaseVars.InitialChannels'], format('[{0}]', ${{ parameters.channelId }} )), eq(dependencies.setupMaestroVars.outputs['setReleaseVars.PromoteToMaestroChannelId'], ${{ parameters.channelId }})) pool: vmImage: 'windows-2019' steps: - task: DownloadBuildArtifacts@0 - displayName: Download Package Artifacts + displayName: Download Build Assets + continueOnError: true inputs: - buildType: current - artifactName: PackageArtifacts - - - task: DownloadBuildArtifacts@0 - displayName: Download Blob Artifacts - inputs: - buildType: current - artifactName: BlobArtifacts - - - task: DownloadBuildArtifacts@0 - displayName: Download Asset Manifests - inputs: - buildType: current - artifactName: AssetManifests + buildType: specific + buildVersionToDownload: specific + project: $(AzDOProjectName) + pipeline: $(AzDOPipelineId) + buildId: $(AzDOBuildId) + downloadType: 'specific' + itemPattern: | + PackageArtifacts/** + BlobArtifacts/** + AssetManifests/** + downloadPath: '$(Build.ArtifactStagingDirectory)' - task: NuGetToolInstaller@1 displayName: 'Install NuGet.exe' @@ -124,7 +153,6 @@ stages: /p:ChecksumsAzureAccountKey=$(InternalChecksumsBlobFeedKey) /p:InstallersTargetStaticFeed=$(InternalInstallersBlobFeedUrl) /p:InstallersAzureAccountKey=$(InternalInstallersBlobFeedKey) - /p:PublishToAzureDevOpsNuGetFeeds=true /p:AzureDevOpsStaticShippingFeed='${{ parameters.shippingFeed }}' /p:AzureDevOpsStaticShippingFeedKey='$(dn-bot-dnceng-artifact-feeds-rw)' /p:AzureDevOpsStaticTransportFeed='${{ parameters.transportFeed }}' @@ -134,6 +162,11 @@ stages: /p:PublishToMSDL=false ${{ parameters.artifactsPublishingAdditionalParameters }} - - template: ../../steps/promote-build.yml + - template: ../../steps/publish-logs.yml + parameters: + StageLabel: '${{ parameters.stageName }}' + JobLabel: 'AssetsPublishing' + + - template: ../../steps/add-build-to-channel.yml parameters: ChannelId: ${{ parameters.channelId }} diff --git a/eng/common/templates/post-build/channels/generic-public-channel.yml b/eng/common/templates/post-build/channels/generic-public-channel.yml index c4bc1897d8..08853ec45e 100644 --- a/eng/common/templates/post-build/channels/generic-public-channel.yml +++ b/eng/common/templates/post-build/channels/generic-public-channel.yml @@ -1,5 +1,7 @@ parameters: artifactsPublishingAdditionalParameters: '' + dependsOn: + - Validate publishInstallersAndChecksums: false symbolPublishingAdditionalParameters: '' stageName: '' @@ -8,36 +10,47 @@ parameters: transportFeed: '' shippingFeed: '' symbolsFeed: '' + # If the channel name is empty, no links will be generated + akaMSChannelName: '' stages: - stage: ${{ parameters.stageName }} - dependsOn: validate + dependsOn: ${{ parameters.dependsOn }} variables: - template: ../common-variables.yml displayName: ${{ parameters.channelName }} Publishing jobs: - template: ../setup-maestro-vars.yml - - job: + - job: publish_symbols displayName: Symbol Publishing dependsOn: setupMaestroVars - condition: contains(dependencies.setupMaestroVars.outputs['setReleaseVars.InitialChannels'], format('[{0}]', ${{ parameters.channelId }} )) + condition: or(contains(dependencies.setupMaestroVars.outputs['setReleaseVars.InitialChannels'], format('[{0}]', ${{ parameters.channelId }} )), eq(dependencies.setupMaestroVars.outputs['setReleaseVars.PromoteToMaestroChannelId'], ${{ parameters.channelId }})) variables: - group: DotNet-Symbol-Server-Pats + - name: AzDOProjectName + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOProjectName'] ] + - name: AzDOPipelineId + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOPipelineId'] ] + - name: AzDOBuildId + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOBuildId'] ] pool: vmImage: 'windows-2019' steps: - task: DownloadBuildArtifacts@0 - displayName: Download Blob Artifacts - inputs: - artifactName: 'BlobArtifacts' + displayName: Download Build Assets continueOnError: true - - - task: DownloadBuildArtifacts@0 - displayName: Download PDB Artifacts inputs: - artifactName: 'PDBArtifacts' - continueOnError: true + buildType: specific + buildVersionToDownload: specific + project: $(AzDOProjectName) + pipeline: $(AzDOPipelineId) + buildId: $(AzDOBuildId) + downloadType: 'specific' + itemPattern: | + PdbArtifacts/** + BlobArtifacts/** + downloadPath: '$(Build.ArtifactStagingDirectory)' # This is necessary whenever we want to publish/restore to an AzDO private feed # Since sdk-task.ps1 tries to restore packages we need to do this authentication here @@ -64,37 +77,47 @@ stages: /p:Configuration=Release ${{ parameters.symbolPublishingAdditionalParameters }} + - template: ../../steps/publish-logs.yml + parameters: + StageLabel: '${{ parameters.stageName }}' + JobLabel: 'SymbolPublishing' + - job: publish_assets displayName: Publish Assets dependsOn: setupMaestroVars + timeoutInMinutes: 120 variables: - name: BARBuildId value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.BARBuildId'] ] - name: IsStableBuild value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.IsStableBuild'] ] - condition: contains(dependencies.setupMaestroVars.outputs['setReleaseVars.InitialChannels'], format('[{0}]', ${{ parameters.channelId }})) + - name: AzDOProjectName + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOProjectName'] ] + - name: AzDOPipelineId + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOPipelineId'] ] + - name: AzDOBuildId + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOBuildId'] ] + - name: ArtifactsCategory + value: ${{ coalesce(variables._DotNetArtifactsCategory, '.NETCore') }} + condition: or(contains(dependencies.setupMaestroVars.outputs['setReleaseVars.InitialChannels'], format('[{0}]', ${{ parameters.channelId }} )), eq(dependencies.setupMaestroVars.outputs['setReleaseVars.PromoteToMaestroChannelId'], ${{ parameters.channelId }})) pool: vmImage: 'windows-2019' steps: - task: DownloadBuildArtifacts@0 - displayName: Download Package Artifacts - inputs: - buildType: current - artifactName: PackageArtifacts + displayName: Download Build Assets continueOnError: true - - - task: DownloadBuildArtifacts@0 - displayName: Download Blob Artifacts inputs: - buildType: current - artifactName: BlobArtifacts - continueOnError: true - - - task: DownloadBuildArtifacts@0 - displayName: Download Asset Manifests - inputs: - buildType: current - artifactName: AssetManifests + buildType: specific + buildVersionToDownload: specific + project: $(AzDOProjectName) + pipeline: $(AzDOPipelineId) + buildId: $(AzDOBuildId) + downloadType: 'specific' + itemPattern: | + PackageArtifacts/** + BlobArtifacts/** + AssetManifests/** + downloadPath: '$(Build.ArtifactStagingDirectory)' - task: NuGetToolInstaller@1 displayName: 'Install NuGet.exe' @@ -114,7 +137,7 @@ stages: inputs: filePath: eng\common\sdk-task.ps1 arguments: -task PublishArtifactsInManifest -restore -msbuildEngine dotnet - /p:ArtifactsCategory=$(_DotNetArtifactsCategory) + /p:ArtifactsCategory=$(ArtifactsCategory) /p:IsStableBuild=$(IsStableBuild) /p:IsInternalBuild=$(IsInternalBuild) /p:RepositoryName=$(Build.Repository.Name) @@ -134,15 +157,22 @@ stages: /p:InstallersAzureAccountKey=$(dotnetcli-storage-key) /p:ChecksumsTargetStaticFeed=$(ChecksumsBlobFeedUrl) /p:ChecksumsAzureAccountKey=$(dotnetclichecksums-storage-key) - /p:PublishToAzureDevOpsNuGetFeeds=true /p:AzureDevOpsStaticShippingFeed='${{ parameters.shippingFeed }}' /p:AzureDevOpsStaticShippingFeedKey='$(dn-bot-dnceng-artifact-feeds-rw)' /p:AzureDevOpsStaticTransportFeed='${{ parameters.transportFeed }}' /p:AzureDevOpsStaticTransportFeedKey='$(dn-bot-dnceng-artifact-feeds-rw)' /p:AzureDevOpsStaticSymbolsFeed='${{ parameters.symbolsFeed }}' /p:AzureDevOpsStaticSymbolsFeedKey='$(dn-bot-dnceng-artifact-feeds-rw)' + /p:LatestLinkShortUrlPrefix=dotnet/'${{ parameters.akaMSChannelName }}' + /p:AkaMSClientId=$(akams-client-id) + /p:AkaMSClientSecret=$(akams-client-secret) ${{ parameters.artifactsPublishingAdditionalParameters }} - - template: ../../steps/promote-build.yml + - template: ../../steps/publish-logs.yml + parameters: + StageLabel: '${{ parameters.stageName }}' + JobLabel: 'AssetsPublishing' + + - template: ../../steps/add-build-to-channel.yml parameters: ChannelId: ${{ parameters.channelId }} diff --git a/eng/common/templates/post-build/common-variables.yml b/eng/common/templates/post-build/common-variables.yml index 216d043e4e..61488fd767 100644 --- a/eng/common/templates/post-build/common-variables.yml +++ b/eng/common/templates/post-build/common-variables.yml @@ -4,7 +4,7 @@ variables: - group: DotNet-DotNetCli-Storage - group: DotNet-MSRC-Storage - group: Publish-Build-Assets - + # .NET Core 3.1 Dev - name: PublicDevRelease_31_Channel_Id value: 128 @@ -49,6 +49,10 @@ variables: - name: NetCore_31_Blazor_Features_Channel_Id value: 531 + # .NET Core Experimental + - name: NetCore_Experimental_Channel_Id + value: 562 + # Whether the build is internal or not - name: IsInternalBuild value: ${{ and(ne(variables['System.TeamProject'], 'public'), contains(variables['Build.SourceBranch'], 'internal')) }} @@ -86,3 +90,10 @@ variables: value: https://dotnetclimsrc.blob.core.windows.net/dotnet/index.json - name: InternalInstallersBlobFeedKey value: $(dotnetclimsrc-access-key) + + # Skip component governance and codesign validation for SDL. These jobs + # create no content. + - name: skipComponentGovernanceDetection + value: true + - name: runCodesignValidationInjection + value: false \ No newline at end of file diff --git a/eng/common/templates/post-build/darc-gather-drop.yml b/eng/common/templates/post-build/darc-gather-drop.yml deleted file mode 100644 index 3268ccaa55..0000000000 --- a/eng/common/templates/post-build/darc-gather-drop.yml +++ /dev/null @@ -1,23 +0,0 @@ -parameters: - ChannelId: 0 - -jobs: -- job: gatherDrop - displayName: Gather Drop - dependsOn: setupMaestroVars - condition: contains(dependencies.setupMaestroVars.outputs['setReleaseVars.InitialChannels'], format('[{0}]', ${{ parameters.ChannelId }})) - variables: - - name: BARBuildId - value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.BARBuildId'] ] - pool: - vmImage: 'windows-2019' - steps: - - task: PowerShell@2 - displayName: Darc gather-drop - inputs: - filePath: $(Build.SourcesDirectory)/eng/common/post-build/darc-gather-drop.ps1 - arguments: -BarBuildId $(BARBuildId) - -DropLocation $(Agent.BuildDirectory)/Temp/Drop/ - -MaestroApiAccessToken $(MaestroApiAccessToken) - -MaestroApiEndPoint $(MaestroApiEndPoint) - -MaestroApiVersion $(MaestroApiVersion) diff --git a/eng/common/templates/post-build/post-build.yml b/eng/common/templates/post-build/post-build.yml index 3c69186f03..7be5b0bfad 100644 --- a/eng/common/templates/post-build/post-build.yml +++ b/eng/common/templates/post-build/post-build.yml @@ -17,100 +17,190 @@ parameters: signingValidationAdditionalParameters: '' # Which stages should finish execution before post-build stages start - dependsOn: [build] + validateDependsOn: + - build + publishDependsOn: + - Validate + # Channel ID's instantiated in this file. + # When adding a new channel implementation the call to `check-channel-consistency.ps1` + # needs to be updated with the new channel ID + NetEngLatestChannelId: 2 + NetEngValidationChannelId: 9 + NetCoreDev5ChannelId: 131 + GeneralTestingChannelId: 529 + NETCoreToolingDevChannelId: 548 + NETCoreToolingReleaseChannelId: 549 + NETInternalToolingChannelId: 551 + NETCoreExperimentalChannelId: 562 + NetEngServicesIntChannelId: 678 + NetEngServicesProdChannelId: 679 + Net5Preview1ChannelId: 737 + Net5Preview2ChannelId: 738 + stages: -- stage: validate - dependsOn: ${{ parameters.dependsOn }} +- stage: Validate + dependsOn: ${{ parameters.validateDependsOn }} displayName: Validate + variables: + - template: common-variables.yml jobs: - - ${{ if eq(parameters.enableNugetValidation, 'true') }}: - - job: - displayName: NuGet Validation - pool: - vmImage: 'windows-2019' - steps: - - task: DownloadBuildArtifacts@0 - displayName: Download Package Artifacts - inputs: - buildType: current - artifactName: PackageArtifacts + - template: setup-maestro-vars.yml - - task: PowerShell@2 - displayName: Validate - inputs: - filePath: $(Build.SourcesDirectory)/eng/common/post-build/nuget-validation.ps1 - arguments: -PackagesPath $(Build.ArtifactStagingDirectory)/PackageArtifacts/ - -ToolDestinationPath $(Agent.BuildDirectory)/Extract/ + - job: + displayName: Post-build Checks + dependsOn: setupMaestroVars + variables: + - name: InitialChannels + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.InitialChannels'] ] + - name: PromoteToMaestroChannelId + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.PromoteToMaestroChannelId'] ] + pool: + vmImage: 'windows-2019' + steps: + - task: PowerShell@2 + displayName: Maestro Channels Consistency + inputs: + filePath: $(Build.SourcesDirectory)/eng/common/post-build/check-channel-consistency.ps1 + arguments: -PromoteToChannels "$(InitialChannels)[$(PromoteToMaestroChannelId)]" + -AvailableChannelIds ${{parameters.NetEngLatestChannelId}},${{parameters.NetEngValidationChannelId}},${{parameters.NetCoreDev5ChannelId}},${{parameters.GeneralTestingChannelId}},${{parameters.NETCoreToolingDevChannelId}},${{parameters.NETCoreToolingReleaseChannelId}},${{parameters.NETInternalToolingChannelId}},${{parameters.NETCoreExperimentalChannelId}},${{parameters.NetEngServicesIntChannelId}},${{parameters.NetEngServicesProdChannelId}},${{parameters.Net5Preview1ChannelId}},${{parameters.Net5Preview2ChannelId}} - - ${{ if eq(parameters.enableSigningValidation, 'true') }}: - - job: - displayName: Signing Validation - pool: - vmImage: 'windows-2019' - steps: - # This is necessary whenever we want to publish/restore to an AzDO private feed - # Since sdk-task.ps1 tries to restore packages we need to do this authentication here - # otherwise it'll complain about accessing a private feed. - - task: NuGetAuthenticate@0 - displayName: 'Authenticate to AzDO Feeds' + - job: + displayName: NuGet Validation + dependsOn: setupMaestroVars + condition: eq( ${{ parameters.enableNugetValidation }}, 'true') + pool: + vmImage: 'windows-2019' + variables: + - name: AzDOProjectName + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOProjectName'] ] + - name: AzDOPipelineId + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOPipelineId'] ] + - name: AzDOBuildId + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOBuildId'] ] + steps: + - task: DownloadBuildArtifacts@0 + displayName: Download Package Artifacts + inputs: + buildType: specific + buildVersionToDownload: specific + project: $(AzDOProjectName) + pipeline: $(AzDOPipelineId) + buildId: $(AzDOBuildId) + artifactName: PackageArtifacts - - task: DownloadBuildArtifacts@0 - displayName: Download Package Artifacts - inputs: - buildType: current - artifactName: PackageArtifacts + - task: PowerShell@2 + displayName: Validate + inputs: + filePath: $(Build.SourcesDirectory)/eng/common/post-build/nuget-validation.ps1 + arguments: -PackagesPath $(Build.ArtifactStagingDirectory)/PackageArtifacts/ + -ToolDestinationPath $(Agent.BuildDirectory)/Extract/ - - task: PowerShell@2 - displayName: Validate - inputs: - filePath: eng\common\sdk-task.ps1 - arguments: -task SigningValidation -restore -msbuildEngine dotnet - /p:PackageBasePath='$(Build.ArtifactStagingDirectory)/PackageArtifacts' - /p:SignCheckExclusionsFile='$(Build.SourcesDirectory)/eng/SignCheckExclusionsFile.txt' - /p:Configuration=Release - ${{ parameters.signingValidationAdditionalParameters }} + - job: + displayName: Signing Validation + dependsOn: setupMaestroVars + condition: eq( ${{ parameters.enableSigningValidation }}, 'true') + variables: + - template: common-variables.yml + - name: AzDOProjectName + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOProjectName'] ] + - name: AzDOPipelineId + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOPipelineId'] ] + - name: AzDOBuildId + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOBuildId'] ] + pool: + vmImage: 'windows-2019' + steps: + - task: DownloadBuildArtifacts@0 + displayName: Download Package Artifacts + inputs: + buildType: specific + buildVersionToDownload: specific + project: $(AzDOProjectName) + pipeline: $(AzDOPipelineId) + buildId: $(AzDOBuildId) + artifactName: PackageArtifacts - - ${{ if eq(parameters.enableSourceLinkValidation, 'true') }}: - - job: - displayName: SourceLink Validation - variables: - - template: common-variables.yml - pool: - vmImage: 'windows-2019' - steps: - - task: DownloadBuildArtifacts@0 - displayName: Download Blob Artifacts - inputs: - buildType: current - artifactName: BlobArtifacts + # This is necessary whenever we want to publish/restore to an AzDO private feed + # Since sdk-task.ps1 tries to restore packages we need to do this authentication here + # otherwise it'll complain about accessing a private feed. + - task: NuGetAuthenticate@0 + displayName: 'Authenticate to AzDO Feeds' - - task: PowerShell@2 - displayName: Validate - inputs: - filePath: $(Build.SourcesDirectory)/eng/common/post-build/sourcelink-validation.ps1 - arguments: -InputPath $(Build.ArtifactStagingDirectory)/BlobArtifacts/ - -ExtractPath $(Agent.BuildDirectory)/Extract/ - -GHRepoName $(Build.Repository.Name) - -GHCommit $(Build.SourceVersion) - -SourcelinkCliVersion $(SourceLinkCLIVersion) - continueOnError: true + - task: PowerShell@2 + displayName: Enable cross-org publishing + inputs: + filePath: eng\common\enable-cross-org-publishing.ps1 + arguments: -token $(dn-bot-dnceng-artifact-feeds-rw) - - ${{ if eq(parameters.SDLValidationParameters.enable, 'true') }}: - - template: /eng/common/templates/job/execute-sdl.yml - parameters: - additionalParameters: ${{ parameters.SDLValidationParameters.params }} - continueOnError: ${{ parameters.SDLValidationParameters.continueOnError }} - artifactNames: ${{ parameters.SDLValidationParameters.artifactNames }} + - task: PowerShell@2 + displayName: Validate + inputs: + filePath: eng\common\sdk-task.ps1 + arguments: -task SigningValidation -restore -msbuildEngine dotnet + /p:PackageBasePath='$(Build.ArtifactStagingDirectory)/PackageArtifacts' + /p:SignCheckExclusionsFile='$(Build.SourcesDirectory)/eng/SignCheckExclusionsFile.txt' + ${{ parameters.signingValidationAdditionalParameters }} + + - template: ../steps/publish-logs.yml + parameters: + StageLabel: 'Validation' + JobLabel: 'Signing' + + - job: + displayName: SourceLink Validation + dependsOn: setupMaestroVars + condition: eq( ${{ parameters.enableSourceLinkValidation }}, 'true') + variables: + - template: common-variables.yml + - name: AzDOProjectName + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOProjectName'] ] + - name: AzDOPipelineId + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOPipelineId'] ] + - name: AzDOBuildId + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOBuildId'] ] + pool: + vmImage: 'windows-2019' + steps: + - task: DownloadBuildArtifacts@0 + displayName: Download Blob Artifacts + inputs: + buildType: specific + buildVersionToDownload: specific + project: $(AzDOProjectName) + pipeline: $(AzDOPipelineId) + buildId: $(AzDOBuildId) + artifactName: BlobArtifacts + + - task: PowerShell@2 + displayName: Validate + inputs: + filePath: $(Build.SourcesDirectory)/eng/common/post-build/sourcelink-validation.ps1 + arguments: -InputPath $(Build.ArtifactStagingDirectory)/BlobArtifacts/ + -ExtractPath $(Agent.BuildDirectory)/Extract/ + -GHRepoName $(Build.Repository.Name) + -GHCommit $(Build.SourceVersion) + -SourcelinkCliVersion $(SourceLinkCLIVersion) + continueOnError: true + + - template: /eng/common/templates/job/execute-sdl.yml + parameters: + enable: ${{ parameters.SDLValidationParameters.enable }} + dependsOn: setupMaestroVars + additionalParameters: ${{ parameters.SDLValidationParameters.params }} + continueOnError: ${{ parameters.SDLValidationParameters.continueOnError }} + artifactNames: ${{ parameters.SDLValidationParameters.artifactNames }} - template: \eng\common\templates\post-build\channels\generic-public-channel.yml parameters: artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} + dependsOn: ${{ parameters.publishDependsOn }} publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} stageName: 'NetCore_Dev5_Publish' channelName: '.NET Core 5 Dev' - channelId: 131 + akaMSChannelName: 'net5/dev' + channelId: ${{ parameters.NetCoreDev5ChannelId }} transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet5-transport/nuget/v3/index.json' shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet5/nuget/v3/index.json' symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet5-symbols/nuget/v3/index.json' @@ -118,23 +208,41 @@ stages: - template: \eng\common\templates\post-build\channels\generic-public-channel.yml parameters: artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} + dependsOn: ${{ parameters.publishDependsOn }} publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} - stageName: 'NetCore_Dev31_Publish' - channelName: '.NET Core 3.1 Dev' - channelId: 128 - transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1-transport/nuget/v3/index.json' - shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1/nuget/v3/index.json' - symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1-symbols/nuget/v3/index.json' + stageName: 'Net5_Preview1_Publish' + channelName: '.NET 5 Preview 1' + akaMSChannelName: 'net5/preview1' + channelId: ${{ parameters.Net5Preview1ChannelId }} + transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet5-transport/nuget/v3/index.json' + shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet5/nuget/v3/index.json' + symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet5-symbols/nuget/v3/index.json' - template: \eng\common\templates\post-build\channels\generic-public-channel.yml parameters: artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} + dependsOn: ${{ parameters.publishDependsOn }} + publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} + symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} + stageName: 'Net5_Preview2_Publish' + channelName: '.NET 5 Preview 2' + akaMSChannelName: 'net5/preview2' + channelId: ${{ parameters.Net5Preview2ChannelId }} + transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet5-transport/nuget/v3/index.json' + shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet5/nuget/v3/index.json' + symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet5-symbols/nuget/v3/index.json' + +- template: \eng\common\templates\post-build\channels\generic-public-channel.yml + parameters: + artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} + dependsOn: ${{ parameters.publishDependsOn }} publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} stageName: 'Net_Eng_Latest_Publish' channelName: '.NET Eng - Latest' - channelId: 2 + akaMSChannelName: 'eng/daily' + channelId: ${{ parameters.NetEngLatestChannelId }} transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json' shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json' symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng-symbols/nuget/v3/index.json' @@ -142,11 +250,13 @@ stages: - template: \eng\common\templates\post-build\channels\generic-public-channel.yml parameters: artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} + dependsOn: ${{ parameters.publishDependsOn }} publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} stageName: 'Net_Eng_Validation_Publish' channelName: '.NET Eng - Validation' - channelId: 9 + akaMSChannelName: 'eng/validation' + channelId: ${{ parameters.NetEngValidationChannelId }} transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json' shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json' symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng-symbols/nuget/v3/index.json' @@ -154,95 +264,13 @@ stages: - template: \eng\common\templates\post-build\channels\generic-public-channel.yml parameters: artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} - publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} - symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} - stageName: 'NetCore_3_Tools_Validation_Publish' - channelName: '.NET 3 Tools - Validation' - channelId: 390 - transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json' - shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json' - symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng-symbols/nuget/v3/index.json' - -- template: \eng\common\templates\post-build\channels\generic-public-channel.yml - parameters: - artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} - publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} - symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} - stageName: 'NetCore_3_Tools_Publish' - channelName: '.NET 3 Tools' - channelId: 344 - transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json' - shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json' - symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng-symbols/nuget/v3/index.json' - -- template: \eng\common\templates\post-build\channels\generic-public-channel.yml - parameters: - artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} - publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} - symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} - stageName: 'NetCore_Release30_Publish' - channelName: '.NET Core 3.0 Release' - channelId: 19 - transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3-transport/nuget/v3/index.json' - shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3/nuget/v3/index.json' - symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3-symbols/nuget/v3/index.json' - -- template: \eng\common\templates\post-build\channels\generic-public-channel.yml - parameters: - artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} - publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} - symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} - stageName: 'NetCore_Release31_Publish' - channelName: '.NET Core 3.1 Release' - channelId: 129 - transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1-transport/nuget/v3/index.json' - shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1/nuget/v3/index.json' - symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1-symbols/nuget/v3/index.json' - -- template: \eng\common\templates\post-build\channels\generic-public-channel.yml - parameters: - artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} - publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} - symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} - stageName: 'NetCore_Blazor31_Features_Publish' - channelName: '.NET Core 3.1 Blazor Features' - channelId: 531 - transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1-blazor/nuget/v3/index.json' - shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1-blazor/nuget/v3/index.json' - symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1-blazor-symbols/nuget/v3/index.json' - -- template: \eng\common\templates\post-build\channels\generic-internal-channel.yml - parameters: - artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} - publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} - symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} - stageName: 'NetCore_30_Internal_Servicing_Publishing' - channelName: '.NET Core 3.0 Internal Servicing' - channelId: 184 - transportFeed: 'https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3-internal-transport/nuget/v3/index.json' - shippingFeed: 'https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3-internal/nuget/v3/index.json' - symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3-internal-symbols/nuget/v3/index.json' - -- template: \eng\common\templates\post-build\channels\generic-internal-channel.yml - parameters: - artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} - publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} - symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} - stageName: 'NetCore_31_Internal_Servicing_Publishing' - channelName: '.NET Core 3.1 Internal Servicing' - channelId: 550 - transportFeed: 'https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal-transport/nuget/v3/index.json' - shippingFeed: 'https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal/nuget/v3/index.json' - symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal-symbols/nuget/v3/index.json' - -- template: \eng\common\templates\post-build\channels\generic-public-channel.yml - parameters: - artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} + dependsOn: ${{ parameters.publishDependsOn }} publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} stageName: 'General_Testing_Publish' channelName: 'General Testing' - channelId: 529 + akaMSChannelName: 'generaltesting' + channelId: ${{ parameters.GeneralTestingChannelId }} transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/general-testing/nuget/v3/index.json' shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/general-testing/nuget/v3/index.json' symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/general-testing-symbols/nuget/v3/index.json' @@ -250,11 +278,12 @@ stages: - template: \eng\common\templates\post-build\channels\generic-public-channel.yml parameters: artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} + dependsOn: ${{ parameters.publishDependsOn }} publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} stageName: 'NETCore_Tooling_Dev_Publishing' channelName: '.NET Core Tooling Dev' - channelId: 548 + channelId: ${{ parameters.NETCoreToolingDevChannelId }} transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json' shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json' symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools-symbols/nuget/v3/index.json' @@ -262,107 +291,64 @@ stages: - template: \eng\common\templates\post-build\channels\generic-public-channel.yml parameters: artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} + dependsOn: ${{ parameters.publishDependsOn }} publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} stageName: 'NETCore_Tooling_Release_Publishing' channelName: '.NET Core Tooling Release' - channelId: 549 + channelId: ${{ parameters.NETCoreToolingReleaseChannelId }} transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json' shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json' symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools-symbols/nuget/v3/index.json' -- template: \eng\common\templates\post-build\channels\generic-public-channel.yml - parameters: - artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} - publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} - symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} - stageName: 'NETCore_SDK_301xx_Publishing' - channelName: '.NET Core SDK 3.0.1xx' - channelId: 556 - transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3-transport/nuget/v3/index.json' - shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3/nuget/v3/index.json' - symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3-symbols/nuget/v3/index.json' - - template: \eng\common\templates\post-build\channels\generic-internal-channel.yml parameters: artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} + dependsOn: ${{ parameters.publishDependsOn }} publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} - stageName: 'NETCore_SDK_301xx_Internal_Publishing' - channelName: '.NET Core SDK 3.0.1xx Internal' - channelId: 555 - transportFeed: 'https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3-internal-transport/nuget/v3/index.json' - shippingFeed: 'https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3-internal/nuget/v3/index.json' - symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3-internal-symbols/nuget/v3/index.json' + stageName: 'NET_Internal_Tooling_Publishing' + channelName: '.NET Internal Tooling' + channelId: ${{ parameters.NETInternalToolingChannelId }} + transportFeed: 'https://pkgs.dev.azure.com/dnceng/internal/_packaging/dotnet-tools-internal/nuget/v3/index.json' + shippingFeed: 'https://pkgs.dev.azure.com/dnceng/internal/_packaging/dotnet-tools-internal/nuget/v3/index.json' + symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/internal/_packaging/dotnet-tools-internal-symbols/nuget/v3/index.json' - template: \eng\common\templates\post-build\channels\generic-public-channel.yml parameters: artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} + dependsOn: ${{ parameters.publishDependsOn }} publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} - stageName: 'NETCore_SDK_311xx_Publishing' - channelName: '.NET Core SDK 3.1.1xx' - channelId: 560 - transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1-transport/nuget/v3/index.json' - shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1/nuget/v3/index.json' - symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1-symbols/nuget/v3/index.json' - -- template: \eng\common\templates\post-build\channels\generic-internal-channel.yml - parameters: - artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} - publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} - symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} - stageName: 'NETCore_SDK_311xx_Internal_Publishing' - channelName: '.NET Core SDK 3.1.1xx Internal' - channelId: 559 - transportFeed: 'https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal-transport/nuget/v3/index.json' - shippingFeed: 'https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal/nuget/v3/index.json' - symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal-symbols/nuget/v3/index.json' + stageName: 'NETCore_Experimental_Publishing' + channelName: '.NET Core Experimental' + channelId: ${{ parameters.NETCoreExperimentalChannelId }} + transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-experimental/nuget/v3/index.json' + shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-experimental/nuget/v3/index.json' + symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-experimental-symbols/nuget/v3/index.json' - template: \eng\common\templates\post-build\channels\generic-public-channel.yml parameters: artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} + dependsOn: ${{ parameters.publishDependsOn }} publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} - stageName: 'NETCore_SDK_312xx_Publishing' - channelName: '.NET Core SDK 3.1.2xx' - channelId: 558 - transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1-transport/nuget/v3/index.json' - shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1/nuget/v3/index.json' - symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1-symbols/nuget/v3/index.json' - -- template: \eng\common\templates\post-build\channels\generic-internal-channel.yml - parameters: - artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} - publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} - symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} - stageName: 'NETCore_SDK_312xx_Internal_Publishing' - channelName: '.NET Core SDK 3.1.2xx Internal' - channelId: 557 - transportFeed: 'https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal-transport/nuget/v3/index.json' - shippingFeed: 'https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal/nuget/v3/index.json' - symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal-symbols/nuget/v3/index.json' + stageName: 'Net_Eng_Services_Int_Publish' + channelName: '.NET Eng Services - Int' + channelId: ${{ parameters.NetEngServicesIntChannelId }} + transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json' + shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json' + symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng-symbols/nuget/v3/index.json' - template: \eng\common\templates\post-build\channels\generic-public-channel.yml parameters: artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} + dependsOn: ${{ parameters.publishDependsOn }} publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} - stageName: 'NETCore_SDK_313xx_Publishing' - channelName: '.NET Core SDK 3.1.3xx' - channelId: 759 - transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1-transport/nuget/v3/index.json' - shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1/nuget/v3/index.json' - symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1-symbols/nuget/v3/index.json' - -- template: \eng\common\templates\post-build\channels\generic-internal-channel.yml - parameters: - artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} - publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} - symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} - stageName: 'NETCore_SDK_313xx_Internal_Publishing' - channelName: '.NET Core SDK 3.1.3xx Internal' - channelId: 760 - transportFeed: 'https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal-transport/nuget/v3/index.json' - shippingFeed: 'https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal/nuget/v3/index.json' - symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal-symbols/nuget/v3/index.json' \ No newline at end of file + stageName: 'Net_Eng_Services_Prod_Publish' + channelName: '.NET Eng Services - Prod' + channelId: ${{ parameters.NetEngServicesProdChannelId }} + transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json' + shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json' + symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng-symbols/nuget/v3/index.json' diff --git a/eng/common/templates/post-build/promote-build.yml b/eng/common/templates/post-build/promote-build.yml deleted file mode 100644 index 6b479c3b82..0000000000 --- a/eng/common/templates/post-build/promote-build.yml +++ /dev/null @@ -1,25 +0,0 @@ -parameters: - ChannelId: 0 - -jobs: -- job: - displayName: Promote Build - dependsOn: setupMaestroVars - condition: contains(dependencies.setupMaestroVars.outputs['setReleaseVars.InitialChannels'], format('[{0}]', ${{ parameters.ChannelId }})) - variables: - - name: BARBuildId - value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.BARBuildId'] ] - - name: ChannelId - value: ${{ parameters.ChannelId }} - pool: - vmImage: 'windows-2019' - steps: - - task: PowerShell@2 - displayName: Add Build to Channel - inputs: - filePath: $(Build.SourcesDirectory)/eng/common/post-build/promote-build.ps1 - arguments: -BuildId $(BARBuildId) - -ChannelId $(ChannelId) - -MaestroApiAccessToken $(MaestroApiAccessToken) - -MaestroApiEndPoint $(MaestroApiEndPoint) - -MaestroApiVersion $(MaestroApiVersion) diff --git a/eng/common/templates/post-build/setup-maestro-vars.yml b/eng/common/templates/post-build/setup-maestro-vars.yml index 56242b068e..05e611edb6 100644 --- a/eng/common/templates/post-build/setup-maestro-vars.yml +++ b/eng/common/templates/post-build/setup-maestro-vars.yml @@ -1,11 +1,20 @@ jobs: - job: setupMaestroVars displayName: Setup Maestro Vars + variables: + - template: common-variables.yml + - name: BuildId + value: $[ coalesce(variables.BARBuildId, 0) ] + - name: PromoteToChannelId + value: $[ coalesce(variables.PromoteToMaestroChannelId, 0) ] pool: vmImage: 'windows-2019' steps: + - checkout: none + - task: DownloadBuildArtifacts@0 displayName: Download Release Configs + condition: eq(variables.PromoteToChannelId, 0) inputs: buildType: current artifactName: ReleaseConfigs @@ -14,5 +23,59 @@ jobs: name: setReleaseVars displayName: Set Release Configs Vars inputs: - filePath: $(Build.SourcesDirectory)/eng/common/post-build/setup-maestro-vars.ps1 - arguments: -ReleaseConfigsPath '$(Build.StagingDirectory)/ReleaseConfigs/ReleaseConfigs.txt' + targetType: inline + script: | + try { + if ($Env:PromoteToChannelId -eq 0) { + $Content = Get-Content $(Build.StagingDirectory)/ReleaseConfigs/ReleaseConfigs.txt + + $BarId = $Content | Select -Index 0 + + $Channels = "" + $Content | Select -Index 1 | ForEach-Object { $Channels += "$_ ," } + + $IsStableBuild = $Content | Select -Index 2 + + $AzureDevOpsProject = $Env:System_TeamProject + $AzureDevOpsBuildDefinitionId = $Env:System_DefinitionId + $AzureDevOpsBuildId = $Env:Build_BuildId + $PromoteToMaestroChannelId = 0 + } + else { + $buildApiEndpoint = "${Env:MaestroApiEndPoint}/api/builds/${Env:BARBuildId}?api-version=${Env:MaestroApiVersion}" + + $apiHeaders = New-Object 'System.Collections.Generic.Dictionary[[String],[String]]' + $apiHeaders.Add('Accept', 'application/json') + $apiHeaders.Add('Authorization',"Bearer ${Env:MAESTRO_API_TOKEN}") + + $buildInfo = try { Invoke-WebRequest -Method Get -Uri $buildApiEndpoint -Headers $apiHeaders | ConvertFrom-Json } catch { Write-Host "Error: $_" } + + $BarId = $Env:BARBuildId + $Channels = 'None' + + #TODO: Fix this once this issue is done: https://github.com/dotnet/arcade/issues/3834 + $IsStableBuild = 'False' + + $AzureDevOpsProject = $buildInfo.azureDevOpsProject + $AzureDevOpsBuildDefinitionId = $buildInfo.azureDevOpsBuildDefinitionId + $AzureDevOpsBuildId = $buildInfo.azureDevOpsBuildId + $PromoteToMaestroChannelId = $Env:PromoteToMaestroChannelId + } + + Write-Host "##vso[task.setvariable variable=BARBuildId;isOutput=true]$BarId" + Write-Host "##vso[task.setvariable variable=InitialChannels;isOutput=true]$Channels" + Write-Host "##vso[task.setvariable variable=IsStableBuild;isOutput=true]$IsStableBuild" + + Write-Host "##vso[task.setvariable variable=AzDOProjectName;isOutput=true]$AzureDevOpsProject" + Write-Host "##vso[task.setvariable variable=AzDOPipelineId;isOutput=true]$AzureDevOpsBuildDefinitionId" + Write-Host "##vso[task.setvariable variable=AzDOBuildId;isOutput=true]$AzureDevOpsBuildId" + Write-Host "##vso[task.setvariable variable=PromoteToMaestroChannelId;isOutput=true]$PromoteToMaestroChannelId" + } + catch { + Write-Host $_ + Write-Host $_.Exception + Write-Host $_.ScriptStackTrace + exit 1 + } + env: + MAESTRO_API_TOKEN: $(MaestroApiAccessToken) diff --git a/eng/common/templates/steps/promote-build.yml b/eng/common/templates/steps/add-build-to-channel.yml similarity index 68% rename from eng/common/templates/steps/promote-build.yml rename to eng/common/templates/steps/add-build-to-channel.yml index b90404435d..f67a210d62 100644 --- a/eng/common/templates/steps/promote-build.yml +++ b/eng/common/templates/steps/add-build-to-channel.yml @@ -5,9 +5,9 @@ steps: - task: PowerShell@2 displayName: Add Build to Channel inputs: - filePath: $(Build.SourcesDirectory)/eng/common/post-build/promote-build.ps1 + filePath: $(Build.SourcesDirectory)/eng/common/post-build/add-build-to-channel.ps1 arguments: -BuildId $(BARBuildId) -ChannelId ${{ parameters.ChannelId }} -MaestroApiAccessToken $(MaestroApiAccessToken) -MaestroApiEndPoint $(MaestroApiEndPoint) - -MaestroApiVersion $(MaestroApiVersion) + -MaestroApiVersion $(MaestroApiVersion) diff --git a/eng/common/templates/steps/publish-logs.yml b/eng/common/templates/steps/publish-logs.yml new file mode 100644 index 0000000000..f91751fe78 --- /dev/null +++ b/eng/common/templates/steps/publish-logs.yml @@ -0,0 +1,23 @@ +parameters: + StageLabel: '' + JobLabel: '' + +steps: +- task: Powershell@2 + displayName: Prepare Binlogs to Upload + inputs: + targetType: inline + script: | + New-Item -ItemType Directory $(Build.SourcesDirectory)/PostBuildLogs/${{parameters.StageLabel}}/${{parameters.JobLabel}}/ + Move-Item -Path $(Build.SourcesDirectory)/artifacts/log/Debug/* $(Build.SourcesDirectory)/PostBuildLogs/${{parameters.StageLabel}}/${{parameters.JobLabel}}/ + continueOnError: true + condition: always() + +- task: PublishBuildArtifacts@1 + displayName: Publish Logs + inputs: + PathtoPublish: '$(Build.SourcesDirectory)/PostBuildLogs' + PublishLocation: Container + ArtifactName: PostBuildLogs + continueOnError: true + condition: always() diff --git a/eng/common/templates/steps/send-to-helix.yml b/eng/common/templates/steps/send-to-helix.yml index 05df886f55..30becf01ea 100644 --- a/eng/common/templates/steps/send-to-helix.yml +++ b/eng/common/templates/steps/send-to-helix.yml @@ -23,6 +23,7 @@ parameters: EnableXUnitReporter: false # optional -- true enables XUnit result reporting to Mission Control WaitForWorkItemCompletion: true # optional -- true will make the task wait until work items have been completed and fail the build if work items fail. False is "fire and forget." IsExternal: false # [DEPRECATED] -- doesn't do anything, jobs are external if HelixAccessToken is empty and Creator is set + HelixBaseUri: 'https://helix.dot.net/' # optional -- sets the Helix API base URI (allows targeting int) Creator: '' # optional -- if the build is external, use this to specify who is sending the job DisplayNamePrefix: 'Run Tests' # optional -- rename the beginning of the displayName of the steps in AzDO condition: succeeded() # optional -- condition for step to execute; defaults to succeeded() @@ -55,6 +56,7 @@ steps: DotNetCliVersion: ${{ parameters.DotNetCliVersion }} EnableXUnitReporter: ${{ parameters.EnableXUnitReporter }} WaitForWorkItemCompletion: ${{ parameters.WaitForWorkItemCompletion }} + HelixBaseUri: ${{ parameters.HelixBaseUri }} Creator: ${{ parameters.Creator }} SYSTEM_ACCESSTOKEN: $(System.AccessToken) condition: and(${{ parameters.condition }}, eq(variables['Agent.Os'], 'Windows_NT')) @@ -85,6 +87,7 @@ steps: DotNetCliVersion: ${{ parameters.DotNetCliVersion }} EnableXUnitReporter: ${{ parameters.EnableXUnitReporter }} WaitForWorkItemCompletion: ${{ parameters.WaitForWorkItemCompletion }} + HelixBaseUri: ${{ parameters.HelixBaseUri }} Creator: ${{ parameters.Creator }} SYSTEM_ACCESSTOKEN: $(System.AccessToken) condition: and(${{ parameters.condition }}, ne(variables['Agent.Os'], 'Windows_NT')) diff --git a/eng/common/tools.ps1 b/eng/common/tools.ps1 index 92a053bd16..60c1cd8975 100644 --- a/eng/common/tools.ps1 +++ b/eng/common/tools.ps1 @@ -5,7 +5,7 @@ [bool]$ci = if (Test-Path variable:ci) { $ci } else { $false } # Build configuration. Common values include 'Debug' and 'Release', but the repository may use other names. -[string]$configuration = if (Test-Path variable:configuration) { $configuration } else { "Debug" } +[string]$configuration = if (Test-Path variable:configuration) { $configuration } else { 'Debug' } # Set to true to output binary log from msbuild. Note that emitting binary log slows down the build. # Binary log must be enabled on CI. @@ -24,7 +24,7 @@ [bool]$restore = if (Test-Path variable:restore) { $restore } else { $true } # Adjusts msbuild verbosity level. -[string]$verbosity = if (Test-Path variable:verbosity) { $verbosity } else { "minimal" } +[string]$verbosity = if (Test-Path variable:verbosity) { $verbosity } else { 'minimal' } # Set to true to reuse msbuild nodes. Recommended to not reuse on CI. [bool]$nodeReuse = if (Test-Path variable:nodeReuse) { $nodeReuse } else { !$ci } @@ -41,21 +41,23 @@ # Enable repos to use a particular version of the on-line dotnet-install scripts. # default URL: https://dot.net/v1/dotnet-install.ps1 -[string]$dotnetInstallScriptVersion = if (Test-Path variable:dotnetInstallScriptVersion) { $dotnetInstallScriptVersion } else { "v1" } +[string]$dotnetInstallScriptVersion = if (Test-Path variable:dotnetInstallScriptVersion) { $dotnetInstallScriptVersion } else { 'v1' } # True to use global NuGet cache instead of restoring packages to repository-local directory. [bool]$useGlobalNuGetCache = if (Test-Path variable:useGlobalNuGetCache) { $useGlobalNuGetCache } else { !$ci } # An array of names of processes to stop on script exit if prepareMachine is true. -$processesToStopOnExit = if (Test-Path variable:processesToStopOnExit) { $processesToStopOnExit } else { @("msbuild", "dotnet", "vbcscompiler") } +$processesToStopOnExit = if (Test-Path variable:processesToStopOnExit) { $processesToStopOnExit } else { @('msbuild', 'dotnet', 'vbcscompiler') } + +$disableConfigureToolsetImport = if (Test-Path variable:disableConfigureToolsetImport) { $disableConfigureToolsetImport } else { $null } set-strictmode -version 2.0 -$ErrorActionPreference = "Stop" +$ErrorActionPreference = 'Stop' [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 function Create-Directory([string[]] $path) { if (!(Test-Path $path)) { - New-Item -path $path -force -itemType "Directory" | Out-Null + New-Item -path $path -force -itemType 'Directory' | Out-Null } } @@ -96,7 +98,10 @@ function Exec-Process([string]$command, [string]$commandArgs) { } } -function InitializeDotNetCli([bool]$install) { +# createSdkLocationFile parameter enables a file being generated under the toolset directory +# which writes the sdk's location into. This is only necessary for cmd --> powershell invocations +# as dot sourcing isn't possible. +function InitializeDotNetCli([bool]$install, [bool]$createSdkLocationFile) { if (Test-Path variable:global:_DotNetInstallDir) { return $global:_DotNetInstallDir } @@ -119,7 +124,7 @@ function InitializeDotNetCli([bool]$install) { # Find the first path on %PATH% that contains the dotnet.exe if ($useInstalledDotNetCli -and (-not $globalJsonHasRuntimes) -and ($env:DOTNET_INSTALL_DIR -eq $null)) { - $dotnetCmd = Get-Command "dotnet.exe" -ErrorAction SilentlyContinue + $dotnetCmd = Get-Command 'dotnet.exe' -ErrorAction SilentlyContinue if ($dotnetCmd -ne $null) { $env:DOTNET_INSTALL_DIR = Split-Path $dotnetCmd.Path -Parent } @@ -132,13 +137,13 @@ function InitializeDotNetCli([bool]$install) { if ((-not $globalJsonHasRuntimes) -and ($env:DOTNET_INSTALL_DIR -ne $null) -and (Test-Path(Join-Path $env:DOTNET_INSTALL_DIR "sdk\$dotnetSdkVersion"))) { $dotnetRoot = $env:DOTNET_INSTALL_DIR } else { - $dotnetRoot = Join-Path $RepoRoot ".dotnet" + $dotnetRoot = Join-Path $RepoRoot '.dotnet' if (-not (Test-Path(Join-Path $dotnetRoot "sdk\$dotnetSdkVersion"))) { if ($install) { InstallDotNetSdk $dotnetRoot $dotnetSdkVersion } else { - Write-PipelineTelemetryError -Category "InitializeToolset" -Message "Unable to find dotnet with SDK version '$dotnetSdkVersion'" + Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "Unable to find dotnet with SDK version '$dotnetSdkVersion'" ExitWithExitCode 1 } } @@ -146,6 +151,24 @@ function InitializeDotNetCli([bool]$install) { $env:DOTNET_INSTALL_DIR = $dotnetRoot } + # Creates a temporary file under the toolset dir. + # The following code block is protecting against concurrent access so that this function can + # be called in parallel. + if ($createSdkLocationFile) { + do { + $sdkCacheFileTemp = Join-Path $ToolsetDir $([System.IO.Path]::GetRandomFileName()) + } + until (!(Test-Path $sdkCacheFileTemp)) + Set-Content -Path $sdkCacheFileTemp -Value $dotnetRoot + + try { + Rename-Item -Force -Path $sdkCacheFileTemp 'sdk.txt' + } catch { + # Somebody beat us + Remove-Item -Path $sdkCacheFileTemp + } + } + # Add dotnet to PATH. This prevents any bare invocation of dotnet in custom # build steps from using anything other than what we've downloaded. # It also ensures that VS msbuild will use the downloaded sdk targets. @@ -154,15 +177,6 @@ function InitializeDotNetCli([bool]$install) { # Make Sure that our bootstrapped dotnet cli is available in future steps of the Azure Pipelines build Write-PipelinePrependPath -Path $dotnetRoot - # Work around issues with Azure Artifacts credential provider - # https://github.com/dotnet/arcade/issues/3932 - if ($ci) { - $env:NUGET_PLUGIN_HANDSHAKE_TIMEOUT_IN_SECONDS = 20 - $env:NUGET_PLUGIN_REQUEST_TIMEOUT_IN_SECONDS = 20 - Write-PipelineSetVariable -Name 'NUGET_PLUGIN_HANDSHAKE_TIMEOUT_IN_SECONDS' -Value '20' - Write-PipelineSetVariable -Name 'NUGET_PLUGIN_REQUEST_TIMEOUT_IN_SECONDS' -Value '20' - } - Write-PipelineSetVariable -Name 'DOTNET_MULTILEVEL_LOOKUP' -Value '0' Write-PipelineSetVariable -Name 'DOTNET_SKIP_FIRST_TIME_EXPERIENCE' -Value '1' @@ -170,27 +184,53 @@ function InitializeDotNetCli([bool]$install) { } function GetDotNetInstallScript([string] $dotnetRoot) { - $installScript = Join-Path $dotnetRoot "dotnet-install.ps1" + $installScript = Join-Path $dotnetRoot 'dotnet-install.ps1' if (!(Test-Path $installScript)) { Create-Directory $dotnetRoot $ProgressPreference = 'SilentlyContinue' # Don't display the console progress UI - it's a huge perf hit - Invoke-WebRequest "https://dot.net/$dotnetInstallScriptVersion/dotnet-install.ps1" -OutFile $installScript + + $maxRetries = 5 + $retries = 1 + + $uri = "https://dot.net/$dotnetInstallScriptVersion/dotnet-install.ps1" + + while($true) { + try { + Write-Host "GET $uri" + Invoke-WebRequest $uri -OutFile $installScript + break + } + catch { + Write-Host "Failed to download '$uri'" + Write-Error $_.Exception.Message -ErrorAction Continue + } + + if (++$retries -le $maxRetries) { + $delayInSeconds = [math]::Pow(2, $retries) - 1 # Exponential backoff + Write-Host "Retrying. Waiting for $delayInSeconds seconds before next attempt ($retries of $maxRetries)." + Start-Sleep -Seconds $delayInSeconds + } + else { + throw "Unable to download file in $maxRetries attempts." + } + + } } return $installScript } -function InstallDotNetSdk([string] $dotnetRoot, [string] $version, [string] $architecture = "") { +function InstallDotNetSdk([string] $dotnetRoot, [string] $version, [string] $architecture = '') { InstallDotNet $dotnetRoot $version $architecture } -function InstallDotNet([string] $dotnetRoot, - [string] $version, - [string] $architecture = "", - [string] $runtime = "", - [bool] $skipNonVersionedFiles = $false, - [string] $runtimeSourceFeed = "", - [string] $runtimeSourceFeedKey = "") { +function InstallDotNet([string] $dotnetRoot, + [string] $version, + [string] $architecture = '', + [string] $runtime = '', + [bool] $skipNonVersionedFiles = $false, + [string] $runtimeSourceFeed = '', + [string] $runtimeSourceFeedKey = '') { $installScript = GetDotNetInstallScript $dotnetRoot $installParameters = @{ @@ -206,7 +246,7 @@ function InstallDotNet([string] $dotnetRoot, & $installScript @installParameters } catch { - Write-PipelineTelemetryError -Category "InitializeToolset" -Message "Failed to install dotnet runtime '$runtime' from public location." + Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "Failed to install dotnet runtime '$runtime' from public location." # Only the runtime can be installed from a custom [private] location. if ($runtime -and ($runtimeSourceFeed -or $runtimeSourceFeedKey)) { @@ -222,7 +262,7 @@ function InstallDotNet([string] $dotnetRoot, & $installScript @installParameters } catch { - Write-PipelineTelemetryError -Category "InitializeToolset" -Message "Failed to install dotnet runtime '$runtime' from custom location '$runtimeSourceFeed'." + Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "Failed to install dotnet runtime '$runtime' from custom location '$runtimeSourceFeed'." ExitWithExitCode 1 } } else { @@ -248,16 +288,16 @@ function InitializeVisualStudioMSBuild([bool]$install, [object]$vsRequirements = } if (!$vsRequirements) { $vsRequirements = $GlobalJson.tools.vs } - $vsMinVersionStr = if ($vsRequirements.version) { $vsRequirements.version } else { "15.9" } + $vsMinVersionStr = if ($vsRequirements.version) { $vsRequirements.version } else { '15.9' } $vsMinVersion = [Version]::new($vsMinVersionStr) # Try msbuild command available in the environment. if ($env:VSINSTALLDIR -ne $null) { - $msbuildCmd = Get-Command "msbuild.exe" -ErrorAction SilentlyContinue + $msbuildCmd = Get-Command 'msbuild.exe' -ErrorAction SilentlyContinue if ($msbuildCmd -ne $null) { # Workaround for https://github.com/dotnet/roslyn/issues/35793 # Due to this issue $msbuildCmd.Version returns 0.0.0.0 for msbuild.exe 16.2+ - $msbuildVersion = [Version]::new((Get-Item $msbuildCmd.Path).VersionInfo.ProductVersion.Split(@('-', '+'))[0]) + $msbuildVersion = [Version]::new((Get-Item $msbuildCmd.Path).VersionInfo.ProductVersion.Split([char[]]@('-', '+'))[0]) if ($msbuildVersion -ge $vsMinVersion) { return $global:_MSBuildExe = $msbuildCmd.Path @@ -277,7 +317,7 @@ function InitializeVisualStudioMSBuild([bool]$install, [object]$vsRequirements = InitializeVisualStudioEnvironmentVariables $vsInstallDir $vsMajorVersion } else { - if (Get-Member -InputObject $GlobalJson.tools -Name "xcopy-msbuild") { + if (Get-Member -InputObject $GlobalJson.tools -Name 'xcopy-msbuild') { $xcopyMSBuildVersion = $GlobalJson.tools.'xcopy-msbuild' $vsMajorVersion = $xcopyMSBuildVersion.Split('.')[0] } else { @@ -285,9 +325,12 @@ function InitializeVisualStudioMSBuild([bool]$install, [object]$vsRequirements = $xcopyMSBuildVersion = "$vsMajorVersion.$($vsMinVersion.Minor).0-alpha" } - $vsInstallDir = InitializeXCopyMSBuild $xcopyMSBuildVersion $install + $vsInstallDir = $null + if ($xcopyMSBuildVersion.Trim() -ine "none") { + $vsInstallDir = InitializeXCopyMSBuild $xcopyMSBuildVersion $install + } if ($vsInstallDir -eq $null) { - throw "Unable to find Visual Studio that has required version and components installed" + throw 'Unable to find Visual Studio that has required version and components installed' } } @@ -311,7 +354,7 @@ function InstallXCopyMSBuild([string]$packageVersion) { } function InitializeXCopyMSBuild([string]$packageVersion, [bool]$install) { - $packageName = "RoslynTools.MSBuild" + $packageName = 'RoslynTools.MSBuild' $packageDir = Join-Path $ToolsDir "msbuild\$packageVersion" $packagePath = Join-Path $packageDir "$packageName.$packageVersion.nupkg" @@ -327,7 +370,7 @@ function InitializeXCopyMSBuild([string]$packageVersion, [bool]$install) { Unzip $packagePath $packageDir } - return Join-Path $packageDir "tools" + return Join-Path $packageDir 'tools' } # @@ -344,32 +387,37 @@ function InitializeXCopyMSBuild([string]$packageVersion, [bool]$install) { # or $null if no instance meeting the requirements is found on the machine. # function LocateVisualStudio([object]$vsRequirements = $null){ - if (Get-Member -InputObject $GlobalJson.tools -Name "vswhere") { + if (Get-Member -InputObject $GlobalJson.tools -Name 'vswhere') { $vswhereVersion = $GlobalJson.tools.vswhere } else { - $vswhereVersion = "2.5.2" + $vswhereVersion = '2.5.2' } $vsWhereDir = Join-Path $ToolsDir "vswhere\$vswhereVersion" - $vsWhereExe = Join-Path $vsWhereDir "vswhere.exe" + $vsWhereExe = Join-Path $vsWhereDir 'vswhere.exe' if (!(Test-Path $vsWhereExe)) { Create-Directory $vsWhereDir - Write-Host "Downloading vswhere" - Invoke-WebRequest "https://github.com/Microsoft/vswhere/releases/download/$vswhereVersion/vswhere.exe" -OutFile $vswhereExe + Write-Host 'Downloading vswhere' + try { + Invoke-WebRequest "https://netcorenativeassets.blob.core.windows.net/resource-packages/external/windows/vswhere/$vswhereVersion/vswhere.exe" -OutFile $vswhereExe + } + catch { + Write-PipelineTelemetryError -Category 'InitializeToolset' -Message $_ + } } if (!$vsRequirements) { $vsRequirements = $GlobalJson.tools.vs } - $args = @("-latest", "-prerelease", "-format", "json", "-requires", "Microsoft.Component.MSBuild", "-products", "*") + $args = @('-latest', '-prerelease', '-format', 'json', '-requires', 'Microsoft.Component.MSBuild', '-products', '*') - if (Get-Member -InputObject $vsRequirements -Name "version") { - $args += "-version" + if (Get-Member -InputObject $vsRequirements -Name 'version') { + $args += '-version' $args += $vsRequirements.version } - if (Get-Member -InputObject $vsRequirements -Name "components") { + if (Get-Member -InputObject $vsRequirements -Name 'components') { foreach ($component in $vsRequirements.components) { - $args += "-requires" + $args += '-requires' $args += $component } } @@ -395,27 +443,27 @@ function InitializeBuildTool() { # Initialize dotnet cli if listed in 'tools' $dotnetRoot = $null - if (Get-Member -InputObject $GlobalJson.tools -Name "dotnet") { + if (Get-Member -InputObject $GlobalJson.tools -Name 'dotnet') { $dotnetRoot = InitializeDotNetCli -install:$restore } - if ($msbuildEngine -eq "dotnet") { + if ($msbuildEngine -eq 'dotnet') { if (!$dotnetRoot) { - Write-PipelineTelemetryError -Category "InitializeToolset" -Message "/global.json must specify 'tools.dotnet'." + Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "/global.json must specify 'tools.dotnet'." ExitWithExitCode 1 } - $buildTool = @{ Path = Join-Path $dotnetRoot "dotnet.exe"; Command = "msbuild"; Tool = "dotnet"; Framework = "netcoreapp2.1" } + $buildTool = @{ Path = Join-Path $dotnetRoot 'dotnet.exe'; Command = 'msbuild'; Tool = 'dotnet'; Framework = 'netcoreapp2.1' } } elseif ($msbuildEngine -eq "vs") { try { $msbuildPath = InitializeVisualStudioMSBuild -install:$restore } catch { - Write-PipelineTelemetryError -Category "InitializeToolset" -Message $_ + Write-PipelineTelemetryError -Category 'InitializeToolset' -Message $_ ExitWithExitCode 1 } $buildTool = @{ Path = $msbuildPath; Command = ""; Tool = "vs"; Framework = "net472" } } else { - Write-PipelineTelemetryError -Category "InitializeToolset" -Message "Unexpected value of -msbuildEngine: '$msbuildEngine'." + Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "Unexpected value of -msbuildEngine: '$msbuildEngine'." ExitWithExitCode 1 } @@ -424,15 +472,15 @@ function InitializeBuildTool() { function GetDefaultMSBuildEngine() { # Presence of tools.vs indicates the repo needs to build using VS msbuild on Windows. - if (Get-Member -InputObject $GlobalJson.tools -Name "vs") { - return "vs" + if (Get-Member -InputObject $GlobalJson.tools -Name 'vs') { + return 'vs' } - if (Get-Member -InputObject $GlobalJson.tools -Name "dotnet") { - return "dotnet" + if (Get-Member -InputObject $GlobalJson.tools -Name 'dotnet') { + return 'dotnet' } - Write-PipelineTelemetryError -Category "InitializeToolset" -Message "-msbuildEngine must be specified, or /global.json must specify 'tools.dotnet' or 'tools.vs'." + Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "-msbuildEngine must be specified, or /global.json must specify 'tools.dotnet' or 'tools.vs'." ExitWithExitCode 1 } @@ -441,9 +489,9 @@ function GetNuGetPackageCachePath() { # Use local cache on CI to ensure deterministic build, # use global cache in dev builds to avoid cost of downloading packages. if ($useGlobalNuGetCache) { - $env:NUGET_PACKAGES = Join-Path $env:UserProfile ".nuget\packages" + $env:NUGET_PACKAGES = Join-Path $env:UserProfile '.nuget\packages' } else { - $env:NUGET_PACKAGES = Join-Path $RepoRoot ".packages" + $env:NUGET_PACKAGES = Join-Path $RepoRoot '.packages' } } @@ -456,7 +504,7 @@ function GetSdkTaskProject([string]$taskName) { } function InitializeNativeTools() { - if (Get-Member -InputObject $GlobalJson -Name "native-tools") { + if (-Not (Test-Path variable:DisableNativeToolsetInstalls) -And (Get-Member -InputObject $GlobalJson -Name "native-tools")) { $nativeArgs= @{} if ($ci) { $nativeArgs = @{ @@ -485,14 +533,14 @@ function InitializeToolset() { } if (-not $restore) { - Write-PipelineTelemetryError -Category "InitializeToolset" -Message "Toolset version $toolsetVersion has not been restored." + Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "Toolset version $toolsetVersion has not been restored." ExitWithExitCode 1 } $buildTool = InitializeBuildTool - $proj = Join-Path $ToolsetDir "restore.proj" - $bl = if ($binaryLog) { "/bl:" + (Join-Path $LogDir "ToolsetRestore.binlog") } else { "" } + $proj = Join-Path $ToolsetDir 'restore.proj' + $bl = if ($binaryLog) { '/bl:' + (Join-Path $LogDir 'ToolsetRestore.binlog') } else { '' } '' | Set-Content $proj @@ -514,7 +562,7 @@ function ExitWithExitCode([int] $exitCode) { } function Stop-Processes() { - Write-Host "Killing running build processes..." + Write-Host 'Killing running build processes...' foreach ($processName in $processesToStopOnExit) { Get-Process -Name $processName -ErrorAction SilentlyContinue | Stop-Process } @@ -531,13 +579,18 @@ function MSBuild() { # Work around issues with Azure Artifacts credential provider # https://github.com/dotnet/arcade/issues/3932 - if ($ci -and $buildTool.Tool -eq "dotnet") { + if ($ci -and $buildTool.Tool -eq 'dotnet') { dotnet nuget locals http-cache -c + + $env:NUGET_PLUGIN_HANDSHAKE_TIMEOUT_IN_SECONDS = 20 + $env:NUGET_PLUGIN_REQUEST_TIMEOUT_IN_SECONDS = 20 + Write-PipelineSetVariable -Name 'NUGET_PLUGIN_HANDSHAKE_TIMEOUT_IN_SECONDS' -Value '20' + Write-PipelineSetVariable -Name 'NUGET_PLUGIN_REQUEST_TIMEOUT_IN_SECONDS' -Value '20' } $toolsetBuildProject = InitializeToolset $path = Split-Path -parent $toolsetBuildProject - $path = Join-Path $path (Join-Path $buildTool.Framework "Microsoft.DotNet.Arcade.Sdk.dll") + $path = Join-Path $path (Join-Path $buildTool.Framework 'Microsoft.DotNet.Arcade.Sdk.dll') $args += "/logger:$path" } @@ -552,12 +605,12 @@ function MSBuild() { function MSBuild-Core() { if ($ci) { if (!$binaryLog) { - Write-PipelineTaskError -Message "Binary log must be enabled in CI build." + Write-PipelineTelemetryError -Category 'Build' -Message 'Binary log must be enabled in CI build.' ExitWithExitCode 1 } if ($nodeReuse) { - Write-PipelineTaskError -Message "Node reuse must be disabled in CI build." + Write-PipelineTelemetryError -Category 'Build' -Message 'Node reuse must be disabled in CI build.' ExitWithExitCode 1 } } @@ -567,10 +620,10 @@ function MSBuild-Core() { $cmdArgs = "$($buildTool.Command) /m /nologo /clp:Summary /v:$verbosity /nr:$nodeReuse /p:ContinuousIntegrationBuild=$ci" if ($warnAsError) { - $cmdArgs += " /warnaserror /p:TreatWarningsAsErrors=true" + $cmdArgs += ' /warnaserror /p:TreatWarningsAsErrors=true' } else { - $cmdArgs += " /p:TreatWarningsAsErrors=false" + $cmdArgs += ' /p:TreatWarningsAsErrors=false' } foreach ($arg in $args) { @@ -582,7 +635,7 @@ function MSBuild-Core() { $exitCode = Exec-Process $buildTool.Path $cmdArgs if ($exitCode -ne 0) { - Write-PipelineTaskError -Message "Build failed." + Write-PipelineTelemetryError -Category 'Build' -Message 'Build failed.' $buildLog = GetMSBuildBinaryLogCommandLineArgument $args if ($buildLog -ne $null) { @@ -597,12 +650,12 @@ function GetMSBuildBinaryLogCommandLineArgument($arguments) { foreach ($argument in $arguments) { if ($argument -ne $null) { $arg = $argument.Trim() - if ($arg.StartsWith("/bl:", "OrdinalIgnoreCase")) { - return $arg.Substring("/bl:".Length) + if ($arg.StartsWith('/bl:', "OrdinalIgnoreCase")) { + return $arg.Substring('/bl:'.Length) } - if ($arg.StartsWith("/binaryLogger:", "OrdinalIgnoreCase")) { - return $arg.Substring("/binaryLogger:".Length) + if ($arg.StartsWith('/binaryLogger:', 'OrdinalIgnoreCase')) { + return $arg.Substring('/binaryLogger:'.Length) } } } @@ -612,14 +665,14 @@ function GetMSBuildBinaryLogCommandLineArgument($arguments) { . $PSScriptRoot\pipeline-logging-functions.ps1 -$RepoRoot = Resolve-Path (Join-Path $PSScriptRoot "..\..") -$EngRoot = Resolve-Path (Join-Path $PSScriptRoot "..") -$ArtifactsDir = Join-Path $RepoRoot "artifacts" -$ToolsetDir = Join-Path $ArtifactsDir "toolset" -$ToolsDir = Join-Path $RepoRoot ".tools" -$LogDir = Join-Path (Join-Path $ArtifactsDir "log") $configuration -$TempDir = Join-Path (Join-Path $ArtifactsDir "tmp") $configuration -$GlobalJson = Get-Content -Raw -Path (Join-Path $RepoRoot "global.json") | ConvertFrom-Json +$RepoRoot = Resolve-Path (Join-Path $PSScriptRoot '..\..') +$EngRoot = Resolve-Path (Join-Path $PSScriptRoot '..') +$ArtifactsDir = Join-Path $RepoRoot 'artifacts' +$ToolsetDir = Join-Path $ArtifactsDir 'toolset' +$ToolsDir = Join-Path $RepoRoot '.tools' +$LogDir = Join-Path (Join-Path $ArtifactsDir 'log') $configuration +$TempDir = Join-Path (Join-Path $ArtifactsDir 'tmp') $configuration +$GlobalJson = Get-Content -Raw -Path (Join-Path $RepoRoot 'global.json') | ConvertFrom-Json # true if global.json contains a "runtimes" section $globalJsonHasRuntimes = if ($GlobalJson.tools.PSObject.Properties.Name -Match 'runtimes') { $true } else { $false } @@ -632,3 +685,18 @@ Write-PipelineSetVariable -Name 'Artifacts.Toolset' -Value $ToolsetDir Write-PipelineSetVariable -Name 'Artifacts.Log' -Value $LogDir Write-PipelineSetVariable -Name 'TEMP' -Value $TempDir Write-PipelineSetVariable -Name 'TMP' -Value $TempDir + +# Import custom tools configuration, if present in the repo. +# Note: Import in global scope so that the script set top-level variables without qualification. +if (!$disableConfigureToolsetImport) { + $configureToolsetScript = Join-Path $EngRoot 'configure-toolset.ps1' + if (Test-Path $configureToolsetScript) { + . $configureToolsetScript + if ((Test-Path variable:failOnConfigureToolsetError) -And $failOnConfigureToolsetError) { + if ((Test-Path variable:LastExitCode) -And ($LastExitCode -ne 0)) { + Write-PipelineTelemetryError -Category 'Build' -Message 'configure-toolset.ps1 returned a non-zero exit code' + ExitWithExitCode $LastExitCode + } + } + } +} diff --git a/eng/common/tools.sh b/eng/common/tools.sh index 94965a8fd2..664ac1055b 100755 --- a/eng/common/tools.sh +++ b/eng/common/tools.sh @@ -41,7 +41,7 @@ fi # Configures warning treatment in msbuild. warn_as_error=${warn_as_error:-true} -# True to attempt using .NET Core already that meets requirements specified in global.json +# True to attempt using .NET Core already that meets requirements specified in global.json # installed on the machine instead of downloading one. use_installed_dotnet_cli=${use_installed_dotnet_cli:-true} @@ -81,7 +81,7 @@ function ReadGlobalVersion { local pattern="\"$key\" *: *\"(.*)\"" if [[ ! $line =~ $pattern ]]; then - Write-PipelineTelemetryError -category 'InitializeToolset' "Error: Cannot find \"$key\" in $global_json_file" + Write-PipelineTelemetryError -category 'Build' "Error: Cannot find \"$key\" in $global_json_file" ExitWithExitCode 1 fi @@ -152,15 +152,6 @@ function InitializeDotNetCli { # build steps from using anything other than what we've downloaded. Write-PipelinePrependPath -path "$dotnet_root" - # Work around issues with Azure Artifacts credential provider - # https://github.com/dotnet/arcade/issues/3932 - if [[ "$ci" == true ]]; then - export NUGET_PLUGIN_HANDSHAKE_TIMEOUT_IN_SECONDS=20 - export NUGET_PLUGIN_REQUEST_TIMEOUT_IN_SECONDS=20 - Write-PipelineSetVariable -name "NUGET_PLUGIN_HANDSHAKE_TIMEOUT_IN_SECONDS" -value "20" - Write-PipelineSetVariable -name "NUGET_PLUGIN_REQUEST_TIMEOUT_IN_SECONDS" -value "20" - fi - Write-PipelineSetVariable -name "DOTNET_MULTILEVEL_LOOKUP" -value "0" Write-PipelineSetVariable -name "DOTNET_SKIP_FIRST_TIME_EXPERIENCE" -value "1" @@ -181,7 +172,7 @@ function InstallDotNetSdk { function InstallDotNet { local root=$1 local version=$2 - + GetDotNetInstallScript "$root" local install_script=$_GetDotNetInstallScript @@ -227,6 +218,28 @@ function InstallDotNet { } } +function with_retries { + local maxRetries=5 + local retries=1 + echo "Trying to run '$@' for maximum of $maxRetries attempts." + while [[ $((retries++)) -le $maxRetries ]]; do + "$@" + + if [[ $? == 0 ]]; then + echo "Ran '$@' successfully." + return 0 + fi + + timeout=$((2**$retries-1)) + echo "Failed to execute '$@'. Waiting $timeout seconds before next attempt ($retries out of $maxRetries)." 1>&2 + sleep $timeout + done + + echo "Failed to execute '$@' for $maxRetries times." 1>&2 + + return 1 +} + function GetDotNetInstallScript { local root=$1 local install_script="$root/dotnet-install.sh" @@ -239,13 +252,13 @@ function GetDotNetInstallScript { # Use curl if available, otherwise use wget if command -v curl > /dev/null; then - curl "$install_script_url" -sSL --retry 10 --create-dirs -o "$install_script" || { + with_retries curl "$install_script_url" -sSL --retry 10 --create-dirs -o "$install_script" || { local exit_code=$? Write-PipelineTelemetryError -category 'InitializeToolset' "Failed to acquire dotnet install script (exit code '$exit_code')." ExitWithExitCode $exit_code } - else - wget -q -O "$install_script" "$install_script_url" || { + else + with_retries wget -v -O "$install_script" "$install_script_url" || { local exit_code=$? Write-PipelineTelemetryError -category 'InitializeToolset' "Failed to acquire dotnet install script (exit code '$exit_code')." ExitWithExitCode $exit_code @@ -260,11 +273,11 @@ function InitializeBuildTool { if [[ -n "${_InitializeBuildTool:-}" ]]; then return fi - + InitializeDotNetCli $restore # return values - _InitializeBuildTool="$_InitializeDotNetCli/dotnet" + _InitializeBuildTool="$_InitializeDotNetCli/dotnet" _InitializeBuildToolCommand="msbuild" _InitializeBuildToolFramework="netcoreapp2.1" } @@ -283,6 +296,9 @@ function GetNuGetPackageCachePath { } function InitializeNativeTools() { + if [[ -n "${DisableNativeToolsetInstalls:-}" ]]; then + return + fi if grep -Fq "native-tools" $global_json_file then local nativeArgs="" @@ -325,14 +341,14 @@ function InitializeToolset { if [[ "$binary_log" == true ]]; then bl="/bl:$log_dir/ToolsetRestore.binlog" fi - + echo '' > "$proj" MSBuild-Core "$proj" $bl /t:__WriteToolsetLocation /clp:ErrorsOnly\;NoSummary /p:__ToolsetLocationOutputFile="$toolset_location_file" local toolset_build_proj=`cat "$toolset_location_file"` if [[ ! -a "$toolset_build_proj" ]]; then - Write-PipelineTelemetryError -category 'InitializeToolset' "Invalid toolset path: $toolset_build_proj" + Write-PipelineTelemetryError -category 'Build' "Invalid toolset path: $toolset_build_proj" ExitWithExitCode 3 fi @@ -363,7 +379,12 @@ function MSBuild { # Work around issues with Azure Artifacts credential provider # https://github.com/dotnet/arcade/issues/3932 if [[ "$ci" == true ]]; then - dotnet nuget locals http-cache -c + "$_InitializeBuildTool" nuget locals http-cache -c + + export NUGET_PLUGIN_HANDSHAKE_TIMEOUT_IN_SECONDS=20 + export NUGET_PLUGIN_REQUEST_TIMEOUT_IN_SECONDS=20 + Write-PipelineSetVariable -name "NUGET_PLUGIN_HANDSHAKE_TIMEOUT_IN_SECONDS" -value "20" + Write-PipelineSetVariable -name "NUGET_PLUGIN_REQUEST_TIMEOUT_IN_SECONDS" -value "20" fi local toolset_dir="${_InitializeToolset%/*}" @@ -377,12 +398,12 @@ function MSBuild { function MSBuild-Core { if [[ "$ci" == true ]]; then if [[ "$binary_log" != true ]]; then - Write-PipelineTaskError "Binary log must be enabled in CI build." + Write-PipelineTelemetryError -category 'Build' "Binary log must be enabled in CI build." ExitWithExitCode 1 fi if [[ "$node_reuse" == true ]]; then - Write-PipelineTaskError "Node reuse must be disabled in CI build." + Write-PipelineTelemetryError -category 'Build' "Node reuse must be disabled in CI build." ExitWithExitCode 1 fi fi @@ -396,7 +417,7 @@ function MSBuild-Core { "$_InitializeBuildTool" "$_InitializeBuildToolCommand" /m /nologo /clp:Summary /v:$verbosity /nr:$node_reuse $warnaserror_switch /p:TreatWarningsAsErrors=$warn_as_error /p:ContinuousIntegrationBuild=$ci "$@" || { local exit_code=$? - Write-PipelineTaskError "Build failed (exit code '$exit_code')." + Write-PipelineTelemetryError -category 'Build' "Build failed (exit code '$exit_code')." ExitWithExitCode $exit_code } } @@ -437,3 +458,18 @@ Write-PipelineSetVariable -name "Artifacts.Toolset" -value "$toolset_dir" Write-PipelineSetVariable -name "Artifacts.Log" -value "$log_dir" Write-PipelineSetVariable -name "Temp" -value "$temp_dir" Write-PipelineSetVariable -name "TMP" -value "$temp_dir" + +# Import custom tools configuration, if present in the repo. +if [ -z "${disable_configure_toolset_import:-}" ]; then + configure_toolset_script="$eng_root/configure-toolset.sh" + if [[ -a "$configure_toolset_script" ]]; then + . "$configure_toolset_script" + fi +fi + +# TODO: https://github.com/dotnet/arcade/issues/1468 +# Temporary workaround to avoid breaking change. +# Remove once repos are updated. +if [[ -n "${useInstalledDotNetCli:-}" ]]; then + use_installed_dotnet_cli="$useInstalledDotNetCli" +fi diff --git a/eng/configure-toolset.sh b/eng/configure-toolset.sh old mode 100644 new mode 100755 diff --git a/eng/helix/content/InstallAppRuntime.ps1 b/eng/helix/content/InstallAppRuntime.ps1 new file mode 100644 index 0000000000..9d9aaffb5c --- /dev/null +++ b/eng/helix/content/InstallAppRuntime.ps1 @@ -0,0 +1,54 @@ + <# + .SYNOPSIS + Installs an AspNetCore shared framework on a machine + .DESCRIPTION + This script installs an AspNetCore shared framework on a machine + .PARAMETER AppRuntimePath + The path to the app runtime package to install. + .PARAMETER InstallDir + The directory to install the shared framework to. + .PARAMETER Framework + The framework directory to copy the shared framework from. + .PARAMETER RuntimeIdentifier + The runtime identifier for the shared framework. + #> +param( + [Parameter(Mandatory = $true)] + $AppRuntimePath, + + [Parameter(Mandatory = $true)] + $InstallDir, + + [Parameter(Mandatory = $true)] + $Framework, + + [Parameter(Mandatory = $true)] + $RuntimeIdentifier) + +$ErrorActionPreference = 'Stop' +$ProgressPreference = 'SilentlyContinue' # Workaround PowerShell/PowerShell#2138 + +Set-StrictMode -Version 1 + +Write-Host "Extracting to $InstallDir" + +$zipPackage = [io.path]::ChangeExtension($AppRuntimePath, ".zip") +Write-Host "Renaming to $zipPackage" +Rename-Item -Path $AppRuntimePath -NewName $zipPackage +if (Get-Command -Name 'Microsoft.PowerShell.Archive\Expand-Archive' -ErrorAction Ignore) { + # Use built-in commands where possible as they are cross-plat compatible + Microsoft.PowerShell.Archive\Expand-Archive -Path $zipPackage -DestinationPath ".\tmpRuntime" -Force +} +else { + Remove-Item ".\tmpRuntime" -Recurse -ErrorAction Ignore + # Fallback to old approach for old installations of PowerShell + Add-Type -AssemblyName System.IO.Compression.FileSystem + [System.IO.Compression.ZipFile]::ExtractToDirectory($zipPackage, ".\tmpRuntime") +} + +Get-ChildItem -Path ".\tmpRuntime" -Recurse + +Write-Host "Copying managed files to $InstallDir" +Copy-Item -Path ".\tmpRuntime\runtimes\$RuntimeIdentifier\lib\$Framework\*" $InstallDir +Write-Host "Copying native files to $InstallDir" +Copy-Item -Path ".\tmpRuntime\runtimes\$RuntimeIdentifier\native\*" $InstallDir diff --git a/eng/helix/content/InstallJdk.ps1 b/eng/helix/content/InstallJdk.ps1 new file mode 100644 index 0000000000..a9346062fa --- /dev/null +++ b/eng/helix/content/InstallJdk.ps1 @@ -0,0 +1,62 @@ +<# +.SYNOPSIS + Installs JDK into a folder in this repo. +.DESCRIPTION + This script downloads an extracts the JDK. +.PARAMETER JdkVersion + The version of the JDK to install. If not set, the default value is read from global.json +.PARAMETER Force + Overwrite the existing installation +#> +param( + [string]$JdkVersion, + [Parameter(Mandatory = $false)] + $InstallDir +) +$ErrorActionPreference = 'Stop' +$ProgressPreference = 'SilentlyContinue' # Workaround PowerShell/PowerShell#2138 + +Set-StrictMode -Version 1 + +if ($InstallDir) { + $installDir = $InstallDir; +} +else { + $repoRoot = Resolve-Path "$PSScriptRoot\..\.." + $installDir = "$repoRoot\.tools\jdk\win-x64\" +} +$tempDir = "$installDir\obj" +if (-not $JdkVersion) { + $globalJson = Get-Content "$repoRoot\global.json" | ConvertFrom-Json + $JdkVersion = $globalJson.tools.jdk +} + +if (Test-Path $installDir) { + if ($Force) { + Remove-Item -Force -Recurse $installDir + } + else { + Write-Host "The JDK already installed to $installDir. Exiting without action. Call this script again with -Force to overwrite." + exit 0 + } +} + +Remove-Item -Force -Recurse $tempDir -ErrorAction Ignore | out-null +mkdir $tempDir -ea Ignore | out-null +mkdir $installDir -ea Ignore | out-null +Write-Host "Starting download of JDK ${JdkVersion}" +Invoke-WebRequest -UseBasicParsing -Uri "https://netcorenativeassets.blob.core.windows.net/resource-packages/external/windows/java/jdk-${JdkVersion}_windows-x64_bin.zip" -OutFile "$tempDir/jdk.zip" +Write-Host "Done downloading JDK ${JdkVersion}" + +Add-Type -assembly "System.IO.Compression.FileSystem" +[System.IO.Compression.ZipFile]::ExtractToDirectory("$tempDir/jdk.zip", "$tempDir/jdk/") + +Write-Host "Expanded JDK to $tempDir" +Write-Host "Installing JDK to $installDir" +Move-Item "$tempDir/jdk/jdk-${JdkVersion}/*" $installDir +Write-Host "Done installing JDK to $installDir" +Remove-Item -Force -Recurse $tempDir -ErrorAction Ignore | out-null + +if ($env:TF_BUILD) { + Write-Host "##vso[task.prependpath]$installDir\bin" +} diff --git a/eng/helix/content/InstallNode.ps1 b/eng/helix/content/InstallNode.ps1 index 862e612582..3754eee5f5 100644 --- a/eng/helix/content/InstallNode.ps1 +++ b/eng/helix/content/InstallNode.ps1 @@ -29,9 +29,9 @@ if (Get-Command "node.exe" -ErrorAction SilentlyContinue) exit } -if (Test-Path "$output_dir\node.exe") +if (Test-Path "$InstallDir\node.exe") { - Write-Host "Node.exe found at $output_dir" + Write-Host "Node.exe found at $InstallDir" exit } @@ -48,9 +48,10 @@ Write-Host "Extracting to $tempDir" if (Get-Command -Name 'Microsoft.PowerShell.Archive\Expand-Archive' -ErrorAction Ignore) { # Use built-in commands where possible as they are cross-plat compatible - Microsoft.PowerShell.Archive\Expand-Archive -Path "nodejs.zip" -DestinationPath $tempDir + Microsoft.PowerShell.Archive\Expand-Archive -Path "nodejs.zip" -DestinationPath $tempDir -Force } else { + Remove-Item $tempDir -Recurse -ErrorAction Ignore # Fallback to old approach for old installations of PowerShell Add-Type -AssemblyName System.IO.Compression.FileSystem [System.IO.Compression.ZipFile]::ExtractToDirectory("nodejs.zip", $tempDir) diff --git a/eng/helix/content/default.runner.json b/eng/helix/content/default.runner.json new file mode 100644 index 0000000000..ac621e1a5d --- /dev/null +++ b/eng/helix/content/default.runner.json @@ -0,0 +1,4 @@ +{ + "longRunningTestSeconds": 60, + "diagnosticMessages": true +} diff --git a/eng/helix/content/installappruntime.sh b/eng/helix/content/installappruntime.sh new file mode 100755 index 0000000000..45cb1554fa --- /dev/null +++ b/eng/helix/content/installappruntime.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash + +# Cause the script to fail if any subcommand fails +set -e + +appRuntimePath=$1 +output_dir=$2 +framework=$3 +rid=$4 +tmpDir=./tmpRuntime + +echo "Installing shared framework from $appRuntimePath" +cp $appRuntimePath sharedFx.zip + +mkdir -p $tmpDir +unzip sharedFx.zip -d $tmpDir +mkdir -p $output_dir +echo "Copying to $output_dir" +cp $tmpDir/runtimes/$rid/lib/$framework/* $output_dir +cp $tmpDir/runtimes/$rid/native/* $output_dir diff --git a/eng/helix/content/installjdk.sh b/eng/helix/content/installjdk.sh new file mode 100755 index 0000000000..6c1c2ff5c6 --- /dev/null +++ b/eng/helix/content/installjdk.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash + +# Cause the script to fail if any subcommand fails +set -e + +pushd . + +if [ "$JAVA_HOME" != "" ]; then + echo "JAVA_HOME is set" + exit +fi + +java_version=$1 +arch=$2 +osname=`uname -s` +if [ "$osname" = "Darwin" ]; then + echo "macOS not supported, relying on the machine providing java itself" + exit 1 +else + platformarch="linux-$arch" +fi +echo "PlatformArch: $platformarch" +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +output_dir="$DIR/java" +url="https://netcorenativeassets.blob.core.windows.net/resource-packages/external/linux/java/jdk-${java_version}_${platformarch}_bin.tar.gz" +echo "Downloading from: $url" +tmp="$(mktemp -d -t install-jdk.XXXXXX)" + +cleanup() { + exitcode=$? + if [ $exitcode -ne 0 ]; then + echo "Failed to install java with exit code: $exitcode" + fi + rm -rf "$tmp" + exit $exitcode +} + +trap "cleanup" EXIT +cd "$tmp" +curl -Lsfo $(basename $url) "$url" +echo "Installing java from $(basename $url) $url" +mkdir $output_dir +echo "Unpacking to $output_dir" +tar --strip-components 1 -xzf "jdk-${java_version}_${platformarch}_bin.tar.gz" --no-same-owner --directory "$output_dir" + +popd \ No newline at end of file diff --git a/eng/helix/content/installnode.sh b/eng/helix/content/installnode.sh old mode 100644 new mode 100755 index 0442958ac2..db5d2fa5a5 --- a/eng/helix/content/installnode.sh +++ b/eng/helix/content/installnode.sh @@ -22,7 +22,17 @@ output_dir="$DIR/node" url="http://nodejs.org/dist/v$node_version/node-v$node_version-$platformarch.tar.gz" echo "Downloading from: $url" tmp="$(mktemp -d -t install-node.XXXXXX)" -trap "rm -rf $tmp" EXIT + +cleanup() { + exitcode=$? + if [ $exitcode -ne 0 ]; then + echo "Failed to install node with exit code: $exitcode" + fi + rm -rf "$tmp" + exit $exitcode +} + +trap "cleanup" EXIT cd "$tmp" curl -Lsfo $(basename $url) "$url" echo "Installing node from $(basename $url) $url" diff --git a/eng/helix/content/runtests.cmd b/eng/helix/content/runtests.cmd index 935b23647d..6dec0880d5 100644 --- a/eng/helix/content/runtests.cmd +++ b/eng/helix/content/runtests.cmd @@ -1,33 +1,62 @@ @echo off -REM Disable "!Foo!" expansions because they break the filter syntax -setlocal disableextensions +REM Need delayed expansion !PATH! so parens in the path don't mess up the parens for the if statements that use parens for blocks +setlocal enabledelayedexpansion -set target=%1 -set targetFrameworkIdentifier=%2 -set sdkVersion=%3 -set runtimeVersion=%4 -set helixQueue=%5 -set arch=%6 +REM Use '$' as a variable name prefix to avoid MSBuild variable collisions with these variables +set $target=%1 +set $sdkVersion=%2 +set $runtimeVersion=%3 +set $helixQueue=%4 +set $arch=%5 +set $quarantined=%6 +set $efVersion=%7 set DOTNET_HOME=%HELIX_CORRELATION_PAYLOAD%\sdk -set DOTNET_ROOT=%DOTNET_HOME%\%arch% +set DOTNET_ROOT=%DOTNET_HOME%\%$arch% set DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 set DOTNET_MULTILEVEL_LOOKUP=0 set DOTNET_CLI_HOME=%HELIX_CORRELATION_PAYLOAD%\home -set PATH=%DOTNET_ROOT%;%PATH%;%HELIX_CORRELATION_PAYLOAD%\node\bin +set PATH=%DOTNET_ROOT%;!PATH!;%HELIX_CORRELATION_PAYLOAD%\node\bin +echo Set path to: %PATH% +echo "Installing SDK" +powershell.exe -NoProfile -ExecutionPolicy unrestricted -Command "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; &([scriptblock]::Create((Invoke-WebRequest -useb 'https://dot.net/v1/dotnet-install.ps1'))) -Architecture %$arch% -Version %$sdkVersion% -InstallDir %DOTNET_ROOT%" +echo "Installing Runtime" +powershell.exe -NoProfile -ExecutionPolicy unrestricted -Command "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; &([scriptblock]::Create((Invoke-WebRequest -useb 'https://dot.net/v1/dotnet-install.ps1'))) -Architecture %$arch% -Runtime dotnet -Version %$runtimeVersion% -InstallDir %DOTNET_ROOT%" +echo "Checking for Microsoft.AspNetCore.App" +if EXIST ".\Microsoft.AspNetCore.App" ( + echo "Found Microsoft.AspNetCore.App, copying to %DOTNET_ROOT%\shared\Microsoft.AspNetCore.App\%runtimeVersion%" + xcopy /i /y ".\Microsoft.AspNetCore.App" %DOTNET_ROOT%\shared\Microsoft.AspNetCore.App\%runtimeVersion%\ -powershell.exe -NoProfile -ExecutionPolicy unrestricted -Command "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; &([scriptblock]::Create((Invoke-WebRequest -useb 'https://dot.net/v1/dotnet-install.ps1'))) -Architecture %arch% -Version %sdkVersion% -InstallDir %DOTNET_ROOT%" -powershell.exe -NoProfile -ExecutionPolicy unrestricted -Command "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; &([scriptblock]::Create((Invoke-WebRequest -useb 'https://dot.net/v1/dotnet-install.ps1'))) -Architecture %arch% -Runtime dotnet -Version %runtimeVersion% -InstallDir %DOTNET_ROOT%" + echo "Adding current directory to nuget sources: %HELIX_WORKITEM_ROOT%" + dotnet nuget add source %HELIX_WORKITEM_ROOT% + dotnet nuget add source https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet5/nuget/v3/index.json + dotnet nuget list source + dotnet tool install dotnet-ef --global --version %$efVersion% -set HELIX=%helixQueue% - -if (%targetFrameworkIdentifier%==.NETFramework) ( - xunit.console.exe %target% -xml testResults.xml - exit /b %ERRORLEVEL% + set PATH=!PATH!;%DOTNET_CLI_HOME%\.dotnet\tools ) -%DOTNET_ROOT%\dotnet vstest %target% -lt >discovered.txt +echo "Current Directory: %HELIX_WORKITEM_ROOT%" +set HELIX=%$helixQueue% +set HELIX_DIR=%HELIX_WORKITEM_ROOT% +set NUGET_FALLBACK_PACKAGES=%HELIX_DIR% +set NUGET_RESTORE=%HELIX_DIR%\nugetRestore +set DotNetEfFullPath=%HELIX_DIR%\nugetRestore\dotnet-ef\%$efVersion%\tools\netcoreapp3.1\any\dotnet-ef.exe +echo "Set DotNetEfFullPath: %DotNetEfFullPath%" +echo "Setting HELIX_DIR: %HELIX_DIR%" +echo Creating nuget restore directory: %NUGET_RESTORE% +mkdir %NUGET_RESTORE% +mkdir logs + +REM "Rename default.runner.json to xunit.runner.json if there is not a custom one from the project" +if not EXIST ".\xunit.runner.json" ( + copy default.runner.json xunit.runner.json +) + +dir + +%DOTNET_ROOT%\dotnet vstest %$target% -lt >discovered.txt find /c "Exception thrown" discovered.txt REM "ERRORLEVEL is not %ERRORLEVEL%" https://blogs.msdn.microsoft.com/oldnewthing/20080926-00/?p=20743/ if not errorlevel 1 ( @@ -38,25 +67,39 @@ if not errorlevel 1 ( set exit_code=0 -REM Run non-flaky tests first -REM We need to specify all possible Flaky filters that apply to this environment, because the flaky attribute -REM only puts the explicit filter traits the user provided in -REM Filter syntax: https://github.com/Microsoft/vstest-docs/blob/master/docs/filter.md -set NONFLAKY_FILTER="Flaky:All!=true&Flaky:Helix:All!=true&Flaky:Helix:Queue:All!=true&Flaky:Helix:Queue:%HELIX%!=true" -echo Running non-flaky tests. -%DOTNET_ROOT%\dotnet vstest %target% --logger:trx --TestCaseFilter:%NONFLAKY_FILTER% -if errorlevel 1 ( - echo Failure in non-flaky test 1>&2 - set exit_code=1 - REM DO NOT EXIT +if %$quarantined%==True ( + set %$quarantined=true ) -set FLAKY_FILTER="Flaky:All=true|Flaky:Helix:All=true|Flaky:Helix:Queue:All=true|Flaky:Helix:Queue:%HELIX%=true" -echo Running known-flaky tests. -%DOTNET_ROOT%\dotnet vstest %target% --TestCaseFilter:%FLAKY_FILTER% -if errorlevel 1 ( - echo Failure in flaky test 1>&2 - REM DO NOT EXIT and DO NOT SET EXIT_CODE to 1 +REM Disable "!Foo!" expansions because they break the filter syntax +setlocal disabledelayedexpansion +set NONQUARANTINE_FILTER="Quarantined!=true" +set QUARANTINE_FILTER="Quarantined=true" +if %$quarantined%==true ( + echo Running quarantined tests. + %DOTNET_ROOT%\dotnet vstest %$target% --logger:xunit --TestCaseFilter:%QUARANTINE_FILTER% + if errorlevel 1 ( + echo Failure in quarantined test 1>&2 + REM DO NOT EXIT and DO NOT SET EXIT_CODE to 1 + ) +) else ( + REM Filter syntax: https://github.com/Microsoft/vstest-docs/blob/master/docs/filter.md + echo Running non-quarantined tests. + %DOTNET_ROOT%\dotnet vstest %$target% --logger:xunit --TestCaseFilter:%NONQUARANTINE_FILTER% + if errorlevel 1 ( + echo Failure in non-quarantined test 1>&2 + set exit_code=1 + REM DO NOT EXIT + ) +) + +echo "Copying TestResults\TestResults.xml to ." +copy TestResults\TestResults.xml testResults.xml +echo "Copying artifacts/logs to %HELIX_WORKITEM_UPLOAD_ROOT%\..\" +for /R artifacts/log %%f in (*.log) do ( + echo "Copying: %%f" + copy "%%f" %HELIX_WORKITEM_UPLOAD_ROOT%\..\ + copy "%%f" %HELIX_WORKITEM_UPLOAD_ROOT%\ ) exit /b %exit_code% diff --git a/eng/helix/content/runtests.sh b/eng/helix/content/runtests.sh old mode 100644 new mode 100755 index e864f097de..7788f800f9 --- a/eng/helix/content/runtests.sh +++ b/eng/helix/content/runtests.sh @@ -4,6 +4,9 @@ test_binary_path="$1" dotnet_sdk_version="$2" dotnet_runtime_version="$3" helix_queue_name="$4" +target_arch="$5" +quarantined="$6" +efVersion="$7" RESET="\033[0m" RED="\033[0;31m" @@ -29,7 +32,16 @@ export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 # Used by SkipOnHelix attribute export helix="$helix_queue_name" +export HELIX_DIR="$DIR" +export NUGET_FALLBACK_PACKAGES="$DIR" +export DotNetEfFullPath=$DIR\nugetRestore\dotnet-ef\$efVersion\tools\netcoreapp3.1\any\dotnet-ef.dll +echo "Set DotNetEfFullPath: $DotNetEfFullPath" +export NUGET_RESTORE="$DIR/nugetRestore" +echo "Creating nugetRestore directory: $NUGET_RESTORE" +mkdir $NUGET_RESTORE +mkdir logs +ls -laR RESET="\033[0m" RED="\033[0;31m" @@ -81,6 +93,38 @@ if [ $? -ne 0 ]; then done fi +# Copy over any local shared fx if found +if [ -d "Microsoft.AspNetCore.App" ] +then + echo "Found Microsoft.AspNetCore.App directory, copying to $DOTNET_ROOT/shared/Microsoft.AspNetCore.App/$dotnet_runtime_version." + cp -r Microsoft.AspNetCore.App $DOTNET_ROOT/shared/Microsoft.AspNetCore.App/$dotnet_runtime_version + + echo "Adding current directory to nuget sources: $DIR" + dotnet nuget add source $DIR + dotnet nuget add source https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet5/nuget/v3/index.json + dotnet nuget list source + + dotnet tool install dotnet-ef --global --version $efVersion + + # Ensure tools are on on PATH + export PATH="$PATH:$DOTNET_CLI_HOME/.dotnet/tools" +fi + +# Rename default.runner.json to xunit.runner.json if there is not a custom one from the project +if [ ! -f "xunit.runner.json" ] +then + cp default.runner.json xunit.runner.json +fi + +if [ -e /proc/self/coredump_filter ]; then + # Include memory in private and shared file-backed mappings in the dump. + # This ensures that we can see disassembly from our shared libraries when + # inspecting the contents of the dump. See 'man core' for details. + echo -n 0x3F > /proc/self/coredump_filter +fi + +sync + $DOTNET_ROOT/dotnet vstest $test_binary_path -lt >discovered.txt if grep -q "Exception thrown" discovered.txt; then echo -e "${RED}Exception thrown during test discovery${RESET}". @@ -88,25 +132,32 @@ if grep -q "Exception thrown" discovered.txt; then exit 1 fi -# Run non-flaky tests first -# We need to specify all possible Flaky filters that apply to this environment, because the flaky attribute -# only puts the explicit filter traits the user provided in the flaky attribute +exit_code=0 + # Filter syntax: https://github.com/Microsoft/vstest-docs/blob/master/docs/filter.md -NONFLAKY_FILTER="Flaky:All!=true&Flaky:Helix:All!=true&Flaky:Helix:Queue:All!=true&Flaky:Helix:Queue:$helix_queue_name!=true" -echo "Running non-flaky tests." -$DOTNET_ROOT/dotnet vstest $test_binary_path --logger:trx --TestCaseFilter:"$NONFLAKY_FILTER" -nonflaky_exitcode=$? -if [ $nonflaky_exitcode != 0 ]; then - echo "Non-flaky tests failed!" 1>&2 - # DO NOT EXIT +NONQUARANTINE_FILTER="Quarantined!=true" +QUARANTINE_FILTER="Quarantined=true" +if [ "$quarantined" == true ]; then + echo "Running all tests including quarantined." + $DOTNET_ROOT/dotnet vstest $test_binary_path --logger:xunit --TestCaseFilter:"$QUARANTINE_FILTER" + if [ $? != 0 ]; then + echo "Quarantined tests failed!" 1>&2 + # DO NOT EXIT + fi +else + echo "Running non-quarantined tests." + $DOTNET_ROOT/dotnet vstest $test_binary_path --logger:xunit --TestCaseFilter:"$NONQUARANTINE_FILTER" + exit_code=$? + if [ $exit_code != 0 ]; then + echo "Non-quarantined tests failed!" 1>&2 + # DO NOT EXIT + fi fi -FLAKY_FILTER="Flaky:All=true|Flaky:Helix:All=true|Flaky:Helix:Queue:All=true|Flaky:Helix:Queue:$helix_queue_name=true" -echo "Running known-flaky tests." -$DOTNET_ROOT/dotnet vstest $test_binary_path --TestCaseFilter:"$FLAKY_FILTER" -if [ $? != 0 ]; then - echo "Flaky tests failed!" 1>&2 - # DO NOT EXIT -fi - -exit $nonflaky_exitcode +echo "Copying TestResults/TestResults to ." +cp TestResults/TestResults.xml testResults.xml +echo "Copying artifacts/logs to $HELIX_WORKITEM_UPLOAD_ROOT/../" +shopt -s globstar +cp artifacts/log/**/*.log $HELIX_WORKITEM_UPLOAD_ROOT/../ +cp artifacts/log/**/*.log $HELIX_WORKITEM_UPLOAD_ROOT/ +exit $exit_code diff --git a/eng/helix/helix.proj b/eng/helix/helix.proj index 105134743c..b905693145 100644 --- a/eng/helix/helix.proj +++ b/eng/helix/helix.proj @@ -12,34 +12,50 @@ - + + - pr/aspnet/aspnetcore private-$(USERNAME) private-$(USER) true true 2 + $(HelixApiAccessToken) ci - aspnetcore + + aspnetcore $(BUILD_BUILDNUMBER).$(TargetArchitecture).$(SYSTEM_JOBATTEMPT) true true - true + true dev - $(USERNAME) - $(USER) + + $(USERNAME) + $(USER) $([System.DateTime]::Now.ToString('yyyyMMddHHmm')) + + + + + + + + + + + + + nul' + $changedFiles = & cmd /c 'git --no-pager diff --ignore-space-change --name-only 2>nul' # Temporary: Disable check for blazor js file and nuget.config (updated automatically for # internal builds) @@ -187,10 +188,9 @@ try { if ($changedFiles) { foreach ($file in $changedFiles) { if ($changedFilesExclusions -contains $file) {continue} - $filePath = Resolve-Path "${repoRoot}/${file}" LogError "Generated code is not up to date in $file. You might need to regenerate the reference assemblies or project list (see docs/ReferenceAssemblies.md and docs/ReferenceResolution.md)" -filepath $filePath - & git --no-pager diff --ignore-space-at-eol $filePath + & git --no-pager diff --ignore-space-change $filePath } } } diff --git a/eng/scripts/InstallVisualStudio.ps1 b/eng/scripts/InstallVisualStudio.ps1 index bbccdaacad..844da67bba 100644 --- a/eng/scripts/InstallVisualStudio.ps1 +++ b/eng/scripts/InstallVisualStudio.ps1 @@ -23,7 +23,7 @@ Run the installer without UI and wait for installation to complete. .LINK https://visualstudio.com - https://github.com/aspnet/AspNetCore/blob/master/docs/BuildFromSource.md + https://github.com/dotnet/aspnetcore/blob/master/docs/BuildFromSource.md .EXAMPLE To install VS 2019 Enterprise, run this command in PowerShell: diff --git a/eng/scripts/RunHelix.ps1 b/eng/scripts/RunHelix.ps1 new file mode 100644 index 0000000000..9e1bc513a2 --- /dev/null +++ b/eng/scripts/RunHelix.ps1 @@ -0,0 +1,42 @@ +<# +.SYNOPSIS + Runs the specified test project on a Helix machine. +.DESCRIPTION + This script runs the Helix msbuild task on the given project and publishes then uploads the output and runs tests on the Helix machine(s) passed in. +.PARAMETER Project + The test project to publish and send to Helix. +.PARAMETER HelixQueues + Set the Helix queues to use, the list is ';' separated. + Some supported queues: + Ubuntu.1604.Amd64.Open + Ubuntu.1804.Amd64.Open + Windows.10.Amd64.Open + Windows.81.Amd64.Open + Windows.7.Amd64.Open + OSX.1014.Amd64.Open + Centos.7.Amd64.Open + Debian.8.Amd64.Open + Debian.9.Amd64.Open + Redhat.7.Amd64.Open +.PARAMETER RunQuarantinedTests + By default quarantined tests are not run. Set this to $true to run only the quarantined tests. +#> +param( + [Parameter(Mandatory=$true)] + [string]$Project, + [string]$HelixQueues = "Windows.10.Amd64.Open", + [string]$TargetArchitecture = "", + [bool]$RunQuarantinedTests = $false +) +$ErrorActionPreference = 'Stop' +$ProgressPreference = 'SilentlyContinue' # Workaround PowerShell/PowerShell#2138 + +Set-StrictMode -Version 1 + +$env:BUILD_REASON="PullRequest" +$env:BUILD_SOURCEBRANCH="local" +$env:BUILD_REPOSITORY_NAME="aspnetcore" +$env:SYSTEM_TEAMPROJECT="aspnetcore" + +$HelixQueues = $HelixQueues -replace ";", "%3B" +dotnet msbuild $Project /t:Helix /p:TargetArchitecture="$TargetArchitecture" /p:IsRequiredCheck=true /p:IsHelixDaily=true /p:HelixTargetQueues=$HelixQueues /p:RunQuarantinedTests=$RunQuarantinedTests /p:_UseHelixOpenQueues=true \ No newline at end of file diff --git a/eng/scripts/StartDumpCollectionForHangingBuilds.ps1 b/eng/scripts/StartDumpCollectionForHangingBuilds.ps1 index 4ed696ec3c..3fd2664d48 100644 --- a/eng/scripts/StartDumpCollectionForHangingBuilds.ps1 +++ b/eng/scripts/StartDumpCollectionForHangingBuilds.ps1 @@ -54,7 +54,7 @@ Write-Output "Watching processes $($CandidateProcessNames -join ', ')"; # This script registers as a scheduled job. This scheduled job executes after $WakeTime. # When the scheduled job executes, it runs procdump on all alive processes whose name matches $CandidateProcessNames. # The dumps are placed in $ProcDumpOutputPath -# If the build completes sucessfully in less than $WakeTime, a final step unregisters the job. +# If the build completes successfully in less than $WakeTime, a final step unregisters the job. # Create a unique identifier for the job name $JobName = "CaptureDumps" + (New-Guid).ToString("N"); diff --git a/eng/scripts/ci-source-build.sh b/eng/scripts/ci-source-build.sh index ebc50dad0a..468f749751 100755 --- a/eng/scripts/ci-source-build.sh +++ b/eng/scripts/ci-source-build.sh @@ -9,31 +9,34 @@ set -euo pipefail scriptroot="$( cd -P "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" reporoot="$(dirname "$(dirname "$scriptroot")")" - # For local development, make a backup copy of this file first -if [ ! -f "$reporoot/global.bak.json" ]; then - mv "$reporoot/global.json" "$reporoot/global.bak.json" -fi +# +# This commented out section is used for servicing branches +# +# For local development, make a backup copy of this file first +# if [ ! -f "$reporoot/global.bak.json" ]; then +# mv "$reporoot/global.json" "$reporoot/global.bak.json" +# fi - # Detect the current version of .NET Core installed -export SDK_VERSION=$(dotnet --version) -echo "The ambient version of .NET Core SDK version = $SDK_VERSION" +# Detect the current version of .NET Core installed +# export SDK_VERSION=$(dotnet --version) +# echo "The ambient version of .NET Core SDK version = $SDK_VERSION" - # Update the global.json file to match the current .NET environment -cat "$reporoot/global.bak.json" | \ - jq '.sdk.version=env.SDK_VERSION' | \ - jq '.tools.dotnet=env.SDK_VERSION' | \ - jq 'del(.tools.runtimes)' \ - > "$reporoot/global.json" +# Update the global.json file to match the current .NET environment +# cat "$reporoot/global.bak.json" | \ +# jq '.sdk.version=env.SDK_VERSION' | \ +# jq '.tools.dotnet=env.SDK_VERSION' | \ +# jq 'del(.tools.runtimes)' \ +# > "$reporoot/global.json" - # Restore the original global.json file -trap "{ - mv "$reporoot/global.bak.json" "$reporoot/global.json" -}" EXIT +# Restore the original global.json file +#trap "{ +# mv "$reporoot/global.bak.json" "$reporoot/global.json" +#}" EXIT - # Build repo tasks +# Build repo tasks "$reporoot/eng/common/build.sh" --restore --build --ci --configuration Release /p:ProjectToBuild=$reporoot/eng/tools/RepoTasks/RepoTasks.csproj export DotNetBuildFromSource='true' - # Build projects +# Build projects "$reporoot/eng/common/build.sh" --restore --build --pack "$@" \ No newline at end of file diff --git a/eng/targets/CSharp.Common.props b/eng/targets/CSharp.Common.props index 6cc6bd2dd8..03e6f56e37 100644 --- a/eng/targets/CSharp.Common.props +++ b/eng/targets/CSharp.Common.props @@ -25,8 +25,11 @@ + + + PreserveNewest @@ -35,6 +38,5 @@ - diff --git a/eng/targets/CSharp.Common.targets b/eng/targets/CSharp.Common.targets index 877665a63b..e327a7b886 100644 --- a/eng/targets/CSharp.Common.targets +++ b/eng/targets/CSharp.Common.targets @@ -30,5 +30,5 @@ - + diff --git a/eng/targets/FunctionalTestAsset.targets b/eng/targets/FunctionalTestAsset.targets new file mode 100644 index 0000000000..04b3513341 --- /dev/null +++ b/eng/targets/FunctionalTestAsset.targets @@ -0,0 +1,9 @@ + + + + + $(MSBuildProjectName)\%(ResolvedFileToPublish.RelativePath) + + + + \ No newline at end of file diff --git a/eng/targets/FunctionalTestWithAssets.targets b/eng/targets/FunctionalTestWithAssets.targets new file mode 100644 index 0000000000..b6194607cb --- /dev/null +++ b/eng/targets/FunctionalTestWithAssets.targets @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + %(DependencyPayload.RelativePath) + PreserveNewest + PreserveNewest + + + + + \ No newline at end of file diff --git a/eng/targets/Helix.Common.props b/eng/targets/Helix.Common.props index 8ea26294a2..e77bcd8828 100644 --- a/eng/targets/Helix.Common.props +++ b/eng/targets/Helix.Common.props @@ -1,10 +1,4 @@ - - - true - - - @@ -12,15 +6,30 @@ - - - + + + + + + + + + + + + + + + + + + + + - - - + @@ -28,29 +37,13 @@ - + - + - - - - - - - - - - - - - - - - diff --git a/eng/targets/Helix.props b/eng/targets/Helix.props index 30a5903b9a..d4c7a99d52 100644 --- a/eng/targets/Helix.props +++ b/eng/targets/Helix.props @@ -12,12 +12,17 @@ true 00:30:00 + false false true - $(MSBuildProjectName)-$(TargetFramework) + false + true + $(MSBuildProjectName)--$(TargetFramework) false - true + false 10.15.3 + 5.0.0-ci + false @@ -32,16 +37,4 @@ - - - - - - - - - - - - diff --git a/eng/targets/Helix.targets b/eng/targets/Helix.targets index d3a45dfa45..154ce92ee6 100644 --- a/eng/targets/Helix.targets +++ b/eng/targets/Helix.targets @@ -1,11 +1,37 @@ - - - - + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -35,14 +61,24 @@ Usage: dotnet msbuild /t:Helix src/MyTestProject.csproj + + + + <_SelectedPlatforms>@(HelixProjectPlatform) + + <_Temp Include="@(HelixAvailableTargetQueue)" /> + + + + - <_HelixProjectTargetQueue Include="%(HelixAvailableTargetQueue.Identity)" Condition="'%(HelixAvailableTargetQueue.Identity)' != '' AND '$(_SelectedPlatforms.Contains(%(Platform)))' == 'true' AND '%(EnableByDefault)' == 'true'" /> + <_HelixProjectTargetQueue Include="%(HelixAvailableTargetQueue.Identity)" Condition="'%(HelixAvailableTargetQueue.Identity)' != '' AND '$(_SelectedPlatforms.Contains(%(Platform)))' == 'true'" /> <_HelixApplicableTargetQueue Include="%(_HelixProjectTargetQueue.Identity)" Condition="'%(Identity)' == '$(HelixTargetQueue)'" /> @@ -60,12 +96,18 @@ Usage: dotnet msbuild /t:Helix src/MyTestProject.csproj + + + + + <_HelixFriendlyNameTargetQueue>$(HelixTargetQueue) <_HelixFriendlyNameTargetQueue Condition="$(HelixTargetQueue.Contains('@'))">$(HelixTargetQueue.Substring(1, $([MSBuild]::Subtract($(HelixTargetQueue.LastIndexOf(')')), 1)))) + @@ -78,8 +120,9 @@ Usage: dotnet msbuild /t:Helix src/MyTestProject.csproj $(TargetFileName) @(HelixPreCommand) @(HelixPostCommand) - call runtests.cmd $(TargetFileName) $(TargetFrameworkIdentifier) $(NETCoreSdkVersion) $(MicrosoftNETCoreAppRuntimeVersion) $(_HelixFriendlyNameTargetQueue) $(TargetArchitecture) - ./runtests.sh $(TargetFileName) $(NETCoreSdkVersion) $(MicrosoftNETCoreAppRuntimeVersion) $(_HelixFriendlyNameTargetQueue) $(TargetArchitecture) + call runtests.cmd $(TargetFileName) $(NETCoreSdkVersion) $(MicrosoftNETCoreAppRuntimeVersion) $(_HelixFriendlyNameTargetQueue) $(TargetArchitecture) $(RunQuarantinedTests) $(DotnetEfPackageVersion) + ./runtests.sh $(TargetFileName) $(NETCoreSdkVersion) $(MicrosoftNETCoreAppRuntimeVersion) $(_HelixFriendlyNameTargetQueue) $(TargetArchitecture) $(RunQuarantinedTests) $(DotnetEfPackageVersion) + $(HelixCommand) $(HelixTimeout) diff --git a/eng/targets/Npm.Common.targets b/eng/targets/Npm.Common.targets index 062a9d3a8f..3460edde2e 100644 --- a/eng/targets/Npm.Common.targets +++ b/eng/targets/Npm.Common.targets @@ -20,22 +20,27 @@ - - - + + + + + + + - - + + + @@ -50,15 +55,29 @@ + + + + + + + + + + DependsOnTargets="GetBuildInputCacheFile" + Inputs="@(TSFiles);$(BaseIntermediateOutputPath)tsfiles.cache" + Outputs="@(BuildOutputFiles)"> - + @@ -73,7 +92,10 @@ - + + <_PackageTargetPath>$(MSBuildProjectDirectory)\$(PackageFileName) @@ -81,7 +103,7 @@ - + @@ -97,7 +119,8 @@ - + + diff --git a/eng/targets/ReferenceAssembly.targets b/eng/targets/ReferenceAssembly.targets index 59568267be..84076d9a41 100644 --- a/eng/targets/ReferenceAssembly.targets +++ b/eng/targets/ReferenceAssembly.targets @@ -23,11 +23,14 @@ + <_TargetFrameworkOverride /> + <_TargetFrameworkOverride + Condition=" @(_ResultTargetFramework->Count()) > 1 ">%0A <TargetFrameworks Condition="'%24(DotNetBuildFromSource)' == 'true'">%24(DefaultNetCoreTargetFramework)</TargetFrameworks> - @(_ResultTargetFramework) + @(_ResultTargetFramework)$(_TargetFrameworkOverride) @(ProjectListContentItem->'%(Identity)', '%0A') @@ -67,7 +70,6 @@ <_GenApiFile>$([MSBuild]::NormalizePath('$(ArtifactsDir)', 'log', 'GenAPI.rsp')) <_GenAPICommand Condition="'$(MSBuildRuntimeType)' == 'core'">"$(DotNetTool)" --roll-forward-on-no-candidate-fx 2 "$(_GenAPIPath)" - <_GenAPICmd>$(_GenAPICommand) <_GenAPICmd>$(_GenAPICommand) @"$(_GenApiFile)" <_GenAPICmd Condition=" '$(AdditionalGenApiCmdOptions)' != '' ">$(_GenAPICmd) $(AdditionalGenApiCmdOptions) diff --git a/eng/targets/ResolveReferences.targets b/eng/targets/ResolveReferences.targets index e77922ebcb..37a17b7140 100644 --- a/eng/targets/ResolveReferences.targets +++ b/eng/targets/ResolveReferences.targets @@ -11,12 +11,11 @@ Items used by the resolution strategy: - * BaselinePackageReference = a list of packages that were referenced in the last release of the project currently building - - mainly used to ensure references do not change in servicing builds unless $(UseLatestPackageReferences) is not true. + * BaselinePackageReference = a list of packages that were reference in the last release of the project currently building * LatestPackageReference = a list of the latest versions of packages * Reference = a list of the references which are needed for compilation or runtime * ProjectReferenceProvider = a list which maps of assembly names to the project file that produces it ---> + --> @@ -30,49 +29,43 @@ + true + true true - true - true + Condition=" '$(UseLatestPackageReferences)' == '' AND '$(IsImplementationProject)' == 'true' AND '$(IsPackable)' == 'true' ">true false - true + true + true + false - true - false + true + false - true - false + true + false - + - true + true @@ -80,40 +73,35 @@ <_AllowedExplicitPackageReference Include="@(PackageReference->WithMetadataValue('AllowExplicitReference', 'true'))" /> <_AllowedExplicitPackageReference Include="FSharp.Core" Condition="'$(MSBuildProjectExtension)' == '.fsproj'" /> - <_ExplicitPackageReference Include="@(PackageReference)" - Exclude="@(_ImplicitPackageReference);@(_AllowedExplicitPackageReference)" /> + <_ExplicitPackageReference Include="@(PackageReference)" Exclude="@(_ImplicitPackageReference);@(_AllowedExplicitPackageReference)" /> <_UnusedProjectReferenceProvider Include="@(ProjectReferenceProvider)" Exclude="@(Reference)" /> - <_CompilationOnlyReference Include="@(Reference->WithMetadataValue('NuGetPackageId','NETStandard.Library'))" - Condition="'$(TargetFramework)' == 'netstandard2.0'" /> + <_CompilationOnlyReference Condition="'$(TargetFramework)' == 'netstandard2.0'" Include="@(Reference->WithMetadataValue('NuGetPackageId','NETStandard.Library'))" /> <_InvalidReferenceToNonSharedFxAssembly Condition="'$(IsAspNetCoreApp)' == 'true'" - Include="@(Reference)" - Exclude=" - @(AspNetCoreAppReference); - @(AspNetCoreAppReferenceAndPackage); - @(ExternalAspNetCoreAppReference); - @(_CompilationOnlyReference); - @(Reference->WithMetadataValue('IsSharedSource', 'true'))" /> + Include="@(Reference)" + Exclude=" + @(AspNetCoreAppReference); + @(AspNetCoreAppReferenceAndPackage); + @(ExternalAspNetCoreAppReference); + @(_CompilationOnlyReference); + @(Reference->WithMetadataValue('IsSharedSource', 'true'))" /> <_OriginalReferences Include="@(Reference)" /> - <_ProjectReferenceByAssemblyName Condition="'$(UseProjectReferences)' == 'true'" - Include="@(ProjectReferenceProvider)" - Exclude="@(_UnusedProjectReferenceProvider)" /> + Include="@(ProjectReferenceProvider)" + Exclude="@(_UnusedProjectReferenceProvider)" /> - + false - + true @@ -121,31 +109,30 @@ - - + + + - + - + @@ -154,15 +141,9 @@ - - + - - + - - <_BaselinePackageReferenceWithVersion Include="@(Reference)" - Condition=" '$(IsServicingBuild)' == 'true' OR '$(UseLatestPackageReferences)' != 'true' "> + + <_BaselinePackageReferenceWithVersion Include="@(Reference)" Condition=" '$(IsServicingBuild)' == 'true' OR '$(UseLatestPackageReferences)' != 'true' "> %(BaselinePackageReference.Identity) %(BaselinePackageReference.Version) - <_BaselinePackageReferenceWithVersion Remove="@(_BaselinePackageReferenceWithVersion)" - Condition="'%(Id)' != '%(Identity)' " /> + + <_BaselinePackageReferenceWithVersion Remove="@(_BaselinePackageReferenceWithVersion)" Condition="'%(Id)' != '%(Identity)' " /> @@ -197,10 +176,10 @@ %(LatestPackageReference.Identity) %(LatestPackageReference.Version) - <_PrivatePackageReferenceWithVersion Remove="@(_PrivatePackageReferenceWithVersion)" - Condition="'%(Id)' != '%(Identity)' " /> - + <_PrivatePackageReferenceWithVersion Remove="@(_PrivatePackageReferenceWithVersion)" Condition="'%(Id)' != '%(Identity)' " /> + + @@ -211,70 +190,31 @@ <_ImplicitPackageReference Remove="@(_ImplicitPackageReference)" /> - + <_ExplicitPackageReference Remove="@(_ExplicitPackageReference)" /> - + - + - + - <_CompileTfmUsingReferenceAssemblies>false <_CompileTfmUsingReferenceAssemblies Condition=" '$(CompileUsingReferenceAssemblies)' != false AND '$(TargetFramework)' == '$(DefaultNetCoreTargetFramework)' ">true - - <_ReferenceProjectFile>$(MSBuildProjectDirectory)/../ref/$(MSBuildProjectFile) - - - $(GetTargetPathWithTargetPlatformMonikerDependsOn);AddReferenceProjectMetadata - RemoveReferenceProjectMetadata;$(PrepareForRunDependsOn) - - - - - ReferenceProjectMetadata - false - - - - - true - @(ReferenceProjectMetadata) - - - - - - - - - @@ -319,36 +249,24 @@ $([MSBuild]::MakeRelative($(RepoRoot), '$(ReferenceAssemblyDirectory)$(MSBuildProjectFile)')) - - - - + + $([MSBuild]::ValueOrDefault($(IsAspNetCoreApp),'false')) $([MSBuild]::ValueOrDefault($(IsPackable),'false')) $([MSBuild]::MakeRelative($(RepoRoot), $(MSBuildProjectFullPath))) - $(ReferenceAssemblyProjectFileRelativePath) + $(ReferenceAssemblyProjectFileRelativePath) - <_CustomCollectProjectReferenceDependsOn - Condition="'$(TargetFramework)' != ''">ResolveProjectReferences + <_CustomCollectProjectReferenceDependsOn Condition="'$(TargetFramework)' != ''">ResolveProjectReferences - + <_TargetFrameworks Include="$(TargetFrameworks)" /> @@ -369,4 +287,4 @@ - + \ No newline at end of file diff --git a/eng/tools/RepoTasks/DownloadFile.cs b/eng/tools/RepoTasks/DownloadFile.cs index 2be0954cc2..7ba2602d0c 100644 --- a/eng/tools/RepoTasks/DownloadFile.cs +++ b/eng/tools/RepoTasks/DownloadFile.cs @@ -22,7 +22,7 @@ namespace RepoTasks /// status, it will try to download the file from `PrivateUri`. /// public string PrivateUri { get; set; } - + /// /// Suffix for the private URI in base64 form (for SAS compatibility) /// @@ -146,4 +146,4 @@ namespace RepoTasks return null; } } -} +} \ No newline at end of file diff --git a/eng/tools/RepoTasks/RepoTasks.tasks b/eng/tools/RepoTasks/RepoTasks.tasks index 4916a97ed3..631944feea 100644 --- a/eng/tools/RepoTasks/RepoTasks.tasks +++ b/eng/tools/RepoTasks/RepoTasks.tasks @@ -1,6 +1,6 @@ - <_RepoTaskAssemblyFolder Condition="'$(MSBuildRuntimeType)' == 'core'">netcoreapp3.1 + <_RepoTaskAssemblyFolder Condition="'$(MSBuildRuntimeType)' == 'core'">netcoreapp5.0 <_RepoTaskAssemblyFolder Condition="'$(MSBuildRuntimeType)' != 'core'">net472 <_RepoTaskAssembly>$(ArtifactsBinDir)RepoTasks\Release\$(_RepoTaskAssemblyFolder)\RepoTasks.dll diff --git a/global.json b/global.json index 67ec79203b..d9cf852876 100644 --- a/global.json +++ b/global.json @@ -1,9 +1,9 @@ { "sdk": { - "version": "3.1.103" + "version": "5.0.100-preview.2.20120.3" }, "tools": { - "dotnet": "3.1.103", + "dotnet": "5.0.100-preview.2.20120.3", "runtimes": { "dotnet/x64": [ "$(MicrosoftNETCoreAppInternalPackageVersion)" @@ -15,7 +15,7 @@ "Git": "2.22.0", "jdk": "11.0.3", "vs": { - "version": "16.0", + "version": "16.3", "components": [ "Microsoft.VisualStudio.Component.VC.ATL", "Microsoft.VisualStudio.Component.VC.Tools.x86.x64", @@ -25,7 +25,7 @@ }, "msbuild-sdks": { "Yarn.MSBuild": "1.15.2", - "Microsoft.DotNet.Arcade.Sdk": "1.0.0-beta.20113.5", - "Microsoft.DotNet.Helix.Sdk": "2.0.0-beta.20113.5" + "Microsoft.DotNet.Arcade.Sdk": "5.0.0-beta.20171.1", + "Microsoft.DotNet.Helix.Sdk": "5.0.0-beta.20171.1" } } diff --git a/src/Analyzers/Analyzers/src/StartupFacts.cs b/src/Analyzers/Analyzers/src/StartupFacts.cs index 221e3ff4c4..f5f833429a 100644 --- a/src/Analyzers/Analyzers/src/StartupFacts.cs +++ b/src/Analyzers/Analyzers/src/StartupFacts.cs @@ -138,6 +138,8 @@ namespace Microsoft.AspNetCore.Analyzers throw new ArgumentNullException(nameof(symbol)); } + // UseSignalR has been removed in 5.0, but we should probably still check for it in this analyzer in case the user + // installs it into a pre-5.0 app. if (string.Equals(symbol.Name, SymbolNames.SignalRAppBuilderExtensions.UseSignalRMethodName, StringComparison.Ordinal) || string.Equals(symbol.Name, SymbolNames.HubEndpointRouteBuilderExtensions.MapHubMethodName, StringComparison.Ordinal) || string.Equals(symbol.Name, SymbolNames.ComponentEndpointRouteBuilderExtensions.MapBlazorHubMethodName, StringComparison.Ordinal)) diff --git a/src/Analyzers/Analyzers/src/UseAuthorizationAnalyzer.cs b/src/Analyzers/Analyzers/src/UseAuthorizationAnalyzer.cs index dbfa9a3dcb..79f8fea633 100644 --- a/src/Analyzers/Analyzers/src/UseAuthorizationAnalyzer.cs +++ b/src/Analyzers/Analyzers/src/UseAuthorizationAnalyzer.cs @@ -41,7 +41,7 @@ namespace Microsoft.AspNetCore.Analyzers if (useRoutingItem != null && useAuthorizationItem == null) { // This looks like - // + // // app.UseAuthorization(); // ... // app.UseRouting(); @@ -60,7 +60,7 @@ namespace Microsoft.AspNetCore.Analyzers if (useAuthorizationItem != null) { // This configuration looks like - // + // // app.UseRouting(); // app.UseEndpoints(...); // ... @@ -86,7 +86,7 @@ namespace Microsoft.AspNetCore.Analyzers // This analyzer expects MiddlewareItem instances to appear in the order in which they appear in source // which unfortunately isn't true for chained calls (the operations appear in reverse order). // We'll avoid doing any analysis in this event and rely on the runtime guardrails. - // We'll use https://github.com/aspnet/AspNetCore/issues/16648 to track addressing this in a future milestone + // We'll use https://github.com/dotnet/aspnetcore/issues/16648 to track addressing this in a future milestone return; } diff --git a/src/Analyzers/Analyzers/test/AnalyzerTestBase.cs b/src/Analyzers/Analyzers/test/AnalyzerTestBase.cs index 269c50a394..eb748f2ab3 100644 --- a/src/Analyzers/Analyzers/test/AnalyzerTestBase.cs +++ b/src/Analyzers/Analyzers/test/AnalyzerTestBase.cs @@ -56,7 +56,7 @@ namespace Microsoft.AspNetCore.Analyzers } // This test code needs to be updated to support distributed testing. -// See https://github.com/aspnet/AspNetCore/issues/10422 +// See https://github.com/dotnet/aspnetcore/issues/10422 #pragma warning disable 0618 var solutionDirectory = TestPathUtilities.GetSolutionRootDirectory("Analyzers"); #pragma warning restore 0618 diff --git a/src/Analyzers/Analyzers/test/CompilationFeatureDetectorTest.cs b/src/Analyzers/Analyzers/test/CompilationFeatureDetectorTest.cs index 0669c76464..9fcc0ff167 100644 --- a/src/Analyzers/Analyzers/test/CompilationFeatureDetectorTest.cs +++ b/src/Analyzers/Analyzers/test/CompilationFeatureDetectorTest.cs @@ -29,7 +29,6 @@ namespace Microsoft.AspNetCore.Analyzers } [Theory] - [InlineData(nameof(StartupWithUseSignalR))] [InlineData(nameof(StartupWithMapHub))] [InlineData(nameof(StartupWithMapBlazorHub))] public async Task DetectFeaturesAsync_FindsSignalR(string source) diff --git a/src/Analyzers/Analyzers/test/Microsoft.AspNetCore.Analyzers.Test.csproj b/src/Analyzers/Analyzers/test/Microsoft.AspNetCore.Analyzers.Test.csproj index 87d62d3fda..e0847bc033 100644 --- a/src/Analyzers/Analyzers/test/Microsoft.AspNetCore.Analyzers.Test.csproj +++ b/src/Analyzers/Analyzers/test/Microsoft.AspNetCore.Analyzers.Test.csproj @@ -4,14 +4,7 @@ $(DefaultNetCoreTargetFramework) true Microsoft.AspNetCore.Analyzers - - - - false - - - - + diff --git a/src/Analyzers/Analyzers/test/StartupAnalyzerTest.cs b/src/Analyzers/Analyzers/test/StartupAnalyzerTest.cs index fc635ef7f2..8345ddc586 100644 --- a/src/Analyzers/Analyzers/test/StartupAnalyzerTest.cs +++ b/src/Analyzers/Analyzers/test/StartupAnalyzerTest.cs @@ -247,7 +247,7 @@ namespace Microsoft.AspNetCore.Analyzers [Fact] public async Task StartupAnalyzer_UseAuthorizationConfiguredAsAChain_ReportsNoDiagnostics() { - // Regression test for https://github.com/aspnet/AspNetCore/issues/15203 + // Regression test for https://github.com/dotnet/aspnetcore/issues/15203 // Arrange var source = Read(nameof(TestFiles.StartupAnalyzerTest.UseAuthConfiguredCorrectlyChained)); @@ -298,7 +298,7 @@ namespace Microsoft.AspNetCore.Analyzers [Fact] public async Task StartupAnalyzer_UseAuthorizationConfiguredBeforeUseRoutingChained_ReportsDiagnostics() { - // This one asserts a false negative for https://github.com/aspnet/AspNetCore/issues/15203. + // This one asserts a false negative for https://github.com/dotnet/aspnetcore/issues/15203. // We don't correctly identify chained calls, this test verifies the behavior. // Arrange var source = Read(nameof(TestFiles.StartupAnalyzerTest.UseAuthBeforeUseRoutingChained)); diff --git a/src/Analyzers/Analyzers/test/TestFiles/CompilationFeatureDetectorTest/StartupWithUseSignalR.cs b/src/Analyzers/Analyzers/test/TestFiles/CompilationFeatureDetectorTest/StartupWithUseSignalR.cs deleted file mode 100644 index aa65f83258..0000000000 --- a/src/Analyzers/Analyzers/test/TestFiles/CompilationFeatureDetectorTest/StartupWithUseSignalR.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using Microsoft.AspNetCore.Builder; - -namespace Microsoft.AspNetCore.Analyzers.TestFiles.CompilationFeatureDetectorTest -{ - public class StartupWithUseSignalR - { - public void Configure(IApplicationBuilder app) - { -#pragma warning disable CS0618 // Type or member is obsolete - app.UseSignalR(routes => - { - - }); -#pragma warning restore CS0618 // Type or member is obsolete - } - } -} diff --git a/src/Analyzers/build.sh b/src/Analyzers/build.sh old mode 100644 new mode 100755 diff --git a/src/Analyzers/shared/FeatureDetection/Microsoft.AspNetCore.Analyzers.FeatureDetection.Sources.csproj b/src/Analyzers/shared/FeatureDetection/Microsoft.AspNetCore.Analyzers.FeatureDetection.Sources.csproj deleted file mode 100644 index 1ec3645965..0000000000 --- a/src/Analyzers/shared/FeatureDetection/Microsoft.AspNetCore.Analyzers.FeatureDetection.Sources.csproj +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - - netstandard1.0 - true - false - true - true - true - false - false - false - false - contentFiles - true - $(DefaultExcludeItems);$(BaseOutputPath);$(BaseIntermediateOutputPath); - $(NoWarn);CS8021 - false - - - - - True - lib - - - - true - $(ContentTargetFolders)\cs\netstandard1.0\shared\ - - - - - - %(FileName)%(Extension) - true - $(ContentTargetFolders)\cs\netstandard1.0\shared\ - - - %(FileName)%(Extension) - true - $(ContentTargetFolders)\cs\netstandard1.0\shared\ - - - %(FileName)%(Extension) - true - $(ContentTargetFolders)\cs\netstandard1.0\shared\ - - - %(FileName)%(Extension) - true - $(ContentTargetFolders)\cs\netstandard1.0\shared\ - - - %(FileName)%(Extension) - true - $(ContentTargetFolders)\cs\netstandard1.0\shared\ - - - %(FileName)%(Extension) - true - $(ContentTargetFolders)\cs\netstandard1.0\shared\ - - - - - - - - - diff --git a/src/Analyzers/shared/FeatureDetection/ProjectCompilationFeatureDetector.cs b/src/Analyzers/shared/FeatureDetection/ProjectCompilationFeatureDetector.cs deleted file mode 100644 index 9a92559a61..0000000000 --- a/src/Analyzers/shared/FeatureDetection/ProjectCompilationFeatureDetector.cs +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Immutable; -using System.ComponentModel.Composition; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis; -using Microsoft.VisualStudio.LanguageServices; -using Microsoft.VisualStudio.Shell; -using Microsoft.VisualStudio.Threading; -using Task = System.Threading.Tasks.Task; - -namespace Microsoft.AspNetCore.Analyzers.FeatureDetection -{ - // Be very careful making changes to this file. No project in this repo builds it. - // - // If you need to verify a change, make a local project (net472) and copy in everything included by this project. - // - // You'll also need some nuget packages like: - // - Microsoft.VisualStudio.LanguageServices - // - Microsoft.VisualStudio.Shell.15.0 - // - Microsoft.VisualStudio.Threading - [Export(typeof(ProjectCompilationFeatureDetector))] - internal class ProjectCompilationFeatureDetector - { - private readonly Lazy _workspace; - - [ImportingConstructor] - public ProjectCompilationFeatureDetector(Lazy workspace) - { - _workspace = workspace; - } - - public async Task> DetectFeaturesAsync(string projectFullPath, CancellationToken cancellationToken = default) - { - if (projectFullPath == null) - { - throw new ArgumentNullException(nameof(projectFullPath)); - } - - // If the workspace is uninitialized, we need to do the first access on the UI thread. - // - // This is very unlikely to occur, but doing it here for completeness. - if (!_workspace.IsValueCreated) - { - await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); - GC.KeepAlive(_workspace.Value); - await TaskScheduler.Default; - } - - var workspace = _workspace.Value; - var solution = workspace.CurrentSolution; - - var project = GetProject(solution, projectFullPath); - if (project == null) - { - // Cannot find matching project. - return ImmutableHashSet.Empty; - } - - var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); - return await CompilationFeatureDetector.DetectFeaturesAsync(compilation, cancellationToken); - } - - private static Project GetProject(Solution solution, string projectFilePath) - { - foreach (var project in solution.Projects) - { - if (string.Equals(projectFilePath, project.FilePath, StringComparison.OrdinalIgnoreCase)) - { - return project; - } - } - - return null; - } - } -} diff --git a/src/Antiforgery/README.md b/src/Antiforgery/README.md index eb59611a42..59aa0da5a0 100644 --- a/src/Antiforgery/README.md +++ b/src/Antiforgery/README.md @@ -3,4 +3,4 @@ Antiforgery Antiforgery system for generating secure tokens to prevent Cross-Site Request Forgery attacks. -This project is part of ASP.NET Core. You can find documentation and getting started instructions for ASP.NET Core at the [AspNetCore](https://github.com/aspnet/AspNetCore) repo. +This project is part of ASP.NET Core. You can find documentation and getting started instructions for ASP.NET Core at the [AspNetCore](https://github.com/dotnet/aspnetcore) repo. diff --git a/src/Antiforgery/build.sh b/src/Antiforgery/build.sh old mode 100644 new mode 100755 diff --git a/src/Antiforgery/ref/Microsoft.AspNetCore.Antiforgery.netcoreapp.cs b/src/Antiforgery/ref/Microsoft.AspNetCore.Antiforgery.netcoreapp.cs index 5eb2c8be65..4ca6748cce 100644 --- a/src/Antiforgery/ref/Microsoft.AspNetCore.Antiforgery.netcoreapp.cs +++ b/src/Antiforgery/ref/Microsoft.AspNetCore.Antiforgery.netcoreapp.cs @@ -9,16 +9,16 @@ namespace Microsoft.AspNetCore.Antiforgery public AntiforgeryOptions() { } public Microsoft.AspNetCore.Http.CookieBuilder Cookie { get { throw null; } set { } } public string FormFieldName { get { throw null; } set { } } - public string HeaderName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public bool SuppressXFrameOptionsHeader { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public string HeaderName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public bool SuppressXFrameOptionsHeader { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } public partial class AntiforgeryTokenSet { public AntiforgeryTokenSet(string requestToken, string cookieToken, string formFieldName, string headerName) { } - public string CookieToken { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public string FormFieldName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public string HeaderName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public string RequestToken { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public string CookieToken { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public string FormFieldName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public string HeaderName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public string RequestToken { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } } public partial class AntiforgeryValidationException : System.Exception { diff --git a/src/Antiforgery/src/IAntiforgeryAdditionalDataProvider.cs b/src/Antiforgery/src/IAntiforgeryAdditionalDataProvider.cs index d66b6245db..485fc8c9e8 100644 --- a/src/Antiforgery/src/IAntiforgeryAdditionalDataProvider.cs +++ b/src/Antiforgery/src/IAntiforgeryAdditionalDataProvider.cs @@ -8,7 +8,7 @@ namespace Microsoft.AspNetCore.Antiforgery /// /// Allows providing or validating additional custom data for antiforgery tokens. /// For example, the developer could use this to supply a nonce when the token is - /// generated, then he could validate the nonce when the token is validated. + /// generated, then validate it when the token is validated. /// /// /// The antiforgery system already embeds the client's username within the diff --git a/src/Antiforgery/src/Internal/BinaryBlob.cs b/src/Antiforgery/src/Internal/BinaryBlob.cs index 0e5039295a..9313175b36 100644 --- a/src/Antiforgery/src/Internal/BinaryBlob.cs +++ b/src/Antiforgery/src/Internal/BinaryBlob.cs @@ -15,7 +15,6 @@ namespace Microsoft.AspNetCore.Antiforgery [DebuggerDisplay("{DebuggerString}")] internal sealed class BinaryBlob : IEquatable { - private static readonly RandomNumberGenerator _randomNumberGenerator = RandomNumberGenerator.Create(); private readonly byte[] _data; // Generates a new token using a specified bit length. @@ -92,7 +91,7 @@ namespace Microsoft.AspNetCore.Antiforgery private static byte[] GenerateNewToken(int bitLength) { var data = new byte[bitLength / 8]; - _randomNumberGenerator.GetBytes(data); + RandomNumberGenerator.Fill(data); return data; } diff --git a/src/Antiforgery/src/Internal/DefaultAntiforgery.cs b/src/Antiforgery/src/Internal/DefaultAntiforgery.cs index 030f79c07e..f88d18bf8a 100644 --- a/src/Antiforgery/src/Internal/DefaultAntiforgery.cs +++ b/src/Antiforgery/src/Internal/DefaultAntiforgery.cs @@ -102,10 +102,10 @@ namespace Microsoft.AspNetCore.Antiforgery CheckSSLConfig(httpContext); var method = httpContext.Request.Method; - if (string.Equals(method, "GET", StringComparison.OrdinalIgnoreCase) || - string.Equals(method, "HEAD", StringComparison.OrdinalIgnoreCase) || - string.Equals(method, "OPTIONS", StringComparison.OrdinalIgnoreCase) || - string.Equals(method, "TRACE", StringComparison.OrdinalIgnoreCase)) + if (HttpMethods.IsGet(method) || + HttpMethods.IsHead(method) || + HttpMethods.IsOptions(method) || + HttpMethods.IsTrace(method)) { // Validation not needed for these request types. return true; @@ -379,7 +379,7 @@ namespace Microsoft.AspNetCore.Antiforgery /// The . protected virtual void SetDoNotCacheHeaders(HttpContext httpContext) { - // Since antifogery token generation is not very obvious to the end users (ex: MVC's form tag generates them + // Since antiforgery token generation is not very obvious to the end users (ex: MVC's form tag generates them // by default), log a warning to let users know of the change in behavior to any cache headers they might // have set explicitly. LogCacheHeaderOverrideWarning(httpContext.Response); diff --git a/src/Antiforgery/test/DefaultAntiforgeryTokenGeneratorTest.cs b/src/Antiforgery/test/DefaultAntiforgeryTokenGeneratorTest.cs index e32fbb85ab..3df264d48d 100644 --- a/src/Antiforgery/test/DefaultAntiforgeryTokenGeneratorTest.cs +++ b/src/Antiforgery/test/DefaultAntiforgeryTokenGeneratorTest.cs @@ -149,10 +149,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal httpContext.User = new ClaimsPrincipal(identity); byte[] data = new byte[256 / 8]; - using (var rng = RandomNumberGenerator.Create()) - { - rng.GetBytes(data); - } + RandomNumberGenerator.Fill(data); var base64ClaimUId = Convert.ToBase64String(data); var expectedClaimUid = new BinaryBlob(256, data); diff --git a/src/Antiforgery/test/DefaultClaimUidExtractorTest.cs b/src/Antiforgery/test/DefaultClaimUidExtractorTest.cs index 1852b910da..67d690a83a 100644 --- a/src/Antiforgery/test/DefaultClaimUidExtractorTest.cs +++ b/src/Antiforgery/test/DefaultClaimUidExtractorTest.cs @@ -56,7 +56,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal public void DefaultUniqueClaimTypes_NotPresent_SerializesAllClaimTypes() { var identity = new ClaimsIdentity("someAuthentication"); - identity.AddClaim(new Claim(ClaimTypes.Email, "someone@antifrogery.com")); + identity.AddClaim(new Claim(ClaimTypes.Email, "someone@antiforgery.com")); identity.AddClaim(new Claim(ClaimTypes.GivenName, "some")); identity.AddClaim(new Claim(ClaimTypes.Surname, "one")); identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, string.Empty)); diff --git a/src/Antiforgery/test/Microsoft.AspNetCore.Antiforgery.Test.csproj b/src/Antiforgery/test/Microsoft.AspNetCore.Antiforgery.Test.csproj index 4b4a43b6c1..d7654d288d 100644 --- a/src/Antiforgery/test/Microsoft.AspNetCore.Antiforgery.Test.csproj +++ b/src/Antiforgery/test/Microsoft.AspNetCore.Antiforgery.Test.csproj @@ -8,7 +8,6 @@ - diff --git a/src/Azure/AzureAD/Authentication.AzureAD.UI/src/Areas/AzureAD/Pages/Account/AccessDenied.cshtml b/src/Azure/AzureAD/Authentication.AzureAD.UI/src/Areas/AzureAD/Pages/Account/AccessDenied.cshtml index cc15816741..487effa8c1 100644 --- a/src/Azure/AzureAD/Authentication.AzureAD.UI/src/Areas/AzureAD/Pages/Account/AccessDenied.cshtml +++ b/src/Azure/AzureAD/Authentication.AzureAD.UI/src/Areas/AzureAD/Pages/Account/AccessDenied.cshtml @@ -5,6 +5,6 @@ }
-

@ViewData["Title"]

-

You do not have access to this resource.

-
\ No newline at end of file +

@ViewData["Title"]

+

You do not have access to this resource.

+ diff --git a/src/Azure/AzureAD/Authentication.AzureAD.UI/src/Areas/AzureAD/Pages/Account/Error.cshtml b/src/Azure/AzureAD/Authentication.AzureAD.UI/src/Areas/AzureAD/Pages/Account/Error.cshtml index b1f4622758..30e569ead1 100644 --- a/src/Azure/AzureAD/Authentication.AzureAD.UI/src/Areas/AzureAD/Pages/Account/Error.cshtml +++ b/src/Azure/AzureAD/Authentication.AzureAD.UI/src/Areas/AzureAD/Pages/Account/Error.cshtml @@ -4,20 +4,20 @@ ViewData["Title"] = "Error"; } -

Error.

-

An error occurred while processing your request.

+

Error.

+

An error occurred while processing your request.

@if (Model.ShowRequestId) { -

+

Request ID: @Model.RequestId

} -

Development Mode

-

+

Development Mode

+

Swapping to Development environment will display more detailed information about the error that occurred.

-

+

Development environment should not be enabled in deployed applications, as it can result in sensitive information from exceptions being displayed to end users. For local debugging, development environment can be enabled by setting the ASPNETCORE_ENVIRONMENT environment variable to Development, and restarting the application. -

\ No newline at end of file +

diff --git a/src/Azure/AzureAD/Authentication.AzureAD.UI/src/Areas/AzureAD/Pages/Account/SignedOut.cshtml b/src/Azure/AzureAD/Authentication.AzureAD.UI/src/Areas/AzureAD/Pages/Account/SignedOut.cshtml index 41fcf9554a..ef41507d5b 100644 --- a/src/Azure/AzureAD/Authentication.AzureAD.UI/src/Areas/AzureAD/Pages/Account/SignedOut.cshtml +++ b/src/Azure/AzureAD/Authentication.AzureAD.UI/src/Areas/AzureAD/Pages/Account/SignedOut.cshtml @@ -4,7 +4,7 @@ ViewData["Title"] = "Signed out"; } -

@ViewData["Title"]

-

+

@ViewData["Title"]

+

You have successfully signed out. -

\ No newline at end of file +

diff --git a/src/Azure/AzureAD/Authentication.AzureAD.UI/src/Microsoft.AspNetCore.Authentication.AzureAD.UI.csproj b/src/Azure/AzureAD/Authentication.AzureAD.UI/src/Microsoft.AspNetCore.Authentication.AzureAD.UI.csproj index 6720f825e6..a17773b58d 100644 --- a/src/Azure/AzureAD/Authentication.AzureAD.UI/src/Microsoft.AspNetCore.Authentication.AzureAD.UI.csproj +++ b/src/Azure/AzureAD/Authentication.AzureAD.UI/src/Microsoft.AspNetCore.Authentication.AzureAD.UI.csproj @@ -6,7 +6,7 @@ $(DefaultNetCoreTargetFramework) aspnetcore;authentication;AzureAD true - true + true Microsoft.AspNetCore.Mvc.ApplicationParts.NullApplicationPartFactory, Microsoft.AspNetCore.Mvc.Core true diff --git a/src/Azure/AzureAD/Authentication.AzureADB2C.UI/src/Areas/AzureADB2C/Pages/Account/AccessDenied.cshtml b/src/Azure/AzureAD/Authentication.AzureADB2C.UI/src/Areas/AzureADB2C/Pages/Account/AccessDenied.cshtml index cc15816741..ac4c026e2f 100644 --- a/src/Azure/AzureAD/Authentication.AzureADB2C.UI/src/Areas/AzureADB2C/Pages/Account/AccessDenied.cshtml +++ b/src/Azure/AzureAD/Authentication.AzureADB2C.UI/src/Areas/AzureADB2C/Pages/Account/AccessDenied.cshtml @@ -5,6 +5,6 @@ }
-

@ViewData["Title"]

-

You do not have access to this resource.

-
\ No newline at end of file +

@ViewData["Title"]

+

You do not have access to this resource.

+ diff --git a/src/Azure/AzureAD/Authentication.AzureADB2C.UI/src/Areas/AzureADB2C/Pages/Account/Error.cshtml b/src/Azure/AzureAD/Authentication.AzureADB2C.UI/src/Areas/AzureADB2C/Pages/Account/Error.cshtml index b1f4622758..60da74a372 100644 --- a/src/Azure/AzureAD/Authentication.AzureADB2C.UI/src/Areas/AzureADB2C/Pages/Account/Error.cshtml +++ b/src/Azure/AzureAD/Authentication.AzureADB2C.UI/src/Areas/AzureADB2C/Pages/Account/Error.cshtml @@ -4,20 +4,20 @@ ViewData["Title"] = "Error"; } -

Error.

-

An error occurred while processing your request.

+

Error.

+

An error occurred while processing your request.

@if (Model.ShowRequestId) { -

+

Request ID: @Model.RequestId

} -

Development Mode

-

+

Development Mode

+

Swapping to Development environment will display more detailed information about the error that occurred.

-

+

Development environment should not be enabled in deployed applications, as it can result in sensitive information from exceptions being displayed to end users. For local debugging, development environment can be enabled by setting the ASPNETCORE_ENVIRONMENT environment variable to Development, and restarting the application. -

\ No newline at end of file +

diff --git a/src/Azure/AzureAD/Authentication.AzureADB2C.UI/src/Areas/AzureADB2C/Pages/Account/SignedOut.cshtml b/src/Azure/AzureAD/Authentication.AzureADB2C.UI/src/Areas/AzureADB2C/Pages/Account/SignedOut.cshtml index 41fcf9554a..b29fbad79c 100644 --- a/src/Azure/AzureAD/Authentication.AzureADB2C.UI/src/Areas/AzureADB2C/Pages/Account/SignedOut.cshtml +++ b/src/Azure/AzureAD/Authentication.AzureADB2C.UI/src/Areas/AzureADB2C/Pages/Account/SignedOut.cshtml @@ -4,7 +4,7 @@ ViewData["Title"] = "Signed out"; } -

@ViewData["Title"]

-

+

@ViewData["Title"]

+

You have successfully signed out. -

\ No newline at end of file +

diff --git a/src/Azure/AzureAD/Authentication.AzureADB2C.UI/src/Microsoft.AspNetCore.Authentication.AzureADB2C.UI.csproj b/src/Azure/AzureAD/Authentication.AzureADB2C.UI/src/Microsoft.AspNetCore.Authentication.AzureADB2C.UI.csproj index 2711ae5303..77ec20937b 100644 --- a/src/Azure/AzureAD/Authentication.AzureADB2C.UI/src/Microsoft.AspNetCore.Authentication.AzureADB2C.UI.csproj +++ b/src/Azure/AzureAD/Authentication.AzureADB2C.UI/src/Microsoft.AspNetCore.Authentication.AzureADB2C.UI.csproj @@ -6,7 +6,7 @@ $(DefaultNetCoreTargetFramework) aspnetcore;authentication;AzureADB2C true - true + true Microsoft.AspNetCore.Mvc.ApplicationParts.NullApplicationPartFactory, Microsoft.AspNetCore.Mvc.Core true diff --git a/src/Azure/AzureAD/test/FunctionalTests/Microsoft.AspNetCore.Authentication.AzureAD.FunctionalTests.csproj b/src/Azure/AzureAD/test/FunctionalTests/Microsoft.AspNetCore.Authentication.AzureAD.FunctionalTests.csproj index d2cb51194b..ba0beb09c8 100644 --- a/src/Azure/AzureAD/test/FunctionalTests/Microsoft.AspNetCore.Authentication.AzureAD.FunctionalTests.csproj +++ b/src/Azure/AzureAD/test/FunctionalTests/Microsoft.AspNetCore.Authentication.AzureAD.FunctionalTests.csproj @@ -2,7 +2,7 @@ $(DefaultNetCoreTargetFramework) - + true @@ -16,7 +16,6 @@ - diff --git a/src/Azure/AzureAppServices.HostingStartup/src/Microsoft.AspNetCore.AzureAppServices.HostingStartup.csproj b/src/Azure/AzureAppServices.HostingStartup/src/Microsoft.AspNetCore.AzureAppServices.HostingStartup.csproj index 8992d007b8..5d4b07647f 100644 --- a/src/Azure/AzureAppServices.HostingStartup/src/Microsoft.AspNetCore.AzureAppServices.HostingStartup.csproj +++ b/src/Azure/AzureAppServices.HostingStartup/src/Microsoft.AspNetCore.AzureAppServices.HostingStartup.csproj @@ -7,7 +7,7 @@ $(DefaultNetCoreTargetFramework) true aspnetcore;azure;appservices - true + true diff --git a/src/Azure/AzureAppServicesIntegration/src/Microsoft.AspNetCore.AzureAppServicesIntegration.csproj b/src/Azure/AzureAppServicesIntegration/src/Microsoft.AspNetCore.AzureAppServicesIntegration.csproj index 972ea62ff8..a9f4eae94d 100644 --- a/src/Azure/AzureAppServicesIntegration/src/Microsoft.AspNetCore.AzureAppServicesIntegration.csproj +++ b/src/Azure/AzureAppServicesIntegration/src/Microsoft.AspNetCore.AzureAppServicesIntegration.csproj @@ -7,7 +7,7 @@ true true aspnetcore;azure;appservices - true + true diff --git a/src/Components/Analyzers/src/ComponentInternalUsageDiagnosticAnalzyer.cs b/src/Components/Analyzers/src/ComponentInternalUsageDiagnosticAnalzyer.cs index 8f6272c326..b1b5724cb3 100644 --- a/src/Components/Analyzers/src/ComponentInternalUsageDiagnosticAnalzyer.cs +++ b/src/Components/Analyzers/src/ComponentInternalUsageDiagnosticAnalzyer.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.Collections.Immutable; using Microsoft.AspNetCore.Components.Analyzers; using Microsoft.CodeAnalysis; @@ -15,6 +16,8 @@ namespace Microsoft.Extensions.Internal [DiagnosticAnalyzer(LanguageNames.CSharp)] public class ComponentInternalUsageDiagnosticAnalyzer : DiagnosticAnalyzer { + private static readonly string[] NamespaceParts = new[] { "RenderTree", "Components", "AspNetCore", "Microsoft", }; + private readonly InternalUsageAnalyzer _inner; public ComponentInternalUsageDiagnosticAnalyzer() @@ -27,17 +30,25 @@ namespace Microsoft.Extensions.Internal public override void Initialize(AnalysisContext context) { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics); + _inner.Register(context); } private static bool IsInInternalNamespace(ISymbol symbol) { - if (symbol?.ContainingNamespace?.ToDisplayString() is string ns) + var @namespace = symbol?.ContainingNamespace; + for (var i = 0; i < NamespaceParts.Length; i++) { - return string.Equals(ns, "Microsoft.AspNetCore.Components.RenderTree"); + if (@namespace == null || !string.Equals(NamespaceParts[i], @namespace.Name, StringComparison.Ordinal)) + { + return false; + } + + @namespace = @namespace.ContainingNamespace; } - return false; + return @namespace.IsGlobalNamespace; } } } diff --git a/src/Components/Analyzers/src/DiagnosticDescriptors.cs b/src/Components/Analyzers/src/DiagnosticDescriptors.cs index 8eb86b3212..05b98d3b4a 100644 --- a/src/Components/Analyzers/src/DiagnosticDescriptors.cs +++ b/src/Components/Analyzers/src/DiagnosticDescriptors.cs @@ -10,7 +10,7 @@ namespace Microsoft.AspNetCore.Components.Analyzers // Note: The Razor Compiler (including Components features) use the RZ prefix for diagnostics, so there's currently // no change of clashing between that and the BL prefix used here. // - // Tracking https://github.com/aspnet/AspNetCore/issues/10382 to rationalize this + // Tracking https://github.com/dotnet/aspnetcore/issues/10382 to rationalize this public static readonly DiagnosticDescriptor ComponentParameterSettersShouldBePublic = new DiagnosticDescriptor( "BL0001", new LocalizableResourceString(nameof(Resources.ComponentParameterSettersShouldBePublic_Title), Resources.ResourceManager, typeof(Resources)), diff --git a/src/Components/Analyzers/src/InternalUsageAnalyzer.cs b/src/Components/Analyzers/src/InternalUsageAnalyzer.cs index 92b07a7ab2..af77a42ecc 100644 --- a/src/Components/Analyzers/src/InternalUsageAnalyzer.cs +++ b/src/Components/Analyzers/src/InternalUsageAnalyzer.cs @@ -2,10 +2,10 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Linq; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Operations; namespace Microsoft.Extensions.Internal { @@ -35,87 +35,149 @@ namespace Microsoft.Extensions.Internal public void Register(AnalysisContext context) { context.EnableConcurrentExecution(); - context.RegisterSyntaxNodeAction(AnalyzeNode, - SyntaxKind.SimpleMemberAccessExpression, - SyntaxKind.ObjectCreationExpression, - SyntaxKind.ClassDeclaration, - SyntaxKind.Parameter); + + // Analyze usage of our internal types in method bodies. + context.RegisterOperationAction( + AnalyzeOperation, + OperationKind.ObjectCreation, + OperationKind.Invocation, + OperationKind.FieldReference, + OperationKind.MethodReference, + OperationKind.PropertyReference, + OperationKind.EventReference); + + // Analyze declarations that use our internal types in API surface. + context.RegisterSymbolAction( + AnalyzeSymbol, + SymbolKind.NamedType, + SymbolKind.Field, + SymbolKind.Method, + SymbolKind.Property, + SymbolKind.Event); } - private void AnalyzeNode(SyntaxNodeAnalysisContext context) + private void AnalyzeOperation(OperationAnalysisContext context) { - switch (context.Node) + var symbol = context.Operation switch { - case MemberAccessExpressionSyntax memberAccessSyntax: + IObjectCreationOperation creation => creation.Constructor, + IInvocationOperation invocation => invocation.TargetMethod, + IFieldReferenceOperation field => field.Member, + IMethodReferenceOperation method => method.Member, + IPropertyReferenceOperation property => property.Member, + IEventReferenceOperation @event => @event.Member, + _ => throw new InvalidOperationException("Unexpected operation kind: " + context.Operation.Kind), + }; + + VisitOperationSymbol(context, symbol); + } + + private void AnalyzeSymbol(SymbolAnalysisContext context) + { + // Note: we don't currently try to detect second-order usage of these types + // like public Task GetFooAsync() { }. + // + // This probably accomplishes our goals OK for now, which are focused on use of these + // types in method bodies. + switch (context.Symbol) + { + case INamedTypeSymbol type: + VisitDeclarationSymbol(context, type.BaseType, type); + foreach (var @interface in type.Interfaces) { - if (context.SemanticModel.GetSymbolInfo(context.Node, context.CancellationToken).Symbol is ISymbol symbol && - symbol.ContainingAssembly != context.Compilation.Assembly) - { - var containingType = symbol.ContainingType; + VisitDeclarationSymbol(context, @interface, type); + } + break; - if (HasInternalAttribute(symbol)) - { - context.ReportDiagnostic(Diagnostic.Create(_descriptor, memberAccessSyntax.Name.GetLocation(), $"{containingType}.{symbol.Name}")); - return; - } + case IFieldSymbol field: + VisitDeclarationSymbol(context, field.Type, field); + break; - if (IsInInternalNamespace(containingType) || HasInternalAttribute(containingType)) - { - context.ReportDiagnostic(Diagnostic.Create(_descriptor, memberAccessSyntax.Name.GetLocation(), containingType)); - return; - } - } - return; + case IMethodSymbol method: + + // Ignore return types on property-getters. Those will be reported through + // the property analysis. + if (method.MethodKind != MethodKind.PropertyGet) + { + VisitDeclarationSymbol(context, method.ReturnType, method); } - case ObjectCreationExpressionSyntax creationSyntax: + // Ignore parameters on property-setters. Those will be reported through + // the property analysis. + if (method.MethodKind != MethodKind.PropertySet) { - if (context.SemanticModel.GetSymbolInfo(context.Node, context.CancellationToken).Symbol is ISymbol symbol && - symbol.ContainingAssembly != context.Compilation.Assembly) + foreach (var parameter in method.Parameters) { - var containingType = symbol.ContainingType; - - if (HasInternalAttribute(symbol)) - { - context.ReportDiagnostic(Diagnostic.Create(_descriptor, creationSyntax.GetLocation(), containingType)); - return; - } - - if (IsInInternalNamespace(containingType) || HasInternalAttribute(containingType)) - { - context.ReportDiagnostic(Diagnostic.Create(_descriptor, creationSyntax.Type.GetLocation(), containingType)); - return; - } + VisitDeclarationSymbol(context, parameter.Type, method); } - - return; } + break; - case ClassDeclarationSyntax declarationSyntax: - { - if (context.SemanticModel.GetDeclaredSymbol(declarationSyntax)?.BaseType is ISymbol symbol && - symbol.ContainingAssembly != context.Compilation.Assembly && - (IsInInternalNamespace(symbol) || HasInternalAttribute(symbol)) && - declarationSyntax.BaseList?.Types.Count > 0) - { - context.ReportDiagnostic(Diagnostic.Create(_descriptor, declarationSyntax.BaseList.Types[0].GetLocation(), symbol)); - } + case IPropertySymbol property: + VisitDeclarationSymbol(context, property.Type, property); + break; - return; - } + case IEventSymbol @event: + VisitDeclarationSymbol(context, @event.Type, @event); + break; + } + } - case ParameterSyntax parameterSyntax: - { - if (context.SemanticModel.GetDeclaredSymbol(parameterSyntax)?.Type is ISymbol symbol && - symbol.ContainingAssembly != context.Compilation.Assembly && - (IsInInternalNamespace(symbol) || HasInternalAttribute(symbol))) - { + // Similar logic here to VisitDeclarationSymbol, keep these in sync. + private void VisitOperationSymbol(OperationAnalysisContext context, ISymbol symbol) + { + if (symbol == null || symbol.ContainingAssembly == context.Compilation.Assembly) + { + // The type is being referenced within the same assembly. This is valid use of an "internal" type + return; + } - context.ReportDiagnostic(Diagnostic.Create(_descriptor, parameterSyntax.GetLocation(), symbol)); - } + if (HasInternalAttribute(symbol)) + { + context.ReportDiagnostic(Diagnostic.Create( + _descriptor, + context.Operation.Syntax.GetLocation(), + symbol.ToDisplayString(SymbolDisplayFormat.CSharpShortErrorMessageFormat))); + return; + } - return; - } + var containingType = symbol.ContainingType; + if (IsInInternalNamespace(containingType) || HasInternalAttribute(containingType)) + { + context.ReportDiagnostic(Diagnostic.Create( + _descriptor, + context.Operation.Syntax.GetLocation(), + containingType.ToDisplayString(SymbolDisplayFormat.CSharpShortErrorMessageFormat))); + return; + } + } + + // Similar logic here to VisitOperationSymbol, keep these in sync. + private void VisitDeclarationSymbol(SymbolAnalysisContext context, ISymbol symbol, ISymbol symbolForDiagnostic) + { + if (symbol == null || symbol.ContainingAssembly == context.Compilation.Assembly) + { + // This is part of the compilation, avoid this analyzer when building from source. + return; + } + + if (HasInternalAttribute(symbol)) + { + context.ReportDiagnostic(Diagnostic.Create( + _descriptor, + symbolForDiagnostic.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax().GetLocation() ?? Location.None, + symbol.ToDisplayString(SymbolDisplayFormat.CSharpShortErrorMessageFormat))); + return; + } + + var containingType = symbol as INamedTypeSymbol ?? symbol.ContainingType; + if (IsInInternalNamespace(containingType) || HasInternalAttribute(containingType)) + { + context.ReportDiagnostic(Diagnostic.Create( + _descriptor, + symbolForDiagnostic.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax().GetLocation() ?? Location.None, + containingType.ToDisplayString(SymbolDisplayFormat.CSharpShortErrorMessageFormat))); + return; } } diff --git a/src/Components/Analyzers/src/Microsoft.AspNetCore.Components.Analyzers.csproj b/src/Components/Analyzers/src/Microsoft.AspNetCore.Components.Analyzers.csproj index 903de47c78..dab047ca11 100644 --- a/src/Components/Analyzers/src/Microsoft.AspNetCore.Components.Analyzers.csproj +++ b/src/Components/Analyzers/src/Microsoft.AspNetCore.Components.Analyzers.csproj @@ -6,7 +6,7 @@ true false Roslyn analyzers for ASP.NET Core Components. - true + true false diff --git a/src/Components/Analyzers/test/AnalyzerTestBase.cs b/src/Components/Analyzers/test/AnalyzerTestBase.cs index 8c1ae95376..91bfe2d9cc 100644 --- a/src/Components/Analyzers/test/AnalyzerTestBase.cs +++ b/src/Components/Analyzers/test/AnalyzerTestBase.cs @@ -56,7 +56,7 @@ namespace Microsoft.AspNetCore.Components.Analyzers } // This test code needs to be updated to support distributed testing. - // See https://github.com/aspnet/AspNetCore/issues/10422 + // See https://github.com/dotnet/aspnetcore/issues/10422 #pragma warning disable 0618 var solutionDirectory = TestPathUtilities.GetSolutionRootDirectory("Components"); #pragma warning restore 0618 diff --git a/src/Components/Analyzers/test/ComponentInternalUsageDiagnosticsAnalyzerTest.cs b/src/Components/Analyzers/test/ComponentInternalUsageDiagnosticsAnalyzerTest.cs new file mode 100644 index 0000000000..c7a01a5c63 --- /dev/null +++ b/src/Components/Analyzers/test/ComponentInternalUsageDiagnosticsAnalyzerTest.cs @@ -0,0 +1,105 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Threading.Tasks; +using Microsoft.AspNetCore.Analyzer.Testing; +using Microsoft.Extensions.Internal; +using Xunit; + +namespace Microsoft.AspNetCore.Components.Analyzers +{ + public class ComponentInternalUsageDiagnosticsAnalyzerTest : AnalyzerTestBase + { + public ComponentInternalUsageDiagnosticsAnalyzerTest() + { + Analyzer = new ComponentInternalUsageDiagnosticAnalyzer(); + Runner = new ComponentAnalyzerDiagnosticAnalyzerRunner(Analyzer); + } + + private ComponentInternalUsageDiagnosticAnalyzer Analyzer { get; } + private ComponentAnalyzerDiagnosticAnalyzerRunner Runner { get; } + + [Fact] + public async Task InternalUsage_FindsUseOfInternalTypesInDeclarations() + { + // Arrange + var source = Read("UsesRendererTypesInDeclarations"); + + // Act + var diagnostics = await Runner.GetDiagnosticsAsync(source.Source); + + // Assert + Assert.Collection( + diagnostics, + diagnostic => + { + Assert.Same(DiagnosticDescriptors.DoNotUseRenderTreeTypes, diagnostic.Descriptor); + AnalyzerAssert.DiagnosticLocation(source.MarkerLocations["MMBaseClass"], diagnostic.Location); + }, + diagnostic => + { + Assert.Same(DiagnosticDescriptors.DoNotUseRenderTreeTypes, diagnostic.Descriptor); + AnalyzerAssert.DiagnosticLocation(source.MarkerLocations["MMField"], diagnostic.Location); + }, + diagnostic => + { + Assert.Same(DiagnosticDescriptors.DoNotUseRenderTreeTypes, diagnostic.Descriptor); + AnalyzerAssert.DiagnosticLocation(source.MarkerLocations["MMInvocation"], diagnostic.Location); + }, + diagnostic => + { + Assert.Same(DiagnosticDescriptors.DoNotUseRenderTreeTypes, diagnostic.Descriptor); + AnalyzerAssert.DiagnosticLocation(source.MarkerLocations["MMProperty"], diagnostic.Location); + }, + diagnostic => + { + Assert.Same(DiagnosticDescriptors.DoNotUseRenderTreeTypes, diagnostic.Descriptor); + AnalyzerAssert.DiagnosticLocation(source.MarkerLocations["MMParameter"], diagnostic.Location); + }, + diagnostic => + { + Assert.Same(DiagnosticDescriptors.DoNotUseRenderTreeTypes, diagnostic.Descriptor); + AnalyzerAssert.DiagnosticLocation(source.MarkerLocations["MMReturnType"], diagnostic.Location); + }); + } + + [Fact] + public async Task InternalUsage_FindsUseOfInternalTypesInMethodBody() + { + // Arrange + var source = Read("UsersRendererTypesInMethodBody"); + + // Act + var diagnostics = await Runner.GetDiagnosticsAsync(source.Source); + + // Assert + Assert.Collection( + diagnostics, + diagnostic => + { + Assert.Same(DiagnosticDescriptors.DoNotUseRenderTreeTypes, diagnostic.Descriptor); + AnalyzerAssert.DiagnosticLocation(source.MarkerLocations["MMField"], diagnostic.Location); + }, + diagnostic => + { + Assert.Same(DiagnosticDescriptors.DoNotUseRenderTreeTypes, diagnostic.Descriptor); + AnalyzerAssert.DiagnosticLocation(source.MarkerLocations["MMNewObject"], diagnostic.Location); + }, + diagnostic => + { + Assert.Same(DiagnosticDescriptors.DoNotUseRenderTreeTypes, diagnostic.Descriptor); + AnalyzerAssert.DiagnosticLocation(source.MarkerLocations["MMProperty"], diagnostic.Location); + }, + diagnostic => + { + Assert.Same(DiagnosticDescriptors.DoNotUseRenderTreeTypes, diagnostic.Descriptor); + AnalyzerAssert.DiagnosticLocation(source.MarkerLocations["MMNewObject2"], diagnostic.Location); + }, + diagnostic => + { + Assert.Same(DiagnosticDescriptors.DoNotUseRenderTreeTypes, diagnostic.Descriptor); + AnalyzerAssert.DiagnosticLocation(source.MarkerLocations["MMInvocation"], diagnostic.Location); + }); + } + } +} diff --git a/src/Components/Analyzers/test/ComponentInternalUsageDiagnoticsAnalyzerTest.cs b/src/Components/Analyzers/test/ComponentInternalUsageDiagnoticsAnalyzerTest.cs deleted file mode 100644 index 92e2252304..0000000000 --- a/src/Components/Analyzers/test/ComponentInternalUsageDiagnoticsAnalyzerTest.cs +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Threading.Tasks; -using Microsoft.AspNetCore.Analyzer.Testing; -using Microsoft.Extensions.Internal; -using Xunit; - -namespace Microsoft.AspNetCore.Components.Analyzers -{ - public class ComponentInternalUsageDiagnoticsAnalyzerTest : AnalyzerTestBase - { - public ComponentInternalUsageDiagnoticsAnalyzerTest() - { - Analyzer = new ComponentInternalUsageDiagnosticAnalyzer(); - Runner = new ComponentAnalyzerDiagnosticAnalyzerRunner(Analyzer); - } - - private ComponentInternalUsageDiagnosticAnalyzer Analyzer { get; } - private ComponentAnalyzerDiagnosticAnalyzerRunner Runner { get; } - - [Fact] - public async Task InternalUsage_FindsUseOfRenderTreeFrameAsParameter() - { - // Arrange - var source = Read("UsesRenderTreeFrameAsParameter"); - - // Act - var diagnostics = await Runner.GetDiagnosticsAsync(source.Source); - - // Assert - Assert.Collection( - diagnostics, - diagnostic => - { - Assert.Same(DiagnosticDescriptors.DoNotUseRenderTreeTypes, diagnostic.Descriptor); - AnalyzerAssert.DiagnosticLocation(source.DefaultMarkerLocation, diagnostic.Location); - }); - } - - [Fact] - public async Task InternalUsage_FindsUseOfRenderTreeType() - { - // Arrange - var source = Read("UsesRenderTreeFrameTypeAsLocal"); - - // Act - var diagnostics = await Runner.GetDiagnosticsAsync(source.Source); - - // Assert - Assert.Collection( - diagnostics, - diagnostic => - { - Assert.Same(DiagnosticDescriptors.DoNotUseRenderTreeTypes, diagnostic.Descriptor); - AnalyzerAssert.DiagnosticLocation(source.DefaultMarkerLocation, diagnostic.Location); - }); - } - } -} diff --git a/src/Components/Analyzers/test/Microsoft.AspNetCore.Components.Analyzers.Tests.csproj b/src/Components/Analyzers/test/Microsoft.AspNetCore.Components.Analyzers.Tests.csproj index 10085017d1..80b92842f4 100644 --- a/src/Components/Analyzers/test/Microsoft.AspNetCore.Components.Analyzers.Tests.csproj +++ b/src/Components/Analyzers/test/Microsoft.AspNetCore.Components.Analyzers.Tests.csproj @@ -3,9 +3,6 @@ $(DefaultNetCoreTargetFramework) - - - false diff --git a/src/Components/Analyzers/test/TestFiles/ComponentInternalUsageDiagnosticsAnalyzerTest/UsersRendererTypesInMethodBody.cs b/src/Components/Analyzers/test/TestFiles/ComponentInternalUsageDiagnosticsAnalyzerTest/UsersRendererTypesInMethodBody.cs new file mode 100644 index 0000000000..9bd27fb960 --- /dev/null +++ b/src/Components/Analyzers/test/TestFiles/ComponentInternalUsageDiagnosticsAnalyzerTest/UsersRendererTypesInMethodBody.cs @@ -0,0 +1,20 @@ +using System; +using Microsoft.AspNetCore.Components.RenderTree; + +namespace Microsoft.AspNetCore.Components.Analyzers.Tests.TestFiles.ComponentInternalUsageDiagnosticsAnalyzerTest +{ + class UsersRendererTypesInMethodBody + { + private void Test() + { + var test = /*MMField*/RenderTreeFrameType.Attribute; + GC.KeepAlive(test); + + var frame = /*MMNewObject*/new RenderTreeFrame(); + GC.KeepAlive(/*MMProperty*/frame.Component); + + var range = /*MMNewObject2*/new ArrayRange(null, 0); + /*MMInvocation*/range.Clone(); + } + } +} diff --git a/src/Components/Analyzers/test/TestFiles/ComponentInternalUsageDiagnosticsAnalyzerTest/UsesRendererAsBaseClass.cs b/src/Components/Analyzers/test/TestFiles/ComponentInternalUsageDiagnosticsAnalyzerTest/UsesRendererAsBaseClass.cs new file mode 100644 index 0000000000..7ca9dfccf5 --- /dev/null +++ b/src/Components/Analyzers/test/TestFiles/ComponentInternalUsageDiagnosticsAnalyzerTest/UsesRendererAsBaseClass.cs @@ -0,0 +1,26 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Components.RenderTree; + +namespace Microsoft.AspNetCore.Components.Analyzers.Tests.TestFiles.ComponentInternalUsageDiagnosticsAnalyzerTest +{ + /*MM*/class UsesRendererAsBaseClass : Renderer + { + public UsesRendererAsBaseClass() + : base(null, null) + { + } + + public override Dispatcher Dispatcher => throw new NotImplementedException(); + + protected override void HandleException(Exception exception) + { + throw new NotImplementedException(); + } + + protected override Task UpdateDisplayAsync(/*M1*/in RenderBatch renderBatch) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/Components/Analyzers/test/TestFiles/ComponentInternalUsageDiagnosticsAnalyzerTest/UsesRendererTypesInDeclarations.cs b/src/Components/Analyzers/test/TestFiles/ComponentInternalUsageDiagnosticsAnalyzerTest/UsesRendererTypesInDeclarations.cs new file mode 100644 index 0000000000..0a0bd11b7b --- /dev/null +++ b/src/Components/Analyzers/test/TestFiles/ComponentInternalUsageDiagnosticsAnalyzerTest/UsesRendererTypesInDeclarations.cs @@ -0,0 +1,36 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Components.RenderTree; + +namespace Microsoft.AspNetCore.Components.Analyzers.Tests.TestFiles.ComponentInternalUsageDiagnosticsAnalyzerTest +{ + /*MMBaseClass*/class UsesRendererTypesInDeclarations : Renderer + { + private Renderer /*MMField*/_field = null; + + public UsesRendererTypesInDeclarations() + /*MMInvocation*/: base(null, null) + { + } + + public override Dispatcher Dispatcher => throw new NotImplementedException(); + + /*MMProperty*/public Renderer Property { get; set; } + + protected override void HandleException(Exception exception) + { + throw new NotImplementedException(); + } + + /*MMParameter*/protected override Task UpdateDisplayAsync(in RenderBatch renderBatch) + { + throw new NotImplementedException(); + } + + /*MMReturnType*/private Renderer GetRenderer() => _field; + + public interface ITestInterface + { + } + } +} diff --git a/src/Components/Analyzers/test/TestFiles/ComponentInternalUsageDiagnoticsAnalyzerTest/UsesRenderTreeFrameAsParameter.cs b/src/Components/Analyzers/test/TestFiles/ComponentInternalUsageDiagnoticsAnalyzerTest/UsesRenderTreeFrameAsParameter.cs deleted file mode 100644 index 415030a011..0000000000 --- a/src/Components/Analyzers/test/TestFiles/ComponentInternalUsageDiagnoticsAnalyzerTest/UsesRenderTreeFrameAsParameter.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Microsoft.AspNetCore.Components.RenderTree; - -namespace Microsoft.AspNetCore.Components.Analyzers.Tests.TestFiles.ComponentInternalUsageDiagnoticsAnalyzerTest -{ - class UsesRenderTreeFrameAsParameter - { - private void Test(/*MM*/RenderTreeFrame frame) - { - } - } -} diff --git a/src/Components/Analyzers/test/TestFiles/ComponentInternalUsageDiagnoticsAnalyzerTest/UsesRenderTreeFrameTypeAsLocal.cs b/src/Components/Analyzers/test/TestFiles/ComponentInternalUsageDiagnoticsAnalyzerTest/UsesRenderTreeFrameTypeAsLocal.cs deleted file mode 100644 index bdd40c2df1..0000000000 --- a/src/Components/Analyzers/test/TestFiles/ComponentInternalUsageDiagnoticsAnalyzerTest/UsesRenderTreeFrameTypeAsLocal.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using Microsoft.AspNetCore.Components.RenderTree; - -namespace Microsoft.AspNetCore.Components.Analyzers.Tests.TestFiles.ComponentInternalUsageDiagnoticsAnalyzerTest -{ - class UsesRenderTreeFrameTypeAsLocal - { - private void Test() - { - var test = RenderTreeFrameType./*MM*/Attribute; - GC.KeepAlive(test); - } - - } -} diff --git a/src/Components/Authorization/ref/Microsoft.AspNetCore.Components.Authorization.csproj b/src/Components/Authorization/ref/Microsoft.AspNetCore.Components.Authorization.csproj index 3a91e8a4b8..53db4b90de 100644 --- a/src/Components/Authorization/ref/Microsoft.AspNetCore.Components.Authorization.csproj +++ b/src/Components/Authorization/ref/Microsoft.AspNetCore.Components.Authorization.csproj @@ -2,6 +2,7 @@ netstandard2.0;$(DefaultNetCoreTargetFramework) + $(DefaultNetCoreTargetFramework) diff --git a/src/Components/Authorization/ref/Microsoft.AspNetCore.Components.Authorization.netcoreapp.cs b/src/Components/Authorization/ref/Microsoft.AspNetCore.Components.Authorization.netcoreapp.cs index ca0535937a..1ed5ecbf0e 100644 --- a/src/Components/Authorization/ref/Microsoft.AspNetCore.Components.Authorization.netcoreapp.cs +++ b/src/Components/Authorization/ref/Microsoft.AspNetCore.Components.Authorization.netcoreapp.cs @@ -6,7 +6,7 @@ namespace Microsoft.AspNetCore.Components.Authorization public partial class AuthenticationState { public AuthenticationState(System.Security.Claims.ClaimsPrincipal user) { } - public System.Security.Claims.ClaimsPrincipal User { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public System.Security.Claims.ClaimsPrincipal User { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } } public delegate void AuthenticationStateChangedHandler(System.Threading.Tasks.Task task); public abstract partial class AuthenticationStateProvider @@ -20,33 +20,33 @@ namespace Microsoft.AspNetCore.Components.Authorization { public AuthorizeRouteView() { } [Microsoft.AspNetCore.Components.ParameterAttribute] - public Microsoft.AspNetCore.Components.RenderFragment Authorizing { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.AspNetCore.Components.RenderFragment Authorizing { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } [Microsoft.AspNetCore.Components.ParameterAttribute] - public Microsoft.AspNetCore.Components.RenderFragment NotAuthorized { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.AspNetCore.Components.RenderFragment NotAuthorized { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } protected override void Render(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder builder) { } } public partial class AuthorizeView : Microsoft.AspNetCore.Components.Authorization.AuthorizeViewCore { public AuthorizeView() { } [Microsoft.AspNetCore.Components.ParameterAttribute] - public string Policy { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public string Policy { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } [Microsoft.AspNetCore.Components.ParameterAttribute] - public string Roles { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public string Roles { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } protected override Microsoft.AspNetCore.Authorization.IAuthorizeData[] GetAuthorizeData() { throw null; } } public abstract partial class AuthorizeViewCore : Microsoft.AspNetCore.Components.ComponentBase { protected AuthorizeViewCore() { } [Microsoft.AspNetCore.Components.ParameterAttribute] - public Microsoft.AspNetCore.Components.RenderFragment Authorized { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.AspNetCore.Components.RenderFragment Authorized { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } [Microsoft.AspNetCore.Components.ParameterAttribute] - public Microsoft.AspNetCore.Components.RenderFragment Authorizing { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.AspNetCore.Components.RenderFragment Authorizing { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } [Microsoft.AspNetCore.Components.ParameterAttribute] - public Microsoft.AspNetCore.Components.RenderFragment ChildContent { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.AspNetCore.Components.RenderFragment ChildContent { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } [Microsoft.AspNetCore.Components.ParameterAttribute] - public Microsoft.AspNetCore.Components.RenderFragment NotAuthorized { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.AspNetCore.Components.RenderFragment NotAuthorized { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } [Microsoft.AspNetCore.Components.ParameterAttribute] - public object Resource { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public object Resource { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } protected override void BuildRenderTree(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder builder) { } protected abstract Microsoft.AspNetCore.Authorization.IAuthorizeData[] GetAuthorizeData(); [System.Diagnostics.DebuggerStepThroughAttribute] @@ -56,7 +56,7 @@ namespace Microsoft.AspNetCore.Components.Authorization { public CascadingAuthenticationState() { } [Microsoft.AspNetCore.Components.ParameterAttribute] - public Microsoft.AspNetCore.Components.RenderFragment ChildContent { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.AspNetCore.Components.RenderFragment ChildContent { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } protected override void BuildRenderTree(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder) { } protected override void OnInitialized() { } void System.IDisposable.Dispose() { } diff --git a/src/Components/Authorization/ref/Microsoft.AspNetCore.Components.Authorization.netstandard2.0.cs b/src/Components/Authorization/ref/Microsoft.AspNetCore.Components.Authorization.netstandard2.0.cs index ca0535937a..1ed5ecbf0e 100644 --- a/src/Components/Authorization/ref/Microsoft.AspNetCore.Components.Authorization.netstandard2.0.cs +++ b/src/Components/Authorization/ref/Microsoft.AspNetCore.Components.Authorization.netstandard2.0.cs @@ -6,7 +6,7 @@ namespace Microsoft.AspNetCore.Components.Authorization public partial class AuthenticationState { public AuthenticationState(System.Security.Claims.ClaimsPrincipal user) { } - public System.Security.Claims.ClaimsPrincipal User { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public System.Security.Claims.ClaimsPrincipal User { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } } public delegate void AuthenticationStateChangedHandler(System.Threading.Tasks.Task task); public abstract partial class AuthenticationStateProvider @@ -20,33 +20,33 @@ namespace Microsoft.AspNetCore.Components.Authorization { public AuthorizeRouteView() { } [Microsoft.AspNetCore.Components.ParameterAttribute] - public Microsoft.AspNetCore.Components.RenderFragment Authorizing { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.AspNetCore.Components.RenderFragment Authorizing { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } [Microsoft.AspNetCore.Components.ParameterAttribute] - public Microsoft.AspNetCore.Components.RenderFragment NotAuthorized { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.AspNetCore.Components.RenderFragment NotAuthorized { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } protected override void Render(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder builder) { } } public partial class AuthorizeView : Microsoft.AspNetCore.Components.Authorization.AuthorizeViewCore { public AuthorizeView() { } [Microsoft.AspNetCore.Components.ParameterAttribute] - public string Policy { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public string Policy { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } [Microsoft.AspNetCore.Components.ParameterAttribute] - public string Roles { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public string Roles { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } protected override Microsoft.AspNetCore.Authorization.IAuthorizeData[] GetAuthorizeData() { throw null; } } public abstract partial class AuthorizeViewCore : Microsoft.AspNetCore.Components.ComponentBase { protected AuthorizeViewCore() { } [Microsoft.AspNetCore.Components.ParameterAttribute] - public Microsoft.AspNetCore.Components.RenderFragment Authorized { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.AspNetCore.Components.RenderFragment Authorized { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } [Microsoft.AspNetCore.Components.ParameterAttribute] - public Microsoft.AspNetCore.Components.RenderFragment Authorizing { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.AspNetCore.Components.RenderFragment Authorizing { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } [Microsoft.AspNetCore.Components.ParameterAttribute] - public Microsoft.AspNetCore.Components.RenderFragment ChildContent { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.AspNetCore.Components.RenderFragment ChildContent { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } [Microsoft.AspNetCore.Components.ParameterAttribute] - public Microsoft.AspNetCore.Components.RenderFragment NotAuthorized { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.AspNetCore.Components.RenderFragment NotAuthorized { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } [Microsoft.AspNetCore.Components.ParameterAttribute] - public object Resource { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public object Resource { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } protected override void BuildRenderTree(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder builder) { } protected abstract Microsoft.AspNetCore.Authorization.IAuthorizeData[] GetAuthorizeData(); [System.Diagnostics.DebuggerStepThroughAttribute] @@ -56,7 +56,7 @@ namespace Microsoft.AspNetCore.Components.Authorization { public CascadingAuthenticationState() { } [Microsoft.AspNetCore.Components.ParameterAttribute] - public Microsoft.AspNetCore.Components.RenderFragment ChildContent { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.AspNetCore.Components.RenderFragment ChildContent { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } protected override void BuildRenderTree(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder) { } protected override void OnInitialized() { } void System.IDisposable.Dispose() { } diff --git a/src/Components/benchmarkapps/Directory.Build.props b/src/Components/Blazor/Blazor.Version.props similarity index 100% rename from src/Components/benchmarkapps/Directory.Build.props rename to src/Components/Blazor/Blazor.Version.props diff --git a/src/Components/Blazor/Blazor/src/Hosting/EntrypointInvoker.cs b/src/Components/Blazor/Blazor/src/Hosting/EntrypointInvoker.cs new file mode 100644 index 0000000000..40a5d07de4 --- /dev/null +++ b/src/Components/Blazor/Blazor/src/Hosting/EntrypointInvoker.cs @@ -0,0 +1,89 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; + +namespace Microsoft.AspNetCore.Blazor.Hosting +{ + internal static class EntrypointInvoker + { + // This method returns void because currently the JS side is not listening to any result, + // nor will it handle any exceptions. We handle all exceptions internally to this method. + // In the future we may want Blazor.start to return something that exposes the possibly-async + // entrypoint result to the JS caller. There's no requirement to do that today, and if we + // do change this it will be non-breaking. + public static void InvokeEntrypoint(string assemblyName, string[] args) + { + object entrypointResult; + try + { + var assembly = Assembly.Load(assemblyName); + var entrypoint = FindUnderlyingEntrypoint(assembly); + var @params = entrypoint.GetParameters().Length == 1 ? new object[] { args ?? Array.Empty() } : new object[] { }; + entrypointResult = entrypoint.Invoke(null, @params); + } + catch (Exception syncException) + { + HandleStartupException(syncException); + return; + } + + // If the entrypoint is async, handle async exceptions in the same way that we would + // have handled sync ones + if (entrypointResult is Task entrypointTask) + { + entrypointTask.ContinueWith(task => + { + if (task.Exception != null) + { + HandleStartupException(task.Exception); + } + }); + } + } + + private static MethodBase FindUnderlyingEntrypoint(Assembly assembly) + { + // This is the entrypoint declared in .NET metadata. In the case of async main, it's the + // compiler-generated wrapper method. Otherwise it's the developer-defined method. + var metadataEntrypointMethodBase = assembly.EntryPoint; + + // For "async Task Main", the C# compiler generates a method called "
" + // that is marked as the assembly entrypoint. Detect this case, and instead of + // calling "", call the sibling "Whatever". + if (metadataEntrypointMethodBase.IsSpecialName) + { + var origName = metadataEntrypointMethodBase.Name; + var origNameLength = origName.Length; + if (origNameLength > 2) + { + var candidateMethodName = origName.Substring(1, origNameLength - 2); + var candidateMethod = metadataEntrypointMethodBase.DeclaringType.GetMethod( + candidateMethodName, + BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, + null, + metadataEntrypointMethodBase.GetParameters().Select(p => p.ParameterType).ToArray(), + null); + + if (candidateMethod != null) + { + return candidateMethod; + } + } + } + + // Either it's not async main, or for some reason we couldn't locate the underlying entrypoint, + // so use the one from assembly metadata. + return metadataEntrypointMethodBase; + } + + private static void HandleStartupException(Exception exception) + { + // Logs to console, and causes the error UI to appear + Console.Error.WriteLine(exception); + } + } +} diff --git a/src/Components/Blazor/Blazor/src/Hosting/IWebAssemblyServiceFactoryAdapter.cs b/src/Components/Blazor/Blazor/src/Hosting/IWebAssemblyServiceFactoryAdapter.cs index c790a3c879..ff63ec3a66 100644 --- a/src/Components/Blazor/Blazor/src/Hosting/IWebAssemblyServiceFactoryAdapter.cs +++ b/src/Components/Blazor/Blazor/src/Hosting/IWebAssemblyServiceFactoryAdapter.cs @@ -6,7 +6,7 @@ using Microsoft.Extensions.DependencyInjection; namespace Microsoft.AspNetCore.Blazor.Hosting { - // Equivalent to https://github.com/aspnet/Extensions/blob/master/src/Hosting/Hosting/src/Internal/IServiceFactoryAdapter.cs + // Equivalent to https://github.com/dotnet/extensions/blob/master/src/Hosting/Hosting/src/Internal/IServiceFactoryAdapter.cs internal interface IWebAssemblyServiceFactoryAdapter { diff --git a/src/Components/Blazor/Blazor/src/Hosting/WebAssemblyHost.cs b/src/Components/Blazor/Blazor/src/Hosting/WebAssemblyHost.cs index 3a2ccfbaae..b90878fdde 100644 --- a/src/Components/Blazor/Blazor/src/Hosting/WebAssemblyHost.cs +++ b/src/Components/Blazor/Blazor/src/Hosting/WebAssemblyHost.cs @@ -19,6 +19,11 @@ namespace Microsoft.AspNetCore.Blazor.Hosting public WebAssemblyHost(IServiceProvider services, IJSRuntime runtime) { + // To ensure JS-invoked methods don't get linked out, have a reference to their enclosing types + GC.KeepAlive(typeof(EntrypointInvoker)); + GC.KeepAlive(typeof(JSInteropMethods)); + GC.KeepAlive(typeof(WebAssemblyEventDispatcher)); + Services = services ?? throw new ArgumentNullException(nameof(services)); _runtime = runtime ?? throw new ArgumentNullException(nameof(runtime)); } diff --git a/src/Components/Blazor/Blazor/src/Hosting/WebAssemblyHostExtensions.cs b/src/Components/Blazor/Blazor/src/Hosting/WebAssemblyHostExtensions.cs index d08162a590..5182a1660d 100644 --- a/src/Components/Blazor/Blazor/src/Hosting/WebAssemblyHostExtensions.cs +++ b/src/Components/Blazor/Blazor/src/Hosting/WebAssemblyHostExtensions.cs @@ -22,7 +22,7 @@ namespace Microsoft.AspNetCore.Blazor.Hosting public static void Run(this IWebAssemblyHost host) { // Behave like async void, because we don't yet support async-main properly on WebAssembly. - // However, don't actualy make this method async, because we rely on startup being synchronous + // However, don't actually make this method async, because we rely on startup being synchronous // for things like attaching navigation event handlers. host.StartAsync().ContinueWith(task => { diff --git a/src/Components/Blazor/Blazor/src/Hosting/WebAssemblyServiceFactoryAdapter.cs b/src/Components/Blazor/Blazor/src/Hosting/WebAssemblyServiceFactoryAdapter.cs index fcc879653a..2cfc0ba093 100644 --- a/src/Components/Blazor/Blazor/src/Hosting/WebAssemblyServiceFactoryAdapter.cs +++ b/src/Components/Blazor/Blazor/src/Hosting/WebAssemblyServiceFactoryAdapter.cs @@ -6,7 +6,7 @@ using Microsoft.Extensions.DependencyInjection; namespace Microsoft.AspNetCore.Blazor.Hosting { - // Equivalent to https://github.com/aspnet/Extensions/blob/master/src/Hosting/Hosting/src/Internal/ServiceFactoryAdapter.cs + // Equivalent to https://github.com/dotnet/extensions/blob/master/src/Hosting/Hosting/src/Internal/ServiceFactoryAdapter.cs internal class WebAssemblyServiceFactoryAdapter : IWebAssemblyServiceFactoryAdapter { diff --git a/src/Components/Blazor/Blazor/src/Microsoft.AspNetCore.Blazor.csproj b/src/Components/Blazor/Blazor/src/Microsoft.AspNetCore.Blazor.csproj index e98ef09268..867dec8215 100644 --- a/src/Components/Blazor/Blazor/src/Microsoft.AspNetCore.Blazor.csproj +++ b/src/Components/Blazor/Blazor/src/Microsoft.AspNetCore.Blazor.csproj @@ -1,7 +1,7 @@  - netstandard2.0 + netstandard2.1 Build client-side single-page applications (SPAs) with Blazor running under WebAssembly. false diff --git a/src/Components/Blazor/Blazor/src/Services/WebAssemblyConsoleLogger.cs b/src/Components/Blazor/Blazor/src/Services/WebAssemblyConsoleLogger.cs index c86c1cf30b..1769dbd915 100644 --- a/src/Components/Blazor/Blazor/src/Services/WebAssemblyConsoleLogger.cs +++ b/src/Components/Blazor/Blazor/src/Services/WebAssemblyConsoleLogger.cs @@ -20,6 +20,11 @@ namespace Microsoft.AspNetCore.Blazor.Services public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) { + if (!IsEnabled(logLevel)) + { + return; + } + var formattedMessage = formatter(state, exception); Console.WriteLine($"[{logLevel}] {formattedMessage}"); } diff --git a/src/Components/Blazor/Blazor/test/Hosting/EntrypointInvokerTest.cs b/src/Components/Blazor/Blazor/test/Hosting/EntrypointInvokerTest.cs new file mode 100644 index 0000000000..60a3d1638b --- /dev/null +++ b/src/Components/Blazor/Blazor/test/Hosting/EntrypointInvokerTest.cs @@ -0,0 +1,153 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; +using System.Reflection; +using System.Runtime.Loader; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Xunit; + +namespace Microsoft.AspNetCore.Blazor.Hosting +{ + public class EntrypointInvokerTest + { + [Theory] + [InlineData(false, false)] + [InlineData(false, true)] + [InlineData(true, false)] + [InlineData(true, true)] + public void InvokesEntrypoint_Sync_Success(bool hasReturnValue, bool hasParams) + { + // Arrange + var returnType = hasReturnValue ? "int" : "void"; + var paramsDecl = hasParams ? "string[] args" : string.Empty; + var returnStatement = hasReturnValue ? "return 123;" : "return;"; + var assembly = CompileToAssembly(@" +static " + returnType + @" Main(" + paramsDecl + @") +{ + DidMainExecute = true; + " + returnStatement + @" +}", out var didMainExecute); + + // Act + EntrypointInvoker.InvokeEntrypoint(assembly.FullName, new string[] { }); + + // Assert + Assert.True(didMainExecute()); + } + + [Theory] + [InlineData(false, false)] + [InlineData(false, true)] + [InlineData(true, false)] + [InlineData(true, true)] + public void InvokesEntrypoint_Async_Success(bool hasReturnValue, bool hasParams) + { + // Arrange + var returnTypeGenericParam = hasReturnValue ? "" : string.Empty; + var paramsDecl = hasParams ? "string[] args" : string.Empty; + var returnStatement = hasReturnValue ? "return 123;" : "return;"; + var assembly = CompileToAssembly(@" +public static TaskCompletionSource ContinueTcs { get; } = new TaskCompletionSource(); + +static async Task" + returnTypeGenericParam + @" Main(" + paramsDecl + @") +{ + await ContinueTcs.Task; + DidMainExecute = true; + " + returnStatement + @" +}", out var didMainExecute); + + // Act/Assert 1: Waits for task + // The fact that we're not blocking here proves that we're not executing the + // metadata-declared entrypoint, as that would block + EntrypointInvoker.InvokeEntrypoint(assembly.FullName, new string[] { }); + Assert.False(didMainExecute()); + + // Act/Assert 2: Continues + var tcs = (TaskCompletionSource)assembly.GetType("SomeApp.Program").GetProperty("ContinueTcs").GetValue(null); + tcs.SetResult(null); + Assert.True(didMainExecute()); + } + + [Fact] + public void InvokesEntrypoint_Sync_Exception() + { + // Arrange + var assembly = CompileToAssembly(@" +public static void Main() +{ + DidMainExecute = true; + throw new InvalidTimeZoneException(""Test message""); +}", out var didMainExecute); + + // Act/Assert + // The fact that this doesn't throw shows that EntrypointInvoker is doing something + // to handle the exception. We can't assert about what it does here, because that + // would involve capturing console output, which isn't safe in unit tests. Instead + // we'll check this in E2E tests. + EntrypointInvoker.InvokeEntrypoint(assembly.FullName, new string[] { }); + Assert.True(didMainExecute()); + } + + [Fact] + public void InvokesEntrypoint_Async_Exception() + { + // Arrange + var assembly = CompileToAssembly(@" +public static TaskCompletionSource ContinueTcs { get; } = new TaskCompletionSource(); + +public static async Task Main() +{ + await ContinueTcs.Task; + DidMainExecute = true; + throw new InvalidTimeZoneException(""Test message""); +}", out var didMainExecute); + + // Act/Assert 1: Waits for task + EntrypointInvoker.InvokeEntrypoint(assembly.FullName, new string[] { }); + Assert.False(didMainExecute()); + + // Act/Assert 2: Continues + // As above, we can't directly observe the exception handling behavior here, + // so this is covered in E2E tests instead. + var tcs = (TaskCompletionSource)assembly.GetType("SomeApp.Program").GetProperty("ContinueTcs").GetValue(null); + tcs.SetResult(null); + Assert.True(didMainExecute()); + } + + private static Assembly CompileToAssembly(string mainMethod, out Func didMainExecute) + { + var syntaxTree = CSharpSyntaxTree.ParseText(@" +using System; +using System.Threading.Tasks; + +namespace SomeApp +{ + public static class Program + { + public static bool DidMainExecute { get; private set; } + + " + mainMethod + @" + } +}"); + + var compilation = CSharpCompilation.Create( + $"TestAssembly-{Guid.NewGuid().ToString("D")}", + new[] { syntaxTree }, + new[] { MetadataReference.CreateFromFile(typeof(object).GetTypeInfo().Assembly.Location) }, + new CSharpCompilationOptions(OutputKind.ConsoleApplication)); + using var ms = new MemoryStream(); + var compilationResult = compilation.Emit(ms); + ms.Seek(0, SeekOrigin.Begin); + var assembly = AssemblyLoadContext.Default.LoadFromStream(ms); + + var didMainExecuteProp = assembly.GetType("SomeApp.Program").GetProperty("DidMainExecute"); + didMainExecute = () => (bool)didMainExecuteProp.GetValue(null); + + return assembly; + } + } +} diff --git a/src/Components/Blazor/Blazor/test/Microsoft.AspNetCore.Blazor.Tests.csproj b/src/Components/Blazor/Blazor/test/Microsoft.AspNetCore.Blazor.Tests.csproj index c93519dfbd..b156fde4a2 100644 --- a/src/Components/Blazor/Blazor/test/Microsoft.AspNetCore.Blazor.Tests.csproj +++ b/src/Components/Blazor/Blazor/test/Microsoft.AspNetCore.Blazor.Tests.csproj @@ -1,4 +1,4 @@ - + $(DefaultNetCoreTargetFramework) @@ -8,6 +8,7 @@ + diff --git a/src/Components/Blazor/Build/src/Cli/Commands/ResolveRuntimeDependenciesCommand.cs b/src/Components/Blazor/Build/src/Cli/Commands/ResolveRuntimeDependenciesCommand.cs deleted file mode 100644 index d5d37bb833..0000000000 --- a/src/Components/Blazor/Build/src/Cli/Commands/ResolveRuntimeDependenciesCommand.cs +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.IO; -using Microsoft.Extensions.CommandLineUtils; - -namespace Microsoft.AspNetCore.Blazor.Build.DevServer.Commands -{ - class ResolveRuntimeDependenciesCommand - { - public static void Command(CommandLineApplication command) - { - var referencesFile = command.Option("--references", - "The path to a file that lists the paths to given referenced dll files", - CommandOptionType.SingleValue); - - var baseClassLibrary = command.Option("--base-class-library", - "Full path to a directory in which BCL assemblies can be found", - CommandOptionType.MultipleValue); - - var outputPath = command.Option("--output", - "Path to the output file that will contain the list with the full paths of the resolved assemblies", - CommandOptionType.SingleValue); - - var mainAssemblyPath = command.Argument("assembly", - "Path to the assembly containing the entry point of the application."); - - command.OnExecute(() => - { - if (string.IsNullOrEmpty(mainAssemblyPath.Value) || - !baseClassLibrary.HasValue() || !outputPath.HasValue()) - { - command.ShowHelp(command.Name); - return 1; - } - - try - { - var referencesSources = referencesFile.HasValue() - ? File.ReadAllLines(referencesFile.Value()) - : Array.Empty(); - - RuntimeDependenciesResolver.ResolveRuntimeDependencies( - mainAssemblyPath.Value, - referencesSources, - baseClassLibrary.Values.ToArray(), - outputPath.Value()); - - return 0; - } - catch (Exception ex) - { - Console.WriteLine($"ERROR: {ex.Message}"); - Console.WriteLine(ex.StackTrace); - return 1; - } - }); - } - } -} diff --git a/src/Components/Blazor/Build/src/Cli/Commands/WriteBootJsonCommand.cs b/src/Components/Blazor/Build/src/Cli/Commands/WriteBootJsonCommand.cs deleted file mode 100644 index dea217958c..0000000000 --- a/src/Components/Blazor/Build/src/Cli/Commands/WriteBootJsonCommand.cs +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using Microsoft.Extensions.CommandLineUtils; -using System; -using System.IO; - -namespace Microsoft.AspNetCore.Blazor.Build.DevServer.Commands -{ - internal class WriteBootJsonCommand - { - public static void Command(CommandLineApplication command) - { - var referencesFile = command.Option("--references", - "The path to a file that lists the paths to given referenced dll files", - CommandOptionType.SingleValue); - - var embeddedResourcesFile = command.Option("--embedded-resources", - "The path to a file that lists the paths of .NET assemblies that may contain embedded resources (typically, referenced assemblies in their pre-linked states)", - CommandOptionType.SingleValue); - - var outputPath = command.Option("--output", - "Path to the output file", - CommandOptionType.SingleValue); - - var mainAssemblyPath = command.Argument("assembly", - "Path to the assembly containing the entry point of the application."); - - var linkerEnabledFlag = command.Option("--linker-enabled", - "If set, specifies that the application is being built with linking enabled.", - CommandOptionType.NoValue); - - command.OnExecute(() => - { - if (string.IsNullOrEmpty(mainAssemblyPath.Value) || !outputPath.HasValue()) - { - command.ShowHelp(command.Name); - return 1; - } - - try - { - var referencesSources = referencesFile.HasValue() - ? File.ReadAllLines(referencesFile.Value()) - : Array.Empty(); - - var embeddedResourcesSources = embeddedResourcesFile.HasValue() - ? File.ReadAllLines(embeddedResourcesFile.Value()) - : Array.Empty(); - - BootJsonWriter.WriteFile( - mainAssemblyPath.Value, - referencesSources, - embeddedResourcesSources, - linkerEnabledFlag.HasValue(), - outputPath.Value()); - return 0; - } - catch (Exception ex) - { - Console.WriteLine($"ERROR: {ex.Message}"); - Console.WriteLine(ex.StackTrace); - return 1; - } - }); - } - } -} diff --git a/src/Components/Blazor/Build/src/Cli/Program.cs b/src/Components/Blazor/Build/src/Cli/Program.cs deleted file mode 100644 index 3bd530453f..0000000000 --- a/src/Components/Blazor/Build/src/Cli/Program.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using Microsoft.AspNetCore.Blazor.Build.DevServer.Commands; -using Microsoft.Extensions.CommandLineUtils; - -namespace Microsoft.AspNetCore.Blazor.Build -{ - static class Program - { - static int Main(string[] args) - { - var app = new CommandLineApplication - { - Name = "Microsoft.AspNetCore.Blazor.Build" - }; - app.HelpOption("-?|-h|--help"); - - app.Command("resolve-dependencies", ResolveRuntimeDependenciesCommand.Command); - app.Command("write-boot-json", WriteBootJsonCommand.Command); - - if (args.Length > 0) - { - return app.Execute(args); - } - else - { - app.ShowHelp(); - return 0; - } - } - } -} diff --git a/src/Components/Blazor/Build/src/Core/BootJsonWriter.cs b/src/Components/Blazor/Build/src/Core/BootJsonWriter.cs deleted file mode 100644 index 4d4c114158..0000000000 --- a/src/Components/Blazor/Build/src/Core/BootJsonWriter.cs +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text.Json; -using Microsoft.AspNetCore.Components; -using Mono.Cecil; - -namespace Microsoft.AspNetCore.Blazor.Build -{ - internal class BootJsonWriter - { - public static void WriteFile( - string assemblyPath, - string[] assemblyReferences, - string[] embeddedResourcesSources, - bool linkerEnabled, - string outputPath) - { - var embeddedContent = EmbeddedResourcesProcessor.ExtractEmbeddedResources( - embeddedResourcesSources, Path.GetDirectoryName(outputPath)); - var bootJsonText = GetBootJsonContent( - Path.GetFileName(assemblyPath), - GetAssemblyEntryPoint(assemblyPath), - assemblyReferences, - embeddedContent, - linkerEnabled); - var normalizedOutputPath = Path.GetFullPath(outputPath); - Console.WriteLine("Writing boot data to: " + normalizedOutputPath); - File.WriteAllText(normalizedOutputPath, bootJsonText); - } - - public static string GetBootJsonContent(string assemblyFileName, string entryPoint, string[] assemblyReferences, IEnumerable embeddedContent, bool linkerEnabled) - { - var data = new BootJsonData( - assemblyFileName, - entryPoint, - assemblyReferences, - embeddedContent, - linkerEnabled); - return JsonSerializer.Serialize(data, JsonSerializerOptionsProvider.Options); - } - - private static string GetAssemblyEntryPoint(string assemblyPath) - { - using (var assemblyDefinition = AssemblyDefinition.ReadAssembly(assemblyPath)) - { - var entryPoint = assemblyDefinition.EntryPoint; - if (entryPoint == null) - { - throw new ArgumentException($"The assembly at {assemblyPath} has no specified entry point."); - } - - return $"{entryPoint.DeclaringType.FullName}::{entryPoint.Name}"; - } - } - - /// - /// Defines the structure of a Blazor boot JSON file - /// - class BootJsonData - { - public string Main { get; } - public string EntryPoint { get; } - public IEnumerable AssemblyReferences { get; } - public IEnumerable CssReferences { get; } - public IEnumerable JsReferences { get; } - public bool LinkerEnabled { get; } - - public BootJsonData( - string entrypointAssemblyWithExtension, - string entryPoint, - IEnumerable assemblyReferences, - IEnumerable embeddedContent, - bool linkerEnabled) - { - Main = entrypointAssemblyWithExtension; - EntryPoint = entryPoint; - AssemblyReferences = assemblyReferences; - LinkerEnabled = linkerEnabled; - - CssReferences = embeddedContent - .Where(c => c.Kind == EmbeddedResourceKind.Css) - .Select(c => c.RelativePath); - - JsReferences = embeddedContent - .Where(c => c.Kind == EmbeddedResourceKind.JavaScript) - .Select(c => c.RelativePath); - } - } - } -} diff --git a/src/Components/Blazor/Build/src/Core/EmbeddedResources/EmbeddedResourceInfo.cs b/src/Components/Blazor/Build/src/Core/EmbeddedResources/EmbeddedResourceInfo.cs deleted file mode 100644 index 97331537f2..0000000000 --- a/src/Components/Blazor/Build/src/Core/EmbeddedResources/EmbeddedResourceInfo.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -namespace Microsoft.AspNetCore.Blazor.Build -{ - internal class EmbeddedResourceInfo - { - public EmbeddedResourceKind Kind { get; } - public string RelativePath { get; } - - public EmbeddedResourceInfo(EmbeddedResourceKind kind, string relativePath) - { - Kind = kind; - RelativePath = relativePath; - } - } -} diff --git a/src/Components/Blazor/Build/src/Core/EmbeddedResources/EmbeddedResourcesProcessor.cs b/src/Components/Blazor/Build/src/Core/EmbeddedResources/EmbeddedResourcesProcessor.cs deleted file mode 100644 index 21a28597e1..0000000000 --- a/src/Components/Blazor/Build/src/Core/EmbeddedResources/EmbeddedResourcesProcessor.cs +++ /dev/null @@ -1,137 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using Mono.Cecil; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; - -namespace Microsoft.AspNetCore.Blazor.Build -{ - internal class EmbeddedResourcesProcessor - { - const string ContentSubdirName = "_content"; - - private readonly static Dictionary _knownResourceKindsByNamePrefix = new Dictionary - { - { "blazor:js:", EmbeddedResourceKind.JavaScript }, - { "blazor:css:", EmbeddedResourceKind.Css }, - { "blazor:file:", EmbeddedResourceKind.Static }, - }; - - /// - /// Finds Blazor-specific embedded resources in the specified assemblies, writes them - /// to disk, and returns a description of those resources in dependency order. - /// - /// The paths to assemblies that may contain embedded resources. - /// The path to the directory where output is being written. - /// A description of the embedded resources that were written to disk. - public static IReadOnlyList ExtractEmbeddedResources( - IEnumerable referencedAssemblyPaths, string outputDir) - { - // Clean away any earlier state - var contentDir = Path.Combine(outputDir, ContentSubdirName); - if (Directory.Exists(contentDir)) - { - Directory.Delete(contentDir, recursive: true); - } - - // First, get an ordered list of AssemblyDefinition instances - var referencedAssemblyDefinitions = referencedAssemblyPaths - .Where(path => !Path.GetFileName(path).StartsWith("System.", StringComparison.Ordinal)) // Skip System.* because they are never going to contain embedded resources that we want - .Select(path => AssemblyDefinition.ReadAssembly(path)) - .ToList(); - referencedAssemblyDefinitions.Sort(OrderWithReferenceSubjectFirst); - - // Now process them in turn - return referencedAssemblyDefinitions - .SelectMany(def => ExtractEmbeddedResourcesFromSingleAssembly(def, outputDir)) - .ToList() - .AsReadOnly(); - } - - private static IEnumerable ExtractEmbeddedResourcesFromSingleAssembly( - AssemblyDefinition assemblyDefinition, string outputDirPath) - { - var assemblyName = assemblyDefinition.Name.Name; - foreach (var res in assemblyDefinition.MainModule.Resources) - { - if (TryExtractEmbeddedResource(assemblyName, res, outputDirPath, out var extractedResourceInfo)) - { - yield return extractedResourceInfo; - } - } - } - - private static bool TryExtractEmbeddedResource(string assemblyName, Resource resource, string outputDirPath, out EmbeddedResourceInfo extractedResourceInfo) - { - if (resource is EmbeddedResource embeddedResource) - { - if (TryInterpretLogicalName(resource.Name, out var kind, out var name)) - { - // Prefix the output path with the assembly name to ensure no clashes - // Also be invariant to the OS on which the package was built - name = Path.Combine(ContentSubdirName, assemblyName, EnsureHasPathSeparators(name, Path.DirectorySeparatorChar)); - - // Write the file content to disk, ensuring we don't try to write outside the output root - var outputPath = Path.GetFullPath(Path.Combine(outputDirPath, name)); - if (!outputPath.StartsWith(outputDirPath)) - { - throw new InvalidOperationException($"Cannot write embedded resource from assembly '{assemblyName}' to '{outputPath}' because it is outside the expected directory {outputDirPath}"); - } - WriteResourceFile(embeddedResource, outputPath); - - // The URLs we write into the boot json file need to use web-style directory separators - extractedResourceInfo = new EmbeddedResourceInfo(kind, EnsureHasPathSeparators(name, '/')); - return true; - } - } - - extractedResourceInfo = null; - return false; - } - - private static void WriteResourceFile(EmbeddedResource resource, string outputPath) - { - Directory.CreateDirectory(Path.GetDirectoryName(outputPath)); - using (var outputStream = File.OpenWrite(outputPath)) - { - resource.GetResourceStream().CopyTo(outputStream); - } - } - - private static string EnsureHasPathSeparators(string name, char desiredSeparatorChar) => name - .Replace('\\', desiredSeparatorChar) - .Replace('/', desiredSeparatorChar); - - private static bool TryInterpretLogicalName(string logicalName, out EmbeddedResourceKind kind, out string resolvedName) - { - foreach (var kvp in _knownResourceKindsByNamePrefix) - { - if (logicalName.StartsWith(kvp.Key, StringComparison.Ordinal)) - { - kind = kvp.Value; - resolvedName = logicalName.Substring(kvp.Key.Length); - return true; - } - } - - kind = default; - resolvedName = default; - return false; - } - - // For each assembly B that references A, we want the resources from A to be loaded before - // the references for B (because B's resources might depend on A's resources) - private static int OrderWithReferenceSubjectFirst(AssemblyDefinition a, AssemblyDefinition b) - => AssemblyHasReference(a, b) ? 1 - : AssemblyHasReference(b, a) ? -1 - : 0; - - private static bool AssemblyHasReference(AssemblyDefinition from, AssemblyDefinition to) - => from.MainModule.AssemblyReferences - .Select(reference => reference.Name) - .Contains(to.Name.Name, StringComparer.Ordinal); - } -} diff --git a/src/Components/Blazor/Build/src/Core/RuntimeDependenciesResolver.cs b/src/Components/Blazor/Build/src/Core/RuntimeDependenciesResolver.cs deleted file mode 100644 index 18637753cc..0000000000 --- a/src/Components/Blazor/Build/src/Core/RuntimeDependenciesResolver.cs +++ /dev/null @@ -1,165 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using Mono.Cecil; - -namespace Microsoft.AspNetCore.Blazor.Build -{ - internal class RuntimeDependenciesResolver - { - public static void ResolveRuntimeDependencies( - string entryPoint, - string[] applicationDependencies, - string[] monoBclDirectories, - string outputFile) - { - var paths = ResolveRuntimeDependenciesCore(entryPoint, applicationDependencies, monoBclDirectories); - File.WriteAllLines(outputFile, paths); - } - - public static IEnumerable ResolveRuntimeDependenciesCore( - string entryPoint, - string[] applicationDependencies, - string[] monoBclDirectories) - { - var assembly = new AssemblyEntry(entryPoint, AssemblyDefinition.ReadAssembly(entryPoint)); - - var dependencies = applicationDependencies - .Select(a => new AssemblyEntry(a, AssemblyDefinition.ReadAssembly(a))) - .ToArray(); - - var bcl = monoBclDirectories - .SelectMany(d => Directory.EnumerateFiles(d, "*.dll").Select(f => Path.Combine(d, f))) - .Select(a => new AssemblyEntry(a, AssemblyDefinition.ReadAssembly(a))) - .ToArray(); - - var assemblyResolutionContext = new AssemblyResolutionContext( - assembly, - dependencies, - bcl); - - assemblyResolutionContext.ResolveAssemblies(); - - var paths = assemblyResolutionContext.Results.Select(r => r.Path); - return paths.Concat(FindPdbs(paths)); - } - - private static IEnumerable FindPdbs(IEnumerable dllPaths) - { - return dllPaths - .Select(path => Path.ChangeExtension(path, "pdb")) - .Where(path => File.Exists(path)); - } - - public class AssemblyResolutionContext - { - public AssemblyResolutionContext( - AssemblyEntry assembly, - AssemblyEntry[] dependencies, - AssemblyEntry[] bcl) - { - Assembly = assembly; - Dependencies = dependencies; - Bcl = bcl; - } - - public AssemblyEntry Assembly { get; } - public AssemblyEntry[] Dependencies { get; } - public AssemblyEntry[] Bcl { get; } - - public IList Results { get; } = new List(); - - internal void ResolveAssemblies() - { - var visitedAssemblies = new HashSet(); - var pendingAssemblies = new Stack(); - pendingAssemblies.Push(Assembly.Definition.Name); - ResolveAssembliesCore(); - - void ResolveAssembliesCore() - { - while (pendingAssemblies.TryPop(out var current)) - { - if (!visitedAssemblies.Contains(current.Name)) - { - visitedAssemblies.Add(current.Name); - - // Not all references will be resolvable within the Mono BCL, particularly - // when building for server-side Blazor as you will be running on CoreCLR - // and therefore may depend on System.* BCL assemblies that aren't present - // in Mono WebAssembly. Skipping unresolved assemblies here is equivalent - // to passing "--skip-unresolved true" to the Mono linker. - var resolved = Resolve(current); - if (resolved != null) - { - Results.Add(resolved); - var references = GetAssemblyReferences(resolved); - foreach (var reference in references) - { - pendingAssemblies.Push(reference); - } - } - } - } - } - - IEnumerable GetAssemblyReferences(AssemblyEntry current) => - current.Definition.Modules.SelectMany(m => m.AssemblyReferences); - - AssemblyEntry Resolve(AssemblyNameReference current) - { - if (Assembly.Definition.Name.Name == current.Name) - { - return Assembly; - } - - var referencedAssemblyCandidate = FindCandidate(current, Dependencies); - var bclAssemblyCandidate = FindCandidate(current, Bcl); - - // Resolution logic. For right now, we will prefer the mono BCL version of a given - // assembly if there is a candidate assembly and an equivalent mono assembly. - if (bclAssemblyCandidate != null) - { - return bclAssemblyCandidate; - } - - return referencedAssemblyCandidate; - } - - AssemblyEntry FindCandidate(AssemblyNameReference current, AssemblyEntry[] candidates) - { - // Do simple name match. Assume no duplicates. - foreach (var candidate in candidates) - { - if (current.Name == candidate.Definition.Name.Name) - { - return candidate; - } - } - - return null; - } - } - } - - [DebuggerDisplay("{ToString(),nq}")] - public class AssemblyEntry - { - public AssemblyEntry(string path, AssemblyDefinition definition) - { - Path = path; - Definition = definition; - } - - public string Path { get; set; } - public AssemblyDefinition Definition { get; set; } - - public override string ToString() => Definition.FullName; - } - } -} diff --git a/src/Components/Blazor/Build/src/Microsoft.AspNetCore.Blazor.Build.csproj b/src/Components/Blazor/Build/src/Microsoft.AspNetCore.Blazor.Build.csproj index 93fa3fa9d6..7bc610f168 100644 --- a/src/Components/Blazor/Build/src/Microsoft.AspNetCore.Blazor.Build.csproj +++ b/src/Components/Blazor/Build/src/Microsoft.AspNetCore.Blazor.Build.csproj @@ -1,25 +1,26 @@ - + - $(DefaultNetCoreTargetFramework) + $(DefaultNetCoreTargetFramework);net46 + Microsoft.AspNetCore.Blazor.Build.Tasks + Microsoft.AspNetCore.Blazor.Build Build mechanism for ASP.NET Core Blazor applications. - Exe false false + false false - $(GenerateNuspecDependsOn);Publish true Microsoft.AspNetCore.Blazor.Build.nuspec - + @@ -27,16 +28,45 @@ - - - - - - - + + + + + + + - - - + + + + + <_NetCoreFilesToCopy Include="$(OutputPath)$(DefaultNetCoreTargetFramework)\*" TargetPath="netcoreapp\" /> + <_DesktopFilesToCopy Include="$(OutputPath)net46\*" TargetPath="netfx\" /> + <_AllFilesToCopy Include="@(_NetCoreFilesToCopy);@(_DesktopFilesToCopy)" /> + + + + + + + + + + diff --git a/src/Components/Blazor/Build/src/Microsoft.AspNetCore.Blazor.Build.nuspec b/src/Components/Blazor/Build/src/Microsoft.AspNetCore.Blazor.Build.nuspec index 2094d3fc5f..459fed97d0 100644 --- a/src/Components/Blazor/Build/src/Microsoft.AspNetCore.Blazor.Build.nuspec +++ b/src/Components/Blazor/Build/src/Microsoft.AspNetCore.Blazor.Build.nuspec @@ -11,7 +11,7 @@ - - + + diff --git a/src/Components/Blazor/Build/src/ReferenceBlazorBuildFromSource.props b/src/Components/Blazor/Build/src/ReferenceBlazorBuildFromSource.props new file mode 100644 index 0000000000..0bcebe22fa --- /dev/null +++ b/src/Components/Blazor/Build/src/ReferenceBlazorBuildFromSource.props @@ -0,0 +1,25 @@ + + + + + $(MSBuildThisFileDirectory)..\..\..\ + $(ComponentsRoot)Web.JS\dist\$(Configuration)\blazor.webassembly.js + $(ComponentsRoot)Web.JS\dist\$(Configuration)\blazor.webassembly.js.map + $(MSBuildThisFileDirectory)bin\$(Configuration)\tools\ + + + + + + + + + + diff --git a/src/Components/Blazor/Build/src/ReferenceFromSource.props b/src/Components/Blazor/Build/src/ReferenceFromSource.props index 8067cdc131..37e2b60e16 100644 --- a/src/Components/Blazor/Build/src/ReferenceFromSource.props +++ b/src/Components/Blazor/Build/src/ReferenceFromSource.props @@ -1,21 +1,6 @@ - - - - true - $(RepoRoot)src\Components\Web.JS\dist\$(Configuration)\blazor.*.js.* - - - - + + + + false - true - TargetFramework + TargetFramework=$(DefaultNetCoreTargetFramework) false diff --git a/src/Components/Blazor/Build/src/Tasks/BlazorCreateRootDescriptorFile.cs b/src/Components/Blazor/Build/src/Tasks/BlazorCreateRootDescriptorFile.cs new file mode 100644 index 0000000000..1aa8536856 --- /dev/null +++ b/src/Components/Blazor/Build/src/Tasks/BlazorCreateRootDescriptorFile.cs @@ -0,0 +1,56 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Xml; +using System.Xml.Linq; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; + +namespace Microsoft.AspNetCore.Blazor.Build +{ + // Based on https://github.com/mono/linker/blob/3b329b9481e300bcf4fb88a2eebf8cb5ef8b323b/src/ILLink.Tasks/CreateRootDescriptorFile.cs + public class BlazorCreateRootDescriptorFile : Task + { + [Required] + public ITaskItem[] AssemblyNames { get; set; } + + [Required] + public ITaskItem RootDescriptorFilePath { get; set; } + + public override bool Execute() + { + using var fileStream = File.Create(RootDescriptorFilePath.ItemSpec); + var assemblyNames = AssemblyNames.Select(a => a.ItemSpec); + + WriteRootDescriptor(fileStream, assemblyNames); + return true; + } + + internal static void WriteRootDescriptor(Stream stream, IEnumerable assemblyNames) + { + var roots = new XElement("linker"); + foreach (var assemblyName in assemblyNames) + { + roots.Add(new XElement("assembly", + new XAttribute("fullname", assemblyName), + new XElement("type", + new XAttribute("fullname", "*"), + new XAttribute("required", "true")))); + } + + var xmlWriterSettings = new XmlWriterSettings + { + Indent = true, + OmitXmlDeclaration = true + }; + + using var writer = XmlWriter.Create(stream, xmlWriterSettings); + var xDocument = new XDocument(roots); + + xDocument.Save(writer); + } + } +} diff --git a/src/Components/Blazor/Build/src/Tasks/BlazorILLink.cs b/src/Components/Blazor/Build/src/Tasks/BlazorILLink.cs new file mode 100644 index 0000000000..d5dc22cde0 --- /dev/null +++ b/src/Components/Blazor/Build/src/Tasks/BlazorILLink.cs @@ -0,0 +1,194 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; + +namespace Microsoft.AspNetCore.Blazor.Build.Tasks +{ + // Based on https://github.com/mono/linker/blob/3b329b9481e300bcf4fb88a2eebf8cb5ef8b323b/src/ILLink.Tasks/LinkTask.cs + public class BlazorILLink : ToolTask + { + private const string DotNetHostPathEnvironmentName = "DOTNET_HOST_PATH"; + + [Required] + public string ILLinkPath { get; set; } + + [Required] + public ITaskItem[] AssemblyPaths { get; set; } + + public ITaskItem[] ReferenceAssemblyPaths { get; set; } + + [Required] + public ITaskItem[] RootAssemblyNames { get; set; } + + [Required] + public ITaskItem OutputDirectory { get; set; } + + public ITaskItem[] RootDescriptorFiles { get; set; } + + public bool ClearInitLocals { get; set; } + + public string ClearInitLocalsAssemblies { get; set; } + + public string ExtraArgs { get; set; } + + public bool DumpDependencies { get; set; } + + private string _dotnetPath; + + private string DotNetPath + { + get + { + if (!string.IsNullOrEmpty(_dotnetPath)) + { + return _dotnetPath; + } + + _dotnetPath = Environment.GetEnvironmentVariable(DotNetHostPathEnvironmentName); + if (string.IsNullOrEmpty(_dotnetPath)) + { + throw new InvalidOperationException($"{DotNetHostPathEnvironmentName} is not set"); + } + + return _dotnetPath; + } + } + + protected override MessageImportance StandardErrorLoggingImportance => MessageImportance.High; + + protected override string ToolName => Path.GetFileName(DotNetPath); + + protected override string GenerateFullPathToTool() => DotNetPath; + + protected override string GenerateCommandLineCommands() + { + var args = new StringBuilder(); + args.Append(Quote(ILLinkPath)); + return args.ToString(); + } + + private static string Quote(string path) + { + return $"\"{path.TrimEnd('\\')}\""; + } + + protected override string GenerateResponseFileCommands() + { + var args = new StringBuilder(); + + if (RootDescriptorFiles != null) + { + foreach (var rootFile in RootDescriptorFiles) + { + args.Append("-x ").AppendLine(Quote(rootFile.ItemSpec)); + } + } + + foreach (var assemblyItem in RootAssemblyNames) + { + args.Append("-a ").AppendLine(Quote(assemblyItem.ItemSpec)); + } + + var assemblyNames = new HashSet(StringComparer.OrdinalIgnoreCase); + foreach (var assembly in AssemblyPaths) + { + var assemblyPath = assembly.ItemSpec; + var assemblyName = Path.GetFileNameWithoutExtension(assemblyPath); + + // If there are multiple paths with the same assembly name, only use the first one. + if (!assemblyNames.Add(assemblyName)) + { + continue; + } + + args.Append("-reference ") + .AppendLine(Quote(assemblyPath)); + + var action = assembly.GetMetadata("action"); + if ((action != null) && (action.Length > 0)) + { + args.Append("-p "); + args.Append(action); + args.Append(" ").AppendLine(Quote(assemblyName)); + } + } + + if (ReferenceAssemblyPaths != null) + { + foreach (var assembly in ReferenceAssemblyPaths) + { + var assemblyPath = assembly.ItemSpec; + var assemblyName = Path.GetFileNameWithoutExtension(assemblyPath); + + // Don't process references for which we already have + // implementation assemblies. + if (assemblyNames.Contains(assemblyName)) + { + continue; + } + + args.Append("-reference ").AppendLine(Quote(assemblyPath)); + + // Treat reference assemblies as "skip". Ideally we + // would not even look at the IL, but only use them to + // resolve surface area. + args.Append("-p skip ").AppendLine(Quote(assemblyName)); + } + } + + if (OutputDirectory != null) + { + args.Append("-out ").AppendLine(Quote(OutputDirectory.ItemSpec)); + } + + if (ClearInitLocals) + { + args.AppendLine("--enable-opt clearinitlocals"); + if ((ClearInitLocalsAssemblies != null) && (ClearInitLocalsAssemblies.Length > 0)) + { + args.Append("-m ClearInitLocalsAssemblies "); + args.AppendLine(ClearInitLocalsAssemblies); + } + } + + if (ExtraArgs != null) + { + args.AppendLine(ExtraArgs); + } + + if (DumpDependencies) + { + args.AppendLine("--dump-dependencies"); + } + + return args.ToString(); + } + + protected override bool HandleTaskExecutionErrors() + { + // Show a slightly better error than the standard ToolTask message that says "dotnet" failed. + Log.LogError($"ILLink failed with exit code {ExitCode}."); + return false; + } + + protected override void LogEventsFromTextOutput(string singleLine, MessageImportance messageImportance) + { + if (!string.IsNullOrEmpty(singleLine) && singleLine.StartsWith("Unhandled exception.", StringComparison.Ordinal)) + { + // The Mono linker currently prints out an entire stack trace when the linker fails. + // We want to show something actionable in the VS Error window. + Log.LogError(singleLine); + } + else + { + base.LogEventsFromTextOutput(singleLine, messageImportance); + } + } + } +} diff --git a/src/Components/Blazor/Build/src/Tasks/GenerateBlazorBootJson.cs b/src/Components/Blazor/Build/src/Tasks/GenerateBlazorBootJson.cs new file mode 100644 index 0000000000..1984de0a57 --- /dev/null +++ b/src/Components/Blazor/Build/src/Tasks/GenerateBlazorBootJson.cs @@ -0,0 +1,86 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.Serialization.Json; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; + +namespace Microsoft.AspNetCore.Blazor.Build +{ + public class GenerateBlazorBootJson : Task + { + [Required] + public string AssemblyPath { get; set; } + + [Required] + public ITaskItem[] References { get; set; } + + [Required] + public bool LinkerEnabled { get; set; } + + [Required] + public string OutputPath { get; set; } + + public override bool Execute() + { + var entryAssemblyName = AssemblyName.GetAssemblyName(AssemblyPath).Name; + var assemblies = References.Select(GetUriPath).OrderBy(c => c, StringComparer.Ordinal).ToArray(); + + using var fileStream = File.Create(OutputPath); + WriteBootJson(fileStream, entryAssemblyName, assemblies, LinkerEnabled); + + return true; + + static string GetUriPath(ITaskItem item) + { + var outputPath = item.GetMetadata("RelativeOutputPath"); + if (string.IsNullOrEmpty(outputPath)) + { + outputPath = Path.GetFileName(item.ItemSpec); + } + + return outputPath.Replace('\\', '/'); + } + } + + internal static void WriteBootJson(Stream stream, string entryAssemblyName, string[] assemblies, bool linkerEnabled) + { + var data = new BootJsonData + { + entryAssembly = entryAssemblyName, + assemblies = assemblies, + linkerEnabled = linkerEnabled, + }; + + var serializer = new DataContractJsonSerializer(typeof(BootJsonData)); + serializer.WriteObject(stream, data); + } + + /// + /// Defines the structure of a Blazor boot JSON file + /// +#pragma warning disable IDE1006 // Naming Styles + public class BootJsonData + { + /// + /// Gets the name of the assembly with the application entry point + /// + public string entryAssembly { get; set; } + + /// + /// Gets the closure of assemblies to be loaded by Blazor WASM. This includes the application entry assembly. + /// + public string[] assemblies { get; set; } + + /// + /// Gets a value that determines if the linker is enabled. + /// + public bool linkerEnabled { get; set; } + } +#pragma warning restore IDE1006 // Naming Styles + } +} diff --git a/src/Components/Blazor/Build/src/Tasks/GenerateTypeGranularityLinkingConfig.cs b/src/Components/Blazor/Build/src/Tasks/GenerateTypeGranularityLinkingConfig.cs new file mode 100644 index 0000000000..8a56b7fc3d --- /dev/null +++ b/src/Components/Blazor/Build/src/Tasks/GenerateTypeGranularityLinkingConfig.cs @@ -0,0 +1,48 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.IO; +using System.Xml.Linq; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; + +namespace Microsoft.AspNetCore.Blazor.Build.Tasks +{ + public class GenerateTypeGranularityLinkingConfig : Task + { + [Required] + public ITaskItem[] Assemblies { get; set; } + + [Required] + public string OutputPath { get; set; } + + public override bool Execute() + { + var linkerElement = new XElement("linker", + new XComment(" THIS IS A GENERATED FILE - DO NOT EDIT MANUALLY ")); + + foreach (var assembly in Assemblies) + { + var assemblyElement = CreateTypeGranularityConfig(assembly); + linkerElement.Add(assemblyElement); + } + + using var fileStream = File.Open(OutputPath, FileMode.Create); + new XDocument(linkerElement).Save(fileStream); + + return true; + } + + private XElement CreateTypeGranularityConfig(ITaskItem assembly) + { + // We match all types in the assembly, and for each one, tell the linker to preserve all + // its members (preserve=all) but only if there's some reference to the type (required=false) + return new XElement("assembly", + new XAttribute("fullname", Path.GetFileNameWithoutExtension(assembly.ItemSpec)), + new XElement("type", + new XAttribute("fullname", "*"), + new XAttribute("preserve", "all"), + new XAttribute("required", "false"))); + } + } +} diff --git a/src/Components/Blazor/Build/src/Tasks/ResolveBlazorRuntimeDependencies.cs b/src/Components/Blazor/Build/src/Tasks/ResolveBlazorRuntimeDependencies.cs new file mode 100644 index 0000000000..1181ea337d --- /dev/null +++ b/src/Components/Blazor/Build/src/Tasks/ResolveBlazorRuntimeDependencies.cs @@ -0,0 +1,203 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Reflection.Metadata; +using System.Reflection.PortableExecutable; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; + +namespace Microsoft.AspNetCore.Blazor.Build +{ + public class ResolveBlazorRuntimeDependencies : Task + { + [Required] + public string EntryPoint { get; set; } + + [Required] + public ITaskItem[] ApplicationDependencies { get; set; } + + [Required] + public ITaskItem[] WebAssemblyBCLAssemblies { get; set; } + + [Output] + public ITaskItem[] Dependencies { get; set; } + + public override bool Execute() + { + var paths = ResolveRuntimeDependenciesCore(EntryPoint, ApplicationDependencies.Select(c => c.ItemSpec), WebAssemblyBCLAssemblies.Select(c => c.ItemSpec)); + Dependencies = paths.Select(p => new TaskItem(p)).ToArray(); + + return true; + } + + public static IEnumerable ResolveRuntimeDependenciesCore( + string entryPoint, + IEnumerable applicationDependencies, + IEnumerable monoBclAssemblies) + { + var entryAssembly = new AssemblyEntry(entryPoint, GetAssemblyName(entryPoint)); + + var dependencies = CreateAssemblyLookup(applicationDependencies); + + var bcl = CreateAssemblyLookup(monoBclAssemblies); + + var assemblyResolutionContext = new AssemblyResolutionContext( + entryAssembly, + dependencies, + bcl); + + assemblyResolutionContext.ResolveAssemblies(); + + var paths = assemblyResolutionContext.Results.Select(r => r.Path); + return paths.Concat(FindPdbs(paths)); + + static Dictionary CreateAssemblyLookup(IEnumerable assemblyPaths) + { + var dictionary = new Dictionary(StringComparer.Ordinal); + foreach (var path in assemblyPaths) + { + var assemblyName = AssemblyName.GetAssemblyName(path).Name; + if (dictionary.TryGetValue(assemblyName, out var previous)) + { + throw new InvalidOperationException($"Multiple assemblies found with the same assembly name '{assemblyName}':" + + Environment.NewLine + string.Join(Environment.NewLine, previous, path)); + } + dictionary[assemblyName] = new AssemblyEntry(path, assemblyName); + } + + return dictionary; + } + } + + private static string GetAssemblyName(string assemblyPath) + { + return AssemblyName.GetAssemblyName(assemblyPath).Name; + } + + private static IEnumerable FindPdbs(IEnumerable dllPaths) + { + return dllPaths + .Select(path => Path.ChangeExtension(path, "pdb")) + .Where(path => File.Exists(path)); + } + + public class AssemblyResolutionContext + { + public AssemblyResolutionContext( + AssemblyEntry entryAssembly, + Dictionary dependencies, + Dictionary bcl) + { + EntryAssembly = entryAssembly; + Dependencies = dependencies; + Bcl = bcl; + } + + public AssemblyEntry EntryAssembly { get; } + public Dictionary Dependencies { get; } + public Dictionary Bcl { get; } + + public IList Results { get; } = new List(); + + internal void ResolveAssemblies() + { + var visitedAssemblies = new HashSet(); + var pendingAssemblies = new Stack(); + pendingAssemblies.Push(EntryAssembly.Name); + ResolveAssembliesCore(); + + void ResolveAssembliesCore() + { + while (pendingAssemblies.Count > 0) + { + var current = pendingAssemblies.Pop(); + if (visitedAssemblies.Add(current)) + { + // Not all references will be resolvable within the Mono BCL. + // Skipping unresolved assemblies here is equivalent to passing "--skip-unresolved true" to the Mono linker. + if (Resolve(current) is AssemblyEntry resolved) + { + Results.Add(resolved); + var references = GetAssemblyReferences(resolved.Path); + foreach (var reference in references) + { + pendingAssemblies.Push(reference); + } + } + } + } + } + + AssemblyEntry? Resolve(string assemblyName) + { + if (EntryAssembly.Name == assemblyName) + { + return EntryAssembly; + } + + // Resolution logic. For right now, we will prefer the mono BCL version of a given + // assembly if there is a candidate assembly and an equivalent mono assembly. + if (Bcl.TryGetValue(assemblyName, out var assembly) || + Dependencies.TryGetValue(assemblyName, out assembly)) + { + return assembly; + } + + return null; + } + + static IReadOnlyList GetAssemblyReferences(string assemblyPath) + { + try + { + using var peReader = new PEReader(File.OpenRead(assemblyPath)); + if (!peReader.HasMetadata) + { + return Array.Empty(); // not a managed assembly + } + + var metadataReader = peReader.GetMetadataReader(); + + var references = new List(); + foreach (var handle in metadataReader.AssemblyReferences) + { + var reference = metadataReader.GetAssemblyReference(handle); + var referenceName = metadataReader.GetString(reference.Name); + + references.Add(referenceName); + } + + return references; + } + catch (BadImageFormatException) + { + // not a PE file, or invalid metadata + } + + return Array.Empty(); // not a managed assembly + } + } + } + + [DebuggerDisplay("{ToString(),nq}")] + public readonly struct AssemblyEntry + { + public AssemblyEntry(string path, string name) + { + Path = path; + Name = name; + } + + public string Path { get; } + public string Name { get; } + + public override string ToString() => Name; + } + } +} diff --git a/src/Components/Blazor/Build/src/targets/All.props b/src/Components/Blazor/Build/src/targets/All.props index 690a29fbab..d1d242f4d9 100644 --- a/src/Components/Blazor/Build/src/targets/All.props +++ b/src/Components/Blazor/Build/src/targets/All.props @@ -4,12 +4,6 @@ $(DefaultWebContentItemExcludes);wwwroot\** - - true - - - true - true diff --git a/src/Components/Blazor/Build/src/targets/All.targets b/src/Components/Blazor/Build/src/targets/All.targets index dd4fbf1b7a..6c69e85a40 100644 --- a/src/Components/Blazor/Build/src/targets/All.targets +++ b/src/Components/Blazor/Build/src/targets/All.targets @@ -6,41 +6,44 @@ - $(MSBuildThisFileDirectory)../tools/ - dotnet "$(BlazorToolsDir)Microsoft.AspNetCore.Blazor.Build.dll" + $(MSBuildThisFileDirectory)..\tools\ + <_BlazorTasksTFM Condition=" '$(MSBuildRuntimeType)' == 'Core'">netcoreapp + <_BlazorTasksTFM Condition=" '$(_BlazorTasksTFM)' == ''">netfx + $(BlazorToolsDir)$(_BlazorTasksTFM)\Microsoft.AspNetCore.Blazor.Build.Tasks.dll true + + + true + - + $(AssemblyName).blazor.config $(TargetDir)$(BlazorMetadataFileName) - - - - + + + <_BlazorConfigContent Include="$(MSBuildProjectFullPath)" /> + <_BlazorConfigContent Include="$(TargetPath)" /> + <_BlazorConfigContent Include="debug:true" Condition="'$(BlazorEnableDebugging)'=='true'" /> + + + + - - - $(GetCurrentProjectStaticWebAssetsDependsOn); - _ClearCurrentStaticWebAssetsForReferenceDiscovery - - - - - - - - - diff --git a/src/Components/Blazor/Build/src/targets/Blazor.MonoRuntime.props b/src/Components/Blazor/Build/src/targets/Blazor.MonoRuntime.props index 03f70748ff..f49c1f8f2f 100644 --- a/src/Components/Blazor/Build/src/targets/Blazor.MonoRuntime.props +++ b/src/Components/Blazor/Build/src/targets/Blazor.MonoRuntime.props @@ -1,22 +1,20 @@ - - $(MSBuildThisFileDirectory)../tools/blazor/blazor.*.js + + $(MSBuildThisFileDirectory)..\tools\blazor\blazor.webassembly.js none - --disable-opt unreachablebodies --verbose --strip-security true --exclude-feature com --exclude-feature sre -v false -c link -u link -b true - dist/ - $(BaseBlazorDistPath)_content/ - $(BaseBlazorDistPath)_framework/ - $(BaseBlazorRuntimeOutputPath)_bin/ - $(BaseBlazorRuntimeOutputPath)wasm/ - $(BaseBlazorRuntimeOutputPath) - blazor/ - wwwroot/ + --disable-opt unreachablebodies --verbose --strip-security true --exclude-feature com -v false -c link -u link -b true + dist\ + $(BaseBlazorDistPath)_content\ + $(BaseBlazorDistPath)_framework\ + $(BaseBlazorRuntimeOutputPath)_bin\ + $(BaseBlazorRuntimeOutputPath)wasm\ + wwwroot\ blazor.boot.json - $(BaseBlazorRuntimeOutputPath)$(BlazorBootJsonName) + <_BlazorBuiltInBclLinkerDescriptor>$(MSBuildThisFileDirectory)BuiltInBclLinkerDescriptor.xml diff --git a/src/Components/Blazor/Build/src/targets/Blazor.MonoRuntime.targets b/src/Components/Blazor/Build/src/targets/Blazor.MonoRuntime.targets index 69976d519b..3c7d126561 100644 --- a/src/Components/Blazor/Build/src/targets/Blazor.MonoRuntime.targets +++ b/src/Components/Blazor/Build/src/targets/Blazor.MonoRuntime.targets @@ -1,653 +1,336 @@ + + true + + + + + $(MonoBaseClassLibraryPath) + $(MonoBaseClassLibraryFacadesPath) + $(MonoWasmRuntimePath) + $(MonoWasmFrameworkPath) + + + + + $(DotNetWebAssemblyArtifactsRoot)\wasm-bcl\wasm\ + $(DotNetWebAssemblyBCLPath)\Facades\ + $(DotNetWebAssemblyArtifactsRoot)\builds\debug\ + $(DotNetWebAssemblyArtifactsRoot)\framework\ + + Condition="'@(BlazorOutputWithTargetPath)' != '' and '$(CopyBuildOutputToOutputDirectory)' == 'true' and '$(SkipCopyBuildProduct)' != 'true'"> - + - - - - - - <_BlazorResolveReferencesDidRun>true - - - - - <_BlazorStatisticsOutput Include="@(BlazorItemOutput->'%(TargetOutputPath)')" /> + <_BlazorStatisticsOutput Include="@(BlazorOutputWithTargetPath->'%(TargetOutputPath)')" /> - - <_BlazorStatisticsReportImportance Condition="'$(BlazorOutputStatistics)' == ''">normal - <_BlazorStatisticsReportImportance Condition="'$(BlazorOutputStatistics)' != ''">high - - - + - - - - - _PrepareBlazorOutputConfiguration; - _DefineBlazorCommonInputs; - _BlazorResolveOutputBinaries; - _GenerateBlazorBootJson; - - - - - - - - - - - - <_BlazorShouldLinkApplicationAssemblies Condition="$(BlazorLinkOnBuild) == 'false'"> - <_BlazorShouldLinkApplicationAssemblies Condition="$(BlazorLinkOnBuild) == 'true'">true - <_BlazorBuiltInBclLinkerDescriptor>$(MSBuildThisFileDirectory)BuiltInBclLinkerDescriptor.xml - - - - - - - $(TargetDir)$(BaseBlazorRuntimeWasmOutputPath)%(FileName)%(Extension) - WebAssembly - true - - - $(TargetDir)$(BaseBlazorJsOutputPath)%(FileName)%(Extension) - BlazorRuntime - true - + + $(BlazorRuntimeWasmOutputPath)%(FileName)%(Extension) + + + $(BaseBlazorRuntimeOutputPath)%(FileName)%(Extension) + - - <_BlazorPackageContentOutput Include="@(BlazorPackageContentFile)" Condition="%(SourcePackage) != ''"> - $(TargetDir)$(BaseBlazorPackageContentOutputPath)%(SourcePackage)\%(RecursiveDir)\%(Filename)%(Extension) - PreserveNewest + $(BaseBlazorPackageContentOutputPath)%(SourcePackage)\%(RecursiveDir)\%(Filename)%(Extension) - + + - - + + - $(IntermediateOutputPath)$(BaseBlazorIntermediateOutputPath) - $([MSBuild]::Escape($([System.IO.Path]::GetFullPath('$([System.IO.Path]::Combine('$(MSBuildProjectDirectory)', '$(BlazorIntermediateOutputPath)'))')))) - - - - - $(BlazorIntermediateOutputPath)inputs.basic.cache - - - $(BlazorIntermediateOutputPath)inputs.copylocal.txt - - - $(BlazorIntermediateOutputPath)inputs.linkerswitch.cache - - - - - $(BlazorIntermediateOutputPath)inputs.linker.cache + $(IntermediateOutputPath)blazor\ $(BlazorIntermediateOutputPath)linker.descriptor.xml + <_TypeGranularityLinkerDescriptor>$(BlazorIntermediateOutputPath)linker.typegranularityconfig.xml + $(BlazorIntermediateOutputPath)linker/ - - $(BlazorIntermediateOutputPath)linked.assemblies.txt - - - - - $(BlazorIntermediateOutputPath)resolvedassemblies/ - - - $(BlazorIntermediateOutputPath)resolved.assemblies.txt - - - - - $(BlazorIntermediateOutputPath) - - $(BlazorBootJsonIntermediateOutputDir)$(BlazorBootJsonName) + $(BlazorIntermediateOutputPath)$(BlazorBootJsonName) - - $(BlazorIntermediateOutputPath)inputs.bootjson.cache - - - $(BlazorIntermediateOutputPath)resolve-dependencies.txt - - - $(BlazorIntermediateOutputPath)bootjson-references.txt - - - $(BlazorIntermediateOutputPath)embedded.resources.txt + <_BlazorLinkerOutputCache>$(BlazorIntermediateOutputPath)linker.output + <_BlazorApplicationAssembliesCacheFile>$(BlazorIntermediateOutputPath)unlinked.output - - $(TargetDir)$(BaseBlazorRuntimeBinOutputPath) - + + <_WebAssemblyBCLFolder Include=" + $(DotNetWebAssemblyBCLPath); + $(DotNetWebAssemblyBCLFacadesPath); + $(DotNetWebAssemblyFrameworkPath)" /> + + <_WebAssemblyBCLAssembly Include="%(_WebAssemblyBCLFolder.Identity)*.dll" /> + + + + + + <_BlazorManagedRuntimeAssemby Include="@(RuntimeCopyLocalItems)" /> + + + <_BlazorUserRuntimeAssembly Include="@(ReferencePath->WithMetadataValue('CopyLocal', 'true'))" /> + <_BlazorUserRuntimeAssembly Include="@(ReferenceDependencyPaths->WithMetadataValue('CopyLocal', 'true'))" /> + + <_BlazorManagedRuntimeAssemby Include="@(_BlazorUserRuntimeAssembly)" /> + <_BlazorManagedRuntimeAssemby Include="@(IntermediateAssembly)" /> + - - - - - - - - - <_BlazorDependencyInput Include="@(ReferenceCopyLocalPaths->WithMetadataValue('Extension','.dll')->'%(FullPath)')" /> - + + - <_BlazorCommonInput Include="@(IntermediateAssembly)" /> - <_BlazorCommonInput Include="@(_BlazorDependencyInput)" /> - <_BlazorCommonInput Include="$(_BlazorShouldLinkApplicationAssemblies)" /> - <_BlazorCommonInput Include="$(BlazorEnableDebugging)" /> - <_BlazorLinkingOption Condition="'$(_BlazorShouldLinkApplicationAssemblies)' == ''" Include="false" /> - <_BlazorLinkingOption Condition="'$(_BlazorShouldLinkApplicationAssemblies)' != ''" Include="true" /> + + <_BlazorCopyLocalPaths Include="@(ReferenceCopyLocalPaths)" /> + <_BlazorCopyLocalPaths Remove="@(_BlazorManagedRuntimeAssemby)" /> + + + true + $(BlazorRuntimeBinOutputPath)%(_BlazorCopyLocalPaths.DestinationSubDirectory)%(FileName)%(Extension) + %(_BlazorCopyLocalPaths.DestinationSubDirectory)%(FileName)%(Extension) + + + + true + $(BlazorRuntimeBinOutputPath)%(FileName)%(Extension) + %(FileName)%(Extension) + - - - - - - - - - - - - - - - - - - - - - - <_CollectLinkerOutputsDependsOn> - _GenerateLinkerDescriptor; - _CollectBlazorLinkerDescriptors; - _LinkBlazorApplication - - - - - - + Name="_ResolveBlazorOutputsWhenLinked" + Condition="'$(BlazorLinkOnBuild)' == 'true'" + DependsOnTargets="_PrepareBlazorLinkerInputs;_GenerateBlazorLinkerDescriptor;_GenerateTypeGranularLinkerDescriptor;_LinkBlazorApplication"> + + + + + + - - $(BlazorRuntimeBinOutputPath)%(FileName)%(Extension) - Assembly - true - - - $(BlazorRuntimeBinOutputPath)%(FileName)%(Extension) - Pdb - - + <_BlazorRuntimeCopyLocalItems Include="@(RuntimeCopyLocalItems)" /> + + + <_BlazorRuntimeCopyLocalItems IsLinkable="true" Condition="$([System.String]::Copy('%(Filename)').StartsWith('System.'))" /> + <_BlazorRuntimeCopyLocalItems IsLinkable="true" TypeGranularity="true" Condition="$([System.String]::Copy('%(Filename)').StartsWith('Microsoft.AspNetCore.'))" /> + <_BlazorRuntimeCopyLocalItems IsLinkable="true" TypeGranularity="true" Condition="$([System.String]::Copy('%(Filename)').StartsWith('Microsoft.Extensions.'))" /> + + <_BlazorAssemblyToLink Include="@(_WebAssemblyBCLAssembly)" /> + <_BlazorAssemblyToLink Include="@(_BlazorRuntimeCopyLocalItems)" Condition="'%(_BlazorRuntimeCopyLocalItems.IsLinkable)' == 'true'" /> + + <_BlazorLinkerRoot Include="@(IntermediateAssembly)" /> + <_BlazorLinkerRoot Include="@(_BlazorUserRuntimeAssembly)" /> + <_BlazorLinkerRoot Include="@(_BlazorRuntimeCopyLocalItems)" Condition="'%(_BlazorRuntimeCopyLocalItems.IsLinkable)' != 'true'" /> - + + Condition="'@(BlazorLinkerDescriptor)' == ''"> + + + + - <_PrepareLinkerDescriptorAssemblyLine Include="@(IntermediateAssembly->'%(FileName)')" /> - <_GeneratedLinkerDescriptorLine Include="<linker>" /> - <_GeneratedLinkerDescriptorLine Include="@(_PrepareLinkerDescriptorAssemblyLine->'<assembly fullname="%(Identity)" />')" /> - <_GeneratedLinkerDescriptorLine Include="</linker>" /> - - - - - - - - - - - + + - - - <_BlazorLinkerInput Include="@(IntermediateAssembly)" /> - <_BlazorLinkerInput Include="@(_BlazorDependencyInput)" /> - <_BlazorLinkerInput Include="@(BlazorLinkerDescriptor)" /> - <_BlazorLinkerInput Include="$(AdditionalMonoLinkerOptions)" /> - - - - - - - - - - - - + + + + + + + + + + + + - - - <_MonoBaseClassLibraryFolder Include="$(MonoBaseClassLibraryPath);$(MonoBaseClassLibraryFacadesPath);$(MonoWasmFrameworkPath)" /> - <_BlazorAssembliesToLink Include="@(_BlazorDependencyInput->'-a "%(Identity)"')" /> - <_BlazorAssembliesToLink Include="@(IntermediateAssembly->'-a "%(FullPath)"')" /> - <_BlazorFolderLookupPaths Include="@(_MonoBaseClassLibraryFolder->'-d "%(Identity)"')" /> - <_BlazorAssemblyDescriptorFiles - Include="@(BlazorLinkerDescriptor->'-x "%(FullPath)"')" Condition="'@(BlazorLinkerDescriptor)' != ''" /> - + Inputs="$(ProjectAssetsFile); + @(_BlazorManagedRuntimeAssemby); + @(BlazorLinkerDescriptor); + $(MSBuildAllProjects)" + Outputs="$(_BlazorLinkerOutputCache)"> <_BlazorLinkerAdditionalOptions>-l $(MonoLinkerI18NAssemblies) $(AdditionalMonoLinkerOptions) - - - - - - - - <_BlazorLinkerOutput Include="$(BlazorIntermediateLinkerOutputPath)*.dll" /> - <_BlazorLinkerOutput Include="$(BlazorIntermediateLinkerOutputPath)*.pdb" /> + <_OldLinkedFile Include="$(BlazorIntermediateLinkerOutputPath)*.dll" /> + <_OldLinkedFile Include="$(BlazorIntermediateLinkerOutputPath)*.pdb" /> + + - + + <_DotNetHostDirectory>$(NetCoreRoot) + <_DotNetHostFileName>dotnet + <_DotNetHostFileName Condition=" '$(OS)' == 'Windows_NT' ">dotnet.exe + + + - - - - + <_LinkerResult Include="$(BlazorIntermediateLinkerOutputPath)*.dll" /> + <_LinkerResult Include="$(BlazorIntermediateLinkerOutputPath)*.pdb" Condition="'$(BlazorEnableDebugging)' == 'true'" /> + + - - - - <_CollectResolvedAssembliesDependsOn> - _ResolveBlazorApplicationAssemblies; - _ReadResolvedBlazorApplicationAssemblies; - _IntermediateCopyBlazorApplicationAssemblies; - _TouchBlazorApplicationAssemblies - - + + + + + Name="_ResolveBlazorRuntimeDependencies" + Inputs="$(ProjectAssetsFile); + @(IntermediateAssembly); + @(_BlazorManagedRuntimeAssemby)" + Outputs="$(_BlazorApplicationAssembliesCacheFile)"> + + + + + + - - $(BlazorRuntimeBinOutputPath)%(FileName)%(Extension) - Assembly - true - - - $(BlazorRuntimeBinOutputPath)%(FileName)%(Extension) - Pdb - - + - - - - <_ReferencesArg Condition="'@(_BlazorDependencyInput)' != ''">--references "$(BlazorResolveDependenciesFilePath)" - <_BclParameter>--base-class-library "$(MonoBaseClassLibraryPath)" --base-class-library "$(MonoBaseClassLibraryFacadesPath)" --base-class-library "$(MonoWasmFrameworkPath)" - - - - - - - - - - - - - - - - <_IntermediateResolvedRuntimeDependencies Include="@(_BlazorResolvedRuntimeDependencies->'$(BlazorIntermediateResolvedApplicationAssembliesOutputPath)%(FileName)%(Extension)')" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - <_UnlinkedAppReferencesPaths Include="@(_BlazorDependencyInput)" /> - <_AppReferences Include="@(BlazorItemOutput->WithMetadataValue('Type','Assembly')->WithMetadataValue('PrimaryOutput','')->'%(FileName)%(Extension)')" /> - <_AppReferences Include="@(BlazorItemOutput->WithMetadataValue('Type','Pdb')->'%(FileName)%(Extension)')" Condition="'$(BlazorEnableDebugging)' == 'true'" /> + <_BlazorRuntimeFile Include="@(BlazorOutputWithTargetPath->WithMetadataValue('BlazorRuntimeFile', 'true'))" /> - - <_LinkerEnabledFlag Condition="'$(_BlazorShouldLinkApplicationAssemblies)' != ''">--linker-enabled - <_ReferencesArg Condition="'@(_AppReferences)' != ''">--references "$(BlazorBootJsonReferencesFilePath)" - <_EmbeddedResourcesArg Condition="'@(_UnlinkedAppReferencesPaths)' != ''">--embedded-resources "$(BlazorEmbeddedResourcesConfigFilePath)" - - + - - - - - - <_BlazorBootJson Include="$(BlazorBootJsonIntermediateOutputPath)" /> - <_BlazorBootJsonEmbeddedContentFile Include="$(BlazorBootJsonIntermediateOutputDir)_content\**\*.*" /> - - $(TargetDir)$(BlazorBootJsonOutputPath) - BootJson - - - $(TargetDir)dist/_content/%(RecursiveDir)%(FileName)%(Extension) - + + - - diff --git a/src/Components/Blazor/Build/src/targets/BuiltInBclLinkerDescriptor.xml b/src/Components/Blazor/Build/src/targets/BuiltInBclLinkerDescriptor.xml index 3275831dca..6f36c72089 100644 --- a/src/Components/Blazor/Build/src/targets/BuiltInBclLinkerDescriptor.xml +++ b/src/Components/Blazor/Build/src/targets/BuiltInBclLinkerDescriptor.xml @@ -2,11 +2,11 @@ + described at https://github.com/mono/linker/blob/master/src/linker/README.md#syntax-of-xml-descriptor --> + to implement timers. Fixes https://github.com/dotnet/blazor/issues/239 --> diff --git a/src/Components/Blazor/Build/src/targets/Publish.targets b/src/Components/Blazor/Build/src/targets/Publish.targets index e431ad1272..7cb7e0ad23 100644 --- a/src/Components/Blazor/Build/src/targets/Publish.targets +++ b/src/Components/Blazor/Build/src/targets/Publish.targets @@ -26,9 +26,8 @@ - <_BlazorGCTPDIDistFiles Include="@(BlazorItemOutput->'%(TargetOutputPath)')" /> - <_BlazorGCTPDI Include="@(_BlazorGCTPDIDistFiles)"> - $(BlazorPublishDistDir)$([MSBuild]::MakeRelative('$(TargetDir)dist\', %(Identity))) + <_BlazorGCTPDI Include="%(BlazorOutputWithTargetPath.Identity)"> + $(AssemblyName)\%(TargetOutputPath) @@ -41,8 +40,17 @@ <_BlazorConfigPath>$(OutDir)$(AssemblyName).blazor.config - - + + + <_BlazorPublishConfigContent Include="." /> + <_BlazorPublishConfigContent Include="$(AssemblyName)/" /> + + + diff --git a/src/Components/Blazor/Build/src/targets/StaticWebAssets.targets b/src/Components/Blazor/Build/src/targets/StaticWebAssets.targets new file mode 100644 index 0000000000..d547f500b1 --- /dev/null +++ b/src/Components/Blazor/Build/src/targets/StaticWebAssets.targets @@ -0,0 +1,37 @@ + + + + + $(ResolveStaticWebAssetsInputsDependsOn); + _RemoveBlazorCurrentProjectAssetsFromStaticWebAssets; + + + + $(GetCurrentProjectStaticWebAssetsDependsOn); + _RemoveBlazorCurrentProjectAssetsFromStaticWebAssets; + + + + + + + + + + + + + + + <_StandaloneExternalPublishStaticWebAsset Include="@(_ExternalPublishStaticWebAsset)" Condition="'%(RelativePath)' != ''"> + $([MSBuild]::MakeRelative('$(MSBuildProjectDirectory)', '$([MSBuild]::NormalizePath('$([System.Text.RegularExpressions.Regex]::Replace('%(RelativePath)','^wwwroot\\?\/?(.*)','$(BlazorPublishDistDir)$1'))'))')) + + + + + + + + + diff --git a/src/Components/Blazor/Build/test/BindRazorIntegrationTest.cs b/src/Components/Blazor/Build/test/BindRazorIntegrationTest.cs index 1fafb9e81d..2292df38d8 100644 --- a/src/Components/Blazor/Build/test/BindRazorIntegrationTest.cs +++ b/src/Components/Blazor/Build/test/BindRazorIntegrationTest.cs @@ -448,7 +448,7 @@ namespace Test frame => AssertFrame.Attribute(frame, "onchange", typeof(EventCallback), 4)); } - [Fact] // See https://github.com/aspnet/Blazor/issues/703 + [Fact] // See https://github.com/dotnet/blazor/issues/703 public void Workaround_703() { // Arrange diff --git a/src/Components/Blazor/Build/test/BlazorCreateRootDescriptorFileTest.cs b/src/Components/Blazor/Build/test/BlazorCreateRootDescriptorFileTest.cs new file mode 100644 index 0000000000..4470546cf0 --- /dev/null +++ b/src/Components/Blazor/Build/test/BlazorCreateRootDescriptorFileTest.cs @@ -0,0 +1,38 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.IO; +using System.Xml.Linq; +using Xunit; + +namespace Microsoft.AspNetCore.Blazor.Build +{ + public class BlazorCreateRootDescriptorFileTest + { + [Fact] + public void ProducesRootDescriptor() + { + // Arrange/Act + using var stream = new MemoryStream(); + + // Act + BlazorCreateRootDescriptorFile.WriteRootDescriptor( + stream, + new[] { "MyApp.dll" }); + + // Assert + stream.Position = 0; + var document = XDocument.Load(stream); + var rootElement = document.Root; + + var assemblyElement = Assert.Single(rootElement.Elements()); + Assert.Equal("assembly", assemblyElement.Name.ToString()); + Assert.Equal("MyApp.dll", assemblyElement.Attribute("fullname").Value); + + var typeElement = Assert.Single(assemblyElement.Elements()); + Assert.Equal("type", typeElement.Name.ToString()); + Assert.Equal("*", typeElement.Attribute("fullname").Value); + Assert.Equal("true", typeElement.Attribute("required").Value); + } + } +} diff --git a/src/Components/Blazor/Build/test/BootJsonWriterTest.cs b/src/Components/Blazor/Build/test/BootJsonWriterTest.cs index 3632a082b7..1e2d89b573 100644 --- a/src/Components/Blazor/Build/test/BootJsonWriterTest.cs +++ b/src/Components/Blazor/Build/test/BootJsonWriterTest.cs @@ -1,62 +1,41 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using System; -using System.Linq; +using System.IO; +using System.Text.Json; +using System.Threading.Tasks; using Xunit; -namespace Microsoft.AspNetCore.Blazor.Build.Test +namespace Microsoft.AspNetCore.Blazor.Build { public class BootJsonWriterTest { [Fact] - public void ProducesJsonReferencingAssemblyAndDependencies() + public async Task ProducesJsonReferencingAssemblyAndDependencies() { // Arrange/Act - var assemblyReferences = new string[] { "System.Abc.dll", "MyApp.ClassLib.dll", }; - var content = BootJsonWriter.GetBootJsonContent( + var assemblyReferences = new string[] { "MyApp.EntryPoint.dll", "System.Abc.dll", "MyApp.ClassLib.dll", }; + using var stream = new MemoryStream(); + + // Act + GenerateBlazorBootJson.WriteBootJson( + stream, "MyApp.Entrypoint.dll", - "MyNamespace.MyType::MyMethod", assemblyReferences, - Enumerable.Empty(), linkerEnabled: true); // Assert - var parsedContent = JsonConvert.DeserializeObject(content); - Assert.Equal("MyApp.Entrypoint.dll", parsedContent["main"].Value()); - Assert.Equal("MyNamespace.MyType::MyMethod", parsedContent["entryPoint"].Value()); - Assert.Equal(assemblyReferences, parsedContent["assemblyReferences"].Values()); - } - - [Fact] - public void IncludesReferencesToEmbeddedContent() - { - // Arrange/Act - var embeddedContent = new[] + stream.Position = 0; + using var parsedContent = await JsonDocument.ParseAsync(stream); + var rootElement = parsedContent.RootElement; + Assert.Equal("MyApp.Entrypoint.dll", rootElement.GetProperty("entryAssembly").GetString()); + var assembliesElement = rootElement.GetProperty("assemblies"); + Assert.Equal(assemblyReferences.Length, assembliesElement.GetArrayLength()); + for (var i = 0; i < assemblyReferences.Length; i++) { - new EmbeddedResourceInfo(EmbeddedResourceKind.Static, "my/static/file"), - new EmbeddedResourceInfo(EmbeddedResourceKind.Css, "css/first.css"), - new EmbeddedResourceInfo(EmbeddedResourceKind.JavaScript, "javascript/first.js"), - new EmbeddedResourceInfo(EmbeddedResourceKind.Css, "css/second.css"), - new EmbeddedResourceInfo(EmbeddedResourceKind.JavaScript, "javascript/second.js"), - }; - var content = BootJsonWriter.GetBootJsonContent( - "MyApp.Entrypoint", - "MyNamespace.MyType::MyMethod", - assemblyReferences: new[] { "Something.dll" }, - embeddedContent: embeddedContent, - linkerEnabled: true); - - // Assert - var parsedContent = JsonConvert.DeserializeObject(content); - Assert.Equal( - new[] { "css/first.css", "css/second.css" }, - parsedContent["cssReferences"].Values()); - Assert.Equal( - new[] { "javascript/first.js", "javascript/second.js" }, - parsedContent["jsReferences"].Values()); + Assert.Equal(assemblyReferences[i], assembliesElement[i].GetString()); + } + Assert.True(rootElement.GetProperty("linkerEnabled").GetBoolean()); } } } diff --git a/src/Components/Blazor/Build/test/BuildIntegrationTests/Assert.cs b/src/Components/Blazor/Build/test/BuildIntegrationTests/Assert.cs new file mode 100644 index 0000000000..8d0aa4b6da --- /dev/null +++ b/src/Components/Blazor/Build/test/BuildIntegrationTests/Assert.cs @@ -0,0 +1,950 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Reflection.Metadata; +using System.Reflection.PortableExecutable; +using System.Text; +using System.Text.RegularExpressions; + +namespace Microsoft.AspNetCore.Blazor.Build +{ + internal class Assert : Xunit.Assert + { + // Matches `{filename}: error {code}: {message} [{project}] + // See https://stackoverflow.com/questions/3441452/msbuild-and-ignorestandarderrorwarningformat/5180353#5180353 + private static readonly Regex ErrorRegex = new Regex(@"^(?'location'.+): error (?'errorcode'[A-Z0-9]+): (?'message'.+) \[(?'project'.+)\]$"); + private static readonly Regex WarningRegex = new Regex(@"^(?'location'.+): warning (?'errorcode'[A-Z0-9]+): (?'message'.+) \[(?'project'.+)\]$"); + private static readonly string[] AllowedBuildWarnings = new[] + { + "MSB3491" , // The process cannot access the file. As long as the build succeeds, we're ok. + "NETSDK1071", // "A PackageReference to 'Microsoft.NETCore.App' specified a Version ..." + }; + + public static void BuildPassed(MSBuildResult result, bool allowWarnings = false) + { + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (result.ExitCode != 0) + { + throw new BuildFailedException(result); + } + + var buildWarnings = GetBuildWarnings(result) + .Where(m => !AllowedBuildWarnings.Contains(m.match.Groups["errorcode"].Value)) + .Select(m => m.line); + + if (!allowWarnings && buildWarnings.Any()) + { + throw new BuildWarningsException(result, buildWarnings); + } + } + + public static void BuildError(MSBuildResult result, string errorCode, string location = null) + { + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + // We don't really need to search line by line, I'm doing this so that it's possible/easy to debug. + var lines = result.Output.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); + for (var i = 0; i < lines.Length; i++) + { + var line = lines[i]; + var match = ErrorRegex.Match(line); + if (match.Success) + { + if (match.Groups["errorcode"].Value != errorCode) + { + continue; + } + + if (location != null && match.Groups["location"].Value.Trim() != location) + { + continue; + } + + // This is a match + return; + } + } + + throw new BuildErrorMissingException(result, errorCode, location); + } + + public static void BuildWarning(MSBuildResult result, string errorCode, string location = null) + { + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + // We don't really need to search line by line, I'm doing this so that it's possible/easy to debug. + foreach (var (_, match) in GetBuildWarnings(result)) + { + if (match.Groups["errorcode"].Value != errorCode) + { + continue; + } + + if (location != null && match.Groups["location"].Value.Trim() != location) + { + continue; + } + + // This is a match + return; + } + + throw new BuildErrorMissingException(result, errorCode, location); + } + + private static IEnumerable<(string line, Match match)> GetBuildWarnings(MSBuildResult result) + { + var lines = result.Output.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); + for (var i = 0; i < lines.Length; i++) + { + var line = lines[i]; + var match = WarningRegex.Match(line); + if (match.Success) + { + yield return (line, match); + } + } + } + + public static void BuildFailed(MSBuildResult result) + { + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + }; + + if (result.ExitCode == 0) + { + throw new BuildPassedException(result); + } + } + + public static void BuildOutputContainsLine(MSBuildResult result, string match) + { + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (match == null) + { + throw new ArgumentNullException(nameof(match)); + } + + // We don't really need to search line by line, I'm doing this so that it's possible/easy to debug. + var lines = result.Output.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); + for (var i = 0; i < lines.Length; i++) + { + var line = lines[i].Trim(); + if (line == match) + { + return; + } + } + + throw new BuildOutputMissingException(result, match); + } + + public static void BuildOutputDoesNotContainLine(MSBuildResult result, string match) + { + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (match == null) + { + throw new ArgumentNullException(nameof(match)); + } + + // We don't really need to search line by line, I'm doing this so that it's possible/easy to debug. + var lines = result.Output.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); + for (var i = 0; i < lines.Length; i++) + { + var line = lines[i].Trim(); + if (line == match) + { + throw new BuildOutputContainsLineException(result, match); + } + } + } + + public static void FileContains(MSBuildResult result, string filePath, string match) + { + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + filePath = Path.Combine(result.Project.DirectoryPath, filePath); + FileExists(result, filePath); + + var text = File.ReadAllText(filePath); + if (text.Contains(match)) + { + return; + } + + throw new FileContentMissingException(result, filePath, File.ReadAllText(filePath), match); + } + + public static void FileDoesNotContain(MSBuildResult result, string filePath, string match) + { + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + filePath = Path.Combine(result.Project.DirectoryPath, filePath); + FileExists(result, filePath); + + var text = File.ReadAllText(filePath); + if (text.Contains(match)) + { + throw new FileContentFoundException(result, filePath, File.ReadAllText(filePath), match); + } + } + + public static void FileContentEquals(MSBuildResult result, string filePath, string expected) + { + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + filePath = Path.Combine(result.Project.DirectoryPath, filePath); + FileExists(result, filePath); + + var actual = File.ReadAllText(filePath); + if (!actual.Equals(expected, StringComparison.Ordinal)) + { + throw new FileContentNotEqualException(result, filePath, expected, actual); + } + } + + public static void FileEquals(MSBuildResult result, string expected, string actual) + { + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + expected = Path.Combine(result.Project.DirectoryPath, expected); + actual = Path.Combine(result.Project.DirectoryPath, actual); + FileExists(result, expected); + FileExists(result, actual); + + if (!Enumerable.SequenceEqual(File.ReadAllBytes(expected), File.ReadAllBytes(actual))) + { + throw new FilesNotEqualException(result, expected, actual); + } + } + + public static void FileContainsLine(MSBuildResult result, string filePath, string match) + { + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + filePath = Path.Combine(result.Project.DirectoryPath, filePath); + FileExists(result, filePath); + + var lines = File.ReadAllLines(filePath); + for (var i = 0; i < lines.Length; i++) + { + var line = lines[i].Trim(); + if (line == match) + { + return; + } + } + + throw new FileContentMissingException(result, filePath, File.ReadAllText(filePath), match); + } + + public static void FileDoesNotContainLine(MSBuildResult result, string filePath, string match) + { + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + filePath = Path.Combine(result.Project.DirectoryPath, filePath); + FileExists(result, filePath); + + var lines = File.ReadAllLines(filePath); + for (var i = 0; i < lines.Length; i++) + { + var line = lines[i].Trim(); + if (line == match) + { + throw new FileContentFoundException(result, filePath, File.ReadAllText(filePath), match); + } + } + } + + public static string FileExists(MSBuildResult result, params string[] paths) + { + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + var filePath = Path.Combine(result.Project.DirectoryPath, Path.Combine(paths)); + if (!File.Exists(filePath)) + { + throw new FileMissingException(result, filePath); + } + + return filePath; + } + + public static void FileCountEquals(MSBuildResult result, int expected, string directoryPath, string searchPattern) + { + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (directoryPath == null) + { + throw new ArgumentNullException(nameof(directoryPath)); + } + + if (searchPattern == null) + { + throw new ArgumentNullException(nameof(searchPattern)); + } + + directoryPath = Path.Combine(result.Project.DirectoryPath, directoryPath); + + if (Directory.Exists(directoryPath)) + { + var files = Directory.GetFiles(directoryPath, searchPattern, SearchOption.AllDirectories); + if (files.Length != expected) + { + throw new FileCountException(result, expected, directoryPath, searchPattern, files); + } + } + else if (expected > 0) + { + // directory doesn't exist, that's OK if we expected to find nothing. + throw new FileCountException(result, expected, directoryPath, searchPattern, Array.Empty()); + } + } + + public static void FileDoesNotExist(MSBuildResult result, params string[] paths) + { + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + var filePath = Path.Combine(result.Project.DirectoryPath, Path.Combine(paths)); + if (File.Exists(filePath)) + { + throw new FileFoundException(result, filePath); + } + } + + public static void NuspecContains(MSBuildResult result, string nuspecPath, string expected) + { + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (nuspecPath == null) + { + throw new ArgumentNullException(nameof(nuspecPath)); + } + + if (expected == null) + { + throw new ArgumentNullException(nameof(expected)); + } + + nuspecPath = Path.Combine(result.Project.DirectoryPath, nuspecPath); + FileExists(result, nuspecPath); + + var content = File.ReadAllText(nuspecPath); + if (!content.Contains(expected)) + { + throw new NuspecException(result, nuspecPath, content, expected); + } + } + + public static void NuspecDoesNotContain(MSBuildResult result, string nuspecPath, string expected) + { + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (nuspecPath == null) + { + throw new ArgumentNullException(nameof(nuspecPath)); + } + + if (expected == null) + { + throw new ArgumentNullException(nameof(expected)); + } + + nuspecPath = Path.Combine(result.Project.DirectoryPath, nuspecPath); + FileExists(result, nuspecPath); + + var content = File.ReadAllText(nuspecPath); + if (content.Contains(expected)) + { + throw new NuspecFoundException(result, nuspecPath, content, expected); + } + } + + // This method extracts the nupkg to a fixed directory path. To avoid the extra work of + // cleaning up after each invocation, this method accepts multiple files. + public static void NupkgContains(MSBuildResult result, string nupkgPath, params string[] filePaths) + { + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (nupkgPath == null) + { + throw new ArgumentNullException(nameof(nupkgPath)); + } + + if (filePaths == null) + { + throw new ArgumentNullException(nameof(filePaths)); + } + + nupkgPath = Path.Combine(result.Project.DirectoryPath, nupkgPath); + FileExists(result, nupkgPath); + + var unzipped = Path.Combine(result.Project.DirectoryPath, Path.GetFileNameWithoutExtension(nupkgPath)); + ZipFile.ExtractToDirectory(nupkgPath, unzipped); + + foreach (var filePath in filePaths) + { + if (!File.Exists(Path.Combine(unzipped, filePath))) + { + throw new NupkgFileMissingException(result, nupkgPath, filePath); + } + } + } + + // This method extracts the nupkg to a fixed directory path. To avoid the extra work of + // cleaning up after each invocation, this method accepts multiple files. + public static void NupkgDoesNotContain(MSBuildResult result, string nupkgPath, params string[] filePaths) + { + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (nupkgPath == null) + { + throw new ArgumentNullException(nameof(nupkgPath)); + } + + if (filePaths == null) + { + throw new ArgumentNullException(nameof(filePaths)); + } + + nupkgPath = Path.Combine(result.Project.DirectoryPath, nupkgPath); + FileExists(result, nupkgPath); + + var unzipped = Path.Combine(result.Project.DirectoryPath, Path.GetFileNameWithoutExtension(nupkgPath)); + ZipFile.ExtractToDirectory(nupkgPath, unzipped); + + foreach (var filePath in filePaths) + { + if (File.Exists(Path.Combine(unzipped, filePath))) + { + throw new NupkgFileFoundException(result, nupkgPath, filePath); + } + } + } + + public static void AssemblyContainsType(MSBuildResult result, string assemblyPath, string fullTypeName) + { + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + assemblyPath = Path.Combine(result.Project.DirectoryPath, Path.Combine(assemblyPath)); + + var typeNames = GetDeclaredTypeNames(assemblyPath); + Assert.Contains(fullTypeName, typeNames); + } + + public static void AssemblyDoesNotContainType(MSBuildResult result, string assemblyPath, string fullTypeName) + { + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + assemblyPath = Path.Combine(result.Project.DirectoryPath, Path.Combine(assemblyPath)); + + var typeNames = GetDeclaredTypeNames(assemblyPath); + Assert.DoesNotContain(fullTypeName, typeNames); + } + + private static IEnumerable GetDeclaredTypeNames(string assemblyPath) + { + using (var file = File.OpenRead(assemblyPath)) + { + var peReader = new PEReader(file); + var metadataReader = peReader.GetMetadataReader(); + return metadataReader.TypeDefinitions.Where(t => !t.IsNil).Select(t => + { + var type = metadataReader.GetTypeDefinition(t); + return metadataReader.GetString(type.Namespace) + "." + metadataReader.GetString(type.Name); + }).ToArray(); + } + } + + public static void AssemblyHasAttribute(MSBuildResult result, string assemblyPath, string fullTypeName) + { + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + assemblyPath = Path.Combine(result.Project.DirectoryPath, Path.Combine(assemblyPath)); + + var typeNames = GetAssemblyAttributes(assemblyPath); + Assert.Contains(fullTypeName, typeNames); + } + + private static IEnumerable GetAssemblyAttributes(string assemblyPath) + { + using (var file = File.OpenRead(assemblyPath)) + { + var peReader = new PEReader(file); + var metadataReader = peReader.GetMetadataReader(); + return metadataReader.CustomAttributes.Where(t => !t.IsNil).Select(t => + { + var attribute = metadataReader.GetCustomAttribute(t); + var constructor = metadataReader.GetMemberReference((MemberReferenceHandle)attribute.Constructor); + var type = metadataReader.GetTypeReference((TypeReferenceHandle)constructor.Parent); + + return metadataReader.GetString(type.Namespace) + "." + metadataReader.GetString(type.Name); + }).ToArray(); + } + } + + private abstract class MSBuildXunitException : Xunit.Sdk.XunitException + { + protected MSBuildXunitException(MSBuildResult result) + { + Result = result; + } + + protected abstract string Heading { get; } + + public MSBuildResult Result { get; } + + public override string Message + { + get + { + var message = new StringBuilder(); + message.AppendLine(Heading); + message.Append(Result.FileName); + message.Append(" "); + message.Append(Result.Arguments); + message.AppendLine(); + message.AppendLine(); + message.Append(Result.Output); + message.AppendLine(); + message.Append("Exit Code:"); + message.Append(Result.ExitCode); + return message.ToString(); + } + } + } + + private class BuildErrorMissingException : MSBuildXunitException + { + public BuildErrorMissingException(MSBuildResult result, string errorCode, string location) + : base(result) + { + ErrorCode = errorCode; + Location = location; + } + + public string ErrorCode { get; } + + public string Location { get; } + + protected override string Heading + { + get + { + return + $"Error code '{ErrorCode}' was not found." + Environment.NewLine + + $"Looking for '{Location ?? ".*"}: error {ErrorCode}: .*'"; + } + } + } + + private class BuildFailedException : MSBuildXunitException + { + public BuildFailedException(MSBuildResult result) + : base(result) + { + } + + protected override string Heading => "Build failed."; + } + + private class BuildWarningsException : MSBuildXunitException + { + public BuildWarningsException(MSBuildResult result, IEnumerable warnings) + : base(result) + { + Warnings = warnings.ToList(); + } + + public List Warnings { get; } + + protected override string Heading => "Build contains unexpected warnings: " + string.Join(Environment.NewLine, Warnings); + } + + private class BuildPassedException : MSBuildXunitException + { + public BuildPassedException(MSBuildResult result) + : base(result) + { + } + + protected override string Heading => "Build should have failed, but it passed."; + } + + private class BuildOutputMissingException : MSBuildXunitException + { + public BuildOutputMissingException(MSBuildResult result, string match) + : base(result) + { + Match = match; + } + + public string Match { get; } + + protected override string Heading => $"Build did not contain the line: '{Match}'."; + } + + private class BuildOutputContainsLineException : MSBuildXunitException + { + public BuildOutputContainsLineException(MSBuildResult result, string match) + : base(result) + { + Match = match; + } + + public string Match { get; } + + protected override string Heading => $"Build output contains the line: '{Match}'."; + } + + private class FileContentFoundException : MSBuildXunitException + { + public FileContentFoundException(MSBuildResult result, string filePath, string content, string match) + : base(result) + { + FilePath = filePath; + Content = content; + Match = match; + } + + public string Content { get; } + + public string FilePath { get; } + + public string Match { get; } + + protected override string Heading + { + get + { + var builder = new StringBuilder(); + builder.AppendFormat("File content of '{0}' should not contain line: '{1}'.", FilePath, Match); + builder.AppendLine(); + builder.AppendLine(); + builder.AppendLine(Content); + return builder.ToString(); + } + } + } + + private class FileContentMissingException : MSBuildXunitException + { + public FileContentMissingException(MSBuildResult result, string filePath, string content, string match) + : base(result) + { + FilePath = filePath; + Content = content; + Match = match; + } + + public string Content { get; } + + public string FilePath { get; } + + public string Match { get; } + + protected override string Heading + { + get + { + var builder = new StringBuilder(); + builder.AppendFormat("File content of '{0}' did not contain the line: '{1}'.", FilePath, Match); + builder.AppendLine(); + builder.AppendLine(); + builder.AppendLine(Content); + return builder.ToString(); + } + } + } + + private class FileContentNotEqualException : MSBuildXunitException + { + public FileContentNotEqualException(MSBuildResult result, string filePath, string expected, string actual) + : base(result) + { + FilePath = filePath; + Expected = expected; + Actual = actual; + } + + public string Actual { get; } + + public string FilePath { get; } + + public string Expected { get; } + + protected override string Heading + { + get + { + var builder = new StringBuilder(); + builder.AppendFormat("File content of '{0}' did not match the expected content: '{1}'.", FilePath, Expected); + builder.AppendLine(); + builder.AppendLine(); + builder.AppendLine(Actual); + return builder.ToString(); + } + } + } + + private class FilesNotEqualException : MSBuildXunitException + { + public FilesNotEqualException(MSBuildResult result, string expected, string actual) + : base(result) + { + Expected = expected; + Actual = actual; + } + + public string Actual { get; } + + public string Expected { get; } + + protected override string Heading + { + get + { + var builder = new StringBuilder(); + builder.AppendFormat("File content of '{0}' did not match the contents of '{1}'.", Expected, Actual); + builder.AppendLine(); + builder.AppendLine(); + builder.AppendLine(Actual); + return builder.ToString(); + } + } + } + + private class FileMissingException : MSBuildXunitException + { + public FileMissingException(MSBuildResult result, string filePath) + : base(result) + { + FilePath = filePath; + } + + public string FilePath { get; } + + protected override string Heading => $"File: '{FilePath}' was not found."; + } + + private class FileCountException : MSBuildXunitException + { + public FileCountException(MSBuildResult result, int expected, string directoryPath, string searchPattern, string[] files) + : base(result) + { + Expected = expected; + DirectoryPath = directoryPath; + SearchPattern = searchPattern; + Files = files; + } + + public string DirectoryPath { get; } + + public int Expected { get; } + + public string[] Files { get; } + + public string SearchPattern { get; } + + protected override string Heading + { + get + { + var heading = new StringBuilder(); + heading.AppendLine($"Expected {Expected} files matching {SearchPattern} in {DirectoryPath}, found {Files.Length}."); + + if (Files.Length > 0) + { + heading.AppendLine("Files:"); + + foreach (var file in Files) + { + heading.Append("\t"); + heading.Append(file); + } + + heading.AppendLine(); + } + + return heading.ToString(); + } + } + } + + private class FileFoundException : MSBuildXunitException + { + public FileFoundException(MSBuildResult result, string filePath) + : base(result) + { + FilePath = filePath; + } + + public string FilePath { get; } + + protected override string Heading => $"File: '{FilePath}' was found, but should not exist."; + } + + private class NuspecException : MSBuildXunitException + { + public NuspecException(MSBuildResult result, string filePath, string content, string expected) + : base(result) + { + FilePath = filePath; + Content = content; + Expected = expected; + } + + public string Content { get; } + + public string Expected { get; } + + public string FilePath { get; } + + protected override string Heading + { + get + { + return + $"nuspec: '{FilePath}' did not contain the expected content." + Environment.NewLine + + Environment.NewLine + + $"expected: {Expected}" + Environment.NewLine + + Environment.NewLine + + $"actual: {Content}"; + } + } + } + + private class NuspecFoundException : MSBuildXunitException + { + public NuspecFoundException(MSBuildResult result, string filePath, string content, string expected) + : base(result) + { + FilePath = filePath; + Content = content; + Expected = expected; + } + + public string Content { get; } + + public string Expected { get; } + + public string FilePath { get; } + + protected override string Heading + { + get + { + return + $"nuspec: '{FilePath}' should not contain the content {Expected}." + + Environment.NewLine + + $"actual content: {Content}"; + } + } + } + + private class NupkgFileMissingException : MSBuildXunitException + { + public NupkgFileMissingException(MSBuildResult result, string nupkgPath, string filePath) + : base(result) + { + NupkgPath = nupkgPath; + FilePath = filePath; + } + + public string FilePath { get; } + + public string NupkgPath { get; } + + protected override string Heading => $"File: '{FilePath}' was not found was not found in {NupkgPath}."; + } + + private class NupkgFileFoundException : MSBuildXunitException + { + public NupkgFileFoundException(MSBuildResult result, string nupkgPath, string filePath) + : base(result) + { + NupkgPath = nupkgPath; + FilePath = filePath; + } + + public string FilePath { get; } + + public string NupkgPath { get; } + + protected override string Heading => $"File: '{FilePath}' was found in {NupkgPath}."; + } + } +} diff --git a/src/Components/Blazor/Build/test/BuildIntegrationTests/BuildIncrementalismTest.cs b/src/Components/Blazor/Build/test/BuildIntegrationTests/BuildIncrementalismTest.cs new file mode 100644 index 0000000000..73d8645029 --- /dev/null +++ b/src/Components/Blazor/Build/test/BuildIntegrationTests/BuildIncrementalismTest.cs @@ -0,0 +1,40 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Threading.Tasks; +using Xunit; + +namespace Microsoft.AspNetCore.Blazor.Build +{ + public class BuildIncrementalismTest + { + [Fact] + public async Task Build_WithLinker_IsIncremental() + { + // Arrange + using var project = ProjectDirectory.Create("standalone"); + var result = await MSBuildProcessManager.DotnetMSBuild(project); + + Assert.BuildPassed(result); + + var buildOutputDirectory = project.BuildOutputDirectory; + + // Act + var thumbPrint = FileThumbPrint.CreateFolderThumbprint(project, project.BuildOutputDirectory); + + // Assert + for (var i = 0; i < 3; i++) + { + result = await MSBuildProcessManager.DotnetMSBuild(project); + Assert.BuildPassed(result); + + var newThumbPrint = FileThumbPrint.CreateFolderThumbprint(project, project.BuildOutputDirectory); + Assert.Equal(thumbPrint.Count, newThumbPrint.Count); + for (var j = 0; j < thumbPrint.Count; j++) + { + Assert.Equal(thumbPrint[j], newThumbPrint[j]); + } + } + } + } +} diff --git a/src/Components/Blazor/Build/test/BuildIntegrationTests/BuildIntegrationTest.cs b/src/Components/Blazor/Build/test/BuildIntegrationTests/BuildIntegrationTest.cs new file mode 100644 index 0000000000..54c089e874 --- /dev/null +++ b/src/Components/Blazor/Build/test/BuildIntegrationTests/BuildIntegrationTest.cs @@ -0,0 +1,135 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.IO; +using System.Threading.Tasks; +using Xunit; + +namespace Microsoft.AspNetCore.Blazor.Build +{ + public class BuildIntegrationTest + { + [Fact] + public async Task Build_WithDefaultSettings_Works() + { + // Arrange + using var project = ProjectDirectory.Create("standalone"); + var result = await MSBuildProcessManager.DotnetMSBuild(project); + + Assert.BuildPassed(result); + + var buildOutputDirectory = project.BuildOutputDirectory; + + Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "blazor.boot.json"); + Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "blazor.webassembly.js"); + Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "wasm", "dotnet.wasm"); + Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "wasm", "dotnet.js"); + Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "standalone.dll"); + Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "Microsoft.Extensions.Logging.Abstractions.dll"); // Verify dependencies are part of the output. + } + + [Fact] + public async Task Build_Hosted_Works() + { + // Arrange + using var project = ProjectDirectory.Create("blazorhosted", additionalProjects: new[] { "standalone", "razorclasslibrary", }); + project.TargetFramework = "netcoreapp5.0"; + var result = await MSBuildProcessManager.DotnetMSBuild(project); + + Assert.BuildPassed(result); + + var buildOutputDirectory = project.BuildOutputDirectory; + var blazorConfig = Path.Combine(buildOutputDirectory, "standalone.blazor.config"); + Assert.FileExists(result, blazorConfig); + + var path = Path.GetFullPath(Path.Combine(project.SolutionPath, "standalone", "bin", project.Configuration, "netstandard2.1", "standalone.dll")); + Assert.FileContains(result, blazorConfig, path); + Assert.FileDoesNotExist(result, buildOutputDirectory, "dist", "_framework", "_bin", "standalone.dll"); + } + + [Fact] + public async Task Build_WithLinkOnBuildDisabled_Works() + { + // Arrange + using var project = ProjectDirectory.Create("standalone"); + project.AddProjectFileContent( +@" + false +"); + + var result = await MSBuildProcessManager.DotnetMSBuild(project); + + Assert.BuildPassed(result); + + var buildOutputDirectory = project.BuildOutputDirectory; + + Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "blazor.boot.json"); + Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "blazor.webassembly.js"); + Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "wasm", "dotnet.wasm"); + Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "wasm", "dotnet.js"); + Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "standalone.dll"); + Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "Microsoft.Extensions.Logging.Abstractions.dll"); // Verify dependencies are part of the output. + } + + [Fact] + public async Task Build_SatelliteAssembliesAreCopiedToBuildOutput() + { + // Arrange + using var project = ProjectDirectory.Create("standalone", additionalProjects: new[] { "razorclasslibrary", "classlibrarywithsatelliteassemblies" }); + project.AddProjectFileContent( +@" + + $(DefineConstants);REFERENCE_classlibrarywithsatelliteassemblies + + + +"); + + var result = await MSBuildProcessManager.DotnetMSBuild(project, args: "/restore"); + + Assert.BuildPassed(result); + + var buildOutputDirectory = project.BuildOutputDirectory; + + Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "standalone.dll"); + Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "classlibrarywithsatelliteassemblies.dll"); + Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "Microsoft.CodeAnalysis.CSharp.dll"); + Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "fr", "Microsoft.CodeAnalysis.CSharp.resources.dll"); // Verify satellite assemblies are present in the build output. + + var bootJsonPath = Path.Combine(buildOutputDirectory, "dist", "_framework", "blazor.boot.json"); + Assert.FileContains(result, bootJsonPath, "\"Microsoft.CodeAnalysis.CSharp.dll\""); + Assert.FileContains(result, bootJsonPath, "\"fr\\/Microsoft.CodeAnalysis.CSharp.resources.dll\""); + } + + [Fact] + public async Task Build_WithBlazorLinkOnBuildFalse_SatelliteAssembliesAreCopiedToBuildOutput() + { + // Arrange + using var project = ProjectDirectory.Create("standalone", additionalProjects: new[] { "razorclasslibrary", "classlibrarywithsatelliteassemblies" }); + project.AddProjectFileContent( +@" + + false + $(DefineConstants);REFERENCE_classlibrarywithsatelliteassemblies + + + +"); + + var result = await MSBuildProcessManager.DotnetMSBuild(project, args: "/restore"); + + Assert.BuildPassed(result); + + var buildOutputDirectory = project.BuildOutputDirectory; + + Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "standalone.dll"); + Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "classlibrarywithsatelliteassemblies.dll"); + Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "Microsoft.CodeAnalysis.CSharp.dll"); + Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "fr", "Microsoft.CodeAnalysis.CSharp.resources.dll"); // Verify satellite assemblies are present in the build output. + + var bootJsonPath = Path.Combine(buildOutputDirectory, "dist", "_framework", "blazor.boot.json"); + Assert.FileContains(result, bootJsonPath, "\"Microsoft.CodeAnalysis.CSharp.dll\""); + Assert.FileContains(result, bootJsonPath, "\"fr\\/Microsoft.CodeAnalysis.CSharp.resources.dll\""); + } + } +} diff --git a/src/Components/Blazor/Build/test/BuildIntegrationTests/FileThumbPrint.cs b/src/Components/Blazor/Build/test/BuildIntegrationTests/FileThumbPrint.cs new file mode 100644 index 0000000000..58b5499e8b --- /dev/null +++ b/src/Components/Blazor/Build/test/BuildIntegrationTests/FileThumbPrint.cs @@ -0,0 +1,74 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Security.Cryptography; + +namespace Microsoft.AspNetCore.Blazor.Build +{ + internal class FileThumbPrint : IEquatable + { + private FileThumbPrint(string path, DateTime lastWriteTimeUtc, string hash) + { + FilePath = path; + LastWriteTimeUtc = lastWriteTimeUtc; + Hash = hash; + } + + public string FilePath { get; } + + public DateTime LastWriteTimeUtc { get; } + + public string Hash { get; } + + public override string ToString() + { + return $"{Path.GetFileName(FilePath)}, {LastWriteTimeUtc.ToString("u")}, {Hash}"; + } + + /// + /// Returns a list of thumbprints for all files (recursive) in the specified directory, sorted by file paths. + /// + public static List CreateFolderThumbprint(ProjectDirectory project, string directoryPath, params string[] filesToIgnore) + { + directoryPath = Path.Combine(project.DirectoryPath, directoryPath); + var files = Directory.GetFiles(directoryPath).Where(p => !filesToIgnore.Contains(p)); + var thumbprintLookup = new List(); + foreach (var file in files) + { + var thumbprint = Create(file); + thumbprintLookup.Add(thumbprint); + } + + thumbprintLookup.Sort(Comparer.Create((a, b) => StringComparer.Ordinal.Compare(a.FilePath, b.FilePath))); + return thumbprintLookup; + } + + public static FileThumbPrint Create(string path) + { + byte[] hashBytes; + using (var sha1 = SHA1.Create()) + using (var fileStream = File.OpenRead(path)) + { + hashBytes = sha1.ComputeHash(fileStream); + } + + var hash = Convert.ToBase64String(hashBytes); + var lastWriteTimeUtc = File.GetLastWriteTimeUtc(path); + return new FileThumbPrint(path, lastWriteTimeUtc, hash); + } + + public bool Equals(FileThumbPrint other) + { + return + string.Equals(FilePath, other.FilePath, StringComparison.Ordinal) && + LastWriteTimeUtc == other.LastWriteTimeUtc && + string.Equals(Hash, other.Hash, StringComparison.Ordinal); + } + + public override int GetHashCode() => LastWriteTimeUtc.GetHashCode(); + } +} diff --git a/src/Components/Blazor/Build/test/BuildIntegrationTests/MSBuildProcessManager.cs b/src/Components/Blazor/Build/test/BuildIntegrationTests/MSBuildProcessManager.cs new file mode 100644 index 0000000000..b7e16ca072 --- /dev/null +++ b/src/Components/Blazor/Build/test/BuildIntegrationTests/MSBuildProcessManager.cs @@ -0,0 +1,282 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.CommandLineUtils; + +namespace Microsoft.AspNetCore.Blazor.Build +{ + internal static class MSBuildProcessManager + { + public static Task DotnetMSBuild( + ProjectDirectory project, + string target = "Build", + string args = null) + { + var buildArgumentList = new List + { + // Disable node-reuse. We don't want msbuild processes to stick around + // once the test is completed. + "/nr:false", + + // Always generate a bin log for debugging purposes + "/bl", + }; + + buildArgumentList.Add($"/t:{target}"); + buildArgumentList.Add($"/p:Configuration={project.Configuration}"); + buildArgumentList.Add(args); + + var buildArguments = string.Join(" ", buildArgumentList); + + return MSBuildProcessManager.RunProcessAsync(project, buildArguments); + } + + public static async Task RunProcessAsync( + ProjectDirectory project, + string arguments, + TimeSpan? timeout = null) + { + var processStartInfo = new ProcessStartInfo() + { + WorkingDirectory = project.DirectoryPath, + UseShellExecute = false, + RedirectStandardError = true, + RedirectStandardOutput = true, + }; + + processStartInfo.FileName = DotNetMuxer.MuxerPathOrDefault(); + processStartInfo.Arguments = $"msbuild {arguments}"; + + // Suppresses the 'Welcome to .NET Core!' output that times out tests and causes locked file issues. + // When using dotnet we're not guarunteed to run in an environment where the dotnet.exe has had its first run experience already invoked. + processStartInfo.EnvironmentVariables["DOTNET_SKIP_FIRST_TIME_EXPERIENCE"] = "true"; + + var processResult = await RunProcessCoreAsync(processStartInfo, timeout); + + return new MSBuildResult(project, processResult.FileName, processResult.Arguments, processResult.ExitCode, processResult.Output); + } + + internal static Task RunProcessCoreAsync( + ProcessStartInfo processStartInfo, + TimeSpan? timeout = null) + { + timeout = timeout ?? TimeSpan.FromSeconds(5 * 60); + + var process = new Process() + { + StartInfo = processStartInfo, + EnableRaisingEvents = true, + }; + + var output = new StringBuilder(); + var outputLock = new object(); + + var diagnostics = new StringBuilder(); + diagnostics.AppendLine("Process execution diagnostics:"); + + process.ErrorDataReceived += Process_ErrorDataReceived; + process.OutputDataReceived += Process_OutputDataReceived; + + process.Start(); + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); + + var timeoutTask = GetTimeoutForProcess(process, timeout, diagnostics); + + var waitTask = Task.Run(() => + { + // We need to use two WaitForExit calls to ensure that all of the output/events are processed. Previously + // this code used Process.Exited, which could result in us missing some output due to the ordering of + // events. + // + // See the remarks here: https://msdn.microsoft.com/en-us/library/ty0d8k56(v=vs.110).aspx + if (!process.WaitForExit(Int32.MaxValue)) + { + // unreachable - the timeoutTask will kill the process before this happens. + throw new TimeoutException(); + } + + process.WaitForExit(); + + + string outputString; + lock (outputLock) + { + // This marks the end of the diagnostic info which we collect when something goes wrong. + diagnostics.AppendLine("Process output:"); + + // Expected output + // Process execution diagnostics: + // ... + // Process output: + outputString = diagnostics.ToString(); + outputString += output.ToString(); + } + + var result = new ProcessResult(process.StartInfo.FileName, process.StartInfo.Arguments, process.ExitCode, outputString); + return result; + }); + + return Task.WhenAny(waitTask, timeoutTask).Unwrap(); + + void Process_ErrorDataReceived(object sender, DataReceivedEventArgs e) + { + lock (outputLock) + { + output.AppendLine(e.Data); + } + } + + void Process_OutputDataReceived(object sender, DataReceivedEventArgs e) + { + lock (outputLock) + { + output.AppendLine(e.Data); + } + } + + async Task GetTimeoutForProcess(Process process, TimeSpan? timeout, StringBuilder diagnostics) + { + await Task.Delay(timeout.Value); + + // Don't timeout during debug sessions + while (Debugger.IsAttached) + { + Thread.Sleep(TimeSpan.FromSeconds(1)); + } + if (!process.HasExited) + { + await CollectDumps(process, timeout, diagnostics); + + // This is a timeout. + process.Kill(); + } + + throw new TimeoutException($"command '${process.StartInfo.FileName} {process.StartInfo.Arguments}' timed out after {timeout}. Output: {output.ToString()}"); + } + + static async Task CollectDumps(Process process, TimeSpan? timeout, StringBuilder diagnostics) + { + var procDumpProcess = await CaptureDump(process, timeout, diagnostics); + var allDotNetProcesses = Process.GetProcessesByName("dotnet"); + + var allDotNetChildProcessCandidates = allDotNetProcesses + .Where(p => p.StartTime >= process.StartTime && p.Id != process.Id); + + if (!allDotNetChildProcessCandidates.Any()) + { + diagnostics.AppendLine("Couldn't find any candidate child process."); + foreach (var dotnetProcess in allDotNetProcesses) + { + diagnostics.AppendLine($"Found dotnet process with PID {dotnetProcess.Id} and start time {dotnetProcess.StartTime}."); + } + } + + foreach (var childProcess in allDotNetChildProcessCandidates) + { + diagnostics.AppendLine($"Found child process candidate '{childProcess.Id}'."); + } + + var childrenProcessDumpProcesses = await Task.WhenAll(allDotNetChildProcessCandidates.Select(d => CaptureDump(d, timeout, diagnostics))); + foreach (var childProcess in childrenProcessDumpProcesses) + { + if (childProcess != null && childProcess.HasExited) + { + diagnostics.AppendLine($"ProcDump failed to run for child dotnet process candidate '{process.Id}'."); + childProcess.Kill(); + } + } + + if (procDumpProcess != null && procDumpProcess.HasExited) + { + diagnostics.AppendLine($"ProcDump failed to run for '{process.Id}'."); + procDumpProcess.Kill(); + } + } + + static async Task CaptureDump(Process process, TimeSpan? timeout, StringBuilder diagnostics) + { + var metadataAttributes = Assembly.GetExecutingAssembly() + .GetCustomAttributes(); + + var procDumpPath = metadataAttributes + .SingleOrDefault(ama => ama.Key == "ProcDumpToolPath")?.Value; + + if (string.IsNullOrEmpty(procDumpPath)) + { + diagnostics.AppendLine("ProcDumpPath not defined."); + return null; + } + var procDumpExePath = Path.Combine(procDumpPath, "procdump.exe"); + if (!File.Exists(procDumpExePath)) + { + diagnostics.AppendLine($"Can't find procdump.exe in '{procDumpPath}'."); + return null; + } + + var dumpDirectory = metadataAttributes + .SingleOrDefault(ama => ama.Key == "ArtifactsLogDir")?.Value; + + if (string.IsNullOrEmpty(dumpDirectory)) + { + diagnostics.AppendLine("ArtifactsLogDir not defined."); + return null; + } + + if (!Directory.Exists(dumpDirectory)) + { + diagnostics.AppendLine($"'{dumpDirectory}' does not exist."); + return null; + } + + var procDumpPattern = Path.Combine(dumpDirectory, "HangingProcess_PROCESSNAME_PID_YYMMDD_HHMMSS.dmp"); + var procDumpStartInfo = new ProcessStartInfo( + procDumpExePath, + $"-accepteula -ma {process.Id} {procDumpPattern}") + { + CreateNoWindow = true, + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true + }; + + var procDumpProcess = Process.Start(procDumpStartInfo); + var tcs = new TaskCompletionSource(); + + procDumpProcess.Exited += (s, a) => tcs.TrySetResult(null); + procDumpProcess.EnableRaisingEvents = true; + + await Task.WhenAny(tcs.Task, Task.Delay(timeout ?? TimeSpan.FromSeconds(30))); + return procDumpProcess; + } + } + + internal class ProcessResult + { + public ProcessResult(string fileName, string arguments, int exitCode, string output) + { + FileName = fileName; + Arguments = arguments; + ExitCode = exitCode; + Output = output; + } + + public string Arguments { get; } + + public string FileName { get; } + + public int ExitCode { get; } + + public string Output { get; } + } + } +} diff --git a/src/Components/Blazor/Build/test/BuildIntegrationTests/MSBuildResult.cs b/src/Components/Blazor/Build/test/BuildIntegrationTests/MSBuildResult.cs new file mode 100644 index 0000000000..9a83df922b --- /dev/null +++ b/src/Components/Blazor/Build/test/BuildIntegrationTests/MSBuildResult.cs @@ -0,0 +1,28 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.AspNetCore.Blazor.Build +{ + internal class MSBuildResult + { + public MSBuildResult(ProjectDirectory project, string fileName, string arguments, int exitCode, string output) + { + Project = project; + FileName = fileName; + Arguments = arguments; + ExitCode = exitCode; + Output = output; + } + + public ProjectDirectory Project { get; } + + public string Arguments { get; } + + public string FileName { get; } + + public int ExitCode { get; } + + public string Output { get; } + } + +} diff --git a/src/Components/Blazor/Build/test/BuildIntegrationTests/ProjectDirectory.cs b/src/Components/Blazor/Build/test/BuildIntegrationTests/ProjectDirectory.cs new file mode 100644 index 0000000000..e50b750ae4 --- /dev/null +++ b/src/Components/Blazor/Build/test/BuildIntegrationTests/ProjectDirectory.cs @@ -0,0 +1,211 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Threading; + +namespace Microsoft.AspNetCore.Blazor.Build +{ + internal class ProjectDirectory : IDisposable + { + public bool PreserveWorkingDirectory { get; set; } = false; + + private static readonly string RepoRoot = GetTestAttribute("Testing.RepoRoot"); + + public static ProjectDirectory Create(string projectName, string baseDirectory = "", string[] additionalProjects = null) + { + var destinationPath = Path.Combine(Path.GetTempPath(), "BlazorBuild", baseDirectory, Path.GetRandomFileName()); + Directory.CreateDirectory(destinationPath); + + try + { + if (Directory.EnumerateFiles(destinationPath).Any()) + { + throw new InvalidOperationException($"{destinationPath} should be empty"); + } + + if (string.IsNullOrEmpty(RepoRoot)) + { + throw new InvalidOperationException("RepoRoot was not specified."); + } + + var testAppsRoot = Path.Combine(RepoRoot, "src", "Components", "Blazor", "Build", "testassets"); + foreach (var project in new string[] { projectName, }.Concat(additionalProjects ?? Array.Empty())) + { + var projectRoot = Path.Combine(testAppsRoot, project); + if (!Directory.Exists(projectRoot)) + { + throw new InvalidOperationException($"Could not find project at '{projectRoot}'"); + } + + var projectDestination = Path.Combine(destinationPath, project); + var projectDestinationDir = Directory.CreateDirectory(projectDestination); + CopyDirectory(new DirectoryInfo(projectRoot), projectDestinationDir); + SetupDirectoryBuildFiles(RepoRoot, testAppsRoot, projectDestination); + } + + var directoryPath = Path.Combine(destinationPath, projectName); + var projectPath = Path.Combine(directoryPath, projectName + ".csproj"); + + CopyRepositoryAssets(destinationPath); + + return new ProjectDirectory( + destinationPath, + directoryPath, + projectPath); + } + catch + { + CleanupDirectory(destinationPath); + throw; + } + + static void CopyDirectory(DirectoryInfo source, DirectoryInfo destination, bool recursive = true) + { + foreach (var file in source.EnumerateFiles()) + { + file.CopyTo(Path.Combine(destination.FullName, file.Name)); + } + + if (!recursive) + { + return; + } + + foreach (var directory in source.EnumerateDirectories()) + { + if (directory.Name == "bin") + { + // Just in case someone has opened the project in an IDE or built it. We don't want to copy + // these folders. + continue; + } + + var created = destination.CreateSubdirectory(directory.Name); + if (directory.Name == "obj") + { + // Copy NuGet restore assets (viz all the files at the root of the obj directory, but stop there.) + CopyDirectory(directory, created, recursive: false); + } + else + { + CopyDirectory(directory, created); + } + } + } + + static void SetupDirectoryBuildFiles(string repoRoot, string testAppsRoot, string projectDestination) + { + var beforeDirectoryPropsContent = +$@" + + {repoRoot} + +"; + File.WriteAllText(Path.Combine(projectDestination, "Before.Directory.Build.props"), beforeDirectoryPropsContent); + + new List { "Directory.Build.props", "Directory.Build.targets", } + .ForEach(file => + { + var source = Path.Combine(testAppsRoot, file); + var destination = Path.Combine(projectDestination, file); + File.Copy(source, destination); + }); + } + + static void CopyRepositoryAssets(string projectRoot) + { + const string GlobalJsonFileName = "global.json"; + var globalJsonPath = Path.Combine(RepoRoot, GlobalJsonFileName); + + var destinationFile = Path.Combine(projectRoot, GlobalJsonFileName); + File.Copy(globalJsonPath, destinationFile); + } + } + + protected ProjectDirectory(string solutionPath, string directoryPath, string projectFilePath) + { + SolutionPath = solutionPath; + DirectoryPath = directoryPath; + ProjectFilePath = projectFilePath; + } + + public string DirectoryPath { get; } + + public string ProjectFilePath { get; } + + public string SolutionPath { get; } + + public string TargetFramework { get; set; } = "netstandard2.1"; + +#if DEBUG + public string Configuration => "Debug"; +#elif RELEASE + public string Configuration => "Release"; +#else +#error Configuration not supported +#endif + + public string IntermediateOutputDirectory => Path.Combine("obj", Configuration, TargetFramework); + + public string BuildOutputDirectory => Path.Combine("bin", Configuration, TargetFramework); + + public string PublishOutputDirectory => Path.Combine(BuildOutputDirectory, "publish"); + + internal void AddProjectFileContent(string content) + { + if (content == null) + { + throw new ArgumentNullException(nameof(content)); + } + + var existing = File.ReadAllText(ProjectFilePath); + var updated = existing.Replace("", content); + File.WriteAllText(ProjectFilePath, updated); + } + + public void Dispose() + { + if (PreserveWorkingDirectory) + { + Console.WriteLine($"Skipping deletion of working directory {SolutionPath}"); + } + else + { + CleanupDirectory(SolutionPath); + } + } + + internal static void CleanupDirectory(string filePath) + { + var tries = 5; + var sleep = TimeSpan.FromSeconds(3); + + for (var i = 0; i < tries; i++) + { + try + { + Directory.Delete(filePath, recursive: true); + return; + } + catch when (i < tries - 1) + { + Console.WriteLine($"Failed to delete directory {filePath}, trying again."); + Thread.Sleep(sleep); + } + } + } + + private static string GetTestAttribute(string key) + { + return typeof(ProjectDirectory).Assembly + .GetCustomAttributes() + .FirstOrDefault(f => f.Key == key) + ?.Value; + } + } +} diff --git a/src/Components/Blazor/Build/test/BuildIntegrationTests/ProjectDirectoryTest.cs b/src/Components/Blazor/Build/test/BuildIntegrationTests/ProjectDirectoryTest.cs new file mode 100644 index 0000000000..23badb4296 --- /dev/null +++ b/src/Components/Blazor/Build/test/BuildIntegrationTests/ProjectDirectoryTest.cs @@ -0,0 +1,21 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Xunit; + +namespace Microsoft.AspNetCore.Blazor.Build +{ + public class ProjectDirectoryTest + { + [Fact] + public void ProjectDirectory_IsNotSetToBePreserved() + { + // Arrange + using var project = ProjectDirectory.Create("standalone"); + + // Act & Assert + // This flag is only meant for local debugging and should not be set when checking in. + Assert.False(project.PreserveWorkingDirectory); + } + } +} diff --git a/src/Components/Blazor/Build/test/BuildIntegrationTests/PublishIntegrationTest.cs b/src/Components/Blazor/Build/test/BuildIntegrationTests/PublishIntegrationTest.cs new file mode 100644 index 0000000000..81f9577e25 --- /dev/null +++ b/src/Components/Blazor/Build/test/BuildIntegrationTests/PublishIntegrationTest.cs @@ -0,0 +1,224 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.IO; +using System.Threading.Tasks; +using Xunit; + +namespace Microsoft.AspNetCore.Blazor.Build +{ + public class PublishIntegrationTest + { + [Fact] + public async Task Publish_WithDefaultSettings_Works() + { + // Arrange + using var project = ProjectDirectory.Create("standalone", additionalProjects: new [] { "razorclasslibrary" }); + var result = await MSBuildProcessManager.DotnetMSBuild(project, "Publish"); + + Assert.BuildPassed(result); + + var publishDirectory = project.PublishOutputDirectory; + var blazorPublishDirectory = Path.Combine(publishDirectory, Path.GetFileNameWithoutExtension(project.ProjectFilePath)); + + Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "blazor.boot.json"); + Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "blazor.webassembly.js"); + Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "wasm", "dotnet.wasm"); + Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "wasm", "dotnet.js"); + Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "_bin", "standalone.dll"); + Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "_bin", "Microsoft.Extensions.Logging.Abstractions.dll"); // Verify dependencies are part of the output. + + // Verify referenced static web assets + Assert.FileExists(result, blazorPublishDirectory, "dist", "_content", "RazorClassLibrary", "wwwroot", "exampleJsInterop.js"); + Assert.FileExists(result, blazorPublishDirectory, "dist", "_content", "RazorClassLibrary", "styles.css"); + + // Verify static assets are in the publish directory + Assert.FileExists(result, blazorPublishDirectory, "dist", "index.html"); + + // Verify web.config + Assert.FileExists(result, publishDirectory, "web.config"); + } + + [Fact] + public async Task Publish_WithNoBuild_Works() + { + // Arrange + using var project = ProjectDirectory.Create("standalone", additionalProjects: new[] { "razorclasslibrary" }); + var result = await MSBuildProcessManager.DotnetMSBuild(project, "Build"); + + Assert.BuildPassed(result); + + result = await MSBuildProcessManager.DotnetMSBuild(project, "Publish", "/p:NoBuild=true"); + + Assert.BuildPassed(result); + + var publishDirectory = project.PublishOutputDirectory; + var blazorPublishDirectory = Path.Combine(publishDirectory, Path.GetFileNameWithoutExtension(project.ProjectFilePath)); + + Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "blazor.boot.json"); + Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "blazor.webassembly.js"); + Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "wasm", "dotnet.wasm"); + Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "wasm", "dotnet.js"); + Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "_bin", "standalone.dll"); + Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "_bin", "Microsoft.Extensions.Logging.Abstractions.dll"); // Verify dependencies are part of the output. + + // Verify static assets are in the publish directory + Assert.FileExists(result, blazorPublishDirectory, "dist", "index.html"); + + // Verify static web assets from referenced projects are copied. + // Uncomment once https://github.com/dotnet/aspnetcore/issues/17426 is resolved. + // Assert.FileExists(result, blazorPublishDirectory, "dist", "_content", "RazorClassLibrary", "wwwroot", "exampleJsInterop.js"); + // Assert.FileExists(result, blazorPublishDirectory, "dist", "_content", "RazorClassLibrary", "styles.css"); + + // Verify static assets are in the publish directory + Assert.FileExists(result, blazorPublishDirectory, "dist", "index.html"); + + // Verify web.config + Assert.FileExists(result, publishDirectory, "web.config"); + } + + [Fact] + public async Task Publish_WithLinkOnBuildDisabled_Works() + { + // Arrange + using var project = ProjectDirectory.Create("standalone", additionalProjects: new [] { "razorclasslibrary" }); + project.AddProjectFileContent( +@" + false +"); + + var result = await MSBuildProcessManager.DotnetMSBuild(project, "Publish"); + + Assert.BuildPassed(result); + + var publishDirectory = project.PublishOutputDirectory; + var blazorPublishDirectory = Path.Combine(publishDirectory, Path.GetFileNameWithoutExtension(project.ProjectFilePath)); + + Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "blazor.boot.json"); + Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "blazor.webassembly.js"); + Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "wasm", "dotnet.wasm"); + Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "wasm", "dotnet.js"); + Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "_bin", "standalone.dll"); + Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "_bin", "Microsoft.Extensions.Logging.Abstractions.dll"); // Verify dependencies are part of the output. + + // Verify static assets are in the publish directory + Assert.FileExists(result, blazorPublishDirectory, "dist", "index.html"); + + // Verify referenced static web assets + Assert.FileExists(result, blazorPublishDirectory, "dist", "_content", "RazorClassLibrary", "wwwroot", "exampleJsInterop.js"); + Assert.FileExists(result, blazorPublishDirectory, "dist", "_content", "RazorClassLibrary", "styles.css"); + + // Verify web.config + Assert.FileExists(result, publishDirectory, "web.config"); + } + + [Fact] + public async Task Publish_SatelliteAssemblies_AreCopiedToBuildOutput() + { + // Arrange + using var project = ProjectDirectory.Create("standalone", additionalProjects: new[] { "razorclasslibrary", "classlibrarywithsatelliteassemblies" }); + project.AddProjectFileContent( +@" + + $(DefineConstants);REFERENCE_classlibrarywithsatelliteassemblies + + + +"); + + var result = await MSBuildProcessManager.DotnetMSBuild(project, "Publish", args: "/restore"); + + Assert.BuildPassed(result); + + var publishDirectory = project.PublishOutputDirectory; + var blazorPublishDirectory = Path.Combine(publishDirectory, Path.GetFileNameWithoutExtension(project.ProjectFilePath)); + + Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "_bin", "Microsoft.CodeAnalysis.CSharp.dll"); + Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "_bin", "fr", "Microsoft.CodeAnalysis.CSharp.resources.dll"); // Verify satellite assemblies are present in the build output. + + var bootJsonPath = Path.Combine(blazorPublishDirectory, "dist", "_framework", "blazor.boot.json"); + Assert.FileContains(result, bootJsonPath, "\"Microsoft.CodeAnalysis.CSharp.dll\""); + Assert.FileContains(result, bootJsonPath, "\"fr\\/Microsoft.CodeAnalysis.CSharp.resources.dll\""); + } + + [Fact] + public async Task Publish_HostedApp_Works() + { + // Arrange + using var project = ProjectDirectory.Create("blazorhosted", additionalProjects: new[] { "standalone", "razorclasslibrary", }); + project.TargetFramework = "netcoreapp5.0"; + var result = await MSBuildProcessManager.DotnetMSBuild(project, "Publish"); + + Assert.BuildPassed(result); + + var publishDirectory = project.PublishOutputDirectory; + // Make sure the main project exists + Assert.FileExists(result, publishDirectory, "blazorhosted.dll"); + + var blazorPublishDirectory = Path.Combine(publishDirectory, "standalone"); + Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "blazor.boot.json"); + Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "blazor.webassembly.js"); + Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "wasm", "dotnet.wasm"); + Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "wasm", "dotnet.js"); + Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "_bin", "standalone.dll"); + Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "_bin", "Microsoft.Extensions.Logging.Abstractions.dll"); // Verify dependencies are part of the output. + + // Verify static assets are in the publish directory + Assert.FileExists(result, blazorPublishDirectory, "dist", "index.html"); + + // Verify static web assets from referenced projects are copied. + Assert.FileExists(result, publishDirectory, "wwwroot", "_content", "RazorClassLibrary", "wwwroot", "exampleJsInterop.js"); + Assert.FileExists(result, publishDirectory, "wwwroot", "_content", "RazorClassLibrary", "styles.css"); + + // Verify static assets are in the publish directory + Assert.FileExists(result, blazorPublishDirectory, "dist", "index.html"); + + // Verify web.config + Assert.FileExists(result, publishDirectory, "web.config"); + + var blazorConfig = Path.Combine(result.Project.DirectoryPath, publishDirectory, "standalone.blazor.config"); + var blazorConfigLines = File.ReadAllLines(blazorConfig); + Assert.Equal(".", blazorConfigLines[0]); + Assert.Equal("standalone/", blazorConfigLines[1]); + } + + [Fact] + public async Task Publish_HostedApp_WithNoBuild_Works() + { + // Arrange + using var project = ProjectDirectory.Create("blazorhosted", additionalProjects: new[] { "standalone", "razorclasslibrary", }); + project.TargetFramework = "netcoreapp5.0"; + var result = await MSBuildProcessManager.DotnetMSBuild(project, "Build"); + + Assert.BuildPassed(result); + + result = await MSBuildProcessManager.DotnetMSBuild(project, "Publish", "/p:NoBuild=true"); + + var publishDirectory = project.PublishOutputDirectory; + // Make sure the main project exists + Assert.FileExists(result, publishDirectory, "blazorhosted.dll"); + + var blazorPublishDirectory = Path.Combine(publishDirectory, "standalone"); + Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "blazor.boot.json"); + Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "blazor.webassembly.js"); + Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "wasm", "dotnet.wasm"); + Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "wasm", "dotnet.js"); + Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "_bin", "standalone.dll"); + Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "_bin", "Microsoft.Extensions.Logging.Abstractions.dll"); // Verify dependencies are part of the output. + + // Verify static assets are in the publish directory + Assert.FileExists(result, blazorPublishDirectory, "dist", "index.html"); + + // Verify static web assets from referenced projects are copied. + // Uncomment once https://github.com/dotnet/aspnetcore/issues/17426 is resolved. + // Assert.FileExists(result, publishDirectory, "wwwroot", "_content", "RazorClassLibrary", "wwwroot", "exampleJsInterop.js"); + // Assert.FileExists(result, publishDirectory, "wwwroot", "_content", "RazorClassLibrary", "styles.css"); + + // Verify static assets are in the publish directory + Assert.FileExists(result, blazorPublishDirectory, "dist", "index.html"); + + // Verify web.config + Assert.FileExists(result, publishDirectory, "web.config"); + } + } +} diff --git a/src/Components/Blazor/Build/test/ComponentRenderingRazorIntegrationTest.cs b/src/Components/Blazor/Build/test/ComponentRenderingRazorIntegrationTest.cs index d15cf4f584..f4a45c7e2f 100644 --- a/src/Components/Blazor/Build/test/ComponentRenderingRazorIntegrationTest.cs +++ b/src/Components/Blazor/Build/test/ComponentRenderingRazorIntegrationTest.cs @@ -96,7 +96,7 @@ namespace Test } [Fact] - public void Render_ChildComponent_TriesToSetNonParamter() + public void Render_ChildComponent_TriesToSetNonParameter() { // Arrange AdditionalSyntaxTrees.Add(Parse(@" @@ -408,7 +408,7 @@ namespace Test frame => AssertFrame.Text(frame, "Some text", 4)); } - [Fact] // https://github.com/aspnet/Blazor/issues/773 + [Fact] // https://github.com/dotnet/blazor/issues/773 public void Regression_773() { // Arrange @@ -470,7 +470,7 @@ namespace Test frame => AssertFrame.Attribute(frame, "style", "background: #FFFFFF;", 2)); } - [Fact(Skip = "https://github.com/aspnet/AspNetCore/issues/6185")] + [Fact(Skip = "https://github.com/dotnet/aspnetcore/issues/6185")] public void Render_Component_HtmlEncoded() { // Arrange @@ -501,7 +501,7 @@ namespace Test } // Integration test for HTML block rewriting - [Fact(Skip = "https://github.com/aspnet/AspNetCore/issues/6183")] + [Fact(Skip = "https://github.com/dotnet/aspnetcore/issues/6183")] public void Render_HtmlBlock_Integration() { // Arrange diff --git a/src/Components/Blazor/Build/test/Microsoft.AspNetCore.Blazor.Build.Tests.csproj b/src/Components/Blazor/Build/test/Microsoft.AspNetCore.Blazor.Build.Tests.csproj index eee74d8755..56eccd1090 100644 --- a/src/Components/Blazor/Build/test/Microsoft.AspNetCore.Blazor.Build.Tests.csproj +++ b/src/Components/Blazor/Build/test/Microsoft.AspNetCore.Blazor.Build.Tests.csproj @@ -5,6 +5,7 @@ $(DefaultItemExcludes);TestFiles\**\* + false false @@ -27,6 +28,8 @@ + + @@ -35,10 +38,32 @@ - + + + + <_Parameter1>Testing.RepoRoot + <_Parameter2>$(RepoRoot) + + + + + + <_TestAsset Include="..\testassets\standalone\standalone.csproj" /> + <_TestAsset Include="..\testassets\blazorhosted\blazorhosted.csproj" /> + + + + + diff --git a/src/Components/Blazor/Build/test/RuntimeDependenciesResolverTest.cs b/src/Components/Blazor/Build/test/RuntimeDependenciesResolverTest.cs index 5838d419d7..64aad9bf45 100644 --- a/src/Components/Blazor/Build/test/RuntimeDependenciesResolverTest.cs +++ b/src/Components/Blazor/Build/test/RuntimeDependenciesResolverTest.cs @@ -9,12 +9,12 @@ using System.Text; using Microsoft.AspNetCore.Testing; using Xunit; -namespace Microsoft.AspNetCore.Blazor.Build.Test +namespace Microsoft.AspNetCore.Blazor.Build { public class RuntimeDependenciesResolverTest { - [ConditionalFact(Skip = " https://github.com/aspnet/AspNetCore/issues/12059")] - [SkipOnHelix("https://github.com/aspnet/AspNetCore/issues/10426")] + [ConditionalFact(Skip = " https://github.com/dotnet/aspnetcore/issues/12059")] + [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/10426")] public void FindsReferenceAssemblyGraph_ForStandaloneApp() { // Arrange @@ -109,7 +109,7 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test // Act - var paths = RuntimeDependenciesResolver + var paths = ResolveBlazorRuntimeDependencies .ResolveRuntimeDependenciesCore( mainAssemblyLocation, references, diff --git a/src/Components/Blazor/Build/testassets/Directory.Build.props b/src/Components/Blazor/Build/testassets/Directory.Build.props new file mode 100644 index 0000000000..f60dc0021d --- /dev/null +++ b/src/Components/Blazor/Build/testassets/Directory.Build.props @@ -0,0 +1,31 @@ + + + + + $([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), global.json))\ + $(RepoRoot)src\Components\ + $(ComponentsRoot)Blazor\Build\src\ + $(BlazorBuildRoot)ReferenceBlazorBuildFromSource.props + + + netcoreapp3.1 + + false + false + + + + + + + + + + + diff --git a/src/Servers/Kestrel/perf/PlatformBenchmarks/Directory.Build.props b/src/Components/Blazor/Build/testassets/Directory.Build.targets similarity index 100% rename from src/Servers/Kestrel/perf/PlatformBenchmarks/Directory.Build.props rename to src/Components/Blazor/Build/testassets/Directory.Build.targets diff --git a/src/Components/Blazor/Build/testassets/blazorhosted/Program.cs b/src/Components/Blazor/Build/testassets/blazorhosted/Program.cs new file mode 100644 index 0000000000..e2efcc0c74 --- /dev/null +++ b/src/Components/Blazor/Build/testassets/blazorhosted/Program.cs @@ -0,0 +1,15 @@ +using System; +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; + +namespace blazorhosted.Server +{ + public class Program + { + public static void Main(string[] args) + { + Console.WriteLine(typeof(IWebHost)); + } + } +} diff --git a/src/Components/Blazor/Build/testassets/blazorhosted/blazorhosted.csproj b/src/Components/Blazor/Build/testassets/blazorhosted/blazorhosted.csproj new file mode 100644 index 0000000000..1b4127e1f4 --- /dev/null +++ b/src/Components/Blazor/Build/testassets/blazorhosted/blazorhosted.csproj @@ -0,0 +1,12 @@ + + + + netcoreapp5.0 + true + + + + + + + diff --git a/src/Components/Blazor/Build/testassets/classlibrarywithsatelliteassemblies/Class1.cs b/src/Components/Blazor/Build/testassets/classlibrarywithsatelliteassemblies/Class1.cs new file mode 100644 index 0000000000..944699cdb3 --- /dev/null +++ b/src/Components/Blazor/Build/testassets/classlibrarywithsatelliteassemblies/Class1.cs @@ -0,0 +1,12 @@ +using System; + +namespace classlibrarywithsatelliteassemblies +{ + public class Class1 + { + public static void Test() + { + GC.KeepAlive(typeof(Microsoft.CodeAnalysis.CSharp.CSharpCompilation)); + } + } +} \ No newline at end of file diff --git a/src/Components/Blazor/Build/testassets/classlibrarywithsatelliteassemblies/classlibrarywithsatelliteassemblies.csproj b/src/Components/Blazor/Build/testassets/classlibrarywithsatelliteassemblies/classlibrarywithsatelliteassemblies.csproj new file mode 100644 index 0000000000..7081842748 --- /dev/null +++ b/src/Components/Blazor/Build/testassets/classlibrarywithsatelliteassemblies/classlibrarywithsatelliteassemblies.csproj @@ -0,0 +1,13 @@ + + + + netstandard2.1 + 3.0 + + + + + + + + diff --git a/src/Components/Blazor/Build/testassets/razorclasslibrary/RazorClassLibrary.csproj b/src/Components/Blazor/Build/testassets/razorclasslibrary/RazorClassLibrary.csproj new file mode 100644 index 0000000000..94e866815d --- /dev/null +++ b/src/Components/Blazor/Build/testassets/razorclasslibrary/RazorClassLibrary.csproj @@ -0,0 +1,8 @@ + + + + netstandard2.1 + 3.0 + + + diff --git a/src/Components/Blazor/Build/testassets/razorclasslibrary/wwwroot/styles.css b/src/Components/Blazor/Build/testassets/razorclasslibrary/wwwroot/styles.css new file mode 100644 index 0000000000..5f282702bb --- /dev/null +++ b/src/Components/Blazor/Build/testassets/razorclasslibrary/wwwroot/styles.css @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Analyzers/shared/FeatureDetection/lib/netstandard1.0/_._ b/src/Components/Blazor/Build/testassets/razorclasslibrary/wwwroot/wwwroot/exampleJsInterop.js similarity index 100% rename from src/Analyzers/shared/FeatureDetection/lib/netstandard1.0/_._ rename to src/Components/Blazor/Build/testassets/razorclasslibrary/wwwroot/wwwroot/exampleJsInterop.js diff --git a/src/Components/Blazor/Build/testassets/standalone/App.razor b/src/Components/Blazor/Build/testassets/standalone/App.razor new file mode 100644 index 0000000000..eba23da9b5 --- /dev/null +++ b/src/Components/Blazor/Build/testassets/standalone/App.razor @@ -0,0 +1,8 @@ + + + + + +

Sorry, there's nothing at this address.

+
+
diff --git a/src/Components/Blazor/Build/testassets/standalone/Pages/Index.razor b/src/Components/Blazor/Build/testassets/standalone/Pages/Index.razor new file mode 100644 index 0000000000..16dac31925 --- /dev/null +++ b/src/Components/Blazor/Build/testassets/standalone/Pages/Index.razor @@ -0,0 +1,5 @@ +@page "/" + +

Hello, world!

+ +Welcome to your new app. diff --git a/src/Components/Blazor/Build/testassets/standalone/Program.cs b/src/Components/Blazor/Build/testassets/standalone/Program.cs new file mode 100644 index 0000000000..3e46e63316 --- /dev/null +++ b/src/Components/Blazor/Build/testassets/standalone/Program.cs @@ -0,0 +1,14 @@ +using System; + +namespace standalone +{ + public class Program + { + public static void Main(string[] args) + { +#if REFERENCE_classlibrarywithsatelliteassemblies + GC.KeepAlive(typeof(classlibrarywithsatelliteassemblies.Class1)); +#endif + } + } +} diff --git a/src/Components/Blazor/Build/testassets/standalone/_Imports.razor b/src/Components/Blazor/Build/testassets/standalone/_Imports.razor new file mode 100644 index 0000000000..129b440e86 --- /dev/null +++ b/src/Components/Blazor/Build/testassets/standalone/_Imports.razor @@ -0,0 +1,2 @@ +@using Microsoft.AspNetCore.Components.Routing +@using standalone diff --git a/src/Components/Blazor/Build/testassets/standalone/standalone.csproj b/src/Components/Blazor/Build/testassets/standalone/standalone.csproj new file mode 100644 index 0000000000..1b13eb3d53 --- /dev/null +++ b/src/Components/Blazor/Build/testassets/standalone/standalone.csproj @@ -0,0 +1,20 @@ + + + + + netstandard2.1 + 3.0 + + + + + + + + + + + + + + diff --git a/src/Components/Blazor/Build/testassets/standalone/wwwroot/index.html b/src/Components/Blazor/Build/testassets/standalone/wwwroot/index.html new file mode 100644 index 0000000000..85994d6e89 --- /dev/null +++ b/src/Components/Blazor/Build/testassets/standalone/wwwroot/index.html @@ -0,0 +1,24 @@ + + + + + + + standalone + + + + + + + Loading... + +
+ An unhandled error has occurred. + Reload + 🗙 +
+ + + + diff --git a/src/Components/Blazor/DevServer/src/Microsoft.AspNetCore.Blazor.DevServer.csproj b/src/Components/Blazor/DevServer/src/Microsoft.AspNetCore.Blazor.DevServer.csproj index 53514cf36c..dc403233a8 100644 --- a/src/Components/Blazor/DevServer/src/Microsoft.AspNetCore.Blazor.DevServer.csproj +++ b/src/Components/Blazor/DevServer/src/Microsoft.AspNetCore.Blazor.DevServer.csproj @@ -18,8 +18,9 @@ - + +
diff --git a/src/Components/Blazor/Directory.Build.props b/src/Components/Blazor/Directory.Build.props index a90d83b4cc..c48cf8a1a9 100644 --- a/src/Components/Blazor/Directory.Build.props +++ b/src/Components/Blazor/Directory.Build.props @@ -1,9 +1,3 @@ - - - - $(BlazorClientPreReleaseVersionLabel) - - diff --git a/src/Components/Blazor/Directory.Build.targets b/src/Components/Blazor/Directory.Build.targets index 178608d3e5..e1a17eb9ca 100644 --- a/src/Components/Blazor/Directory.Build.targets +++ b/src/Components/Blazor/Directory.Build.targets @@ -4,4 +4,5 @@ $(PackageVersion) + diff --git a/src/Components/Blazor/Http/src/Microsoft.AspNetCore.Blazor.HttpClient.csproj b/src/Components/Blazor/Http/src/Microsoft.AspNetCore.Blazor.HttpClient.csproj index 9c5974d9d6..9d6deb6173 100644 --- a/src/Components/Blazor/Http/src/Microsoft.AspNetCore.Blazor.HttpClient.csproj +++ b/src/Components/Blazor/Http/src/Microsoft.AspNetCore.Blazor.HttpClient.csproj @@ -4,6 +4,7 @@ netstandard2.0 Provides experimental support for using System.Text.Json with HttpClient. Intended for use with Blazor running under WebAssembly. false + false diff --git a/src/Components/Blazor/Mono.WebAssembly.Interop/src/InternalCalls.cs b/src/Components/Blazor/Mono.WebAssembly.Interop/src/InternalCalls.cs new file mode 100644 index 0000000000..60c0cdc429 --- /dev/null +++ b/src/Components/Blazor/Mono.WebAssembly.Interop/src/InternalCalls.cs @@ -0,0 +1,25 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Runtime.CompilerServices; + +namespace WebAssembly.JSInterop +{ + /// + /// Methods that map to the functions compiled into the Mono WebAssembly runtime, + /// as defined by 'mono_add_internal_call' calls in driver.c + /// + internal class InternalCalls + { + // The exact namespace, type, and method names must match the corresponding entries + // in driver.c in the Mono distribution + + // We're passing asyncHandle by ref not because we want it to be writable, but so it gets + // passed as a pointer (4 bytes). We can pass 4-byte values, but not 8-byte ones. + [MethodImpl(MethodImplOptions.InternalCall)] + public static extern string InvokeJSMarshalled(out string exception, ref long asyncHandle, string functionIdentifier, string argsJson); + + [MethodImpl(MethodImplOptions.InternalCall)] + public static extern TRes InvokeJSUnmarshalled(out string exception, string functionIdentifier, T0 arg0, T1 arg1, T2 arg2); + } +} diff --git a/src/Components/Blazor/Mono.WebAssembly.Interop/src/Mono.WebAssembly.Interop.csproj b/src/Components/Blazor/Mono.WebAssembly.Interop/src/Mono.WebAssembly.Interop.csproj new file mode 100644 index 0000000000..ea714b2d92 --- /dev/null +++ b/src/Components/Blazor/Mono.WebAssembly.Interop/src/Mono.WebAssembly.Interop.csproj @@ -0,0 +1,17 @@ + + + + netstandard2.1 + Abstractions and features for interop between Mono WebAssembly and JavaScript code. + wasm;javascript;interop + true + true + true + false + + + + + + + diff --git a/src/Components/Blazor/Mono.WebAssembly.Interop/src/MonoWebAssemblyJSRuntime.cs b/src/Components/Blazor/Mono.WebAssembly.Interop/src/MonoWebAssemblyJSRuntime.cs new file mode 100644 index 0000000000..654263a123 --- /dev/null +++ b/src/Components/Blazor/Mono.WebAssembly.Interop/src/MonoWebAssemblyJSRuntime.cs @@ -0,0 +1,157 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Text.Json; +using Microsoft.JSInterop; +using Microsoft.JSInterop.Infrastructure; +using WebAssembly.JSInterop; + +namespace Mono.WebAssembly.Interop +{ + /// + /// Provides methods for invoking JavaScript functions for applications running + /// on the Mono WebAssembly runtime. + /// + public class MonoWebAssemblyJSRuntime : JSInProcessRuntime + { + /// + /// Gets the used to perform operations using . + /// + private static MonoWebAssemblyJSRuntime Instance { get; set; } + + /// + /// Initializes the to be used to perform operations using . + /// + /// The instance. + protected static void Initialize(MonoWebAssemblyJSRuntime jsRuntime) + { + if (Instance != null) + { + throw new InvalidOperationException("MonoWebAssemblyJSRuntime has already been initialized."); + } + + Instance = jsRuntime ?? throw new ArgumentNullException(nameof(jsRuntime)); + } + + /// + protected override string InvokeJS(string identifier, string argsJson) + { + var noAsyncHandle = default(long); + var result = InternalCalls.InvokeJSMarshalled(out var exception, ref noAsyncHandle, identifier, argsJson); + return exception != null + ? throw new JSException(exception) + : result; + } + + /// + protected override void BeginInvokeJS(long asyncHandle, string identifier, string argsJson) + { + InternalCalls.InvokeJSMarshalled(out _, ref asyncHandle, identifier, argsJson); + } + + // Invoked via Mono's JS interop mechanism (invoke_method) + private static string InvokeDotNet(string assemblyName, string methodIdentifier, string dotNetObjectId, string argsJson) + { + var callInfo = new DotNetInvocationInfo(assemblyName, methodIdentifier, dotNetObjectId == null ? default : long.Parse(dotNetObjectId), callId: null); + return DotNetDispatcher.Invoke(Instance, callInfo, argsJson); + } + + // Invoked via Mono's JS interop mechanism (invoke_method) + private static void EndInvokeJS(string argsJson) + => DotNetDispatcher.EndInvokeJS(Instance, argsJson); + + // Invoked via Mono's JS interop mechanism (invoke_method) + private static void BeginInvokeDotNet(string callId, string assemblyNameOrDotNetObjectId, string methodIdentifier, string argsJson) + { + // Figure out whether 'assemblyNameOrDotNetObjectId' is the assembly name or the instance ID + // We only need one for any given call. This helps to work around the limitation that we can + // only pass a maximum of 4 args in a call from JS to Mono WebAssembly. + string assemblyName; + long dotNetObjectId; + if (char.IsDigit(assemblyNameOrDotNetObjectId[0])) + { + dotNetObjectId = long.Parse(assemblyNameOrDotNetObjectId); + assemblyName = null; + } + else + { + dotNetObjectId = default; + assemblyName = assemblyNameOrDotNetObjectId; + } + + var callInfo = new DotNetInvocationInfo(assemblyName, methodIdentifier, dotNetObjectId, callId); + DotNetDispatcher.BeginInvokeDotNet(Instance, callInfo, argsJson); + } + + protected override void EndInvokeDotNet(DotNetInvocationInfo callInfo, in DotNetInvocationResult dispatchResult) + { + // For failures, the common case is to call EndInvokeDotNet with the Exception object. + // For these we'll serialize as something that's useful to receive on the JS side. + // If the value is not an Exception, we'll just rely on it being directly JSON-serializable. + var resultOrError = dispatchResult.Success ? dispatchResult.Result : dispatchResult.Exception.ToString(); + + // We pass 0 as the async handle because we don't want the JS-side code to + // send back any notification (we're just providing a result for an existing async call) + var args = JsonSerializer.Serialize(new[] { callInfo.CallId, dispatchResult.Success, resultOrError }, JsonSerializerOptions); + BeginInvokeJS(0, "DotNet.jsCallDispatcher.endInvokeDotNetFromJS", args); + } + + #region Custom MonoWebAssemblyJSRuntime methods + + /// + /// Invokes the JavaScript function registered with the specified identifier. + /// + /// The .NET type corresponding to the function's return value type. + /// The identifier used when registering the target function. + /// The result of the function invocation. + public TRes InvokeUnmarshalled(string identifier) + => InvokeUnmarshalled(identifier, null, null, null); + + /// + /// Invokes the JavaScript function registered with the specified identifier. + /// + /// The type of the first argument. + /// The .NET type corresponding to the function's return value type. + /// The identifier used when registering the target function. + /// The first argument. + /// The result of the function invocation. + public TRes InvokeUnmarshalled(string identifier, T0 arg0) + => InvokeUnmarshalled(identifier, arg0, null, null); + + /// + /// Invokes the JavaScript function registered with the specified identifier. + /// + /// The type of the first argument. + /// The type of the second argument. + /// The .NET type corresponding to the function's return value type. + /// The identifier used when registering the target function. + /// The first argument. + /// The second argument. + /// The result of the function invocation. + public TRes InvokeUnmarshalled(string identifier, T0 arg0, T1 arg1) + => InvokeUnmarshalled(identifier, arg0, arg1, null); + + /// + /// Invokes the JavaScript function registered with the specified identifier. + /// + /// The type of the first argument. + /// The type of the second argument. + /// The type of the third argument. + /// The .NET type corresponding to the function's return value type. + /// The identifier used when registering the target function. + /// The first argument. + /// The second argument. + /// The third argument. + /// The result of the function invocation. + public TRes InvokeUnmarshalled(string identifier, T0 arg0, T1 arg1, T2 arg2) + { + var result = InternalCalls.InvokeJSUnmarshalled(out var exception, identifier, arg0, arg1, arg2); + return exception != null + ? throw new JSException(exception) + : result; + } + + #endregion + } +} diff --git a/src/Components/Blazor/Server/src/Microsoft.AspNetCore.Blazor.Server.csproj b/src/Components/Blazor/Server/src/Microsoft.AspNetCore.Blazor.Server.csproj index ab6c3b6ee6..4bdbc1bb1b 100644 --- a/src/Components/Blazor/Server/src/Microsoft.AspNetCore.Blazor.Server.csproj +++ b/src/Components/Blazor/Server/src/Microsoft.AspNetCore.Blazor.Server.csproj @@ -4,13 +4,14 @@ $(DefaultNetCoreTargetFramework) Runtime server features for ASP.NET Core Blazor applications. false + false - + diff --git a/src/Components/Blazor/Server/src/MonoDebugProxy/BlazorMonoDebugProxyAppBuilderExtensions.cs b/src/Components/Blazor/Server/src/MonoDebugProxy/BlazorMonoDebugProxyAppBuilderExtensions.cs index 533cd99399..cbe0fe363a 100644 --- a/src/Components/Blazor/Server/src/MonoDebugProxy/BlazorMonoDebugProxyAppBuilderExtensions.cs +++ b/src/Components/Blazor/Server/src/MonoDebugProxy/BlazorMonoDebugProxyAppBuilderExtensions.cs @@ -9,9 +9,9 @@ using System.Linq; using System.Net; using System.Net.Http; using System.Runtime.InteropServices; +using System.Text.Json; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; -using Newtonsoft.Json; using WsProxy; namespace Microsoft.AspNetCore.Builder @@ -21,6 +21,15 @@ namespace Microsoft.AspNetCore.Builder /// public static class BlazorMonoDebugProxyAppBuilderExtensions { + private static JsonSerializerOptions JsonOptions = new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + PropertyNameCaseInsensitive = true, + IgnoreNullValues = true + }; + + private static string DefaultDebuggerHost = "http://localhost:9222"; + /// /// Adds middleware for needed for debugging Blazor applications /// inside Chromium dev tools. @@ -29,6 +38,8 @@ namespace Microsoft.AspNetCore.Builder { app.UseWebSockets(); + app.UseVisualStudioDebuggerConnectionRequestHandlers(); + app.Use((context, next) => { var requestPath = context.Request.Path; @@ -52,6 +63,85 @@ namespace Microsoft.AspNetCore.Builder }); } + private static string GetDebuggerHost() + { + var envVar = Environment.GetEnvironmentVariable("ASPNETCORE_WEBASSEMBLYDEBUGHOST"); + + if (string.IsNullOrEmpty(envVar)) + { + return DefaultDebuggerHost; + } + else + { + return envVar; + } + } + + private static int GetDebuggerPort() + { + var host = GetDebuggerHost(); + return new Uri(host).Port; + } + + private static void UseVisualStudioDebuggerConnectionRequestHandlers(this IApplicationBuilder app) + { + // Unfortunately VS doesn't send any deliberately distinguishing information so we know it's + // not a regular browser or API client. The closest we can do is look for the *absence* of a + // User-Agent header. In the future, we should try to get VS to send a special header to indicate + // this is a debugger metadata request. + app.Use(async (context, next) => + { + var request = context.Request; + var requestPath = request.Path; + if (requestPath.StartsWithSegments("/json") + && !request.Headers.ContainsKey("User-Agent")) + { + if (requestPath.Equals("/json", StringComparison.OrdinalIgnoreCase) || requestPath.Equals("/json/list", StringComparison.OrdinalIgnoreCase)) + { + var availableTabs = await GetOpenedBrowserTabs(); + + // Filter the list to only include tabs displaying the requested app, + // but only during the "choose application to debug" phase. We can't apply + // the same filter during the "connecting" phase (/json/list), nor do we need to. + if (requestPath.Equals("/json")) + { + availableTabs = availableTabs.Where(tab => tab.Url.StartsWith($"{request.Scheme}://{request.Host}{request.PathBase}/")); + } + + var proxiedTabInfos = availableTabs.Select(tab => + { + var underlyingV8Endpoint = tab.WebSocketDebuggerUrl; + var proxiedV8Endpoint = $"ws://{request.Host}{request.PathBase}/_framework/debug/ws-proxy?browser={WebUtility.UrlEncode(underlyingV8Endpoint)}"; + return new + { + description = "", + devtoolsFrontendUrl = "", + id = tab.Id, + title = tab.Title, + type = tab.Type, + url = tab.Url, + webSocketDebuggerUrl = proxiedV8Endpoint + }; + }); + + context.Response.ContentType = "application/json"; + await context.Response.WriteAsync(JsonSerializer.Serialize(proxiedTabInfos)); + } + else if (requestPath.Equals("/json/version", StringComparison.OrdinalIgnoreCase)) + { + var browserVersionJson = await GetBrowserVersionInfoAsync(); + + context.Response.ContentType = "application/json"; + await context.Response.WriteAsync(browserVersionJson); + } + } + else + { + await next(); + } + }); + } + private static async Task DebugWebSocketProxyRequest(HttpContext context) { if (!context.WebSockets.IsWebSocketRequest) @@ -81,13 +171,13 @@ namespace Microsoft.AspNetCore.Builder // TODO: Allow overriding port (but not hostname, as we're connecting to the // local browser, not to the webserver serving the app) - var debuggerHost = "http://localhost:9222"; + var debuggerHost = GetDebuggerHost(); var debuggerTabsListUrl = $"{debuggerHost}/json"; IEnumerable availableTabs; try { - availableTabs = await GetOpenedBrowserTabs(debuggerHost); + availableTabs = await GetOpenedBrowserTabs(); } catch (Exception ex) { @@ -147,28 +237,30 @@ namespace Microsoft.AspNetCore.Builder var underlyingV8Endpoint = tabToDebug.WebSocketDebuggerUrl; var proxyEndpoint = $"{request.Host}{request.PathBase}/_framework/debug/ws-proxy?browser={WebUtility.UrlEncode(underlyingV8Endpoint)}"; var devToolsUrlAbsolute = new Uri(debuggerHost + tabToDebug.DevtoolsFrontendUrl); - var devToolsUrlWithProxy = $"{devToolsUrlAbsolute.Scheme}://{devToolsUrlAbsolute.Authority}{devToolsUrlAbsolute.AbsolutePath}?ws={proxyEndpoint}"; + var wsParamName = request.IsHttps ? "wss" : "ws"; + var devToolsUrlWithProxy = $"{devToolsUrlAbsolute.Scheme}://{devToolsUrlAbsolute.Authority}{devToolsUrlAbsolute.AbsolutePath}?{wsParamName}={proxyEndpoint}"; context.Response.Redirect(devToolsUrlWithProxy); } private static string GetLaunchChromeInstructions(string appRootUrl) { - var profilePath = Path.Combine(Path.GetTempPath(), "blazor-edge-debug"); + var profilePath = Path.Combine(Path.GetTempPath(), "blazor-chrome-debug"); + var debuggerPort = GetDebuggerPort(); if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { return $@"

Press Win+R and enter the following:

-

chrome --remote-debugging-port=9222 --user-data-dir=""{profilePath}"" {appRootUrl}

"; +

chrome --remote-debugging-port={debuggerPort} --user-data-dir=""{profilePath}"" {appRootUrl}

"; } else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { return $@"

In a terminal window execute the following:

-

google-chrome --remote-debugging-port=9222 --user-data-dir={profilePath} {appRootUrl}

"; +

google-chrome --remote-debugging-port={debuggerPort} --user-data-dir={profilePath} {appRootUrl}

"; } else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { return $@"

Execute the following:

-

open /Applications/Google\ Chrome.app --args --remote-debugging-port=9222 --user-data-dir={profilePath} {appRootUrl}

"; +

open /Applications/Google\ Chrome.app --args --remote-debugging-port={debuggerPort} --user-data-dir={profilePath} {appRootUrl}

"; } else { @@ -178,17 +270,18 @@ namespace Microsoft.AspNetCore.Builder private static string GetLaunchEdgeInstructions(string appRootUrl) { - var profilePath = Path.Combine(Path.GetTempPath(), "blazor-chrome-debug"); + var profilePath = Path.Combine(Path.GetTempPath(), "blazor-edge-debug"); + var debugggerPort = GetDebuggerPort(); if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { return $@"

Press Win+R and enter the following:

-

msedge --remote-debugging-port=9222 --user-data-dir=""{profilePath}"" {appRootUrl}

"; +

msedge --remote-debugging-port={debugggerPort} --user-data-dir=""{profilePath}"" --no-first-run {appRootUrl}

"; } else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { return $@"

In a terminal window execute the following:

-

open /Applications/Microsoft\ Edge\ Dev.app --args --remote-debugging-port=9222 --user-data-dir={profilePath} {appRootUrl}

"; +

open /Applications/Microsoft\ Edge\ Dev.app --args --remote-debugging-port={debugggerPort} --user-data-dir={profilePath} {appRootUrl}

"; } else { @@ -196,17 +289,24 @@ namespace Microsoft.AspNetCore.Builder } } - private static async Task> GetOpenedBrowserTabs(string debuggerHost) + private static async Task GetBrowserVersionInfoAsync() { - using (var httpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(5) }) - { - var jsonResponse = await httpClient.GetStringAsync($"{debuggerHost}/json"); - return JsonConvert.DeserializeObject(jsonResponse); - } + using var httpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(5) }; + var debuggerHost = GetDebuggerHost(); + return await httpClient.GetStringAsync($"{debuggerHost}/json/version"); + } + + private static async Task> GetOpenedBrowserTabs() + { + using var httpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(5) }; + var debuggerHost = GetDebuggerHost(); + var jsonResponse = await httpClient.GetStringAsync($"{debuggerHost}/json"); + return JsonSerializer.Deserialize(jsonResponse, JsonOptions); } class BrowserTab { + public string Id { get; set; } public string Type { get; set; } public string Url { get; set; } public string Title { get; set; } diff --git a/src/Components/Blazor/Server/src/MonoDebugProxy/ws-proxy/MonoProxy.cs b/src/Components/Blazor/Server/src/MonoDebugProxy/ws-proxy/MonoProxy.cs index 8c440da1ce..7aa4dcf710 100644 --- a/src/Components/Blazor/Server/src/MonoDebugProxy/ws-proxy/MonoProxy.cs +++ b/src/Components/Blazor/Server/src/MonoDebugProxy/ws-proxy/MonoProxy.cs @@ -100,7 +100,7 @@ namespace WsProxy { break; } case "Debugger.paused": { - //TODO figure out how to stich out more frames and, in particular what happens when real wasm is on the stack + //TODO figure out how to stitch out more frames and, in particular what happens when real wasm is on the stack var top_func = args? ["callFrames"]? [0]? ["functionName"]?.Value (); if (top_func == "mono_wasm_fire_bp" || top_func == "_mono_wasm_fire_bp") { await OnBreakPointHit (args, token); @@ -419,7 +419,7 @@ namespace WsProxy { var res = await SendCommand("Runtime.evaluate", o, token); - //if we fail we just buble that to the IDE (and let it panic over it) + //if we fail we just bubble that to the IDE (and let it panic over it) if (res.IsErr) { SendResponse(msg_id, res, token); @@ -475,7 +475,7 @@ namespace WsProxy { var res = await SendCommand ("Runtime.evaluate", o, token); - //if we fail we just buble that to the IDE (and let it panic over it) + //if we fail we just bubble that to the IDE (and let it panic over it) if (res.IsErr) { SendResponse (msg_id, res, token); return; @@ -594,7 +594,7 @@ namespace WsProxy { var res = await EnableBreakPoint (bp, token); var ret_code = res.Value? ["result"]? ["value"]?.Value (); - //if we fail we just buble that to the IDE (and let it panic over it) + //if we fail we just bubble that to the IDE (and let it panic over it) if (!ret_code.HasValue) { //FIXME figure out how to inform the IDE of that. Info ($"FAILED TO ENABLE BP {bp.LocalId}"); @@ -668,7 +668,7 @@ namespace WsProxy { var res = await EnableBreakPoint (bp, token); var ret_code = res.Value? ["result"]? ["value"]?.Value (); - //if we fail we just buble that to the IDE (and let it panic over it) + //if we fail we just bubble that to the IDE (and let it panic over it) if (!ret_code.HasValue) { SendResponse (msg_id, res, token); return; diff --git a/src/Components/Blazor/Templates/src/Directory.Build.props b/src/Components/Blazor/Templates/src/Directory.Build.props deleted file mode 100644 index ed5e015014..0000000000 --- a/src/Components/Blazor/Templates/src/Directory.Build.props +++ /dev/null @@ -1,15 +0,0 @@ - - - - - false - false - - - - - 0.8.0-preview-19064-0339 - 3.0.0-preview-19064-0339 - - - diff --git a/src/Components/Blazor/Templates/src/Directory.Build.targets b/src/Components/Blazor/Templates/src/Directory.Build.targets deleted file mode 100644 index 7c6f423add..0000000000 --- a/src/Components/Blazor/Templates/src/Directory.Build.targets +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - TemplateBlazorVersion=$(PackageVersion); - TemplateComponentsVersion=$(ComponentsPackageVersion); - RepositoryCommit=$(SourceRevisionId); - - - - diff --git a/src/Components/Blazor/Templates/src/Microsoft.AspNetCore.Blazor.Templates.csproj b/src/Components/Blazor/Templates/src/Microsoft.AspNetCore.Blazor.Templates.csproj deleted file mode 100644 index 55364eee45..0000000000 --- a/src/Components/Blazor/Templates/src/Microsoft.AspNetCore.Blazor.Templates.csproj +++ /dev/null @@ -1,64 +0,0 @@ - - - netstandard2.0 - Microsoft.AspNetCore.Blazor.Templates.nuspec - false - False - False - False - false - none - false - $(NoWarn);2008 - Templates for ASP.NET Core Blazor projects. - aspnet;templates;blazor;spa - false - - - - - - - - - <_TemplateConfigMainFile Include="content\**\.template.config.src\template.json" /> - <_TemplateConfigDir Include="@(_TemplateConfigMainFile->'$([System.IO.Path]::GetDirectoryName('%(_TemplateConfigMainFile.FullPath)'))')" /> - <_TemplateConfigFileToCopy Include="%(_TemplateConfigDir.Identity)\**\*.*"> - $([System.IO.Path]::GetDirectoryName('%(_TemplateConfigDir.Identity)'))\.template.config\ - - - - - - - - - - - - - - %(DestDir)%(RecursiveDir)%(Filename)%(Extension) - $(GeneratedContentProperties) - - - - - - - - - diff --git a/src/Components/Blazor/Templates/src/Microsoft.AspNetCore.Blazor.Templates.nuspec b/src/Components/Blazor/Templates/src/Microsoft.AspNetCore.Blazor.Templates.nuspec deleted file mode 100644 index fd19750231..0000000000 --- a/src/Components/Blazor/Templates/src/Microsoft.AspNetCore.Blazor.Templates.nuspec +++ /dev/null @@ -1,16 +0,0 @@ - - - - $CommonMetadataElements$ - - - - - - $CommonFileElements$ - - - diff --git a/src/Components/Blazor/Templates/src/content/BlazorWasm-CSharp/Shared/BlazorWasm-CSharp.Shared.csproj b/src/Components/Blazor/Templates/src/content/BlazorWasm-CSharp/Shared/BlazorWasm-CSharp.Shared.csproj deleted file mode 100644 index 2a77f0c7cc..0000000000 --- a/src/Components/Blazor/Templates/src/content/BlazorWasm-CSharp/Shared/BlazorWasm-CSharp.Shared.csproj +++ /dev/null @@ -1,8 +0,0 @@ - - - - netstandard2.0 - 7.3 - - - diff --git a/src/Components/Blazor/Validation/src/Microsoft.AspNetCore.Blazor.DataAnnotations.Validation.csproj b/src/Components/Blazor/Validation/src/Microsoft.AspNetCore.Blazor.DataAnnotations.Validation.csproj index 89023db6b9..53cc678edb 100644 --- a/src/Components/Blazor/Validation/src/Microsoft.AspNetCore.Blazor.DataAnnotations.Validation.csproj +++ b/src/Components/Blazor/Validation/src/Microsoft.AspNetCore.Blazor.DataAnnotations.Validation.csproj @@ -1,7 +1,7 @@ - netstandard2.0 + netstandard2.1 Provides experimental support for validation using DataAnnotations. false false diff --git a/src/Components/Blazor/testassets/HostedInAspNet.Client/HostedInAspNet.Client.csproj b/src/Components/Blazor/testassets/HostedInAspNet.Client/HostedInAspNet.Client.csproj index ef12ac3c4e..e27de695c1 100644 --- a/src/Components/Blazor/testassets/HostedInAspNet.Client/HostedInAspNet.Client.csproj +++ b/src/Components/Blazor/testassets/HostedInAspNet.Client/HostedInAspNet.Client.csproj @@ -1,7 +1,7 @@  - netstandard2.0 + netstandard2.1 Exe true 3.0 @@ -11,17 +11,4 @@ - - - $(GetCurrentProjectStaticWebAssetsDependsOn); - _ClearCurrentStaticWebAssetsForReferenceDiscovery - - - - - - - - - diff --git a/src/Components/Blazor/testassets/HostedInAspNet.Server/HostedInAspNet.Server.csproj b/src/Components/Blazor/testassets/HostedInAspNet.Server/HostedInAspNet.Server.csproj index afc09e4f77..1350803e6b 100644 --- a/src/Components/Blazor/testassets/HostedInAspNet.Server/HostedInAspNet.Server.csproj +++ b/src/Components/Blazor/testassets/HostedInAspNet.Server/HostedInAspNet.Server.csproj @@ -8,7 +8,7 @@ - + diff --git a/src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/wwwroot/benchmarks/index.js b/src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/wwwroot/benchmarks/index.js deleted file mode 100644 index 4600066f38..0000000000 --- a/src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/wwwroot/benchmarks/index.js +++ /dev/null @@ -1,6 +0,0 @@ -import { HtmlUI } from './lib/minibench/minibench.js'; -import './appStartup.js'; -import './renderList.js'; -import './jsonHandling.js'; - -new HtmlUI('E2E Performance', '#display'); diff --git a/src/Components/Blazor/testassets/MonoSanity/MonoSanity.csproj b/src/Components/Blazor/testassets/MonoSanity/MonoSanity.csproj index 5297f5bca6..e2158c8c06 100644 --- a/src/Components/Blazor/testassets/MonoSanity/MonoSanity.csproj +++ b/src/Components/Blazor/testassets/MonoSanity/MonoSanity.csproj @@ -2,6 +2,7 @@ $(DefaultNetCoreTargetFramework) + diff --git a/src/Components/Blazor/testassets/MonoSanity/wwwroot/index.html b/src/Components/Blazor/testassets/MonoSanity/wwwroot/index.html index c4d1ab60e2..8a42e8e5d1 100644 --- a/src/Components/Blazor/testassets/MonoSanity/wwwroot/index.html +++ b/src/Components/Blazor/testassets/MonoSanity/wwwroot/index.html @@ -36,14 +36,6 @@ -
- Invoke wiped method -
- -
-
-
-
Call JS from .NET
@@ -111,16 +103,6 @@ } }; - el('invokeWipedMethod').onsubmit = function (evt) { - evt.preventDefault(); - try { - invokeMonoMethod('MonoSanityClient', 'MonoSanityClient', 'Examples', 'InvokeWipedMethod', []); - el('invokeWipedMethodStackTrace').value = 'WARNING: No exception occurred'; - } catch (ex) { - el('invokeWipedMethodStackTrace').value = ex.toString(); - } - }; - el('callJs').onsubmit = function (evt) { evt.preventDefault(); var expression = el('callJsEvalExpression').value; diff --git a/src/Components/Blazor/testassets/MonoSanity/wwwroot/loader.js b/src/Components/Blazor/testassets/MonoSanity/wwwroot/loader.js index 48d4530d3e..328acacdff 100644 --- a/src/Components/Blazor/testassets/MonoSanity/wwwroot/loader.js +++ b/src/Components/Blazor/testassets/MonoSanity/wwwroot/loader.js @@ -12,7 +12,7 @@ window.initMono = function initMono(loadAssemblyUrls, onReadyCallback) { window.Module = { locateFile: function (fileName) { - return fileName === 'mono.wasm' ? '/_framework/wasm/mono.wasm' : fileName; + return fileName === 'dotnet.wasm' ? '/_framework/wasm/dotnet.wasm' : fileName; }, onRuntimeInitialized: function () { var allAssemblyUrls = loadAssemblyUrls.concat([ @@ -117,7 +117,7 @@ } var scriptElem = document.createElement('script'); - scriptElem.src = '/_framework/wasm/mono.js'; + scriptElem.src = '/_framework/wasm/dotnet.js'; document.body.appendChild(scriptElem); } diff --git a/src/Components/Blazor/testassets/MonoSanityClient/Examples.cs b/src/Components/Blazor/testassets/MonoSanityClient/Examples.cs index 8023ded4d9..1d56128e35 100644 --- a/src/Components/Blazor/testassets/MonoSanityClient/Examples.cs +++ b/src/Components/Blazor/testassets/MonoSanityClient/Examples.cs @@ -31,11 +31,6 @@ namespace MonoSanityClient throw new InvalidOperationException(message); } - public static void InvokeWipedMethod() - { - new HttpClientHandler().Dispose(); - } - public static string EvaluateJavaScript(string expression) { var result = InternalCalls.InvokeJSUnmarshalled(out var exceptionMessage, "evaluateJsExpression", expression, null, null); diff --git a/src/Components/Blazor/testassets/MonoSanityClient/MonoSanityClient.csproj b/src/Components/Blazor/testassets/MonoSanityClient/MonoSanityClient.csproj index b186c39194..e01c600843 100644 --- a/src/Components/Blazor/testassets/MonoSanityClient/MonoSanityClient.csproj +++ b/src/Components/Blazor/testassets/MonoSanityClient/MonoSanityClient.csproj @@ -1,7 +1,7 @@  - netstandard2.0 + netstandard2.1 false false exe diff --git a/src/Components/Blazor/testassets/StandaloneApp/StandaloneApp.csproj b/src/Components/Blazor/testassets/StandaloneApp/StandaloneApp.csproj index cddd429b6a..32156c56b8 100644 --- a/src/Components/Blazor/testassets/StandaloneApp/StandaloneApp.csproj +++ b/src/Components/Blazor/testassets/StandaloneApp/StandaloneApp.csproj @@ -1,7 +1,7 @@  - netstandard2.0 + netstandard2.1 true 3.0 diff --git a/src/Components/Blazor/testassets/StandaloneApp/wwwroot/index.html b/src/Components/Blazor/testassets/StandaloneApp/wwwroot/index.html index 5da6ba26b3..fde34bd639 100644 --- a/src/Components/Blazor/testassets/StandaloneApp/wwwroot/index.html +++ b/src/Components/Blazor/testassets/StandaloneApp/wwwroot/index.html @@ -1,4 +1,4 @@ - + @@ -11,6 +11,12 @@ Loading... +
+ An unhandled exception has occurred. See browser dev tools for details. + Reload + 🗙 +
+ diff --git a/src/Components/Components.sln b/src/Components/Components.sln index ba0b2476ff..c88695cf66 100644 --- a/src/Components/Components.sln +++ b/src/Components/Components.sln @@ -21,12 +21,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Blazor EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Blazor.DevServer", "Blazor\DevServer\src\Microsoft.AspNetCore.Blazor.DevServer.csproj", "{A6C8050D-7C18-4585-ADCF-833AC1765847}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Blazor.E2EPerformance", "Blazor\testassets\Microsoft.AspNetCore.Blazor.E2EPerformance\Microsoft.AspNetCore.Blazor.E2EPerformance.csproj", "{08773DD6-6FED-4BF2-BD9F-C19D2CF919BB}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Blazor.Server", "Blazor\Server\src\Microsoft.AspNetCore.Blazor.Server.csproj", "{A4859630-F9F7-4F5C-9FF3-6C013D7C58FA}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Blazor.Templates", "Blazor\Templates\src\Microsoft.AspNetCore.Blazor.Templates.csproj", "{66036B70-6C93-4E45-A1A1-819F15CA757A}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "testassets", "testassets", "{A7ABAC29-F73F-456D-AE54-46842CFC2E10}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HostedInAspNet.Client", "Blazor\testassets\HostedInAspNet.Client\HostedInAspNet.Client.csproj", "{FD37F740-A654-4117-BFB6-9112CE4C1D3B}" @@ -240,12 +236,24 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ignitor", "Ignitor\src\Igni EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ignitor.Test", "Ignitor\test\Ignitor.Test.csproj", "{F31E8118-014E-4CCE-8A48-5282F7B9BB3E}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Validation", "Validation", "{FD9BD646-9D50-42ED-A3E1-90558BA0C6B2}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Blazor.DataAnnotations.Validation", "Blazor\Validation\src\Microsoft.AspNetCore.Blazor.DataAnnotations.Validation.csproj", "{B70F90C7-2696-4050-B24E-BF0308F4E059}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Blazor.DataAnnotations.Validation.Tests", "Blazor\Validation\test\Microsoft.AspNetCore.Blazor.DataAnnotations.Validation.Tests.csproj", "{A5617A9D-C71E-44DE-936C-27611EB40A02}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Mono.WebAssembly.Interop", "Mono.WebAssembly.Interop", "{21BB9C13-20C1-4F2B-80E4-D7C64AA3BD05}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mono.WebAssembly.Interop", "Blazor\Mono.WebAssembly.Interop\src\Mono.WebAssembly.Interop.csproj", "{D141CFEE-D10A-406B-8963-F86FA13732E3}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ComponentsApp.Server", "test\testassets\ComponentsApp.Server\ComponentsApp.Server.csproj", "{F2E27E1C-2E47-42C1-9AC7-36265A381717}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "benchmarkapps", "benchmarkapps", "{CCC82E97-7B58-43E2-BBBD-23D82F926367}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Wasm.Performance", "Wasm.Performance", "{F65EFF0F-ACF3-46BD-9A8F-CDA94AF1885A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wasm.Performance.Driver", "benchmarkapps\Wasm.Performance\Driver\Wasm.Performance.Driver.csproj", "{CA9948CA-B3FA-4C2E-A726-5E47BAD19457}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wasm.Performance.TestApp", "benchmarkapps\Wasm.Performance\TestApp\Wasm.Performance.TestApp.csproj", "{97EA0A7D-FE5E-47D1-ADDC-4BFD702F55AB}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -340,18 +348,6 @@ Global {A6C8050D-7C18-4585-ADCF-833AC1765847}.Release|x64.Build.0 = Release|Any CPU {A6C8050D-7C18-4585-ADCF-833AC1765847}.Release|x86.ActiveCfg = Release|Any CPU {A6C8050D-7C18-4585-ADCF-833AC1765847}.Release|x86.Build.0 = Release|Any CPU - {08773DD6-6FED-4BF2-BD9F-C19D2CF919BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {08773DD6-6FED-4BF2-BD9F-C19D2CF919BB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {08773DD6-6FED-4BF2-BD9F-C19D2CF919BB}.Debug|x64.ActiveCfg = Debug|Any CPU - {08773DD6-6FED-4BF2-BD9F-C19D2CF919BB}.Debug|x64.Build.0 = Debug|Any CPU - {08773DD6-6FED-4BF2-BD9F-C19D2CF919BB}.Debug|x86.ActiveCfg = Debug|Any CPU - {08773DD6-6FED-4BF2-BD9F-C19D2CF919BB}.Debug|x86.Build.0 = Debug|Any CPU - {08773DD6-6FED-4BF2-BD9F-C19D2CF919BB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {08773DD6-6FED-4BF2-BD9F-C19D2CF919BB}.Release|Any CPU.Build.0 = Release|Any CPU - {08773DD6-6FED-4BF2-BD9F-C19D2CF919BB}.Release|x64.ActiveCfg = Release|Any CPU - {08773DD6-6FED-4BF2-BD9F-C19D2CF919BB}.Release|x64.Build.0 = Release|Any CPU - {08773DD6-6FED-4BF2-BD9F-C19D2CF919BB}.Release|x86.ActiveCfg = Release|Any CPU - {08773DD6-6FED-4BF2-BD9F-C19D2CF919BB}.Release|x86.Build.0 = Release|Any CPU {A4859630-F9F7-4F5C-9FF3-6C013D7C58FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {A4859630-F9F7-4F5C-9FF3-6C013D7C58FA}.Debug|Any CPU.Build.0 = Debug|Any CPU {A4859630-F9F7-4F5C-9FF3-6C013D7C58FA}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -364,18 +360,6 @@ Global {A4859630-F9F7-4F5C-9FF3-6C013D7C58FA}.Release|x64.Build.0 = Release|Any CPU {A4859630-F9F7-4F5C-9FF3-6C013D7C58FA}.Release|x86.ActiveCfg = Release|Any CPU {A4859630-F9F7-4F5C-9FF3-6C013D7C58FA}.Release|x86.Build.0 = Release|Any CPU - {66036B70-6C93-4E45-A1A1-819F15CA757A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {66036B70-6C93-4E45-A1A1-819F15CA757A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {66036B70-6C93-4E45-A1A1-819F15CA757A}.Debug|x64.ActiveCfg = Debug|Any CPU - {66036B70-6C93-4E45-A1A1-819F15CA757A}.Debug|x64.Build.0 = Debug|Any CPU - {66036B70-6C93-4E45-A1A1-819F15CA757A}.Debug|x86.ActiveCfg = Debug|Any CPU - {66036B70-6C93-4E45-A1A1-819F15CA757A}.Debug|x86.Build.0 = Debug|Any CPU - {66036B70-6C93-4E45-A1A1-819F15CA757A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {66036B70-6C93-4E45-A1A1-819F15CA757A}.Release|Any CPU.Build.0 = Release|Any CPU - {66036B70-6C93-4E45-A1A1-819F15CA757A}.Release|x64.ActiveCfg = Release|Any CPU - {66036B70-6C93-4E45-A1A1-819F15CA757A}.Release|x64.Build.0 = Release|Any CPU - {66036B70-6C93-4E45-A1A1-819F15CA757A}.Release|x86.ActiveCfg = Release|Any CPU - {66036B70-6C93-4E45-A1A1-819F15CA757A}.Release|x86.Build.0 = Release|Any CPU {FD37F740-A654-4117-BFB6-9112CE4C1D3B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {FD37F740-A654-4117-BFB6-9112CE4C1D3B}.Debug|Any CPU.Build.0 = Debug|Any CPU {FD37F740-A654-4117-BFB6-9112CE4C1D3B}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -1516,6 +1500,54 @@ Global {A5617A9D-C71E-44DE-936C-27611EB40A02}.Release|x64.Build.0 = Release|Any CPU {A5617A9D-C71E-44DE-936C-27611EB40A02}.Release|x86.ActiveCfg = Release|Any CPU {A5617A9D-C71E-44DE-936C-27611EB40A02}.Release|x86.Build.0 = Release|Any CPU + {D141CFEE-D10A-406B-8963-F86FA13732E3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D141CFEE-D10A-406B-8963-F86FA13732E3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D141CFEE-D10A-406B-8963-F86FA13732E3}.Debug|x64.ActiveCfg = Debug|Any CPU + {D141CFEE-D10A-406B-8963-F86FA13732E3}.Debug|x64.Build.0 = Debug|Any CPU + {D141CFEE-D10A-406B-8963-F86FA13732E3}.Debug|x86.ActiveCfg = Debug|Any CPU + {D141CFEE-D10A-406B-8963-F86FA13732E3}.Debug|x86.Build.0 = Debug|Any CPU + {D141CFEE-D10A-406B-8963-F86FA13732E3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D141CFEE-D10A-406B-8963-F86FA13732E3}.Release|Any CPU.Build.0 = Release|Any CPU + {D141CFEE-D10A-406B-8963-F86FA13732E3}.Release|x64.ActiveCfg = Release|Any CPU + {D141CFEE-D10A-406B-8963-F86FA13732E3}.Release|x64.Build.0 = Release|Any CPU + {D141CFEE-D10A-406B-8963-F86FA13732E3}.Release|x86.ActiveCfg = Release|Any CPU + {D141CFEE-D10A-406B-8963-F86FA13732E3}.Release|x86.Build.0 = Release|Any CPU + {F2E27E1C-2E47-42C1-9AC7-36265A381717}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F2E27E1C-2E47-42C1-9AC7-36265A381717}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F2E27E1C-2E47-42C1-9AC7-36265A381717}.Debug|x64.ActiveCfg = Debug|Any CPU + {F2E27E1C-2E47-42C1-9AC7-36265A381717}.Debug|x64.Build.0 = Debug|Any CPU + {F2E27E1C-2E47-42C1-9AC7-36265A381717}.Debug|x86.ActiveCfg = Debug|Any CPU + {F2E27E1C-2E47-42C1-9AC7-36265A381717}.Debug|x86.Build.0 = Debug|Any CPU + {F2E27E1C-2E47-42C1-9AC7-36265A381717}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F2E27E1C-2E47-42C1-9AC7-36265A381717}.Release|Any CPU.Build.0 = Release|Any CPU + {F2E27E1C-2E47-42C1-9AC7-36265A381717}.Release|x64.ActiveCfg = Release|Any CPU + {F2E27E1C-2E47-42C1-9AC7-36265A381717}.Release|x64.Build.0 = Release|Any CPU + {F2E27E1C-2E47-42C1-9AC7-36265A381717}.Release|x86.ActiveCfg = Release|Any CPU + {F2E27E1C-2E47-42C1-9AC7-36265A381717}.Release|x86.Build.0 = Release|Any CPU + {CA9948CA-B3FA-4C2E-A726-5E47BAD19457}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CA9948CA-B3FA-4C2E-A726-5E47BAD19457}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CA9948CA-B3FA-4C2E-A726-5E47BAD19457}.Debug|x64.ActiveCfg = Debug|Any CPU + {CA9948CA-B3FA-4C2E-A726-5E47BAD19457}.Debug|x64.Build.0 = Debug|Any CPU + {CA9948CA-B3FA-4C2E-A726-5E47BAD19457}.Debug|x86.ActiveCfg = Debug|Any CPU + {CA9948CA-B3FA-4C2E-A726-5E47BAD19457}.Debug|x86.Build.0 = Debug|Any CPU + {CA9948CA-B3FA-4C2E-A726-5E47BAD19457}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CA9948CA-B3FA-4C2E-A726-5E47BAD19457}.Release|Any CPU.Build.0 = Release|Any CPU + {CA9948CA-B3FA-4C2E-A726-5E47BAD19457}.Release|x64.ActiveCfg = Release|Any CPU + {CA9948CA-B3FA-4C2E-A726-5E47BAD19457}.Release|x64.Build.0 = Release|Any CPU + {CA9948CA-B3FA-4C2E-A726-5E47BAD19457}.Release|x86.ActiveCfg = Release|Any CPU + {CA9948CA-B3FA-4C2E-A726-5E47BAD19457}.Release|x86.Build.0 = Release|Any CPU + {97EA0A7D-FE5E-47D1-ADDC-4BFD702F55AB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {97EA0A7D-FE5E-47D1-ADDC-4BFD702F55AB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {97EA0A7D-FE5E-47D1-ADDC-4BFD702F55AB}.Debug|x64.ActiveCfg = Debug|Any CPU + {97EA0A7D-FE5E-47D1-ADDC-4BFD702F55AB}.Debug|x64.Build.0 = Debug|Any CPU + {97EA0A7D-FE5E-47D1-ADDC-4BFD702F55AB}.Debug|x86.ActiveCfg = Debug|Any CPU + {97EA0A7D-FE5E-47D1-ADDC-4BFD702F55AB}.Debug|x86.Build.0 = Debug|Any CPU + {97EA0A7D-FE5E-47D1-ADDC-4BFD702F55AB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {97EA0A7D-FE5E-47D1-ADDC-4BFD702F55AB}.Release|Any CPU.Build.0 = Release|Any CPU + {97EA0A7D-FE5E-47D1-ADDC-4BFD702F55AB}.Release|x64.ActiveCfg = Release|Any CPU + {97EA0A7D-FE5E-47D1-ADDC-4BFD702F55AB}.Release|x64.Build.0 = Release|Any CPU + {97EA0A7D-FE5E-47D1-ADDC-4BFD702F55AB}.Release|x86.ActiveCfg = Release|Any CPU + {97EA0A7D-FE5E-47D1-ADDC-4BFD702F55AB}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1528,9 +1560,7 @@ Global {E8AD67A4-77D3-4B85-AE19-4711388B62B1} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF} {E38FDBB0-08C1-444E-A449-69C8A59D721B} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF} {A6C8050D-7C18-4585-ADCF-833AC1765847} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF} - {08773DD6-6FED-4BF2-BD9F-C19D2CF919BB} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF} {A4859630-F9F7-4F5C-9FF3-6C013D7C58FA} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF} - {66036B70-6C93-4E45-A1A1-819F15CA757A} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF} {A7ABAC29-F73F-456D-AE54-46842CFC2E10} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF} {FD37F740-A654-4117-BFB6-9112CE4C1D3B} = {A7ABAC29-F73F-456D-AE54-46842CFC2E10} {C1E2C117-BE47-4E29-94B3-753262D97A5C} = {A7ABAC29-F73F-456D-AE54-46842CFC2E10} @@ -1626,9 +1656,14 @@ Global {BBF37AF9-8290-4B70-8BA8-0F6017B3B620} = {46E4300C-5726-4108-B9A2-18BB94EB26ED} {CD0EF85C-4187-4515-A355-E5A0D4485F40} = {BDE2397D-C53A-4783-8B3A-1F54F48A6926} {F31E8118-014E-4CCE-8A48-5282F7B9BB3E} = {BDE2397D-C53A-4783-8B3A-1F54F48A6926} - {FD9BD646-9D50-42ED-A3E1-90558BA0C6B2} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF} {B70F90C7-2696-4050-B24E-BF0308F4E059} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF} {A5617A9D-C71E-44DE-936C-27611EB40A02} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF} + {21BB9C13-20C1-4F2B-80E4-D7C64AA3BD05} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF} + {D141CFEE-D10A-406B-8963-F86FA13732E3} = {21BB9C13-20C1-4F2B-80E4-D7C64AA3BD05} + {F2E27E1C-2E47-42C1-9AC7-36265A381717} = {44E0D4F3-4430-4175-B482-0D1AEE4BB699} + {F65EFF0F-ACF3-46BD-9A8F-CDA94AF1885A} = {CCC82E97-7B58-43E2-BBBD-23D82F926367} + {CA9948CA-B3FA-4C2E-A726-5E47BAD19457} = {F65EFF0F-ACF3-46BD-9A8F-CDA94AF1885A} + {97EA0A7D-FE5E-47D1-ADDC-4BFD702F55AB} = {F65EFF0F-ACF3-46BD-9A8F-CDA94AF1885A} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {CC3C47E1-AD1A-4619-9CD3-E08A0148E5CE} diff --git a/src/Components/Components/perf/Microsoft.AspNetCore.Components.Performance.csproj b/src/Components/Components/perf/Microsoft.AspNetCore.Components.Performance.csproj index a893d64abd..f40a0a4098 100644 --- a/src/Components/Components/perf/Microsoft.AspNetCore.Components.Performance.csproj +++ b/src/Components/Components/perf/Microsoft.AspNetCore.Components.Performance.csproj @@ -10,7 +10,8 @@ - + +
diff --git a/src/Components/Components/ref/Microsoft.AspNetCore.Components.Manual.cs b/src/Components/Components/ref/Microsoft.AspNetCore.Components.Manual.cs index fe78ce3bd4..26646bf79b 100644 --- a/src/Components/Components/ref/Microsoft.AspNetCore.Components.Manual.cs +++ b/src/Components/Components/ref/Microsoft.AspNetCore.Components.Manual.cs @@ -46,32 +46,6 @@ namespace Microsoft.AspNetCore.Components bool HasDelegate { get; } object UnpackForRenderTree(); } - [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] - public readonly partial struct EventCallback : Microsoft.AspNetCore.Components.IEventCallback - { - public static readonly Microsoft.AspNetCore.Components.EventCallback Empty; - public static readonly Microsoft.AspNetCore.Components.EventCallbackFactory Factory; - internal readonly MulticastDelegate Delegate; - internal readonly IHandleEvent Receiver; - public EventCallback(Microsoft.AspNetCore.Components.IHandleEvent receiver, System.MulticastDelegate @delegate) { throw null; } - public bool HasDelegate { get { throw null; } } - internal bool RequiresExplicitReceiver { get { throw null; } } - public System.Threading.Tasks.Task InvokeAsync(object arg) { throw null; } - object Microsoft.AspNetCore.Components.IEventCallback.UnpackForRenderTree() { throw null; } - } - [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] - public readonly partial struct EventCallback : Microsoft.AspNetCore.Components.IEventCallback - { - public static readonly Microsoft.AspNetCore.Components.EventCallback Empty; - internal readonly MulticastDelegate Delegate; - internal readonly IHandleEvent Receiver; - public EventCallback(Microsoft.AspNetCore.Components.IHandleEvent receiver, System.MulticastDelegate @delegate) { throw null; } - public bool HasDelegate { get { throw null; } } - internal bool RequiresExplicitReceiver { get { throw null; } } - internal Microsoft.AspNetCore.Components.EventCallback AsUntyped() { throw null; } - public System.Threading.Tasks.Task InvokeAsync(TValue arg) { throw null; } - object Microsoft.AspNetCore.Components.IEventCallback.UnpackForRenderTree() { throw null; } - } internal partial interface ICascadingValueComponent { object CurrentValue { get; } diff --git a/src/Components/Components/ref/Microsoft.AspNetCore.Components.csproj b/src/Components/Components/ref/Microsoft.AspNetCore.Components.csproj index f1aa3dab10..50f888c26a 100644 --- a/src/Components/Components/ref/Microsoft.AspNetCore.Components.csproj +++ b/src/Components/Components/ref/Microsoft.AspNetCore.Components.csproj @@ -2,6 +2,7 @@ netstandard2.0;$(DefaultNetCoreTargetFramework) + $(DefaultNetCoreTargetFramework) @@ -9,7 +10,6 @@ - @@ -18,6 +18,5 @@ - diff --git a/src/Components/Components/ref/Microsoft.AspNetCore.Components.netcoreapp.cs b/src/Components/Components/ref/Microsoft.AspNetCore.Components.netcoreapp.cs index a62693a5d9..17c4d7a4f5 100644 --- a/src/Components/Components/ref/Microsoft.AspNetCore.Components.netcoreapp.cs +++ b/src/Components/Components/ref/Microsoft.AspNetCore.Components.netcoreapp.cs @@ -12,6 +12,7 @@ namespace Microsoft.AspNetCore.Components public static string FormatValue(System.DateTimeOffset value, string format, System.Globalization.CultureInfo culture = null) { throw null; } public static string FormatValue(decimal value, System.Globalization.CultureInfo culture = null) { throw null; } public static string FormatValue(double value, System.Globalization.CultureInfo culture = null) { throw null; } + public static string FormatValue(short value, System.Globalization.CultureInfo culture = null) { throw null; } public static string FormatValue(int value, System.Globalization.CultureInfo culture = null) { throw null; } public static string FormatValue(long value, System.Globalization.CultureInfo culture = null) { throw null; } public static bool? FormatValue(bool? value, System.Globalization.CultureInfo culture = null) { throw null; } @@ -21,6 +22,7 @@ namespace Microsoft.AspNetCore.Components public static string FormatValue(System.DateTime? value, string format, System.Globalization.CultureInfo culture = null) { throw null; } public static string FormatValue(decimal? value, System.Globalization.CultureInfo culture = null) { throw null; } public static string FormatValue(double? value, System.Globalization.CultureInfo culture = null) { throw null; } + public static string FormatValue(short? value, System.Globalization.CultureInfo culture = null) { throw null; } public static string FormatValue(int? value, System.Globalization.CultureInfo culture = null) { throw null; } public static string FormatValue(long? value, System.Globalization.CultureInfo culture = null) { throw null; } public static string FormatValue(float? value, System.Globalization.CultureInfo culture = null) { throw null; } @@ -47,6 +49,8 @@ namespace Microsoft.AspNetCore.Components public static bool TryConvertToNullableFloat(object obj, System.Globalization.CultureInfo culture, out float? value) { throw null; } public static bool TryConvertToNullableInt(object obj, System.Globalization.CultureInfo culture, out int? value) { throw null; } public static bool TryConvertToNullableLong(object obj, System.Globalization.CultureInfo culture, out long? value) { throw null; } + public static bool TryConvertToNullableShort(object obj, System.Globalization.CultureInfo culture, out short? value) { throw null; } + public static bool TryConvertToShort(object obj, System.Globalization.CultureInfo culture, out short value) { throw null; } public static bool TryConvertToString(object obj, System.Globalization.CultureInfo culture, out string value) { throw null; } public static bool TryConvertTo(object obj, System.Globalization.CultureInfo culture, out T value) { throw null; } } @@ -54,35 +58,35 @@ namespace Microsoft.AspNetCore.Components public sealed partial class BindElementAttribute : System.Attribute { public BindElementAttribute(string element, string suffix, string valueAttribute, string changeAttribute) { } - public string ChangeAttribute { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public string Element { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public string Suffix { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public string ValueAttribute { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public string ChangeAttribute { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public string Element { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public string Suffix { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public string ValueAttribute { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } } [System.AttributeUsageAttribute(System.AttributeTargets.Property, AllowMultiple=false, Inherited=true)] public sealed partial class CascadingParameterAttribute : System.Attribute { public CascadingParameterAttribute() { } - public string Name { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public string Name { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } public partial class CascadingValue : Microsoft.AspNetCore.Components.IComponent { public CascadingValue() { } [Microsoft.AspNetCore.Components.ParameterAttribute] - public Microsoft.AspNetCore.Components.RenderFragment ChildContent { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.AspNetCore.Components.RenderFragment ChildContent { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } [Microsoft.AspNetCore.Components.ParameterAttribute] - public bool IsFixed { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public bool IsFixed { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } [Microsoft.AspNetCore.Components.ParameterAttribute] - public string Name { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public string Name { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } [Microsoft.AspNetCore.Components.ParameterAttribute] - public TValue Value { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public TValue Value { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public void Attach(Microsoft.AspNetCore.Components.RenderHandle renderHandle) { } public System.Threading.Tasks.Task SetParametersAsync(Microsoft.AspNetCore.Components.ParameterView parameters) { throw null; } } public partial class ChangeEventArgs : System.EventArgs { public ChangeEventArgs() { } - public object Value { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public object Value { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } public abstract partial class ComponentBase : Microsoft.AspNetCore.Components.IComponent, Microsoft.AspNetCore.Components.IHandleAfterRender, Microsoft.AspNetCore.Components.IHandleEvent { @@ -119,8 +123,20 @@ namespace Microsoft.AspNetCore.Components public readonly partial struct ElementReference { private readonly object _dummy; + private readonly int _dummyPrimitive; public ElementReference(string id) { throw null; } - public string Id { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public string Id { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + } + [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] + public readonly partial struct EventCallback + { + private readonly object _dummy; + private readonly int _dummyPrimitive; + public static readonly Microsoft.AspNetCore.Components.EventCallback Empty; + public static readonly Microsoft.AspNetCore.Components.EventCallbackFactory Factory; + public EventCallback(Microsoft.AspNetCore.Components.IHandleEvent receiver, System.MulticastDelegate @delegate) { throw null; } + public bool HasDelegate { get { throw null; } } + public System.Threading.Tasks.Task InvokeAsync(object arg) { throw null; } } public sealed partial class EventCallbackFactory { @@ -153,6 +169,7 @@ namespace Microsoft.AspNetCore.Components public static Microsoft.AspNetCore.Components.EventCallback CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory factory, object receiver, System.Action setter, System.DateTime existingValue, string format, System.Globalization.CultureInfo culture = null) { throw null; } public static Microsoft.AspNetCore.Components.EventCallback CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory factory, object receiver, System.Action setter, decimal existingValue, System.Globalization.CultureInfo culture = null) { throw null; } public static Microsoft.AspNetCore.Components.EventCallback CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory factory, object receiver, System.Action setter, double existingValue, System.Globalization.CultureInfo culture = null) { throw null; } + public static Microsoft.AspNetCore.Components.EventCallback CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory factory, object receiver, System.Action setter, short existingValue, System.Globalization.CultureInfo culture = null) { throw null; } public static Microsoft.AspNetCore.Components.EventCallback CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory factory, object receiver, System.Action setter, int existingValue, System.Globalization.CultureInfo culture = null) { throw null; } public static Microsoft.AspNetCore.Components.EventCallback CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory factory, object receiver, System.Action setter, long existingValue, System.Globalization.CultureInfo culture = null) { throw null; } public static Microsoft.AspNetCore.Components.EventCallback CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory factory, object receiver, System.Action setter, bool? existingValue, System.Globalization.CultureInfo culture = null) { throw null; } @@ -162,6 +179,7 @@ namespace Microsoft.AspNetCore.Components public static Microsoft.AspNetCore.Components.EventCallback CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory factory, object receiver, System.Action setter, System.DateTime? existingValue, string format, System.Globalization.CultureInfo culture = null) { throw null; } public static Microsoft.AspNetCore.Components.EventCallback CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory factory, object receiver, System.Action setter, decimal? existingValue, System.Globalization.CultureInfo culture = null) { throw null; } public static Microsoft.AspNetCore.Components.EventCallback CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory factory, object receiver, System.Action setter, double? existingValue, System.Globalization.CultureInfo culture = null) { throw null; } + public static Microsoft.AspNetCore.Components.EventCallback CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory factory, object receiver, System.Action setter, short? existingValue, System.Globalization.CultureInfo culture = null) { throw null; } public static Microsoft.AspNetCore.Components.EventCallback CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory factory, object receiver, System.Action setter, int? existingValue, System.Globalization.CultureInfo culture = null) { throw null; } public static Microsoft.AspNetCore.Components.EventCallback CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory factory, object receiver, System.Action setter, long? existingValue, System.Globalization.CultureInfo culture = null) { throw null; } public static Microsoft.AspNetCore.Components.EventCallback CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory factory, object receiver, System.Action setter, float? existingValue, System.Globalization.CultureInfo culture = null) { throw null; } @@ -180,19 +198,30 @@ namespace Microsoft.AspNetCore.Components public readonly partial struct EventCallbackWorkItem { private readonly object _dummy; + private readonly int _dummyPrimitive; public static readonly Microsoft.AspNetCore.Components.EventCallbackWorkItem Empty; public EventCallbackWorkItem(System.MulticastDelegate @delegate) { throw null; } public System.Threading.Tasks.Task InvokeAsync(object arg) { throw null; } } + [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] + public readonly partial struct EventCallback + { + private readonly object _dummy; + private readonly int _dummyPrimitive; + public static readonly Microsoft.AspNetCore.Components.EventCallback Empty; + public EventCallback(Microsoft.AspNetCore.Components.IHandleEvent receiver, System.MulticastDelegate @delegate) { throw null; } + public bool HasDelegate { get { throw null; } } + public System.Threading.Tasks.Task InvokeAsync(TValue arg) { throw null; } + } [System.AttributeUsageAttribute(System.AttributeTargets.Class, AllowMultiple=true, Inherited=true)] public sealed partial class EventHandlerAttribute : System.Attribute { public EventHandlerAttribute(string attributeName, System.Type eventArgsType) { } public EventHandlerAttribute(string attributeName, System.Type eventArgsType, bool enableStopPropagation, bool enablePreventDefault) { } - public string AttributeName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public bool EnablePreventDefault { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public bool EnableStopPropagation { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public System.Type EventArgsType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public string AttributeName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public bool EnablePreventDefault { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public bool EnableStopPropagation { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public System.Type EventArgsType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } } public partial interface IComponent { @@ -216,21 +245,21 @@ namespace Microsoft.AspNetCore.Components public sealed partial class LayoutAttribute : System.Attribute { public LayoutAttribute(System.Type layoutType) { } - public System.Type LayoutType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public System.Type LayoutType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } } public abstract partial class LayoutComponentBase : Microsoft.AspNetCore.Components.ComponentBase { protected LayoutComponentBase() { } [Microsoft.AspNetCore.Components.ParameterAttribute] - public Microsoft.AspNetCore.Components.RenderFragment Body { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.AspNetCore.Components.RenderFragment Body { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } public partial class LayoutView : Microsoft.AspNetCore.Components.IComponent { public LayoutView() { } [Microsoft.AspNetCore.Components.ParameterAttribute] - public Microsoft.AspNetCore.Components.RenderFragment ChildContent { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.AspNetCore.Components.RenderFragment ChildContent { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } [Microsoft.AspNetCore.Components.ParameterAttribute] - public System.Type Layout { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public System.Type Layout { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public void Attach(Microsoft.AspNetCore.Components.RenderHandle renderHandle) { } public System.Threading.Tasks.Task SetParametersAsync(Microsoft.AspNetCore.Components.ParameterView parameters) { throw null; } } @@ -242,15 +271,16 @@ namespace Microsoft.AspNetCore.Components public readonly partial struct MarkupString { private readonly object _dummy; + private readonly int _dummyPrimitive; public MarkupString(string value) { throw null; } - public string Value { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public string Value { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } public static explicit operator Microsoft.AspNetCore.Components.MarkupString (string value) { throw null; } public override string ToString() { throw null; } } public partial class NavigationException : System.Exception { public NavigationException(string uri) { } - public string Location { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public string Location { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } } public abstract partial class NavigationManager { @@ -269,7 +299,7 @@ namespace Microsoft.AspNetCore.Components public abstract partial class OwningComponentBase : Microsoft.AspNetCore.Components.ComponentBase, System.IDisposable { protected OwningComponentBase() { } - protected bool IsDisposed { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + protected bool IsDisposed { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } protected System.IServiceProvider ScopedServices { get { throw null; } } protected virtual void Dispose(bool disposing) { } void System.IDisposable.Dispose() { } @@ -283,16 +313,16 @@ namespace Microsoft.AspNetCore.Components public sealed partial class ParameterAttribute : System.Attribute { public ParameterAttribute() { } - public bool CaptureUnmatchedValues { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public bool CaptureUnmatchedValues { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] public readonly partial struct ParameterValue { private readonly object _dummy; private readonly int _dummyPrimitive; - public bool Cascading { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public string Name { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public object Value { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public bool Cascading { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public string Name { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public object Value { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } } [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] public readonly partial struct ParameterView @@ -331,21 +361,21 @@ namespace Microsoft.AspNetCore.Components public sealed partial class RouteAttribute : System.Attribute { public RouteAttribute(string template) { } - public string Template { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public string Template { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } } public sealed partial class RouteData { public RouteData(System.Type pageType, System.Collections.Generic.IReadOnlyDictionary routeValues) { } - public System.Type PageType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public System.Collections.Generic.IReadOnlyDictionary RouteValues { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public System.Type PageType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public System.Collections.Generic.IReadOnlyDictionary RouteValues { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } } public partial class RouteView : Microsoft.AspNetCore.Components.IComponent { public RouteView() { } [Microsoft.AspNetCore.Components.ParameterAttribute] - public System.Type DefaultLayout { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public System.Type DefaultLayout { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } [Microsoft.AspNetCore.Components.ParameterAttribute] - public Microsoft.AspNetCore.Components.RouteData RouteData { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.AspNetCore.Components.RouteData RouteData { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public void Attach(Microsoft.AspNetCore.Components.RenderHandle renderHandle) { } protected virtual void Render(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder builder) { } public System.Threading.Tasks.Task SetParametersAsync(Microsoft.AspNetCore.Components.ParameterView parameters) { throw null; } @@ -420,17 +450,18 @@ namespace Microsoft.AspNetCore.Components.RenderTree public partial class EventFieldInfo { public EventFieldInfo() { } - public int ComponentId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public object FieldValue { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public int ComponentId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public object FieldValue { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] public readonly partial struct RenderBatch { private readonly object _dummy; - public Microsoft.AspNetCore.Components.RenderTree.ArrayRange DisposedComponentIDs { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public Microsoft.AspNetCore.Components.RenderTree.ArrayRange DisposedEventHandlerIDs { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public Microsoft.AspNetCore.Components.RenderTree.ArrayRange ReferenceFrames { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public Microsoft.AspNetCore.Components.RenderTree.ArrayRange UpdatedComponents { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + private readonly int _dummyPrimitive; + public Microsoft.AspNetCore.Components.RenderTree.ArrayRange DisposedComponentIDs { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public Microsoft.AspNetCore.Components.RenderTree.ArrayRange DisposedEventHandlerIDs { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public Microsoft.AspNetCore.Components.RenderTree.ArrayRange ReferenceFrames { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public Microsoft.AspNetCore.Components.RenderTree.ArrayRange UpdatedComponents { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } } public abstract partial class Renderer : System.IDisposable { @@ -509,20 +540,20 @@ namespace Microsoft.AspNetCore.Components.Routing public partial class LocationChangedEventArgs : System.EventArgs { public LocationChangedEventArgs(string location, bool isNavigationIntercepted) { } - public bool IsNavigationIntercepted { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public string Location { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public bool IsNavigationIntercepted { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public string Location { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } } public partial class Router : Microsoft.AspNetCore.Components.IComponent, Microsoft.AspNetCore.Components.IHandleAfterRender, System.IDisposable { public Router() { } [Microsoft.AspNetCore.Components.ParameterAttribute] - public System.Collections.Generic.IEnumerable AdditionalAssemblies { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public System.Collections.Generic.IEnumerable AdditionalAssemblies { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } [Microsoft.AspNetCore.Components.ParameterAttribute] - public System.Reflection.Assembly AppAssembly { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public System.Reflection.Assembly AppAssembly { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } [Microsoft.AspNetCore.Components.ParameterAttribute] - public Microsoft.AspNetCore.Components.RenderFragment Found { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.AspNetCore.Components.RenderFragment Found { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } [Microsoft.AspNetCore.Components.ParameterAttribute] - public Microsoft.AspNetCore.Components.RenderFragment NotFound { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.AspNetCore.Components.RenderFragment NotFound { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public void Attach(Microsoft.AspNetCore.Components.RenderHandle renderHandle) { } public void Dispose() { } System.Threading.Tasks.Task Microsoft.AspNetCore.Components.IHandleAfterRender.OnAfterRenderAsync() { throw null; } diff --git a/src/Components/Components/ref/Microsoft.AspNetCore.Components.netstandard2.0.cs b/src/Components/Components/ref/Microsoft.AspNetCore.Components.netstandard2.0.cs index a62693a5d9..17c4d7a4f5 100644 --- a/src/Components/Components/ref/Microsoft.AspNetCore.Components.netstandard2.0.cs +++ b/src/Components/Components/ref/Microsoft.AspNetCore.Components.netstandard2.0.cs @@ -12,6 +12,7 @@ namespace Microsoft.AspNetCore.Components public static string FormatValue(System.DateTimeOffset value, string format, System.Globalization.CultureInfo culture = null) { throw null; } public static string FormatValue(decimal value, System.Globalization.CultureInfo culture = null) { throw null; } public static string FormatValue(double value, System.Globalization.CultureInfo culture = null) { throw null; } + public static string FormatValue(short value, System.Globalization.CultureInfo culture = null) { throw null; } public static string FormatValue(int value, System.Globalization.CultureInfo culture = null) { throw null; } public static string FormatValue(long value, System.Globalization.CultureInfo culture = null) { throw null; } public static bool? FormatValue(bool? value, System.Globalization.CultureInfo culture = null) { throw null; } @@ -21,6 +22,7 @@ namespace Microsoft.AspNetCore.Components public static string FormatValue(System.DateTime? value, string format, System.Globalization.CultureInfo culture = null) { throw null; } public static string FormatValue(decimal? value, System.Globalization.CultureInfo culture = null) { throw null; } public static string FormatValue(double? value, System.Globalization.CultureInfo culture = null) { throw null; } + public static string FormatValue(short? value, System.Globalization.CultureInfo culture = null) { throw null; } public static string FormatValue(int? value, System.Globalization.CultureInfo culture = null) { throw null; } public static string FormatValue(long? value, System.Globalization.CultureInfo culture = null) { throw null; } public static string FormatValue(float? value, System.Globalization.CultureInfo culture = null) { throw null; } @@ -47,6 +49,8 @@ namespace Microsoft.AspNetCore.Components public static bool TryConvertToNullableFloat(object obj, System.Globalization.CultureInfo culture, out float? value) { throw null; } public static bool TryConvertToNullableInt(object obj, System.Globalization.CultureInfo culture, out int? value) { throw null; } public static bool TryConvertToNullableLong(object obj, System.Globalization.CultureInfo culture, out long? value) { throw null; } + public static bool TryConvertToNullableShort(object obj, System.Globalization.CultureInfo culture, out short? value) { throw null; } + public static bool TryConvertToShort(object obj, System.Globalization.CultureInfo culture, out short value) { throw null; } public static bool TryConvertToString(object obj, System.Globalization.CultureInfo culture, out string value) { throw null; } public static bool TryConvertTo(object obj, System.Globalization.CultureInfo culture, out T value) { throw null; } } @@ -54,35 +58,35 @@ namespace Microsoft.AspNetCore.Components public sealed partial class BindElementAttribute : System.Attribute { public BindElementAttribute(string element, string suffix, string valueAttribute, string changeAttribute) { } - public string ChangeAttribute { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public string Element { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public string Suffix { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public string ValueAttribute { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public string ChangeAttribute { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public string Element { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public string Suffix { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public string ValueAttribute { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } } [System.AttributeUsageAttribute(System.AttributeTargets.Property, AllowMultiple=false, Inherited=true)] public sealed partial class CascadingParameterAttribute : System.Attribute { public CascadingParameterAttribute() { } - public string Name { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public string Name { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } public partial class CascadingValue : Microsoft.AspNetCore.Components.IComponent { public CascadingValue() { } [Microsoft.AspNetCore.Components.ParameterAttribute] - public Microsoft.AspNetCore.Components.RenderFragment ChildContent { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.AspNetCore.Components.RenderFragment ChildContent { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } [Microsoft.AspNetCore.Components.ParameterAttribute] - public bool IsFixed { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public bool IsFixed { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } [Microsoft.AspNetCore.Components.ParameterAttribute] - public string Name { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public string Name { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } [Microsoft.AspNetCore.Components.ParameterAttribute] - public TValue Value { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public TValue Value { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public void Attach(Microsoft.AspNetCore.Components.RenderHandle renderHandle) { } public System.Threading.Tasks.Task SetParametersAsync(Microsoft.AspNetCore.Components.ParameterView parameters) { throw null; } } public partial class ChangeEventArgs : System.EventArgs { public ChangeEventArgs() { } - public object Value { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public object Value { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } public abstract partial class ComponentBase : Microsoft.AspNetCore.Components.IComponent, Microsoft.AspNetCore.Components.IHandleAfterRender, Microsoft.AspNetCore.Components.IHandleEvent { @@ -119,8 +123,20 @@ namespace Microsoft.AspNetCore.Components public readonly partial struct ElementReference { private readonly object _dummy; + private readonly int _dummyPrimitive; public ElementReference(string id) { throw null; } - public string Id { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public string Id { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + } + [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] + public readonly partial struct EventCallback + { + private readonly object _dummy; + private readonly int _dummyPrimitive; + public static readonly Microsoft.AspNetCore.Components.EventCallback Empty; + public static readonly Microsoft.AspNetCore.Components.EventCallbackFactory Factory; + public EventCallback(Microsoft.AspNetCore.Components.IHandleEvent receiver, System.MulticastDelegate @delegate) { throw null; } + public bool HasDelegate { get { throw null; } } + public System.Threading.Tasks.Task InvokeAsync(object arg) { throw null; } } public sealed partial class EventCallbackFactory { @@ -153,6 +169,7 @@ namespace Microsoft.AspNetCore.Components public static Microsoft.AspNetCore.Components.EventCallback CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory factory, object receiver, System.Action setter, System.DateTime existingValue, string format, System.Globalization.CultureInfo culture = null) { throw null; } public static Microsoft.AspNetCore.Components.EventCallback CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory factory, object receiver, System.Action setter, decimal existingValue, System.Globalization.CultureInfo culture = null) { throw null; } public static Microsoft.AspNetCore.Components.EventCallback CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory factory, object receiver, System.Action setter, double existingValue, System.Globalization.CultureInfo culture = null) { throw null; } + public static Microsoft.AspNetCore.Components.EventCallback CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory factory, object receiver, System.Action setter, short existingValue, System.Globalization.CultureInfo culture = null) { throw null; } public static Microsoft.AspNetCore.Components.EventCallback CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory factory, object receiver, System.Action setter, int existingValue, System.Globalization.CultureInfo culture = null) { throw null; } public static Microsoft.AspNetCore.Components.EventCallback CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory factory, object receiver, System.Action setter, long existingValue, System.Globalization.CultureInfo culture = null) { throw null; } public static Microsoft.AspNetCore.Components.EventCallback CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory factory, object receiver, System.Action setter, bool? existingValue, System.Globalization.CultureInfo culture = null) { throw null; } @@ -162,6 +179,7 @@ namespace Microsoft.AspNetCore.Components public static Microsoft.AspNetCore.Components.EventCallback CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory factory, object receiver, System.Action setter, System.DateTime? existingValue, string format, System.Globalization.CultureInfo culture = null) { throw null; } public static Microsoft.AspNetCore.Components.EventCallback CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory factory, object receiver, System.Action setter, decimal? existingValue, System.Globalization.CultureInfo culture = null) { throw null; } public static Microsoft.AspNetCore.Components.EventCallback CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory factory, object receiver, System.Action setter, double? existingValue, System.Globalization.CultureInfo culture = null) { throw null; } + public static Microsoft.AspNetCore.Components.EventCallback CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory factory, object receiver, System.Action setter, short? existingValue, System.Globalization.CultureInfo culture = null) { throw null; } public static Microsoft.AspNetCore.Components.EventCallback CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory factory, object receiver, System.Action setter, int? existingValue, System.Globalization.CultureInfo culture = null) { throw null; } public static Microsoft.AspNetCore.Components.EventCallback CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory factory, object receiver, System.Action setter, long? existingValue, System.Globalization.CultureInfo culture = null) { throw null; } public static Microsoft.AspNetCore.Components.EventCallback CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory factory, object receiver, System.Action setter, float? existingValue, System.Globalization.CultureInfo culture = null) { throw null; } @@ -180,19 +198,30 @@ namespace Microsoft.AspNetCore.Components public readonly partial struct EventCallbackWorkItem { private readonly object _dummy; + private readonly int _dummyPrimitive; public static readonly Microsoft.AspNetCore.Components.EventCallbackWorkItem Empty; public EventCallbackWorkItem(System.MulticastDelegate @delegate) { throw null; } public System.Threading.Tasks.Task InvokeAsync(object arg) { throw null; } } + [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] + public readonly partial struct EventCallback + { + private readonly object _dummy; + private readonly int _dummyPrimitive; + public static readonly Microsoft.AspNetCore.Components.EventCallback Empty; + public EventCallback(Microsoft.AspNetCore.Components.IHandleEvent receiver, System.MulticastDelegate @delegate) { throw null; } + public bool HasDelegate { get { throw null; } } + public System.Threading.Tasks.Task InvokeAsync(TValue arg) { throw null; } + } [System.AttributeUsageAttribute(System.AttributeTargets.Class, AllowMultiple=true, Inherited=true)] public sealed partial class EventHandlerAttribute : System.Attribute { public EventHandlerAttribute(string attributeName, System.Type eventArgsType) { } public EventHandlerAttribute(string attributeName, System.Type eventArgsType, bool enableStopPropagation, bool enablePreventDefault) { } - public string AttributeName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public bool EnablePreventDefault { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public bool EnableStopPropagation { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public System.Type EventArgsType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public string AttributeName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public bool EnablePreventDefault { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public bool EnableStopPropagation { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public System.Type EventArgsType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } } public partial interface IComponent { @@ -216,21 +245,21 @@ namespace Microsoft.AspNetCore.Components public sealed partial class LayoutAttribute : System.Attribute { public LayoutAttribute(System.Type layoutType) { } - public System.Type LayoutType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public System.Type LayoutType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } } public abstract partial class LayoutComponentBase : Microsoft.AspNetCore.Components.ComponentBase { protected LayoutComponentBase() { } [Microsoft.AspNetCore.Components.ParameterAttribute] - public Microsoft.AspNetCore.Components.RenderFragment Body { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.AspNetCore.Components.RenderFragment Body { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } public partial class LayoutView : Microsoft.AspNetCore.Components.IComponent { public LayoutView() { } [Microsoft.AspNetCore.Components.ParameterAttribute] - public Microsoft.AspNetCore.Components.RenderFragment ChildContent { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.AspNetCore.Components.RenderFragment ChildContent { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } [Microsoft.AspNetCore.Components.ParameterAttribute] - public System.Type Layout { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public System.Type Layout { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public void Attach(Microsoft.AspNetCore.Components.RenderHandle renderHandle) { } public System.Threading.Tasks.Task SetParametersAsync(Microsoft.AspNetCore.Components.ParameterView parameters) { throw null; } } @@ -242,15 +271,16 @@ namespace Microsoft.AspNetCore.Components public readonly partial struct MarkupString { private readonly object _dummy; + private readonly int _dummyPrimitive; public MarkupString(string value) { throw null; } - public string Value { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public string Value { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } public static explicit operator Microsoft.AspNetCore.Components.MarkupString (string value) { throw null; } public override string ToString() { throw null; } } public partial class NavigationException : System.Exception { public NavigationException(string uri) { } - public string Location { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public string Location { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } } public abstract partial class NavigationManager { @@ -269,7 +299,7 @@ namespace Microsoft.AspNetCore.Components public abstract partial class OwningComponentBase : Microsoft.AspNetCore.Components.ComponentBase, System.IDisposable { protected OwningComponentBase() { } - protected bool IsDisposed { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + protected bool IsDisposed { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } protected System.IServiceProvider ScopedServices { get { throw null; } } protected virtual void Dispose(bool disposing) { } void System.IDisposable.Dispose() { } @@ -283,16 +313,16 @@ namespace Microsoft.AspNetCore.Components public sealed partial class ParameterAttribute : System.Attribute { public ParameterAttribute() { } - public bool CaptureUnmatchedValues { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public bool CaptureUnmatchedValues { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] public readonly partial struct ParameterValue { private readonly object _dummy; private readonly int _dummyPrimitive; - public bool Cascading { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public string Name { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public object Value { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public bool Cascading { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public string Name { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public object Value { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } } [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] public readonly partial struct ParameterView @@ -331,21 +361,21 @@ namespace Microsoft.AspNetCore.Components public sealed partial class RouteAttribute : System.Attribute { public RouteAttribute(string template) { } - public string Template { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public string Template { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } } public sealed partial class RouteData { public RouteData(System.Type pageType, System.Collections.Generic.IReadOnlyDictionary routeValues) { } - public System.Type PageType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public System.Collections.Generic.IReadOnlyDictionary RouteValues { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public System.Type PageType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public System.Collections.Generic.IReadOnlyDictionary RouteValues { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } } public partial class RouteView : Microsoft.AspNetCore.Components.IComponent { public RouteView() { } [Microsoft.AspNetCore.Components.ParameterAttribute] - public System.Type DefaultLayout { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public System.Type DefaultLayout { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } [Microsoft.AspNetCore.Components.ParameterAttribute] - public Microsoft.AspNetCore.Components.RouteData RouteData { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.AspNetCore.Components.RouteData RouteData { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public void Attach(Microsoft.AspNetCore.Components.RenderHandle renderHandle) { } protected virtual void Render(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder builder) { } public System.Threading.Tasks.Task SetParametersAsync(Microsoft.AspNetCore.Components.ParameterView parameters) { throw null; } @@ -420,17 +450,18 @@ namespace Microsoft.AspNetCore.Components.RenderTree public partial class EventFieldInfo { public EventFieldInfo() { } - public int ComponentId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public object FieldValue { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public int ComponentId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public object FieldValue { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] public readonly partial struct RenderBatch { private readonly object _dummy; - public Microsoft.AspNetCore.Components.RenderTree.ArrayRange DisposedComponentIDs { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public Microsoft.AspNetCore.Components.RenderTree.ArrayRange DisposedEventHandlerIDs { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public Microsoft.AspNetCore.Components.RenderTree.ArrayRange ReferenceFrames { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public Microsoft.AspNetCore.Components.RenderTree.ArrayRange UpdatedComponents { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + private readonly int _dummyPrimitive; + public Microsoft.AspNetCore.Components.RenderTree.ArrayRange DisposedComponentIDs { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public Microsoft.AspNetCore.Components.RenderTree.ArrayRange DisposedEventHandlerIDs { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public Microsoft.AspNetCore.Components.RenderTree.ArrayRange ReferenceFrames { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public Microsoft.AspNetCore.Components.RenderTree.ArrayRange UpdatedComponents { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } } public abstract partial class Renderer : System.IDisposable { @@ -509,20 +540,20 @@ namespace Microsoft.AspNetCore.Components.Routing public partial class LocationChangedEventArgs : System.EventArgs { public LocationChangedEventArgs(string location, bool isNavigationIntercepted) { } - public bool IsNavigationIntercepted { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public string Location { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public bool IsNavigationIntercepted { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public string Location { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } } public partial class Router : Microsoft.AspNetCore.Components.IComponent, Microsoft.AspNetCore.Components.IHandleAfterRender, System.IDisposable { public Router() { } [Microsoft.AspNetCore.Components.ParameterAttribute] - public System.Collections.Generic.IEnumerable AdditionalAssemblies { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public System.Collections.Generic.IEnumerable AdditionalAssemblies { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } [Microsoft.AspNetCore.Components.ParameterAttribute] - public System.Reflection.Assembly AppAssembly { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public System.Reflection.Assembly AppAssembly { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } [Microsoft.AspNetCore.Components.ParameterAttribute] - public Microsoft.AspNetCore.Components.RenderFragment Found { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.AspNetCore.Components.RenderFragment Found { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } [Microsoft.AspNetCore.Components.ParameterAttribute] - public Microsoft.AspNetCore.Components.RenderFragment NotFound { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.AspNetCore.Components.RenderFragment NotFound { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public void Attach(Microsoft.AspNetCore.Components.RenderHandle renderHandle) { } public void Dispose() { } System.Threading.Tasks.Task Microsoft.AspNetCore.Components.IHandleAfterRender.OnAfterRenderAsync() { throw null; } diff --git a/src/Components/Components/src/BindConverter.cs b/src/Components/Components/src/BindConverter.cs index f77689bde2..0aadb8077e 100644 --- a/src/Components/Components/src/BindConverter.cs +++ b/src/Components/Components/src/BindConverter.cs @@ -15,7 +15,7 @@ namespace Microsoft.AspNetCore.Components // // Perf: our conversion routines present a regular API surface that allows us to specialize on types to avoid boxing. // for instance, many of these types could be cast to IFormattable to do the appropriate formatting, but that's going - // to allocate. + // to allocate. public static class BindConverter { private static object BoxedTrue = true; @@ -158,6 +158,41 @@ namespace Microsoft.AspNetCore.Components return value.Value.ToString(culture ?? CultureInfo.CurrentCulture); } + /// + /// Formats the provided for inclusion in an attribute. + /// + /// The value to format. + /// + /// The to use while formatting. Defaults to . + /// + /// The formatted value. + public static string FormatValue(short value, CultureInfo culture = null) => FormatShortValueCore(value, culture); + + private static string FormatShortValueCore(short value, CultureInfo culture) + { + return value.ToString(culture ?? CultureInfo.CurrentCulture); + } + + /// + /// Formats the provided for inclusion in an attribute. + /// + /// The value to format. + /// + /// The to use while formatting. Defaults to . + /// + /// The formatted value. + public static string FormatValue(short? value, CultureInfo culture = null) => FormatNullableShortValueCore(value, culture); + + private static string FormatNullableShortValueCore(short? value, CultureInfo culture) + { + if (value == null) + { + return null; + } + + return value.Value.ToString(culture ?? CultureInfo.CurrentCulture); + } + /// /// Formats the provided for inclusion in an attribute. /// @@ -430,7 +465,7 @@ namespace Microsoft.AspNetCore.Components private static string FormatEnumValueCore(T value, CultureInfo culture) where T : struct, Enum { - return value.ToString(); // The overload that acccepts a culture is [Obsolete] + return value.ToString(); // The overload that accepts a culture is [Obsolete] } private static string FormatNullableEnumValueCore(T? value, CultureInfo culture) where T : struct, Enum @@ -440,7 +475,7 @@ namespace Microsoft.AspNetCore.Components return null; } - return value.Value.ToString(); // The overload that acccepts a culture is [Obsolete] + return value.Value.ToString(); // The overload that accepts a culture is [Obsolete] } /// @@ -649,6 +684,71 @@ namespace Microsoft.AspNetCore.Components return true; } + /// + /// Attempts to convert a value to a . + /// + /// The object to convert. + /// The to use for conversion. + /// The converted value. + /// true if conversion is successful, otherwise false. + public static bool TryConvertToShort(object obj, CultureInfo culture, out short value) + { + return ConvertToShortCore(obj, culture, out value); + } + + /// + /// Attempts to convert a value to a nullable . + /// + /// The object to convert. + /// The to use for conversion. + /// The converted value. + /// true if conversion is successful, otherwise false. + public static bool TryConvertToNullableShort(object obj, CultureInfo culture, out short? value) + { + return ConvertToNullableShort(obj, culture, out value); + } + + internal static BindParser ConvertToShort = ConvertToShortCore; + internal static BindParser ConvertToNullableShort = ConvertToNullableShortCore; + + private static bool ConvertToShortCore(object obj, CultureInfo culture, out short value) + { + var text = (string)obj; + if (string.IsNullOrEmpty(text)) + { + value = default; + return false; + } + + if (!short.TryParse(text, NumberStyles.Number, culture ?? CultureInfo.CurrentCulture, out var converted)) + { + value = default; + return false; + } + + value = converted; + return true; + } + + private static bool ConvertToNullableShortCore(object obj, CultureInfo culture, out short? value) + { + var text = (string)obj; + if (string.IsNullOrEmpty(text)) + { + value = default; + return true; + } + + if (!short.TryParse(text, NumberStyles.Number, culture ?? CultureInfo.CurrentCulture, out var converted)) + { + value = default; + return false; + } + + value = converted; + return true; + } + /// /// Attempts to convert a value to a . /// @@ -1166,99 +1266,107 @@ namespace Microsoft.AspNetCore.Components public static BindFormatter Get() { - if (!_cache.TryGetValue(typeof(T), out var formattter)) + if (!_cache.TryGetValue(typeof(T), out var formatter)) { // We need to replicate all of the primitive cases that we handle here so that they will behave the same way. // The result will be cached. if (typeof(T) == typeof(string)) { - formattter = (BindFormatter)FormatStringValueCore; + formatter = (BindFormatter)FormatStringValueCore; } else if (typeof(T) == typeof(bool)) { - formattter = (BindFormatter)FormatBoolValueCore; + formatter = (BindFormatter)FormatBoolValueCore; } else if (typeof(T) == typeof(bool?)) { - formattter = (BindFormatter)FormatNullableBoolValueCore; + formatter = (BindFormatter)FormatNullableBoolValueCore; } else if (typeof(T) == typeof(int)) { - formattter = (BindFormatter)FormatIntValueCore; + formatter = (BindFormatter)FormatIntValueCore; } else if (typeof(T) == typeof(int?)) { - formattter = (BindFormatter)FormatNullableIntValueCore; + formatter = (BindFormatter)FormatNullableIntValueCore; } else if (typeof(T) == typeof(long)) { - formattter = (BindFormatter)FormatLongValueCore; + formatter = (BindFormatter)FormatLongValueCore; } else if (typeof(T) == typeof(long?)) { - formattter = (BindFormatter)FormatNullableLongValueCore; + formatter = (BindFormatter)FormatNullableLongValueCore; + } + else if (typeof(T) == typeof(short)) + { + formatter = (BindFormatter)FormatShortValueCore; + } + else if (typeof(T) == typeof(short?)) + { + formatter = (BindFormatter)FormatNullableShortValueCore; } else if (typeof(T) == typeof(float)) { - formattter = (BindFormatter)FormatFloatValueCore; + formatter = (BindFormatter)FormatFloatValueCore; } else if (typeof(T) == typeof(float?)) { - formattter = (BindFormatter)FormatNullableFloatValueCore; + formatter = (BindFormatter)FormatNullableFloatValueCore; } else if (typeof(T) == typeof(double)) { - formattter = (BindFormatter)FormatDoubleValueCore; + formatter = (BindFormatter)FormatDoubleValueCore; } else if (typeof(T) == typeof(double?)) { - formattter = (BindFormatter)FormatNullableDoubleValueCore; + formatter = (BindFormatter)FormatNullableDoubleValueCore; } else if (typeof(T) == typeof(decimal)) { - formattter = (BindFormatter)FormatDecimalValueCore; + formatter = (BindFormatter)FormatDecimalValueCore; } else if (typeof(T) == typeof(decimal?)) { - formattter = (BindFormatter)FormatNullableDecimalValueCore; + formatter = (BindFormatter)FormatNullableDecimalValueCore; } else if (typeof(T) == typeof(DateTime)) { - formattter = (BindFormatter)FormatDateTimeValueCore; + formatter = (BindFormatter)FormatDateTimeValueCore; } else if (typeof(T) == typeof(DateTime?)) { - formattter = (BindFormatter)FormatNullableDateTimeValueCore; + formatter = (BindFormatter)FormatNullableDateTimeValueCore; } else if (typeof(T) == typeof(DateTimeOffset)) { - formattter = (BindFormatter)FormatDateTimeOffsetValueCore; + formatter = (BindFormatter)FormatDateTimeOffsetValueCore; } else if (typeof(T) == typeof(DateTimeOffset?)) { - formattter = (BindFormatter)FormatNullableDateTimeOffsetValueCore; + formatter = (BindFormatter)FormatNullableDateTimeOffsetValueCore; } else if (typeof(T).IsEnum) { // We have to deal invoke this dynamically to work around the type constraint on Enum.TryParse. var method = _formatEnumValue ??= typeof(BindConverter).GetMethod(nameof(FormatEnumValueCore), BindingFlags.NonPublic | BindingFlags.Static); - formattter = method.MakeGenericMethod(typeof(T)).CreateDelegate(typeof(BindFormatter), target: null); + formatter = method.MakeGenericMethod(typeof(T)).CreateDelegate(typeof(BindFormatter), target: null); } else if (Nullable.GetUnderlyingType(typeof(T)) is Type innerType && innerType.IsEnum) { // We have to deal invoke this dynamically to work around the type constraint on Enum.TryParse. var method = _formatNullableEnumValue ??= typeof(BindConverter).GetMethod(nameof(FormatNullableEnumValueCore), BindingFlags.NonPublic | BindingFlags.Static); - formattter = method.MakeGenericMethod(innerType).CreateDelegate(typeof(BindFormatter), target: null); + formatter = method.MakeGenericMethod(innerType).CreateDelegate(typeof(BindFormatter), target: null); } else { - formattter = MakeTypeConverterFormatter(); + formatter = MakeTypeConverterFormatter(); } - _cache.TryAdd(typeof(T), formattter); + _cache.TryAdd(typeof(T), formatter); } - return (BindFormatter)formattter; + return (BindFormatter)formatter; } private static BindFormatter MakeTypeConverterFormatter() @@ -1323,6 +1431,14 @@ namespace Microsoft.AspNetCore.Components { parser = ConvertToNullableLong; } + else if (typeof(T) == typeof(short)) + { + parser = ConvertToShort; + } + else if (typeof(T) == typeof(short?)) + { + parser = ConvertToNullableShort; + } else if (typeof(T) == typeof(float)) { parser = ConvertToFloat; diff --git a/src/Components/Components/src/BindElementAttribute.cs b/src/Components/Components/src/BindElementAttribute.cs index ffc17a6049..4907b3e27e 100644 --- a/src/Components/Components/src/BindElementAttribute.cs +++ b/src/Components/Components/src/BindElementAttribute.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; @@ -15,7 +15,7 @@ namespace Microsoft.AspNetCore.Components /// Constructs an instance of . /// /// The tag name of the element. - /// The suffix value. For example, set this to value for bind-value, or set this to null for bind. + /// The suffix value. For example, set this to value for bind-value, or set this to for bind. /// The name of the value attribute to be bound. /// The name of an attribute that will register an associated change event. public BindElementAttribute(string element, string suffix, string valueAttribute, string changeAttribute) @@ -47,7 +47,7 @@ namespace Microsoft.AspNetCore.Components /// /// Gets the suffix value. - /// For example, this will be value to mean bind-value, or null to mean bind. + /// For example, this will be value to mean bind-value, or to mean bind. /// public string Suffix { get; } diff --git a/src/Components/Components/src/CascadingValue.cs b/src/Components/Components/src/CascadingValue.cs index db03b3d416..605d24134d 100644 --- a/src/Components/Components/src/CascadingValue.cs +++ b/src/Components/Components/src/CascadingValue.cs @@ -105,7 +105,7 @@ namespace Microsoft.AspNetCore.Components _hasSetParametersPreviously = true; - // It's OK for the value to be null, but some "Value" param must be suppled + // It's OK for the value to be null, but some "Value" param must be supplied // because it serves no useful purpose to have a otherwise. if (!hasSuppliedValue) { diff --git a/src/Components/Components/src/ComponentBase.cs b/src/Components/Components/src/ComponentBase.cs index 51c95f386b..d4b38db345 100644 --- a/src/Components/Components/src/ComponentBase.cs +++ b/src/Components/Components/src/ComponentBase.cs @@ -17,7 +17,7 @@ namespace Microsoft.AspNetCore.Components // about IComponent). This gives us flexibility to change the lifecycle concepts easily, // or for developers to design their own lifecycles as different base classes. - // TODO: When the component lifecycle design stabilises, add proper unit tests for ComponentBase. + // TODO: When the component lifecycle design stabilizes, add proper unit tests for ComponentBase. /// /// Optional base class for components. Alternatively, components may @@ -136,7 +136,7 @@ namespace Microsoft.AspNetCore.Components /// /// /// The and lifecycle methods - /// are useful for performing interop, or interacting with values recieved from @ref. + /// are useful for performing interop, or interacting with values received from @ref. /// Use the parameter to ensure that initialization work is only performed /// once. /// @@ -156,7 +156,7 @@ namespace Microsoft.AspNetCore.Components /// A representing any asynchronous operation. /// /// The and lifecycle methods - /// are useful for performing interop, or interacting with values recieved from @ref. + /// are useful for performing interop, or interacting with values received from @ref. /// Use the parameter to ensure that initialization work is only performed /// once. /// @@ -246,7 +246,7 @@ namespace Microsoft.AspNetCore.Components } catch // avoiding exception filters for AOT runtime support { - // Ignore exceptions from task cancelletions. + // Ignore exceptions from task cancellations. // Awaiting a canceled task may produce either an OperationCanceledException (if produced as a consequence of // CancellationToken.ThrowIfCancellationRequested()) or a TaskCanceledException (produced as a consequence of awaiting Task.FromCanceled). // It's much easier to check the state of the Task (i.e. Task.IsCanceled) rather than catch two distinct exceptions. @@ -289,7 +289,7 @@ namespace Microsoft.AspNetCore.Components } catch // avoiding exception filters for AOT runtime support { - // Ignore exceptions from task cancelletions, but don't bother issuing a state change. + // Ignore exceptions from task cancellations, but don't bother issuing a state change. if (task.IsCanceled) { return; diff --git a/src/Components/Components/src/EventCallbackFactoryBinderExtensions.cs b/src/Components/Components/src/EventCallbackFactoryBinderExtensions.cs index 99eac1965a..0305c1b469 100644 --- a/src/Components/Components/src/EventCallbackFactoryBinderExtensions.cs +++ b/src/Components/Components/src/EventCallbackFactoryBinderExtensions.cs @@ -12,9 +12,9 @@ namespace Microsoft.AspNetCore.Components /// // // NOTE: for number parsing, the HTML5 spec dictates that the DOM will represent - // number values as floating point numbers using `.` as the period separator. This is NOT culture senstive. + // number values as floating point numbers using `.` as the period separator. This is NOT culture sensitive. // Put another way, the user might see `,` as their decimal separator, but the value available in events - // to JS code is always simpilar to what .NET parses with InvariantCulture. + // to JS code is always similar to what .NET parses with InvariantCulture. // // See: https://www.w3.org/TR/html5/sec-forms.html#number-state-typenumber // See: https://www.w3.org/TR/html5/infrastructure.html#valid-floating-point-number @@ -136,6 +136,25 @@ namespace Microsoft.AspNetCore.Components return CreateBinderCore(factory, receiver, setter, culture, ConvertToLong); } + /// + /// For internal use only. + /// + /// + /// + /// + /// + /// + /// + public static EventCallback CreateBinder( + this EventCallbackFactory factory, + object receiver, + Action setter, + short existingValue, + CultureInfo culture = null) + { + return CreateBinderCore(factory, receiver, setter, culture, ConvertToShort); + } + /// /// For internal use only. /// @@ -155,6 +174,25 @@ namespace Microsoft.AspNetCore.Components return CreateBinderCore(factory, receiver, setter, culture, ConvertToNullableLong); } + /// + /// For internal use only. + /// + /// + /// + /// + /// + /// + /// + public static EventCallback CreateBinder( + this EventCallbackFactory factory, + object receiver, + Action setter, + short? existingValue, + CultureInfo culture = null) + { + return CreateBinderCore(factory, receiver, setter, culture, ConvertToNullableShort); + } + /// /// For internal use only. /// diff --git a/src/Components/Components/src/Microsoft.AspNetCore.Components.csproj b/src/Components/Components/src/Microsoft.AspNetCore.Components.csproj index 87960ab55f..2f1841b906 100644 --- a/src/Components/Components/src/Microsoft.AspNetCore.Components.csproj +++ b/src/Components/Components/src/Microsoft.AspNetCore.Components.csproj @@ -15,7 +15,8 @@ - + + @@ -33,12 +34,16 @@ + Projects=" + ../../Analyzers/src/Microsoft.AspNetCore.Components.Analyzers.csproj; + ../../../JSInterop/Microsoft.JSInterop/src/Microsoft.JSInterop.csproj; + ../../../Security/Authorization/Core/src/Microsoft.AspNetCore.Authorization.csproj"> + @@ -50,7 +55,6 @@
- diff --git a/src/Components/Components/src/Microsoft.AspNetCore.Components.multitarget.nuspec b/src/Components/Components/src/Microsoft.AspNetCore.Components.multitarget.nuspec index 4bd2ba3b7c..e2190e2be5 100644 --- a/src/Components/Components/src/Microsoft.AspNetCore.Components.multitarget.nuspec +++ b/src/Components/Components/src/Microsoft.AspNetCore.Components.multitarget.nuspec @@ -9,7 +9,7 @@ - + diff --git a/src/Components/Components/src/Microsoft.AspNetCore.Components.netcoreapp.nuspec b/src/Components/Components/src/Microsoft.AspNetCore.Components.netcoreapp.nuspec index 87fb784083..69d234bc08 100644 --- a/src/Components/Components/src/Microsoft.AspNetCore.Components.netcoreapp.nuspec +++ b/src/Components/Components/src/Microsoft.AspNetCore.Components.netcoreapp.nuspec @@ -3,7 +3,7 @@ $CommonMetadataElements$ - + diff --git a/src/Components/Components/src/NavigationManager.cs b/src/Components/Components/src/NavigationManager.cs index d75077026f..0ad565fb54 100644 --- a/src/Components/Components/src/NavigationManager.cs +++ b/src/Components/Components/src/NavigationManager.cs @@ -7,7 +7,7 @@ using Microsoft.AspNetCore.Components.Routing; namespace Microsoft.AspNetCore.Components { /// - /// Provides an abstraction for querying and mananging URI navigation. + /// Provides an abstraction for querying and managing URI navigation. /// public abstract class NavigationManager { @@ -134,7 +134,7 @@ namespace Microsoft.AspNetCore.Components } /// - /// Allows derived classes to lazyly self-initialize. Implementations that support lazy-initialization should override + /// Allows derived classes to lazily self-initialize. Implementations that support lazy-initialization should override /// this method and call . /// protected virtual void EnsureInitialized() diff --git a/src/Components/Components/src/RenderTree/RenderTreeFrameType.cs b/src/Components/Components/src/RenderTree/RenderTreeFrameType.cs index e73119e038..1e6249c8d7 100644 --- a/src/Components/Components/src/RenderTree/RenderTreeFrameType.cs +++ b/src/Components/Components/src/RenderTree/RenderTreeFrameType.cs @@ -16,7 +16,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree public enum RenderTreeFrameType: short { /// - /// Used only for unintialized frames. + /// Used only for uninitialized frames. /// None = 0, diff --git a/src/Components/Components/src/RenderTree/Renderer.cs b/src/Components/Components/src/RenderTree/Renderer.cs index 05cfb41abe..09470cebda 100644 --- a/src/Components/Components/src/RenderTree/Renderer.cs +++ b/src/Components/Components/src/RenderTree/Renderer.cs @@ -142,7 +142,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree // remaining work. // During the synchronous rendering process we don't wait for the pending asynchronous // work to finish as it will simply trigger new renders that will be handled afterwards. - // During the asynchronous rendering process we want to wait up untill al components have + // During the asynchronous rendering process we want to wait up until all components have // finished rendering so that we can produce the complete output. var componentState = GetRequiredComponentState(componentId); componentState.SetDirectParameters(initialParameters); @@ -388,7 +388,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree : null; /// - /// Processses pending renders requests from components if there are any. + /// Processes pending renders requests from components if there are any. /// protected virtual void ProcessPendingRender() { diff --git a/src/Components/Components/src/Rendering/RenderTreeBuilder.cs b/src/Components/Components/src/Rendering/RenderTreeBuilder.cs index f8a28273e4..3cc4aed9bf 100644 --- a/src/Components/Components/src/Rendering/RenderTreeBuilder.cs +++ b/src/Components/Components/src/Rendering/RenderTreeBuilder.cs @@ -12,7 +12,7 @@ namespace Microsoft.AspNetCore.Components.Rendering // IMPORTANT // // Many of these names are used in code generation. Keep these in sync with the code generation code - // See: aspnet/AspNetCore-Tooling + // See: dotnet/aspnetcore-tooling /// /// Provides methods for building a collection of entries. @@ -242,7 +242,7 @@ namespace Microsoft.AspNetCore.Components.Rendering AssertCanAddAttribute(); if (_lastNonAttributeFrameType == RenderTreeFrameType.Component) { - // Since this is a component, we need to preserve the type of the EventCallabck, so we have + // Since this is a component, we need to preserve the type of the EventCallback, so we have // to box. Append(RenderTreeFrame.Attribute(sequence, name, (object)value)); } diff --git a/src/Components/Components/src/Rendering/RendererSynchronizationContext.cs b/src/Components/Components/src/Rendering/RendererSynchronizationContext.cs index 176e4d83e4..d25d50b6de 100644 --- a/src/Components/Components/src/Rendering/RendererSynchronizationContext.cs +++ b/src/Components/Components/src/Rendering/RendererSynchronizationContext.cs @@ -147,20 +147,20 @@ namespace Microsoft.AspNetCore.Components.Rendering // synchronously runs the callback public override void Send(SendOrPostCallback d, object state) { - Task antecedant; + Task antecedent; var completion = new TaskCompletionSource(); lock (_state.Lock) { - antecedant = _state.Task; + antecedent = _state.Task; _state.Task = completion.Task; } // We have to block. That's the contract of Send - we don't expect this to be used // in many scenarios in Components. // - // Using Wait here is ok because the antecedant task will never throw. - antecedant.Wait(); + // Using Wait here is ok because the antecedent task will never throw. + antecedent.Wait(); ExecuteSynchronously(completion, d, state); } @@ -195,7 +195,7 @@ namespace Microsoft.AspNetCore.Components.Rendering ExecuteSynchronously(completion, d, state); } - private Task Enqueue(Task antecedant, SendOrPostCallback d, object state, bool forceAsync = false) + private Task Enqueue(Task antecedent, SendOrPostCallback d, object state, bool forceAsync = false) { // If we get here is means that a callback is being explicitly queued. Let's instead add it to the queue and yield. // @@ -212,7 +212,7 @@ namespace Microsoft.AspNetCore.Components.Rendering } var flags = forceAsync ? TaskContinuationOptions.RunContinuationsAsynchronously : TaskContinuationOptions.None; - return antecedant.ContinueWith(BackgroundWorkThunk, new WorkItem() + return antecedent.ContinueWith(BackgroundWorkThunk, new WorkItem() { SynchronizationContext = this, ExecutionContext = executionContext, diff --git a/src/Components/Components/test/CascadingParameterTest.cs b/src/Components/Components/test/CascadingParameterTest.cs index a906664741..522d027d6a 100644 --- a/src/Components/Components/test/CascadingParameterTest.cs +++ b/src/Components/Components/test/CascadingParameterTest.cs @@ -222,7 +222,7 @@ namespace Microsoft.AspNetCore.Components.Test // Act/Assert 2: Re-render the CascadingValue; observe nested component wasn't re-rendered providedValue = "Updated value"; - displayNestedComponent = false; // Remove the nested componet + displayNestedComponent = false; // Remove the nested component component.TriggerRender(); // Assert: We did not render the nested component now it's been removed diff --git a/src/Components/Components/test/ParameterViewTest.Assignment.cs b/src/Components/Components/test/ParameterViewTest.Assignment.cs index 9df9feab9f..7dd8537f4c 100644 --- a/src/Components/Components/test/ParameterViewTest.Assignment.cs +++ b/src/Components/Components/test/ParameterViewTest.Assignment.cs @@ -73,7 +73,7 @@ namespace Microsoft.AspNetCore.Components [Fact] public void IncomingParameterMatchesOverridenParameter_ThatDoesNotHasAttribute() { - // Test for https://github.com/aspnet/AspNetCore/issues/13162 + // Test for https://github.com/dotnet/aspnetcore/issues/13162 // Arrange var parameters = new ParameterViewBuilder { @@ -366,7 +366,7 @@ namespace Microsoft.AspNetCore.Components public void HasDuplicateCaptureUnmatchedValuesParameters_Throws() { // Arrange - var target = new HasDupliateCaptureUnmatchedValuesProperty(); + var target = new HasDuplicateCaptureUnmatchedValuesProperty(); var parameters = new ParameterViewBuilder().Build(); // Act @@ -374,17 +374,17 @@ namespace Microsoft.AspNetCore.Components // Assert Assert.Equal( - $"Multiple properties were found on component type '{typeof(HasDupliateCaptureUnmatchedValuesProperty).FullName}' " + + $"Multiple properties were found on component type '{typeof(HasDuplicateCaptureUnmatchedValuesProperty).FullName}' " + $"with '{nameof(ParameterAttribute)}.{nameof(ParameterAttribute.CaptureUnmatchedValues)}'. " + $"Only a single property per type can use '{nameof(ParameterAttribute)}.{nameof(ParameterAttribute.CaptureUnmatchedValues)}'. " + $"Properties:" + Environment.NewLine + - $"{nameof(HasDupliateCaptureUnmatchedValuesProperty.CaptureUnmatchedValuesProp1)}" + Environment.NewLine + - $"{nameof(HasDupliateCaptureUnmatchedValuesProperty.CaptureUnmatchedValuesProp2)}", + $"{nameof(HasDuplicateCaptureUnmatchedValuesProperty.CaptureUnmatchedValuesProp1)}" + Environment.NewLine + + $"{nameof(HasDuplicateCaptureUnmatchedValuesProperty.CaptureUnmatchedValuesProp2)}", ex.Message); } [Fact] - public void HasCaptureUnmatchedValuesParameteterWithWrongType_Throws() + public void HasCaptureUnmatchedValuesParameterWithWrongType_Throws() { // Arrange var target = new HasWrongTypeCaptureUnmatchedValuesProperty(); @@ -630,7 +630,7 @@ namespace Microsoft.AspNetCore.Components [Parameter(CaptureUnmatchedValues = true)] public IReadOnlyDictionary CaptureUnmatchedValues { get; set; } } - class HasDupliateCaptureUnmatchedValuesProperty + class HasDuplicateCaptureUnmatchedValuesProperty { [Parameter(CaptureUnmatchedValues = true)] public Dictionary CaptureUnmatchedValuesProp1 { get; set; } [Parameter(CaptureUnmatchedValues = true)] public IDictionary CaptureUnmatchedValuesProp2 { get; set; } diff --git a/src/Components/Components/test/RenderTreeDiffBuilderTest.cs b/src/Components/Components/test/RenderTreeDiffBuilderTest.cs index 28f617eba2..9f3ba71809 100644 --- a/src/Components/Components/test/RenderTreeDiffBuilderTest.cs +++ b/src/Components/Components/test/RenderTreeDiffBuilderTest.cs @@ -442,7 +442,7 @@ namespace Microsoft.AspNetCore.Components.Test [Fact] public void HandlesKeyBeingAdded() { - // This is an anomolous situation that can't occur with .razor components. + // This is an anomalous situation that can't occur with .razor components. // It represents the case where, for the same sequence number, we have an // old frame without a key and a new frame with a key. @@ -472,7 +472,7 @@ namespace Microsoft.AspNetCore.Components.Test [Fact] public void HandlesKeyBeingRemoved() { - // This is an anomolous situation that can't occur with .razor components. + // This is an anomalous situation that can't occur with .razor components. // It represents the case where, for the same sequence number, we have an // old frame with a key and a new frame without a key. diff --git a/src/Components/Components/test/RendererTest.cs b/src/Components/Components/test/RendererTest.cs index d0e2affea2..77e0b06faa 100644 --- a/src/Components/Components/test/RendererTest.cs +++ b/src/Components/Components/test/RendererTest.cs @@ -2575,7 +2575,7 @@ namespace Microsoft.AspNetCore.Components.Test [Fact] public async Task CanCombineBindAndConditionalAttribute() { - // This test represents https://github.com/aspnet/Blazor/issues/624 + // This test represents https://github.com/dotnet/blazor/issues/624 // Arrange: Rendered with textbox enabled var renderer = new TestRenderer(); @@ -2811,7 +2811,7 @@ namespace Microsoft.AspNetCore.Components.Test } [ConditionalFact] - [SkipOnHelix("https://github.com/aspnet/AspNetCore/issues/7487")] + [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/7487")] public async Task CanTriggerEventHandlerDisposedInEarlierPendingBatchAsync() { // This represents the scenario where the same event handler is being triggered @@ -3574,7 +3574,7 @@ namespace Microsoft.AspNetCore.Components.Test // Act &A Assert renderer.Dispose(); - // All components must be disposed even if some throw as part of being diposed. + // All components must be disposed even if some throw as part of being disposed. Assert.True(component.Disposed); var aex = Assert.IsType(Assert.Single(renderer.HandledExceptions)); Assert.Contains(exception1, aex.InnerExceptions); diff --git a/src/Components/Components/test/Rendering/RenderTreeBuilderTest.cs b/src/Components/Components/test/Rendering/RenderTreeBuilderTest.cs index 31364dca00..b4ad71cba2 100644 --- a/src/Components/Components/test/Rendering/RenderTreeBuilderTest.cs +++ b/src/Components/Components/test/Rendering/RenderTreeBuilderTest.cs @@ -301,7 +301,7 @@ namespace Microsoft.AspNetCore.Components.Rendering [Fact] public void CanAddMultipleAttributes_WithChildRegion() { - // This represents bug https://github.com/aspnet/AspNetCore/issues/16570 + // This represents bug https://github.com/dotnet/aspnetcore/issues/16570 // If a sequence of attributes is terminated by a call to builder.OpenRegion, // then the attribute deduplication logic wasn't working correctly diff --git a/src/Components/Components/test/Rendering/RendererSynchronizationContextTest.cs b/src/Components/Components/test/Rendering/RendererSynchronizationContextTest.cs index 568d2501bb..c92a585bd6 100644 --- a/src/Components/Components/test/Rendering/RendererSynchronizationContextTest.cs +++ b/src/Components/Components/test/Rendering/RendererSynchronizationContextTest.cs @@ -40,7 +40,7 @@ namespace Microsoft.AspNetCore.Components.Rendering } [Fact] - public void Post_RunsAynchronously_WhenNotBusy_Exception() + public void Post_RunsAsynchronously_WhenNotBusy_Exception() { // Arrange var context = new RendererSynchronizationContext(); diff --git a/src/Components/Components/test/Routing/RouteTableFactoryTests.cs b/src/Components/Components/test/Routing/RouteTableFactoryTests.cs index 92bea90d79..e596f27956 100644 --- a/src/Components/Components/test/Routing/RouteTableFactoryTests.cs +++ b/src/Components/Components/test/Routing/RouteTableFactoryTests.cs @@ -369,7 +369,7 @@ namespace Microsoft.AspNetCore.Components.Test.Routing [Fact] public void DoesNotThrowIfStableSortComparesRouteWithItself() { - // Test for https://github.com/aspnet/AspNetCore/issues/13313 + // Test for https://github.com/dotnet/aspnetcore/issues/13313 // Arrange & Act var builder = new TestRouteTableBuilder(); builder.AddRoute("r16"); diff --git a/src/Components/ComponentsNoDeps.slnf b/src/Components/ComponentsNoDeps.slnf index 09f6a0859f..7e09eeea25 100644 --- a/src/Components/ComponentsNoDeps.slnf +++ b/src/Components/ComponentsNoDeps.slnf @@ -13,13 +13,12 @@ "Blazor\\DevServer\\src\\Microsoft.AspNetCore.Blazor.DevServer.csproj", "Blazor\\Http\\src\\Microsoft.AspNetCore.Blazor.HttpClient.csproj", "Blazor\\Http\\test\\Microsoft.AspNetCore.Blazor.HttpClient.Tests.csproj", + "Blazor\\Mono.WebAssembly.Interop\\src\\Mono.WebAssembly.Interop.csproj", "Blazor\\Server\\src\\Microsoft.AspNetCore.Blazor.Server.csproj", - "Blazor\\Templates\\src\\Microsoft.AspNetCore.Blazor.Templates.csproj", "Blazor\\Validation\\src\\Microsoft.AspNetCore.Blazor.DataAnnotations.Validation.csproj", "Blazor\\Validation\\test\\Microsoft.AspNetCore.Blazor.DataAnnotations.Validation.Tests.csproj", "Blazor\\testassets\\HostedInAspNet.Client\\HostedInAspNet.Client.csproj", "Blazor\\testassets\\HostedInAspNet.Server\\HostedInAspNet.Server.csproj", - "Blazor\\testassets\\Microsoft.AspNetCore.Blazor.E2EPerformance\\Microsoft.AspNetCore.Blazor.E2EPerformance.csproj", "Blazor\\testassets\\MonoSanityClient\\MonoSanityClient.csproj", "Blazor\\testassets\\MonoSanity\\MonoSanity.csproj", "Blazor\\testassets\\StandaloneApp\\StandaloneApp.csproj", @@ -35,6 +34,8 @@ "Server\\test\\Microsoft.AspNetCore.Components.Server.Tests.csproj", "Web\\src\\Microsoft.AspNetCore.Components.Web.csproj", "Web\\test\\Microsoft.AspNetCore.Components.Web.Tests.csproj", + "benchmarkapps\\Wasm.Performance\\Driver\\Wasm.Performance.Driver.csproj", + "benchmarkapps\\Wasm.Performance\\TestApp\\Wasm.Performance.TestApp.csproj", "test\\E2ETest\\Microsoft.AspNetCore.Components.E2ETests.csproj", "test\\testassets\\BasicTestApp\\BasicTestApp.csproj", "test\\testassets\\TestContentPackage\\TestContentPackage.csproj", diff --git a/src/Components/Directory.Build.props b/src/Components/Directory.Build.props index b017bb81ea..65e48e29f5 100644 --- a/src/Components/Directory.Build.props +++ b/src/Components/Directory.Build.props @@ -2,7 +2,7 @@ - + @@ -12,6 +12,10 @@ aspnetcore;components + + 3.1.0 + $(MSBuildThisFileDirectory)Shared\ diff --git a/src/Components/Directory.Build.targets b/src/Components/Directory.Build.targets index a3eb973629..d6569c4088 100644 --- a/src/Components/Directory.Build.targets +++ b/src/Components/Directory.Build.targets @@ -3,6 +3,26 @@ true + + + + + + netcoreapp3.1 + Microsoft.AspNetCore.App + $(LatestAspNetCoreReferenceVersion) + $(LatestAspNetCoreReferenceVersion) + Microsoft.AspNetCore.App.Ref + $(LatestAspNetCoreReferenceVersion) + Microsoft.AspNetCore.App.Runtime.**RID** + linux-arm;linux-arm64;linux-musl-arm64;linux-musl-x64;linux-x64;osx-x64;rhel.6-x64;tizen.4.0.0-armel;tizen.5.0.0-armel;win-arm;win-arm64;win-x64;win-x86 + true + + + + diff --git a/src/Components/Forms/ref/Microsoft.AspNetCore.Components.Forms.csproj b/src/Components/Forms/ref/Microsoft.AspNetCore.Components.Forms.csproj index 2ef391ee71..0709f6d84b 100644 --- a/src/Components/Forms/ref/Microsoft.AspNetCore.Components.Forms.csproj +++ b/src/Components/Forms/ref/Microsoft.AspNetCore.Components.Forms.csproj @@ -2,6 +2,7 @@ netstandard2.0;$(DefaultNetCoreTargetFramework) + $(DefaultNetCoreTargetFramework) diff --git a/src/Components/Forms/ref/Microsoft.AspNetCore.Components.Forms.netcoreapp.cs b/src/Components/Forms/ref/Microsoft.AspNetCore.Components.Forms.netcoreapp.cs index 4674ff0fb4..c1961a8a25 100644 --- a/src/Components/Forms/ref/Microsoft.AspNetCore.Components.Forms.netcoreapp.cs +++ b/src/Components/Forms/ref/Microsoft.AspNetCore.Components.Forms.netcoreapp.cs @@ -11,7 +11,7 @@ namespace Microsoft.AspNetCore.Components.Forms public sealed partial class EditContext { public EditContext(object model) { } - public object Model { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public object Model { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } public event System.EventHandler OnFieldChanged { add { } remove { } } public event System.EventHandler OnValidationRequested { add { } remove { } } public event System.EventHandler OnValidationStateChanged { add { } remove { } } @@ -35,15 +35,16 @@ namespace Microsoft.AspNetCore.Components.Forms public sealed partial class FieldChangedEventArgs : System.EventArgs { public FieldChangedEventArgs(in Microsoft.AspNetCore.Components.Forms.FieldIdentifier fieldIdentifier) { } - public Microsoft.AspNetCore.Components.Forms.FieldIdentifier FieldIdentifier { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public Microsoft.AspNetCore.Components.Forms.FieldIdentifier FieldIdentifier { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } } [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] public readonly partial struct FieldIdentifier : System.IEquatable { private readonly object _dummy; + private readonly int _dummyPrimitive; public FieldIdentifier(object model, string fieldName) { throw null; } - public string FieldName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public object Model { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public string FieldName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public object Model { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } public static Microsoft.AspNetCore.Components.Forms.FieldIdentifier Create(System.Linq.Expressions.Expression> accessor) { throw null; } public bool Equals(Microsoft.AspNetCore.Components.Forms.FieldIdentifier otherIdentifier) { throw null; } public override bool Equals(object obj) { throw null; } diff --git a/src/Components/Forms/ref/Microsoft.AspNetCore.Components.Forms.netstandard2.0.cs b/src/Components/Forms/ref/Microsoft.AspNetCore.Components.Forms.netstandard2.0.cs index 4674ff0fb4..c1961a8a25 100644 --- a/src/Components/Forms/ref/Microsoft.AspNetCore.Components.Forms.netstandard2.0.cs +++ b/src/Components/Forms/ref/Microsoft.AspNetCore.Components.Forms.netstandard2.0.cs @@ -11,7 +11,7 @@ namespace Microsoft.AspNetCore.Components.Forms public sealed partial class EditContext { public EditContext(object model) { } - public object Model { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public object Model { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } public event System.EventHandler OnFieldChanged { add { } remove { } } public event System.EventHandler OnValidationRequested { add { } remove { } } public event System.EventHandler OnValidationStateChanged { add { } remove { } } @@ -35,15 +35,16 @@ namespace Microsoft.AspNetCore.Components.Forms public sealed partial class FieldChangedEventArgs : System.EventArgs { public FieldChangedEventArgs(in Microsoft.AspNetCore.Components.Forms.FieldIdentifier fieldIdentifier) { } - public Microsoft.AspNetCore.Components.Forms.FieldIdentifier FieldIdentifier { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public Microsoft.AspNetCore.Components.Forms.FieldIdentifier FieldIdentifier { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } } [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] public readonly partial struct FieldIdentifier : System.IEquatable { private readonly object _dummy; + private readonly int _dummyPrimitive; public FieldIdentifier(object model, string fieldName) { throw null; } - public string FieldName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public object Model { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public string FieldName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public object Model { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } public static Microsoft.AspNetCore.Components.Forms.FieldIdentifier Create(System.Linq.Expressions.Expression> accessor) { throw null; } public bool Equals(Microsoft.AspNetCore.Components.Forms.FieldIdentifier otherIdentifier) { throw null; } public override bool Equals(object obj) { throw null; } diff --git a/src/Components/Ignitor/src/ElementHive.cs b/src/Components/Ignitor/src/ElementHive.cs index 3bb37c38ce..34d128ecc5 100644 --- a/src/Components/Ignitor/src/ElementHive.cs +++ b/src/Components/Ignitor/src/ElementHive.cs @@ -42,7 +42,7 @@ namespace Ignitor foreach (var kvp in Components) { var component = kvp.Value; - if (TryGetElementFromChildren(component, out element)) + if (TryGetElementFromChildren(component, id, out element)) { return true; } @@ -50,31 +50,31 @@ namespace Ignitor element = null; return false; + } - bool TryGetElementFromChildren(Node node, out ElementNode? foundNode) + bool TryGetElementFromChildren(Node node, string id, [NotNullWhen(true)] out ElementNode? foundNode) + { + if (node is ElementNode elementNode && + elementNode.Attributes.TryGetValue("id", out var elementId) && + elementId.ToString() == id) { - if (node is ElementNode elementNode && - elementNode.Attributes.TryGetValue("id", out var elementId) && - elementId?.ToString() == id) - { - foundNode = elementNode; - return true; - } + foundNode = elementNode; + return true; + } - if (node is ContainerNode containerNode) + if (node is ContainerNode containerNode) + { + for (var i = 0; i < containerNode.Children.Count; i++) { - for (var i = 0; i < containerNode.Children.Count; i++) + if (TryGetElementFromChildren(containerNode.Children[i], id, out foundNode)) { - if (TryGetElementFromChildren(containerNode.Children[i], out foundNode)) - { - return true; - } + return true; } } - - foundNode = null; - return false; } + + foundNode = null; + return false; } private void UpdateComponent(RenderBatch batch, int componentId, ArrayBuilderSegment edits) diff --git a/src/Components/README.md b/src/Components/README.md index 6eacb16cda..6f7ed330ff 100644 --- a/src/Components/README.md +++ b/src/Components/README.md @@ -8,7 +8,7 @@ Blazor is a component based web UI framework. Blazor apps can run client-side in Blazor uses only the latest web standards. No plugins or transpilation needed. It runs in the browser on a real .NET runtime implemented in [WebAssembly](http://webassembly.org) that executes normal .NET assemblies. -[![Gitter](https://badges.gitter.im/aspnet/Blazor.svg)](https://gitter.im/aspnet/Blazor?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) +[![Gitter](https://badges.gitter.im/aspnet/blazor.svg)](https://gitter.im/aspnet/blazor?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) You can learn more about Blazor at https://blazor.net. diff --git a/src/Components/Server/ref/Microsoft.AspNetCore.Components.Server.netcoreapp.cs b/src/Components/Server/ref/Microsoft.AspNetCore.Components.Server.netcoreapp.cs index b9cfbda974..6265d868b9 100644 --- a/src/Components/Server/ref/Microsoft.AspNetCore.Components.Server.netcoreapp.cs +++ b/src/Components/Server/ref/Microsoft.AspNetCore.Components.Server.netcoreapp.cs @@ -21,11 +21,11 @@ namespace Microsoft.AspNetCore.Components.Server public sealed partial class CircuitOptions { public CircuitOptions() { } - public bool DetailedErrors { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public int DisconnectedCircuitMaxRetained { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public System.TimeSpan DisconnectedCircuitRetentionPeriod { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public System.TimeSpan JSInteropDefaultCallTimeout { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public int MaxBufferedUnacknowledgedRenderBatches { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public bool DetailedErrors { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public int DisconnectedCircuitMaxRetained { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public System.TimeSpan DisconnectedCircuitRetentionPeriod { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public System.TimeSpan JSInteropDefaultCallTimeout { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public int MaxBufferedUnacknowledgedRenderBatches { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } public abstract partial class RevalidatingServerAuthenticationStateProvider : Microsoft.AspNetCore.Components.Server.ServerAuthenticationStateProvider, System.IDisposable { diff --git a/src/Components/Server/src/CircuitDisconnectMiddleware.cs b/src/Components/Server/src/CircuitDisconnectMiddleware.cs index d64c31e7da..03c8c551e3 100644 --- a/src/Components/Server/src/CircuitDisconnectMiddleware.cs +++ b/src/Components/Server/src/CircuitDisconnectMiddleware.cs @@ -9,7 +9,7 @@ using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.Components.Server { - // We use a middlware so that we can use DI. + // We use a middleware so that we can use DI. internal class CircuitDisconnectMiddleware { private const string CircuitIdKey = "circuitId"; @@ -97,7 +97,7 @@ namespace Microsoft.AspNetCore.Components.Server LoggerMessage.Define(LogLevel.Debug, new EventId(2, "CircuitTerminatedGracefully"), "Circuit with id '{CircuitId}' terminated gracefully."); private static readonly Action _invalidCircuitId = - LoggerMessage.Define(LogLevel.Debug, new EventId(3, "InvalidCircuitId"), "CircuitDisconnectMiddleware recieved an invalid circuit id '{CircuitIdSecret}'."); + LoggerMessage.Define(LogLevel.Debug, new EventId(3, "InvalidCircuitId"), "CircuitDisconnectMiddleware received an invalid circuit id '{CircuitIdSecret}'."); public static void CircuitTerminatingGracefully(ILogger logger, CircuitId circuitId) => _circuitTerminatingGracefully(logger, circuitId, null); diff --git a/src/Components/Server/src/CircuitOptions.cs b/src/Components/Server/src/CircuitOptions.cs index 68ca25c85a..9f862b069d 100644 --- a/src/Components/Server/src/CircuitOptions.cs +++ b/src/Components/Server/src/CircuitOptions.cs @@ -19,7 +19,7 @@ namespace Microsoft.AspNetCore.Components.Server /// without losing any state in the event of transient connection issues. /// /// - /// This value determines the maximium number of circuit states retained by the server. + /// This value determines the maximum number of circuit states retained by the server. /// /// /// @@ -37,7 +37,7 @@ namespace Microsoft.AspNetCore.Components.Server /// without losing any state in the event of transient connection issues. /// /// - /// This value determines the maximium duration circuit state is retained by the server before being evicted. + /// This value determines the maximum duration circuit state is retained by the server before being evicted. /// /// /// diff --git a/src/Components/Server/src/Circuits/CircuitHost.cs b/src/Components/Server/src/Circuits/CircuitHost.cs index 47a04da3a9..0c43ce2699 100644 --- a/src/Components/Server/src/Circuits/CircuitHost.cs +++ b/src/Components/Server/src/Circuits/CircuitHost.cs @@ -356,7 +356,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits // EndInvokeJSFromDotNet is used in a fire-and-forget context, so it's responsible for its own // error handling. - public async Task EndInvokeJSFromDotNet(long asyncCall, bool succeded, string arguments) + public async Task EndInvokeJSFromDotNet(long asyncCall, bool succeeded, string arguments) { AssertInitialized(); AssertNotDisposed(); @@ -365,7 +365,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits { await Renderer.Dispatcher.InvokeAsync(() => { - if (!succeded) + if (!succeeded) { // We can log the arguments here because it is simply the JS error with the call stack. Log.EndInvokeJSFailed(_logger, asyncCall, arguments); @@ -578,11 +578,11 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits private static class Log { - private static readonly Action _intializationStarted; - private static readonly Action _intializationSucceded; - private static readonly Action _intializationFailed; + private static readonly Action _initializationStarted; + private static readonly Action _initializationSucceded; + private static readonly Action _initializationFailed; private static readonly Action _disposeStarted; - private static readonly Action _disposeSucceded; + private static readonly Action _disposeSucceeded; private static readonly Action _disposeFailed; private static readonly Action _onCircuitOpened; private static readonly Action _onConnectionUp; @@ -640,7 +640,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits public static readonly EventId EndInvokeJSSucceeded = new EventId(206, "EndInvokeJSSucceeded"); public static readonly EventId DispatchEventThroughJSInterop = new EventId(207, "DispatchEventThroughJSInterop"); public static readonly EventId LocationChange = new EventId(208, "LocationChange"); - public static readonly EventId LocationChangeSucceded = new EventId(209, "LocationChangeSucceeded"); + public static readonly EventId LocationChangeSucceeded = new EventId(209, "LocationChangeSucceeded"); public static readonly EventId LocationChangeFailed = new EventId(210, "LocationChangeFailed"); public static readonly EventId LocationChangeFailedInCircuit = new EventId(211, "LocationChangeFailedInCircuit"); public static readonly EventId OnRenderCompletedFailed = new EventId(212, "OnRenderCompletedFailed"); @@ -648,17 +648,17 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits static Log() { - _intializationStarted = LoggerMessage.Define( + _initializationStarted = LoggerMessage.Define( LogLevel.Debug, EventIds.InitializationStarted, "Circuit initialization started."); - _intializationSucceded = LoggerMessage.Define( + _initializationSucceded = LoggerMessage.Define( LogLevel.Debug, EventIds.InitializationSucceeded, "Circuit initialization succeeded."); - _intializationFailed = LoggerMessage.Define( + _initializationFailed = LoggerMessage.Define( LogLevel.Debug, EventIds.InitializationFailed, "Circuit initialization failed."); @@ -668,10 +668,10 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits EventIds.DisposeStarted, "Disposing circuit '{CircuitId}' started."); - _disposeSucceded = LoggerMessage.Define( + _disposeSucceeded = LoggerMessage.Define( LogLevel.Debug, EventIds.DisposeSucceeded, - "Disposing circuit '{CircuitId}' succeded."); + "Disposing circuit '{CircuitId}' succeeded."); _disposeFailed = LoggerMessage.Define( LogLevel.Debug, @@ -726,7 +726,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits _unhandledExceptionClientDisconnected = LoggerMessage.Define( LogLevel.Debug, EventIds.UnhandledExceptionClientDisconnected, - "An exception ocurred on the circuit host '{CircuitId}' while the client is disconnected."); + "An exception occurred on the circuit host '{CircuitId}' while the client is disconnected."); _beginInvokeDotNetStatic = LoggerMessage.Define( LogLevel.Debug, @@ -780,8 +780,8 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits _locationChangeSucceeded = LoggerMessage.Define( LogLevel.Debug, - EventIds.LocationChangeSucceded, - "Location change to '{URI}' in circuit '{CircuitId}' succeded."); + EventIds.LocationChangeSucceeded, + "Location change to '{URI}' in circuit '{CircuitId}' succeeded."); _locationChangeFailed = LoggerMessage.Define( LogLevel.Debug, @@ -799,11 +799,11 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits "Failed to complete render batch '{RenderId}' in circuit host '{CircuitId}'."); } - public static void InitializationStarted(ILogger logger) => _intializationStarted(logger, null); - public static void InitializationSucceeded(ILogger logger) => _intializationSucceded(logger, null); - public static void InitializationFailed(ILogger logger, Exception exception) => _intializationFailed(logger, exception); + public static void InitializationStarted(ILogger logger) => _initializationStarted(logger, null); + public static void InitializationSucceeded(ILogger logger) => _initializationSucceded(logger, null); + public static void InitializationFailed(ILogger logger, Exception exception) => _initializationFailed(logger, exception); public static void DisposeStarted(ILogger logger, CircuitId circuitId) => _disposeStarted(logger, circuitId, null); - public static void DisposeSucceeded(ILogger logger, CircuitId circuitId) => _disposeSucceded(logger, circuitId, null); + public static void DisposeSucceeded(ILogger logger, CircuitId circuitId) => _disposeSucceeded(logger, circuitId, null); public static void DisposeFailed(ILogger logger, CircuitId circuitId, Exception exception) => _disposeFailed(logger, circuitId, exception); public static void CircuitOpened(ILogger logger, CircuitId circuitId) => _onCircuitOpened(logger, circuitId, null); public static void ConnectionUp(ILogger logger, CircuitId circuitId, string connectionId) => _onConnectionUp(logger, circuitId, connectionId, null); diff --git a/src/Components/Server/src/Circuits/CircuitIdFactory.cs b/src/Components/Server/src/Circuits/CircuitIdFactory.cs index 544a1b791c..7250ee8116 100644 --- a/src/Components/Server/src/Circuits/CircuitIdFactory.cs +++ b/src/Components/Server/src/Circuits/CircuitIdFactory.cs @@ -20,7 +20,6 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits private const int SecretLength = 64; private const int IdLength = 32; - private readonly RandomNumberGenerator _generator = RandomNumberGenerator.Create(); private readonly IDataProtector _protector; public CircuitIdFactory(IDataProtectionProvider provider) @@ -35,7 +34,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits public CircuitId CreateCircuitId() { var buffer = new byte[SecretLength]; - _generator.GetBytes(buffer); + RandomNumberGenerator.Fill(buffer); var id = new byte[IdLength]; Array.Copy( diff --git a/src/Components/Server/src/Circuits/CircuitRegistry.cs b/src/Components/Server/src/Circuits/CircuitRegistry.cs index ab261a108b..f8e4971a64 100644 --- a/src/Components/Server/src/Circuits/CircuitRegistry.cs +++ b/src/Components/Server/src/Circuits/CircuitRegistry.cs @@ -32,7 +32,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits /// the to use the new client instance that attempted to reconnect to the server. Removing the entry from /// should ensure we no longer have to concern ourselves with entry expiration. /// - /// Knowing when a client disconnected is not an exact science. There's a fair possiblity that a client may reconnect before the server realizes. + /// Knowing when a client disconnected is not an exact science. There's a fair possibility that a client may reconnect before the server realizes. /// Consequently, we have to account for reconnects and disconnects occuring simultaneously as well as appearing out of order. /// To manage this, we use a critical section to manage all state transitions. /// @@ -99,7 +99,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits else { // DisconnectCore may fail to disconnect the circuit if it was previously marked inactive or - // has been transfered to a new connection. Do not invoke the circuit handlers in this instance. + // has been transferred to a new connection. Do not invoke the circuit handlers in this instance. // We have to do in this instance. return Task.CompletedTask; @@ -181,7 +181,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits { // Transition the host from disconnected to connected if it's available. In this critical section, we return // an existing host if it's currently considered connected or transition a disconnected host to connected. - // Transfering also wires up the client to the new set. + // Transferring also wires up the client to the new set. (circuitHost, previouslyConnected) = ConnectCore(circuitId, clientProxy, connectionId); if (circuitHost == null) @@ -428,7 +428,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits _connectingToDisconnectedCircuit = LoggerMessage.Define( LogLevel.Debug, EventIds.ConnectingToDisconnectedCircuit, - "Transfering disconnected circuit {CircuitId} to connection {ConnectionId}."); + "Transferring disconnected circuit {CircuitId} to connection {ConnectionId}."); _failedToReconnectToCircuit = LoggerMessage.Define( LogLevel.Debug, diff --git a/src/Components/Server/src/Circuits/RemoteJSRuntime.cs b/src/Components/Server/src/Circuits/RemoteJSRuntime.cs index 0ef78bcf0f..1d9205e1ba 100644 --- a/src/Components/Server/src/Circuits/RemoteJSRuntime.cs +++ b/src/Components/Server/src/Circuits/RemoteJSRuntime.cs @@ -74,7 +74,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits { throw new InvalidOperationException( "JavaScript interop calls cannot be issued at this time. This is because the component is being " + - $"statically rendererd. When prerendering is enabled, JavaScript interop calls can only be performed " + + $"statically rendered. When prerendering is enabled, JavaScript interop calls can only be performed " + $"during the OnAfterRenderAsync lifecycle method."); } diff --git a/src/Components/Server/src/Circuits/RemoteRenderer.cs b/src/Components/Server/src/Circuits/RemoteRenderer.cs index 509944e17a..e7ab9c1a4c 100644 --- a/src/Components/Server/src/Circuits/RemoteRenderer.cs +++ b/src/Components/Server/src/Circuits/RemoteRenderer.cs @@ -25,7 +25,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits private bool _disposing = false; /// - /// Notifies when a rendering exception occured. + /// Notifies when a rendering exception occurred. /// public event EventHandler UnhandledException; @@ -90,14 +90,14 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits // as we have a client that is not acknowledging render batches fast enough (something we consider needs // to be fast). // The result is something as follows: - // Lets imagine an extreme case where the server produces a new batch every milisecond. - // Lets say the client is able to ACK a batch every 100 miliseconds. + // Lets imagine an extreme case where the server produces a new batch every millisecond. + // Lets say the client is able to ACK a batch every 100 milliseconds. // When the app starts the client might see the sequence 0->(MaxUnacknowledgedRenderBatches-1) and then - // after 100 miliseconds it sees it jump to 1xx, then to 2xx where xx is something between {0..99} the + // after 100 milliseconds it sees it jump to 1xx, then to 2xx where xx is something between {0..99} the // reason for this is that the server slows down rendering new batches to as fast as the client can consume // them. // Similarly, if a client were to send events at a faster pace than the server can consume them, the server - // would still proces the events, but would not produce new renders until it gets an ack that frees up space + // would still process the events, but would not produce new renders until it gets an ack that frees up space // for a new render. // We should never see UnacknowledgedRenderBatches.Count > _options.MaxBufferedUnacknowledgedRenderBatches @@ -202,7 +202,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits { // Send the render batch to the client // If the "send" operation fails (synchronously or asynchronously) or the client - // gets disconected simply give up. This likely means that + // gets disconnected simply give up. This likely means that // the circuit went offline while sending the data, so simply wait until the // client reconnects back or the circuit gets evicted because it stayed // disconnected for too long. @@ -247,7 +247,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits // from the client that it has received and successfully applied all batches up to that point). // If receive an ack for a previously acknowledged batch, its an error, as the messages are - // guranteed to be delivered in order, so a message for a render batch of 2 will never arrive + // guaranteed to be delivered in order, so a message for a render batch of 2 will never arrive // after a message for a render batch for 3. // If that were to be the case, it would just be enough to relax the checks here and simply skip // the message. @@ -282,7 +282,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits if (lastBatchId < incomingBatchId) { - // This exception is due to a bad client input, so we mark it as such to prevent loging it as a warning and + // This exception is due to a bad client input, so we mark it as such to prevent logging it as a warning and // flooding the logs with warnings. throw new InvalidOperationException($"Received an acknowledgement for batch with id '{incomingBatchId}' when the last batch produced was '{lastBatchId}'."); } diff --git a/src/Components/Server/src/ComponentHub.cs b/src/Components/Server/src/ComponentHub.cs index cb347a30be..779b4b0053 100644 --- a/src/Components/Server/src/ComponentHub.cs +++ b/src/Components/Server/src/ComponentHub.cs @@ -302,7 +302,7 @@ namespace Microsoft.AspNetCore.Components.Server LoggerMessage.Define(LogLevel.Debug, new EventId(7, "CreatedCircuit"), "Created circuit '{CircuitId}' with secret '{CircuitIdSecret}' for '{ConnectionId}'"); private static readonly Action _invalidCircuitId = - LoggerMessage.Define(LogLevel.Debug, new EventId(8, "InvalidCircuitId"), "ConnectAsync recieved an invalid circuit id '{CircuitIdSecret}'"); + LoggerMessage.Define(LogLevel.Debug, new EventId(8, "InvalidCircuitId"), "ConnectAsync received an invalid circuit id '{CircuitIdSecret}'"); public static void ReceivedConfirmationForBatch(ILogger logger, long batchId) => _receivedConfirmationForBatch(logger, batchId, null); diff --git a/src/Components/Server/src/Directory.Build.targets b/src/Components/Server/src/Directory.Build.targets new file mode 100644 index 0000000000..09953b9b6c --- /dev/null +++ b/src/Components/Server/src/Directory.Build.targets @@ -0,0 +1,6 @@ + + + + + diff --git a/src/Components/Server/src/Microsoft.AspNetCore.Components.Server.csproj b/src/Components/Server/src/Microsoft.AspNetCore.Components.Server.csproj index b47c1c2fe3..98fa33ca3e 100644 --- a/src/Components/Server/src/Microsoft.AspNetCore.Components.Server.csproj +++ b/src/Components/Server/src/Microsoft.AspNetCore.Components.Server.csproj @@ -1,5 +1,4 @@ - $(DefaultNetCoreTargetFramework) Runtime server features for ASP.NET Core Components. @@ -8,8 +7,9 @@ true true CS0436;$(NoWarn) - $(DefineConstants);MESSAGEPACK_INTERNAL;COMPONENTS_SERVER + $(DefineConstants);ENABLE_UNSAFE_MSGPACK;SPAN_BUILTIN;MESSAGEPACK_INTERNAL;COMPONENTS_SERVER false + Microsoft.Extensions.FileProviders.Embedded.Manifest.xml @@ -22,7 +22,8 @@ - + + @@ -36,9 +37,18 @@ - $(RepoRoot)src\submodules\MessagePack-CSharp\src\MessagePack\ + $(RepoRoot)src\submodules\MessagePack-CSharp\src\MessagePack.UnityClient\Assets\Scripts\MessagePack\ + + + <_FileProviderTaskAssembly>$(ArtifactsDir)bin\Microsoft.Extensions.FileProviders.Embedded.Manifest.Task\$(Configuration)\netstandard2.0\Microsoft.Extensions.FileProviders.Embedded.Manifest.Task.dll + + + + @@ -58,15 +68,16 @@ - + + - + @@ -83,5 +94,4 @@ - - + \ No newline at end of file diff --git a/src/Components/Server/test/Circuits/CircuitIdFactoryTest.cs b/src/Components/Server/test/Circuits/CircuitIdFactoryTest.cs index 35bc454416..588eadfdd8 100644 --- a/src/Components/Server/test/Circuits/CircuitIdFactoryTest.cs +++ b/src/Components/Server/test/Circuits/CircuitIdFactoryTest.cs @@ -25,7 +25,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits } [Fact] - public void CreateCircuitId_Generates_GeneratesDifferentIds_ForSuccesiveCalls() + public void CreateCircuitId_Generates_GeneratesDifferentIds_ForSuccessiveCalls() { // Arrange var factory = TestCircuitIdFactory.CreateTestFactory(); diff --git a/src/Components/Server/test/Circuits/RemoteRendererTest.cs b/src/Components/Server/test/Circuits/RemoteRendererTest.cs index e7cda62bb2..6befe0c65d 100644 --- a/src/Components/Server/test/Circuits/RemoteRendererTest.cs +++ b/src/Components/Server/test/Circuits/RemoteRendererTest.cs @@ -179,7 +179,7 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering // Assert Assert.Equal(new long[] { 2, 3, 4 }, renderIds); - Assert.True(task.Wait(3000), "One or more render batches werent acknowledged"); + Assert.True(task.Wait(3000), "One or more render batches weren't acknowledged"); await task; } @@ -233,7 +233,7 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering exceptions.Add(e); }; - // Receive the ack for the intial batch + // Receive the ack for the initial batch _ = renderer.OnRenderCompletedAsync(2, null); // Receive the ack for the second batch _ = renderer.OnRenderCompletedAsync(3, null); diff --git a/src/Components/Server/test/Circuits/RevalidatingServerAuthenticationStateProvider.cs b/src/Components/Server/test/Circuits/RevalidatingServerAuthenticationStateProvider.cs index dc7d28d502..ef4eae3630 100644 --- a/src/Components/Server/test/Circuits/RevalidatingServerAuthenticationStateProvider.cs +++ b/src/Components/Server/test/Circuits/RevalidatingServerAuthenticationStateProvider.cs @@ -9,6 +9,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Components.Authorization; using Microsoft.AspNetCore.Components.Server; +using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.Logging.Abstractions; using Xunit; @@ -184,7 +185,8 @@ namespace Microsoft.AspNetCore.Components } [Fact] - public async Task IfValidateAuthenticationStateAsyncReturnsUnrelatedCancelledTask_TreatAsFailure() + [QuarantinedTest] + public async Task IfValidateAuthenticationStateAsyncReturnsUnrelatedCanceledTask_TreatAsFailure() { // Arrange var validationTcs = new TaskCompletionSource(); @@ -200,11 +202,11 @@ namespace Microsoft.AspNetCore.Components var firstRevalidationCall = provider.RevalidationCallLog.Single(); Assert.Equal(0, authenticationStateChangedCount); - // Act: ValidateAuthenticationStateAsync returns cancelled task, but the cancellation + // Act: ValidateAuthenticationStateAsync returns canceled task, but the cancellation // is unrelated to the CT we supplied validationTcs.TrySetCanceled(new CancellationTokenSource().Token); - // Assert: Since we didn't ask for that operation to be cancelled, this is treated as + // Assert: Since we didn't ask for that operation to be canceled, this is treated as // a failure to validate, so we force a logout Assert.Equal(1, authenticationStateChangedCount); var newAuthState = await provider.GetAuthenticationStateAsync(); diff --git a/src/Components/Server/test/ComponentEndpointRouteBuilderExtensionsTest.cs b/src/Components/Server/test/ComponentEndpointRouteBuilderExtensionsTest.cs index e0a6d8ff44..03a651d7be 100644 --- a/src/Components/Server/test/ComponentEndpointRouteBuilderExtensionsTest.cs +++ b/src/Components/Server/test/ComponentEndpointRouteBuilderExtensionsTest.cs @@ -66,9 +66,9 @@ namespace Microsoft.AspNetCore.Components.Server.Tests services.AddServerSideBlazor(); services.AddSingleton(new ConfigurationBuilder().Build()); - var serviceProvder = services.BuildServiceProvider(); + var serviceProvider = services.BuildServiceProvider(); - return new ApplicationBuilder(serviceProvder); + return new ApplicationBuilder(serviceProvider); } private class MyComponent : IComponent diff --git a/src/Components/Server/test/Microsoft.AspNetCore.Components.Server.Tests.csproj b/src/Components/Server/test/Microsoft.AspNetCore.Components.Server.Tests.csproj index c6e294a63c..2463622ac4 100644 --- a/src/Components/Server/test/Microsoft.AspNetCore.Components.Server.Tests.csproj +++ b/src/Components/Server/test/Microsoft.AspNetCore.Components.Server.Tests.csproj @@ -6,7 +6,6 @@ - diff --git a/src/Components/Shared/src/ElementReferenceJsonConverter.cs b/src/Components/Shared/src/ElementReferenceJsonConverter.cs index 465a688bf2..80a7738107 100644 --- a/src/Components/Shared/src/ElementReferenceJsonConverter.cs +++ b/src/Components/Shared/src/ElementReferenceJsonConverter.cs @@ -30,7 +30,7 @@ namespace Microsoft.AspNetCore.Components } else { - throw new JsonException($"Unexcepted JSON Token {reader.TokenType}."); + throw new JsonException($"Unexpected JSON Token {reader.TokenType}."); } } @@ -49,4 +49,4 @@ namespace Microsoft.AspNetCore.Components writer.WriteEndObject(); } } -} \ No newline at end of file +} diff --git a/src/Components/Web.JS/Microsoft.AspNetCore.Components.Web.JS.npmproj b/src/Components/Web.JS/Microsoft.AspNetCore.Components.Web.JS.npmproj index 89ca145324..915e6f0190 100644 --- a/src/Components/Web.JS/Microsoft.AspNetCore.Components.Web.JS.npmproj +++ b/src/Components/Web.JS/Microsoft.AspNetCore.Components.Web.JS.npmproj @@ -6,6 +6,11 @@ false + + + + + + + diff --git a/src/Components/Web.JS/dist/Release/blazor.server.js b/src/Components/Web.JS/dist/Release/blazor.server.js index 30d3ae3949..3c88d5b5a3 100644 --- a/src/Components/Web.JS/dist/Release/blazor.server.js +++ b/src/Components/Web.JS/dist/Release/blazor.server.js @@ -1,15 +1,15 @@ -!function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=49)}([function(e,t,n){"use strict";var r;n.d(t,"a",function(){return r}),function(e){e[e.Trace=0]="Trace",e[e.Debug=1]="Debug",e[e.Information=2]="Information",e[e.Warning=3]="Warning",e[e.Error=4]="Error",e[e.Critical=5]="Critical",e[e.None=6]="None"}(r||(r={}))},function(e,t,n){"use strict";n.d(t,"a",function(){return s}),n.d(t,"c",function(){return c}),n.d(t,"f",function(){return u}),n.d(t,"g",function(){return l}),n.d(t,"h",function(){return f}),n.d(t,"e",function(){return h}),n.d(t,"d",function(){return p}),n.d(t,"b",function(){return d});var r=n(0),o=n(7),i=function(e,t,n,r){return new(n||(n=Promise))(function(o,i){function a(e){try{c(r.next(e))}catch(e){i(e)}}function s(e){try{c(r.throw(e))}catch(e){i(e)}}function c(e){e.done?o(e.value):new n(function(t){t(e.value)}).then(a,s)}c((r=r.apply(e,t||[])).next())})},a=function(e,t){var n,r,o,i,a={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return i={next:s(0),throw:s(1),return:s(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function s(i){return function(s){return function(i){if(n)throw new TypeError("Generator is already executing.");for(;a;)try{if(n=1,r&&(o=2&i[0]?r.return:i[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,i[1])).done)return o;switch(r=0,o&&(i=[2&i[0],o.value]),i[0]){case 0:case 1:o=i;break;case 4:return a.label++,{value:i[1],done:!1};case 5:a.label++,r=i[1],i=[0];continue;case 7:i=a.ops.pop(),a.trys.pop();continue;default:if(!(o=(o=a.trys).length>0&&o[o.length-1])&&(6===i[0]||2===i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]-1&&this.subject.observers.splice(e,1),0===this.subject.observers.length&&this.subject.cancelCallback&&this.subject.cancelCallback().catch(function(e){})},e}(),d=function(){function e(e){this.minimumLogLevel=e,this.outputConsole=console}return e.prototype.log=function(e,t){if(e>=this.minimumLogLevel)switch(e){case r.a.Critical:case r.a.Error:this.outputConsole.error("["+(new Date).toISOString()+"] "+r.a[e]+": "+t);break;case r.a.Warning:this.outputConsole.warn("["+(new Date).toISOString()+"] "+r.a[e]+": "+t);break;case r.a.Information:this.outputConsole.info("["+(new Date).toISOString()+"] "+r.a[e]+": "+t);break;default:this.outputConsole.log("["+(new Date).toISOString()+"] "+r.a[e]+": "+t)}},e}()},function(e,t,n){"use strict";n.r(t);var r,o,i=n(3),a=n(4),s=n(43),c=n(0),u=(r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)t.hasOwnProperty(n)&&(e[n]=t[n])},function(e,t){function n(){this.constructor=e}r(e,t),e.prototype=null===t?Object.create(t):(n.prototype=t.prototype,new n)}),l=function(e){function t(t){var n=e.call(this)||this;return n.logger=t,n}return u(t,e),t.prototype.send=function(e){var t=this;return e.abortSignal&&e.abortSignal.aborted?Promise.reject(new i.a):e.method?e.url?new Promise(function(n,r){var o=new XMLHttpRequest;o.open(e.method,e.url,!0),o.withCredentials=!0,o.setRequestHeader("X-Requested-With","XMLHttpRequest"),o.setRequestHeader("Content-Type","text/plain;charset=UTF-8");var s=e.headers;s&&Object.keys(s).forEach(function(e){o.setRequestHeader(e,s[e])}),e.responseType&&(o.responseType=e.responseType),e.abortSignal&&(e.abortSignal.onabort=function(){o.abort(),r(new i.a)}),e.timeout&&(o.timeout=e.timeout),o.onload=function(){e.abortSignal&&(e.abortSignal.onabort=null),o.status>=200&&o.status<300?n(new a.b(o.status,o.statusText,o.response||o.responseText)):r(new i.b(o.statusText,o.status))},o.onerror=function(){t.logger.log(c.a.Warning,"Error from HTTP request. "+o.status+": "+o.statusText+"."),r(new i.b(o.statusText,o.status))},o.ontimeout=function(){t.logger.log(c.a.Warning,"Timeout from HTTP request."),r(new i.c)},o.send(e.content||"")}):Promise.reject(new Error("No url defined.")):Promise.reject(new Error("No method defined."))},t}(a.a),f=function(){var e=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)t.hasOwnProperty(n)&&(e[n]=t[n])};return function(t,n){function r(){this.constructor=t}e(t,n),t.prototype=null===n?Object.create(n):(r.prototype=n.prototype,new r)}}(),h=function(e){function t(t){var n=e.call(this)||this;return"undefined"!=typeof XMLHttpRequest?n.httpClient=new l(t):n.httpClient=new s.a(t),n}return f(t,e),t.prototype.send=function(e){return e.abortSignal&&e.abortSignal.aborted?Promise.reject(new i.a):e.method?e.url?this.httpClient.send(e):Promise.reject(new Error("No url defined.")):Promise.reject(new Error("No method defined."))},t.prototype.getCookieString=function(e){return this.httpClient.getCookieString(e)},t}(a.a),p=n(44);!function(e){e[e.Invocation=1]="Invocation",e[e.StreamItem=2]="StreamItem",e[e.Completion=3]="Completion",e[e.StreamInvocation=4]="StreamInvocation",e[e.CancelInvocation=5]="CancelInvocation",e[e.Ping=6]="Ping",e[e.Close=7]="Close"}(o||(o={}));var d,g=n(1),y=function(){function e(){this.observers=[]}return e.prototype.next=function(e){for(var t=0,n=this.observers;t0&&o[o.length-1])&&(6===i[0]||2===i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]0&&o[o.length-1])&&(6===i[0]||2===i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]0&&o[o.length-1])&&(6===i[0]||2===i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]0&&o[o.length-1])&&(6===i[0]||2===i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]0&&o[o.length-1])&&(6===i[0]||2===i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]0?[2,Promise.reject(new Error("Unable to connect to the server with any of the available transports. "+i.join(" ")))]:[2,Promise.reject(new Error("None of the transports supported by the client are supported by the server."))]}})})},e.prototype.constructTransport=function(e){switch(e){case E.WebSockets:if(!this.options.WebSocket)throw new Error("'WebSocket' is not supported in your environment.");return new A(this.httpClient,this.accessTokenFactory,this.logger,this.options.logMessageContent||!1,this.options.WebSocket);case E.ServerSentEvents:if(!this.options.EventSource)throw new Error("'EventSource' is not supported in your environment.");return new O(this.httpClient,this.accessTokenFactory,this.logger,this.options.logMessageContent||!1,this.options.EventSource);case E.LongPolling:return new x(this.httpClient,this.accessTokenFactory,this.logger,this.options.logMessageContent||!1);default:throw new Error("Unknown transport: "+e+".")}},e.prototype.startTransport=function(e,t){var n=this;return this.transport.onreceive=this.onreceive,this.transport.onclose=function(e){return n.stopConnection(e)},this.transport.connect(e,t)},e.prototype.resolveTransportOrError=function(e,t,n){var r=E[e.transport];if(null==r)return this.logger.log(c.a.Debug,"Skipping transport '"+e.transport+"' because it is not supported by this client."),new Error("Skipping transport '"+e.transport+"' because it is not supported by this client.");if(!function(e,t){return!e||0!=(t&e)}(t,r))return this.logger.log(c.a.Debug,"Skipping transport '"+E[r]+"' because it was disabled by the client."),new Error("'"+E[r]+"' is disabled by the client.");if(!(e.transferFormats.map(function(e){return S[e]}).indexOf(n)>=0))return this.logger.log(c.a.Debug,"Skipping transport '"+E[r]+"' because it does not support the requested transfer format '"+S[n]+"'."),new Error("'"+E[r]+"' does not support "+S[n]+".");if(r===E.WebSockets&&!this.options.WebSocket||r===E.ServerSentEvents&&!this.options.EventSource)return this.logger.log(c.a.Debug,"Skipping transport '"+E[r]+"' because it is not supported in your environment.'"),new Error("'"+E[r]+"' is not supported in your environment.");this.logger.log(c.a.Debug,"Selecting transport '"+E[r]+"'.");try{return this.constructTransport(r)}catch(e){return e}},e.prototype.isITransport=function(e){return e&&"object"==typeof e&&"connect"in e},e.prototype.stopConnection=function(e){if(this.logger.log(c.a.Debug,"HttpConnection.stopConnection("+e+") called while in state "+this.connectionState+"."),this.transport=void 0,e=this.stopError||e,this.stopError=void 0,"Disconnected"!==this.connectionState)if("Connecting "!==this.connectionState){if("Disconnecting"===this.connectionState&&this.stopPromiseResolver(),e?this.logger.log(c.a.Error,"Connection disconnected with error '"+e+"'."):this.logger.log(c.a.Information,"Connection disconnected."),this.connectionId=void 0,this.connectionState="Disconnected",this.onclose&&this.connectionStarted){this.connectionStarted=!1;try{this.onclose(e)}catch(t){this.logger.log(c.a.Error,"HttpConnection.onclose("+e+") threw error '"+t+"'.")}}}else this.logger.log(c.a.Warning,"Call to HttpConnection.stopConnection("+e+") was ignored because the connection hasn't yet left the in the connecting state.");else this.logger.log(c.a.Debug,"Call to HttpConnection.stopConnection("+e+") was ignored because the connection is already in the disconnected state.")},e.prototype.resolveUrl=function(e){if(0===e.lastIndexOf("https://",0)||0===e.lastIndexOf("http://",0))return e;if(!g.c.isBrowser||!window.document)throw new Error("Cannot resolve '"+e+"'.");var t=window.document.createElement("a");return t.href=e,this.logger.log(c.a.Information,"Normalizing '"+e+"' to '"+t.href+"'."),t.href},e.prototype.resolveNegotiateUrl=function(e){var t=e.indexOf("?"),n=e.substring(0,-1===t?e.length:t);return"/"!==n[n.length-1]&&(n+="/"),n+="negotiate",-1===(n+=-1===t?"":e.substring(t)).indexOf("negotiateVersion")&&(n+=-1===t?"?":"&",n+="negotiateVersion="+this.negotiateVersion),n},e}();var q=function(){function e(e){this.transport=e,this.buffer=[],this.executing=!0,this.sendBufferedData=new W,this.transportResult=new W,this.sendLoopPromise=this.sendLoop()}return e.prototype.send=function(e){return this.bufferData(e),this.transportResult||(this.transportResult=new W),this.transportResult.promise},e.prototype.stop=function(){return this.executing=!1,this.sendBufferedData.resolve(),this.sendLoopPromise},e.prototype.bufferData=function(e){if(this.buffer.length&&typeof this.buffer[0]!=typeof e)throw new Error("Expected data to be of type "+typeof this.buffer+" but was of type "+typeof e);this.buffer.push(e),this.sendBufferedData.resolve()},e.prototype.sendLoop=function(){return B(this,void 0,void 0,function(){var t,n,r;return j(this,function(o){switch(o.label){case 0:return[4,this.sendBufferedData.promise];case 1:if(o.sent(),!this.executing)return this.transportResult&&this.transportResult.reject("Connection stopped."),[3,6];this.sendBufferedData=new W,t=this.transportResult,this.transportResult=void 0,n="string"==typeof this.buffer[0]?this.buffer.join(""):e.concatBuffers(this.buffer),this.buffer.length=0,o.label=2;case 2:return o.trys.push([2,4,,5]),[4,this.transport.send(n)];case 3:return o.sent(),t.resolve(),[3,5];case 4:return r=o.sent(),t.reject(r),[3,5];case 5:return[3,0];case 6:return[2]}})})},e.concatBuffers=function(e){for(var t=e.map(function(e){return e.byteLength}).reduce(function(e,t){return e+t}),n=new Uint8Array(t),r=0,o=0,i=e;o0&&o[o.length-1])&&(6===i[0]||2===i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]-1&&this.subject.observers.splice(e,1),0===this.subject.observers.length&&this.subject.cancelCallback&&this.subject.cancelCallback().catch(function(e){})},e}(),g=function(){function e(e){this.minimumLogLevel=e,this.outputConsole=console}return e.prototype.log=function(e,t){if(e>=this.minimumLogLevel)switch(e){case r.a.Critical:case r.a.Error:this.outputConsole.error("["+(new Date).toISOString()+"] "+r.a[e]+": "+t);break;case r.a.Warning:this.outputConsole.warn("["+(new Date).toISOString()+"] "+r.a[e]+": "+t);break;case r.a.Information:this.outputConsole.info("["+(new Date).toISOString()+"] "+r.a[e]+": "+t);break;default:this.outputConsole.log("["+(new Date).toISOString()+"] "+r.a[e]+": "+t)}},e}();function y(){var e="X-SignalR-User-Agent";return u.isNode&&(e="User-Agent"),[e,v(s,b(),w(),m())]}function v(e,t,n,r){var o="Microsoft SignalR/",i=e.split(".");return o+=i[0]+"."+i[1],o+=" ("+e+"; ",o+=t&&""!==t?t+"; ":"Unknown OS; ",o+=""+n,o+=r?"; "+r:"; Unknown Runtime Version",o+=")"}function b(){if(!u.isNode)return"";switch(e.platform){case"win32":return"Windows NT";case"darwin":return"macOS";case"linux":return"Linux";default:return e.platform}}function m(){if(u.isNode)return e.versions.node}function w(){return u.isNode?"NodeJS":"Browser"}}).call(this,n(14))},function(e,t,n){"use strict";n.r(t);var r,o=n(3),i=n(4),a=n(0),s=(r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)t.hasOwnProperty(n)&&(e[n]=t[n])},function(e,t){function n(){this.constructor=e}r(e,t),e.prototype=null===t?Object.create(t):(n.prototype=t.prototype,new n)}),c=Object.assign||function(e){for(var t,n=1,r=arguments.length;n0&&o[o.length-1])&&(6===i[0]||2===i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]=200&&s.status<300?n(new i.b(s.status,s.statusText,s.response||s.responseText)):r(new o.b(s.statusText,s.status))},s.onerror=function(){t.logger.log(a.a.Warning,"Error from HTTP request. "+s.status+": "+s.statusText+"."),r(new o.b(s.statusText,s.status))},s.ontimeout=function(){t.logger.log(a.a.Warning,"Timeout from HTTP request."),r(new o.c)},s.send(e.content||"")}):Promise.reject(new Error("No url defined.")):Promise.reject(new Error("No method defined."))},t}(i.a),y=function(){var e=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)t.hasOwnProperty(n)&&(e[n]=t[n])};return function(t,n){function r(){this.constructor=t}e(t,n),t.prototype=null===n?Object.create(n):(r.prototype=n.prototype,new r)}}(),v=function(e){function t(t){var n=e.call(this)||this;return"undefined"!=typeof fetch?n.httpClient=new f(t):"undefined"!=typeof XMLHttpRequest?n.httpClient=new g(t):n.httpClient=new p.a(t),n}return y(t,e),t.prototype.send=function(e){return e.abortSignal&&e.abortSignal.aborted?Promise.reject(new o.a):e.method?e.url?this.httpClient.send(e):Promise.reject(new Error("No url defined.")):Promise.reject(new Error("No method defined."))},t.prototype.getCookieString=function(e){return this.httpClient.getCookieString(e)},t}(i.a),b=n(44);!function(e){e[e.Invocation=1]="Invocation",e[e.StreamItem=2]="StreamItem",e[e.Completion=3]="Completion",e[e.StreamInvocation=4]="StreamInvocation",e[e.CancelInvocation=5]="CancelInvocation",e[e.Ping=6]="Ping",e[e.Close=7]="Close"}(h||(h={}));var m,w=n(1),E=function(){function e(){this.observers=[]}return e.prototype.next=function(e){for(var t=0,n=this.observers;t0&&o[o.length-1])&&(6===i[0]||2===i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]0&&o[o.length-1])&&(6===i[0]||2===i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]0&&o[o.length-1])&&(6===i[0]||2===i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]0&&o[o.length-1])&&(6===i[0]||2===i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]0&&o[o.length-1])&&(6===i[0]||2===i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]0?[2,Promise.reject(new Error("Unable to connect to the server with any of the available transports. "+i.join(" ")))]:[2,Promise.reject(new Error("None of the transports supported by the client are supported by the server."))]}})})},e.prototype.constructTransport=function(e){switch(e){case I.WebSockets:if(!this.options.WebSocket)throw new Error("'WebSocket' is not supported in your environment.");return new F(this.httpClient,this.accessTokenFactory,this.logger,this.options.logMessageContent||!1,this.options.WebSocket);case I.ServerSentEvents:if(!this.options.EventSource)throw new Error("'EventSource' is not supported in your environment.");return new B(this.httpClient,this.accessTokenFactory,this.logger,this.options.logMessageContent||!1,this.options.EventSource,this.options.withCredentials);case I.LongPolling:return new M(this.httpClient,this.accessTokenFactory,this.logger,this.options.logMessageContent||!1,this.options.withCredentials);default:throw new Error("Unknown transport: "+e+".")}},e.prototype.startTransport=function(e,t){var n=this;return this.transport.onreceive=this.onreceive,this.transport.onclose=function(e){return n.stopConnection(e)},this.transport.connect(e,t)},e.prototype.resolveTransportOrError=function(e,t,n){var r=I[e.transport];if(null==r)return this.logger.log(a.a.Debug,"Skipping transport '"+e.transport+"' because it is not supported by this client."),new Error("Skipping transport '"+e.transport+"' because it is not supported by this client.");if(!function(e,t){return!e||0!=(t&e)}(t,r))return this.logger.log(a.a.Debug,"Skipping transport '"+I[r]+"' because it was disabled by the client."),new Error("'"+I[r]+"' is disabled by the client.");if(!(e.transferFormats.map(function(e){return k[e]}).indexOf(n)>=0))return this.logger.log(a.a.Debug,"Skipping transport '"+I[r]+"' because it does not support the requested transfer format '"+k[n]+"'."),new Error("'"+I[r]+"' does not support "+k[n]+".");if(r===I.WebSockets&&!this.options.WebSocket||r===I.ServerSentEvents&&!this.options.EventSource)return this.logger.log(a.a.Debug,"Skipping transport '"+I[r]+"' because it is not supported in your environment.'"),new Error("'"+I[r]+"' is not supported in your environment.");this.logger.log(a.a.Debug,"Selecting transport '"+I[r]+"'.");try{return this.constructTransport(r)}catch(e){return e}},e.prototype.isITransport=function(e){return e&&"object"==typeof e&&"connect"in e},e.prototype.stopConnection=function(e){var t=this;if(this.logger.log(a.a.Debug,"HttpConnection.stopConnection("+e+") called while in state "+this.connectionState+"."),this.transport=void 0,e=this.stopError||e,this.stopError=void 0,"Disconnected"!==this.connectionState){if("Connecting "===this.connectionState)throw this.logger.log(a.a.Warning,"Call to HttpConnection.stopConnection("+e+") was ignored because the connection is still in the connecting state."),new Error("HttpConnection.stopConnection("+e+") was called while the connection is still in the connecting state.");if("Disconnecting"===this.connectionState&&this.stopPromiseResolver(),e?this.logger.log(a.a.Error,"Connection disconnected with error '"+e+"'."):this.logger.log(a.a.Information,"Connection disconnected."),this.sendQueue&&(this.sendQueue.stop().catch(function(e){t.logger.log(a.a.Error,"TransportSendQueue.stop() threw error '"+e+"'.")}),this.sendQueue=void 0),this.connectionId=void 0,this.connectionState="Disconnected",this.connectionStarted){this.connectionStarted=!1;try{this.onclose&&this.onclose(e)}catch(t){this.logger.log(a.a.Error,"HttpConnection.onclose("+e+") threw error '"+t+"'.")}}}else this.logger.log(a.a.Debug,"Call to HttpConnection.stopConnection("+e+") was ignored because the connection is already in the disconnected state.")},e.prototype.resolveUrl=function(e){if(0===e.lastIndexOf("https://",0)||0===e.lastIndexOf("http://",0))return e;if(!w.c.isBrowser||!window.document)throw new Error("Cannot resolve '"+e+"'.");var t=window.document.createElement("a");return t.href=e,this.logger.log(a.a.Information,"Normalizing '"+e+"' to '"+t.href+"'."),t.href},e.prototype.resolveNegotiateUrl=function(e){var t=e.indexOf("?"),n=e.substring(0,-1===t?e.length:t);return"/"!==n[n.length-1]&&(n+="/"),n+="negotiate",-1===(n+=-1===t?"":e.substring(t)).indexOf("negotiateVersion")&&(n+=-1===t?"?":"&",n+="negotiateVersion="+this.negotiateVersion),n},e}();var K=function(){function e(e){this.transport=e,this.buffer=[],this.executing=!0,this.sendBufferedData=new V,this.transportResult=new V,this.sendLoopPromise=this.sendLoop()}return e.prototype.send=function(e){return this.bufferData(e),this.transportResult||(this.transportResult=new V),this.transportResult.promise},e.prototype.stop=function(){return this.executing=!1,this.sendBufferedData.resolve(),this.sendLoopPromise},e.prototype.bufferData=function(e){if(this.buffer.length&&typeof this.buffer[0]!=typeof e)throw new Error("Expected data to be of type "+typeof this.buffer+" but was of type "+typeof e);this.buffer.push(e),this.sendBufferedData.resolve()},e.prototype.sendLoop=function(){return H(this,void 0,void 0,function(){var t,n,r;return q(this,function(o){switch(o.label){case 0:return[4,this.sendBufferedData.promise];case 1:if(o.sent(),!this.executing)return this.transportResult&&this.transportResult.reject("Connection stopped."),[3,6];this.sendBufferedData=new V,t=this.transportResult,this.transportResult=void 0,n="string"==typeof this.buffer[0]?this.buffer.join(""):e.concatBuffers(this.buffer),this.buffer.length=0,o.label=2;case 2:return o.trys.push([2,4,,5]),[4,this.transport.send(n)];case 3:return o.sent(),t.resolve(),[3,5];case 4:return r=o.sent(),t.reject(r),[3,5];case 5:return[3,0];case 6:return[2]}})})},e.concatBuffers=function(e){for(var t=e.map(function(e){return e.byteLength}).reduce(function(e,t){return e+t}),n=new Uint8Array(t),r=0,o=0,i=e;o * @license MIT */ -var r=n(50),o=n(51),i=n(52);function a(){return c.TYPED_ARRAY_SUPPORT?2147483647:1073741823}function s(e,t){if(a()=a())throw new RangeError("Attempt to allocate Buffer larger than maximum size: 0x"+a().toString(16)+" bytes");return 0|e}function d(e,t){if(c.isBuffer(e))return e.length;if("undefined"!=typeof ArrayBuffer&&"function"==typeof ArrayBuffer.isView&&(ArrayBuffer.isView(e)||e instanceof ArrayBuffer))return e.byteLength;"string"!=typeof e&&(e=""+e);var n=e.length;if(0===n)return 0;for(var r=!1;;)switch(t){case"ascii":case"latin1":case"binary":return n;case"utf8":case"utf-8":case void 0:return F(e).length;case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return 2*n;case"hex":return n>>>1;case"base64":return H(e).length;default:if(r)return F(e).length;t=(""+t).toLowerCase(),r=!0}}function g(e,t,n){var r=e[t];e[t]=e[n],e[n]=r}function y(e,t,n,r,o){if(0===e.length)return-1;if("string"==typeof n?(r=n,n=0):n>2147483647?n=2147483647:n<-2147483648&&(n=-2147483648),n=+n,isNaN(n)&&(n=o?0:e.length-1),n<0&&(n=e.length+n),n>=e.length){if(o)return-1;n=e.length-1}else if(n<0){if(!o)return-1;n=0}if("string"==typeof t&&(t=c.from(t,r)),c.isBuffer(t))return 0===t.length?-1:v(e,t,n,r,o);if("number"==typeof t)return t&=255,c.TYPED_ARRAY_SUPPORT&&"function"==typeof Uint8Array.prototype.indexOf?o?Uint8Array.prototype.indexOf.call(e,t,n):Uint8Array.prototype.lastIndexOf.call(e,t,n):v(e,[t],n,r,o);throw new TypeError("val must be string, number or Buffer")}function v(e,t,n,r,o){var i,a=1,s=e.length,c=t.length;if(void 0!==r&&("ucs2"===(r=String(r).toLowerCase())||"ucs-2"===r||"utf16le"===r||"utf-16le"===r)){if(e.length<2||t.length<2)return-1;a=2,s/=2,c/=2,n/=2}function u(e,t){return 1===a?e[t]:e.readUInt16BE(t*a)}if(o){var l=-1;for(i=n;is&&(n=s-c),i=n;i>=0;i--){for(var f=!0,h=0;ho&&(r=o):r=o;var i=t.length;if(i%2!=0)throw new TypeError("Invalid hex string");r>i/2&&(r=i/2);for(var a=0;a>8,o=n%256,i.push(o),i.push(r);return i}(t,e.length-n),e,n,r)}function _(e,t,n){return 0===t&&n===e.length?r.fromByteArray(e):r.fromByteArray(e.slice(t,n))}function I(e,t,n){n=Math.min(e.length,n);for(var r=[],o=t;o239?4:u>223?3:u>191?2:1;if(o+f<=n)switch(f){case 1:u<128&&(l=u);break;case 2:128==(192&(i=e[o+1]))&&(c=(31&u)<<6|63&i)>127&&(l=c);break;case 3:i=e[o+1],a=e[o+2],128==(192&i)&&128==(192&a)&&(c=(15&u)<<12|(63&i)<<6|63&a)>2047&&(c<55296||c>57343)&&(l=c);break;case 4:i=e[o+1],a=e[o+2],s=e[o+3],128==(192&i)&&128==(192&a)&&128==(192&s)&&(c=(15&u)<<18|(63&i)<<12|(63&a)<<6|63&s)>65535&&c<1114112&&(l=c)}null===l?(l=65533,f=1):l>65535&&(l-=65536,r.push(l>>>10&1023|55296),l=56320|1023&l),r.push(l),o+=f}return function(e){var t=e.length;if(t<=T)return String.fromCharCode.apply(String,e);var n="",r=0;for(;rthis.length)return"";if((void 0===n||n>this.length)&&(n=this.length),n<=0)return"";if((n>>>=0)<=(t>>>=0))return"";for(e||(e="utf8");;)switch(e){case"hex":return x(this,t,n);case"utf8":case"utf-8":return I(this,t,n);case"ascii":return k(this,t,n);case"latin1":case"binary":return P(this,t,n);case"base64":return _(this,t,n);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return R(this,t,n);default:if(r)throw new TypeError("Unknown encoding: "+e);e=(e+"").toLowerCase(),r=!0}}.apply(this,arguments)},c.prototype.equals=function(e){if(!c.isBuffer(e))throw new TypeError("Argument must be a Buffer");return this===e||0===c.compare(this,e)},c.prototype.inspect=function(){var e="",n=t.INSPECT_MAX_BYTES;return this.length>0&&(e=this.toString("hex",0,n).match(/.{2}/g).join(" "),this.length>n&&(e+=" ... ")),""},c.prototype.compare=function(e,t,n,r,o){if(!c.isBuffer(e))throw new TypeError("Argument must be a Buffer");if(void 0===t&&(t=0),void 0===n&&(n=e?e.length:0),void 0===r&&(r=0),void 0===o&&(o=this.length),t<0||n>e.length||r<0||o>this.length)throw new RangeError("out of range index");if(r>=o&&t>=n)return 0;if(r>=o)return-1;if(t>=n)return 1;if(this===e)return 0;for(var i=(o>>>=0)-(r>>>=0),a=(n>>>=0)-(t>>>=0),s=Math.min(i,a),u=this.slice(r,o),l=e.slice(t,n),f=0;fo)&&(n=o),e.length>0&&(n<0||t<0)||t>this.length)throw new RangeError("Attempt to write outside buffer bounds");r||(r="utf8");for(var i=!1;;)switch(r){case"hex":return b(this,e,t,n);case"utf8":case"utf-8":return m(this,e,t,n);case"ascii":return w(this,e,t,n);case"latin1":case"binary":return E(this,e,t,n);case"base64":return S(this,e,t,n);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return C(this,e,t,n);default:if(i)throw new TypeError("Unknown encoding: "+r);r=(""+r).toLowerCase(),i=!0}},c.prototype.toJSON=function(){return{type:"Buffer",data:Array.prototype.slice.call(this._arr||this,0)}};var T=4096;function k(e,t,n){var r="";n=Math.min(e.length,n);for(var o=t;or)&&(n=r);for(var o="",i=t;in)throw new RangeError("Trying to access beyond buffer length")}function O(e,t,n,r,o,i){if(!c.isBuffer(e))throw new TypeError('"buffer" argument must be a Buffer instance');if(t>o||te.length)throw new RangeError("Index out of range")}function L(e,t,n,r){t<0&&(t=65535+t+1);for(var o=0,i=Math.min(e.length-n,2);o>>8*(r?o:1-o)}function M(e,t,n,r){t<0&&(t=4294967295+t+1);for(var o=0,i=Math.min(e.length-n,4);o>>8*(r?o:3-o)&255}function A(e,t,n,r,o,i){if(n+r>e.length)throw new RangeError("Index out of range");if(n<0)throw new RangeError("Index out of range")}function B(e,t,n,r,i){return i||A(e,0,n,4),o.write(e,t,n,r,23,4),n+4}function j(e,t,n,r,i){return i||A(e,0,n,8),o.write(e,t,n,r,52,8),n+8}c.prototype.slice=function(e,t){var n,r=this.length;if((e=~~e)<0?(e+=r)<0&&(e=0):e>r&&(e=r),(t=void 0===t?r:~~t)<0?(t+=r)<0&&(t=0):t>r&&(t=r),t0&&(o*=256);)r+=this[e+--t]*o;return r},c.prototype.readUInt8=function(e,t){return t||D(e,1,this.length),this[e]},c.prototype.readUInt16LE=function(e,t){return t||D(e,2,this.length),this[e]|this[e+1]<<8},c.prototype.readUInt16BE=function(e,t){return t||D(e,2,this.length),this[e]<<8|this[e+1]},c.prototype.readUInt32LE=function(e,t){return t||D(e,4,this.length),(this[e]|this[e+1]<<8|this[e+2]<<16)+16777216*this[e+3]},c.prototype.readUInt32BE=function(e,t){return t||D(e,4,this.length),16777216*this[e]+(this[e+1]<<16|this[e+2]<<8|this[e+3])},c.prototype.readIntLE=function(e,t,n){e|=0,t|=0,n||D(e,t,this.length);for(var r=this[e],o=1,i=0;++i=(o*=128)&&(r-=Math.pow(2,8*t)),r},c.prototype.readIntBE=function(e,t,n){e|=0,t|=0,n||D(e,t,this.length);for(var r=t,o=1,i=this[e+--r];r>0&&(o*=256);)i+=this[e+--r]*o;return i>=(o*=128)&&(i-=Math.pow(2,8*t)),i},c.prototype.readInt8=function(e,t){return t||D(e,1,this.length),128&this[e]?-1*(255-this[e]+1):this[e]},c.prototype.readInt16LE=function(e,t){t||D(e,2,this.length);var n=this[e]|this[e+1]<<8;return 32768&n?4294901760|n:n},c.prototype.readInt16BE=function(e,t){t||D(e,2,this.length);var n=this[e+1]|this[e]<<8;return 32768&n?4294901760|n:n},c.prototype.readInt32LE=function(e,t){return t||D(e,4,this.length),this[e]|this[e+1]<<8|this[e+2]<<16|this[e+3]<<24},c.prototype.readInt32BE=function(e,t){return t||D(e,4,this.length),this[e]<<24|this[e+1]<<16|this[e+2]<<8|this[e+3]},c.prototype.readFloatLE=function(e,t){return t||D(e,4,this.length),o.read(this,e,!0,23,4)},c.prototype.readFloatBE=function(e,t){return t||D(e,4,this.length),o.read(this,e,!1,23,4)},c.prototype.readDoubleLE=function(e,t){return t||D(e,8,this.length),o.read(this,e,!0,52,8)},c.prototype.readDoubleBE=function(e,t){return t||D(e,8,this.length),o.read(this,e,!1,52,8)},c.prototype.writeUIntLE=function(e,t,n,r){(e=+e,t|=0,n|=0,r)||O(this,e,t,n,Math.pow(2,8*n)-1,0);var o=1,i=0;for(this[t]=255&e;++i=0&&(i*=256);)this[t+o]=e/i&255;return t+n},c.prototype.writeUInt8=function(e,t,n){return e=+e,t|=0,n||O(this,e,t,1,255,0),c.TYPED_ARRAY_SUPPORT||(e=Math.floor(e)),this[t]=255&e,t+1},c.prototype.writeUInt16LE=function(e,t,n){return e=+e,t|=0,n||O(this,e,t,2,65535,0),c.TYPED_ARRAY_SUPPORT?(this[t]=255&e,this[t+1]=e>>>8):L(this,e,t,!0),t+2},c.prototype.writeUInt16BE=function(e,t,n){return e=+e,t|=0,n||O(this,e,t,2,65535,0),c.TYPED_ARRAY_SUPPORT?(this[t]=e>>>8,this[t+1]=255&e):L(this,e,t,!1),t+2},c.prototype.writeUInt32LE=function(e,t,n){return e=+e,t|=0,n||O(this,e,t,4,4294967295,0),c.TYPED_ARRAY_SUPPORT?(this[t+3]=e>>>24,this[t+2]=e>>>16,this[t+1]=e>>>8,this[t]=255&e):M(this,e,t,!0),t+4},c.prototype.writeUInt32BE=function(e,t,n){return e=+e,t|=0,n||O(this,e,t,4,4294967295,0),c.TYPED_ARRAY_SUPPORT?(this[t]=e>>>24,this[t+1]=e>>>16,this[t+2]=e>>>8,this[t+3]=255&e):M(this,e,t,!1),t+4},c.prototype.writeIntLE=function(e,t,n,r){if(e=+e,t|=0,!r){var o=Math.pow(2,8*n-1);O(this,e,t,n,o-1,-o)}var i=0,a=1,s=0;for(this[t]=255&e;++i>0)-s&255;return t+n},c.prototype.writeIntBE=function(e,t,n,r){if(e=+e,t|=0,!r){var o=Math.pow(2,8*n-1);O(this,e,t,n,o-1,-o)}var i=n-1,a=1,s=0;for(this[t+i]=255&e;--i>=0&&(a*=256);)e<0&&0===s&&0!==this[t+i+1]&&(s=1),this[t+i]=(e/a>>0)-s&255;return t+n},c.prototype.writeInt8=function(e,t,n){return e=+e,t|=0,n||O(this,e,t,1,127,-128),c.TYPED_ARRAY_SUPPORT||(e=Math.floor(e)),e<0&&(e=255+e+1),this[t]=255&e,t+1},c.prototype.writeInt16LE=function(e,t,n){return e=+e,t|=0,n||O(this,e,t,2,32767,-32768),c.TYPED_ARRAY_SUPPORT?(this[t]=255&e,this[t+1]=e>>>8):L(this,e,t,!0),t+2},c.prototype.writeInt16BE=function(e,t,n){return e=+e,t|=0,n||O(this,e,t,2,32767,-32768),c.TYPED_ARRAY_SUPPORT?(this[t]=e>>>8,this[t+1]=255&e):L(this,e,t,!1),t+2},c.prototype.writeInt32LE=function(e,t,n){return e=+e,t|=0,n||O(this,e,t,4,2147483647,-2147483648),c.TYPED_ARRAY_SUPPORT?(this[t]=255&e,this[t+1]=e>>>8,this[t+2]=e>>>16,this[t+3]=e>>>24):M(this,e,t,!0),t+4},c.prototype.writeInt32BE=function(e,t,n){return e=+e,t|=0,n||O(this,e,t,4,2147483647,-2147483648),e<0&&(e=4294967295+e+1),c.TYPED_ARRAY_SUPPORT?(this[t]=e>>>24,this[t+1]=e>>>16,this[t+2]=e>>>8,this[t+3]=255&e):M(this,e,t,!1),t+4},c.prototype.writeFloatLE=function(e,t,n){return B(this,e,t,!0,n)},c.prototype.writeFloatBE=function(e,t,n){return B(this,e,t,!1,n)},c.prototype.writeDoubleLE=function(e,t,n){return j(this,e,t,!0,n)},c.prototype.writeDoubleBE=function(e,t,n){return j(this,e,t,!1,n)},c.prototype.copy=function(e,t,n,r){if(n||(n=0),r||0===r||(r=this.length),t>=e.length&&(t=e.length),t||(t=0),r>0&&r=this.length)throw new RangeError("sourceStart out of bounds");if(r<0)throw new RangeError("sourceEnd out of bounds");r>this.length&&(r=this.length),e.length-t=0;--o)e[o+t]=this[o+n];else if(i<1e3||!c.TYPED_ARRAY_SUPPORT)for(o=0;o>>=0,n=void 0===n?this.length:n>>>0,e||(e=0),"number"==typeof e)for(i=t;i55295&&n<57344){if(!o){if(n>56319){(t-=3)>-1&&i.push(239,191,189);continue}if(a+1===r){(t-=3)>-1&&i.push(239,191,189);continue}o=n;continue}if(n<56320){(t-=3)>-1&&i.push(239,191,189),o=n;continue}n=65536+(o-55296<<10|n-56320)}else o&&(t-=3)>-1&&i.push(239,191,189);if(o=null,n<128){if((t-=1)<0)break;i.push(n)}else if(n<2048){if((t-=2)<0)break;i.push(n>>6|192,63&n|128)}else if(n<65536){if((t-=3)<0)break;i.push(n>>12|224,n>>6&63|128,63&n|128)}else{if(!(n<1114112))throw new Error("Invalid code point");if((t-=4)<0)break;i.push(n>>18|240,n>>12&63|128,n>>6&63|128,63&n|128)}}return i}function H(e){return r.toByteArray(function(e){if((e=function(e){return e.trim?e.trim():e.replace(/^\s+|\s+$/g,"")}(e).replace(U,"")).length<2)return"";for(;e.length%4!=0;)e+="=";return e}(e))}function q(e,t,n,r){for(var o=0;o=t.length||o>=e.length);++o)t[o+n]=e[o];return o}}).call(this,n(9))},function(e,t,n){"use strict";n.d(t,"a",function(){return r});var r=function(){function e(){}return e.prototype.log=function(e,t){},e.instance=new e,e}()},function(e,t,n){"use strict";n.d(t,"a",function(){return r});var r=function(){function e(){}return e.write=function(t){return""+t+e.RecordSeparator},e.parse=function(t){if(t[t.length-1]!==e.RecordSeparator)throw new Error("Message is incomplete.");var n=t.split(e.RecordSeparator);return n.pop(),n},e.RecordSeparatorCode=30,e.RecordSeparator=String.fromCharCode(e.RecordSeparatorCode),e}()},function(e,t){var n;n=function(){return this}();try{n=n||new Function("return this")()}catch(e){"object"==typeof window&&(n=window)}e.exports=n},function(e,t,n){"use strict";var r=n(23),o=Object.keys||function(e){var t=[];for(var n in e)t.push(n);return t};e.exports=f;var i=n(21);i.inherits=n(15);var a=n(36),s=n(41);i.inherits(f,a);for(var c=o(s.prototype),u=0;u=0,"must have a non-negative type"),o(a,"must have a decode function"),this.registerEncoder(function(e){return e instanceof t},function(t){var o=i(),a=r.allocUnsafe(1);return a.writeInt8(e,0),o.append(a),o.append(n(t)),o}),this.registerDecoder(e,a),this},registerEncoder:function(e,n){return o(e,"must have an encode function"),o(n,"must have an encode function"),t.push({check:e,encode:n}),this},registerDecoder:function(e,t){return o(e>=0,"must have a non-negative type"),o(t,"must have a decode function"),n.push({type:e,decode:t}),this},encoder:a.encoder,decoder:a.decoder,buffer:!0,type:"msgpack5",IncompleteBufferError:s.IncompleteBufferError}}},function(e,t,n){"use strict";var r=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))(function(o,i){function a(e){try{c(r.next(e))}catch(e){i(e)}}function s(e){try{c(r.throw(e))}catch(e){i(e)}}function c(e){e.done?o(e.value):new n(function(t){t(e.value)}).then(a,s)}c((r=r.apply(e,t||[])).next())})},o=this&&this.__generator||function(e,t){var n,r,o,i,a={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return i={next:s(0),throw:s(1),return:s(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function s(i){return function(s){return function(i){if(n)throw new TypeError("Generator is already executing.");for(;a;)try{if(n=1,r&&(o=2&i[0]?r.return:i[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,i[1])).done)return o;switch(r=0,o&&(i=[2&i[0],o.value]),i[0]){case 0:case 1:o=i;break;case 4:return a.label++,{value:i[1],done:!1};case 5:a.label++,r=i[1],i=[0];continue;case 7:i=a.ops.pop(),a.trys.pop();continue;default:if(!(o=(o=a.trys).length>0&&o[o.length-1])&&(6===i[0]||2===i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]0&&!t)throw new Error("New logical elements must start empty, or allowExistingContents must be true");return e[r]=[],e}function s(e,t,n){var i=e;if(e instanceof Comment&&(u(i)&&u(i).length>0))throw new Error("Not implemented: inserting non-empty logical container");if(c(i))throw new Error("Not implemented: moving existing logical children");var a=u(t);if(n0;)e(r,0);var i=r;i.parentNode.removeChild(i)},t.getLogicalParent=c,t.getLogicalSiblingEnd=function(e){return e[i]||null},t.getLogicalChild=function(e,t){return u(e)[t]},t.isSvgElement=function(e){return"http://www.w3.org/2000/svg"===l(e).namespaceURI},t.getLogicalChildrenArray=u,t.permuteLogicalChildren=function(e,t){var n=u(e);t.forEach(function(e){e.moveRangeStart=n[e.fromSiblingIndex],e.moveRangeEnd=function e(t){if(t instanceof Element)return t;var n=f(t);if(n)return n.previousSibling;var r=c(t);return r instanceof Element?r.lastChild:e(r)}(e.moveRangeStart)}),t.forEach(function(t){var r=t.moveToBeforeMarker=document.createComment("marker"),o=n[t.toSiblingIndex+1];o?o.parentNode.insertBefore(r,o):h(r,e)}),t.forEach(function(e){for(var t=e.moveToBeforeMarker,n=t.parentNode,r=e.moveRangeStart,o=e.moveRangeEnd,i=r;i;){var a=i.nextSibling;if(n.insertBefore(i,t),i===o)break;i=a}n.removeChild(t)}),t.forEach(function(e){n[e.toSiblingIndex]=e.moveRangeStart})},t.getClosestDomElement=l},function(e,t,n){var r=n(6),o=r.Buffer;function i(e,t){for(var n in e)t[n]=e[n]}function a(e,t,n){return o(e,t,n)}o.from&&o.alloc&&o.allocUnsafe&&o.allocUnsafeSlow?e.exports=r:(i(r,t),t.Buffer=a),i(o,a),a.from=function(e,t,n){if("number"==typeof e)throw new TypeError("Argument must not be a number");return o(e,t,n)},a.alloc=function(e,t,n){if("number"!=typeof e)throw new TypeError("Argument must be a number");var r=o(e);return void 0!==t?"string"==typeof n?r.fill(t,n):r.fill(t):r.fill(0),r},a.allocUnsafe=function(e){if("number"!=typeof e)throw new TypeError("Argument must be a number");return o(e)},a.allocUnsafeSlow=function(e){if("number"!=typeof e)throw new TypeError("Argument must be a number");return r.SlowBuffer(e)}},function(e,t){"function"==typeof Object.create?e.exports=function(e,t){e.super_=t,e.prototype=Object.create(t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}})}:e.exports=function(e,t){e.super_=t;var n=function(){};n.prototype=t.prototype,e.prototype=new n,e.prototype.constructor=e}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),function(e){e[e.Trace=0]="Trace",e[e.Debug=1]="Debug",e[e.Information=2]="Information",e[e.Warning=3]="Warning",e[e.Error=4]="Error",e[e.Critical=5]="Critical",e[e.None=6]="None"}(t.LogLevel||(t.LogLevel={}))},function(e,t,n){"use strict";var r;!function(e){window.DotNet=e;var t=[],n={},r={},o=1,i=null;function a(e){t.push(e)}function s(e,t,n,r){var o=u();if(o.invokeDotNetFromJS){var i=JSON.stringify(r,g),a=o.invokeDotNetFromJS(e,t,n,i);return a?f(a):null}throw new Error("The current dispatcher does not support synchronous calls from JS to .NET. Use invokeMethodAsync instead.")}function c(e,t,r,i){if(e&&r)throw new Error("For instance method calls, assemblyName should be null. Received '"+e+"'.");var a=o++,s=new Promise(function(e,t){n[a]={resolve:e,reject:t}});try{var c=JSON.stringify(i,g);u().beginInvokeDotNetFromJS(a,e,t,r,c)}catch(e){l(a,!1,e)}return s}function u(){if(null!==i)return i;throw new Error("No .NET call dispatcher has been set.")}function l(e,t,r){if(!n.hasOwnProperty(e))throw new Error("There is no pending async call with ID "+e+".");var o=n[e];delete n[e],t?o.resolve(r):o.reject(r)}function f(e){return e?JSON.parse(e,function(e,n){return t.reduce(function(t,n){return n(e,t)},n)}):null}function h(e){return e instanceof Error?e.message+"\n"+e.stack:e?e.toString():"null"}function p(e){if(r.hasOwnProperty(e))return r[e];var t,n=window,o="window";if(e.split(".").forEach(function(e){if(!(e in n))throw new Error("Could not find '"+e+"' in '"+o+"'.");t=n,n=n[e],o+="."+e}),n instanceof Function)return n=n.bind(t),r[e]=n,n;throw new Error("The value '"+o+"' is not a function.")}e.attachDispatcher=function(e){i=e},e.attachReviver=a,e.invokeMethod=function(e,t){for(var n=[],r=2;r1)for(var n=1;nthis.length)&&(r=this.length),n>=this.length)return e||i.alloc(0);if(r<=0)return e||i.alloc(0);var o,a,s=!!e,c=this._offset(n),u=r-n,l=u,f=s&&t||0,h=c[1];if(0===n&&r==this.length){if(!s)return 1===this._bufs.length?this._bufs[0]:i.concat(this._bufs,this.length);for(a=0;a(o=this._bufs[a].length-h))){this._bufs[a].copy(e,f,h,h+l);break}this._bufs[a].copy(e,f,h),f+=o,l-=o,h&&(h=0)}return e},a.prototype.shallowSlice=function(e,t){e=e||0,t=t||this.length,e<0&&(e+=this.length),t<0&&(t+=this.length);var n=this._offset(e),r=this._offset(t),o=this._bufs.slice(n[0],r[0]+1);return 0==r[1]?o.pop():o[o.length-1]=o[o.length-1].slice(0,r[1]),0!=n[1]&&(o[0]=o[0].slice(n[1])),new a(o)},a.prototype.toString=function(e,t,n){return this.slice(t,n).toString(e)},a.prototype.consume=function(e){for(;this._bufs.length;){if(!(e>=this._bufs[0].length)){this._bufs[0]=this._bufs[0].slice(e),this.length-=e;break}e-=this._bufs[0].length,this.length-=this._bufs[0].length,this._bufs.shift()}return this},a.prototype.duplicate=function(){for(var e=0,t=new a;e0&&o[o.length-1])&&(6===i[0]||2===i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]=i)return e;switch(e){case"%s":return String(r[n++]);case"%d":return Number(r[n++]);case"%j":try{return JSON.stringify(r[n++])}catch(e){return"[Circular]"}default:return e}}),c=r[n];n=3&&(r.depth=arguments[2]),arguments.length>=4&&(r.colors=arguments[3]),d(n)?r.showHidden=n:n&&t._extend(r,n),b(r.showHidden)&&(r.showHidden=!1),b(r.depth)&&(r.depth=2),b(r.colors)&&(r.colors=!1),b(r.customInspect)&&(r.customInspect=!0),r.colors&&(r.stylize=c),l(r,e,r.depth)}function c(e,t){var n=s.styles[t];return n?"["+s.colors[n][0]+"m"+e+"["+s.colors[n][1]+"m":e}function u(e,t){return e}function l(e,n,r){if(e.customInspect&&n&&C(n.inspect)&&n.inspect!==t.inspect&&(!n.constructor||n.constructor.prototype!==n)){var o=n.inspect(r,e);return v(o)||(o=l(e,o,r)),o}var i=function(e,t){if(b(t))return e.stylize("undefined","undefined");if(v(t)){var n="'"+JSON.stringify(t).replace(/^"|"$/g,"").replace(/'/g,"\\'").replace(/\\"/g,'"')+"'";return e.stylize(n,"string")}if(y(t))return e.stylize(""+t,"number");if(d(t))return e.stylize(""+t,"boolean");if(g(t))return e.stylize("null","null")}(e,n);if(i)return i;var a=Object.keys(n),s=function(e){var t={};return e.forEach(function(e,n){t[e]=!0}),t}(a);if(e.showHidden&&(a=Object.getOwnPropertyNames(n)),S(n)&&(a.indexOf("message")>=0||a.indexOf("description")>=0))return f(n);if(0===a.length){if(C(n)){var c=n.name?": "+n.name:"";return e.stylize("[Function"+c+"]","special")}if(m(n))return e.stylize(RegExp.prototype.toString.call(n),"regexp");if(E(n))return e.stylize(Date.prototype.toString.call(n),"date");if(S(n))return f(n)}var u,w="",_=!1,I=["{","}"];(p(n)&&(_=!0,I=["[","]"]),C(n))&&(w=" [Function"+(n.name?": "+n.name:"")+"]");return m(n)&&(w=" "+RegExp.prototype.toString.call(n)),E(n)&&(w=" "+Date.prototype.toUTCString.call(n)),S(n)&&(w=" "+f(n)),0!==a.length||_&&0!=n.length?r<0?m(n)?e.stylize(RegExp.prototype.toString.call(n),"regexp"):e.stylize("[Object]","special"):(e.seen.push(n),u=_?function(e,t,n,r,o){for(var i=[],a=0,s=t.length;a=0&&0,e+t.replace(/\u001b\[\d\d?m/g,"").length+1},0)>60)return n[0]+(""===t?"":t+"\n ")+" "+e.join(",\n ")+" "+n[1];return n[0]+t+" "+e.join(", ")+" "+n[1]}(u,w,I)):I[0]+w+I[1]}function f(e){return"["+Error.prototype.toString.call(e)+"]"}function h(e,t,n,r,o,i){var a,s,c;if((c=Object.getOwnPropertyDescriptor(t,o)||{value:t[o]}).get?s=c.set?e.stylize("[Getter/Setter]","special"):e.stylize("[Getter]","special"):c.set&&(s=e.stylize("[Setter]","special")),k(r,o)||(a="["+o+"]"),s||(e.seen.indexOf(c.value)<0?(s=g(n)?l(e,c.value,null):l(e,c.value,n-1)).indexOf("\n")>-1&&(s=i?s.split("\n").map(function(e){return" "+e}).join("\n").substr(2):"\n"+s.split("\n").map(function(e){return" "+e}).join("\n")):s=e.stylize("[Circular]","special")),b(a)){if(i&&o.match(/^\d+$/))return s;(a=JSON.stringify(""+o)).match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)?(a=a.substr(1,a.length-2),a=e.stylize(a,"name")):(a=a.replace(/'/g,"\\'").replace(/\\"/g,'"').replace(/(^"|"$)/g,"'"),a=e.stylize(a,"string"))}return a+": "+s}function p(e){return Array.isArray(e)}function d(e){return"boolean"==typeof e}function g(e){return null===e}function y(e){return"number"==typeof e}function v(e){return"string"==typeof e}function b(e){return void 0===e}function m(e){return w(e)&&"[object RegExp]"===_(e)}function w(e){return"object"==typeof e&&null!==e}function E(e){return w(e)&&"[object Date]"===_(e)}function S(e){return w(e)&&("[object Error]"===_(e)||e instanceof Error)}function C(e){return"function"==typeof e}function _(e){return Object.prototype.toString.call(e)}function I(e){return e<10?"0"+e.toString(10):e.toString(10)}t.debuglog=function(n){if(b(i)&&(i=e.env.NODE_DEBUG||""),n=n.toUpperCase(),!a[n])if(new RegExp("\\b"+n+"\\b","i").test(i)){var r=e.pid;a[n]=function(){var e=t.format.apply(t,arguments);console.error("%s %d: %s",n,r,e)}}else a[n]=function(){};return a[n]},t.inspect=s,s.colors={bold:[1,22],italic:[3,23],underline:[4,24],inverse:[7,27],white:[37,39],grey:[90,39],black:[30,39],blue:[34,39],cyan:[36,39],green:[32,39],magenta:[35,39],red:[31,39],yellow:[33,39]},s.styles={special:"cyan",number:"yellow",boolean:"yellow",undefined:"grey",null:"bold",string:"green",date:"magenta",regexp:"red"},t.isArray=p,t.isBoolean=d,t.isNull=g,t.isNullOrUndefined=function(e){return null==e},t.isNumber=y,t.isString=v,t.isSymbol=function(e){return"symbol"==typeof e},t.isUndefined=b,t.isRegExp=m,t.isObject=w,t.isDate=E,t.isError=S,t.isFunction=C,t.isPrimitive=function(e){return null===e||"boolean"==typeof e||"number"==typeof e||"string"==typeof e||"symbol"==typeof e||void 0===e},t.isBuffer=n(54);var T=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];function k(e,t){return Object.prototype.hasOwnProperty.call(e,t)}t.log=function(){var e,n;console.log("%s - %s",(e=new Date,n=[I(e.getHours()),I(e.getMinutes()),I(e.getSeconds())].join(":"),[e.getDate(),T[e.getMonth()],n].join(" ")),t.format.apply(t,arguments))},t.inherits=n(55),t._extend=function(e,t){if(!t||!w(t))return e;for(var n=Object.keys(t),r=n.length;r--;)e[n[r]]=t[n[r]];return e};var P="undefined"!=typeof Symbol?Symbol("util.promisify.custom"):void 0;function x(e,t){if(!e){var n=new Error("Promise was rejected with a falsy value");n.reason=e,e=n}return t(e)}t.promisify=function(e){if("function"!=typeof e)throw new TypeError('The "original" argument must be of type Function');if(P&&e[P]){var t;if("function"!=typeof(t=e[P]))throw new TypeError('The "util.promisify.custom" argument must be of type Function');return Object.defineProperty(t,P,{value:t,enumerable:!1,writable:!1,configurable:!0}),t}function t(){for(var t,n,r=new Promise(function(e,r){t=e,n=r}),o=[],i=0;i0?("string"==typeof t||a.objectMode||Object.getPrototypeOf(t)===u.prototype||(t=function(e){return u.from(e)}(t)),r?a.endEmitted?e.emit("error",new Error("stream.unshift() after end event")):E(e,a,t,!0):a.ended?e.emit("error",new Error("stream.push() after EOF")):(a.reading=!1,a.decoder&&!n?(t=a.decoder.write(t),a.objectMode||0!==t.length?E(e,a,t,!1):T(e,a)):E(e,a,t,!1))):r||(a.reading=!1));return function(e){return!e.ended&&(e.needReadable||e.lengtht.highWaterMark&&(t.highWaterMark=function(e){return e>=S?e=S:(e--,e|=e>>>1,e|=e>>>2,e|=e>>>4,e|=e>>>8,e|=e>>>16,e++),e}(e)),e<=t.length?e:t.ended?t.length:(t.needReadable=!0,0))}function _(e){var t=e._readableState;t.needReadable=!1,t.emittedReadable||(p("emitReadable",t.flowing),t.emittedReadable=!0,t.sync?o.nextTick(I,e):I(e))}function I(e){p("emit readable"),e.emit("readable"),R(e)}function T(e,t){t.readingMore||(t.readingMore=!0,o.nextTick(k,e,t))}function k(e,t){for(var n=t.length;!t.reading&&!t.flowing&&!t.ended&&t.length=t.length?(n=t.decoder?t.buffer.join(""):1===t.buffer.length?t.buffer.head.data:t.buffer.concat(t.length),t.buffer.clear()):n=function(e,t,n){var r;ei.length?i.length:e;if(a===i.length?o+=i:o+=i.slice(0,e),0===(e-=a)){a===i.length?(++r,n.next?t.head=n.next:t.head=t.tail=null):(t.head=n,n.data=i.slice(a));break}++r}return t.length-=r,o}(e,t):function(e,t){var n=u.allocUnsafe(e),r=t.head,o=1;r.data.copy(n),e-=r.data.length;for(;r=r.next;){var i=r.data,a=e>i.length?i.length:e;if(i.copy(n,n.length-e,0,a),0===(e-=a)){a===i.length?(++o,r.next?t.head=r.next:t.head=t.tail=null):(t.head=r,r.data=i.slice(a));break}++o}return t.length-=o,n}(e,t);return r}(e,t.buffer,t.decoder),n);var n}function O(e){var t=e._readableState;if(t.length>0)throw new Error('"endReadable()" called on non-empty stream');t.endEmitted||(t.ended=!0,o.nextTick(L,t,e))}function L(e,t){e.endEmitted||0!==e.length||(e.endEmitted=!0,t.readable=!1,t.emit("end"))}function M(e,t){for(var n=0,r=e.length;n=t.highWaterMark||t.ended))return p("read: emitReadable",t.length,t.ended),0===t.length&&t.ended?O(this):_(this),null;if(0===(e=C(e,t))&&t.ended)return 0===t.length&&O(this),null;var r,o=t.needReadable;return p("need readable",o),(0===t.length||t.length-e0?D(e,t):null)?(t.needReadable=!0,e=0):t.length-=e,0===t.length&&(t.ended||(t.needReadable=!0),n!==e&&t.ended&&O(this)),null!==r&&this.emit("data",r),r},m.prototype._read=function(e){this.emit("error",new Error("_read() is not implemented"))},m.prototype.pipe=function(e,t){var n=this,i=this._readableState;switch(i.pipesCount){case 0:i.pipes=e;break;case 1:i.pipes=[i.pipes,e];break;default:i.pipes.push(e)}i.pipesCount+=1,p("pipe count=%d opts=%j",i.pipesCount,t);var c=(!t||!1!==t.end)&&e!==r.stdout&&e!==r.stderr?l:m;function u(t,r){p("onunpipe"),t===n&&r&&!1===r.hasUnpiped&&(r.hasUnpiped=!0,p("cleanup"),e.removeListener("close",v),e.removeListener("finish",b),e.removeListener("drain",f),e.removeListener("error",y),e.removeListener("unpipe",u),n.removeListener("end",l),n.removeListener("end",m),n.removeListener("data",g),h=!0,!i.awaitDrain||e._writableState&&!e._writableState.needDrain||f())}function l(){p("onend"),e.end()}i.endEmitted?o.nextTick(c):n.once("end",c),e.on("unpipe",u);var f=function(e){return function(){var t=e._readableState;p("pipeOnDrain",t.awaitDrain),t.awaitDrain&&t.awaitDrain--,0===t.awaitDrain&&s(e,"data")&&(t.flowing=!0,R(e))}}(n);e.on("drain",f);var h=!1;var d=!1;function g(t){p("ondata"),d=!1,!1!==e.write(t)||d||((1===i.pipesCount&&i.pipes===e||i.pipesCount>1&&-1!==M(i.pipes,e))&&!h&&(p("false write response, pause",n._readableState.awaitDrain),n._readableState.awaitDrain++,d=!0),n.pause())}function y(t){p("onerror",t),m(),e.removeListener("error",y),0===s(e,"error")&&e.emit("error",t)}function v(){e.removeListener("finish",b),m()}function b(){p("onfinish"),e.removeListener("close",v),m()}function m(){p("unpipe"),n.unpipe(e)}return n.on("data",g),function(e,t,n){if("function"==typeof e.prependListener)return e.prependListener(t,n);e._events&&e._events[t]?a(e._events[t])?e._events[t].unshift(n):e._events[t]=[n,e._events[t]]:e.on(t,n)}(e,"error",y),e.once("close",v),e.once("finish",b),e.emit("pipe",n),i.flowing||(p("pipe resume"),n.resume()),e},m.prototype.unpipe=function(e){var t=this._readableState,n={hasUnpiped:!1};if(0===t.pipesCount)return this;if(1===t.pipesCount)return e&&e!==t.pipes?this:(e||(e=t.pipes),t.pipes=null,t.pipesCount=0,t.flowing=!1,e&&e.emit("unpipe",this,n),this);if(!e){var r=t.pipes,o=t.pipesCount;t.pipes=null,t.pipesCount=0,t.flowing=!1;for(var i=0;i0&&a.length>o&&!a.warned){a.warned=!0;var c=new Error("Possible EventEmitter memory leak detected. "+a.length+" "+String(t)+" listeners added. Use emitter.setMaxListeners() to increase limit");c.name="MaxListenersExceededWarning",c.emitter=e,c.type=t,c.count=a.length,s=c,console&&console.warn&&console.warn(s)}return e}function f(e,t,n){var r={fired:!1,wrapFn:void 0,target:e,type:t,listener:n},o=function(){for(var e=[],t=0;t0&&(a=t[0]),a instanceof Error)throw a;var s=new Error("Unhandled error."+(a?" ("+a.message+")":""));throw s.context=a,s}var c=o[e];if(void 0===c)return!1;if("function"==typeof c)i(c,this,t);else{var u=c.length,l=d(c,u);for(n=0;n=0;i--)if(n[i]===t||n[i].listener===t){a=n[i].listener,o=i;break}if(o<0)return this;0===o?n.shift():function(e,t){for(;t+1=0;r--)this.removeListener(e,t[r]);return this},s.prototype.listeners=function(e){return h(this,e,!0)},s.prototype.rawListeners=function(e){return h(this,e,!1)},s.listenerCount=function(e,t){return"function"==typeof e.listenerCount?e.listenerCount(t):p.call(e,t)},s.prototype.listenerCount=p,s.prototype.eventNames=function(){return this._eventsCount>0?r(this._events):[]}},function(e,t,n){e.exports=n(37).EventEmitter},function(e,t,n){"use strict";var r=n(23);function o(e,t){e.emit("error",t)}e.exports={destroy:function(e,t){var n=this,i=this._readableState&&this._readableState.destroyed,a=this._writableState&&this._writableState.destroyed;return i||a?(t?t(e):!e||this._writableState&&this._writableState.errorEmitted||r.nextTick(o,this,e),this):(this._readableState&&(this._readableState.destroyed=!0),this._writableState&&(this._writableState.destroyed=!0),this._destroy(e||null,function(e){!t&&e?(r.nextTick(o,n,e),n._writableState&&(n._writableState.errorEmitted=!0)):t&&t(e)}),this)},undestroy:function(){this._readableState&&(this._readableState.destroyed=!1,this._readableState.reading=!1,this._readableState.ended=!1,this._readableState.endEmitted=!1),this._writableState&&(this._writableState.destroyed=!1,this._writableState.ended=!1,this._writableState.ending=!1,this._writableState.finished=!1,this._writableState.errorEmitted=!1)}}},function(e,t,n){"use strict";var r=n(61).Buffer,o=r.isEncoding||function(e){switch((e=""+e)&&e.toLowerCase()){case"hex":case"utf8":case"utf-8":case"ascii":case"binary":case"base64":case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":case"raw":return!0;default:return!1}};function i(e){var t;switch(this.encoding=function(e){var t=function(e){if(!e)return"utf8";for(var t;;)switch(e){case"utf8":case"utf-8":return"utf8";case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return"utf16le";case"latin1":case"binary":return"latin1";case"base64":case"ascii":case"hex":return e;default:if(t)return;e=(""+e).toLowerCase(),t=!0}}(e);if("string"!=typeof t&&(r.isEncoding===o||!o(e)))throw new Error("Unknown encoding: "+e);return t||e}(e),this.encoding){case"utf16le":this.text=c,this.end=u,t=4;break;case"utf8":this.fillLast=s,t=4;break;case"base64":this.text=l,this.end=f,t=3;break;default:return this.write=h,void(this.end=p)}this.lastNeed=0,this.lastTotal=0,this.lastChar=r.allocUnsafe(t)}function a(e){return e<=127?0:e>>5==6?2:e>>4==14?3:e>>3==30?4:e>>6==2?-1:-2}function s(e){var t=this.lastTotal-this.lastNeed,n=function(e,t,n){if(128!=(192&t[0]))return e.lastNeed=0,"�";if(e.lastNeed>1&&t.length>1){if(128!=(192&t[1]))return e.lastNeed=1,"�";if(e.lastNeed>2&&t.length>2&&128!=(192&t[2]))return e.lastNeed=2,"�"}}(this,e);return void 0!==n?n:this.lastNeed<=e.length?(e.copy(this.lastChar,t,0,this.lastNeed),this.lastChar.toString(this.encoding,0,this.lastTotal)):(e.copy(this.lastChar,t,0,e.length),void(this.lastNeed-=e.length))}function c(e,t){if((e.length-t)%2==0){var n=e.toString("utf16le",t);if(n){var r=n.charCodeAt(n.length-1);if(r>=55296&&r<=56319)return this.lastNeed=2,this.lastTotal=4,this.lastChar[0]=e[e.length-2],this.lastChar[1]=e[e.length-1],n.slice(0,-1)}return n}return this.lastNeed=1,this.lastTotal=2,this.lastChar[0]=e[e.length-1],e.toString("utf16le",t,e.length-1)}function u(e){var t=e&&e.length?this.write(e):"";if(this.lastNeed){var n=this.lastTotal-this.lastNeed;return t+this.lastChar.toString("utf16le",0,n)}return t}function l(e,t){var n=(e.length-t)%3;return 0===n?e.toString("base64",t):(this.lastNeed=3-n,this.lastTotal=3,1===n?this.lastChar[0]=e[e.length-1]:(this.lastChar[0]=e[e.length-2],this.lastChar[1]=e[e.length-1]),e.toString("base64",t,e.length-n))}function f(e){var t=e&&e.length?this.write(e):"";return this.lastNeed?t+this.lastChar.toString("base64",0,3-this.lastNeed):t}function h(e){return e.toString(this.encoding)}function p(e){return e&&e.length?this.write(e):""}t.StringDecoder=i,i.prototype.write=function(e){if(0===e.length)return"";var t,n;if(this.lastNeed){if(void 0===(t=this.fillLast(e)))return"";n=this.lastNeed,this.lastNeed=0}else n=0;return n=0)return o>0&&(e.lastNeed=o-1),o;if(--r=0)return o>0&&(e.lastNeed=o-2),o;if(--r=0)return o>0&&(2===o?o=0:e.lastNeed=o-3),o;return 0}(this,e,t);if(!this.lastNeed)return e.toString("utf8",t);this.lastTotal=n;var r=e.length-(n-this.lastNeed);return e.copy(this.lastChar,0,r),e.toString("utf8",t,r)},i.prototype.fillLast=function(e){if(this.lastNeed<=e.length)return e.copy(this.lastChar,this.lastTotal-this.lastNeed,0,this.lastNeed),this.lastChar.toString(this.encoding,0,this.lastTotal);e.copy(this.lastChar,this.lastTotal-this.lastNeed,0,e.length),this.lastNeed-=e.length}},function(e,t,n){"use strict";(function(t,r,o){var i=n(23);function a(e){var t=this;this.next=null,this.entry=null,this.finish=function(){!function(e,t,n){var r=e.entry;e.entry=null;for(;r;){var o=r.callback;t.pendingcb--,o(n),r=r.next}t.corkedRequestsFree?t.corkedRequestsFree.next=e:t.corkedRequestsFree=e}(t,e)}}e.exports=b;var s,c=!t.browser&&["v0.10","v0.9."].indexOf(t.version.slice(0,5))>-1?r:i.nextTick;b.WritableState=v;var u=n(21);u.inherits=n(15);var l={deprecate:n(64)},f=n(38),h=n(14).Buffer,p=o.Uint8Array||function(){};var d,g=n(39);function y(){}function v(e,t){s=s||n(10),e=e||{};var r=t instanceof s;this.objectMode=!!e.objectMode,r&&(this.objectMode=this.objectMode||!!e.writableObjectMode);var o=e.highWaterMark,u=e.writableHighWaterMark,l=this.objectMode?16:16384;this.highWaterMark=o||0===o?o:r&&(u||0===u)?u:l,this.highWaterMark=Math.floor(this.highWaterMark),this.finalCalled=!1,this.needDrain=!1,this.ending=!1,this.ended=!1,this.finished=!1,this.destroyed=!1;var f=!1===e.decodeStrings;this.decodeStrings=!f,this.defaultEncoding=e.defaultEncoding||"utf8",this.length=0,this.writing=!1,this.corked=0,this.sync=!0,this.bufferProcessing=!1,this.onwrite=function(e){!function(e,t){var n=e._writableState,r=n.sync,o=n.writecb;if(function(e){e.writing=!1,e.writecb=null,e.length-=e.writelen,e.writelen=0}(n),t)!function(e,t,n,r,o){--t.pendingcb,n?(i.nextTick(o,r),i.nextTick(_,e,t),e._writableState.errorEmitted=!0,e.emit("error",r)):(o(r),e._writableState.errorEmitted=!0,e.emit("error",r),_(e,t))}(e,n,r,t,o);else{var a=S(n);a||n.corked||n.bufferProcessing||!n.bufferedRequest||E(e,n),r?c(w,e,n,a,o):w(e,n,a,o)}}(t,e)},this.writecb=null,this.writelen=0,this.bufferedRequest=null,this.lastBufferedRequest=null,this.pendingcb=0,this.prefinished=!1,this.errorEmitted=!1,this.bufferedRequestCount=0,this.corkedRequestsFree=new a(this)}function b(e){if(s=s||n(10),!(d.call(b,this)||this instanceof s))return new b(e);this._writableState=new v(e,this),this.writable=!0,e&&("function"==typeof e.write&&(this._write=e.write),"function"==typeof e.writev&&(this._writev=e.writev),"function"==typeof e.destroy&&(this._destroy=e.destroy),"function"==typeof e.final&&(this._final=e.final)),f.call(this)}function m(e,t,n,r,o,i,a){t.writelen=r,t.writecb=a,t.writing=!0,t.sync=!0,n?e._writev(o,t.onwrite):e._write(o,i,t.onwrite),t.sync=!1}function w(e,t,n,r){n||function(e,t){0===t.length&&t.needDrain&&(t.needDrain=!1,e.emit("drain"))}(e,t),t.pendingcb--,r(),_(e,t)}function E(e,t){t.bufferProcessing=!0;var n=t.bufferedRequest;if(e._writev&&n&&n.next){var r=t.bufferedRequestCount,o=new Array(r),i=t.corkedRequestsFree;i.entry=n;for(var s=0,c=!0;n;)o[s]=n,n.isBuf||(c=!1),n=n.next,s+=1;o.allBuffers=c,m(e,t,!0,t.length,o,"",i.finish),t.pendingcb++,t.lastBufferedRequest=null,i.next?(t.corkedRequestsFree=i.next,i.next=null):t.corkedRequestsFree=new a(t),t.bufferedRequestCount=0}else{for(;n;){var u=n.chunk,l=n.encoding,f=n.callback;if(m(e,t,!1,t.objectMode?1:u.length,u,l,f),n=n.next,t.bufferedRequestCount--,t.writing)break}null===n&&(t.lastBufferedRequest=null)}t.bufferedRequest=n,t.bufferProcessing=!1}function S(e){return e.ending&&0===e.length&&null===e.bufferedRequest&&!e.finished&&!e.writing}function C(e,t){e._final(function(n){t.pendingcb--,n&&e.emit("error",n),t.prefinished=!0,e.emit("prefinish"),_(e,t)})}function _(e,t){var n=S(t);return n&&(!function(e,t){t.prefinished||t.finalCalled||("function"==typeof e._final?(t.pendingcb++,t.finalCalled=!0,i.nextTick(C,e,t)):(t.prefinished=!0,e.emit("prefinish")))}(e,t),0===t.pendingcb&&(t.finished=!0,e.emit("finish"))),n}u.inherits(b,f),v.prototype.getBuffer=function(){for(var e=this.bufferedRequest,t=[];e;)t.push(e),e=e.next;return t},function(){try{Object.defineProperty(v.prototype,"buffer",{get:l.deprecate(function(){return this.getBuffer()},"_writableState.buffer is deprecated. Use _writableState.getBuffer instead.","DEP0003")})}catch(e){}}(),"function"==typeof Symbol&&Symbol.hasInstance&&"function"==typeof Function.prototype[Symbol.hasInstance]?(d=Function.prototype[Symbol.hasInstance],Object.defineProperty(b,Symbol.hasInstance,{value:function(e){return!!d.call(this,e)||this===b&&(e&&e._writableState instanceof v)}})):d=function(e){return e instanceof this},b.prototype.pipe=function(){this.emit("error",new Error("Cannot pipe, not readable"))},b.prototype.write=function(e,t,n){var r,o=this._writableState,a=!1,s=!o.objectMode&&(r=e,h.isBuffer(r)||r instanceof p);return s&&!h.isBuffer(e)&&(e=function(e){return h.from(e)}(e)),"function"==typeof t&&(n=t,t=null),s?t="buffer":t||(t=o.defaultEncoding),"function"!=typeof n&&(n=y),o.ended?function(e,t){var n=new Error("write after end");e.emit("error",n),i.nextTick(t,n)}(this,n):(s||function(e,t,n,r){var o=!0,a=!1;return null===n?a=new TypeError("May not write null values to stream"):"string"==typeof n||void 0===n||t.objectMode||(a=new TypeError("Invalid non-string/buffer chunk")),a&&(e.emit("error",a),i.nextTick(r,a),o=!1),o}(this,o,e,n))&&(o.pendingcb++,a=function(e,t,n,r,o,i){if(!n){var a=function(e,t,n){e.objectMode||!1===e.decodeStrings||"string"!=typeof t||(t=h.from(t,n));return t}(t,r,o);r!==a&&(n=!0,o="buffer",r=a)}var s=t.objectMode?1:r.length;t.length+=s;var c=t.length-1))throw new TypeError("Unknown encoding: "+e);return this._writableState.defaultEncoding=e,this},Object.defineProperty(b.prototype,"writableHighWaterMark",{enumerable:!1,get:function(){return this._writableState.highWaterMark}}),b.prototype._write=function(e,t,n){n(new Error("_write() is not implemented"))},b.prototype._writev=null,b.prototype.end=function(e,t,n){var r=this._writableState;"function"==typeof e?(n=e,e=null,t=null):"function"==typeof t&&(n=t,t=null),null!=e&&this.write(e,t),r.corked&&(r.corked=1,this.uncork()),r.ending||r.finished||function(e,t,n){t.ending=!0,_(e,t),n&&(t.finished?i.nextTick(n):e.once("finish",n));t.ended=!0,e.writable=!1}(this,r,n)},Object.defineProperty(b.prototype,"destroyed",{get:function(){return void 0!==this._writableState&&this._writableState.destroyed},set:function(e){this._writableState&&(this._writableState.destroyed=e)}}),b.prototype.destroy=g.destroy,b.prototype._undestroy=g.undestroy,b.prototype._destroy=function(e,t){this.end(),t(e)}}).call(this,n(20),n(62).setImmediate,n(9))},function(e,t,n){"use strict";e.exports=a;var r=n(10),o=n(21);function i(e,t){var n=this._transformState;n.transforming=!1;var r=n.writecb;if(!r)return this.emit("error",new Error("write callback called multiple times"));n.writechunk=null,n.writecb=null,null!=t&&this.push(t),r(e);var o=this._readableState;o.reading=!1,(o.needReadable||o.length=200&&c.statusCode<300?r(new a.b(c.statusCode,c.statusMessage||"",u)):o(new i.b(c.statusMessage||"",c.statusCode||0))});t.abortSignal&&(t.abortSignal.onabort=function(){f.abort(),o(new i.a)})})},n.prototype.getCookieString=function(e){return this.cookieJar.getCookieString(e)},n}(a.a)}).call(this,n(6).Buffer)},function(e,t,n){"use strict";(function(e){n.d(t,"a",function(){return i});var r=n(8),o=n(1),i=function(){function t(){}return t.prototype.writeHandshakeRequest=function(e){return r.a.write(JSON.stringify(e))},t.prototype.parseHandshakeResponse=function(t){var n,i;if(Object(o.g)(t)||void 0!==e&&t instanceof e){var a=new Uint8Array(t);if(-1===(c=a.indexOf(r.a.RecordSeparatorCode)))throw new Error("Message is incomplete.");var s=c+1;n=String.fromCharCode.apply(null,a.slice(0,s)),i=a.byteLength>s?a.slice(s).buffer:null}else{var c,u=t;if(-1===(c=u.indexOf(r.a.RecordSeparator)))throw new Error("Message is incomplete.");s=c+1;n=u.substring(0,s),i=u.length>s?u.substring(s):null}var l=r.a.parse(n),f=JSON.parse(l[0]);if(f.type)throw new Error("Expected a handshake response from the server.");return[i,f]},t}()}).call(this,n(6).Buffer)},,,,,function(e,t,n){"use strict";var r=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))(function(o,i){function a(e){try{c(r.next(e))}catch(e){i(e)}}function s(e){try{c(r.throw(e))}catch(e){i(e)}}function c(e){e.done?o(e.value):new n(function(t){t(e.value)}).then(a,s)}c((r=r.apply(e,t||[])).next())})},o=this&&this.__generator||function(e,t){var n,r,o,i,a={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return i={next:s(0),throw:s(1),return:s(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function s(i){return function(s){return function(i){if(n)throw new TypeError("Generator is already executing.");for(;a;)try{if(n=1,r&&(o=2&i[0]?r.return:i[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,i[1])).done)return o;switch(r=0,o&&(i=[2&i[0],o.value]),i[0]){case 0:case 1:o=i;break;case 4:return a.label++,{value:i[1],done:!1};case 5:a.label++,r=i[1],i=[0];continue;case 7:i=a.ops.pop(),a.trys.pop();continue;default:if(!(o=(o=a.trys).length>0&&o[o.length-1])&&(6===i[0]||2===i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]0)&&!(r=i.next()).done;)a.push(r.value)}catch(e){o={error:e}}finally{try{r&&!r.done&&(n=i.return)&&n.call(i)}finally{if(o)throw o.error}}return a},a=this&&this.__spread||function(){for(var e=[],t=0;t0?r-4:r,f=0;f>16&255,s[c++]=t>>8&255,s[c++]=255&t;2===a&&(t=o[e.charCodeAt(f)]<<2|o[e.charCodeAt(f+1)]>>4,s[c++]=255&t);1===a&&(t=o[e.charCodeAt(f)]<<10|o[e.charCodeAt(f+1)]<<4|o[e.charCodeAt(f+2)]>>2,s[c++]=t>>8&255,s[c++]=255&t);return s},t.fromByteArray=function(e){for(var t,n=e.length,o=n%3,i=[],a=0,s=n-o;as?s:a+16383));1===o?(t=e[n-1],i.push(r[t>>2]+r[t<<4&63]+"==")):2===o&&(t=(e[n-2]<<8)+e[n-1],i.push(r[t>>10]+r[t>>4&63]+r[t<<2&63]+"="));return i.join("")};for(var r=[],o=[],i="undefined"!=typeof Uint8Array?Uint8Array:Array,a="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",s=0,c=a.length;s0)throw new Error("Invalid string. Length must be a multiple of 4");var n=e.indexOf("=");return-1===n&&(n=t),[n,n===t?0:4-n%4]}function l(e,t,n){for(var o,i,a=[],s=t;s>18&63]+r[i>>12&63]+r[i>>6&63]+r[63&i]);return a.join("")}o["-".charCodeAt(0)]=62,o["_".charCodeAt(0)]=63},function(e,t){t.read=function(e,t,n,r,o){var i,a,s=8*o-r-1,c=(1<>1,l=-7,f=n?o-1:0,h=n?-1:1,p=e[t+f];for(f+=h,i=p&(1<<-l)-1,p>>=-l,l+=s;l>0;i=256*i+e[t+f],f+=h,l-=8);for(a=i&(1<<-l)-1,i>>=-l,l+=r;l>0;a=256*a+e[t+f],f+=h,l-=8);if(0===i)i=1-u;else{if(i===c)return a?NaN:1/0*(p?-1:1);a+=Math.pow(2,r),i-=u}return(p?-1:1)*a*Math.pow(2,i-r)},t.write=function(e,t,n,r,o,i){var a,s,c,u=8*i-o-1,l=(1<>1,h=23===o?Math.pow(2,-24)-Math.pow(2,-77):0,p=r?0:i-1,d=r?1:-1,g=t<0||0===t&&1/t<0?1:0;for(t=Math.abs(t),isNaN(t)||t===1/0?(s=isNaN(t)?1:0,a=l):(a=Math.floor(Math.log(t)/Math.LN2),t*(c=Math.pow(2,-a))<1&&(a--,c*=2),(t+=a+f>=1?h/c:h*Math.pow(2,1-f))*c>=2&&(a++,c/=2),a+f>=l?(s=0,a=l):a+f>=1?(s=(t*c-1)*Math.pow(2,o),a+=f):(s=t*Math.pow(2,f-1)*Math.pow(2,o),a=0));o>=8;e[n+p]=255&s,p+=d,s/=256,o-=8);for(a=a<0;e[n+p]=255&a,p+=d,a/=256,u-=8);e[n+p-d]|=128*g}},function(e,t){var n={}.toString;e.exports=Array.isArray||function(e){return"[object Array]"==n.call(e)}},function(e,t,n){"use strict";(function(t){ +var r=n(50),o=n(51),i=n(52);function a(){return c.TYPED_ARRAY_SUPPORT?2147483647:1073741823}function s(e,t){if(a()=a())throw new RangeError("Attempt to allocate Buffer larger than maximum size: 0x"+a().toString(16)+" bytes");return 0|e}function d(e,t){if(c.isBuffer(e))return e.length;if("undefined"!=typeof ArrayBuffer&&"function"==typeof ArrayBuffer.isView&&(ArrayBuffer.isView(e)||e instanceof ArrayBuffer))return e.byteLength;"string"!=typeof e&&(e=""+e);var n=e.length;if(0===n)return 0;for(var r=!1;;)switch(t){case"ascii":case"latin1":case"binary":return n;case"utf8":case"utf-8":case void 0:return F(e).length;case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return 2*n;case"hex":return n>>>1;case"base64":return H(e).length;default:if(r)return F(e).length;t=(""+t).toLowerCase(),r=!0}}function g(e,t,n){var r=e[t];e[t]=e[n],e[n]=r}function y(e,t,n,r,o){if(0===e.length)return-1;if("string"==typeof n?(r=n,n=0):n>2147483647?n=2147483647:n<-2147483648&&(n=-2147483648),n=+n,isNaN(n)&&(n=o?0:e.length-1),n<0&&(n=e.length+n),n>=e.length){if(o)return-1;n=e.length-1}else if(n<0){if(!o)return-1;n=0}if("string"==typeof t&&(t=c.from(t,r)),c.isBuffer(t))return 0===t.length?-1:v(e,t,n,r,o);if("number"==typeof t)return t&=255,c.TYPED_ARRAY_SUPPORT&&"function"==typeof Uint8Array.prototype.indexOf?o?Uint8Array.prototype.indexOf.call(e,t,n):Uint8Array.prototype.lastIndexOf.call(e,t,n):v(e,[t],n,r,o);throw new TypeError("val must be string, number or Buffer")}function v(e,t,n,r,o){var i,a=1,s=e.length,c=t.length;if(void 0!==r&&("ucs2"===(r=String(r).toLowerCase())||"ucs-2"===r||"utf16le"===r||"utf-16le"===r)){if(e.length<2||t.length<2)return-1;a=2,s/=2,c/=2,n/=2}function u(e,t){return 1===a?e[t]:e.readUInt16BE(t*a)}if(o){var l=-1;for(i=n;is&&(n=s-c),i=n;i>=0;i--){for(var f=!0,h=0;ho&&(r=o):r=o;var i=t.length;if(i%2!=0)throw new TypeError("Invalid hex string");r>i/2&&(r=i/2);for(var a=0;a>8,o=n%256,i.push(o),i.push(r);return i}(t,e.length-n),e,n,r)}function _(e,t,n){return 0===t&&n===e.length?r.fromByteArray(e):r.fromByteArray(e.slice(t,n))}function T(e,t,n){n=Math.min(e.length,n);for(var r=[],o=t;o239?4:u>223?3:u>191?2:1;if(o+f<=n)switch(f){case 1:u<128&&(l=u);break;case 2:128==(192&(i=e[o+1]))&&(c=(31&u)<<6|63&i)>127&&(l=c);break;case 3:i=e[o+1],a=e[o+2],128==(192&i)&&128==(192&a)&&(c=(15&u)<<12|(63&i)<<6|63&a)>2047&&(c<55296||c>57343)&&(l=c);break;case 4:i=e[o+1],a=e[o+2],s=e[o+3],128==(192&i)&&128==(192&a)&&128==(192&s)&&(c=(15&u)<<18|(63&i)<<12|(63&a)<<6|63&s)>65535&&c<1114112&&(l=c)}null===l?(l=65533,f=1):l>65535&&(l-=65536,r.push(l>>>10&1023|55296),l=56320|1023&l),r.push(l),o+=f}return function(e){var t=e.length;if(t<=I)return String.fromCharCode.apply(String,e);var n="",r=0;for(;rthis.length)return"";if((void 0===n||n>this.length)&&(n=this.length),n<=0)return"";if((n>>>=0)<=(t>>>=0))return"";for(e||(e="utf8");;)switch(e){case"hex":return x(this,t,n);case"utf8":case"utf-8":return T(this,t,n);case"ascii":return k(this,t,n);case"latin1":case"binary":return P(this,t,n);case"base64":return _(this,t,n);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return R(this,t,n);default:if(r)throw new TypeError("Unknown encoding: "+e);e=(e+"").toLowerCase(),r=!0}}.apply(this,arguments)},c.prototype.equals=function(e){if(!c.isBuffer(e))throw new TypeError("Argument must be a Buffer");return this===e||0===c.compare(this,e)},c.prototype.inspect=function(){var e="",n=t.INSPECT_MAX_BYTES;return this.length>0&&(e=this.toString("hex",0,n).match(/.{2}/g).join(" "),this.length>n&&(e+=" ... ")),""},c.prototype.compare=function(e,t,n,r,o){if(!c.isBuffer(e))throw new TypeError("Argument must be a Buffer");if(void 0===t&&(t=0),void 0===n&&(n=e?e.length:0),void 0===r&&(r=0),void 0===o&&(o=this.length),t<0||n>e.length||r<0||o>this.length)throw new RangeError("out of range index");if(r>=o&&t>=n)return 0;if(r>=o)return-1;if(t>=n)return 1;if(this===e)return 0;for(var i=(o>>>=0)-(r>>>=0),a=(n>>>=0)-(t>>>=0),s=Math.min(i,a),u=this.slice(r,o),l=e.slice(t,n),f=0;fo)&&(n=o),e.length>0&&(n<0||t<0)||t>this.length)throw new RangeError("Attempt to write outside buffer bounds");r||(r="utf8");for(var i=!1;;)switch(r){case"hex":return b(this,e,t,n);case"utf8":case"utf-8":return m(this,e,t,n);case"ascii":return w(this,e,t,n);case"latin1":case"binary":return E(this,e,t,n);case"base64":return S(this,e,t,n);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return C(this,e,t,n);default:if(i)throw new TypeError("Unknown encoding: "+r);r=(""+r).toLowerCase(),i=!0}},c.prototype.toJSON=function(){return{type:"Buffer",data:Array.prototype.slice.call(this._arr||this,0)}};var I=4096;function k(e,t,n){var r="";n=Math.min(e.length,n);for(var o=t;or)&&(n=r);for(var o="",i=t;in)throw new RangeError("Trying to access beyond buffer length")}function O(e,t,n,r,o,i){if(!c.isBuffer(e))throw new TypeError('"buffer" argument must be a Buffer instance');if(t>o||te.length)throw new RangeError("Index out of range")}function L(e,t,n,r){t<0&&(t=65535+t+1);for(var o=0,i=Math.min(e.length-n,2);o>>8*(r?o:1-o)}function M(e,t,n,r){t<0&&(t=4294967295+t+1);for(var o=0,i=Math.min(e.length-n,4);o>>8*(r?o:3-o)&255}function A(e,t,n,r,o,i){if(n+r>e.length)throw new RangeError("Index out of range");if(n<0)throw new RangeError("Index out of range")}function j(e,t,n,r,i){return i||A(e,0,n,4),o.write(e,t,n,r,23,4),n+4}function B(e,t,n,r,i){return i||A(e,0,n,8),o.write(e,t,n,r,52,8),n+8}c.prototype.slice=function(e,t){var n,r=this.length;if((e=~~e)<0?(e+=r)<0&&(e=0):e>r&&(e=r),(t=void 0===t?r:~~t)<0?(t+=r)<0&&(t=0):t>r&&(t=r),t0&&(o*=256);)r+=this[e+--t]*o;return r},c.prototype.readUInt8=function(e,t){return t||D(e,1,this.length),this[e]},c.prototype.readUInt16LE=function(e,t){return t||D(e,2,this.length),this[e]|this[e+1]<<8},c.prototype.readUInt16BE=function(e,t){return t||D(e,2,this.length),this[e]<<8|this[e+1]},c.prototype.readUInt32LE=function(e,t){return t||D(e,4,this.length),(this[e]|this[e+1]<<8|this[e+2]<<16)+16777216*this[e+3]},c.prototype.readUInt32BE=function(e,t){return t||D(e,4,this.length),16777216*this[e]+(this[e+1]<<16|this[e+2]<<8|this[e+3])},c.prototype.readIntLE=function(e,t,n){e|=0,t|=0,n||D(e,t,this.length);for(var r=this[e],o=1,i=0;++i=(o*=128)&&(r-=Math.pow(2,8*t)),r},c.prototype.readIntBE=function(e,t,n){e|=0,t|=0,n||D(e,t,this.length);for(var r=t,o=1,i=this[e+--r];r>0&&(o*=256);)i+=this[e+--r]*o;return i>=(o*=128)&&(i-=Math.pow(2,8*t)),i},c.prototype.readInt8=function(e,t){return t||D(e,1,this.length),128&this[e]?-1*(255-this[e]+1):this[e]},c.prototype.readInt16LE=function(e,t){t||D(e,2,this.length);var n=this[e]|this[e+1]<<8;return 32768&n?4294901760|n:n},c.prototype.readInt16BE=function(e,t){t||D(e,2,this.length);var n=this[e+1]|this[e]<<8;return 32768&n?4294901760|n:n},c.prototype.readInt32LE=function(e,t){return t||D(e,4,this.length),this[e]|this[e+1]<<8|this[e+2]<<16|this[e+3]<<24},c.prototype.readInt32BE=function(e,t){return t||D(e,4,this.length),this[e]<<24|this[e+1]<<16|this[e+2]<<8|this[e+3]},c.prototype.readFloatLE=function(e,t){return t||D(e,4,this.length),o.read(this,e,!0,23,4)},c.prototype.readFloatBE=function(e,t){return t||D(e,4,this.length),o.read(this,e,!1,23,4)},c.prototype.readDoubleLE=function(e,t){return t||D(e,8,this.length),o.read(this,e,!0,52,8)},c.prototype.readDoubleBE=function(e,t){return t||D(e,8,this.length),o.read(this,e,!1,52,8)},c.prototype.writeUIntLE=function(e,t,n,r){(e=+e,t|=0,n|=0,r)||O(this,e,t,n,Math.pow(2,8*n)-1,0);var o=1,i=0;for(this[t]=255&e;++i=0&&(i*=256);)this[t+o]=e/i&255;return t+n},c.prototype.writeUInt8=function(e,t,n){return e=+e,t|=0,n||O(this,e,t,1,255,0),c.TYPED_ARRAY_SUPPORT||(e=Math.floor(e)),this[t]=255&e,t+1},c.prototype.writeUInt16LE=function(e,t,n){return e=+e,t|=0,n||O(this,e,t,2,65535,0),c.TYPED_ARRAY_SUPPORT?(this[t]=255&e,this[t+1]=e>>>8):L(this,e,t,!0),t+2},c.prototype.writeUInt16BE=function(e,t,n){return e=+e,t|=0,n||O(this,e,t,2,65535,0),c.TYPED_ARRAY_SUPPORT?(this[t]=e>>>8,this[t+1]=255&e):L(this,e,t,!1),t+2},c.prototype.writeUInt32LE=function(e,t,n){return e=+e,t|=0,n||O(this,e,t,4,4294967295,0),c.TYPED_ARRAY_SUPPORT?(this[t+3]=e>>>24,this[t+2]=e>>>16,this[t+1]=e>>>8,this[t]=255&e):M(this,e,t,!0),t+4},c.prototype.writeUInt32BE=function(e,t,n){return e=+e,t|=0,n||O(this,e,t,4,4294967295,0),c.TYPED_ARRAY_SUPPORT?(this[t]=e>>>24,this[t+1]=e>>>16,this[t+2]=e>>>8,this[t+3]=255&e):M(this,e,t,!1),t+4},c.prototype.writeIntLE=function(e,t,n,r){if(e=+e,t|=0,!r){var o=Math.pow(2,8*n-1);O(this,e,t,n,o-1,-o)}var i=0,a=1,s=0;for(this[t]=255&e;++i>0)-s&255;return t+n},c.prototype.writeIntBE=function(e,t,n,r){if(e=+e,t|=0,!r){var o=Math.pow(2,8*n-1);O(this,e,t,n,o-1,-o)}var i=n-1,a=1,s=0;for(this[t+i]=255&e;--i>=0&&(a*=256);)e<0&&0===s&&0!==this[t+i+1]&&(s=1),this[t+i]=(e/a>>0)-s&255;return t+n},c.prototype.writeInt8=function(e,t,n){return e=+e,t|=0,n||O(this,e,t,1,127,-128),c.TYPED_ARRAY_SUPPORT||(e=Math.floor(e)),e<0&&(e=255+e+1),this[t]=255&e,t+1},c.prototype.writeInt16LE=function(e,t,n){return e=+e,t|=0,n||O(this,e,t,2,32767,-32768),c.TYPED_ARRAY_SUPPORT?(this[t]=255&e,this[t+1]=e>>>8):L(this,e,t,!0),t+2},c.prototype.writeInt16BE=function(e,t,n){return e=+e,t|=0,n||O(this,e,t,2,32767,-32768),c.TYPED_ARRAY_SUPPORT?(this[t]=e>>>8,this[t+1]=255&e):L(this,e,t,!1),t+2},c.prototype.writeInt32LE=function(e,t,n){return e=+e,t|=0,n||O(this,e,t,4,2147483647,-2147483648),c.TYPED_ARRAY_SUPPORT?(this[t]=255&e,this[t+1]=e>>>8,this[t+2]=e>>>16,this[t+3]=e>>>24):M(this,e,t,!0),t+4},c.prototype.writeInt32BE=function(e,t,n){return e=+e,t|=0,n||O(this,e,t,4,2147483647,-2147483648),e<0&&(e=4294967295+e+1),c.TYPED_ARRAY_SUPPORT?(this[t]=e>>>24,this[t+1]=e>>>16,this[t+2]=e>>>8,this[t+3]=255&e):M(this,e,t,!1),t+4},c.prototype.writeFloatLE=function(e,t,n){return j(this,e,t,!0,n)},c.prototype.writeFloatBE=function(e,t,n){return j(this,e,t,!1,n)},c.prototype.writeDoubleLE=function(e,t,n){return B(this,e,t,!0,n)},c.prototype.writeDoubleBE=function(e,t,n){return B(this,e,t,!1,n)},c.prototype.copy=function(e,t,n,r){if(n||(n=0),r||0===r||(r=this.length),t>=e.length&&(t=e.length),t||(t=0),r>0&&r=this.length)throw new RangeError("sourceStart out of bounds");if(r<0)throw new RangeError("sourceEnd out of bounds");r>this.length&&(r=this.length),e.length-t=0;--o)e[o+t]=this[o+n];else if(i<1e3||!c.TYPED_ARRAY_SUPPORT)for(o=0;o>>=0,n=void 0===n?this.length:n>>>0,e||(e=0),"number"==typeof e)for(i=t;i55295&&n<57344){if(!o){if(n>56319){(t-=3)>-1&&i.push(239,191,189);continue}if(a+1===r){(t-=3)>-1&&i.push(239,191,189);continue}o=n;continue}if(n<56320){(t-=3)>-1&&i.push(239,191,189),o=n;continue}n=65536+(o-55296<<10|n-56320)}else o&&(t-=3)>-1&&i.push(239,191,189);if(o=null,n<128){if((t-=1)<0)break;i.push(n)}else if(n<2048){if((t-=2)<0)break;i.push(n>>6|192,63&n|128)}else if(n<65536){if((t-=3)<0)break;i.push(n>>12|224,n>>6&63|128,63&n|128)}else{if(!(n<1114112))throw new Error("Invalid code point");if((t-=4)<0)break;i.push(n>>18|240,n>>12&63|128,n>>6&63|128,63&n|128)}}return i}function H(e){return r.toByteArray(function(e){if((e=function(e){return e.trim?e.trim():e.replace(/^\s+|\s+$/g,"")}(e).replace(U,"")).length<2)return"";for(;e.length%4!=0;)e+="=";return e}(e))}function q(e,t,n,r){for(var o=0;o=t.length||o>=e.length);++o)t[o+n]=e[o];return o}}).call(this,n(9))},function(e,t,n){"use strict";n.d(t,"a",function(){return r});var r=function(){function e(){}return e.prototype.log=function(e,t){},e.instance=new e,e}()},function(e,t,n){"use strict";n.d(t,"a",function(){return r});var r=function(){function e(){}return e.write=function(t){return""+t+e.RecordSeparator},e.parse=function(t){if(t[t.length-1]!==e.RecordSeparator)throw new Error("Message is incomplete.");var n=t.split(e.RecordSeparator);return n.pop(),n},e.RecordSeparatorCode=30,e.RecordSeparator=String.fromCharCode(e.RecordSeparatorCode),e}()},function(e,t){var n;n=function(){return this}();try{n=n||new Function("return this")()}catch(e){"object"==typeof window&&(n=window)}e.exports=n},function(e,t,n){"use strict";var r=n(23),o=Object.keys||function(e){var t=[];for(var n in e)t.push(n);return t};e.exports=f;var i=n(21);i.inherits=n(16);var a=n(36),s=n(41);i.inherits(f,a);for(var c=o(s.prototype),u=0;u=0,"must have a non-negative type"),o(a,"must have a decode function"),this.registerEncoder(function(e){return e instanceof t},function(t){var o=i(),a=r.allocUnsafe(1);return a.writeInt8(e,0),o.append(a),o.append(n(t)),o}),this.registerDecoder(e,a),this},registerEncoder:function(e,n){return o(e,"must have an encode function"),o(n,"must have an encode function"),t.push({check:e,encode:n}),this},registerDecoder:function(e,t){return o(e>=0,"must have a non-negative type"),o(t,"must have a decode function"),n.push({type:e,decode:t}),this},encoder:a.encoder,decoder:a.decoder,buffer:!0,type:"msgpack5",IncompleteBufferError:s.IncompleteBufferError}}},function(e,t,n){"use strict";var r=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))(function(o,i){function a(e){try{c(r.next(e))}catch(e){i(e)}}function s(e){try{c(r.throw(e))}catch(e){i(e)}}function c(e){e.done?o(e.value):new n(function(t){t(e.value)}).then(a,s)}c((r=r.apply(e,t||[])).next())})},o=this&&this.__generator||function(e,t){var n,r,o,i,a={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return i={next:s(0),throw:s(1),return:s(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function s(i){return function(s){return function(i){if(n)throw new TypeError("Generator is already executing.");for(;a;)try{if(n=1,r&&(o=2&i[0]?r.return:i[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,i[1])).done)return o;switch(r=0,o&&(i=[2&i[0],o.value]),i[0]){case 0:case 1:o=i;break;case 4:return a.label++,{value:i[1],done:!1};case 5:a.label++,r=i[1],i=[0];continue;case 7:i=a.ops.pop(),a.trys.pop();continue;default:if(!(o=(o=a.trys).length>0&&o[o.length-1])&&(6===i[0]||2===i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]0&&!t)throw new Error("New logical elements must start empty, or allowExistingContents must be true");return r in e||(e[r]=[]),e}function s(e,t,n){var i=e;if(e instanceof Comment&&(u(i)&&u(i).length>0))throw new Error("Not implemented: inserting non-empty logical container");if(c(i))throw new Error("Not implemented: moving existing logical children");var a=u(t);if(n0;)e(r,0);var i=r;i.parentNode.removeChild(i)},t.getLogicalParent=c,t.getLogicalSiblingEnd=function(e){return e[i]||null},t.getLogicalChild=function(e,t){return u(e)[t]},t.isSvgElement=function(e){return"http://www.w3.org/2000/svg"===l(e).namespaceURI},t.getLogicalChildrenArray=u,t.permuteLogicalChildren=function(e,t){var n=u(e);t.forEach(function(e){e.moveRangeStart=n[e.fromSiblingIndex],e.moveRangeEnd=function e(t){if(t instanceof Element)return t;var n=f(t);if(n)return n.previousSibling;var r=c(t);return r instanceof Element?r.lastChild:e(r)}(e.moveRangeStart)}),t.forEach(function(t){var r=t.moveToBeforeMarker=document.createComment("marker"),o=n[t.toSiblingIndex+1];o?o.parentNode.insertBefore(r,o):h(r,e)}),t.forEach(function(e){for(var t=e.moveToBeforeMarker,n=t.parentNode,r=e.moveRangeStart,o=e.moveRangeEnd,i=r;i;){var a=i.nextSibling;if(n.insertBefore(i,t),i===o)break;i=a}n.removeChild(t)}),t.forEach(function(e){n[e.toSiblingIndex]=e.moveRangeStart})},t.getClosestDomElement=l},function(e,t){var n,r,o=e.exports={};function i(){throw new Error("setTimeout has not been defined")}function a(){throw new Error("clearTimeout has not been defined")}function s(e){if(n===setTimeout)return setTimeout(e,0);if((n===i||!n)&&setTimeout)return n=setTimeout,setTimeout(e,0);try{return n(e,0)}catch(t){try{return n.call(null,e,0)}catch(t){return n.call(this,e,0)}}}!function(){try{n="function"==typeof setTimeout?setTimeout:i}catch(e){n=i}try{r="function"==typeof clearTimeout?clearTimeout:a}catch(e){r=a}}();var c,u=[],l=!1,f=-1;function h(){l&&c&&(l=!1,c.length?u=c.concat(u):f=-1,u.length&&p())}function p(){if(!l){var e=s(h);l=!0;for(var t=u.length;t;){for(c=u,u=[];++f1)for(var n=1;nthis.length)&&(r=this.length),n>=this.length)return e||i.alloc(0);if(r<=0)return e||i.alloc(0);var o,a,s=!!e,c=this._offset(n),u=r-n,l=u,f=s&&t||0,h=c[1];if(0===n&&r==this.length){if(!s)return 1===this._bufs.length?this._bufs[0]:i.concat(this._bufs,this.length);for(a=0;a(o=this._bufs[a].length-h))){this._bufs[a].copy(e,f,h,h+l);break}this._bufs[a].copy(e,f,h),f+=o,l-=o,h&&(h=0)}return e},a.prototype.shallowSlice=function(e,t){e=e||0,t=t||this.length,e<0&&(e+=this.length),t<0&&(t+=this.length);var n=this._offset(e),r=this._offset(t),o=this._bufs.slice(n[0],r[0]+1);return 0==r[1]?o.pop():o[o.length-1]=o[o.length-1].slice(0,r[1]),0!=n[1]&&(o[0]=o[0].slice(n[1])),new a(o)},a.prototype.toString=function(e,t,n){return this.slice(t,n).toString(e)},a.prototype.consume=function(e){for(;this._bufs.length;){if(!(e>=this._bufs[0].length)){this._bufs[0]=this._bufs[0].slice(e),this.length-=e;break}e-=this._bufs[0].length,this.length-=this._bufs[0].length,this._bufs.shift()}return this},a.prototype.duplicate=function(){for(var e=0,t=new a;e0&&o[o.length-1])&&(6===i[0]||2===i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]=i)return e;switch(e){case"%s":return String(r[n++]);case"%d":return Number(r[n++]);case"%j":try{return JSON.stringify(r[n++])}catch(e){return"[Circular]"}default:return e}}),c=r[n];n=3&&(r.depth=arguments[2]),arguments.length>=4&&(r.colors=arguments[3]),d(n)?r.showHidden=n:n&&t._extend(r,n),b(r.showHidden)&&(r.showHidden=!1),b(r.depth)&&(r.depth=2),b(r.colors)&&(r.colors=!1),b(r.customInspect)&&(r.customInspect=!0),r.colors&&(r.stylize=c),l(r,e,r.depth)}function c(e,t){var n=s.styles[t];return n?"["+s.colors[n][0]+"m"+e+"["+s.colors[n][1]+"m":e}function u(e,t){return e}function l(e,n,r){if(e.customInspect&&n&&C(n.inspect)&&n.inspect!==t.inspect&&(!n.constructor||n.constructor.prototype!==n)){var o=n.inspect(r,e);return v(o)||(o=l(e,o,r)),o}var i=function(e,t){if(b(t))return e.stylize("undefined","undefined");if(v(t)){var n="'"+JSON.stringify(t).replace(/^"|"$/g,"").replace(/'/g,"\\'").replace(/\\"/g,'"')+"'";return e.stylize(n,"string")}if(y(t))return e.stylize(""+t,"number");if(d(t))return e.stylize(""+t,"boolean");if(g(t))return e.stylize("null","null")}(e,n);if(i)return i;var a=Object.keys(n),s=function(e){var t={};return e.forEach(function(e,n){t[e]=!0}),t}(a);if(e.showHidden&&(a=Object.getOwnPropertyNames(n)),S(n)&&(a.indexOf("message")>=0||a.indexOf("description")>=0))return f(n);if(0===a.length){if(C(n)){var c=n.name?": "+n.name:"";return e.stylize("[Function"+c+"]","special")}if(m(n))return e.stylize(RegExp.prototype.toString.call(n),"regexp");if(E(n))return e.stylize(Date.prototype.toString.call(n),"date");if(S(n))return f(n)}var u,w="",_=!1,T=["{","}"];(p(n)&&(_=!0,T=["[","]"]),C(n))&&(w=" [Function"+(n.name?": "+n.name:"")+"]");return m(n)&&(w=" "+RegExp.prototype.toString.call(n)),E(n)&&(w=" "+Date.prototype.toUTCString.call(n)),S(n)&&(w=" "+f(n)),0!==a.length||_&&0!=n.length?r<0?m(n)?e.stylize(RegExp.prototype.toString.call(n),"regexp"):e.stylize("[Object]","special"):(e.seen.push(n),u=_?function(e,t,n,r,o){for(var i=[],a=0,s=t.length;a=0&&0,e+t.replace(/\u001b\[\d\d?m/g,"").length+1},0)>60)return n[0]+(""===t?"":t+"\n ")+" "+e.join(",\n ")+" "+n[1];return n[0]+t+" "+e.join(", ")+" "+n[1]}(u,w,T)):T[0]+w+T[1]}function f(e){return"["+Error.prototype.toString.call(e)+"]"}function h(e,t,n,r,o,i){var a,s,c;if((c=Object.getOwnPropertyDescriptor(t,o)||{value:t[o]}).get?s=c.set?e.stylize("[Getter/Setter]","special"):e.stylize("[Getter]","special"):c.set&&(s=e.stylize("[Setter]","special")),k(r,o)||(a="["+o+"]"),s||(e.seen.indexOf(c.value)<0?(s=g(n)?l(e,c.value,null):l(e,c.value,n-1)).indexOf("\n")>-1&&(s=i?s.split("\n").map(function(e){return" "+e}).join("\n").substr(2):"\n"+s.split("\n").map(function(e){return" "+e}).join("\n")):s=e.stylize("[Circular]","special")),b(a)){if(i&&o.match(/^\d+$/))return s;(a=JSON.stringify(""+o)).match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)?(a=a.substr(1,a.length-2),a=e.stylize(a,"name")):(a=a.replace(/'/g,"\\'").replace(/\\"/g,'"').replace(/(^"|"$)/g,"'"),a=e.stylize(a,"string"))}return a+": "+s}function p(e){return Array.isArray(e)}function d(e){return"boolean"==typeof e}function g(e){return null===e}function y(e){return"number"==typeof e}function v(e){return"string"==typeof e}function b(e){return void 0===e}function m(e){return w(e)&&"[object RegExp]"===_(e)}function w(e){return"object"==typeof e&&null!==e}function E(e){return w(e)&&"[object Date]"===_(e)}function S(e){return w(e)&&("[object Error]"===_(e)||e instanceof Error)}function C(e){return"function"==typeof e}function _(e){return Object.prototype.toString.call(e)}function T(e){return e<10?"0"+e.toString(10):e.toString(10)}t.debuglog=function(n){if(b(i)&&(i=e.env.NODE_DEBUG||""),n=n.toUpperCase(),!a[n])if(new RegExp("\\b"+n+"\\b","i").test(i)){var r=e.pid;a[n]=function(){var e=t.format.apply(t,arguments);console.error("%s %d: %s",n,r,e)}}else a[n]=function(){};return a[n]},t.inspect=s,s.colors={bold:[1,22],italic:[3,23],underline:[4,24],inverse:[7,27],white:[37,39],grey:[90,39],black:[30,39],blue:[34,39],cyan:[36,39],green:[32,39],magenta:[35,39],red:[31,39],yellow:[33,39]},s.styles={special:"cyan",number:"yellow",boolean:"yellow",undefined:"grey",null:"bold",string:"green",date:"magenta",regexp:"red"},t.isArray=p,t.isBoolean=d,t.isNull=g,t.isNullOrUndefined=function(e){return null==e},t.isNumber=y,t.isString=v,t.isSymbol=function(e){return"symbol"==typeof e},t.isUndefined=b,t.isRegExp=m,t.isObject=w,t.isDate=E,t.isError=S,t.isFunction=C,t.isPrimitive=function(e){return null===e||"boolean"==typeof e||"number"==typeof e||"string"==typeof e||"symbol"==typeof e||void 0===e},t.isBuffer=n(54);var I=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];function k(e,t){return Object.prototype.hasOwnProperty.call(e,t)}t.log=function(){var e,n;console.log("%s - %s",(e=new Date,n=[T(e.getHours()),T(e.getMinutes()),T(e.getSeconds())].join(":"),[e.getDate(),I[e.getMonth()],n].join(" ")),t.format.apply(t,arguments))},t.inherits=n(55),t._extend=function(e,t){if(!t||!w(t))return e;for(var n=Object.keys(t),r=n.length;r--;)e[n[r]]=t[n[r]];return e};var P="undefined"!=typeof Symbol?Symbol("util.promisify.custom"):void 0;function x(e,t){if(!e){var n=new Error("Promise was rejected with a falsy value");n.reason=e,e=n}return t(e)}t.promisify=function(e){if("function"!=typeof e)throw new TypeError('The "original" argument must be of type Function');if(P&&e[P]){var t;if("function"!=typeof(t=e[P]))throw new TypeError('The "util.promisify.custom" argument must be of type Function');return Object.defineProperty(t,P,{value:t,enumerable:!1,writable:!1,configurable:!0}),t}function t(){for(var t,n,r=new Promise(function(e,r){t=e,n=r}),o=[],i=0;i0?("string"==typeof t||a.objectMode||Object.getPrototypeOf(t)===u.prototype||(t=function(e){return u.from(e)}(t)),r?a.endEmitted?e.emit("error",new Error("stream.unshift() after end event")):E(e,a,t,!0):a.ended?e.emit("error",new Error("stream.push() after EOF")):(a.reading=!1,a.decoder&&!n?(t=a.decoder.write(t),a.objectMode||0!==t.length?E(e,a,t,!1):I(e,a)):E(e,a,t,!1))):r||(a.reading=!1));return function(e){return!e.ended&&(e.needReadable||e.lengtht.highWaterMark&&(t.highWaterMark=function(e){return e>=S?e=S:(e--,e|=e>>>1,e|=e>>>2,e|=e>>>4,e|=e>>>8,e|=e>>>16,e++),e}(e)),e<=t.length?e:t.ended?t.length:(t.needReadable=!0,0))}function _(e){var t=e._readableState;t.needReadable=!1,t.emittedReadable||(p("emitReadable",t.flowing),t.emittedReadable=!0,t.sync?o.nextTick(T,e):T(e))}function T(e){p("emit readable"),e.emit("readable"),R(e)}function I(e,t){t.readingMore||(t.readingMore=!0,o.nextTick(k,e,t))}function k(e,t){for(var n=t.length;!t.reading&&!t.flowing&&!t.ended&&t.length=t.length?(n=t.decoder?t.buffer.join(""):1===t.buffer.length?t.buffer.head.data:t.buffer.concat(t.length),t.buffer.clear()):n=function(e,t,n){var r;ei.length?i.length:e;if(a===i.length?o+=i:o+=i.slice(0,e),0===(e-=a)){a===i.length?(++r,n.next?t.head=n.next:t.head=t.tail=null):(t.head=n,n.data=i.slice(a));break}++r}return t.length-=r,o}(e,t):function(e,t){var n=u.allocUnsafe(e),r=t.head,o=1;r.data.copy(n),e-=r.data.length;for(;r=r.next;){var i=r.data,a=e>i.length?i.length:e;if(i.copy(n,n.length-e,0,a),0===(e-=a)){a===i.length?(++o,r.next?t.head=r.next:t.head=t.tail=null):(t.head=r,r.data=i.slice(a));break}++o}return t.length-=o,n}(e,t);return r}(e,t.buffer,t.decoder),n);var n}function O(e){var t=e._readableState;if(t.length>0)throw new Error('"endReadable()" called on non-empty stream');t.endEmitted||(t.ended=!0,o.nextTick(L,t,e))}function L(e,t){e.endEmitted||0!==e.length||(e.endEmitted=!0,t.readable=!1,t.emit("end"))}function M(e,t){for(var n=0,r=e.length;n=t.highWaterMark||t.ended))return p("read: emitReadable",t.length,t.ended),0===t.length&&t.ended?O(this):_(this),null;if(0===(e=C(e,t))&&t.ended)return 0===t.length&&O(this),null;var r,o=t.needReadable;return p("need readable",o),(0===t.length||t.length-e0?D(e,t):null)?(t.needReadable=!0,e=0):t.length-=e,0===t.length&&(t.ended||(t.needReadable=!0),n!==e&&t.ended&&O(this)),null!==r&&this.emit("data",r),r},m.prototype._read=function(e){this.emit("error",new Error("_read() is not implemented"))},m.prototype.pipe=function(e,t){var n=this,i=this._readableState;switch(i.pipesCount){case 0:i.pipes=e;break;case 1:i.pipes=[i.pipes,e];break;default:i.pipes.push(e)}i.pipesCount+=1,p("pipe count=%d opts=%j",i.pipesCount,t);var c=(!t||!1!==t.end)&&e!==r.stdout&&e!==r.stderr?l:m;function u(t,r){p("onunpipe"),t===n&&r&&!1===r.hasUnpiped&&(r.hasUnpiped=!0,p("cleanup"),e.removeListener("close",v),e.removeListener("finish",b),e.removeListener("drain",f),e.removeListener("error",y),e.removeListener("unpipe",u),n.removeListener("end",l),n.removeListener("end",m),n.removeListener("data",g),h=!0,!i.awaitDrain||e._writableState&&!e._writableState.needDrain||f())}function l(){p("onend"),e.end()}i.endEmitted?o.nextTick(c):n.once("end",c),e.on("unpipe",u);var f=function(e){return function(){var t=e._readableState;p("pipeOnDrain",t.awaitDrain),t.awaitDrain&&t.awaitDrain--,0===t.awaitDrain&&s(e,"data")&&(t.flowing=!0,R(e))}}(n);e.on("drain",f);var h=!1;var d=!1;function g(t){p("ondata"),d=!1,!1!==e.write(t)||d||((1===i.pipesCount&&i.pipes===e||i.pipesCount>1&&-1!==M(i.pipes,e))&&!h&&(p("false write response, pause",n._readableState.awaitDrain),n._readableState.awaitDrain++,d=!0),n.pause())}function y(t){p("onerror",t),m(),e.removeListener("error",y),0===s(e,"error")&&e.emit("error",t)}function v(){e.removeListener("finish",b),m()}function b(){p("onfinish"),e.removeListener("close",v),m()}function m(){p("unpipe"),n.unpipe(e)}return n.on("data",g),function(e,t,n){if("function"==typeof e.prependListener)return e.prependListener(t,n);e._events&&e._events[t]?a(e._events[t])?e._events[t].unshift(n):e._events[t]=[n,e._events[t]]:e.on(t,n)}(e,"error",y),e.once("close",v),e.once("finish",b),e.emit("pipe",n),i.flowing||(p("pipe resume"),n.resume()),e},m.prototype.unpipe=function(e){var t=this._readableState,n={hasUnpiped:!1};if(0===t.pipesCount)return this;if(1===t.pipesCount)return e&&e!==t.pipes?this:(e||(e=t.pipes),t.pipes=null,t.pipesCount=0,t.flowing=!1,e&&e.emit("unpipe",this,n),this);if(!e){var r=t.pipes,o=t.pipesCount;t.pipes=null,t.pipesCount=0,t.flowing=!1;for(var i=0;i0&&a.length>o&&!a.warned){a.warned=!0;var c=new Error("Possible EventEmitter memory leak detected. "+a.length+" "+String(t)+" listeners added. Use emitter.setMaxListeners() to increase limit");c.name="MaxListenersExceededWarning",c.emitter=e,c.type=t,c.count=a.length,s=c,console&&console.warn&&console.warn(s)}return e}function f(e,t,n){var r={fired:!1,wrapFn:void 0,target:e,type:t,listener:n},o=function(){for(var e=[],t=0;t0&&(a=t[0]),a instanceof Error)throw a;var s=new Error("Unhandled error."+(a?" ("+a.message+")":""));throw s.context=a,s}var c=o[e];if(void 0===c)return!1;if("function"==typeof c)i(c,this,t);else{var u=c.length,l=d(c,u);for(n=0;n=0;i--)if(n[i]===t||n[i].listener===t){a=n[i].listener,o=i;break}if(o<0)return this;0===o?n.shift():function(e,t){for(;t+1=0;r--)this.removeListener(e,t[r]);return this},s.prototype.listeners=function(e){return h(this,e,!0)},s.prototype.rawListeners=function(e){return h(this,e,!1)},s.listenerCount=function(e,t){return"function"==typeof e.listenerCount?e.listenerCount(t):p.call(e,t)},s.prototype.listenerCount=p,s.prototype.eventNames=function(){return this._eventsCount>0?r(this._events):[]}},function(e,t,n){e.exports=n(37).EventEmitter},function(e,t,n){"use strict";var r=n(23);function o(e,t){e.emit("error",t)}e.exports={destroy:function(e,t){var n=this,i=this._readableState&&this._readableState.destroyed,a=this._writableState&&this._writableState.destroyed;return i||a?(t?t(e):!e||this._writableState&&this._writableState.errorEmitted||r.nextTick(o,this,e),this):(this._readableState&&(this._readableState.destroyed=!0),this._writableState&&(this._writableState.destroyed=!0),this._destroy(e||null,function(e){!t&&e?(r.nextTick(o,n,e),n._writableState&&(n._writableState.errorEmitted=!0)):t&&t(e)}),this)},undestroy:function(){this._readableState&&(this._readableState.destroyed=!1,this._readableState.reading=!1,this._readableState.ended=!1,this._readableState.endEmitted=!1),this._writableState&&(this._writableState.destroyed=!1,this._writableState.ended=!1,this._writableState.ending=!1,this._writableState.finished=!1,this._writableState.errorEmitted=!1)}}},function(e,t,n){"use strict";var r=n(61).Buffer,o=r.isEncoding||function(e){switch((e=""+e)&&e.toLowerCase()){case"hex":case"utf8":case"utf-8":case"ascii":case"binary":case"base64":case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":case"raw":return!0;default:return!1}};function i(e){var t;switch(this.encoding=function(e){var t=function(e){if(!e)return"utf8";for(var t;;)switch(e){case"utf8":case"utf-8":return"utf8";case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return"utf16le";case"latin1":case"binary":return"latin1";case"base64":case"ascii":case"hex":return e;default:if(t)return;e=(""+e).toLowerCase(),t=!0}}(e);if("string"!=typeof t&&(r.isEncoding===o||!o(e)))throw new Error("Unknown encoding: "+e);return t||e}(e),this.encoding){case"utf16le":this.text=c,this.end=u,t=4;break;case"utf8":this.fillLast=s,t=4;break;case"base64":this.text=l,this.end=f,t=3;break;default:return this.write=h,void(this.end=p)}this.lastNeed=0,this.lastTotal=0,this.lastChar=r.allocUnsafe(t)}function a(e){return e<=127?0:e>>5==6?2:e>>4==14?3:e>>3==30?4:e>>6==2?-1:-2}function s(e){var t=this.lastTotal-this.lastNeed,n=function(e,t,n){if(128!=(192&t[0]))return e.lastNeed=0,"�";if(e.lastNeed>1&&t.length>1){if(128!=(192&t[1]))return e.lastNeed=1,"�";if(e.lastNeed>2&&t.length>2&&128!=(192&t[2]))return e.lastNeed=2,"�"}}(this,e);return void 0!==n?n:this.lastNeed<=e.length?(e.copy(this.lastChar,t,0,this.lastNeed),this.lastChar.toString(this.encoding,0,this.lastTotal)):(e.copy(this.lastChar,t,0,e.length),void(this.lastNeed-=e.length))}function c(e,t){if((e.length-t)%2==0){var n=e.toString("utf16le",t);if(n){var r=n.charCodeAt(n.length-1);if(r>=55296&&r<=56319)return this.lastNeed=2,this.lastTotal=4,this.lastChar[0]=e[e.length-2],this.lastChar[1]=e[e.length-1],n.slice(0,-1)}return n}return this.lastNeed=1,this.lastTotal=2,this.lastChar[0]=e[e.length-1],e.toString("utf16le",t,e.length-1)}function u(e){var t=e&&e.length?this.write(e):"";if(this.lastNeed){var n=this.lastTotal-this.lastNeed;return t+this.lastChar.toString("utf16le",0,n)}return t}function l(e,t){var n=(e.length-t)%3;return 0===n?e.toString("base64",t):(this.lastNeed=3-n,this.lastTotal=3,1===n?this.lastChar[0]=e[e.length-1]:(this.lastChar[0]=e[e.length-2],this.lastChar[1]=e[e.length-1]),e.toString("base64",t,e.length-n))}function f(e){var t=e&&e.length?this.write(e):"";return this.lastNeed?t+this.lastChar.toString("base64",0,3-this.lastNeed):t}function h(e){return e.toString(this.encoding)}function p(e){return e&&e.length?this.write(e):""}t.StringDecoder=i,i.prototype.write=function(e){if(0===e.length)return"";var t,n;if(this.lastNeed){if(void 0===(t=this.fillLast(e)))return"";n=this.lastNeed,this.lastNeed=0}else n=0;return n=0)return o>0&&(e.lastNeed=o-1),o;if(--r=0)return o>0&&(e.lastNeed=o-2),o;if(--r=0)return o>0&&(2===o?o=0:e.lastNeed=o-3),o;return 0}(this,e,t);if(!this.lastNeed)return e.toString("utf8",t);this.lastTotal=n;var r=e.length-(n-this.lastNeed);return e.copy(this.lastChar,0,r),e.toString("utf8",t,r)},i.prototype.fillLast=function(e){if(this.lastNeed<=e.length)return e.copy(this.lastChar,this.lastTotal-this.lastNeed,0,this.lastNeed),this.lastChar.toString(this.encoding,0,this.lastTotal);e.copy(this.lastChar,this.lastTotal-this.lastNeed,0,e.length),this.lastNeed-=e.length}},function(e,t,n){"use strict";(function(t,r,o){var i=n(23);function a(e){var t=this;this.next=null,this.entry=null,this.finish=function(){!function(e,t,n){var r=e.entry;e.entry=null;for(;r;){var o=r.callback;t.pendingcb--,o(n),r=r.next}t.corkedRequestsFree?t.corkedRequestsFree.next=e:t.corkedRequestsFree=e}(t,e)}}e.exports=b;var s,c=!t.browser&&["v0.10","v0.9."].indexOf(t.version.slice(0,5))>-1?r:i.nextTick;b.WritableState=v;var u=n(21);u.inherits=n(16);var l={deprecate:n(64)},f=n(38),h=n(15).Buffer,p=o.Uint8Array||function(){};var d,g=n(39);function y(){}function v(e,t){s=s||n(10),e=e||{};var r=t instanceof s;this.objectMode=!!e.objectMode,r&&(this.objectMode=this.objectMode||!!e.writableObjectMode);var o=e.highWaterMark,u=e.writableHighWaterMark,l=this.objectMode?16:16384;this.highWaterMark=o||0===o?o:r&&(u||0===u)?u:l,this.highWaterMark=Math.floor(this.highWaterMark),this.finalCalled=!1,this.needDrain=!1,this.ending=!1,this.ended=!1,this.finished=!1,this.destroyed=!1;var f=!1===e.decodeStrings;this.decodeStrings=!f,this.defaultEncoding=e.defaultEncoding||"utf8",this.length=0,this.writing=!1,this.corked=0,this.sync=!0,this.bufferProcessing=!1,this.onwrite=function(e){!function(e,t){var n=e._writableState,r=n.sync,o=n.writecb;if(function(e){e.writing=!1,e.writecb=null,e.length-=e.writelen,e.writelen=0}(n),t)!function(e,t,n,r,o){--t.pendingcb,n?(i.nextTick(o,r),i.nextTick(_,e,t),e._writableState.errorEmitted=!0,e.emit("error",r)):(o(r),e._writableState.errorEmitted=!0,e.emit("error",r),_(e,t))}(e,n,r,t,o);else{var a=S(n);a||n.corked||n.bufferProcessing||!n.bufferedRequest||E(e,n),r?c(w,e,n,a,o):w(e,n,a,o)}}(t,e)},this.writecb=null,this.writelen=0,this.bufferedRequest=null,this.lastBufferedRequest=null,this.pendingcb=0,this.prefinished=!1,this.errorEmitted=!1,this.bufferedRequestCount=0,this.corkedRequestsFree=new a(this)}function b(e){if(s=s||n(10),!(d.call(b,this)||this instanceof s))return new b(e);this._writableState=new v(e,this),this.writable=!0,e&&("function"==typeof e.write&&(this._write=e.write),"function"==typeof e.writev&&(this._writev=e.writev),"function"==typeof e.destroy&&(this._destroy=e.destroy),"function"==typeof e.final&&(this._final=e.final)),f.call(this)}function m(e,t,n,r,o,i,a){t.writelen=r,t.writecb=a,t.writing=!0,t.sync=!0,n?e._writev(o,t.onwrite):e._write(o,i,t.onwrite),t.sync=!1}function w(e,t,n,r){n||function(e,t){0===t.length&&t.needDrain&&(t.needDrain=!1,e.emit("drain"))}(e,t),t.pendingcb--,r(),_(e,t)}function E(e,t){t.bufferProcessing=!0;var n=t.bufferedRequest;if(e._writev&&n&&n.next){var r=t.bufferedRequestCount,o=new Array(r),i=t.corkedRequestsFree;i.entry=n;for(var s=0,c=!0;n;)o[s]=n,n.isBuf||(c=!1),n=n.next,s+=1;o.allBuffers=c,m(e,t,!0,t.length,o,"",i.finish),t.pendingcb++,t.lastBufferedRequest=null,i.next?(t.corkedRequestsFree=i.next,i.next=null):t.corkedRequestsFree=new a(t),t.bufferedRequestCount=0}else{for(;n;){var u=n.chunk,l=n.encoding,f=n.callback;if(m(e,t,!1,t.objectMode?1:u.length,u,l,f),n=n.next,t.bufferedRequestCount--,t.writing)break}null===n&&(t.lastBufferedRequest=null)}t.bufferedRequest=n,t.bufferProcessing=!1}function S(e){return e.ending&&0===e.length&&null===e.bufferedRequest&&!e.finished&&!e.writing}function C(e,t){e._final(function(n){t.pendingcb--,n&&e.emit("error",n),t.prefinished=!0,e.emit("prefinish"),_(e,t)})}function _(e,t){var n=S(t);return n&&(!function(e,t){t.prefinished||t.finalCalled||("function"==typeof e._final?(t.pendingcb++,t.finalCalled=!0,i.nextTick(C,e,t)):(t.prefinished=!0,e.emit("prefinish")))}(e,t),0===t.pendingcb&&(t.finished=!0,e.emit("finish"))),n}u.inherits(b,f),v.prototype.getBuffer=function(){for(var e=this.bufferedRequest,t=[];e;)t.push(e),e=e.next;return t},function(){try{Object.defineProperty(v.prototype,"buffer",{get:l.deprecate(function(){return this.getBuffer()},"_writableState.buffer is deprecated. Use _writableState.getBuffer instead.","DEP0003")})}catch(e){}}(),"function"==typeof Symbol&&Symbol.hasInstance&&"function"==typeof Function.prototype[Symbol.hasInstance]?(d=Function.prototype[Symbol.hasInstance],Object.defineProperty(b,Symbol.hasInstance,{value:function(e){return!!d.call(this,e)||this===b&&(e&&e._writableState instanceof v)}})):d=function(e){return e instanceof this},b.prototype.pipe=function(){this.emit("error",new Error("Cannot pipe, not readable"))},b.prototype.write=function(e,t,n){var r,o=this._writableState,a=!1,s=!o.objectMode&&(r=e,h.isBuffer(r)||r instanceof p);return s&&!h.isBuffer(e)&&(e=function(e){return h.from(e)}(e)),"function"==typeof t&&(n=t,t=null),s?t="buffer":t||(t=o.defaultEncoding),"function"!=typeof n&&(n=y),o.ended?function(e,t){var n=new Error("write after end");e.emit("error",n),i.nextTick(t,n)}(this,n):(s||function(e,t,n,r){var o=!0,a=!1;return null===n?a=new TypeError("May not write null values to stream"):"string"==typeof n||void 0===n||t.objectMode||(a=new TypeError("Invalid non-string/buffer chunk")),a&&(e.emit("error",a),i.nextTick(r,a),o=!1),o}(this,o,e,n))&&(o.pendingcb++,a=function(e,t,n,r,o,i){if(!n){var a=function(e,t,n){e.objectMode||!1===e.decodeStrings||"string"!=typeof t||(t=h.from(t,n));return t}(t,r,o);r!==a&&(n=!0,o="buffer",r=a)}var s=t.objectMode?1:r.length;t.length+=s;var c=t.length-1))throw new TypeError("Unknown encoding: "+e);return this._writableState.defaultEncoding=e,this},Object.defineProperty(b.prototype,"writableHighWaterMark",{enumerable:!1,get:function(){return this._writableState.highWaterMark}}),b.prototype._write=function(e,t,n){n(new Error("_write() is not implemented"))},b.prototype._writev=null,b.prototype.end=function(e,t,n){var r=this._writableState;"function"==typeof e?(n=e,e=null,t=null):"function"==typeof t&&(n=t,t=null),null!=e&&this.write(e,t),r.corked&&(r.corked=1,this.uncork()),r.ending||r.finished||function(e,t,n){t.ending=!0,_(e,t),n&&(t.finished?i.nextTick(n):e.once("finish",n));t.ended=!0,e.writable=!1}(this,r,n)},Object.defineProperty(b.prototype,"destroyed",{get:function(){return void 0!==this._writableState&&this._writableState.destroyed},set:function(e){this._writableState&&(this._writableState.destroyed=e)}}),b.prototype.destroy=g.destroy,b.prototype._undestroy=g.undestroy,b.prototype._destroy=function(e,t){this.end(),t(e)}}).call(this,n(14),n(62).setImmediate,n(9))},function(e,t,n){"use strict";e.exports=a;var r=n(10),o=n(21);function i(e,t){var n=this._transformState;n.transforming=!1;var r=n.writecb;if(!r)return this.emit("error",new Error("write callback called multiple times"));n.writechunk=null,n.writecb=null,null!=t&&this.push(t),r(e);var o=this._readableState;o.reading=!1,(o.needReadable||o.length=200&&c.statusCode<300?r(new a.b(c.statusCode,c.statusMessage||"",u)):o(new i.b(c.statusMessage||"",c.statusCode||0))});t.abortSignal&&(t.abortSignal.onabort=function(){f.abort(),o(new i.a)})})},n.prototype.getCookieString=function(e){return this.cookieJar.getCookieString(e)},n}(a.a)}).call(this,n(6).Buffer)},function(e,t,n){"use strict";(function(e){n.d(t,"a",function(){return i});var r=n(8),o=n(1),i=function(){function t(){}return t.prototype.writeHandshakeRequest=function(e){return r.a.write(JSON.stringify(e))},t.prototype.parseHandshakeResponse=function(t){var n,i;if(Object(o.i)(t)||void 0!==e&&t instanceof e){var a=new Uint8Array(t);if(-1===(c=a.indexOf(r.a.RecordSeparatorCode)))throw new Error("Message is incomplete.");var s=c+1;n=String.fromCharCode.apply(null,a.slice(0,s)),i=a.byteLength>s?a.slice(s).buffer:null}else{var c,u=t;if(-1===(c=u.indexOf(r.a.RecordSeparator)))throw new Error("Message is incomplete.");s=c+1;n=u.substring(0,s),i=u.length>s?u.substring(s):null}var l=r.a.parse(n),f=JSON.parse(l[0]);if(f.type)throw new Error("Expected a handshake response from the server.");return[i,f]},t}()}).call(this,n(6).Buffer)},,,,,function(e,t,n){"use strict";var r=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))(function(o,i){function a(e){try{c(r.next(e))}catch(e){i(e)}}function s(e){try{c(r.throw(e))}catch(e){i(e)}}function c(e){e.done?o(e.value):new n(function(t){t(e.value)}).then(a,s)}c((r=r.apply(e,t||[])).next())})},o=this&&this.__generator||function(e,t){var n,r,o,i,a={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return i={next:s(0),throw:s(1),return:s(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function s(i){return function(s){return function(i){if(n)throw new TypeError("Generator is already executing.");for(;a;)try{if(n=1,r&&(o=2&i[0]?r.return:i[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,i[1])).done)return o;switch(r=0,o&&(i=[2&i[0],o.value]),i[0]){case 0:case 1:o=i;break;case 4:return a.label++,{value:i[1],done:!1};case 5:a.label++,r=i[1],i=[0];continue;case 7:i=a.ops.pop(),a.trys.pop();continue;default:if(!(o=(o=a.trys).length>0&&o[o.length-1])&&(6===i[0]||2===i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]0)&&!(r=i.next()).done;)a.push(r.value)}catch(e){o={error:e}}finally{try{r&&!r.done&&(n=i.return)&&n.call(i)}finally{if(o)throw o.error}}return a},a=this&&this.__spread||function(){for(var e=[],t=0;t0?r-4:r,f=0;f>16&255,s[c++]=t>>8&255,s[c++]=255&t;2===a&&(t=o[e.charCodeAt(f)]<<2|o[e.charCodeAt(f+1)]>>4,s[c++]=255&t);1===a&&(t=o[e.charCodeAt(f)]<<10|o[e.charCodeAt(f+1)]<<4|o[e.charCodeAt(f+2)]>>2,s[c++]=t>>8&255,s[c++]=255&t);return s},t.fromByteArray=function(e){for(var t,n=e.length,o=n%3,i=[],a=0,s=n-o;as?s:a+16383));1===o?(t=e[n-1],i.push(r[t>>2]+r[t<<4&63]+"==")):2===o&&(t=(e[n-2]<<8)+e[n-1],i.push(r[t>>10]+r[t>>4&63]+r[t<<2&63]+"="));return i.join("")};for(var r=[],o=[],i="undefined"!=typeof Uint8Array?Uint8Array:Array,a="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",s=0,c=a.length;s0)throw new Error("Invalid string. Length must be a multiple of 4");var n=e.indexOf("=");return-1===n&&(n=t),[n,n===t?0:4-n%4]}function l(e,t,n){for(var o,i,a=[],s=t;s>18&63]+r[i>>12&63]+r[i>>6&63]+r[63&i]);return a.join("")}o["-".charCodeAt(0)]=62,o["_".charCodeAt(0)]=63},function(e,t){t.read=function(e,t,n,r,o){var i,a,s=8*o-r-1,c=(1<>1,l=-7,f=n?o-1:0,h=n?-1:1,p=e[t+f];for(f+=h,i=p&(1<<-l)-1,p>>=-l,l+=s;l>0;i=256*i+e[t+f],f+=h,l-=8);for(a=i&(1<<-l)-1,i>>=-l,l+=r;l>0;a=256*a+e[t+f],f+=h,l-=8);if(0===i)i=1-u;else{if(i===c)return a?NaN:1/0*(p?-1:1);a+=Math.pow(2,r),i-=u}return(p?-1:1)*a*Math.pow(2,i-r)},t.write=function(e,t,n,r,o,i){var a,s,c,u=8*i-o-1,l=(1<>1,h=23===o?Math.pow(2,-24)-Math.pow(2,-77):0,p=r?0:i-1,d=r?1:-1,g=t<0||0===t&&1/t<0?1:0;for(t=Math.abs(t),isNaN(t)||t===1/0?(s=isNaN(t)?1:0,a=l):(a=Math.floor(Math.log(t)/Math.LN2),t*(c=Math.pow(2,-a))<1&&(a--,c*=2),(t+=a+f>=1?h/c:h*Math.pow(2,1-f))*c>=2&&(a++,c/=2),a+f>=l?(s=0,a=l):a+f>=1?(s=(t*c-1)*Math.pow(2,o),a+=f):(s=t*Math.pow(2,f-1)*Math.pow(2,o),a=0));o>=8;e[n+p]=255&s,p+=d,s/=256,o-=8);for(a=a<0;e[n+p]=255&a,p+=d,a/=256,u-=8);e[n+p-d]|=128*g}},function(e,t){var n={}.toString;e.exports=Array.isArray||function(e){return"[object Array]"==n.call(e)}},function(e,t,n){"use strict";(function(t){ /*! * The buffer module from node.js, for the browser. * * @author Feross Aboukhadijeh * @license MIT */ -function r(e,t){if(e===t)return 0;for(var n=e.length,r=t.length,o=0,i=Math.min(n,r);o=0;u--)if(l[u]!==f[u])return!1;for(u=l.length-1;u>=0;u--)if(c=l[u],!b(e[c],t[c],n,r))return!1;return!0}(e,t,n,a))}return n?e===t:e==t}function m(e){return"[object Arguments]"==Object.prototype.toString.call(e)}function w(e,t){if(!e||!t)return!1;if("[object RegExp]"==Object.prototype.toString.call(t))return t.test(e);try{if(e instanceof t)return!0}catch(e){}return!Error.isPrototypeOf(t)&&!0===t.call({},e)}function E(e,t,n,r){var o;if("function"!=typeof t)throw new TypeError('"block" argument must be a function');"string"==typeof n&&(r=n,n=null),o=function(e){var t;try{e()}catch(e){t=e}return t}(t),r=(n&&n.name?" ("+n.name+").":".")+(r?" "+r:"."),e&&!o&&y(o,n,"Missing expected exception"+r);var a="string"==typeof r,s=!e&&o&&!n;if((!e&&i.isError(o)&&a&&w(o,n)||s)&&y(o,n,"Got unwanted exception"+r),e&&o&&n&&!w(o,n)||!e&&o)throw o}f.AssertionError=function(e){var t;this.name="AssertionError",this.actual=e.actual,this.expected=e.expected,this.operator=e.operator,e.message?(this.message=e.message,this.generatedMessage=!1):(this.message=d(g((t=this).actual),128)+" "+t.operator+" "+d(g(t.expected),128),this.generatedMessage=!0);var n=e.stackStartFunction||y;if(Error.captureStackTrace)Error.captureStackTrace(this,n);else{var r=new Error;if(r.stack){var o=r.stack,i=p(n),a=o.indexOf("\n"+i);if(a>=0){var s=o.indexOf("\n",a+1);o=o.substring(s+1)}this.stack=o}}},i.inherits(f.AssertionError,Error),f.fail=y,f.ok=v,f.equal=function(e,t,n){e!=t&&y(e,t,n,"==",f.equal)},f.notEqual=function(e,t,n){e==t&&y(e,t,n,"!=",f.notEqual)},f.deepEqual=function(e,t,n){b(e,t,!1)||y(e,t,n,"deepEqual",f.deepEqual)},f.deepStrictEqual=function(e,t,n){b(e,t,!0)||y(e,t,n,"deepStrictEqual",f.deepStrictEqual)},f.notDeepEqual=function(e,t,n){b(e,t,!1)&&y(e,t,n,"notDeepEqual",f.notDeepEqual)},f.notDeepStrictEqual=function e(t,n,r){b(t,n,!0)&&y(t,n,r,"notDeepStrictEqual",e)},f.strictEqual=function(e,t,n){e!==t&&y(e,t,n,"===",f.strictEqual)},f.notStrictEqual=function(e,t,n){e===t&&y(e,t,n,"!==",f.notStrictEqual)},f.throws=function(e,t,n){E(!0,e,t,n)},f.doesNotThrow=function(e,t,n){E(!1,e,t,n)},f.ifError=function(e){if(e)throw e};var S=Object.keys||function(e){var t=[];for(var n in e)a.call(e,n)&&t.push(n);return t}}).call(this,n(9))},function(e,t){e.exports=function(e){return e&&"object"==typeof e&&"function"==typeof e.copy&&"function"==typeof e.fill&&"function"==typeof e.readUInt8}},function(e,t){"function"==typeof Object.create?e.exports=function(e,t){e.super_=t,e.prototype=Object.create(t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}})}:e.exports=function(e,t){e.super_=t;var n=function(){};n.prototype=t.prototype,e.prototype=new n,e.prototype.constructor=e}},function(e,t,n){e.exports=n(10)},function(e,t){var n={}.toString;e.exports=Array.isArray||function(e){return"[object Array]"==n.call(e)}},function(e,t){},function(e,t,n){"use strict";var r=n(14).Buffer,o=n(60);e.exports=function(){function e(){!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,e),this.head=null,this.tail=null,this.length=0}return e.prototype.push=function(e){var t={data:e,next:null};this.length>0?this.tail.next=t:this.head=t,this.tail=t,++this.length},e.prototype.unshift=function(e){var t={data:e,next:this.head};0===this.length&&(this.tail=t),this.head=t,++this.length},e.prototype.shift=function(){if(0!==this.length){var e=this.head.data;return 1===this.length?this.head=this.tail=null:this.head=this.head.next,--this.length,e}},e.prototype.clear=function(){this.head=this.tail=null,this.length=0},e.prototype.join=function(e){if(0===this.length)return"";for(var t=this.head,n=""+t.data;t=t.next;)n+=e+t.data;return n},e.prototype.concat=function(e){if(0===this.length)return r.alloc(0);if(1===this.length)return this.head.data;for(var t,n,o,i=r.allocUnsafe(e>>>0),a=this.head,s=0;a;)t=a.data,n=i,o=s,t.copy(n,o),s+=a.data.length,a=a.next;return i},e}(),o&&o.inspect&&o.inspect.custom&&(e.exports.prototype[o.inspect.custom]=function(){var e=o.inspect({length:this.length});return this.constructor.name+" "+e})},function(e,t){},function(e,t,n){var r=n(6),o=r.Buffer;function i(e,t){for(var n in e)t[n]=e[n]}function a(e,t,n){return o(e,t,n)}o.from&&o.alloc&&o.allocUnsafe&&o.allocUnsafeSlow?e.exports=r:(i(r,t),t.Buffer=a),i(o,a),a.from=function(e,t,n){if("number"==typeof e)throw new TypeError("Argument must not be a number");return o(e,t,n)},a.alloc=function(e,t,n){if("number"!=typeof e)throw new TypeError("Argument must be a number");var r=o(e);return void 0!==t?"string"==typeof n?r.fill(t,n):r.fill(t):r.fill(0),r},a.allocUnsafe=function(e){if("number"!=typeof e)throw new TypeError("Argument must be a number");return o(e)},a.allocUnsafeSlow=function(e){if("number"!=typeof e)throw new TypeError("Argument must be a number");return r.SlowBuffer(e)}},function(e,t,n){(function(e){var r=void 0!==e&&e||"undefined"!=typeof self&&self||window,o=Function.prototype.apply;function i(e,t){this._id=e,this._clearFn=t}t.setTimeout=function(){return new i(o.call(setTimeout,r,arguments),clearTimeout)},t.setInterval=function(){return new i(o.call(setInterval,r,arguments),clearInterval)},t.clearTimeout=t.clearInterval=function(e){e&&e.close()},i.prototype.unref=i.prototype.ref=function(){},i.prototype.close=function(){this._clearFn.call(r,this._id)},t.enroll=function(e,t){clearTimeout(e._idleTimeoutId),e._idleTimeout=t},t.unenroll=function(e){clearTimeout(e._idleTimeoutId),e._idleTimeout=-1},t._unrefActive=t.active=function(e){clearTimeout(e._idleTimeoutId);var t=e._idleTimeout;t>=0&&(e._idleTimeoutId=setTimeout(function(){e._onTimeout&&e._onTimeout()},t))},n(63),t.setImmediate="undefined"!=typeof self&&self.setImmediate||void 0!==e&&e.setImmediate||this&&this.setImmediate,t.clearImmediate="undefined"!=typeof self&&self.clearImmediate||void 0!==e&&e.clearImmediate||this&&this.clearImmediate}).call(this,n(9))},function(e,t,n){(function(e,t){!function(e,n){"use strict";if(!e.setImmediate){var r,o,i,a,s,c=1,u={},l=!1,f=e.document,h=Object.getPrototypeOf&&Object.getPrototypeOf(e);h=h&&h.setTimeout?h:e,"[object process]"==={}.toString.call(e.process)?r=function(e){t.nextTick(function(){d(e)})}:!function(){if(e.postMessage&&!e.importScripts){var t=!0,n=e.onmessage;return e.onmessage=function(){t=!1},e.postMessage("","*"),e.onmessage=n,t}}()?e.MessageChannel?((i=new MessageChannel).port1.onmessage=function(e){d(e.data)},r=function(e){i.port2.postMessage(e)}):f&&"onreadystatechange"in f.createElement("script")?(o=f.documentElement,r=function(e){var t=f.createElement("script");t.onreadystatechange=function(){d(e),t.onreadystatechange=null,o.removeChild(t),t=null},o.appendChild(t)}):r=function(e){setTimeout(d,0,e)}:(a="setImmediate$"+Math.random()+"$",s=function(t){t.source===e&&"string"==typeof t.data&&0===t.data.indexOf(a)&&d(+t.data.slice(a.length))},e.addEventListener?e.addEventListener("message",s,!1):e.attachEvent("onmessage",s),r=function(t){e.postMessage(a+t,"*")}),h.setImmediate=function(e){"function"!=typeof e&&(e=new Function(""+e));for(var t=new Array(arguments.length-1),n=0;n0?this._transform(null,t,n):n()},e.exports.decoder=c,e.exports.encoder=s},function(e,t,n){(t=e.exports=n(36)).Stream=t,t.Readable=t,t.Writable=n(41),t.Duplex=n(10),t.Transform=n(42),t.PassThrough=n(67)},function(e,t,n){"use strict";e.exports=i;var r=n(42),o=n(21);function i(e){if(!(this instanceof i))return new i(e);r.call(this,e)}o.inherits=n(15),o.inherits(i,r),i.prototype._transform=function(e,t,n){n(null,e)}},function(e,t,n){var r=n(22);function o(e){Error.call(this),Error.captureStackTrace&&Error.captureStackTrace(this,this.constructor),this.name=this.constructor.name,this.message=e||"unable to decode"}n(35).inherits(o,Error),e.exports=function(e){return function(e){e instanceof r||(e=r().append(e));var t=i(e);if(t)return e.consume(t.bytesConsumed),t.value;throw new o};function t(e,t,n){return t>=n+e}function n(e,t){return{value:e,bytesConsumed:t}}function i(e,r){r=void 0===r?0:r;var o=e.length-r;if(o<=0)return null;var i,l,f,h=e.readUInt8(r),p=0;if(!function(e,t){var n=function(e){switch(e){case 196:return 2;case 197:return 3;case 198:return 5;case 199:return 3;case 200:return 4;case 201:return 6;case 202:return 5;case 203:return 9;case 204:return 2;case 205:return 3;case 206:return 5;case 207:return 9;case 208:return 2;case 209:return 3;case 210:return 5;case 211:return 9;case 212:return 3;case 213:return 4;case 214:return 6;case 215:return 10;case 216:return 18;case 217:return 2;case 218:return 3;case 219:return 5;case 222:return 3;default:return-1}}(e);return!(-1!==n&&t=0;f--)p+=e.readUInt8(r+f+1)*Math.pow(2,8*(7-f));return n(p,9);case 208:return n(p=e.readInt8(r+1),2);case 209:return n(p=e.readInt16BE(r+1),3);case 210:return n(p=e.readInt32BE(r+1),5);case 211:return n(p=function(e,t){var n=128==(128&e[t]);if(n)for(var r=1,o=t+7;o>=t;o--){var i=(255^e[o])+r;e[o]=255&i,r=i>>8}var a=e.readUInt32BE(t+0),s=e.readUInt32BE(t+4);return(4294967296*a+s)*(n?-1:1)}(e.slice(r+1,r+9),0),9);case 202:return n(p=e.readFloatBE(r+1),5);case 203:return n(p=e.readDoubleBE(r+1),9);case 217:return t(i=e.readUInt8(r+1),o,2)?n(p=e.toString("utf8",r+2,r+2+i),2+i):null;case 218:return t(i=e.readUInt16BE(r+1),o,3)?n(p=e.toString("utf8",r+3,r+3+i),3+i):null;case 219:return t(i=e.readUInt32BE(r+1),o,5)?n(p=e.toString("utf8",r+5,r+5+i),5+i):null;case 196:return t(i=e.readUInt8(r+1),o,2)?n(p=e.slice(r+2,r+2+i),2+i):null;case 197:return t(i=e.readUInt16BE(r+1),o,3)?n(p=e.slice(r+3,r+3+i),3+i):null;case 198:return t(i=e.readUInt32BE(r+1),o,5)?n(p=e.slice(r+5,r+5+i),5+i):null;case 220:return o<3?null:(i=e.readUInt16BE(r+1),a(e,r,i,3));case 221:return o<5?null:(i=e.readUInt32BE(r+1),a(e,r,i,5));case 222:return i=e.readUInt16BE(r+1),s(e,r,i,3);case 223:throw new Error("map too big to decode in JS");case 212:return c(e,r,1);case 213:return c(e,r,2);case 214:return c(e,r,4);case 215:return c(e,r,8);case 216:return c(e,r,16);case 199:return i=e.readUInt8(r+1),l=e.readUInt8(r+2),t(i,o,3)?u(e,r,l,i,3):null;case 200:return i=e.readUInt16BE(r+1),l=e.readUInt8(r+3),t(i,o,4)?u(e,r,l,i,4):null;case 201:return i=e.readUInt32BE(r+1),l=e.readUInt8(r+5),t(i,o,6)?u(e,r,l,i,6):null}if(144==(240&h))return a(e,r,i=15&h,1);if(128==(240&h))return s(e,r,i=15&h,1);if(160==(224&h))return t(i=31&h,o,1)?n(p=e.toString("utf8",r+1,r+i+1),i+1):null;if(h>=224)return n(p=h-256,1);if(h<128)return n(h,1);throw new Error("not implemented yet")}function a(e,t,r,o){var a,s=[],c=0;for(t+=o,a=0;ai)&&((n=r.allocUnsafe(9))[0]=203,n.writeDoubleBE(e,1)),n}e.exports=function(e,t,n,i){function s(c,u){var l,f,h;if(void 0===c)throw new Error("undefined is not encodable in msgpack!");if(null===c)(l=r.allocUnsafe(1))[0]=192;else if(!0===c)(l=r.allocUnsafe(1))[0]=195;else if(!1===c)(l=r.allocUnsafe(1))[0]=194;else if("string"==typeof c)(f=r.byteLength(c))<32?((l=r.allocUnsafe(1+f))[0]=160|f,f>0&&l.write(c,1)):f<=255&&!n?((l=r.allocUnsafe(2+f))[0]=217,l[1]=f,l.write(c,2)):f<=65535?((l=r.allocUnsafe(3+f))[0]=218,l.writeUInt16BE(f,1),l.write(c,3)):((l=r.allocUnsafe(5+f))[0]=219,l.writeUInt32BE(f,1),l.write(c,5));else if(c&&(c.readUInt32LE||c instanceof Uint8Array))c instanceof Uint8Array&&(c=r.from(c)),c.length<=255?((l=r.allocUnsafe(2))[0]=196,l[1]=c.length):c.length<=65535?((l=r.allocUnsafe(3))[0]=197,l.writeUInt16BE(c.length,1)):((l=r.allocUnsafe(5))[0]=198,l.writeUInt32BE(c.length,1)),l=o([l,c]);else if(Array.isArray(c))c.length<16?(l=r.allocUnsafe(1))[0]=144|c.length:c.length<65536?((l=r.allocUnsafe(3))[0]=220,l.writeUInt16BE(c.length,1)):((l=r.allocUnsafe(5))[0]=221,l.writeUInt32BE(c.length,1)),l=c.reduce(function(e,t){return e.append(s(t,!0)),e},o().append(l));else{if(!i&&"function"==typeof c.getDate)return function(e){var t,n=1*e,i=Math.floor(n/1e3),a=1e6*(n-1e3*i);if(a||i>4294967295){(t=new r(10))[0]=215,t[1]=-1;var s=4*a,c=i/Math.pow(2,32),u=s+c&4294967295,l=4294967295&i;t.writeInt32BE(u,2),t.writeInt32BE(l,6)}else(t=new r(6))[0]=214,t[1]=-1,t.writeUInt32BE(Math.floor(n/1e3),2);return o().append(t)}(c);if("object"==typeof c)l=function(t){var n,i,a=-1,s=[];for(n=0;n>8),s.push(255&a)):(s.push(201),s.push(a>>24),s.push(a>>16&255),s.push(a>>8&255),s.push(255&a));return o().append(r.from(s)).append(i)}(c)||function(e){var t,n,i=[],a=0;for(t in e)e.hasOwnProperty(t)&&void 0!==e[t]&&"function"!=typeof e[t]&&(++a,i.push(s(t,!0)),i.push(s(e[t],!0)));a<16?(n=r.allocUnsafe(1))[0]=128|a:((n=r.allocUnsafe(3))[0]=222,n.writeUInt16BE(a,1));return i.unshift(n),i.reduce(function(e,t){return e.append(t)},o())}(c);else if("number"==typeof c){if((h=c)!==Math.floor(h))return a(c,t);if(c>=0)if(c<128)(l=r.allocUnsafe(1))[0]=c;else if(c<256)(l=r.allocUnsafe(2))[0]=204,l[1]=c;else if(c<65536)(l=r.allocUnsafe(3))[0]=205,l.writeUInt16BE(c,1);else if(c<=4294967295)(l=r.allocUnsafe(5))[0]=206,l.writeUInt32BE(c,1);else{if(!(c<=9007199254740991))return a(c,!0);(l=r.allocUnsafe(9))[0]=207,function(e,t){for(var n=7;n>=0;n--)e[n+1]=255&t,t/=256}(l,c)}else if(c>=-32)(l=r.allocUnsafe(1))[0]=256+c;else if(c>=-128)(l=r.allocUnsafe(2))[0]=208,l.writeInt8(c,1);else if(c>=-32768)(l=r.allocUnsafe(3))[0]=209,l.writeInt16BE(c,1);else if(c>-214748365)(l=r.allocUnsafe(5))[0]=210,l.writeInt32BE(c,1);else{if(!(c>=-9007199254740991))return a(c,!0);(l=r.allocUnsafe(9))[0]=211,function(e,t,n){var r=n<0;r&&(n=Math.abs(n));var o=n%4294967296,i=n/4294967296;if(e.writeUInt32BE(Math.floor(i),t+0),e.writeUInt32BE(o,t+4),r)for(var a=1,s=t+7;s>=t;s--){var c=(255^e[s])+a;e[s]=255&c,a=c>>8}}(l,1,c)}}}if(!l)throw new Error("not implemented yet");return u?l:l.slice()}return s}},function(e,t,n){"use strict";var r=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))(function(o,i){function a(e){try{c(r.next(e))}catch(e){i(e)}}function s(e){try{c(r.throw(e))}catch(e){i(e)}}function c(e){e.done?o(e.value):new n(function(t){t(e.value)}).then(a,s)}c((r=r.apply(e,t||[])).next())})},o=this&&this.__generator||function(e,t){var n,r,o,i,a={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return i={next:s(0),throw:s(1),return:s(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function s(i){return function(s){return function(i){if(n)throw new TypeError("Generator is already executing.");for(;a;)try{if(n=1,r&&(o=2&i[0]?r.return:i[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,i[1])).done)return o;switch(r=0,o&&(i=[2&i[0],o.value]),i[0]){case 0:case 1:o=i;break;case 4:return a.label++,{value:i[1],done:!1};case 5:a.label++,r=i[1],i=[0];continue;case 7:i=a.ops.pop(),a.trys.pop();continue;default:if(!(o=(o=a.trys).length>0&&o[o.length-1])&&(6===i[0]||2===i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]this.nextBatchId?this.fatalError?(this.logger.log(s.LogLevel.Debug,"Received a new batch "+e+" but errored out on a previous batch "+(this.nextBatchId-1)),[4,n.send("OnRenderCompleted",this.nextBatchId-1,this.fatalError.toString())]):[3,4]:[3,5];case 3:return o.sent(),[2];case 4:return this.logger.log(s.LogLevel.Debug,"Waiting for batch "+this.nextBatchId+". Batch "+e+" not processed."),[2];case 5:return o.trys.push([5,7,,8]),this.nextBatchId++,this.logger.log(s.LogLevel.Debug,"Applying batch "+e+"."),i.renderBatch(this.browserRendererId,new a.OutOfProcessRenderBatch(t)),[4,this.completeBatch(n,e)];case 6:return o.sent(),[3,8];case 7:throw r=o.sent(),this.fatalError=r.toString(),this.logger.log(s.LogLevel.Error,"There was an error applying batch "+e+"."),n.send("OnRenderCompleted",e,r.toString()),r;case 8:return[2]}})})},e.prototype.getLastBatchid=function(){return this.nextBatchId-1},e.prototype.completeBatch=function(e,t){return r(this,void 0,void 0,function(){return o(this,function(n){switch(n.label){case 0:return n.trys.push([0,2,,3]),[4,e.send("OnRenderCompleted",t,null)];case 1:return n.sent(),[3,3];case 2:return n.sent(),this.logger.log(s.LogLevel.Warning,"Failed to deliver completion notification for render '"+t+"'."),[3,3];case 3:return[2]}})})},e}();t.RenderQueue=c},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n(72),o=Math.pow(2,32),i=Math.pow(2,21)-1,a=function(){function e(e){this.batchData=e;var t=new l(e);this.arrayRangeReader=new f(e),this.arrayBuilderSegmentReader=new h(e),this.diffReader=new s(e),this.editReader=new c(e,t),this.frameReader=new u(e,t)}return e.prototype.updatedComponents=function(){return p(this.batchData,this.batchData.length-20)},e.prototype.referenceFrames=function(){return p(this.batchData,this.batchData.length-16)},e.prototype.disposedComponentIds=function(){return p(this.batchData,this.batchData.length-12)},e.prototype.disposedEventHandlerIds=function(){return p(this.batchData,this.batchData.length-8)},e.prototype.updatedComponentsEntry=function(e,t){var n=e+4*t;return p(this.batchData,n)},e.prototype.referenceFramesEntry=function(e,t){return e+20*t},e.prototype.disposedComponentIdsEntry=function(e,t){var n=e+4*t;return p(this.batchData,n)},e.prototype.disposedEventHandlerIdsEntry=function(e,t){var n=e+8*t;return g(this.batchData,n)},e}();t.OutOfProcessRenderBatch=a;var s=function(){function e(e){this.batchDataUint8=e}return e.prototype.componentId=function(e){return p(this.batchDataUint8,e)},e.prototype.edits=function(e){return e+4},e.prototype.editsEntry=function(e,t){return e+16*t},e}(),c=function(){function e(e,t){this.batchDataUint8=e,this.stringReader=t}return e.prototype.editType=function(e){return p(this.batchDataUint8,e)},e.prototype.siblingIndex=function(e){return p(this.batchDataUint8,e+4)},e.prototype.newTreeIndex=function(e){return p(this.batchDataUint8,e+8)},e.prototype.moveToSiblingIndex=function(e){return p(this.batchDataUint8,e+8)},e.prototype.removedAttributeName=function(e){var t=p(this.batchDataUint8,e+12);return this.stringReader.readString(t)},e}(),u=function(){function e(e,t){this.batchDataUint8=e,this.stringReader=t}return e.prototype.frameType=function(e){return p(this.batchDataUint8,e)},e.prototype.subtreeLength=function(e){return p(this.batchDataUint8,e+4)},e.prototype.elementReferenceCaptureId=function(e){var t=p(this.batchDataUint8,e+4);return this.stringReader.readString(t)},e.prototype.componentId=function(e){return p(this.batchDataUint8,e+8)},e.prototype.elementName=function(e){var t=p(this.batchDataUint8,e+8);return this.stringReader.readString(t)},e.prototype.textContent=function(e){var t=p(this.batchDataUint8,e+4);return this.stringReader.readString(t)},e.prototype.markupContent=function(e){var t=p(this.batchDataUint8,e+4);return this.stringReader.readString(t)},e.prototype.attributeName=function(e){var t=p(this.batchDataUint8,e+4);return this.stringReader.readString(t)},e.prototype.attributeValue=function(e){var t=p(this.batchDataUint8,e+8);return this.stringReader.readString(t)},e.prototype.attributeEventHandlerId=function(e){return g(this.batchDataUint8,e+12)},e}(),l=function(){function e(e){this.batchDataUint8=e,this.stringTableStartIndex=p(e,e.length-4)}return e.prototype.readString=function(e){if(-1===e)return null;var t,n=p(this.batchDataUint8,this.stringTableStartIndex+4*e),o=function(e,t){for(var n=0,r=0,o=0;o<4;o++){var i=e[t+o];if(n|=(127&i)<>>0)}function g(e,t){var n=d(e,t+4);if(n>i)throw new Error("Cannot read uint64 with high order part "+n+", because the result would exceed Number.MAX_SAFE_INTEGER.");return n*o+d(e,t)}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r="function"==typeof TextDecoder?new TextDecoder("utf-8"):null;t.decodeUtf8=r?r.decode.bind(r):function(e){var t=0,n=e.length,r=[],o=[];for(;t65535&&(u-=65536,r.push(u>>>10&1023|55296),u=56320|1023&u),r.push(u)}r.length>1024&&(o.push(String.fromCharCode.apply(null,r)),r.length=0)}return o.push(String.fromCharCode.apply(null,r)),o.join("")}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n(16),o=function(){function e(){}return e.prototype.log=function(e,t){},e.instance=new e,e}();t.NullLogger=o;var i=function(){function e(e){this.minimumLogLevel=e}return e.prototype.log=function(e,t){if(e>=this.minimumLogLevel)switch(e){case r.LogLevel.Critical:case r.LogLevel.Error:console.error("["+(new Date).toISOString()+"] "+r.LogLevel[e]+": "+t);break;case r.LogLevel.Warning:console.warn("["+(new Date).toISOString()+"] "+r.LogLevel[e]+": "+t);break;case r.LogLevel.Information:console.info("["+(new Date).toISOString()+"] "+r.LogLevel[e]+": "+t);break;default:console.log("["+(new Date).toISOString()+"] "+r.LogLevel[e]+": "+t)}},e}();t.ConsoleLogger=i},function(e,t,n){"use strict";var r=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))(function(o,i){function a(e){try{c(r.next(e))}catch(e){i(e)}}function s(e){try{c(r.throw(e))}catch(e){i(e)}}function c(e){e.done?o(e.value):new n(function(t){t(e.value)}).then(a,s)}c((r=r.apply(e,t||[])).next())})},o=this&&this.__generator||function(e,t){var n,r,o,i,a={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return i={next:s(0),throw:s(1),return:s(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function s(i){return function(s){return function(i){if(n)throw new TypeError("Generator is already executing.");for(;a;)try{if(n=1,r&&(o=2&i[0]?r.return:i[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,i[1])).done)return o;switch(r=0,o&&(i=[2&i[0],o.value]),i[0]){case 0:case 1:o=i;break;case 4:return a.label++,{value:i[1],done:!1};case 5:a.label++,r=i[1],i=[0];continue;case 7:i=a.ops.pop(),a.trys.pop();continue;default:if(!(o=(o=a.trys).length>0&&o[o.length-1])&&(6===i[0]||2===i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]0&&o[o.length-1])&&(6===i[0]||2===i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]0&&o[o.length-1])&&(6===i[0]||2===i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]reloading the page if you're unable to reconnect.",this.message.querySelector("a").addEventListener("click",function(){return location.reload()})},e.prototype.rejected=function(){this.button.style.display="none",this.reloadParagraph.style.display="none",this.message.innerHTML="Could not reconnect to the server. Reload the page to restore functionality.",this.message.querySelector("a").addEventListener("click",function(){return location.reload()})},e}();t.DefaultReconnectDisplay=a},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=function(){function e(e){this.dialog=e}return e.prototype.show=function(){this.removeClasses(),this.dialog.classList.add(e.ShowClassName)},e.prototype.hide=function(){this.removeClasses(),this.dialog.classList.add(e.HideClassName)},e.prototype.failed=function(){this.removeClasses(),this.dialog.classList.add(e.FailedClassName)},e.prototype.rejected=function(){this.removeClasses(),this.dialog.classList.add(e.RejectedClassName)},e.prototype.removeClasses=function(){this.dialog.classList.remove(e.ShowClassName,e.HideClassName,e.FailedClassName,e.RejectedClassName)},e.ShowClassName="components-reconnect-show",e.HideClassName="components-reconnect-hide",e.FailedClassName="components-reconnect-failed",e.RejectedClassName="components-reconnect-rejected",e}();t.UserSpecifiedDisplay=r},function(e,t,n){"use strict";n.r(t);var r=n(6),o=n(11),i=n(2),a=function(){function e(){}return e.write=function(e){var t=e.byteLength||e.length,n=[];do{var r=127&t;(t>>=7)>0&&(r|=128),n.push(r)}while(t>0);t=e.byteLength||e.length;var o=new Uint8Array(n.length+t);return o.set(n,0),o.set(e,n.length),o.buffer},e.parse=function(e){for(var t=[],n=new Uint8Array(e),r=[0,7,14,21,28],o=0;o7)throw new Error("Messages bigger than 2GB are not supported.");if(!(n.byteLength>=o+i+a))throw new Error("Incomplete message.");t.push(n.slice?n.slice(o+i,o+i+a):n.subarray(o+i,o+i+a)),o=o+i+a}return t},e}();var s=new Uint8Array([145,i.MessageType.Ping]),c=function(){function e(){this.name="messagepack",this.version=1,this.transferFormat=i.TransferFormat.Binary,this.errorResult=1,this.voidResult=2,this.nonVoidResult=3}return e.prototype.parseMessages=function(e,t){if(!(e instanceof r.Buffer||(n=e,n&&"undefined"!=typeof ArrayBuffer&&(n instanceof ArrayBuffer||n.constructor&&"ArrayBuffer"===n.constructor.name))))throw new Error("Invalid input for MessagePack hub protocol. Expected an ArrayBuffer or Buffer.");var n;null===t&&(t=i.NullLogger.instance);for(var o=[],s=0,c=a.parse(e);s=3?e[2]:void 0,error:e[1],type:i.MessageType.Close}},e.prototype.createPingMessage=function(e){if(e.length<1)throw new Error("Invalid payload for Ping message.");return{type:i.MessageType.Ping}},e.prototype.createInvocationMessage=function(e,t){if(t.length<5)throw new Error("Invalid payload for Invocation message.");var n=t[2];return n?{arguments:t[4],headers:e,invocationId:n,streamIds:[],target:t[3],type:i.MessageType.Invocation}:{arguments:t[4],headers:e,streamIds:[],target:t[3],type:i.MessageType.Invocation}},e.prototype.createStreamItemMessage=function(e,t){if(t.length<4)throw new Error("Invalid payload for StreamItem message.");return{headers:e,invocationId:t[2],item:t[3],type:i.MessageType.StreamItem}},e.prototype.createCompletionMessage=function(e,t){if(t.length<4)throw new Error("Invalid payload for Completion message.");var n,r,o=t[3];if(o!==this.voidResult&&t.length<5)throw new Error("Invalid payload for Completion message.");switch(o){case this.errorResult:n=t[4];break;case this.nonVoidResult:r=t[4]}return{error:n,headers:e,invocationId:t[2],result:r,type:i.MessageType.Completion}},e.prototype.writeInvocation=function(e){var t=o().encode([i.MessageType.Invocation,e.headers||{},e.invocationId||null,e.target,e.arguments,e.streamIds]);return a.write(t.slice())},e.prototype.writeStreamInvocation=function(e){var t=o().encode([i.MessageType.StreamInvocation,e.headers||{},e.invocationId,e.target,e.arguments,e.streamIds]);return a.write(t.slice())},e.prototype.writeStreamItem=function(e){var t=o().encode([i.MessageType.StreamItem,e.headers||{},e.invocationId,e.item]);return a.write(t.slice())},e.prototype.writeCompletion=function(e){var t,n=o(),r=e.error?this.errorResult:e.result?this.nonVoidResult:this.voidResult;switch(r){case this.errorResult:t=n.encode([i.MessageType.Completion,e.headers||{},e.invocationId,r,e.error]);break;case this.voidResult:t=n.encode([i.MessageType.Completion,e.headers||{},e.invocationId,r]);break;case this.nonVoidResult:t=n.encode([i.MessageType.Completion,e.headers||{},e.invocationId,r,e.result])}return a.write(t.slice())},e.prototype.writeCancelInvocation=function(e){var t=o().encode([i.MessageType.CancelInvocation,e.headers||{},e.invocationId]);return a.write(t.slice())},e.prototype.readHeaders=function(e){var t=e[1];if("object"!=typeof t)throw new Error("Invalid headers.");return t},e}();n.d(t,"VERSION",function(){return u}),n.d(t,"MessagePackHubProtocol",function(){return c});var u="3.1.1-dev"}]); \ No newline at end of file +function r(e,t){if(e===t)return 0;for(var n=e.length,r=t.length,o=0,i=Math.min(n,r);o=0;u--)if(l[u]!==f[u])return!1;for(u=l.length-1;u>=0;u--)if(c=l[u],!b(e[c],t[c],n,r))return!1;return!0}(e,t,n,a))}return n?e===t:e==t}function m(e){return"[object Arguments]"==Object.prototype.toString.call(e)}function w(e,t){if(!e||!t)return!1;if("[object RegExp]"==Object.prototype.toString.call(t))return t.test(e);try{if(e instanceof t)return!0}catch(e){}return!Error.isPrototypeOf(t)&&!0===t.call({},e)}function E(e,t,n,r){var o;if("function"!=typeof t)throw new TypeError('"block" argument must be a function');"string"==typeof n&&(r=n,n=null),o=function(e){var t;try{e()}catch(e){t=e}return t}(t),r=(n&&n.name?" ("+n.name+").":".")+(r?" "+r:"."),e&&!o&&y(o,n,"Missing expected exception"+r);var a="string"==typeof r,s=!e&&o&&!n;if((!e&&i.isError(o)&&a&&w(o,n)||s)&&y(o,n,"Got unwanted exception"+r),e&&o&&n&&!w(o,n)||!e&&o)throw o}f.AssertionError=function(e){var t;this.name="AssertionError",this.actual=e.actual,this.expected=e.expected,this.operator=e.operator,e.message?(this.message=e.message,this.generatedMessage=!1):(this.message=d(g((t=this).actual),128)+" "+t.operator+" "+d(g(t.expected),128),this.generatedMessage=!0);var n=e.stackStartFunction||y;if(Error.captureStackTrace)Error.captureStackTrace(this,n);else{var r=new Error;if(r.stack){var o=r.stack,i=p(n),a=o.indexOf("\n"+i);if(a>=0){var s=o.indexOf("\n",a+1);o=o.substring(s+1)}this.stack=o}}},i.inherits(f.AssertionError,Error),f.fail=y,f.ok=v,f.equal=function(e,t,n){e!=t&&y(e,t,n,"==",f.equal)},f.notEqual=function(e,t,n){e==t&&y(e,t,n,"!=",f.notEqual)},f.deepEqual=function(e,t,n){b(e,t,!1)||y(e,t,n,"deepEqual",f.deepEqual)},f.deepStrictEqual=function(e,t,n){b(e,t,!0)||y(e,t,n,"deepStrictEqual",f.deepStrictEqual)},f.notDeepEqual=function(e,t,n){b(e,t,!1)&&y(e,t,n,"notDeepEqual",f.notDeepEqual)},f.notDeepStrictEqual=function e(t,n,r){b(t,n,!0)&&y(t,n,r,"notDeepStrictEqual",e)},f.strictEqual=function(e,t,n){e!==t&&y(e,t,n,"===",f.strictEqual)},f.notStrictEqual=function(e,t,n){e===t&&y(e,t,n,"!==",f.notStrictEqual)},f.throws=function(e,t,n){E(!0,e,t,n)},f.doesNotThrow=function(e,t,n){E(!1,e,t,n)},f.ifError=function(e){if(e)throw e};var S=Object.keys||function(e){var t=[];for(var n in e)a.call(e,n)&&t.push(n);return t}}).call(this,n(9))},function(e,t){e.exports=function(e){return e&&"object"==typeof e&&"function"==typeof e.copy&&"function"==typeof e.fill&&"function"==typeof e.readUInt8}},function(e,t){"function"==typeof Object.create?e.exports=function(e,t){e.super_=t,e.prototype=Object.create(t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}})}:e.exports=function(e,t){e.super_=t;var n=function(){};n.prototype=t.prototype,e.prototype=new n,e.prototype.constructor=e}},function(e,t,n){e.exports=n(10)},function(e,t){var n={}.toString;e.exports=Array.isArray||function(e){return"[object Array]"==n.call(e)}},function(e,t){},function(e,t,n){"use strict";var r=n(15).Buffer,o=n(60);e.exports=function(){function e(){!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,e),this.head=null,this.tail=null,this.length=0}return e.prototype.push=function(e){var t={data:e,next:null};this.length>0?this.tail.next=t:this.head=t,this.tail=t,++this.length},e.prototype.unshift=function(e){var t={data:e,next:this.head};0===this.length&&(this.tail=t),this.head=t,++this.length},e.prototype.shift=function(){if(0!==this.length){var e=this.head.data;return 1===this.length?this.head=this.tail=null:this.head=this.head.next,--this.length,e}},e.prototype.clear=function(){this.head=this.tail=null,this.length=0},e.prototype.join=function(e){if(0===this.length)return"";for(var t=this.head,n=""+t.data;t=t.next;)n+=e+t.data;return n},e.prototype.concat=function(e){if(0===this.length)return r.alloc(0);if(1===this.length)return this.head.data;for(var t,n,o,i=r.allocUnsafe(e>>>0),a=this.head,s=0;a;)t=a.data,n=i,o=s,t.copy(n,o),s+=a.data.length,a=a.next;return i},e}(),o&&o.inspect&&o.inspect.custom&&(e.exports.prototype[o.inspect.custom]=function(){var e=o.inspect({length:this.length});return this.constructor.name+" "+e})},function(e,t){},function(e,t,n){var r=n(6),o=r.Buffer;function i(e,t){for(var n in e)t[n]=e[n]}function a(e,t,n){return o(e,t,n)}o.from&&o.alloc&&o.allocUnsafe&&o.allocUnsafeSlow?e.exports=r:(i(r,t),t.Buffer=a),i(o,a),a.from=function(e,t,n){if("number"==typeof e)throw new TypeError("Argument must not be a number");return o(e,t,n)},a.alloc=function(e,t,n){if("number"!=typeof e)throw new TypeError("Argument must be a number");var r=o(e);return void 0!==t?"string"==typeof n?r.fill(t,n):r.fill(t):r.fill(0),r},a.allocUnsafe=function(e){if("number"!=typeof e)throw new TypeError("Argument must be a number");return o(e)},a.allocUnsafeSlow=function(e){if("number"!=typeof e)throw new TypeError("Argument must be a number");return r.SlowBuffer(e)}},function(e,t,n){(function(e){var r=void 0!==e&&e||"undefined"!=typeof self&&self||window,o=Function.prototype.apply;function i(e,t){this._id=e,this._clearFn=t}t.setTimeout=function(){return new i(o.call(setTimeout,r,arguments),clearTimeout)},t.setInterval=function(){return new i(o.call(setInterval,r,arguments),clearInterval)},t.clearTimeout=t.clearInterval=function(e){e&&e.close()},i.prototype.unref=i.prototype.ref=function(){},i.prototype.close=function(){this._clearFn.call(r,this._id)},t.enroll=function(e,t){clearTimeout(e._idleTimeoutId),e._idleTimeout=t},t.unenroll=function(e){clearTimeout(e._idleTimeoutId),e._idleTimeout=-1},t._unrefActive=t.active=function(e){clearTimeout(e._idleTimeoutId);var t=e._idleTimeout;t>=0&&(e._idleTimeoutId=setTimeout(function(){e._onTimeout&&e._onTimeout()},t))},n(63),t.setImmediate="undefined"!=typeof self&&self.setImmediate||void 0!==e&&e.setImmediate||this&&this.setImmediate,t.clearImmediate="undefined"!=typeof self&&self.clearImmediate||void 0!==e&&e.clearImmediate||this&&this.clearImmediate}).call(this,n(9))},function(e,t,n){(function(e,t){!function(e,n){"use strict";if(!e.setImmediate){var r,o,i,a,s,c=1,u={},l=!1,f=e.document,h=Object.getPrototypeOf&&Object.getPrototypeOf(e);h=h&&h.setTimeout?h:e,"[object process]"==={}.toString.call(e.process)?r=function(e){t.nextTick(function(){d(e)})}:!function(){if(e.postMessage&&!e.importScripts){var t=!0,n=e.onmessage;return e.onmessage=function(){t=!1},e.postMessage("","*"),e.onmessage=n,t}}()?e.MessageChannel?((i=new MessageChannel).port1.onmessage=function(e){d(e.data)},r=function(e){i.port2.postMessage(e)}):f&&"onreadystatechange"in f.createElement("script")?(o=f.documentElement,r=function(e){var t=f.createElement("script");t.onreadystatechange=function(){d(e),t.onreadystatechange=null,o.removeChild(t),t=null},o.appendChild(t)}):r=function(e){setTimeout(d,0,e)}:(a="setImmediate$"+Math.random()+"$",s=function(t){t.source===e&&"string"==typeof t.data&&0===t.data.indexOf(a)&&d(+t.data.slice(a.length))},e.addEventListener?e.addEventListener("message",s,!1):e.attachEvent("onmessage",s),r=function(t){e.postMessage(a+t,"*")}),h.setImmediate=function(e){"function"!=typeof e&&(e=new Function(""+e));for(var t=new Array(arguments.length-1),n=0;n0?this._transform(null,t,n):n()},e.exports.decoder=c,e.exports.encoder=s},function(e,t,n){(t=e.exports=n(36)).Stream=t,t.Readable=t,t.Writable=n(41),t.Duplex=n(10),t.Transform=n(42),t.PassThrough=n(67)},function(e,t,n){"use strict";e.exports=i;var r=n(42),o=n(21);function i(e){if(!(this instanceof i))return new i(e);r.call(this,e)}o.inherits=n(16),o.inherits(i,r),i.prototype._transform=function(e,t,n){n(null,e)}},function(e,t,n){var r=n(22);function o(e){Error.call(this),Error.captureStackTrace&&Error.captureStackTrace(this,this.constructor),this.name=this.constructor.name,this.message=e||"unable to decode"}n(34).inherits(o,Error),e.exports=function(e){return function(e){e instanceof r||(e=r().append(e));var t=i(e);if(t)return e.consume(t.bytesConsumed),t.value;throw new o};function t(e,t,n){return t>=n+e}function n(e,t){return{value:e,bytesConsumed:t}}function i(e,r){r=void 0===r?0:r;var o=e.length-r;if(o<=0)return null;var i,l,f,h=e.readUInt8(r),p=0;if(!function(e,t){var n=function(e){switch(e){case 196:return 2;case 197:return 3;case 198:return 5;case 199:return 3;case 200:return 4;case 201:return 6;case 202:return 5;case 203:return 9;case 204:return 2;case 205:return 3;case 206:return 5;case 207:return 9;case 208:return 2;case 209:return 3;case 210:return 5;case 211:return 9;case 212:return 3;case 213:return 4;case 214:return 6;case 215:return 10;case 216:return 18;case 217:return 2;case 218:return 3;case 219:return 5;case 222:return 3;default:return-1}}(e);return!(-1!==n&&t=0;f--)p+=e.readUInt8(r+f+1)*Math.pow(2,8*(7-f));return n(p,9);case 208:return n(p=e.readInt8(r+1),2);case 209:return n(p=e.readInt16BE(r+1),3);case 210:return n(p=e.readInt32BE(r+1),5);case 211:return n(p=function(e,t){var n=128==(128&e[t]);if(n)for(var r=1,o=t+7;o>=t;o--){var i=(255^e[o])+r;e[o]=255&i,r=i>>8}var a=e.readUInt32BE(t+0),s=e.readUInt32BE(t+4);return(4294967296*a+s)*(n?-1:1)}(e.slice(r+1,r+9),0),9);case 202:return n(p=e.readFloatBE(r+1),5);case 203:return n(p=e.readDoubleBE(r+1),9);case 217:return t(i=e.readUInt8(r+1),o,2)?n(p=e.toString("utf8",r+2,r+2+i),2+i):null;case 218:return t(i=e.readUInt16BE(r+1),o,3)?n(p=e.toString("utf8",r+3,r+3+i),3+i):null;case 219:return t(i=e.readUInt32BE(r+1),o,5)?n(p=e.toString("utf8",r+5,r+5+i),5+i):null;case 196:return t(i=e.readUInt8(r+1),o,2)?n(p=e.slice(r+2,r+2+i),2+i):null;case 197:return t(i=e.readUInt16BE(r+1),o,3)?n(p=e.slice(r+3,r+3+i),3+i):null;case 198:return t(i=e.readUInt32BE(r+1),o,5)?n(p=e.slice(r+5,r+5+i),5+i):null;case 220:return o<3?null:(i=e.readUInt16BE(r+1),a(e,r,i,3));case 221:return o<5?null:(i=e.readUInt32BE(r+1),a(e,r,i,5));case 222:return i=e.readUInt16BE(r+1),s(e,r,i,3);case 223:throw new Error("map too big to decode in JS");case 212:return c(e,r,1);case 213:return c(e,r,2);case 214:return c(e,r,4);case 215:return c(e,r,8);case 216:return c(e,r,16);case 199:return i=e.readUInt8(r+1),l=e.readUInt8(r+2),t(i,o,3)?u(e,r,l,i,3):null;case 200:return i=e.readUInt16BE(r+1),l=e.readUInt8(r+3),t(i,o,4)?u(e,r,l,i,4):null;case 201:return i=e.readUInt32BE(r+1),l=e.readUInt8(r+5),t(i,o,6)?u(e,r,l,i,6):null}if(144==(240&h))return a(e,r,i=15&h,1);if(128==(240&h))return s(e,r,i=15&h,1);if(160==(224&h))return t(i=31&h,o,1)?n(p=e.toString("utf8",r+1,r+i+1),i+1):null;if(h>=224)return n(p=h-256,1);if(h<128)return n(h,1);throw new Error("not implemented yet")}function a(e,t,r,o){var a,s=[],c=0;for(t+=o,a=0;ai)&&((n=r.allocUnsafe(9))[0]=203,n.writeDoubleBE(e,1)),n}e.exports=function(e,t,n,i){function s(c,u){var l,f,h;if(void 0===c)throw new Error("undefined is not encodable in msgpack!");if(null===c)(l=r.allocUnsafe(1))[0]=192;else if(!0===c)(l=r.allocUnsafe(1))[0]=195;else if(!1===c)(l=r.allocUnsafe(1))[0]=194;else if("string"==typeof c)(f=r.byteLength(c))<32?((l=r.allocUnsafe(1+f))[0]=160|f,f>0&&l.write(c,1)):f<=255&&!n?((l=r.allocUnsafe(2+f))[0]=217,l[1]=f,l.write(c,2)):f<=65535?((l=r.allocUnsafe(3+f))[0]=218,l.writeUInt16BE(f,1),l.write(c,3)):((l=r.allocUnsafe(5+f))[0]=219,l.writeUInt32BE(f,1),l.write(c,5));else if(c&&(c.readUInt32LE||c instanceof Uint8Array))c instanceof Uint8Array&&(c=r.from(c)),c.length<=255?((l=r.allocUnsafe(2))[0]=196,l[1]=c.length):c.length<=65535?((l=r.allocUnsafe(3))[0]=197,l.writeUInt16BE(c.length,1)):((l=r.allocUnsafe(5))[0]=198,l.writeUInt32BE(c.length,1)),l=o([l,c]);else if(Array.isArray(c))c.length<16?(l=r.allocUnsafe(1))[0]=144|c.length:c.length<65536?((l=r.allocUnsafe(3))[0]=220,l.writeUInt16BE(c.length,1)):((l=r.allocUnsafe(5))[0]=221,l.writeUInt32BE(c.length,1)),l=c.reduce(function(e,t){return e.append(s(t,!0)),e},o().append(l));else{if(!i&&"function"==typeof c.getDate)return function(e){var t,n=1*e,i=Math.floor(n/1e3),a=1e6*(n-1e3*i);if(a||i>4294967295){(t=new r(10))[0]=215,t[1]=-1;var s=4*a,c=i/Math.pow(2,32),u=s+c&4294967295,l=4294967295&i;t.writeInt32BE(u,2),t.writeInt32BE(l,6)}else(t=new r(6))[0]=214,t[1]=-1,t.writeUInt32BE(Math.floor(n/1e3),2);return o().append(t)}(c);if("object"==typeof c)l=function(t){var n,i,a=-1,s=[];for(n=0;n>8),s.push(255&a)):(s.push(201),s.push(a>>24),s.push(a>>16&255),s.push(a>>8&255),s.push(255&a));return o().append(r.from(s)).append(i)}(c)||function(e){var t,n,i=[],a=0;for(t in e)e.hasOwnProperty(t)&&void 0!==e[t]&&"function"!=typeof e[t]&&(++a,i.push(s(t,!0)),i.push(s(e[t],!0)));a<16?(n=r.allocUnsafe(1))[0]=128|a:((n=r.allocUnsafe(3))[0]=222,n.writeUInt16BE(a,1));return i.unshift(n),i.reduce(function(e,t){return e.append(t)},o())}(c);else if("number"==typeof c){if((h=c)!==Math.floor(h))return a(c,t);if(c>=0)if(c<128)(l=r.allocUnsafe(1))[0]=c;else if(c<256)(l=r.allocUnsafe(2))[0]=204,l[1]=c;else if(c<65536)(l=r.allocUnsafe(3))[0]=205,l.writeUInt16BE(c,1);else if(c<=4294967295)(l=r.allocUnsafe(5))[0]=206,l.writeUInt32BE(c,1);else{if(!(c<=9007199254740991))return a(c,!0);(l=r.allocUnsafe(9))[0]=207,function(e,t){for(var n=7;n>=0;n--)e[n+1]=255&t,t/=256}(l,c)}else if(c>=-32)(l=r.allocUnsafe(1))[0]=256+c;else if(c>=-128)(l=r.allocUnsafe(2))[0]=208,l.writeInt8(c,1);else if(c>=-32768)(l=r.allocUnsafe(3))[0]=209,l.writeInt16BE(c,1);else if(c>-214748365)(l=r.allocUnsafe(5))[0]=210,l.writeInt32BE(c,1);else{if(!(c>=-9007199254740991))return a(c,!0);(l=r.allocUnsafe(9))[0]=211,function(e,t,n){var r=n<0;r&&(n=Math.abs(n));var o=n%4294967296,i=n/4294967296;if(e.writeUInt32BE(Math.floor(i),t+0),e.writeUInt32BE(o,t+4),r)for(var a=1,s=t+7;s>=t;s--){var c=(255^e[s])+a;e[s]=255&c,a=c>>8}}(l,1,c)}}}if(!l)throw new Error("not implemented yet");return u?l:l.slice()}return s}},function(e,t,n){"use strict";var r=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))(function(o,i){function a(e){try{c(r.next(e))}catch(e){i(e)}}function s(e){try{c(r.throw(e))}catch(e){i(e)}}function c(e){e.done?o(e.value):new n(function(t){t(e.value)}).then(a,s)}c((r=r.apply(e,t||[])).next())})},o=this&&this.__generator||function(e,t){var n,r,o,i,a={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return i={next:s(0),throw:s(1),return:s(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function s(i){return function(s){return function(i){if(n)throw new TypeError("Generator is already executing.");for(;a;)try{if(n=1,r&&(o=2&i[0]?r.return:i[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,i[1])).done)return o;switch(r=0,o&&(i=[2&i[0],o.value]),i[0]){case 0:case 1:o=i;break;case 4:return a.label++,{value:i[1],done:!1};case 5:a.label++,r=i[1],i=[0];continue;case 7:i=a.ops.pop(),a.trys.pop();continue;default:if(!(o=(o=a.trys).length>0&&o[o.length-1])&&(6===i[0]||2===i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]this.nextBatchId?this.fatalError?(this.logger.log(s.LogLevel.Debug,"Received a new batch "+e+" but errored out on a previous batch "+(this.nextBatchId-1)),[4,n.send("OnRenderCompleted",this.nextBatchId-1,this.fatalError.toString())]):[3,4]:[3,5];case 3:return o.sent(),[2];case 4:return this.logger.log(s.LogLevel.Debug,"Waiting for batch "+this.nextBatchId+". Batch "+e+" not processed."),[2];case 5:return o.trys.push([5,7,,8]),this.nextBatchId++,this.logger.log(s.LogLevel.Debug,"Applying batch "+e+"."),i.renderBatch(this.browserRendererId,new a.OutOfProcessRenderBatch(t)),[4,this.completeBatch(n,e)];case 6:return o.sent(),[3,8];case 7:throw r=o.sent(),this.fatalError=r.toString(),this.logger.log(s.LogLevel.Error,"There was an error applying batch "+e+"."),n.send("OnRenderCompleted",e,r.toString()),r;case 8:return[2]}})})},e.prototype.getLastBatchid=function(){return this.nextBatchId-1},e.prototype.completeBatch=function(e,t){return r(this,void 0,void 0,function(){return o(this,function(n){switch(n.label){case 0:return n.trys.push([0,2,,3]),[4,e.send("OnRenderCompleted",t,null)];case 1:return n.sent(),[3,3];case 2:return n.sent(),this.logger.log(s.LogLevel.Warning,"Failed to deliver completion notification for render '"+t+"'."),[3,3];case 3:return[2]}})})},e}();t.RenderQueue=c},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n(72),o=Math.pow(2,32),i=Math.pow(2,21)-1,a=function(){function e(e){this.batchData=e;var t=new l(e);this.arrayRangeReader=new f(e),this.arrayBuilderSegmentReader=new h(e),this.diffReader=new s(e),this.editReader=new c(e,t),this.frameReader=new u(e,t)}return e.prototype.updatedComponents=function(){return p(this.batchData,this.batchData.length-20)},e.prototype.referenceFrames=function(){return p(this.batchData,this.batchData.length-16)},e.prototype.disposedComponentIds=function(){return p(this.batchData,this.batchData.length-12)},e.prototype.disposedEventHandlerIds=function(){return p(this.batchData,this.batchData.length-8)},e.prototype.updatedComponentsEntry=function(e,t){var n=e+4*t;return p(this.batchData,n)},e.prototype.referenceFramesEntry=function(e,t){return e+20*t},e.prototype.disposedComponentIdsEntry=function(e,t){var n=e+4*t;return p(this.batchData,n)},e.prototype.disposedEventHandlerIdsEntry=function(e,t){var n=e+8*t;return g(this.batchData,n)},e}();t.OutOfProcessRenderBatch=a;var s=function(){function e(e){this.batchDataUint8=e}return e.prototype.componentId=function(e){return p(this.batchDataUint8,e)},e.prototype.edits=function(e){return e+4},e.prototype.editsEntry=function(e,t){return e+16*t},e}(),c=function(){function e(e,t){this.batchDataUint8=e,this.stringReader=t}return e.prototype.editType=function(e){return p(this.batchDataUint8,e)},e.prototype.siblingIndex=function(e){return p(this.batchDataUint8,e+4)},e.prototype.newTreeIndex=function(e){return p(this.batchDataUint8,e+8)},e.prototype.moveToSiblingIndex=function(e){return p(this.batchDataUint8,e+8)},e.prototype.removedAttributeName=function(e){var t=p(this.batchDataUint8,e+12);return this.stringReader.readString(t)},e}(),u=function(){function e(e,t){this.batchDataUint8=e,this.stringReader=t}return e.prototype.frameType=function(e){return p(this.batchDataUint8,e)},e.prototype.subtreeLength=function(e){return p(this.batchDataUint8,e+4)},e.prototype.elementReferenceCaptureId=function(e){var t=p(this.batchDataUint8,e+4);return this.stringReader.readString(t)},e.prototype.componentId=function(e){return p(this.batchDataUint8,e+8)},e.prototype.elementName=function(e){var t=p(this.batchDataUint8,e+8);return this.stringReader.readString(t)},e.prototype.textContent=function(e){var t=p(this.batchDataUint8,e+4);return this.stringReader.readString(t)},e.prototype.markupContent=function(e){var t=p(this.batchDataUint8,e+4);return this.stringReader.readString(t)},e.prototype.attributeName=function(e){var t=p(this.batchDataUint8,e+4);return this.stringReader.readString(t)},e.prototype.attributeValue=function(e){var t=p(this.batchDataUint8,e+8);return this.stringReader.readString(t)},e.prototype.attributeEventHandlerId=function(e){return g(this.batchDataUint8,e+12)},e}(),l=function(){function e(e){this.batchDataUint8=e,this.stringTableStartIndex=p(e,e.length-4)}return e.prototype.readString=function(e){if(-1===e)return null;var t,n=p(this.batchDataUint8,this.stringTableStartIndex+4*e),o=function(e,t){for(var n=0,r=0,o=0;o<4;o++){var i=e[t+o];if(n|=(127&i)<>>0)}function g(e,t){var n=d(e,t+4);if(n>i)throw new Error("Cannot read uint64 with high order part "+n+", because the result would exceed Number.MAX_SAFE_INTEGER.");return n*o+d(e,t)}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r="function"==typeof TextDecoder?new TextDecoder("utf-8"):null;t.decodeUtf8=r?r.decode.bind(r):function(e){var t=0,n=e.length,r=[],o=[];for(;t65535&&(u-=65536,r.push(u>>>10&1023|55296),u=56320|1023&u),r.push(u)}r.length>1024&&(o.push(String.fromCharCode.apply(null,r)),r.length=0)}return o.push(String.fromCharCode.apply(null,r)),o.join("")}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n(17),o=function(){function e(){}return e.prototype.log=function(e,t){},e.instance=new e,e}();t.NullLogger=o;var i=function(){function e(e){this.minimumLogLevel=e}return e.prototype.log=function(e,t){if(e>=this.minimumLogLevel)switch(e){case r.LogLevel.Critical:case r.LogLevel.Error:console.error("["+(new Date).toISOString()+"] "+r.LogLevel[e]+": "+t);break;case r.LogLevel.Warning:console.warn("["+(new Date).toISOString()+"] "+r.LogLevel[e]+": "+t);break;case r.LogLevel.Information:console.info("["+(new Date).toISOString()+"] "+r.LogLevel[e]+": "+t);break;default:console.log("["+(new Date).toISOString()+"] "+r.LogLevel[e]+": "+t)}},e}();t.ConsoleLogger=i},function(e,t,n){"use strict";var r=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))(function(o,i){function a(e){try{c(r.next(e))}catch(e){i(e)}}function s(e){try{c(r.throw(e))}catch(e){i(e)}}function c(e){e.done?o(e.value):new n(function(t){t(e.value)}).then(a,s)}c((r=r.apply(e,t||[])).next())})},o=this&&this.__generator||function(e,t){var n,r,o,i,a={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return i={next:s(0),throw:s(1),return:s(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function s(i){return function(s){return function(i){if(n)throw new TypeError("Generator is already executing.");for(;a;)try{if(n=1,r&&(o=2&i[0]?r.return:i[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,i[1])).done)return o;switch(r=0,o&&(i=[2&i[0],o.value]),i[0]){case 0:case 1:o=i;break;case 4:return a.label++,{value:i[1],done:!1};case 5:a.label++,r=i[1],i=[0];continue;case 7:i=a.ops.pop(),a.trys.pop();continue;default:if(!(o=(o=a.trys).length>0&&o[o.length-1])&&(6===i[0]||2===i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]0&&o[o.length-1])&&(6===i[0]||2===i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]0&&o[o.length-1])&&(6===i[0]||2===i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]reloading the page if you're unable to reconnect.",this.message.querySelector("a").addEventListener("click",function(){return location.reload()})},e.prototype.rejected=function(){this.button.style.display="none",this.reloadParagraph.style.display="none",this.message.innerHTML="Could not reconnect to the server. Reload the page to restore functionality.",this.message.querySelector("a").addEventListener("click",function(){return location.reload()})},e}();t.DefaultReconnectDisplay=a},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=function(){function e(e){this.dialog=e}return e.prototype.show=function(){this.removeClasses(),this.dialog.classList.add(e.ShowClassName)},e.prototype.hide=function(){this.removeClasses(),this.dialog.classList.add(e.HideClassName)},e.prototype.failed=function(){this.removeClasses(),this.dialog.classList.add(e.FailedClassName)},e.prototype.rejected=function(){this.removeClasses(),this.dialog.classList.add(e.RejectedClassName)},e.prototype.removeClasses=function(){this.dialog.classList.remove(e.ShowClassName,e.HideClassName,e.FailedClassName,e.RejectedClassName)},e.ShowClassName="components-reconnect-show",e.HideClassName="components-reconnect-hide",e.FailedClassName="components-reconnect-failed",e.RejectedClassName="components-reconnect-rejected",e}();t.UserSpecifiedDisplay=r},function(e,t,n){"use strict";n.r(t);var r=n(6),o=n(11),i=n(2),a=function(){function e(){}return e.write=function(e){var t=e.byteLength||e.length,n=[];do{var r=127&t;(t>>=7)>0&&(r|=128),n.push(r)}while(t>0);t=e.byteLength||e.length;var o=new Uint8Array(n.length+t);return o.set(n,0),o.set(e,n.length),o.buffer},e.parse=function(e){for(var t=[],n=new Uint8Array(e),r=[0,7,14,21,28],o=0;o7)throw new Error("Messages bigger than 2GB are not supported.");if(!(n.byteLength>=o+i+a))throw new Error("Incomplete message.");t.push(n.slice?n.slice(o+i,o+i+a):n.subarray(o+i,o+i+a)),o=o+i+a}return t},e}();var s=new Uint8Array([145,i.MessageType.Ping]),c=function(){function e(){this.name="messagepack",this.version=1,this.transferFormat=i.TransferFormat.Binary,this.errorResult=1,this.voidResult=2,this.nonVoidResult=3}return e.prototype.parseMessages=function(e,t){if(!(e instanceof r.Buffer||(n=e,n&&"undefined"!=typeof ArrayBuffer&&(n instanceof ArrayBuffer||n.constructor&&"ArrayBuffer"===n.constructor.name))))throw new Error("Invalid input for MessagePack hub protocol. Expected an ArrayBuffer or Buffer.");var n;null===t&&(t=i.NullLogger.instance);for(var o=[],s=0,c=a.parse(e);s=3?e[2]:void 0,error:e[1],type:i.MessageType.Close}},e.prototype.createPingMessage=function(e){if(e.length<1)throw new Error("Invalid payload for Ping message.");return{type:i.MessageType.Ping}},e.prototype.createInvocationMessage=function(e,t){if(t.length<5)throw new Error("Invalid payload for Invocation message.");var n=t[2];return n?{arguments:t[4],headers:e,invocationId:n,streamIds:[],target:t[3],type:i.MessageType.Invocation}:{arguments:t[4],headers:e,streamIds:[],target:t[3],type:i.MessageType.Invocation}},e.prototype.createStreamItemMessage=function(e,t){if(t.length<4)throw new Error("Invalid payload for StreamItem message.");return{headers:e,invocationId:t[2],item:t[3],type:i.MessageType.StreamItem}},e.prototype.createCompletionMessage=function(e,t){if(t.length<4)throw new Error("Invalid payload for Completion message.");var n,r,o=t[3];if(o!==this.voidResult&&t.length<5)throw new Error("Invalid payload for Completion message.");switch(o){case this.errorResult:n=t[4];break;case this.nonVoidResult:r=t[4]}return{error:n,headers:e,invocationId:t[2],result:r,type:i.MessageType.Completion}},e.prototype.writeInvocation=function(e){var t,n=o();return t=e.streamIds?n.encode([i.MessageType.Invocation,e.headers||{},e.invocationId||null,e.target,e.arguments,e.streamIds]):n.encode([i.MessageType.Invocation,e.headers||{},e.invocationId||null,e.target,e.arguments]),a.write(t.slice())},e.prototype.writeStreamInvocation=function(e){var t,n=o();return t=e.streamIds?n.encode([i.MessageType.StreamInvocation,e.headers||{},e.invocationId,e.target,e.arguments,e.streamIds]):n.encode([i.MessageType.StreamInvocation,e.headers||{},e.invocationId,e.target,e.arguments]),a.write(t.slice())},e.prototype.writeStreamItem=function(e){var t=o().encode([i.MessageType.StreamItem,e.headers||{},e.invocationId,e.item]);return a.write(t.slice())},e.prototype.writeCompletion=function(e){var t,n=o(),r=e.error?this.errorResult:e.result?this.nonVoidResult:this.voidResult;switch(r){case this.errorResult:t=n.encode([i.MessageType.Completion,e.headers||{},e.invocationId,r,e.error]);break;case this.voidResult:t=n.encode([i.MessageType.Completion,e.headers||{},e.invocationId,r]);break;case this.nonVoidResult:t=n.encode([i.MessageType.Completion,e.headers||{},e.invocationId,r,e.result])}return a.write(t.slice())},e.prototype.writeCancelInvocation=function(e){var t=o().encode([i.MessageType.CancelInvocation,e.headers||{},e.invocationId]);return a.write(t.slice())},e.prototype.readHeaders=function(e){var t=e[1];if("object"!=typeof t)throw new Error("Invalid headers.");return t},e}();n.d(t,"VERSION",function(){return u}),n.d(t,"MessagePackHubProtocol",function(){return c});var u="5.0.0-dev"}]); \ No newline at end of file diff --git a/src/Components/Web.JS/dist/Release/blazor.webassembly.js b/src/Components/Web.JS/dist/Release/blazor.webassembly.js index 5279765b23..f0b6c6e598 100644 --- a/src/Components/Web.JS/dist/Release/blazor.webassembly.js +++ b/src/Components/Web.JS/dist/Release/blazor.webassembly.js @@ -1 +1 @@ -!function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=45)}([,,,,,function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),n(25),n(18);var r=n(26),o=n(13),a={},i=!1;function l(e,t,n){var o=a[e];o||(o=a[e]=new r.BrowserRenderer(e)),o.attachRootComponentToLogicalElement(n,t)}t.attachRootComponentToLogicalElement=l,t.attachRootComponentToElement=function(e,t,n){var r=document.querySelector(e);if(!r)throw new Error("Could not find any element matching selector '"+e+"'.");l(n||0,o.toLogicalElement(r,!0),t)},t.renderBatch=function(e,t){var n=a[e];if(!n)throw new Error("There is no browser renderer with ID "+e+".");for(var r=t.arrayRangeReader,o=t.updatedComponents(),l=r.values(o),u=r.count(o),s=t.referenceFrames(),c=r.values(s),d=t.diffReader,f=0;f0&&o[o.length-1])&&(6===a[0]||2===a[0])){i=0;continue}if(3===a[0]&&(!o||a[1]>o[0]&&a[1]0&&!t)throw new Error("New logical elements must start empty, or allowExistingContents must be true");return e[r]=[],e}function l(e,t,n){var a=e;if(e instanceof Comment&&(s(a)&&s(a).length>0))throw new Error("Not implemented: inserting non-empty logical container");if(u(a))throw new Error("Not implemented: moving existing logical children");var i=s(t);if(n0;)e(r,0);var a=r;a.parentNode.removeChild(a)},t.getLogicalParent=u,t.getLogicalSiblingEnd=function(e){return e[a]||null},t.getLogicalChild=function(e,t){return s(e)[t]},t.isSvgElement=function(e){return"http://www.w3.org/2000/svg"===c(e).namespaceURI},t.getLogicalChildrenArray=s,t.permuteLogicalChildren=function(e,t){var n=s(e);t.forEach(function(e){e.moveRangeStart=n[e.fromSiblingIndex],e.moveRangeEnd=function e(t){if(t instanceof Element)return t;var n=d(t);if(n)return n.previousSibling;var r=u(t);return r instanceof Element?r.lastChild:e(r)}(e.moveRangeStart)}),t.forEach(function(t){var r=t.moveToBeforeMarker=document.createComment("marker"),o=n[t.toSiblingIndex+1];o?o.parentNode.insertBefore(r,o):f(r,e)}),t.forEach(function(e){for(var t=e.moveToBeforeMarker,n=t.parentNode,r=e.moveRangeStart,o=e.moveRangeEnd,a=r;a;){var i=a.nextSibling;if(n.insertBefore(a,t),a===o)break;a=i}n.removeChild(t)}),t.forEach(function(e){n[e.toSiblingIndex]=e.moveRangeStart})},t.getClosestDomElement=c},,,,function(e,t,n){"use strict";var r;!function(e){window.DotNet=e;var t=[],n={},r={},o=1,a=null;function i(e){t.push(e)}function l(e,t,n,r){var o=s();if(o.invokeDotNetFromJS){var a=JSON.stringify(r,h),i=o.invokeDotNetFromJS(e,t,n,a);return i?d(i):null}throw new Error("The current dispatcher does not support synchronous calls from JS to .NET. Use invokeMethodAsync instead.")}function u(e,t,r,a){if(e&&r)throw new Error("For instance method calls, assemblyName should be null. Received '"+e+"'.");var i=o++,l=new Promise(function(e,t){n[i]={resolve:e,reject:t}});try{var u=JSON.stringify(a,h);s().beginInvokeDotNetFromJS(i,e,t,r,u)}catch(e){c(i,!1,e)}return l}function s(){if(null!==a)return a;throw new Error("No .NET call dispatcher has been set.")}function c(e,t,r){if(!n.hasOwnProperty(e))throw new Error("There is no pending async call with ID "+e+".");var o=n[e];delete n[e],t?o.resolve(r):o.reject(r)}function d(e){return e?JSON.parse(e,function(e,n){return t.reduce(function(t,n){return n(e,t)},n)}):null}function f(e){return e instanceof Error?e.message+"\n"+e.stack:e?e.toString():"null"}function p(e){if(r.hasOwnProperty(e))return r[e];var t,n=window,o="window";if(e.split(".").forEach(function(e){if(!(e in n))throw new Error("Could not find '"+e+"' in '"+o+"'.");t=n,n=n[e],o+="."+e}),n instanceof Function)return n=n.bind(t),r[e]=n,n;throw new Error("The value '"+o+"' is not a function.")}e.attachDispatcher=function(e){a=e},e.attachReviver=i,e.invokeMethod=function(e,t){for(var n=[],r=2;r0&&o[o.length-1])&&(6===a[0]||2===a[0])){i=0;continue}if(3===a[0]&&(!o||a[1]>o[0]&&a[1]0&&o[o.length-1])&&(6===a[0]||2===a[0])){i=0;continue}if(3===a[0]&&(!o||a[1]>o[0]&&a[1]-1?a.substring(0,l):"",s=l>-1?a.substring(l+1):a,c=t.monoPlatform.findMethod(e,u,s,i);t.monoPlatform.callMethod(c,null,r)},callMethod:function(e,n,r){if(r.length>4)throw new Error("Currently, MonoPlatform supports passing a maximum of 4 arguments from JS to .NET. You tried to pass "+r.length+".");var o=Module.stackSave();try{for(var a=Module.stackAlloc(r.length),l=Module.stackAlloc(4),u=0;u>2,r=Module.HEAPU32[n+1];if(r>y)throw new Error("Cannot read uint64 with high order part "+r+", because the result would exceed Number.MAX_SAFE_INTEGER.");return r*v+Module.HEAPU32[n]},readFloatField:function(e,t){return Module.getValue(e+(t||0),"float")},readObjectField:function(e,t){return Module.getValue(e+(t||0),"i32")},readStringField:function(e,n){var r=Module.getValue(e+(n||0),"i32");return 0===r?null:t.monoPlatform.toJavaScriptString(r)},readStructField:function(e,t){return e+(t||0)}};var w=document.createElement("a");function E(e){return e+12}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n(32),o=window.chrome&&navigator.userAgent.indexOf("Edge")<0,a=!1;function i(){return a&&o}t.hasDebuggingEnabled=i,t.attachDebuggerHotkey=function(e){a=e.some(function(e){return/\.pdb$/.test(r.getFileNameFromUrl(e))});var t=navigator.platform.match(/^Mac/i)?"Cmd":"Alt";i()&&console.info("Debugging hotkey: Shift+"+t+"+D (when application has focus)"),document.addEventListener("keydown",function(e){var t;e.shiftKey&&(e.metaKey||e.altKey)&&"KeyD"===e.code&&(a?o?((t=document.createElement("a")).href="_framework/debug?url="+encodeURIComponent(location.href),t.target="_blank",t.rel="noopener noreferrer",t.click()):console.error("Currently, only Edge(Chromium) or Chrome is supported for debugging."):console.error("Cannot start debugging, because the application was not compiled with debugging enabled."))})}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n(18),o=function(){function e(e){this.batchAddress=e,this.arrayRangeReader=a,this.arrayBuilderSegmentReader=i,this.diffReader=l,this.editReader=u,this.frameReader=s}return e.prototype.updatedComponents=function(){return r.platform.readStructField(this.batchAddress,0)},e.prototype.referenceFrames=function(){return r.platform.readStructField(this.batchAddress,a.structLength)},e.prototype.disposedComponentIds=function(){return r.platform.readStructField(this.batchAddress,2*a.structLength)},e.prototype.disposedEventHandlerIds=function(){return r.platform.readStructField(this.batchAddress,3*a.structLength)},e.prototype.updatedComponentsEntry=function(e,t){return c(e,t,l.structLength)},e.prototype.referenceFramesEntry=function(e,t){return c(e,t,s.structLength)},e.prototype.disposedComponentIdsEntry=function(e,t){var n=c(e,t,4);return r.platform.readInt32Field(n)},e.prototype.disposedEventHandlerIdsEntry=function(e,t){var n=c(e,t,8);return r.platform.readUint64Field(n)},e}();t.SharedMemoryRenderBatch=o;var a={structLength:8,values:function(e){return r.platform.readObjectField(e,0)},count:function(e){return r.platform.readInt32Field(e,4)}},i={structLength:12,values:function(e){var t=r.platform.readObjectField(e,0),n=r.platform.getObjectFieldsBaseAddress(t);return r.platform.readObjectField(n,0)},offset:function(e){return r.platform.readInt32Field(e,4)},count:function(e){return r.platform.readInt32Field(e,8)}},l={structLength:4+i.structLength,componentId:function(e){return r.platform.readInt32Field(e,0)},edits:function(e){return r.platform.readStructField(e,4)},editsEntry:function(e,t){return c(e,t,u.structLength)}},u={structLength:20,editType:function(e){return r.platform.readInt32Field(e,0)},siblingIndex:function(e){return r.platform.readInt32Field(e,4)},newTreeIndex:function(e){return r.platform.readInt32Field(e,8)},moveToSiblingIndex:function(e){return r.platform.readInt32Field(e,8)},removedAttributeName:function(e){return r.platform.readStringField(e,16)}},s={structLength:36,frameType:function(e){return r.platform.readInt16Field(e,4)},subtreeLength:function(e){return r.platform.readInt32Field(e,8)},elementReferenceCaptureId:function(e){return r.platform.readStringField(e,16)},componentId:function(e){return r.platform.readInt32Field(e,12)},elementName:function(e){return r.platform.readStringField(e,16)},textContent:function(e){return r.platform.readStringField(e,16)},markupContent:function(e){return r.platform.readStringField(e,16)},attributeName:function(e){return r.platform.readStringField(e,16)},attributeValue:function(e){return r.platform.readStringField(e,24)},attributeEventHandlerId:function(e){return r.platform.readUint64Field(e,8)}};function c(e,t,n){return r.platform.getArrayEntryPtr(e,t,n)}}]); \ No newline at end of file +!function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=42)}([,,,function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),n(23),n(17);var r=n(24),o=n(11),a={},i=!1;function u(e,t,n){var o=a[e];o||(o=a[e]=new r.BrowserRenderer(e)),o.attachRootComponentToLogicalElement(n,t)}t.attachRootComponentToLogicalElement=u,t.attachRootComponentToElement=function(e,t,n){var r=document.querySelector(e);if(!r)throw new Error("Could not find any element matching selector '"+e+"'.");u(n||0,o.toLogicalElement(r,!0),t)},t.renderBatch=function(e,t){var n=a[e];if(!n)throw new Error("There is no browser renderer with ID "+e+".");for(var r=t.arrayRangeReader,o=t.updatedComponents(),u=r.values(o),l=r.count(o),s=t.referenceFrames(),c=r.values(s),d=t.diffReader,f=0;f0&&o[o.length-1])&&(6===a[0]||2===a[0])){i=0;continue}if(3===a[0]&&(!o||a[1]>o[0]&&a[1]0&&!t)throw new Error("New logical elements must start empty, or allowExistingContents must be true");return r in e||(e[r]=[]),e}function u(e,t,n){var a=e;if(e instanceof Comment&&(s(a)&&s(a).length>0))throw new Error("Not implemented: inserting non-empty logical container");if(l(a))throw new Error("Not implemented: moving existing logical children");var i=s(t);if(n0;)e(r,0);var a=r;a.parentNode.removeChild(a)},t.getLogicalParent=l,t.getLogicalSiblingEnd=function(e){return e[a]||null},t.getLogicalChild=function(e,t){return s(e)[t]},t.isSvgElement=function(e){return"http://www.w3.org/2000/svg"===c(e).namespaceURI},t.getLogicalChildrenArray=s,t.permuteLogicalChildren=function(e,t){var n=s(e);t.forEach(function(e){e.moveRangeStart=n[e.fromSiblingIndex],e.moveRangeEnd=function e(t){if(t instanceof Element)return t;var n=d(t);if(n)return n.previousSibling;var r=l(t);return r instanceof Element?r.lastChild:e(r)}(e.moveRangeStart)}),t.forEach(function(t){var r=t.moveToBeforeMarker=document.createComment("marker"),o=n[t.toSiblingIndex+1];o?o.parentNode.insertBefore(r,o):f(r,e)}),t.forEach(function(e){for(var t=e.moveToBeforeMarker,n=t.parentNode,r=e.moveRangeStart,o=e.moveRangeEnd,a=r;a;){var i=a.nextSibling;if(n.insertBefore(a,t),a===o)break;a=i}n.removeChild(t)}),t.forEach(function(e){n[e.toSiblingIndex]=e.moveRangeStart})},t.getClosestDomElement=c},,,,,function(e,t,n){"use strict";var r;!function(e){window.DotNet=e;var t=[],n={},r={},o=1,a=null;function i(e){t.push(e)}function u(e,t,n,r){var o=s();if(o.invokeDotNetFromJS){var a=JSON.stringify(r,h),i=o.invokeDotNetFromJS(e,t,n,a);return i?d(i):null}throw new Error("The current dispatcher does not support synchronous calls from JS to .NET. Use invokeMethodAsync instead.")}function l(e,t,r,a){if(e&&r)throw new Error("For instance method calls, assemblyName should be null. Received '"+e+"'.");var i=o++,u=new Promise(function(e,t){n[i]={resolve:e,reject:t}});try{var l=JSON.stringify(a,h);s().beginInvokeDotNetFromJS(i,e,t,r,l)}catch(e){c(i,!1,e)}return u}function s(){if(null!==a)return a;throw new Error("No .NET call dispatcher has been set.")}function c(e,t,r){if(!n.hasOwnProperty(e))throw new Error("There is no pending async call with ID "+e+".");var o=n[e];delete n[e],t?o.resolve(r):o.reject(r)}function d(e){return e?JSON.parse(e,function(e,n){return t.reduce(function(t,n){return n(e,t)},n)}):null}function f(e){return e instanceof Error?e.message+"\n"+e.stack:e?e.toString():"null"}function p(e){if(r.hasOwnProperty(e))return r[e];var t,n=window,o="window";if(e.split(".").forEach(function(e){if(!(e in n))throw new Error("Could not find '"+e+"' in '"+o+"'.");t=n,n=n[e],o+="."+e}),n instanceof Function)return n=n.bind(t),r[e]=n,n;throw new Error("The value '"+o+"' is not a function.")}e.attachDispatcher=function(e){a=e},e.attachReviver=i,e.invokeMethod=function(e,t){for(var n=[],r=2;r0&&o[o.length-1])&&(6===a[0]||2===a[0])){i=0;continue}if(3===a[0]&&(!o||a[1]>o[0]&&a[1]0&&o[o.length-1])&&(6===a[0]||2===a[0])){i=0;continue}if(3===a[0]&&(!o||a[1]>o[0]&&a[1]>2,r=Module.HEAPU32[n+1];if(r>s)throw new Error("Cannot read uint64 with high order part "+r+", because the result would exceed Number.MAX_SAFE_INTEGER.");return r*l+Module.HEAPU32[n]},readFloatField:function(e,t){return Module.getValue(e+(t||0),"float")},readObjectField:function(e,t){return Module.getValue(e+(t||0),"i32")},readStringField:function(e,n){var r=Module.getValue(e+(n||0),"i32");return 0===r?null:t.monoPlatform.toJavaScriptString(r)},readStructField:function(e,t){return e+(t||0)}};var c=document.createElement("a");function d(e){return e+12}function f(e,t,n){var r="["+e+"] "+t+":"+n;return Module.mono_bind_static_method(r)}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n(33),o=window.chrome&&navigator.userAgent.indexOf("Edge")<0,a=!1;function i(){return a&&o}t.hasDebuggingEnabled=i,t.attachDebuggerHotkey=function(e){a=e.some(function(e){return/\.pdb$/.test(r.getFileNameFromUrl(e))});var t=navigator.platform.match(/^Mac/i)?"Cmd":"Alt";i()&&console.info("Debugging hotkey: Shift+"+t+"+D (when application has focus)"),document.addEventListener("keydown",function(e){var t;e.shiftKey&&(e.metaKey||e.altKey)&&"KeyD"===e.code&&(a?o?((t=document.createElement("a")).href="_framework/debug?url="+encodeURIComponent(location.href),t.target="_blank",t.rel="noopener noreferrer",t.click()):console.error("Currently, only Edge(Chromium) or Chrome is supported for debugging."):console.error("Cannot start debugging, because the application was not compiled with debugging enabled."))})}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n(17),o=function(){function e(e){this.batchAddress=e,this.arrayRangeReader=a,this.arrayBuilderSegmentReader=i,this.diffReader=u,this.editReader=l,this.frameReader=s}return e.prototype.updatedComponents=function(){return r.platform.readStructField(this.batchAddress,0)},e.prototype.referenceFrames=function(){return r.platform.readStructField(this.batchAddress,a.structLength)},e.prototype.disposedComponentIds=function(){return r.platform.readStructField(this.batchAddress,2*a.structLength)},e.prototype.disposedEventHandlerIds=function(){return r.platform.readStructField(this.batchAddress,3*a.structLength)},e.prototype.updatedComponentsEntry=function(e,t){return c(e,t,u.structLength)},e.prototype.referenceFramesEntry=function(e,t){return c(e,t,s.structLength)},e.prototype.disposedComponentIdsEntry=function(e,t){var n=c(e,t,4);return r.platform.readInt32Field(n)},e.prototype.disposedEventHandlerIdsEntry=function(e,t){var n=c(e,t,8);return r.platform.readUint64Field(n)},e}();t.SharedMemoryRenderBatch=o;var a={structLength:8,values:function(e){return r.platform.readObjectField(e,0)},count:function(e){return r.platform.readInt32Field(e,4)}},i={structLength:12,values:function(e){var t=r.platform.readObjectField(e,0),n=r.platform.getObjectFieldsBaseAddress(t);return r.platform.readObjectField(n,0)},offset:function(e){return r.platform.readInt32Field(e,4)},count:function(e){return r.platform.readInt32Field(e,8)}},u={structLength:4+i.structLength,componentId:function(e){return r.platform.readInt32Field(e,0)},edits:function(e){return r.platform.readStructField(e,4)},editsEntry:function(e,t){return c(e,t,l.structLength)}},l={structLength:20,editType:function(e){return r.platform.readInt32Field(e,0)},siblingIndex:function(e){return r.platform.readInt32Field(e,4)},newTreeIndex:function(e){return r.platform.readInt32Field(e,8)},moveToSiblingIndex:function(e){return r.platform.readInt32Field(e,8)},removedAttributeName:function(e){return r.platform.readStringField(e,16)}},s={structLength:36,frameType:function(e){return r.platform.readInt16Field(e,4)},subtreeLength:function(e){return r.platform.readInt32Field(e,8)},elementReferenceCaptureId:function(e){return r.platform.readStringField(e,16)},componentId:function(e){return r.platform.readInt32Field(e,12)},elementName:function(e){return r.platform.readStringField(e,16)},textContent:function(e){return r.platform.readStringField(e,16)},markupContent:function(e){return r.platform.readStringField(e,16)},attributeName:function(e){return r.platform.readStringField(e,16)},attributeValue:function(e){return r.platform.readStringField(e,24)},attributeEventHandlerId:function(e){return r.platform.readUint64Field(e,8)}};function c(e,t,n){return r.platform.getArrayEntryPtr(e,t,n)}}]); \ No newline at end of file diff --git a/src/Components/Web.JS/package.json b/src/Components/Web.JS/package.json index ca36ee9808..e750da915c 100644 --- a/src/Components/Web.JS/package.json +++ b/src/Components/Web.JS/package.json @@ -14,9 +14,9 @@ "test": "jest" }, "devDependencies": { - "@aspnet/signalr": "link:../../SignalR/clients/ts/signalr", - "@aspnet/signalr-protocol-msgpack": "link:../../SignalR/clients/ts/signalr-protocol-msgpack", - "@dotnet/jsinterop": "https://dotnet.myget.org/F/aspnetcore-dev/npm/@dotnet/jsinterop/-/@dotnet/jsinterop-3.0.0-preview9.19415.3.tgz", + "@microsoft/signalr": "link:../../SignalR/clients/ts/signalr", + "@microsoft/signalr-protocol-msgpack": "link:../../SignalR/clients/ts/signalr-protocol-msgpack", + "@microsoft/dotnet-js-interop": "https://dotnet.myget.org/F/aspnetcore-dev/npm/@microsoft/dotnet-js-interop/-/@microsoft/dotnet-js-interop-5.0.0-alpha1.19572.2.tgz", "@types/emscripten": "0.0.31", "@types/jest": "^24.0.6", "@types/jsdom": "11.0.6", diff --git a/src/Components/Web.JS/src/Boot.Server.ts b/src/Components/Web.JS/src/Boot.Server.ts index 4ea227247c..a7934b5112 100644 --- a/src/Components/Web.JS/src/Boot.Server.ts +++ b/src/Components/Web.JS/src/Boot.Server.ts @@ -1,7 +1,7 @@ -import '@dotnet/jsinterop'; +import '@microsoft/dotnet-js-interop'; import './GlobalExports'; -import * as signalR from '@aspnet/signalr'; -import { MessagePackHubProtocol } from '@aspnet/signalr-protocol-msgpack'; +import * as signalR from '@microsoft/signalr'; +import { MessagePackHubProtocol } from '@microsoft/signalr-protocol-msgpack'; import { showErrorNotification } from './BootErrors'; import { shouldAutoStart } from './BootCommon'; import { RenderQueue } from './Platform/Circuits/RenderQueue'; diff --git a/src/Components/Web.JS/src/Boot.WebAssembly.ts b/src/Components/Web.JS/src/Boot.WebAssembly.ts index 74bd496b02..fcc7a8f5a9 100644 --- a/src/Components/Web.JS/src/Boot.WebAssembly.ts +++ b/src/Components/Web.JS/src/Boot.WebAssembly.ts @@ -1,8 +1,7 @@ -import '@dotnet/jsinterop'; +import '@microsoft/dotnet-js-interop'; import './GlobalExports'; import * as Environment from './Environment'; import { monoPlatform } from './Platform/Mono/MonoPlatform'; -import { getAssemblyNameFromUrl } from './Platform/Url'; import { renderBatch } from './Rendering/Renderer'; import { SharedMemoryRenderBatch } from './Rendering/RenderBatch/SharedMemoryRenderBatch'; import { Pointer } from './Platform/Platform'; @@ -39,15 +38,13 @@ async function boot(options?: any): Promise { // Fetch the boot JSON file const bootConfig = await fetchBootConfigAsync(); - const embeddedResourcesPromise = loadEmbeddedResourcesAsync(bootConfig); if (!bootConfig.linkerEnabled) { console.info('Blazor is running in dev mode without IL stripping. To make the bundle size significantly smaller, publish the application or see https://go.microsoft.com/fwlink/?linkid=870414'); } // Determine the URLs of the assemblies we want to load, then begin fetching them all - const loadAssemblyUrls = [bootConfig.main] - .concat(bootConfig.assemblyReferences) + const loadAssemblyUrls = bootConfig.assemblies .map(filename => `_framework/_bin/${filename}`); try { @@ -56,12 +53,8 @@ async function boot(options?: any): Promise { throw new Error(`Failed to start platform. Reason: ${ex}`); } - // Before we start running .NET code, be sure embedded content resources are all loaded - await embeddedResourcesPromise; - // Start up the application - const mainAssemblyName = getAssemblyNameFromUrl(bootConfig.main); - platform.callEntryPoint(mainAssemblyName, bootConfig.entryPoint, []); + platform.callEntryPoint(bootConfig.entryAssembly); } async function fetchBootConfigAsync() { @@ -71,40 +64,16 @@ async function fetchBootConfigAsync() { return bootConfigResponse.json() as Promise; } -function loadEmbeddedResourcesAsync(bootConfig: BootJsonData): Promise { - const cssLoadingPromises = bootConfig.cssReferences.map(cssReference => { - const linkElement = document.createElement('link'); - linkElement.rel = 'stylesheet'; - linkElement.href = cssReference; - return loadResourceFromElement(linkElement); - }); - const jsLoadingPromises = bootConfig.jsReferences.map(jsReference => { - const scriptElement = document.createElement('script'); - scriptElement.src = jsReference; - return loadResourceFromElement(scriptElement); - }); - return Promise.all(cssLoadingPromises.concat(jsLoadingPromises)); -} - -function loadResourceFromElement(element: HTMLElement) { - return new Promise((resolve, reject) => { - element.onload = resolve; - element.onerror = reject; - document.head!.appendChild(element); - }); -} - // Keep in sync with BootJsonData in Microsoft.AspNetCore.Blazor.Build interface BootJsonData { - main: string; - entryPoint: string; - assemblyReferences: string[]; - cssReferences: string[]; - jsReferences: string[]; + entryAssembly: string; + assemblies: string[]; linkerEnabled: boolean; } window['Blazor'].start = boot; if (shouldAutoStart()) { - boot(); + boot().catch(error => { + Module.printErr(error); // Logs it, and causes the error UI to appear + }); } diff --git a/src/Components/Web.JS/src/Platform/Circuits/DefaultReconnectDisplay.ts b/src/Components/Web.JS/src/Platform/Circuits/DefaultReconnectDisplay.ts index b8f755d8ac..58d9eb7e37 100644 --- a/src/Components/Web.JS/src/Platform/Circuits/DefaultReconnectDisplay.ts +++ b/src/Components/Web.JS/src/Platform/Circuits/DefaultReconnectDisplay.ts @@ -22,7 +22,7 @@ export class DefaultReconnectDisplay implements ReconnectDisplay { 'right: 0', 'bottom: 0', 'left: 0', - 'z-index: 1000', + 'z-index: 1050', 'display: none', 'overflow: hidden', 'background-color: #fff', diff --git a/src/Components/Web.JS/src/Platform/Circuits/RenderQueue.ts b/src/Components/Web.JS/src/Platform/Circuits/RenderQueue.ts index 311df1b43e..f4548eed47 100644 --- a/src/Components/Web.JS/src/Platform/Circuits/RenderQueue.ts +++ b/src/Components/Web.JS/src/Platform/Circuits/RenderQueue.ts @@ -1,7 +1,7 @@ import { renderBatch } from '../../Rendering/Renderer'; import { OutOfProcessRenderBatch } from '../../Rendering/RenderBatch/OutOfProcessRenderBatch'; import { Logger, LogLevel } from '../Logging/Logger'; -import { HubConnection } from '@aspnet/signalr'; +import { HubConnection } from '@microsoft/signalr'; export class RenderQueue { private static instance: RenderQueue; diff --git a/src/Components/Web.JS/src/Platform/Mono/MonoPlatform.ts b/src/Components/Web.JS/src/Platform/Mono/MonoPlatform.ts index 997b3d7bca..321a708f57 100644 --- a/src/Components/Web.JS/src/Platform/Mono/MonoPlatform.ts +++ b/src/Components/Web.JS/src/Platform/Mono/MonoPlatform.ts @@ -1,18 +1,9 @@ -import { MethodHandle, System_Object, System_String, System_Array, Pointer, Platform } from '../Platform'; +import { System_Object, System_String, System_Array, Pointer, Platform } from '../Platform'; import { getFileNameFromUrl } from '../Url'; import { attachDebuggerHotkey, hasDebuggingEnabled } from './MonoDebugger'; import { showErrorNotification } from '../../BootErrors'; -const assemblyHandleCache: { [assemblyName: string]: number } = {}; -const typeHandleCache: { [fullyQualifiedTypeName: string]: number } = {}; -const methodHandleCache: { [fullyQualifiedMethodName: string]: MethodHandle } = {}; - -let assembly_load: (assemblyName: string) => number; -let find_class: (assemblyHandle: number, namespace: string, className: string) => number; -let find_method: (typeHandle: number, methodName: string, unknownArg: number) => MethodHandle; -let invoke_method: (method: MethodHandle, target: System_Object, argsArrayPtr: number, exceptionFlagIntPtr: number) => System_Object; let mono_string_get_utf8: (managedString: System_String) => Mono.Utf8Ptr; -let mono_string: (jsString: string) => System_String; const appBinDirName = 'appBinDir'; const uint64HighOrderShift = Math.pow(2, 32); const maxSafeNumberHighPart = Math.pow(2, 21) - 1; // The high-order int32 from Number.MAX_SAFE_INTEGER @@ -22,7 +13,7 @@ export const monoPlatform: Platform = { return new Promise((resolve, reject) => { attachDebuggerHotkey(loadAssemblyUrls); - // mono.js assumes the existence of this + // dotnet.js assumes the existence of this window['Browser'] = { init: () => { }, }; @@ -37,52 +28,16 @@ export const monoPlatform: Platform = { }); }, - findMethod: findMethod, - - callEntryPoint: function callEntryPoint(assemblyName: string, entrypointMethod: string, args: System_Object[]): void { - // Parse the entrypointMethod, which is of the form MyApp.MyNamespace.MyTypeName::MyMethodName - // Note that we don't support specifying a method overload, so it has to be unique - const entrypointSegments = entrypointMethod.split('::'); - if (entrypointSegments.length != 2) { - throw new Error('Malformed entry point method name; could not resolve class name and method name.'); - } - const typeFullName = entrypointSegments[0]; - const methodName = entrypointSegments[1]; - const lastDot = typeFullName.lastIndexOf('.'); - const namespace = lastDot > -1 ? typeFullName.substring(0, lastDot) : ''; - const typeShortName = lastDot > -1 ? typeFullName.substring(lastDot + 1) : typeFullName; - - const entryPointMethodHandle = monoPlatform.findMethod(assemblyName, namespace, typeShortName, methodName); - monoPlatform.callMethod(entryPointMethodHandle, null, args); - }, - - callMethod: function callMethod(method: MethodHandle, target: System_Object, args: System_Object[]): System_Object { - if (args.length > 4) { - // Hopefully this restriction can be eased soon, but for now make it clear what's going on - throw new Error(`Currently, MonoPlatform supports passing a maximum of 4 arguments from JS to .NET. You tried to pass ${args.length}.`); - } - - const stack = Module.stackSave(); - - try { - const argsBuffer = Module.stackAlloc(args.length); - const exceptionFlagManagedInt = Module.stackAlloc(4); - for (let i = 0; i < args.length; ++i) { - Module.setValue(argsBuffer + i * 4, args[i], 'i32'); - } - Module.setValue(exceptionFlagManagedInt, 0, 'i32'); - - const res = invoke_method(method, target, argsBuffer, exceptionFlagManagedInt); - - if (Module.getValue(exceptionFlagManagedInt, 'i32') !== 0) { - // If the exception flag is set, the returned value is exception.ToString() - throw new Error(monoPlatform.toJavaScriptString(res)); - } - - return res; - } finally { - Module.stackRestore(stack); - } + callEntryPoint: function callEntryPoint(assemblyName: string) { + // Instead of using Module.mono_call_assembly_entry_point, we have our own logic for invoking + // the entrypoint which adds support for async main. + // Currently we disregard the return value from the entrypoint, whether it's sync or async. + // In the future, we might want Blazor.start to return a Promise>, where the + // outer promise reflects the startup process, and the inner one reflects the possibly-async + // .NET entrypoint method. + const invokeEntrypoint = bindStaticMethod('Microsoft.AspNetCore.Blazor', 'Microsoft.AspNetCore.Blazor.Hosting.EntrypointInvoker', 'InvokeEntrypoint'); + // Note we're passing in null because passing arrays is problematic until https://github.com/mono/mono/issues/18245 is resolved. + invokeEntrypoint(assemblyName, null); }, toJavaScriptString: function toJavaScriptString(managedString: System_String) { @@ -96,10 +51,6 @@ export const monoPlatform: Platform = { return res; }, - toDotNetString: function toDotNetString(jsString: string): System_String { - return mono_string(jsString); - }, - toUint8Array: function toUint8Array(array: System_Array): Uint8Array { const dataPtr = getArrayDataPointer(array); const length = Module.getValue(dataPtr, 'i32'); @@ -160,44 +111,6 @@ export const monoPlatform: Platform = { }, }; -function findAssembly(assemblyName: string): number { - let assemblyHandle = assemblyHandleCache[assemblyName]; - if (!assemblyHandle) { - assemblyHandle = assembly_load(assemblyName); - if (!assemblyHandle) { - throw new Error(`Could not find assembly "${assemblyName}"`); - } - assemblyHandleCache[assemblyName] = assemblyHandle; - } - return assemblyHandle; -} - -function findType(assemblyName: string, namespace: string, className: string): number { - const fullyQualifiedTypeName = `[${assemblyName}]${namespace}.${className}`; - let typeHandle = typeHandleCache[fullyQualifiedTypeName]; - if (!typeHandle) { - typeHandle = find_class(findAssembly(assemblyName), namespace, className); - if (!typeHandle) { - throw new Error(`Could not find type "${className}" in namespace "${namespace}" in assembly "${assemblyName}"`); - } - typeHandleCache[fullyQualifiedTypeName] = typeHandle; - } - return typeHandle; -} - -function findMethod(assemblyName: string, namespace: string, className: string, methodName: string): MethodHandle { - const fullyQualifiedMethodName = `[${assemblyName}]${namespace}.${className}::${methodName}`; - let methodHandle = methodHandleCache[fullyQualifiedMethodName]; - if (!methodHandle) { - methodHandle = find_method(findType(assemblyName, namespace, className), methodName, -1); - if (!methodHandle) { - throw new Error(`Could not find method "${methodName}" on type "${namespace}.${className}"`); - } - methodHandleCache[fullyQualifiedMethodName] = methodHandle; - } - return methodHandle; -} - function addScriptTagsToDocument() { const browserSupportsNativeWebAssembly = typeof WebAssembly !== 'undefined' && WebAssembly.validate; if (!browserSupportsNativeWebAssembly) { @@ -205,7 +118,7 @@ function addScriptTagsToDocument() { } const scriptElem = document.createElement('script'); - scriptElem.src = '_framework/wasm/mono.js'; + scriptElem.src = '_framework/wasm/dotnet.js'; scriptElem.defer = true; document.body.appendChild(scriptElem); } @@ -229,7 +142,7 @@ function addGlobalModuleScriptTagsToDocument(callback: () => void) { function createEmscriptenModuleInstance(loadAssemblyUrls: string[], onReady: () => void, onError: (reason?: any) => void) { const module = {} as typeof Module; - const wasmBinaryFile = '_framework/wasm/mono.wasm'; + const wasmBinaryFile = '_framework/wasm/dotnet.wasm'; const suppressMessages = ['DEBUGGING ENABLED']; module.print = line => (suppressMessages.indexOf(line) < 0 && console.log(`WASM: ${line}`)); @@ -244,7 +157,7 @@ function createEmscriptenModuleInstance(loadAssemblyUrls: string[], onReady: () module.locateFile = fileName => { switch (fileName) { - case 'mono.wasm': return wasmBinaryFile; + case 'dotnet.wasm': return wasmBinaryFile; default: return fileName; } }; @@ -256,24 +169,8 @@ function createEmscriptenModuleInstance(loadAssemblyUrls: string[], onReady: () 'number', 'number', ]); - assembly_load = Module.cwrap('mono_wasm_assembly_load', 'number', ['string']); - find_class = Module.cwrap('mono_wasm_assembly_find_class', 'number', [ - 'number', - 'string', - 'string', - ]); - find_method = Module.cwrap('mono_wasm_assembly_find_method', 'number', [ - 'number', - 'string', - 'number', - ]); - invoke_method = Module.cwrap('mono_wasm_invoke_method', 'number', [ - 'number', - 'number', - 'number', - ]); + mono_string_get_utf8 = Module.cwrap('mono_wasm_string_get_utf8', 'number', ['number']); - mono_string = Module.cwrap('mono_wasm_string_from_js', 'number', ['string']); MONO.loaded_files = []; @@ -346,10 +243,16 @@ function getArrayDataPointer(array: System_Array): number { return array + 12; // First byte from here is length, then following bytes are entries } +function bindStaticMethod(assembly: string, typeName: string, method: string) : (...args: any[]) => any { + // Fully qualified name looks like this: "[debugger-test] Math:IntAdd" + const fqn = `[${assembly}] ${typeName}:${method}`; + return Module.mono_bind_static_method(fqn); +} + function attachInteropInvoker(): void { - const dotNetDispatcherInvokeMethodHandle = findMethod('Mono.WebAssembly.Interop', 'Mono.WebAssembly.Interop', 'MonoWebAssemblyJSRuntime', 'InvokeDotNet'); - const dotNetDispatcherBeginInvokeMethodHandle = findMethod('Mono.WebAssembly.Interop', 'Mono.WebAssembly.Interop', 'MonoWebAssemblyJSRuntime', 'BeginInvokeDotNet'); - const dotNetDispatcherEndInvokeJSMethodHandle = findMethod('Mono.WebAssembly.Interop', 'Mono.WebAssembly.Interop', 'MonoWebAssemblyJSRuntime', 'EndInvokeJS'); + const dotNetDispatcherInvokeMethodHandle = bindStaticMethod('Mono.WebAssembly.Interop', 'Mono.WebAssembly.Interop.MonoWebAssemblyJSRuntime', 'InvokeDotNet'); + const dotNetDispatcherBeginInvokeMethodHandle = bindStaticMethod('Mono.WebAssembly.Interop', 'Mono.WebAssembly.Interop.MonoWebAssemblyJSRuntime', 'BeginInvokeDotNet'); + const dotNetDispatcherEndInvokeJSMethodHandle = bindStaticMethod('Mono.WebAssembly.Interop', 'Mono.WebAssembly.Interop.MonoWebAssemblyJSRuntime', 'EndInvokeJS'); DotNet.attachDispatcher({ beginInvokeDotNetFromJS: (callId: number, assemblyName: string | null, methodIdentifier: string, dotNetObjectId: any | null, argsJson: string): void => { @@ -362,30 +265,25 @@ function attachInteropInvoker(): void { ? dotNetObjectId.toString() : assemblyName; - monoPlatform.callMethod(dotNetDispatcherBeginInvokeMethodHandle, null, [ - callId ? monoPlatform.toDotNetString(callId.toString()) : null, - monoPlatform.toDotNetString(assemblyNameOrDotNetObjectId), - monoPlatform.toDotNetString(methodIdentifier), - monoPlatform.toDotNetString(argsJson), - ]); + dotNetDispatcherBeginInvokeMethodHandle( + callId ? callId.toString() : null, + assemblyNameOrDotNetObjectId, + methodIdentifier, + argsJson, + ); }, endInvokeJSFromDotNet: (asyncHandle, succeeded, serializedArgs): void => { - monoPlatform.callMethod( - dotNetDispatcherEndInvokeJSMethodHandle, - null, - [monoPlatform.toDotNetString(serializedArgs)] + dotNetDispatcherEndInvokeJSMethodHandle( + serializedArgs ); }, invokeDotNetFromJS: (assemblyName, methodIdentifier, dotNetObjectId, argsJson) => { - const resultJsonStringPtr = monoPlatform.callMethod(dotNetDispatcherInvokeMethodHandle, null, [ - assemblyName ? monoPlatform.toDotNetString(assemblyName) : null, - monoPlatform.toDotNetString(methodIdentifier), - dotNetObjectId ? monoPlatform.toDotNetString(dotNetObjectId.toString()) : null, - monoPlatform.toDotNetString(argsJson), - ]) as System_String; - return resultJsonStringPtr - ? monoPlatform.toJavaScriptString(resultJsonStringPtr) - : null; + return dotNetDispatcherInvokeMethodHandle( + assemblyName ? assemblyName : null, + methodIdentifier, + dotNetObjectId ? dotNetObjectId.toString() : null, + argsJson, + ) as string; }, }); } diff --git a/src/Components/Web.JS/src/Platform/Mono/MonoTypes.d.ts b/src/Components/Web.JS/src/Platform/Mono/MonoTypes.d.ts index 783af016f7..7d2f5c23bf 100644 --- a/src/Components/Web.JS/src/Platform/Mono/MonoTypes.d.ts +++ b/src/Components/Web.JS/src/Platform/Mono/MonoTypes.d.ts @@ -9,6 +9,8 @@ declare namespace Module { // These should probably be in @types/emscripten function FS_createPath(parent, path, canRead, canWrite); function FS_createDataFile(parent, name, data, canRead, canWrite, canOwn); + + function mono_bind_static_method(fqn: string): BoundStaticMethod; } // Emscripten declares these globals @@ -26,3 +28,7 @@ declare namespace MONO { var mono_wasm_runtime_is_ready: boolean; function mono_wasm_setenv (name: string, value: string): void; } + +// mono_bind_static_method allows arbitrary JS data types to be sent over the wire. However we are +// artifically limiting it to a subset of types that we actually use. +declare type BoundStaticMethod = (...args: (string | number | null)[]) => (string | number | null); diff --git a/src/Components/Web.JS/src/Platform/Platform.ts b/src/Components/Web.JS/src/Platform/Platform.ts index bb2f52113b..8d5daf454a 100644 --- a/src/Components/Web.JS/src/Platform/Platform.ts +++ b/src/Components/Web.JS/src/Platform/Platform.ts @@ -1,13 +1,9 @@ export interface Platform { start(loadAssemblyUrls: string[]): Promise; - callEntryPoint(assemblyName: string, entrypointMethod: string, args: (System_Object | null)[]); - findMethod(assemblyName: string, namespace: string, className: string, methodName: string): MethodHandle; - callMethod(method: MethodHandle, target: System_Object | null, args: (System_Object | null)[]): System_Object; + callEntryPoint(assemblyName: string): void; toJavaScriptString(dotNetString: System_String): string; - toDotNetString(javaScriptString: string): System_String; - toUint8Array(array: System_Array): Uint8Array; getArrayLength(array: System_Array): number; diff --git a/src/Components/Web.JS/src/Rendering/BrowserRenderer.ts b/src/Components/Web.JS/src/Rendering/BrowserRenderer.ts index 67a01446b9..6596741509 100644 --- a/src/Components/Web.JS/src/Rendering/BrowserRenderer.ts +++ b/src/Components/Web.JS/src/Rendering/BrowserRenderer.ts @@ -372,7 +372,7 @@ export class BrowserRenderer { } case 'OPTION': { const value = attributeFrame ? frameReader.attributeValue(attributeFrame) : null; - if (value) { + if (value || value === '') { element.setAttribute('value', value); } else { element.removeAttribute('value'); diff --git a/src/Components/Web.JS/src/Rendering/LogicalElements.ts b/src/Components/Web.JS/src/Rendering/LogicalElements.ts index 577c815f58..5b4aac4295 100644 --- a/src/Components/Web.JS/src/Rendering/LogicalElements.ts +++ b/src/Components/Web.JS/src/Rendering/LogicalElements.ts @@ -54,13 +54,14 @@ export function toLogicalRootCommentElement(start: Comment, end: Comment): Logic const parentLogicalElement = toLogicalElement(parent, /* allow existing contents */ true); const children = getLogicalChildrenArray(parentLogicalElement); Array.from(parent.childNodes).forEach(n => children.push(n as unknown as LogicalElement)); + start[logicalParentPropname] = parentLogicalElement; // We might not have an end comment in the case of non-prerendered components. if (end) { start[logicalEndSiblingPropname] = end; - toLogicalElement(end, /* allowExistingcontents */ true); + toLogicalElement(end); } - return toLogicalElement(start, /* allowExistingContents */ true); + return toLogicalElement(start); } export function toLogicalElement(element: Node, allowExistingContents?: boolean): LogicalElement { @@ -71,7 +72,10 @@ export function toLogicalElement(element: Node, allowExistingContents?: boolean) throw new Error('New logical elements must start empty, or allowExistingContents must be true'); } - element[logicalChildrenPropname] = []; + if (!(logicalChildrenPropname in element)) { // If it's already a logical element, leave it alone + element[logicalChildrenPropname] = []; + } + return element as unknown as LogicalElement; } diff --git a/src/Components/Web.JS/src/Services/NavigationManager.ts b/src/Components/Web.JS/src/Services/NavigationManager.ts index 5f217d3659..2fca100f83 100644 --- a/src/Components/Web.JS/src/Services/NavigationManager.ts +++ b/src/Components/Web.JS/src/Services/NavigationManager.ts @@ -1,4 +1,4 @@ -import '@dotnet/jsinterop'; +import '@microsoft/dotnet-js-interop'; import { resetScrollAfterNextBatch } from '../Rendering/Renderer'; import { EventDelegator } from '../Rendering/EventDelegator'; @@ -81,7 +81,7 @@ export function navigateTo(uri: string, forceLoad: boolean) { } else if (forceLoad && location.href === uri) { // Force-loading the same URL you're already on requires special handling to avoid // triggering browser-specific behavior issues. - // For details about what this fixes and why, see https://github.com/aspnet/AspNetCore/pull/10839 + // For details about what this fixes and why, see https://github.com/dotnet/aspnetcore/pull/10839 const temporaryUri = uri + '?'; history.replaceState(null, '', temporaryUri); location.replace(uri); diff --git a/src/Components/Web.JS/tests/RenderQueue.test.ts b/src/Components/Web.JS/tests/RenderQueue.test.ts index 81e283fc0d..5936d31a01 100644 --- a/src/Components/Web.JS/tests/RenderQueue.test.ts +++ b/src/Components/Web.JS/tests/RenderQueue.test.ts @@ -2,7 +2,7 @@ import { RenderQueue } from '../src/Platform/Circuits/RenderQueue'; import { NullLogger } from '../src/Platform/Logging/Loggers'; -import * as signalR from '@aspnet/signalr'; +import * as signalR from '@microsoft/signalr'; jest.mock('../src/Rendering/Renderer', () => ({ renderBatch: jest.fn() diff --git a/src/Components/Web.JS/yarn.lock b/src/Components/Web.JS/yarn.lock index 6fcffaae8a..4bbabdcfc8 100644 --- a/src/Components/Web.JS/yarn.lock +++ b/src/Components/Web.JS/yarn.lock @@ -2,14 +2,6 @@ # yarn lockfile v1 -"@aspnet/signalr-protocol-msgpack@link:../../SignalR/clients/ts/signalr-protocol-msgpack": - version "0.0.0" - uid "" - -"@aspnet/signalr@link:../../SignalR/clients/ts/signalr": - version "0.0.0" - uid "" - "@babel/code-frame@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.0.0.tgz#06e2ab19bdb535385559aabb5ba59729482800f8" @@ -152,10 +144,6 @@ exec-sh "^0.3.2" minimist "^1.2.0" -"@dotnet/jsinterop@https://dotnet.myget.org/F/aspnetcore-dev/npm/@dotnet/jsinterop/-/@dotnet/jsinterop-3.0.0-preview9.19415.3.tgz": - version "3.0.0-preview9.19415.3" - resolved "https://dotnet.myget.org/F/aspnetcore-dev/npm/@dotnet/jsinterop/-/@dotnet/jsinterop-3.0.0-preview9.19415.3.tgz#f44f482897c612e8d174b8f6d8795d2cda75d643" - "@jest/console@^24.7.1": version "24.7.1" resolved "https://registry.yarnpkg.com/@jest/console/-/console-24.7.1.tgz#32a9e42535a97aedfe037e725bd67e954b459545" @@ -302,6 +290,14 @@ "@types/istanbul-reports" "^1.1.1" "@types/yargs" "^12.0.9" +"@microsoft/dotnet-js-interop@https://dotnet.myget.org/F/aspnetcore-dev/npm/@microsoft/dotnet-js-interop/-/@microsoft/dotnet-js-interop-5.0.0-alpha1.19572.2.tgz": + version "5.0.0-alpha1.19572.2" + resolved "https://dotnet.myget.org/F/aspnetcore-dev/npm/@microsoft/dotnet-js-interop/-/@microsoft/dotnet-js-interop-5.0.0-alpha1.19572.2.tgz#8abd8d315f2304ffa441d9fb42bd5a571969e9a0" + +"@microsoft/signalr-protocol-msgpack@link:../../SignalR/clients/ts/signalr-protocol-msgpack": + version "0.0.0" + uid "" + "@microsoft/signalr@link:../../SignalR/clients/ts/signalr": version "0.0.0" uid "" @@ -611,6 +607,13 @@ abbrev@1: resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== +abort-controller@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" + integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== + dependencies: + event-target-shim "^5.0.0" + acorn-globals@^4.1.0: version "4.3.0" resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-4.3.0.tgz#e3b6f8da3c1552a95ae627571f7dd6923bb54103" @@ -1661,6 +1664,11 @@ es-to-primitive@^1.2.0: is-date-object "^1.0.1" is-symbol "^1.0.2" +es6-denodeify@^0.1.1: + version "0.1.5" + resolved "https://registry.yarnpkg.com/es6-denodeify/-/es6-denodeify-0.1.5.tgz#31d4d5fe9c5503e125460439310e16a2a3f39c1f" + integrity sha1-MdTV/pxVA+ElRgQ5MQ4WoqPznB8= + escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" @@ -1789,6 +1797,11 @@ esutils@^2.0.2: resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" integrity sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs= +event-target-shim@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" + integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== + events@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/events/-/events-3.0.0.tgz#9a0a0dfaf62893d92b875b8f2698ca4114973e88" @@ -1939,6 +1952,14 @@ fb-watchman@^2.0.0: dependencies: bser "^2.0.0" +fetch-cookie@^0.7.3: + version "0.7.3" + resolved "https://registry.yarnpkg.com/fetch-cookie/-/fetch-cookie-0.7.3.tgz#b8d023f421dd2b2f4a0eca9cd7318a967ed4eed8" + integrity sha512-rZPkLnI8x5V+zYAiz8QonAHsTb4BY+iFowFBI1RFn0zrO343AVp9X7/yUj/9wL6Ef/8fLls8b/vGtzUvmyAUGA== + dependencies: + es6-denodeify "^0.1.1" + tough-cookie "^2.3.3" + figgy-pudding@^3.5.1: version "3.5.1" resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.1.tgz#862470112901c727a0e495a80744bd5baa1d6790" @@ -3580,6 +3601,11 @@ nice-try@^1.0.4: resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== +node-fetch@^2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd" + integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA== + node-int64@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" @@ -4269,7 +4295,7 @@ request-promise-native@^1.0.5: stealthy-require "^1.1.1" tough-cookie "^2.3.3" -request@^2.87.0, request@^2.88.0: +request@^2.87.0: version "2.88.0" resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg== diff --git a/src/Components/Web/ref/Microsoft.AspNetCore.Components.Web.csproj b/src/Components/Web/ref/Microsoft.AspNetCore.Components.Web.csproj index 18b1e18b02..bb71916133 100644 --- a/src/Components/Web/ref/Microsoft.AspNetCore.Components.Web.csproj +++ b/src/Components/Web/ref/Microsoft.AspNetCore.Components.Web.csproj @@ -2,6 +2,7 @@ netstandard2.0;$(DefaultNetCoreTargetFramework) + $(DefaultNetCoreTargetFramework) diff --git a/src/Components/Web/ref/Microsoft.AspNetCore.Components.Web.netcoreapp.cs b/src/Components/Web/ref/Microsoft.AspNetCore.Components.Web.netcoreapp.cs index 4928dc03fd..15e4665454 100644 --- a/src/Components/Web/ref/Microsoft.AspNetCore.Components.Web.netcoreapp.cs +++ b/src/Components/Web/ref/Microsoft.AspNetCore.Components.Web.netcoreapp.cs @@ -7,12 +7,12 @@ namespace Microsoft.AspNetCore.Components public sealed partial class BindInputElementAttribute : System.Attribute { public BindInputElementAttribute(string type, string suffix, string valueAttribute, string changeAttribute, bool isInvariantCulture, string format) { } - public string ChangeAttribute { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public string Format { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public bool IsInvariantCulture { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public string Suffix { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public string Type { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public string ValueAttribute { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public string ChangeAttribute { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public string Format { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public bool IsInvariantCulture { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public string Suffix { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public string Type { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public string ValueAttribute { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } } } namespace Microsoft.AspNetCore.Components.Forms @@ -26,19 +26,19 @@ namespace Microsoft.AspNetCore.Components.Forms { public EditForm() { } [Microsoft.AspNetCore.Components.ParameterAttribute(CaptureUnmatchedValues=true)] - public System.Collections.Generic.IReadOnlyDictionary AdditionalAttributes { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public System.Collections.Generic.IReadOnlyDictionary AdditionalAttributes { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } [Microsoft.AspNetCore.Components.ParameterAttribute] - public Microsoft.AspNetCore.Components.RenderFragment ChildContent { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.AspNetCore.Components.RenderFragment ChildContent { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } [Microsoft.AspNetCore.Components.ParameterAttribute] - public Microsoft.AspNetCore.Components.Forms.EditContext EditContext { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.AspNetCore.Components.Forms.EditContext EditContext { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } [Microsoft.AspNetCore.Components.ParameterAttribute] - public object Model { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public object Model { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } [Microsoft.AspNetCore.Components.ParameterAttribute] - public Microsoft.AspNetCore.Components.EventCallback OnInvalidSubmit { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.AspNetCore.Components.EventCallback OnInvalidSubmit { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } [Microsoft.AspNetCore.Components.ParameterAttribute] - public Microsoft.AspNetCore.Components.EventCallback OnSubmit { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.AspNetCore.Components.EventCallback OnSubmit { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } [Microsoft.AspNetCore.Components.ParameterAttribute] - public Microsoft.AspNetCore.Components.EventCallback OnValidSubmit { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.AspNetCore.Components.EventCallback OnValidSubmit { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } protected override void BuildRenderTree(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder builder) { } protected override void OnParametersSet() { } } @@ -46,18 +46,18 @@ namespace Microsoft.AspNetCore.Components.Forms { protected InputBase() { } [Microsoft.AspNetCore.Components.ParameterAttribute(CaptureUnmatchedValues=true)] - public System.Collections.Generic.IReadOnlyDictionary AdditionalAttributes { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public System.Collections.Generic.IReadOnlyDictionary AdditionalAttributes { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } protected string CssClass { get { throw null; } } protected TValue CurrentValue { get { throw null; } set { } } protected string CurrentValueAsString { get { throw null; } set { } } - protected Microsoft.AspNetCore.Components.Forms.EditContext EditContext { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - protected Microsoft.AspNetCore.Components.Forms.FieldIdentifier FieldIdentifier { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + protected Microsoft.AspNetCore.Components.Forms.EditContext EditContext { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + protected Microsoft.AspNetCore.Components.Forms.FieldIdentifier FieldIdentifier { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } [Microsoft.AspNetCore.Components.ParameterAttribute] - public TValue Value { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public TValue Value { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } [Microsoft.AspNetCore.Components.ParameterAttribute] - public Microsoft.AspNetCore.Components.EventCallback ValueChanged { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.AspNetCore.Components.EventCallback ValueChanged { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } [Microsoft.AspNetCore.Components.ParameterAttribute] - public System.Linq.Expressions.Expression> ValueExpression { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public System.Linq.Expressions.Expression> ValueExpression { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } protected virtual void Dispose(bool disposing) { } protected virtual string FormatValueAsString(TValue value) { throw null; } public override System.Threading.Tasks.Task SetParametersAsync(Microsoft.AspNetCore.Components.ParameterView parameters) { throw null; } @@ -74,7 +74,7 @@ namespace Microsoft.AspNetCore.Components.Forms { public InputDate() { } [Microsoft.AspNetCore.Components.ParameterAttribute] - public string ParsingErrorMessage { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public string ParsingErrorMessage { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } protected override void BuildRenderTree(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder builder) { } protected override string FormatValueAsString(TValue value) { throw null; } protected override bool TryParseValueFromString(string value, out TValue result, out string validationErrorMessage) { throw null; } @@ -83,7 +83,7 @@ namespace Microsoft.AspNetCore.Components.Forms { public InputNumber() { } [Microsoft.AspNetCore.Components.ParameterAttribute] - public string ParsingErrorMessage { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public string ParsingErrorMessage { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } protected override void BuildRenderTree(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder builder) { } protected override string FormatValueAsString(TValue value) { throw null; } protected override bool TryParseValueFromString(string value, out TValue result, out string validationErrorMessage) { throw null; } @@ -92,7 +92,7 @@ namespace Microsoft.AspNetCore.Components.Forms { public InputSelect() { } [Microsoft.AspNetCore.Components.ParameterAttribute] - public Microsoft.AspNetCore.Components.RenderFragment ChildContent { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.AspNetCore.Components.RenderFragment ChildContent { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } protected override void BuildRenderTree(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder builder) { } protected override bool TryParseValueFromString(string value, out TValue result, out string validationErrorMessage) { throw null; } } @@ -112,9 +112,9 @@ namespace Microsoft.AspNetCore.Components.Forms { public ValidationMessage() { } [Microsoft.AspNetCore.Components.ParameterAttribute(CaptureUnmatchedValues=true)] - public System.Collections.Generic.IReadOnlyDictionary AdditionalAttributes { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public System.Collections.Generic.IReadOnlyDictionary AdditionalAttributes { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } [Microsoft.AspNetCore.Components.ParameterAttribute] - public System.Linq.Expressions.Expression> For { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public System.Linq.Expressions.Expression> For { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } protected override void BuildRenderTree(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder builder) { } protected virtual void Dispose(bool disposing) { } protected override void OnParametersSet() { } @@ -124,9 +124,9 @@ namespace Microsoft.AspNetCore.Components.Forms { public ValidationSummary() { } [Microsoft.AspNetCore.Components.ParameterAttribute(CaptureUnmatchedValues=true)] - public System.Collections.Generic.IReadOnlyDictionary AdditionalAttributes { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public System.Collections.Generic.IReadOnlyDictionary AdditionalAttributes { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } [Microsoft.AspNetCore.Components.ParameterAttribute] - public object Model { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public object Model { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } protected override void BuildRenderTree(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder builder) { } protected virtual void Dispose(bool disposing) { } protected override void OnParametersSet() { } @@ -138,10 +138,10 @@ namespace Microsoft.AspNetCore.Components.RenderTree public sealed partial class WebEventDescriptor { public WebEventDescriptor() { } - public int BrowserRendererId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public string EventArgsType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public Microsoft.AspNetCore.Components.RenderTree.EventFieldInfo EventFieldInfo { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public ulong EventHandlerId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public int BrowserRendererId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public string EventArgsType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public Microsoft.AspNetCore.Components.RenderTree.EventFieldInfo EventFieldInfo { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public ulong EventHandlerId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } } namespace Microsoft.AspNetCore.Components.Routing @@ -150,14 +150,14 @@ namespace Microsoft.AspNetCore.Components.Routing { public NavLink() { } [Microsoft.AspNetCore.Components.ParameterAttribute] - public string ActiveClass { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public string ActiveClass { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } [Microsoft.AspNetCore.Components.ParameterAttribute(CaptureUnmatchedValues=true)] - public System.Collections.Generic.IReadOnlyDictionary AdditionalAttributes { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public System.Collections.Generic.IReadOnlyDictionary AdditionalAttributes { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } [Microsoft.AspNetCore.Components.ParameterAttribute] - public Microsoft.AspNetCore.Components.RenderFragment ChildContent { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - protected string CssClass { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.AspNetCore.Components.RenderFragment ChildContent { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + protected string CssClass { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } [Microsoft.AspNetCore.Components.ParameterAttribute] - public Microsoft.AspNetCore.Components.Routing.NavLinkMatch Match { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.AspNetCore.Components.Routing.NavLinkMatch Match { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } protected override void BuildRenderTree(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder builder) { } public void Dispose() { } protected override void OnInitialized() { } @@ -193,36 +193,36 @@ namespace Microsoft.AspNetCore.Components.Web public partial class ClipboardEventArgs : System.EventArgs { public ClipboardEventArgs() { } - public string Type { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public string Type { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } public partial class DataTransfer { public DataTransfer() { } - public string DropEffect { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public string EffectAllowed { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public string[] Files { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public Microsoft.AspNetCore.Components.Web.DataTransferItem[] Items { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public string[] Types { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public string DropEffect { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public string EffectAllowed { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public string[] Files { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public Microsoft.AspNetCore.Components.Web.DataTransferItem[] Items { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public string[] Types { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } public partial class DataTransferItem { public DataTransferItem() { } - public string Kind { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public string Type { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public string Kind { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public string Type { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } public partial class DragEventArgs : Microsoft.AspNetCore.Components.Web.MouseEventArgs { public DragEventArgs() { } - public Microsoft.AspNetCore.Components.Web.DataTransfer DataTransfer { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.AspNetCore.Components.Web.DataTransfer DataTransfer { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } public partial class ErrorEventArgs : System.EventArgs { public ErrorEventArgs() { } - public int Colno { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public string Filename { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public int Lineno { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public string Message { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public string Type { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public int Colno { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public string Filename { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public int Lineno { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public string Message { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public string Type { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } [Microsoft.AspNetCore.Components.EventHandlerAttribute("onabort", typeof(Microsoft.AspNetCore.Components.Web.ProgressEventArgs), true, true)] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onactivate", typeof(System.EventArgs), true, true)] @@ -321,80 +321,80 @@ namespace Microsoft.AspNetCore.Components.Web public partial class FocusEventArgs : System.EventArgs { public FocusEventArgs() { } - public string Type { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public string Type { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } public partial class KeyboardEventArgs : System.EventArgs { public KeyboardEventArgs() { } - public bool AltKey { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public string Code { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public bool CtrlKey { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public string Key { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public float Location { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public bool MetaKey { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public bool Repeat { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public bool ShiftKey { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public string Type { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public bool AltKey { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public string Code { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public bool CtrlKey { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public string Key { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public float Location { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public bool MetaKey { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public bool Repeat { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public bool ShiftKey { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public string Type { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } public partial class MouseEventArgs : System.EventArgs { public MouseEventArgs() { } - public bool AltKey { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public long Button { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public long Buttons { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public double ClientX { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public double ClientY { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public bool CtrlKey { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public long Detail { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public bool MetaKey { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public double ScreenX { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public double ScreenY { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public bool ShiftKey { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public string Type { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public bool AltKey { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public long Button { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public long Buttons { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public double ClientX { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public double ClientY { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public bool CtrlKey { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public long Detail { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public bool MetaKey { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public double ScreenX { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public double ScreenY { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public bool ShiftKey { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public string Type { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } public partial class PointerEventArgs : Microsoft.AspNetCore.Components.Web.MouseEventArgs { public PointerEventArgs() { } - public float Height { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public bool IsPrimary { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public long PointerId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public string PointerType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public float Pressure { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public float TiltX { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public float TiltY { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public float Width { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public float Height { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public bool IsPrimary { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public long PointerId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public string PointerType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public float Pressure { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public float TiltX { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public float TiltY { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public float Width { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } public partial class ProgressEventArgs : System.EventArgs { public ProgressEventArgs() { } - public bool LengthComputable { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public long Loaded { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public long Total { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public string Type { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public bool LengthComputable { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public long Loaded { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public long Total { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public string Type { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } public partial class TouchEventArgs : System.EventArgs { public TouchEventArgs() { } - public bool AltKey { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public Microsoft.AspNetCore.Components.Web.TouchPoint[] ChangedTouches { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public bool CtrlKey { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public long Detail { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public bool MetaKey { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public bool ShiftKey { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public Microsoft.AspNetCore.Components.Web.TouchPoint[] TargetTouches { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public Microsoft.AspNetCore.Components.Web.TouchPoint[] Touches { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public string Type { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public bool AltKey { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public Microsoft.AspNetCore.Components.Web.TouchPoint[] ChangedTouches { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public bool CtrlKey { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public long Detail { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public bool MetaKey { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public bool ShiftKey { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public Microsoft.AspNetCore.Components.Web.TouchPoint[] TargetTouches { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public Microsoft.AspNetCore.Components.Web.TouchPoint[] Touches { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public string Type { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } public partial class TouchPoint { public TouchPoint() { } - public double ClientX { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public double ClientY { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public long Identifier { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public double PageX { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public double PageY { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public double ScreenX { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public double ScreenY { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public double ClientX { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public double ClientY { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public long Identifier { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public double PageX { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public double PageY { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public double ScreenX { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public double ScreenY { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } public static partial class WebEventCallbackFactoryEventArgsExtensions { @@ -427,9 +427,9 @@ namespace Microsoft.AspNetCore.Components.Web public partial class WheelEventArgs : Microsoft.AspNetCore.Components.Web.MouseEventArgs { public WheelEventArgs() { } - public long DeltaMode { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public double DeltaX { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public double DeltaY { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public double DeltaZ { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public long DeltaMode { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public double DeltaX { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public double DeltaY { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public double DeltaZ { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } } diff --git a/src/Components/Web/ref/Microsoft.AspNetCore.Components.Web.netstandard2.0.cs b/src/Components/Web/ref/Microsoft.AspNetCore.Components.Web.netstandard2.0.cs index 4928dc03fd..15e4665454 100644 --- a/src/Components/Web/ref/Microsoft.AspNetCore.Components.Web.netstandard2.0.cs +++ b/src/Components/Web/ref/Microsoft.AspNetCore.Components.Web.netstandard2.0.cs @@ -7,12 +7,12 @@ namespace Microsoft.AspNetCore.Components public sealed partial class BindInputElementAttribute : System.Attribute { public BindInputElementAttribute(string type, string suffix, string valueAttribute, string changeAttribute, bool isInvariantCulture, string format) { } - public string ChangeAttribute { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public string Format { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public bool IsInvariantCulture { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public string Suffix { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public string Type { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public string ValueAttribute { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public string ChangeAttribute { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public string Format { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public bool IsInvariantCulture { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public string Suffix { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public string Type { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public string ValueAttribute { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } } } namespace Microsoft.AspNetCore.Components.Forms @@ -26,19 +26,19 @@ namespace Microsoft.AspNetCore.Components.Forms { public EditForm() { } [Microsoft.AspNetCore.Components.ParameterAttribute(CaptureUnmatchedValues=true)] - public System.Collections.Generic.IReadOnlyDictionary AdditionalAttributes { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public System.Collections.Generic.IReadOnlyDictionary AdditionalAttributes { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } [Microsoft.AspNetCore.Components.ParameterAttribute] - public Microsoft.AspNetCore.Components.RenderFragment ChildContent { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.AspNetCore.Components.RenderFragment ChildContent { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } [Microsoft.AspNetCore.Components.ParameterAttribute] - public Microsoft.AspNetCore.Components.Forms.EditContext EditContext { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.AspNetCore.Components.Forms.EditContext EditContext { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } [Microsoft.AspNetCore.Components.ParameterAttribute] - public object Model { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public object Model { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } [Microsoft.AspNetCore.Components.ParameterAttribute] - public Microsoft.AspNetCore.Components.EventCallback OnInvalidSubmit { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.AspNetCore.Components.EventCallback OnInvalidSubmit { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } [Microsoft.AspNetCore.Components.ParameterAttribute] - public Microsoft.AspNetCore.Components.EventCallback OnSubmit { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.AspNetCore.Components.EventCallback OnSubmit { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } [Microsoft.AspNetCore.Components.ParameterAttribute] - public Microsoft.AspNetCore.Components.EventCallback OnValidSubmit { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.AspNetCore.Components.EventCallback OnValidSubmit { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } protected override void BuildRenderTree(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder builder) { } protected override void OnParametersSet() { } } @@ -46,18 +46,18 @@ namespace Microsoft.AspNetCore.Components.Forms { protected InputBase() { } [Microsoft.AspNetCore.Components.ParameterAttribute(CaptureUnmatchedValues=true)] - public System.Collections.Generic.IReadOnlyDictionary AdditionalAttributes { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public System.Collections.Generic.IReadOnlyDictionary AdditionalAttributes { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } protected string CssClass { get { throw null; } } protected TValue CurrentValue { get { throw null; } set { } } protected string CurrentValueAsString { get { throw null; } set { } } - protected Microsoft.AspNetCore.Components.Forms.EditContext EditContext { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - protected Microsoft.AspNetCore.Components.Forms.FieldIdentifier FieldIdentifier { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + protected Microsoft.AspNetCore.Components.Forms.EditContext EditContext { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + protected Microsoft.AspNetCore.Components.Forms.FieldIdentifier FieldIdentifier { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } [Microsoft.AspNetCore.Components.ParameterAttribute] - public TValue Value { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public TValue Value { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } [Microsoft.AspNetCore.Components.ParameterAttribute] - public Microsoft.AspNetCore.Components.EventCallback ValueChanged { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.AspNetCore.Components.EventCallback ValueChanged { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } [Microsoft.AspNetCore.Components.ParameterAttribute] - public System.Linq.Expressions.Expression> ValueExpression { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public System.Linq.Expressions.Expression> ValueExpression { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } protected virtual void Dispose(bool disposing) { } protected virtual string FormatValueAsString(TValue value) { throw null; } public override System.Threading.Tasks.Task SetParametersAsync(Microsoft.AspNetCore.Components.ParameterView parameters) { throw null; } @@ -74,7 +74,7 @@ namespace Microsoft.AspNetCore.Components.Forms { public InputDate() { } [Microsoft.AspNetCore.Components.ParameterAttribute] - public string ParsingErrorMessage { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public string ParsingErrorMessage { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } protected override void BuildRenderTree(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder builder) { } protected override string FormatValueAsString(TValue value) { throw null; } protected override bool TryParseValueFromString(string value, out TValue result, out string validationErrorMessage) { throw null; } @@ -83,7 +83,7 @@ namespace Microsoft.AspNetCore.Components.Forms { public InputNumber() { } [Microsoft.AspNetCore.Components.ParameterAttribute] - public string ParsingErrorMessage { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public string ParsingErrorMessage { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } protected override void BuildRenderTree(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder builder) { } protected override string FormatValueAsString(TValue value) { throw null; } protected override bool TryParseValueFromString(string value, out TValue result, out string validationErrorMessage) { throw null; } @@ -92,7 +92,7 @@ namespace Microsoft.AspNetCore.Components.Forms { public InputSelect() { } [Microsoft.AspNetCore.Components.ParameterAttribute] - public Microsoft.AspNetCore.Components.RenderFragment ChildContent { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.AspNetCore.Components.RenderFragment ChildContent { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } protected override void BuildRenderTree(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder builder) { } protected override bool TryParseValueFromString(string value, out TValue result, out string validationErrorMessage) { throw null; } } @@ -112,9 +112,9 @@ namespace Microsoft.AspNetCore.Components.Forms { public ValidationMessage() { } [Microsoft.AspNetCore.Components.ParameterAttribute(CaptureUnmatchedValues=true)] - public System.Collections.Generic.IReadOnlyDictionary AdditionalAttributes { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public System.Collections.Generic.IReadOnlyDictionary AdditionalAttributes { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } [Microsoft.AspNetCore.Components.ParameterAttribute] - public System.Linq.Expressions.Expression> For { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public System.Linq.Expressions.Expression> For { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } protected override void BuildRenderTree(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder builder) { } protected virtual void Dispose(bool disposing) { } protected override void OnParametersSet() { } @@ -124,9 +124,9 @@ namespace Microsoft.AspNetCore.Components.Forms { public ValidationSummary() { } [Microsoft.AspNetCore.Components.ParameterAttribute(CaptureUnmatchedValues=true)] - public System.Collections.Generic.IReadOnlyDictionary AdditionalAttributes { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public System.Collections.Generic.IReadOnlyDictionary AdditionalAttributes { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } [Microsoft.AspNetCore.Components.ParameterAttribute] - public object Model { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public object Model { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } protected override void BuildRenderTree(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder builder) { } protected virtual void Dispose(bool disposing) { } protected override void OnParametersSet() { } @@ -138,10 +138,10 @@ namespace Microsoft.AspNetCore.Components.RenderTree public sealed partial class WebEventDescriptor { public WebEventDescriptor() { } - public int BrowserRendererId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public string EventArgsType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public Microsoft.AspNetCore.Components.RenderTree.EventFieldInfo EventFieldInfo { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public ulong EventHandlerId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public int BrowserRendererId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public string EventArgsType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public Microsoft.AspNetCore.Components.RenderTree.EventFieldInfo EventFieldInfo { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public ulong EventHandlerId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } } namespace Microsoft.AspNetCore.Components.Routing @@ -150,14 +150,14 @@ namespace Microsoft.AspNetCore.Components.Routing { public NavLink() { } [Microsoft.AspNetCore.Components.ParameterAttribute] - public string ActiveClass { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public string ActiveClass { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } [Microsoft.AspNetCore.Components.ParameterAttribute(CaptureUnmatchedValues=true)] - public System.Collections.Generic.IReadOnlyDictionary AdditionalAttributes { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public System.Collections.Generic.IReadOnlyDictionary AdditionalAttributes { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } [Microsoft.AspNetCore.Components.ParameterAttribute] - public Microsoft.AspNetCore.Components.RenderFragment ChildContent { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - protected string CssClass { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.AspNetCore.Components.RenderFragment ChildContent { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + protected string CssClass { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } [Microsoft.AspNetCore.Components.ParameterAttribute] - public Microsoft.AspNetCore.Components.Routing.NavLinkMatch Match { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.AspNetCore.Components.Routing.NavLinkMatch Match { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } protected override void BuildRenderTree(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder builder) { } public void Dispose() { } protected override void OnInitialized() { } @@ -193,36 +193,36 @@ namespace Microsoft.AspNetCore.Components.Web public partial class ClipboardEventArgs : System.EventArgs { public ClipboardEventArgs() { } - public string Type { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public string Type { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } public partial class DataTransfer { public DataTransfer() { } - public string DropEffect { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public string EffectAllowed { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public string[] Files { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public Microsoft.AspNetCore.Components.Web.DataTransferItem[] Items { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public string[] Types { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public string DropEffect { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public string EffectAllowed { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public string[] Files { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public Microsoft.AspNetCore.Components.Web.DataTransferItem[] Items { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public string[] Types { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } public partial class DataTransferItem { public DataTransferItem() { } - public string Kind { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public string Type { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public string Kind { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public string Type { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } public partial class DragEventArgs : Microsoft.AspNetCore.Components.Web.MouseEventArgs { public DragEventArgs() { } - public Microsoft.AspNetCore.Components.Web.DataTransfer DataTransfer { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.AspNetCore.Components.Web.DataTransfer DataTransfer { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } public partial class ErrorEventArgs : System.EventArgs { public ErrorEventArgs() { } - public int Colno { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public string Filename { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public int Lineno { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public string Message { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public string Type { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public int Colno { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public string Filename { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public int Lineno { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public string Message { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public string Type { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } [Microsoft.AspNetCore.Components.EventHandlerAttribute("onabort", typeof(Microsoft.AspNetCore.Components.Web.ProgressEventArgs), true, true)] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onactivate", typeof(System.EventArgs), true, true)] @@ -321,80 +321,80 @@ namespace Microsoft.AspNetCore.Components.Web public partial class FocusEventArgs : System.EventArgs { public FocusEventArgs() { } - public string Type { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public string Type { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } public partial class KeyboardEventArgs : System.EventArgs { public KeyboardEventArgs() { } - public bool AltKey { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public string Code { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public bool CtrlKey { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public string Key { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public float Location { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public bool MetaKey { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public bool Repeat { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public bool ShiftKey { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public string Type { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public bool AltKey { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public string Code { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public bool CtrlKey { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public string Key { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public float Location { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public bool MetaKey { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public bool Repeat { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public bool ShiftKey { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public string Type { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } public partial class MouseEventArgs : System.EventArgs { public MouseEventArgs() { } - public bool AltKey { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public long Button { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public long Buttons { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public double ClientX { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public double ClientY { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public bool CtrlKey { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public long Detail { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public bool MetaKey { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public double ScreenX { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public double ScreenY { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public bool ShiftKey { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public string Type { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public bool AltKey { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public long Button { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public long Buttons { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public double ClientX { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public double ClientY { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public bool CtrlKey { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public long Detail { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public bool MetaKey { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public double ScreenX { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public double ScreenY { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public bool ShiftKey { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public string Type { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } public partial class PointerEventArgs : Microsoft.AspNetCore.Components.Web.MouseEventArgs { public PointerEventArgs() { } - public float Height { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public bool IsPrimary { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public long PointerId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public string PointerType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public float Pressure { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public float TiltX { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public float TiltY { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public float Width { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public float Height { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public bool IsPrimary { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public long PointerId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public string PointerType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public float Pressure { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public float TiltX { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public float TiltY { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public float Width { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } public partial class ProgressEventArgs : System.EventArgs { public ProgressEventArgs() { } - public bool LengthComputable { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public long Loaded { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public long Total { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public string Type { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public bool LengthComputable { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public long Loaded { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public long Total { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public string Type { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } public partial class TouchEventArgs : System.EventArgs { public TouchEventArgs() { } - public bool AltKey { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public Microsoft.AspNetCore.Components.Web.TouchPoint[] ChangedTouches { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public bool CtrlKey { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public long Detail { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public bool MetaKey { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public bool ShiftKey { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public Microsoft.AspNetCore.Components.Web.TouchPoint[] TargetTouches { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public Microsoft.AspNetCore.Components.Web.TouchPoint[] Touches { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public string Type { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public bool AltKey { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public Microsoft.AspNetCore.Components.Web.TouchPoint[] ChangedTouches { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public bool CtrlKey { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public long Detail { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public bool MetaKey { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public bool ShiftKey { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public Microsoft.AspNetCore.Components.Web.TouchPoint[] TargetTouches { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public Microsoft.AspNetCore.Components.Web.TouchPoint[] Touches { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public string Type { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } public partial class TouchPoint { public TouchPoint() { } - public double ClientX { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public double ClientY { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public long Identifier { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public double PageX { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public double PageY { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public double ScreenX { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public double ScreenY { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public double ClientX { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public double ClientY { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public long Identifier { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public double PageX { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public double PageY { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public double ScreenX { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public double ScreenY { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } public static partial class WebEventCallbackFactoryEventArgsExtensions { @@ -427,9 +427,9 @@ namespace Microsoft.AspNetCore.Components.Web public partial class WheelEventArgs : Microsoft.AspNetCore.Components.Web.MouseEventArgs { public WheelEventArgs() { } - public long DeltaMode { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public double DeltaX { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public double DeltaY { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public double DeltaZ { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public long DeltaMode { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public double DeltaX { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public double DeltaY { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public double DeltaZ { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } } diff --git a/src/Components/Web/src/BindInputElementAttribute.cs b/src/Components/Web/src/BindInputElementAttribute.cs index 0cba1a8e84..f6169c0ca5 100644 --- a/src/Components/Web/src/BindInputElementAttribute.cs +++ b/src/Components/Web/src/BindInputElementAttribute.cs @@ -7,7 +7,7 @@ using System.Globalization; namespace Microsoft.AspNetCore.Components { /// - /// Configures options for binding subtypes of an HTML input element. + /// Configures options for binding subtypes of an HTML input element. /// [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)] public sealed class BindInputElementAttribute : Attribute @@ -15,7 +15,7 @@ namespace Microsoft.AspNetCore.Components /// /// Constructs an instance of . /// - /// The value of the element's type attribute. + /// The value of the element's type attribute. /// The suffix value. /// The name of the value attribute to be bound. /// The name of an attribute that will register an associated change event. @@ -46,7 +46,7 @@ namespace Microsoft.AspNetCore.Components } /// - /// Gets the value of the element's type attribute. + /// Gets the value of the element's type attribute. /// public string Type { get; } diff --git a/src/Components/Web/src/Forms/InputNumber.cs b/src/Components/Web/src/Forms/InputNumber.cs index 5b2a08a3b8..efdb53c905 100644 --- a/src/Components/Web/src/Forms/InputNumber.cs +++ b/src/Components/Web/src/Forms/InputNumber.cs @@ -9,7 +9,7 @@ namespace Microsoft.AspNetCore.Components.Forms { /// /// An input component for editing numeric values. - /// Supported numeric types are , , , , . + /// Supported numeric types are , , , , , . /// public class InputNumber : InputBase { @@ -22,6 +22,7 @@ namespace Microsoft.AspNetCore.Components.Forms var targetType = Nullable.GetUnderlyingType(typeof(TValue)) ?? typeof(TValue); if (targetType == typeof(int) || targetType == typeof(long) || + targetType == typeof(short) || targetType == typeof(float) || targetType == typeof(double) || targetType == typeof(decimal)) @@ -68,7 +69,7 @@ namespace Microsoft.AspNetCore.Components.Forms } /// - /// Formats the value as a string. Derived classes can override this to determine the formating used for CurrentValueAsString. + /// Formats the value as a string. Derived classes can override this to determine the formatting used for CurrentValueAsString. /// /// The value to format. /// A string representation of the value. @@ -86,6 +87,9 @@ namespace Microsoft.AspNetCore.Components.Forms case long @long: return BindConverter.FormatValue(@long, CultureInfo.InvariantCulture); + case short @short: + return BindConverter.FormatValue(@short, CultureInfo.InvariantCulture); + case float @float: return BindConverter.FormatValue(@float, CultureInfo.InvariantCulture); diff --git a/src/Components/Web/src/Forms/InputSelect.cs b/src/Components/Web/src/Forms/InputSelect.cs index b8cdbeab1f..e1a3ab0eae 100644 --- a/src/Components/Web/src/Forms/InputSelect.cs +++ b/src/Components/Web/src/Forms/InputSelect.cs @@ -17,6 +17,8 @@ namespace Microsoft.AspNetCore.Components.Forms /// [Parameter] public RenderFragment ChildContent { get; set; } + private readonly Type _nullableUnderlyingType = Nullable.GetUnderlyingType(typeof(TValue)); + /// protected override void BuildRenderTree(RenderTreeBuilder builder) { @@ -38,7 +40,7 @@ namespace Microsoft.AspNetCore.Components.Forms validationErrorMessage = null; return true; } - else if (typeof(TValue).IsEnum) + else if (typeof(TValue).IsEnum || (_nullableUnderlyingType != null && _nullableUnderlyingType.IsEnum)) { var success = BindConverter.TryConvertTo(value, CultureInfo.CurrentCulture, out var parsedValue); if (success) diff --git a/src/Components/Web/src/Forms/ValidationMessage.cs b/src/Components/Web/src/Forms/ValidationMessage.cs index d033fdba20..d15efd2d4c 100644 --- a/src/Components/Web/src/Forms/ValidationMessage.cs +++ b/src/Components/Web/src/Forms/ValidationMessage.cs @@ -80,11 +80,6 @@ namespace Microsoft.AspNetCore.Components.Forms } } - private void HandleValidationStateChanged(object sender, ValidationStateChangedEventArgs eventArgs) - { - StateHasChanged(); - } - protected virtual void Dispose(bool disposing) { } diff --git a/src/Components/Web/src/Forms/ValidationSummary.cs b/src/Components/Web/src/Forms/ValidationSummary.cs index 270f787176..4e168f35ba 100644 --- a/src/Components/Web/src/Forms/ValidationSummary.cs +++ b/src/Components/Web/src/Forms/ValidationSummary.cs @@ -92,11 +92,6 @@ namespace Microsoft.AspNetCore.Components.Forms } } - private void HandleValidationStateChanged(object sender, ValidationStateChangedEventArgs eventArgs) - { - StateHasChanged(); - } - protected virtual void Dispose(bool disposing) { } diff --git a/src/Components/Web/test/Forms/InputBaseTest.cs b/src/Components/Web/test/Forms/InputBaseTest.cs index ce1bf7bfa2..285361d538 100644 --- a/src/Components/Web/test/Forms/InputBaseTest.cs +++ b/src/Components/Web/test/Forms/InputBaseTest.cs @@ -212,7 +212,7 @@ namespace Microsoft.AspNetCore.Components.Forms }; var fieldIdentifier = FieldIdentifier.Create(() => model.StringProperty); - // Act/Assert: Initally, it's valid and unmodified + // Act/Assert: Initially, it's valid and unmodified var inputComponent = await RenderAndGetTestInputComponentAsync(rootComponent); Assert.Equal("valid", inputComponent.CssClass); // no Class was specified diff --git a/src/Components/Web/test/Forms/InputSelectTest.cs b/src/Components/Web/test/Forms/InputSelectTest.cs new file mode 100644 index 0000000000..7c49fe03be --- /dev/null +++ b/src/Components/Web/test/Forms/InputSelectTest.cs @@ -0,0 +1,153 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq; +using System.Linq.Expressions; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Components.Rendering; +using Microsoft.AspNetCore.Components.RenderTree; +using Microsoft.AspNetCore.Components.Test.Helpers; +using Xunit; + +namespace Microsoft.AspNetCore.Components.Forms +{ + public class InputSelectTest + { + [Fact] + public async Task ParsesCurrentValueWhenUsingNotNullableEnumWithNotEmptyValue() + { + // Arrange + var model = new TestModel(); + var rootComponent = new TestInputSelectHostComponent + { + EditContext = new EditContext(model), + ValueExpression = () => model.NotNullableEnum + }; + var inputSelectComponent = await RenderAndGetTestInputComponentAsync(rootComponent); + + // Act + inputSelectComponent.CurrentValueAsString = "Two"; + + // Assert + Assert.Equal(TestEnum.Two, inputSelectComponent.CurrentValue); + } + + [Fact] + public async Task ParsesCurrentValueWhenUsingNotNullableEnumWithEmptyValue() + { + // Arrange + var model = new TestModel(); + var rootComponent = new TestInputSelectHostComponent + { + EditContext = new EditContext(model), + ValueExpression = () => model.NotNullableEnum + }; + var inputSelectComponent = await RenderAndGetTestInputComponentAsync(rootComponent); + + // Act + inputSelectComponent.CurrentValueAsString = ""; + + // Assert + Assert.Equal(default, inputSelectComponent.CurrentValue); + } + + [Fact] + public async Task ParsesCurrentValueWhenUsingNullableEnumWithNotEmptyValue() + { + // Arrange + var model = new TestModel(); + var rootComponent = new TestInputSelectHostComponent + { + EditContext = new EditContext(model), + ValueExpression = () => model.NullableEnum + }; + var inputSelectComponent = await RenderAndGetTestInputComponentAsync(rootComponent); + + // Act + inputSelectComponent.CurrentValueAsString = "Two"; + + // Assert + Assert.Equal(TestEnum.Two, inputSelectComponent.Value); + } + + [Fact] + public async Task ParsesCurrentValueWhenUsingNullableEnumWithEmptyValue() + { + // Arrange + var model = new TestModel(); + var rootComponent = new TestInputSelectHostComponent + { + EditContext = new EditContext(model), + ValueExpression = () => model.NullableEnum + }; + var inputSelectComponent = await RenderAndGetTestInputComponentAsync(rootComponent); + + // Act + inputSelectComponent.CurrentValueAsString = ""; + + // Assert + Assert.Null(inputSelectComponent.CurrentValue); + } + + private static TestInputSelect FindInputSelectComponent(CapturedBatch batch) + => batch.ReferenceFrames + .Where(f => f.FrameType == RenderTreeFrameType.Component) + .Select(f => f.Component) + .OfType>() + .Single(); + + private static async Task> RenderAndGetTestInputComponentAsync(TestInputSelectHostComponent hostComponent) + { + var testRenderer = new TestRenderer(); + var componentId = testRenderer.AssignRootComponentId(hostComponent); + await testRenderer.RenderRootComponentAsync(componentId); + return FindInputSelectComponent(testRenderer.Batches.Single()); + } + + enum TestEnum + { + One, + Two, + Tree + } + + class TestModel + { + public TestEnum NotNullableEnum { get; set; } + + public TestEnum? NullableEnum { get; set; } + } + + class TestInputSelect : InputSelect + { + public new TValue CurrentValue => base.CurrentValue; + + public new string CurrentValueAsString + { + get => base.CurrentValueAsString; + set => base.CurrentValueAsString = value; + } + } + + class TestInputSelectHostComponent : AutoRenderComponent + { + public EditContext EditContext { get; set; } + + public Expression> ValueExpression { get; set; } + + protected override void BuildRenderTree(RenderTreeBuilder builder) + { + builder.OpenComponent>(0); + builder.AddAttribute(1, "Value", EditContext); + builder.AddAttribute(2, "ChildContent", new RenderFragment(childBuilder => + { + childBuilder.OpenComponent>(0); + childBuilder.AddAttribute(0, "ValueExpression", ValueExpression); + childBuilder.CloseComponent(); + })); + builder.CloseComponent(); + } + } + } +} diff --git a/src/Components/benchmarkapps/BlazingPizza.Server/Directory.Build.props b/src/Components/benchmarkapps/BlazingPizza.Server/Directory.Build.props new file mode 100644 index 0000000000..8c119d5413 --- /dev/null +++ b/src/Components/benchmarkapps/BlazingPizza.Server/Directory.Build.props @@ -0,0 +1,2 @@ + + diff --git a/src/Components/benchmarkapps/Directory.Build.targets b/src/Components/benchmarkapps/BlazingPizza.Server/Directory.Build.targets similarity index 100% rename from src/Components/benchmarkapps/Directory.Build.targets rename to src/Components/benchmarkapps/BlazingPizza.Server/Directory.Build.targets diff --git a/src/Components/benchmarkapps/NuGet.config b/src/Components/benchmarkapps/BlazingPizza.Server/NuGet.config similarity index 100% rename from src/Components/benchmarkapps/NuGet.config rename to src/Components/benchmarkapps/BlazingPizza.Server/NuGet.config diff --git a/src/Components/benchmarkapps/BlazingPizza.Server/ToppingsService.cs b/src/Components/benchmarkapps/BlazingPizza.Server/ToppingsService.cs index 2c17218233..07c80c9790 100644 --- a/src/Components/benchmarkapps/BlazingPizza.Server/ToppingsService.cs +++ b/src/Components/benchmarkapps/BlazingPizza.Server/ToppingsService.cs @@ -90,7 +90,7 @@ namespace BlazingPizza.Server }, new Topping() { - Name = "Fresh tomatos", + Name = "Fresh tomatoes", Price = 1.50m, }, new Topping() diff --git a/src/Components/benchmarkapps/Wasm.Performance/Driver/BenchmarkMeasurement.cs b/src/Components/benchmarkapps/Wasm.Performance/Driver/BenchmarkMeasurement.cs new file mode 100644 index 0000000000..62016cf630 --- /dev/null +++ b/src/Components/benchmarkapps/Wasm.Performance/Driver/BenchmarkMeasurement.cs @@ -0,0 +1,14 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; + +namespace Wasm.Performance.Driver +{ + internal class BenchmarkMeasurement + { + public DateTime Timestamp { get; internal set; } + public string Name { get; internal set; } + public double Value { get; internal set; } + } +} \ No newline at end of file diff --git a/src/Components/benchmarkapps/Wasm.Performance/Driver/BenchmarkMetadata.cs b/src/Components/benchmarkapps/Wasm.Performance/Driver/BenchmarkMetadata.cs new file mode 100644 index 0000000000..ab98fef891 --- /dev/null +++ b/src/Components/benchmarkapps/Wasm.Performance/Driver/BenchmarkMetadata.cs @@ -0,0 +1,14 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Wasm.Performance.Driver +{ + internal class BenchmarkMetadata + { + public string Source { get; set; } + public string Name { get; set; } + public string ShortDescription { get; set; } + public string LongDescription { get; set; } + public string Format { get; set; } + } +} diff --git a/src/Components/benchmarkapps/Wasm.Performance/Driver/BenchmarkOutput.cs b/src/Components/benchmarkapps/Wasm.Performance/Driver/BenchmarkOutput.cs new file mode 100644 index 0000000000..7a32ce146d --- /dev/null +++ b/src/Components/benchmarkapps/Wasm.Performance/Driver/BenchmarkOutput.cs @@ -0,0 +1,14 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; + +namespace Wasm.Performance.Driver +{ + internal class BenchmarkOutput + { + public List Metadata { get; } = new List(); + + public List Measurements { get; } = new List(); + } +} diff --git a/src/Components/benchmarkapps/Wasm.Performance/Driver/BenchmarkResult.cs b/src/Components/benchmarkapps/Wasm.Performance/Driver/BenchmarkResult.cs new file mode 100644 index 0000000000..3173341e4b --- /dev/null +++ b/src/Components/benchmarkapps/Wasm.Performance/Driver/BenchmarkResult.cs @@ -0,0 +1,13 @@ +namespace Wasm.Performance.Driver +{ + class BenchmarkResult + { + public string Name { get; set; } + + public bool Success { get; set; } + + public int NumExecutions { get; set; } + + public double Duration { get; set; } + } +} \ No newline at end of file diff --git a/src/Components/benchmarkapps/Wasm.Performance/Driver/BenchmarkResultsStartup.cs b/src/Components/benchmarkapps/Wasm.Performance/Driver/BenchmarkResultsStartup.cs new file mode 100644 index 0000000000..7a4af028df --- /dev/null +++ b/src/Components/benchmarkapps/Wasm.Performance/Driver/BenchmarkResultsStartup.cs @@ -0,0 +1,37 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Text.Json; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace Wasm.Performance.Driver +{ + public class BenchmarkDriverStartup + { + + public void ConfigureServices(IServiceCollection services) + { + services.AddCors(c => c.AddDefaultPolicy(builder => builder.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin())); + } + + public void Configure(IApplicationBuilder app) + { + app.UseCors(); + + app.Run(async context => + { + var result = await JsonSerializer.DeserializeAsync>(context.Request.Body, new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + }); + await context.Response.WriteAsync("OK"); + Program.SetBenchmarkResult(result); + }); + } + } +} diff --git a/src/Components/benchmarkapps/Wasm.Performance/Driver/Program.cs b/src/Components/benchmarkapps/Wasm.Performance/Driver/Program.cs new file mode 100644 index 0000000000..cfaa9cef0f --- /dev/null +++ b/src/Components/benchmarkapps/Wasm.Performance/Driver/Program.cs @@ -0,0 +1,232 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.ExceptionServices; +using System.Text.Encodings.Web; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Hosting.Server; +using Microsoft.AspNetCore.Hosting.Server.Features; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using DevHostServerProgram = Microsoft.AspNetCore.Blazor.DevServer.Server.Program; + +namespace Wasm.Performance.Driver +{ + public class Program + { + static readonly TimeSpan Timeout = TimeSpan.FromMinutes(3); + static TaskCompletionSource> benchmarkResult = new TaskCompletionSource>(); + + public static async Task Main(string[] args) + { + var seleniumPort = 4444; + if (args.Length > 0) + { + if (!int.TryParse(args[0], out seleniumPort)) + { + Console.Error.WriteLine("Usage Driver "); + return 1; + } + } + + // This write is required for the benchmarking infrastructure. + Console.WriteLine("Application started."); + + var cancellationToken = new CancellationTokenSource(Timeout); + cancellationToken.Token.Register(() => benchmarkResult.TrySetException(new TimeoutException($"Timed out after {Timeout}"))); + + using var browser = await Selenium.CreateBrowser(seleniumPort, cancellationToken.Token); + using var testApp = StartTestApp(); + using var benchmarkReceiver = StartBenchmarkResultReceiver(); + + var testAppUrl = GetListeningUrl(testApp); + var receiverUrl = GetListeningUrl(benchmarkReceiver); + + Console.WriteLine($"Test app listening at {testAppUrl}."); + + var launchUrl = $"{testAppUrl}?resultsUrl={UrlEncoder.Default.Encode(receiverUrl)}#automated"; + browser.Url = launchUrl; + browser.Navigate(); + + var results = await benchmarkResult.Task; + FormatAsBenchmarksOutput(results); + + Console.WriteLine("Done executing benchmark"); + return 0; + } + + internal static void SetBenchmarkResult(List result) + { + benchmarkResult.TrySetResult(result); + } + + private static void FormatAsBenchmarksOutput(List results) + { + // Sample of the the format: https://github.com/aspnet/Benchmarks/blob/e55f9e0312a7dd019d1268c1a547d1863f0c7237/src/Benchmarks/Program.cs#L51-L67 + var output = new BenchmarkOutput(); + foreach (var result in results) + { + output.Metadata.Add(new BenchmarkMetadata + { + Source = "BlazorWasm", + Name = result.Name, + ShortDescription = $"{result.Name} Duration", + LongDescription = $"{result.Name} Duration", + Format = "n2" + }); + + output.Measurements.Add(new BenchmarkMeasurement + { + Timestamp = DateTime.UtcNow, + Name = result.Name, + Value = result.Duration, + }); + } + + // Statistics about publish sizes + output.Metadata.Add(new BenchmarkMetadata + { + Source = "BlazorWasm", + Name = "Publish size", + ShortDescription = "Publish size (KB)", + LongDescription = "Publish size (KB)", + Format = "n2", + }); + + var testAssembly = typeof(TestApp.Startup).Assembly; + var testAssemblyLocation = new FileInfo(testAssembly.Location); + var testApp = new DirectoryInfo(Path.Combine( + testAssemblyLocation.Directory.FullName, + testAssembly.GetName().Name)); + + output.Measurements.Add(new BenchmarkMeasurement + { + Timestamp = DateTime.UtcNow, + Name = "Publish size", + Value = GetDirectorySize(testApp) / 1024, + }); + + output.Metadata.Add(new BenchmarkMetadata + { + Source = "BlazorWasm", + Name = "Publish size (compressed)", + ShortDescription = "Publish size compressed app (KB)", + LongDescription = "Publish size - compressed app (KB)", + Format = "n2", + }); + + var gzip = new FileInfo(Path.Combine( + testAssemblyLocation.Directory.FullName, + $"{testAssembly.GetName().Name}.gzip")); + + output.Measurements.Add(new BenchmarkMeasurement + { + Timestamp = DateTime.UtcNow, + Name = "Publish size (compressed)", + Value = (gzip.Exists ? gzip.Length : 0) / 1024, + }); + + Console.WriteLine("#StartJobStatistics"); + Console.WriteLine(JsonSerializer.Serialize(output)); + Console.WriteLine("#EndJobStatistics"); + } + + static IHost StartTestApp() + { + var args = new[] + { + "--urls", "http://127.0.0.1:0", + "--applicationpath", typeof(TestApp.Startup).Assembly.Location, + }; + + var host = DevHostServerProgram.BuildWebHost(args); + RunInBackgroundThread(host.Start); + return host; + } + + static IHost StartBenchmarkResultReceiver() + { + var args = new[] + { + "--urls", "http://127.0.0.1:0", + }; + + var host = Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(builder => builder.UseStartup()) + .Build(); + + RunInBackgroundThread(host.Start); + return host; + } + + static void RunInBackgroundThread(Action action) + { + var isDone = new ManualResetEvent(false); + + ExceptionDispatchInfo edi = null; + Task.Run(() => + { + try + { + action(); + } + catch (Exception ex) + { + edi = ExceptionDispatchInfo.Capture(ex); + } + + isDone.Set(); + }); + + if (!isDone.WaitOne(Timeout)) + { + throw new TimeoutException("Timed out waiting for: " + action); + } + + if (edi != null) + { + throw edi.SourceException; + } + } + + static string GetListeningUrl(IHost testApp) + { + return testApp.Services.GetRequiredService() + .Features + .Get() + .Addresses + .First(); + } + + static long GetDirectorySize(DirectoryInfo directory) + { + // This can happen if you run the app without publishing it. + if (!directory.Exists) + { + return 0; + } + + long size = 0; + foreach (var item in directory.EnumerateFileSystemInfos()) + { + if (item is FileInfo fileInfo) + { + size += fileInfo.Length; + } + else if (item is DirectoryInfo directoryInfo) + { + size += GetDirectorySize(directoryInfo); + } + } + + return size; + } + } +} diff --git a/src/Components/benchmarkapps/Wasm.Performance/Driver/Selenium.cs b/src/Components/benchmarkapps/Wasm.Performance/Driver/Selenium.cs new file mode 100644 index 0000000000..1c30e69e20 --- /dev/null +++ b/src/Components/benchmarkapps/Wasm.Performance/Driver/Selenium.cs @@ -0,0 +1,121 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using OpenQA.Selenium; +using OpenQA.Selenium.Chrome; +using OpenQA.Selenium.Remote; + +namespace Wasm.Performance.Driver +{ + class Selenium + { + static bool RunHeadlessBrowser = true; + static bool PoolForBrowserLogs = true; + + private static async ValueTask WaitForServerAsync(int port, CancellationToken cancellationToken) + { + var uri = new UriBuilder("http", "localhost", port, "/wd/hub/").Uri; + var httpClient = new HttpClient + { + BaseAddress = uri, + Timeout = TimeSpan.FromSeconds(1), + }; + + Console.WriteLine($"Attempting to connect to Selenium Server running at {uri}"); + + const int MaxRetries = 30; + var retries = 0; + + while (retries < MaxRetries) + { + retries++; + try + { + var response = (await httpClient.GetAsync("status", cancellationToken)).EnsureSuccessStatusCode(); + Console.WriteLine("Connected to Selenium"); + return uri; + } + catch + { + if (retries == 1) + { + Console.WriteLine("Could not connect to selenium-server. Has it been started as yet?"); + } + } + + await Task.Delay(1000); + } + + throw new Exception($"Unable to connect to selenium-server at {uri}"); + } + + public static async Task CreateBrowser(int port, CancellationToken cancellationToken) + { + var uri = await WaitForServerAsync(port, cancellationToken); + + var options = new ChromeOptions(); + + if (RunHeadlessBrowser) + { + options.AddArgument("--headless"); + } + + options.SetLoggingPreference(LogType.Browser, LogLevel.All); + + var attempt = 0; + const int MaxAttempts = 3; + do + { + try + { + // The driver opens the browser window and tries to connect to it on the constructor. + // Under heavy load, this can cause issues + // To prevent this we let the client attempt several times to connect to the server, increasing + // the max allowed timeout for a command on each attempt linearly. + var driver = new RemoteWebDriver( + uri, + options.ToCapabilities(), + TimeSpan.FromSeconds(60).Add(TimeSpan.FromSeconds(attempt * 60))); + + driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(1); + + if (PoolForBrowserLogs) + { + // Run in background. + var logs = new RemoteLogs(driver); + _ = Task.Run(async () => + { + while (!cancellationToken.IsCancellationRequested) + { + await Task.Delay(TimeSpan.FromSeconds(3)); + + var consoleLogs = logs.GetLog(LogType.Browser); + foreach (var entry in consoleLogs) + { + Console.WriteLine($"[Browser Log]: {entry.Timestamp}: {entry.Message}"); + } + } + }); + } + + return driver; + } + catch (Exception ex) + { + Console.WriteLine($"Error initializing RemoteWebDriver: {ex.Message}"); + } + + attempt++; + + } while (attempt < MaxAttempts); + + throw new InvalidOperationException("Couldn't create a Selenium remote driver client. The server is irresponsive"); + } + } +} diff --git a/src/Components/benchmarkapps/Wasm.Performance/Driver/Wasm.Performance.Driver.csproj b/src/Components/benchmarkapps/Wasm.Performance/Driver/Wasm.Performance.Driver.csproj new file mode 100644 index 0000000000..026ce4b98a --- /dev/null +++ b/src/Components/benchmarkapps/Wasm.Performance/Driver/Wasm.Performance.Driver.csproj @@ -0,0 +1,26 @@ + + + + $(DefaultNetCoreTargetFramework) + exe + + false + false + false + false + + + false + + + + + + + + + + + + + diff --git a/src/Components/benchmarkapps/Wasm.Performance/Driver/appsettings.json b/src/Components/benchmarkapps/Wasm.Performance/Driver/appsettings.json new file mode 100644 index 0000000000..bed61b254f --- /dev/null +++ b/src/Components/benchmarkapps/Wasm.Performance/Driver/appsettings.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "IncludeScopes": false, + "LogLevel": { + "Default": "Warning" + } + } +} \ No newline at end of file diff --git a/src/Components/benchmarkapps/Wasm.Performance/README.md b/src/Components/benchmarkapps/Wasm.Performance/README.md new file mode 100644 index 0000000000..9522ecc502 --- /dev/null +++ b/src/Components/benchmarkapps/Wasm.Performance/README.md @@ -0,0 +1,20 @@ +## Blazor WASM benchmarks + +These projects assist in Benchmarking Components. +See https://github.com/aspnet/Benchmarks#benchmarks for usage guidance on using the Benchmarking tool with your application + +### Running the benchmarks + +The TestApp is a regular BlazorWASM project and can be run using `dotnet run`. The Driver is an app that connects against an existing Selenium server, and speaks the Benchmark protocol. You generally do not need to run the Driver locally, but if you were to do so, you can either start a selenium-server instance and run using `dotnet run []` or run it inside a Linux-based docker container. + +Here are the commands you would need to run it locally inside docker: + +1. `dotnet publish -c Release -r linux-x64 Driver/Wasm.Performance.Driver.csproj` +2. `docker build -t blazor-local -f ./local.dockerfile . ` +3. `docker run -it blazor-local` + +To run the benchmark app in the Benchmark server, run + +``` +dotnet run -- --config aspnetcore/src/Components/benchmarkapps/Wasm.Performance/benchmarks.compose.json application.endpoints --scenario blazorwasmbenchmark +``` diff --git a/src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/App.razor b/src/Components/benchmarkapps/Wasm.Performance/TestApp/App.razor similarity index 100% rename from src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/App.razor rename to src/Components/benchmarkapps/Wasm.Performance/TestApp/App.razor diff --git a/src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/BenchmarkEvent.cs b/src/Components/benchmarkapps/Wasm.Performance/TestApp/BenchmarkEvent.cs similarity index 89% rename from src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/BenchmarkEvent.cs rename to src/Components/benchmarkapps/Wasm.Performance/TestApp/BenchmarkEvent.cs index bdf98fd388..81cd361dce 100644 --- a/src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/BenchmarkEvent.cs +++ b/src/Components/benchmarkapps/Wasm.Performance/TestApp/BenchmarkEvent.cs @@ -3,7 +3,7 @@ using Microsoft.JSInterop; -namespace Microsoft.AspNetCore.Blazor.E2EPerformance +namespace Wasm.Performance.TestApp { public static class BenchmarkEvent { diff --git a/src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/Pages/Index.razor b/src/Components/benchmarkapps/Wasm.Performance/TestApp/Pages/Index.razor similarity index 100% rename from src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/Pages/Index.razor rename to src/Components/benchmarkapps/Wasm.Performance/TestApp/Pages/Index.razor diff --git a/src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/Pages/Json.razor b/src/Components/benchmarkapps/Wasm.Performance/TestApp/Pages/Json.razor similarity index 100% rename from src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/Pages/Json.razor rename to src/Components/benchmarkapps/Wasm.Performance/TestApp/Pages/Json.razor diff --git a/src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/Pages/RenderList.razor b/src/Components/benchmarkapps/Wasm.Performance/TestApp/Pages/RenderList.razor similarity index 100% rename from src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/Pages/RenderList.razor rename to src/Components/benchmarkapps/Wasm.Performance/TestApp/Pages/RenderList.razor diff --git a/src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/Pages/_Imports.razor b/src/Components/benchmarkapps/Wasm.Performance/TestApp/Pages/_Imports.razor similarity index 100% rename from src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/Pages/_Imports.razor rename to src/Components/benchmarkapps/Wasm.Performance/TestApp/Pages/_Imports.razor diff --git a/src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/Program.cs b/src/Components/benchmarkapps/Wasm.Performance/TestApp/Program.cs similarity index 91% rename from src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/Program.cs rename to src/Components/benchmarkapps/Wasm.Performance/TestApp/Program.cs index f498eb0222..403bc37c9c 100644 --- a/src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/Program.cs +++ b/src/Components/benchmarkapps/Wasm.Performance/TestApp/Program.cs @@ -3,7 +3,7 @@ using Microsoft.AspNetCore.Blazor.Hosting; -namespace Microsoft.AspNetCore.Blazor.E2EPerformance +namespace Wasm.Performance.TestApp { public class Program { diff --git a/src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/Shared/MainLayout.razor b/src/Components/benchmarkapps/Wasm.Performance/TestApp/Shared/MainLayout.razor similarity index 100% rename from src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/Shared/MainLayout.razor rename to src/Components/benchmarkapps/Wasm.Performance/TestApp/Shared/MainLayout.razor diff --git a/src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/Startup.cs b/src/Components/benchmarkapps/Wasm.Performance/TestApp/Startup.cs similarity index 90% rename from src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/Startup.cs rename to src/Components/benchmarkapps/Wasm.Performance/TestApp/Startup.cs index 7422cd806c..c79b0efb8c 100644 --- a/src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/Startup.cs +++ b/src/Components/benchmarkapps/Wasm.Performance/TestApp/Startup.cs @@ -4,7 +4,7 @@ using Microsoft.AspNetCore.Components.Builder; using Microsoft.Extensions.DependencyInjection; -namespace Microsoft.AspNetCore.Blazor.E2EPerformance +namespace Wasm.Performance.TestApp { public class Startup { diff --git a/src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/Microsoft.AspNetCore.Blazor.E2EPerformance.csproj b/src/Components/benchmarkapps/Wasm.Performance/TestApp/Wasm.Performance.TestApp.csproj similarity index 53% rename from src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/Microsoft.AspNetCore.Blazor.E2EPerformance.csproj rename to src/Components/benchmarkapps/Wasm.Performance/TestApp/Wasm.Performance.TestApp.csproj index 9f796fdbcf..3d28c77a21 100644 --- a/src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/Microsoft.AspNetCore.Blazor.E2EPerformance.csproj +++ b/src/Components/benchmarkapps/Wasm.Performance/TestApp/Wasm.Performance.TestApp.csproj @@ -1,9 +1,12 @@ - + - netstandard2.0 - true + netstandard2.1 3.0 + + false + false + true diff --git a/src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/_Imports.razor b/src/Components/benchmarkapps/Wasm.Performance/TestApp/_Imports.razor similarity index 56% rename from src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/_Imports.razor rename to src/Components/benchmarkapps/Wasm.Performance/TestApp/_Imports.razor index dc263c9383..fef56339a9 100644 --- a/src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/_Imports.razor +++ b/src/Components/benchmarkapps/Wasm.Performance/TestApp/_Imports.razor @@ -2,5 +2,5 @@ @using Microsoft.AspNetCore.Components.Routing @using Microsoft.AspNetCore.Components.Web @using Microsoft.JSInterop -@using Microsoft.AspNetCore.Blazor.E2EPerformance -@using Microsoft.AspNetCore.Blazor.E2EPerformance.Shared +@using Wasm.Performance.TestApp +@using Wasm.Performance.TestApp.Shared diff --git a/src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/wwwroot/benchmarks/appStartup.js b/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/appStartup.js similarity index 100% rename from src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/wwwroot/benchmarks/appStartup.js rename to src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/appStartup.js diff --git a/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/index.js b/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/index.js new file mode 100644 index 0000000000..c1690cfac8 --- /dev/null +++ b/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/index.js @@ -0,0 +1,39 @@ +import { groups, BenchmarkEvent, onBenchmarkEvent } from './lib/minibench/minibench.js'; +import { HtmlUI } from './lib/minibench/minibench.ui.js'; +import './appStartup.js'; +import './renderList.js'; +import './jsonHandling.js'; + +new HtmlUI('E2E Performance', '#display'); + +if (location.href.indexOf('#automated') !== -1) { + const query = new URLSearchParams(window.location.search); + const group = query.get('group'); + const resultsUrl = query.get('resultsUrl'); + + groups.filter(g => !group || g.name === group).forEach(g => g.runAll()); + + const benchmarksResults = []; + onBenchmarkEvent(async (status, args) => { + switch (status) { + case BenchmarkEvent.runStarted: + benchmarksResults.length = 0; + break; + case BenchmarkEvent.benchmarkCompleted: + case BenchmarkEvent.benchmarkError: + console.log(`Completed benchmark ${args.name}`); + benchmarksResults.push(args); + break; + case BenchmarkEvent.runCompleted: + if (resultsUrl) { + await fetch(resultsUrl, { + method: 'post', + body: JSON.stringify(benchmarksResults) + }); + } + break; + default: + throw new Error(`Unknown status: ${status}`); + } + }) +} diff --git a/src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/wwwroot/benchmarks/jsonHandling.js b/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/jsonHandling.js similarity index 100% rename from src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/wwwroot/benchmarks/jsonHandling.js rename to src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/jsonHandling.js diff --git a/src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/wwwroot/benchmarks/jsonHandlingData.js b/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/jsonHandlingData.js similarity index 100% rename from src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/wwwroot/benchmarks/jsonHandlingData.js rename to src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/jsonHandlingData.js diff --git a/src/Components/Blazor/Templates/src/content/BlazorWasm-CSharp/Client/wwwroot/css/bootstrap/bootstrap.min.css b/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/lib/bootstrap.min.css similarity index 100% rename from src/Components/Blazor/Templates/src/content/BlazorWasm-CSharp/Client/wwwroot/css/bootstrap/bootstrap.min.css rename to src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/lib/bootstrap.min.css diff --git a/src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/wwwroot/benchmarks/lib/minibench/README.md b/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/lib/minibench/README.md similarity index 100% rename from src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/wwwroot/benchmarks/lib/minibench/README.md rename to src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/lib/minibench/README.md diff --git a/src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/wwwroot/benchmarks/lib/minibench/minibench.js b/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/lib/minibench/minibench.js similarity index 58% rename from src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/wwwroot/benchmarks/lib/minibench/minibench.js rename to src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/lib/minibench/minibench.js index 8214419982..241721ceeb 100644 --- a/src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/wwwroot/benchmarks/lib/minibench/minibench.js +++ b/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/lib/minibench/minibench.js @@ -66,7 +66,7 @@ window.addEventListener('message', evt => { To work around browsers' current nonsupport for high-resolution timers (since Spectre etc.), the approach used here is to group executions into blocks of roughly fixed duration. - + - In each block, we execute the test code as many times as we can until the end of the block duration, without even yielding the thread if it's a synchronous call. We count how many executions completed. It @@ -82,7 +82,7 @@ window.addEventListener('message', evt => { during which there was no unrelated GC cycle or other background contention. - We keep running blocks until some larger timeout occurs *and* we've done at least some minimum number of executions. - + Note that this approach does *not* allow for per-execution setup/teardown logic whose timing is separated from the code under test. Because of the low timer precision, there would be no way to separate the setup duration @@ -174,10 +174,23 @@ class Benchmark extends EventEmitter { } run(runOptions) { + if (reportBenchmarkEvent) { + const areAllIdle = groups.reduce( + (prev, next) => prev && next.status === BenchmarkStatus.idle, + true + ); + + if (areAllIdle) { + // This is the first test being run from the idle state + reportBenchmarkEvent(BenchmarkEvent.runStarted); + } + } + this._currentRunWasAborted = false; if (this._state.status === BenchmarkStatus.idle) { this._updateState({ status: BenchmarkStatus.queued }); this.workQueueCancelHandle = addToWorkQueue(async () => { + try { if (!(runOptions && runOptions.skipGroupSetup)) { await this._group.runSetup(); @@ -192,10 +205,13 @@ class Benchmark extends EventEmitter { await this._group.runTeardown(); } + reportBenchmarkEvent(BenchmarkEvent.benchmarkCompleted, { 'name': this.name, success: true, numExecutions: this._state.numExecutions, duration: this._state.estimatedExecutionDurationMs }); + this._updateState({ status: BenchmarkStatus.idle }); } catch (ex) { this._updateState({ status: BenchmarkStatus.error }); console.error(ex); + reportBenchmarkEvent(BenchmarkEvent.benchmarkError, { 'name': this.name, success: false }); } }); } @@ -237,6 +253,13 @@ const BenchmarkStatus = { error: 3, }; +const BenchmarkEvent = { + runStarted: 0, + benchmarkCompleted : 1, + benchmarkError: 2, + runCompleted: 3, +} + class Group extends EventEmitter { constructor(name) { super(); @@ -279,6 +302,7 @@ class Group extends EventEmitter { } const groups = []; +let reportBenchmarkEvent = () => {}; function group(name, configure) { groups.push(new Group(name)); @@ -298,184 +322,21 @@ function teardown(fn) { groups[groups.length - 1].teardown = fn; } -class BenchmarkDisplay { - constructor(htmlUi, benchmark) { - this.benchmark = benchmark; - this.elem = document.createElement('tr'); - - const headerCol = this.elem.appendChild(document.createElement('th')); - headerCol.className = 'pl-4'; - headerCol.textContent = benchmark.name; - headerCol.setAttribute('scope', 'row'); +function onBenchmarkEvent(fn) { + reportBenchmarkEvent = fn; - const progressCol = this.elem.appendChild(document.createElement('td')); - this.numExecutionsText = progressCol.appendChild(document.createTextNode('')); + groups.forEach(group$$1 => { + group$$1.on('changed', () => { + const areAllIdle = groups.reduce( + (prev, next) => prev && next.status === BenchmarkStatus.idle, + true + ); - const timingCol = this.elem.appendChild(document.createElement('td')); - this.executionDurationText = timingCol.appendChild(document.createElement('span')); - - const runCol = this.elem.appendChild(document.createElement('td')); - runCol.className = 'pr-4'; - runCol.setAttribute('align', 'right'); - this.runButton = document.createElement('a'); - this.runButton.className = 'run-button'; - runCol.appendChild(this.runButton); - this.runButton.textContent = 'Run'; - this.runButton.onclick = evt => { - evt.preventDefault(); - this.benchmark.run(htmlUi.globalRunOptions); - }; - - benchmark.on('changed', state => this.updateDisplay(state)); - this.updateDisplay(this.benchmark.state); - } - - updateDisplay(state) { - const benchmark = this.benchmark; - this.elem.className = rowClass(state.status); - this.runButton.textContent = runButtonText(state.status); - this.numExecutionsText.textContent = state.numExecutions - ? `Executions: ${state.numExecutions}` : ''; - this.executionDurationText.innerHTML = state.estimatedExecutionDurationMs - ? `Duration: ${parseFloat(state.estimatedExecutionDurationMs.toPrecision(3))}ms` : ''; - if (state.status === BenchmarkStatus.idle) { - this.runButton.setAttribute('href', ''); - } else { - this.runButton.removeAttribute('href'); - if (state.status === BenchmarkStatus.error) { - this.numExecutionsText.textContent = 'Error - see console'; + if (areAllIdle) { + fn(BenchmarkEvent.runCompleted); } - } - } -} - -function runButtonText(status) { - switch (status) { - case BenchmarkStatus.idle: - case BenchmarkStatus.error: - return 'Run'; - case BenchmarkStatus.queued: - return 'Waiting...'; - case BenchmarkStatus.running: - return 'Running...'; - default: - throw new Error(`Unknown status: ${status}`); - } -} - -function rowClass(status) { - switch (status) { - case BenchmarkStatus.idle: - return 'benchmark-idle'; - case BenchmarkStatus.queued: - return 'benchmark-waiting'; - case BenchmarkStatus.running: - return 'benchmark-running'; - case BenchmarkStatus.error: - return 'benchmark-error'; - default: - throw new Error(`Unknown status: ${status}`); - } -} - -class GroupDisplay { - constructor(htmlUi, group) { - this.group = group; - - this.elem = document.createElement('div'); - this.elem.className = 'my-3 py-2 bg-white rounded shadow-sm'; - - const headerContainer = this.elem.appendChild(document.createElement('div')); - headerContainer.className = 'd-flex align-items-baseline px-4'; - const header = headerContainer.appendChild(document.createElement('h5')); - header.className = 'py-2'; - header.textContent = group.name; - - this.runButton = document.createElement('a'); - this.runButton.className = 'ml-auto run-button'; - this.runButton.setAttribute('href', ''); - headerContainer.appendChild(this.runButton); - this.runButton.textContent = 'Run all'; - this.runButton.onclick = evt => { - evt.preventDefault(); - group.runAll(htmlUi.globalRunOptions); - }; - - const table = this.elem.appendChild(document.createElement('table')); - table.className = 'table mb-0 benchmarks'; - const tbody = table.appendChild(document.createElement('tbody')); - - group.benchmarks.forEach(benchmark => { - const benchmarkDisplay = new BenchmarkDisplay(htmlUi, benchmark); - tbody.appendChild(benchmarkDisplay.elem); }); - - group.on('changed', () => this.updateDisplay()); - this.updateDisplay(); - } - - updateDisplay() { - const canRun = this.group.status === BenchmarkStatus.idle; - this.runButton.style.display = canRun ? 'block' : 'none'; - } -} - -class HtmlUI { - constructor(title, selector) { - this.containerElement = document.querySelector(selector); - - const headerDiv = this.containerElement.appendChild(document.createElement('div')); - headerDiv.className = 'd-flex align-items-center'; - - const header = headerDiv.appendChild(document.createElement('h2')); - header.className = 'mx-3 flex-grow-1'; - header.textContent = title; - - const verifyCheckboxLabel = document.createElement('label'); - verifyCheckboxLabel.className = 'ml-auto mr-5'; - headerDiv.appendChild(verifyCheckboxLabel); - this.verifyCheckbox = verifyCheckboxLabel.appendChild(document.createElement('input')); - this.verifyCheckbox.type = 'checkbox'; - this.verifyCheckbox.className = 'mr-2'; - verifyCheckboxLabel.appendChild(document.createTextNode('Verify only')); - - this.runButton = document.createElement('button'); - this.runButton.className = 'btn btn-success ml-auto px-4 run-button'; - headerDiv.appendChild(this.runButton); - this.runButton.textContent = 'Run all'; - this.runButton.onclick = () => { - groups.forEach(g => g.runAll(this.globalRunOptions)); - }; - - this.stopButton = document.createElement('button'); - this.stopButton.className = 'btn btn-danger ml-auto px-4 stop-button'; - headerDiv.appendChild(this.stopButton); - this.stopButton.textContent = 'Stop'; - this.stopButton.onclick = () => { - groups.forEach(g => g.stopAll()); - }; - - groups.forEach(group$$1 => { - const groupDisplay = new GroupDisplay(this, group$$1); - this.containerElement.appendChild(groupDisplay.elem); - group$$1.on('changed', () => this.updateDisplay()); - }); - - this.updateDisplay(); - } - - updateDisplay() { - const areAllIdle = groups.reduce( - (prev, next) => prev && next.status === BenchmarkStatus.idle, - true - ); - this.runButton.style.display = areAllIdle ? 'block' : 'none'; - this.stopButton.style.display = areAllIdle ? 'none' : 'block'; - } - - get globalRunOptions() { - return { verifyOnly: this.verifyCheckbox.checked }; - } + }); } /** @@ -483,4 +344,4 @@ class HtmlUI { * https://github.com/SteveSanderson/minibench */ -export { group, benchmark, setup, teardown, HtmlUI }; +export { groups, group, benchmark, setup, teardown, onBenchmarkEvent, BenchmarkEvent, BenchmarkStatus }; diff --git a/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/lib/minibench/minibench.ui.js b/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/lib/minibench/minibench.ui.js new file mode 100644 index 0000000000..4384b7660b --- /dev/null +++ b/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/lib/minibench/minibench.ui.js @@ -0,0 +1,191 @@ +/** minibench - https://github.com/SteveSanderson/minibench */ + +import { groups, BenchmarkStatus } from './minibench.js'; + +class BenchmarkDisplay { + constructor(htmlUi, benchmark) { + this.benchmark = benchmark; + this.elem = document.createElement('tr'); + + const headerCol = this.elem.appendChild(document.createElement('th')); + headerCol.className = 'pl-4'; + headerCol.textContent = benchmark.name; + headerCol.setAttribute('scope', 'row'); + + const progressCol = this.elem.appendChild(document.createElement('td')); + this.numExecutionsText = progressCol.appendChild(document.createTextNode('')); + + const timingCol = this.elem.appendChild(document.createElement('td')); + this.executionDurationText = timingCol.appendChild(document.createElement('span')); + + const runCol = this.elem.appendChild(document.createElement('td')); + runCol.className = 'pr-4'; + runCol.setAttribute('align', 'right'); + this.runButton = document.createElement('a'); + this.runButton.className = 'run-button'; + runCol.appendChild(this.runButton); + this.runButton.textContent = 'Run'; + this.runButton.onclick = evt => { + evt.preventDefault(); + this.benchmark.run(htmlUi.globalRunOptions); + }; + + benchmark.on('changed', state => this.updateDisplay(state)); + this.updateDisplay(this.benchmark.state); + } + + updateDisplay(state) { + const benchmark = this.benchmark; + this.elem.className = rowClass(state.status); + this.runButton.textContent = runButtonText(state.status); + this.numExecutionsText.textContent = state.numExecutions + ? `Executions: ${state.numExecutions}` : ''; + this.executionDurationText.innerHTML = state.estimatedExecutionDurationMs + ? `Duration: ${parseFloat(state.estimatedExecutionDurationMs.toPrecision(3))}ms` : ''; + if (state.status === BenchmarkStatus.idle) { + this.runButton.setAttribute('href', ''); + } else { + this.runButton.removeAttribute('href'); + if (state.status === BenchmarkStatus.error) { + this.numExecutionsText.textContent = 'Error - see console'; + } + } + } +} + +function runButtonText(status) { + switch (status) { + case BenchmarkStatus.idle: + case BenchmarkStatus.error: + return 'Run'; + case BenchmarkStatus.queued: + return 'Waiting...'; + case BenchmarkStatus.running: + return 'Running...'; + default: + throw new Error(`Unknown status: ${status}`); + } +} + +function rowClass(status) { + switch (status) { + case BenchmarkStatus.idle: + return 'benchmark-idle'; + case BenchmarkStatus.queued: + return 'benchmark-waiting'; + case BenchmarkStatus.running: + return 'benchmark-running'; + case BenchmarkStatus.error: + return 'benchmark-error'; + default: + throw new Error(`Unknown status: ${status}`); + } +} + +class GroupDisplay { + constructor(htmlUi, group) { + this.group = group; + + this.elem = document.createElement('div'); + this.elem.className = 'my-3 py-2 bg-white rounded shadow-sm'; + + const headerContainer = this.elem.appendChild(document.createElement('div')); + headerContainer.className = 'd-flex align-items-baseline px-4'; + const header = headerContainer.appendChild(document.createElement('h5')); + header.className = 'py-2'; + header.textContent = group.name; + + this.runButton = document.createElement('a'); + this.runButton.className = 'ml-auto run-button'; + this.runButton.setAttribute('href', ''); + headerContainer.appendChild(this.runButton); + this.runButton.textContent = 'Run all'; + this.runButton.onclick = evt => { + evt.preventDefault(); + group.runAll(htmlUi.globalRunOptions); + }; + + const table = this.elem.appendChild(document.createElement('table')); + table.className = 'table mb-0 benchmarks'; + const tbody = table.appendChild(document.createElement('tbody')); + + group.benchmarks.forEach(benchmark => { + const benchmarkDisplay = new BenchmarkDisplay(htmlUi, benchmark); + tbody.appendChild(benchmarkDisplay.elem); + }); + + group.on('changed', () => this.updateDisplay()); + this.updateDisplay(); + } + + updateDisplay() { + const canRun = this.group.status === BenchmarkStatus.idle; + this.runButton.style.display = canRun ? 'block' : 'none'; + } +} + +class HtmlUI { + constructor(title, selector) { + this.containerElement = document.querySelector(selector); + + const headerDiv = this.containerElement.appendChild(document.createElement('div')); + headerDiv.className = 'd-flex align-items-center'; + + const header = headerDiv.appendChild(document.createElement('h2')); + header.className = 'mx-3 flex-grow-1'; + header.textContent = title; + + const verifyCheckboxLabel = document.createElement('label'); + verifyCheckboxLabel.className = 'ml-auto mr-5'; + headerDiv.appendChild(verifyCheckboxLabel); + this.verifyCheckbox = verifyCheckboxLabel.appendChild(document.createElement('input')); + this.verifyCheckbox.type = 'checkbox'; + this.verifyCheckbox.className = 'mr-2'; + verifyCheckboxLabel.appendChild(document.createTextNode('Verify only')); + + this.runButton = document.createElement('button'); + this.runButton.className = 'btn btn-success ml-auto px-4 run-button'; + headerDiv.appendChild(this.runButton); + this.runButton.textContent = 'Run all'; + this.runButton.setAttribute('id', 'runAll'); + this.runButton.onclick = () => { + groups.forEach(g => g.runAll(this.globalRunOptions)); + }; + + this.stopButton = document.createElement('button'); + this.stopButton.className = 'btn btn-danger ml-auto px-4 stop-button'; + headerDiv.appendChild(this.stopButton); + this.stopButton.textContent = 'Stop'; + this.stopButton.onclick = () => { + groups.forEach(g => g.stopAll()); + }; + + groups.forEach(group$$1 => { + const groupDisplay = new GroupDisplay(this, group$$1); + this.containerElement.appendChild(groupDisplay.elem); + group$$1.on('changed', () => this.updateDisplay()); + }); + + this.updateDisplay(); + } + + updateDisplay() { + const areAllIdle = groups.reduce( + (prev, next) => prev && next.status === BenchmarkStatus.idle, + true + ); + this.runButton.style.display = areAllIdle ? 'block' : 'none'; + this.stopButton.style.display = areAllIdle ? 'none' : 'block';; + } + + get globalRunOptions() { + return { verifyOnly: this.verifyCheckbox.checked }; + } +} + +/** + * minibench + * https://github.com/SteveSanderson/minibench + */ + +export { HtmlUI }; diff --git a/src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/wwwroot/benchmarks/lib/minibench/style.css b/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/lib/minibench/style.css similarity index 100% rename from src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/wwwroot/benchmarks/lib/minibench/style.css rename to src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/lib/minibench/style.css diff --git a/src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/wwwroot/benchmarks/renderList.js b/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/renderList.js similarity index 100% rename from src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/wwwroot/benchmarks/renderList.js rename to src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/renderList.js diff --git a/src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/wwwroot/benchmarks/util/BenchmarkEvents.js b/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/util/BenchmarkEvents.js similarity index 100% rename from src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/wwwroot/benchmarks/util/BenchmarkEvents.js rename to src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/util/BenchmarkEvents.js diff --git a/src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/wwwroot/benchmarks/util/BlazorApp.js b/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/util/BlazorApp.js similarity index 100% rename from src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/wwwroot/benchmarks/util/BlazorApp.js rename to src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/util/BlazorApp.js diff --git a/src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/wwwroot/benchmarks/util/DOM.js b/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/util/DOM.js similarity index 100% rename from src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/wwwroot/benchmarks/util/DOM.js rename to src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/util/DOM.js diff --git a/src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/wwwroot/blazor-frame.html b/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/blazor-frame.html similarity index 100% rename from src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/wwwroot/blazor-frame.html rename to src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/blazor-frame.html diff --git a/src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/wwwroot/index.html b/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/index.html similarity index 100% rename from src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/wwwroot/index.html rename to src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/index.html diff --git a/src/Components/benchmarkapps/Wasm.Performance/benchmarks.compose.json b/src/Components/benchmarkapps/Wasm.Performance/benchmarks.compose.json new file mode 100644 index 0000000000..81607364dc --- /dev/null +++ b/src/Components/benchmarkapps/Wasm.Performance/benchmarks.compose.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://raw.githubusercontent.com/aspnet/Benchmarks/master/src/BenchmarksDriver2/benchmarks.schema.json", + "scenarios": { + "blazorwasmbenchmark": { + "application": { + "job": "blazorwasmbenchmark" + } + } + }, + "jobs": { + "blazorwasmbenchmark": { + "source": { + "repository": "https://github.com/dotnet/AspNetCore.git", + "branchOrCommit": "blazor-wasm", + "dockerfile": "src/Components/benchmarkapps/Wasm.Performance/dockerfile" + }, + "waitForExit": true, + "readyStateText": "Application started." + } + } +} diff --git a/src/Components/benchmarkapps/Wasm.Performance/dockerfile b/src/Components/benchmarkapps/Wasm.Performance/dockerfile new file mode 100644 index 0000000000..410556f21a --- /dev/null +++ b/src/Components/benchmarkapps/Wasm.Performance/dockerfile @@ -0,0 +1,33 @@ +FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS build + +ARG DEBIAN_FRONTEND=noninteractive + +# Setup for nodejs +RUN curl -sL https://deb.nodesource.com/setup_13.x | bash - + +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + libunwind-dev \ + nodejs \ + git + +ARG gitBranch=blazor-wasm + +WORKDIR /src +ADD https://api.github.com/repos/dotnet/aspnetcore/git/ref/heads/${gitBranch} /aspnetcore.commit + +RUN git init \ + && git fetch https://github.com/aspnet/aspnetcore ${gitBranch} \ + && git reset --hard FETCH_HEAD \ + && git submodule update --init + +RUN ./restore.sh +RUN .dotnet/dotnet publish -c Release -r linux-x64 -o /app ./src/Components/benchmarkapps/Wasm.Performance/Driver/Wasm.Performance.Driver.csproj +RUN chmod +x /app/Wasm.Performance.Driver + +WORKDIR /app +FROM selenium/standalone-chrome:3.141.59-mercury as final +COPY --from=build ./app ./ +COPY ./exec.sh ./ + +ENTRYPOINT [ "bash", "./exec.sh" ] diff --git a/src/Components/benchmarkapps/Wasm.Performance/exec.sh b/src/Components/benchmarkapps/Wasm.Performance/exec.sh new file mode 100755 index 0000000000..bae38ae1e1 --- /dev/null +++ b/src/Components/benchmarkapps/Wasm.Performance/exec.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +/opt/bin/start-selenium-standalone.sh& +./Wasm.Performance.Driver + diff --git a/src/Components/benchmarkapps/Wasm.Performance/local.dockerfile b/src/Components/benchmarkapps/Wasm.Performance/local.dockerfile new file mode 100644 index 0000000000..188bc5dc5a --- /dev/null +++ b/src/Components/benchmarkapps/Wasm.Performance/local.dockerfile @@ -0,0 +1,7 @@ +FROM selenium/standalone-chrome:3.141.59-mercury as final + +WORKDIR /app +COPY ./Driver/bin/Release/netcoreapp3.1/linux-x64/publish ./ +COPY ./exec.sh ./ + +ENTRYPOINT [ "bash", "./exec.sh" ] diff --git a/src/Components/test/E2ETest/Microsoft.AspNetCore.Components.E2ETests.csproj b/src/Components/test/E2ETest/Microsoft.AspNetCore.Components.E2ETests.csproj index 97c30edc5d..200d135dff 100644 --- a/src/Components/test/E2ETest/Microsoft.AspNetCore.Components.E2ETests.csproj +++ b/src/Components/test/E2ETest/Microsoft.AspNetCore.Components.E2ETests.csproj @@ -7,11 +7,12 @@ $(DefaultNetCoreTargetFramework) Components.E2ETests - + false true + false @@ -32,12 +33,11 @@ - - + diff --git a/src/Components/test/E2ETest/ServerExecutionTests/ComponentHubInvalidEventTest.cs b/src/Components/test/E2ETest/ServerExecutionTests/ComponentHubInvalidEventTest.cs index 26bce75205..7d46a7defb 100644 --- a/src/Components/test/E2ETest/ServerExecutionTests/ComponentHubInvalidEventTest.cs +++ b/src/Components/test/E2ETest/ServerExecutionTests/ComponentHubInvalidEventTest.cs @@ -4,11 +4,11 @@ using System; using System.Text.Json; using System.Threading.Tasks; -using Microsoft.AspNetCore.Components.E2ETest; using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures; using Microsoft.AspNetCore.Components.RenderTree; using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.SignalR.Client; +using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.Logging; using TestServer; using Xunit; @@ -16,6 +16,7 @@ using Xunit.Abstractions; namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests { + [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/19666")] public class ComponentHubInvalidEventTest : IgnitorTest { public ComponentHubInvalidEventTest(BasicTestAppServerSiteFixture serverFixture, ITestOutputHelper output) diff --git a/src/Components/test/E2ETest/ServerExecutionTests/ComponentHubReliabilityTest.cs b/src/Components/test/E2ETest/ServerExecutionTests/ComponentHubReliabilityTest.cs index 52546042fb..616cdab1e0 100644 --- a/src/Components/test/E2ETest/ServerExecutionTests/ComponentHubReliabilityTest.cs +++ b/src/Components/test/E2ETest/ServerExecutionTests/ComponentHubReliabilityTest.cs @@ -2,14 +2,9 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Collections.Generic; -using System.Text.Json; using System.Text.RegularExpressions; using System.Threading.Tasks; -using Ignitor; using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures; -using Microsoft.AspNetCore.Components.RenderTree; -using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.SignalR.Client; using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.Logging; @@ -19,6 +14,7 @@ using Xunit.Abstractions; namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests { + [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/19666")] public class ComponentHubReliabilityTest : IgnitorTest { public ComponentHubReliabilityTest(BasicTestAppServerSiteFixture serverFixture, ITestOutputHelper output) @@ -27,6 +23,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests } [Fact] + [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/19414")] public async Task CannotStartMultipleCircuits() { // Arrange diff --git a/src/Components/test/E2ETest/ServerExecutionTests/GlobalizationTest.cs b/src/Components/test/E2ETest/ServerExecutionTests/GlobalizationTest.cs index 76a01c13f3..609fd83f30 100644 --- a/src/Components/test/E2ETest/ServerExecutionTests/GlobalizationTest.cs +++ b/src/Components/test/E2ETest/ServerExecutionTests/GlobalizationTest.cs @@ -37,7 +37,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests [Theory] [InlineData("en-US")] [InlineData("fr-FR")] - public void CanSetCultureAndParseCultueSensitiveNumbersAndDates(string culture) + public void CanSetCultureAndParseCultureSensitiveNumbersAndDates(string culture) { var cultureInfo = CultureInfo.GetCultureInfo(culture); SetCulture(culture); @@ -186,6 +186,18 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests Browser.Equal(90000000000.ToString(cultureInfo), () => display.Text); Browser.Equal(90000000000.ToString(CultureInfo.InvariantCulture), () => input.GetAttribute("value")); + // short + input = Browser.FindElement(By.Id("inputnumber_short")); + display = Browser.FindElement(By.Id("inputnumber_short_value")); + Browser.Equal(42.ToString(cultureInfo), () => display.Text); + Browser.Equal(42.ToString(CultureInfo.InvariantCulture), () => input.GetAttribute("value")); + + input.Clear(); + input.SendKeys(127.ToString(CultureInfo.InvariantCulture)); + input.SendKeys("\t"); + Browser.Equal(127.ToString(cultureInfo), () => display.Text); + Browser.Equal(127.ToString(CultureInfo.InvariantCulture), () => input.GetAttribute("value")); + // decimal input = Browser.FindElement(By.Id("inputnumber_decimal")); display = Browser.FindElement(By.Id("inputnumber_decimal_value")); diff --git a/src/Components/test/E2ETest/ServerExecutionTests/InteropReliabilityTests.cs b/src/Components/test/E2ETest/ServerExecutionTests/InteropReliabilityTests.cs index c3e0626eb6..ef66a592d3 100644 --- a/src/Components/test/E2ETest/ServerExecutionTests/InteropReliabilityTests.cs +++ b/src/Components/test/E2ETest/ServerExecutionTests/InteropReliabilityTests.cs @@ -11,6 +11,7 @@ using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures; using Microsoft.AspNetCore.Components.RenderTree; using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.SignalR.Client; +using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.Logging; using TestServer; using Xunit; @@ -18,6 +19,7 @@ using Xunit.Abstractions; namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests { + [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/19666")] public class InteropReliabilityTests : IgnitorTest { public InteropReliabilityTests(BasicTestAppServerSiteFixture serverFixture, ITestOutputHelper output) @@ -213,6 +215,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests } [Fact] + [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/19410")] public async Task ContinuesWorkingAfterInvalidAsyncReturnCallback() { // Arrange diff --git a/src/Components/test/E2ETest/ServerExecutionTests/RemoteRendererBufferLimitTest.cs b/src/Components/test/E2ETest/ServerExecutionTests/RemoteRendererBufferLimitTest.cs index 5e9e2ac4e9..3f4958bdc3 100644 --- a/src/Components/test/E2ETest/ServerExecutionTests/RemoteRendererBufferLimitTest.cs +++ b/src/Components/test/E2ETest/ServerExecutionTests/RemoteRendererBufferLimitTest.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Threading.Tasks; using Ignitor; using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures; +using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.Logging; using TestServer; using Xunit; @@ -13,6 +14,7 @@ using Xunit.Abstractions; namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests { + [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/19666")] public class RemoteRendererBufferLimitTest : IgnitorTest { public RemoteRendererBufferLimitTest(BasicTestAppServerSiteFixture serverFixture, ITestOutputHelper output) @@ -31,7 +33,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests await Client.SelectAsync("test-selector-select", "BasicTestApp.LimitCounterComponent"); Client.ConfirmRenderBatch = false; - for (int i = 0; i < 10; i++) + for (var i = 0; i < 10; i++) { await Client.ClickAsync("increment"); } diff --git a/src/Components/test/E2ETest/ServerExecutionTests/ServerComponentRenderingTest.cs b/src/Components/test/E2ETest/ServerExecutionTests/ServerComponentRenderingTest.cs index 25b7b552af..de7a9321e9 100644 --- a/src/Components/test/E2ETest/ServerExecutionTests/ServerComponentRenderingTest.cs +++ b/src/Components/test/E2ETest/ServerExecutionTests/ServerComponentRenderingTest.cs @@ -6,8 +6,8 @@ using BasicTestApp; using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures; using Microsoft.AspNetCore.Components.E2ETest.Tests; using Microsoft.AspNetCore.E2ETesting; +using Microsoft.AspNetCore.Testing; using OpenQA.Selenium; -using OpenQA.Selenium.Support.UI; using Xunit; using Xunit.Abstractions; @@ -35,5 +35,10 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests $"{typeof(InvalidOperationException).FullName}: The current thread is not associated with the Dispatcher. Use InvokeAsync() to switch execution to the Dispatcher when triggering rendering or component state.", () => result.Text); } + + [Fact] + [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/19413")] + public override void CanDispatchAsyncWorkToSyncContext() + => base.CanDispatchAsyncWorkToSyncContext(); } } diff --git a/src/Components/test/E2ETest/Tests/ErrorNotificationServerSideTest.cs b/src/Components/test/E2ETest/ServerExecutionTests/ServerErrorNotificationTest.cs similarity index 85% rename from src/Components/test/E2ETest/Tests/ErrorNotificationServerSideTest.cs rename to src/Components/test/E2ETest/ServerExecutionTests/ServerErrorNotificationTest.cs index 6d2e860573..77b7da75d6 100644 --- a/src/Components/test/E2ETest/Tests/ErrorNotificationServerSideTest.cs +++ b/src/Components/test/E2ETest/ServerExecutionTests/ServerErrorNotificationTest.cs @@ -5,16 +5,15 @@ using BasicTestApp; using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures; using Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests; using Microsoft.AspNetCore.E2ETesting; -using OpenQA.Selenium; using Xunit; using Xunit.Abstractions; namespace Microsoft.AspNetCore.Components.E2ETest.Tests { [Collection("ErrorNotification")] // When the clientside and serverside tests run together it seems to cause failures, possibly due to connection lose on exception. - public class ErrorNotificationServerSideTest : ErrorNotificationClientSideTest + public class ServerErrorNotificationTest : ErrorNotificationTest { - public ErrorNotificationServerSideTest( + public ServerErrorNotificationTest( BrowserFixture browserFixture, ToggleExecutionModeServerFixture serverFixture, ITestOutputHelper output) diff --git a/src/Components/test/E2ETest/Tests/BindTest.cs b/src/Components/test/E2ETest/Tests/BindTest.cs index 40e9494a9b..70fc700e89 100644 --- a/src/Components/test/E2ETest/Tests/BindTest.cs +++ b/src/Components/test/E2ETest/Tests/BindTest.cs @@ -214,6 +214,11 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests Browser.FindElement(By.Id("select-box-add-option")).Click(); Browser.Equal("Fourth", () => boundValue.Text); Assert.Equal("Fourth choice", target.SelectedOption.Text); + + // Verify we can select options whose value is empty + // https://github.com/dotnet/aspnetcore/issues/17735 + target.SelectByText("Empty value"); + Browser.Equal(string.Empty, () => boundValue.Text); } [Fact] @@ -227,6 +232,11 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests // Modify target; verify value is updated target.SelectByText("Third choice"); Browser.Equal("Third", () => boundValue.Text); + + // Verify we can select options whose value is empty + // https://github.com/dotnet/aspnetcore/issues/17735 + target.SelectByText("Empty value"); + Browser.Equal(string.Empty, () => boundValue.Text); } [Fact] @@ -345,6 +355,65 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests Assert.Equal(string.Empty, mirrorValue.GetAttribute("value")); } + [Fact] + public void CanBindTextboxShort() + { + var target = Browser.FindElement(By.Id("textbox-short")); + var boundValue = Browser.FindElement(By.Id("textbox-short-value")); + var mirrorValue = Browser.FindElement(By.Id("textbox-short-mirror")); + Assert.Equal("-42", target.GetAttribute("value")); + Assert.Equal("-42", boundValue.Text); + Assert.Equal("-42", mirrorValue.GetAttribute("value")); + + // Clear target; value resets to zero + target.Clear(); + Browser.Equal("0", () => target.GetAttribute("value")); + Assert.Equal("0", boundValue.Text); + Assert.Equal("0", mirrorValue.GetAttribute("value")); + + // Modify target; verify value is updated and that textboxes linked to the same data are updated + // Leading zeros are not preserved + target.SendKeys("42"); + Browser.Equal("042", () => target.GetAttribute("value")); + target.SendKeys("\t"); + Browser.Equal("42", () => target.GetAttribute("value")); + Assert.Equal("42", boundValue.Text); + Assert.Equal("42", mirrorValue.GetAttribute("value")); + } + + [Fact] + public void CanBindTextboxNullableShort() + { + var target = Browser.FindElement(By.Id("textbox-nullable-short")); + var boundValue = Browser.FindElement(By.Id("textbox-nullable-short-value")); + var mirrorValue = Browser.FindElement(By.Id("textbox-nullable-short-mirror")); + Assert.Equal(string.Empty, target.GetAttribute("value")); + Assert.Equal(string.Empty, boundValue.Text); + Assert.Equal(string.Empty, mirrorValue.GetAttribute("value")); + + // Modify target; verify value is updated and that textboxes linked to the same data are updated + target.Clear(); + Browser.Equal("", () => boundValue.Text); + Assert.Equal("", mirrorValue.GetAttribute("value")); + + // Modify target; verify value is updated and that textboxes linked to the same data are updated + target.SendKeys("-42\t"); + Browser.Equal("-42", () => boundValue.Text); + Assert.Equal("-42", mirrorValue.GetAttribute("value")); + + // Modify target; verify value is updated and that textboxes linked to the same data are updated + target.Clear(); + target.SendKeys("42\t"); + Browser.Equal("42", () => boundValue.Text); + Assert.Equal("42", mirrorValue.GetAttribute("value")); + + // Modify target; verify value is updated and that textboxes linked to the same data are updated + target.Clear(); + target.SendKeys("\t"); + Browser.Equal(string.Empty, () => boundValue.Text); + Assert.Equal(string.Empty, mirrorValue.GetAttribute("value")); + } + [Fact] public void CanBindTextboxFloat() { diff --git a/src/Components/test/E2ETest/Tests/ComponentRenderingTest.cs b/src/Components/test/E2ETest/Tests/ComponentRenderingTest.cs index fa93b85717..372995c2f5 100644 --- a/src/Components/test/E2ETest/Tests/ComponentRenderingTest.cs +++ b/src/Components/test/E2ETest/Tests/ComponentRenderingTest.cs @@ -12,6 +12,7 @@ using BasicTestApp.HierarchicalImportsTest.Subdir; using Microsoft.AspNetCore.Components.E2ETest.Infrastructure; using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures; using Microsoft.AspNetCore.E2ETesting; +using Microsoft.AspNetCore.Testing; using OpenQA.Selenium; using OpenQA.Selenium.Support.UI; using Xunit; @@ -579,7 +580,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests } [Fact] - public void CanDispatchAsyncWorkToSyncContext() + public virtual void CanDispatchAsyncWorkToSyncContext() { var appElement = Browser.MountTestComponent(); var result = appElement.FindElement(By.Id("result")); @@ -626,7 +627,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests Browser.Exists(incompleteItemsSelector); // Mark first item as done; observe the remaining incomplete item appears unchecked - // because the diff algoritm explicitly unchecks it + // because the diff algorithm explicitly unchecks it appElement.FindElement(By.CssSelector(".incomplete-items .item-isdone")).Click(); Browser.True(() => { @@ -636,7 +637,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests }); // Mark first done item as not done; observe the remaining complete item appears checked - // because the diff algoritm explicitly re-checks it + // because the diff algorithm explicitly re-checks it appElement.FindElement(By.CssSelector(".complete-items .item-isdone")).Click(); Browser.True(() => { diff --git a/src/Components/test/E2ETest/Tests/ErrorNotificationClientSideTest.cs b/src/Components/test/E2ETest/Tests/ErrorNotificationTest.cs similarity index 94% rename from src/Components/test/E2ETest/Tests/ErrorNotificationClientSideTest.cs rename to src/Components/test/E2ETest/Tests/ErrorNotificationTest.cs index 7c0705acde..883d1bc5ab 100644 --- a/src/Components/test/E2ETest/Tests/ErrorNotificationClientSideTest.cs +++ b/src/Components/test/E2ETest/Tests/ErrorNotificationTest.cs @@ -13,9 +13,9 @@ using Xunit.Abstractions; namespace Microsoft.AspNetCore.Components.E2ETest.Tests { [Collection("ErrorNotification")] // When the clientside and serverside tests run together it seems to cause failures, possibly due to connection lose on exception. - public class ErrorNotificationClientSideTest : ServerTestBase> + public class ErrorNotificationTest : ServerTestBase> { - public ErrorNotificationClientSideTest( + public ErrorNotificationTest( BrowserFixture browserFixture, ToggleExecutionModeServerFixture serverFixture, ITestOutputHelper output) diff --git a/src/Components/test/E2ETest/Tests/EventTest.cs b/src/Components/test/E2ETest/Tests/EventTest.cs index 5cbf1c3ac2..d2fd55b195 100644 --- a/src/Components/test/E2ETest/Tests/EventTest.cs +++ b/src/Components/test/E2ETest/Tests/EventTest.cs @@ -167,7 +167,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests } [Fact] - [Flaky("https://github.com/aspnet/AspNetCore-Internal/issues/1987", FlakyOn.AzP.Windows)] + [QuarantinedTest("https://github.com/dotnet/aspnetcore-internal/issues/1987")] public void InputEvent_RespondsOnKeystrokes() { Browser.MountTestComponent(); @@ -191,7 +191,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests // up for a bit it doesn't cause typing to lose keystrokes. But when running server-side, this // shows that network latency doesn't cause keystrokes to be lost even if: // [1] By the time a keystroke event arrives, the event handler ID has since changed - // [2] We have the situation described under "the problem" at https://github.com/aspnet/AspNetCore/issues/8204#issuecomment-493986702 + // [2] We have the situation described under "the problem" at https://github.com/dotnet/aspnetcore/issues/8204#issuecomment-493986702 Browser.MountTestComponent(); diff --git a/src/Components/test/E2ETest/Tests/FormsTest.cs b/src/Components/test/E2ETest/Tests/FormsTest.cs index edd7404f6f..6db7988271 100644 --- a/src/Components/test/E2ETest/Tests/FormsTest.cs +++ b/src/Components/test/E2ETest/Tests/FormsTest.cs @@ -42,7 +42,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests [Fact] public async Task EditFormWorksWithDataAnnotationsValidator() { - var appElement = MountSimpleValidationComponent();; + var appElement = MountSimpleValidationComponent(); var form = appElement.FindElement(By.TagName("form")); var userNameInput = appElement.FindElement(By.ClassName("user-name")).FindElement(By.TagName("input")); var acceptsTermsInput = appElement.FindElement(By.ClassName("accepts-terms")).FindElement(By.TagName("input")); diff --git a/src/Components/test/E2ETest/Tests/MonoSanityTest.cs b/src/Components/test/E2ETest/Tests/MonoSanityTest.cs index 1fb1b26ec9..b8db27fd2b 100644 --- a/src/Components/test/E2ETest/Tests/MonoSanityTest.cs +++ b/src/Components/test/E2ETest/Tests/MonoSanityTest.cs @@ -74,14 +74,6 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests Assert.Contains("Hello from test", GetValue(Browser, "triggerExceptionMessageStackTrace")); } - [Fact] - public void ProvidesDiagnosticIfInvokingWipedMethod() - { - Browser.FindElement(By.CssSelector("#invokeWipedMethod button")).Click(); - - Assert.Contains("System.NotImplementedException: Cannot invoke method because it was wiped. See stack trace for details.", GetValue(Browser, "invokeWipedMethodStackTrace")); - } - [Fact] public void CanCallJavaScriptFromDotNet() { diff --git a/src/Components/test/E2ETest/Tests/PerformanceTest.cs b/src/Components/test/E2ETest/Tests/PerformanceTest.cs index 652226bf26..f7187a4557 100644 --- a/src/Components/test/E2ETest/Tests/PerformanceTest.cs +++ b/src/Components/test/E2ETest/Tests/PerformanceTest.cs @@ -13,11 +13,11 @@ using Xunit.Abstractions; namespace Microsoft.AspNetCore.Components.E2ETest.Tests { public class PerformanceTest - : ServerTestBase> + : ServerTestBase> { public PerformanceTest( BrowserFixture browserFixture, - DevHostServerFixture serverFixture, + DevHostServerFixture serverFixture, ITestOutputHelper output) : base(browserFixture, serverFixture, output) { @@ -52,10 +52,8 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests () => runAllButton.Displayed || Browser.FindElements(By.CssSelector(".benchmark-error")).Any(), TimeSpan.FromSeconds(60)); - var finishedBenchmarks = Browser.FindElements(By.CssSelector(".benchmark-idle")); - var failedBenchmarks = Browser.FindElements(By.CssSelector(".benchmark-error")); - Assert.NotEmpty(finishedBenchmarks); - Assert.Empty(failedBenchmarks); + Browser.DoesNotExist(By.CssSelector(".benchmark-error")); // no failures + Browser.Exists(By.CssSelector(".benchmark-idle")); // everything's done } } } diff --git a/src/Components/test/E2ETest/Tests/StartupErrorNotificationTest.cs b/src/Components/test/E2ETest/Tests/StartupErrorNotificationTest.cs new file mode 100644 index 0000000000..75359253c0 --- /dev/null +++ b/src/Components/test/E2ETest/Tests/StartupErrorNotificationTest.cs @@ -0,0 +1,40 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using BasicTestApp; +using Microsoft.AspNetCore.E2ETesting; +using Microsoft.AspNetCore.Components.E2ETest.Infrastructure; +using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures; +using OpenQA.Selenium; +using Xunit.Abstractions; +using Xunit; + +namespace Microsoft.AspNetCore.Components.E2ETest.Tests +{ + public class StartupErrorNotificationTest : ServerTestBase> + { + public StartupErrorNotificationTest( + BrowserFixture browserFixture, + DevHostServerFixture serverFixture, + ITestOutputHelper output) + : base(browserFixture, serverFixture, output) + { + _serverFixture.PathBase = ServerPathBase; + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void DisplaysNotificationForStartupException(bool errorIsAsync) + { + var url = $"{ServerPathBase}?error={(errorIsAsync ? "async" : "sync")}"; + + Navigate(url, noReload: true); + var errorUiElem = Browser.Exists(By.Id("blazor-error-ui"), TimeSpan.FromSeconds(10)); + Assert.NotNull(errorUiElem); + + Browser.Equal("block", () => errorUiElem.GetCssValue("display")); + } + } +} diff --git a/src/Components/test/E2ETest/package.json b/src/Components/test/E2ETest/package.json index a84e769eb4..8f9d6e2a12 100644 --- a/src/Components/test/E2ETest/package.json +++ b/src/Components/test/E2ETest/package.json @@ -6,11 +6,11 @@ "private": true, "scripts": { "selenium-standalone": "selenium-standalone", - "prepare": "selenium-standalone install" + "prepare": "selenium-standalone install --config ../../../Shared/E2ETesting/selenium-config.json" }, "author": "", "license": "Apache-2.0", "dependencies": { - "selenium-standalone": "^6.15.4" + "selenium-standalone": "^6.17.0" } } diff --git a/src/Components/test/E2ETest/yarn.lock b/src/Components/test/E2ETest/yarn.lock index 6f2b1cc3e3..937ef009e7 100644 --- a/src/Components/test/E2ETest/yarn.lock +++ b/src/Components/test/E2ETest/yarn.lock @@ -432,10 +432,10 @@ safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== -selenium-standalone@^6.15.4: - version "6.16.0" - resolved "https://registry.yarnpkg.com/selenium-standalone/-/selenium-standalone-6.16.0.tgz#ffcf02665c58ff7a7472427ae819ba79c15967ac" - integrity sha512-tl7HFH2FOxJD1is7Pzzsl0pY4vuePSdSWiJdPn+6ETBkpeJDiuzou8hBjvWYWpD+eIVcOrmy3L0R3GzkdHLzDw== +selenium-standalone@^6.17.0: + version "6.17.0" + resolved "https://registry.yarnpkg.com/selenium-standalone/-/selenium-standalone-6.17.0.tgz#0f24b691836205ee9bc3d7a6f207ebcb28170cd9" + integrity sha512-5PSnDHwMiq+OCiAGlhwQ8BM9xuwFfvBOZ7Tfbw+ifkTnOy0PWbZmI1B9gPGuyGHpbQ/3J3CzIK7BYwrQ7EjtWQ== dependencies: async "^2.6.2" commander "^2.19.0" diff --git a/src/Components/test/testassets/BasicTestApp/BasicTestApp.csproj b/src/Components/test/testassets/BasicTestApp/BasicTestApp.csproj index 98357d0e88..9914ec4521 100644 --- a/src/Components/test/testassets/BasicTestApp/BasicTestApp.csproj +++ b/src/Components/test/testassets/BasicTestApp/BasicTestApp.csproj @@ -1,7 +1,7 @@ - netstandard2.0 + netstandard2.1 3.0 true diff --git a/src/Components/test/testassets/BasicTestApp/BindCasesComponent.razor b/src/Components/test/testassets/BasicTestApp/BindCasesComponent.razor index 71024bffb8..fafd59d49b 100644 --- a/src/Components/test/testassets/BasicTestApp/BindCasesComponent.razor +++ b/src/Components/test/testassets/BasicTestApp/BindCasesComponent.razor @@ -50,6 +50,18 @@ @textboxNullableLongValue

+

+ short: + + @textboxShortValue + +

+

+ Nullable short: + + @textboxNullableShortValue + +

float: @@ -229,6 +241,7 @@ + @@ -326,6 +340,8 @@ int? textboxNullableIntValue = null; long textboxLongValue = 3_000_000_000; long? textboxNullableLongValue = null; + short textboxShortValue = -42; + short? textboxNullableShortValue = null; float textboxFloatValue = 3.141f; float? textboxNullableFloatValue = null; double textboxDoubleValue = 3.14159265359d; @@ -368,8 +384,8 @@ bool includeFourthOption = false; enum SelectableValue { First, Second, Third, Fourth } - SelectableValue selectValue = SelectableValue.Second; - SelectableValue selectMarkupValue = SelectableValue.Second; + SelectableValue? selectValue = SelectableValue.Second; + SelectableValue? selectMarkupValue = SelectableValue.Second; void AddAndSelectNewSelectOption() { diff --git a/src/Components/test/testassets/BasicTestApp/GlobalizationBindCases.razor b/src/Components/test/testassets/BasicTestApp/GlobalizationBindCases.razor index 5294f2b1df..04b93ea0d3 100644 --- a/src/Components/test/testassets/BasicTestApp/GlobalizationBindCases.razor +++ b/src/Components/test/testassets/BasicTestApp/GlobalizationBindCases.razor @@ -66,6 +66,10 @@ long: @inputNumberLong +

+ short: + @inputNumberShort +
decimal: @inputNumberDecimal @@ -104,6 +108,7 @@ int inputNumberInt = 42; long inputNumberLong = 4200; + short inputNumberShort = 42; decimal inputNumberDecimal = 4.2m; DateTime inputDateDateTime = new DateTime(1985, 3, 4); diff --git a/src/Components/test/testassets/BasicTestApp/Index.razor b/src/Components/test/testassets/BasicTestApp/Index.razor index 0548f382a8..eff29276a4 100644 --- a/src/Components/test/testassets/BasicTestApp/Index.razor +++ b/src/Components/test/testassets/BasicTestApp/Index.razor @@ -87,12 +87,6 @@ @((RenderFragment)RenderSelectedComponent) -
- An unhandled error has occurred. - Reload - 🗙 -
- @code { string SelectedComponentTypeName { get; set; } = "none"; diff --git a/src/Components/test/testassets/BasicTestApp/Program.cs b/src/Components/test/testassets/BasicTestApp/Program.cs index cebb226e7c..2be7d81b4e 100644 --- a/src/Components/test/testassets/BasicTestApp/Program.cs +++ b/src/Components/test/testassets/BasicTestApp/Program.cs @@ -1,15 +1,20 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.Globalization; +using System.Threading.Tasks; using Microsoft.AspNetCore.Blazor.Hosting; +using Mono.WebAssembly.Interop; namespace BasicTestApp { public class Program { - public static void Main(string[] args) + public static async Task Main(string[] args) { + await SimulateErrorsIfNeededForTest(); + // We want the culture to be en-US so that the tests for bind can work consistently. CultureInfo.CurrentCulture = new CultureInfo("en-US"); @@ -19,5 +24,22 @@ namespace BasicTestApp public static IWebAssemblyHostBuilder CreateHostBuilder(string[] args) => BlazorWebAssemblyHost.CreateDefaultBuilder() .UseBlazorStartup(); + + // Supports E2E tests in StartupErrorNotificationTest + private static async Task SimulateErrorsIfNeededForTest() + { + var currentUrl = new MonoWebAssemblyJSRuntime().Invoke("getCurrentUrl"); + if (currentUrl.Contains("error=sync")) + { + throw new InvalidTimeZoneException("This is a synchronous startup exception"); + } + + await Task.Yield(); + + if (currentUrl.Contains("error=async")) + { + throw new InvalidTimeZoneException("This is an asynchronous startup exception"); + } + } } } diff --git a/src/Components/test/testassets/BasicTestApp/ServerReliability/ReliabilityComponent.razor b/src/Components/test/testassets/BasicTestApp/ServerReliability/ReliabilityComponent.razor index 13256fb7bc..77fbab5eb5 100644 --- a/src/Components/test/testassets/BasicTestApp/ServerReliability/ReliabilityComponent.razor +++ b/src/Components/test/testassets/BasicTestApp/ServerReliability/ReliabilityComponent.razor @@ -18,7 +18,7 @@

Error = @errorFailure

- + diff --git a/src/Components/test/testassets/BasicTestApp/wwwroot/index.html b/src/Components/test/testassets/BasicTestApp/wwwroot/index.html index 517d9d6fcc..a37c08a7d1 100644 --- a/src/Components/test/testassets/BasicTestApp/wwwroot/index.html +++ b/src/Components/test/testassets/BasicTestApp/wwwroot/index.html @@ -14,6 +14,13 @@ Loading... + + + @@ -27,6 +34,10 @@ function navigationManagerNavigate() { Blazor.navigateTo('/subdir/some-path'); } + + function getCurrentUrl() { + return location.href; + } diff --git a/src/Components/test/testassets/BasicTestApp/wwwroot/style.css b/src/Components/test/testassets/BasicTestApp/wwwroot/style.css index 777375d9e0..ea9900430b 100644 --- a/src/Components/test/testassets/BasicTestApp/wwwroot/style.css +++ b/src/Components/test/testassets/BasicTestApp/wwwroot/style.css @@ -7,11 +7,23 @@ } #blazor-error-ui { + background: lightyellow; + bottom: 0; + box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2); display: none; + left: 0; + padding: 0.6rem 1.25rem 0.7rem 1.25rem; + position: fixed; + width: 100%; + z-index: 1000; + box-sizing: border-box; } - #blazor-error-ui dismiss { + #blazor-error-ui .dismiss { cursor: pointer; + position: absolute; + right: 0.75rem; + top: 0.5rem; } .validation-message { diff --git a/src/Components/test/testassets/TestServer/Components.TestServer.csproj b/src/Components/test/testassets/TestServer/Components.TestServer.csproj index d512e61f61..4eebc1f24b 100644 --- a/src/Components/test/testassets/TestServer/Components.TestServer.csproj +++ b/src/Components/test/testassets/TestServer/Components.TestServer.csproj @@ -5,7 +5,7 @@ false - + diff --git a/src/Components/test/testassets/TestServer/Pages/_ServerHost.cshtml b/src/Components/test/testassets/TestServer/Pages/_ServerHost.cshtml index af2f28f658..b0ba837af2 100644 --- a/src/Components/test/testassets/TestServer/Pages/_ServerHost.cshtml +++ b/src/Components/test/testassets/TestServer/Pages/_ServerHost.cshtml @@ -17,6 +17,12 @@ +
+ An unhandled error has occurred. + Reload + 🗙 +
+ - diff --git a/src/Components/Blazor/Templates/src/content/BlazorWasm-CSharp/Client/wwwroot/sample-data/weather.json b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/wwwroot/sample-data/weather.json similarity index 100% rename from src/Components/Blazor/Templates/src/content/BlazorWasm-CSharp/Client/wwwroot/sample-data/weather.json rename to src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/wwwroot/sample-data/weather.json diff --git a/src/Components/Blazor/Templates/src/content/BlazorWasm-CSharp/Server/Controllers/WeatherForecastController.cs b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Server/Controllers/WeatherForecastController.cs similarity index 100% rename from src/Components/Blazor/Templates/src/content/BlazorWasm-CSharp/Server/Controllers/WeatherForecastController.cs rename to src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Server/Controllers/WeatherForecastController.cs diff --git a/src/Components/Blazor/Templates/src/content/BlazorWasm-CSharp/Server/Program.cs b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Server/Program.cs similarity index 100% rename from src/Components/Blazor/Templates/src/content/BlazorWasm-CSharp/Server/Program.cs rename to src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Server/Program.cs diff --git a/src/Components/Blazor/Templates/src/content/BlazorWasm-CSharp/Server/Startup.cs b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Server/Startup.cs similarity index 100% rename from src/Components/Blazor/Templates/src/content/BlazorWasm-CSharp/Server/Startup.cs rename to src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Server/Startup.cs diff --git a/src/Components/Blazor/Templates/src/content/BlazorWasm-CSharp/Shared/WeatherForecast.cs b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Shared/WeatherForecast.cs similarity index 100% rename from src/Components/Blazor/Templates/src/content/BlazorWasm-CSharp/Shared/WeatherForecast.cs rename to src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Shared/WeatherForecast.cs diff --git a/src/Components/Blazor/Templates/src/content/Directory.Build.props b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/Directory.Build.props similarity index 100% rename from src/Components/Blazor/Templates/src/content/Directory.Build.props rename to src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/Directory.Build.props diff --git a/src/Components/Blazor/Templates/src/content/Directory.Build.targets b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/Directory.Build.targets similarity index 100% rename from src/Components/Blazor/Templates/src/content/Directory.Build.targets rename to src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/Directory.Build.targets diff --git a/src/ProjectTemplates/Directory.Build.props b/src/ProjectTemplates/Directory.Build.props index c12abcf463..c97ca68bd0 100644 --- a/src/ProjectTemplates/Directory.Build.props +++ b/src/ProjectTemplates/Directory.Build.props @@ -8,4 +8,10 @@ + + + + PreserveNewest + + diff --git a/src/ProjectTemplates/ProjectTemplates.sln b/src/ProjectTemplates/ProjectTemplates.sln index da18f3a2e5..7628d8233d 100644 --- a/src/ProjectTemplates/ProjectTemplates.sln +++ b/src/ProjectTemplates/ProjectTemplates.sln @@ -177,6 +177,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.ApiAut EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.SpaServices.Extensions", "..\Middleware\SpaServices.Extensions\src\Microsoft.AspNetCore.SpaServices.Extensions.csproj", "{06D0D7B2-EDA3-45A2-A060-AB791ED1DB80}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Blazor.Templates", "BlazorWasm.ProjectTemplates\Microsoft.AspNetCore.Blazor.Templates.csproj", "{C6D371A8-D8EA-4CF4-844E-8C6DFDF61642}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -1231,6 +1233,18 @@ Global {06D0D7B2-EDA3-45A2-A060-AB791ED1DB80}.Release|x64.Build.0 = Release|Any CPU {06D0D7B2-EDA3-45A2-A060-AB791ED1DB80}.Release|x86.ActiveCfg = Release|Any CPU {06D0D7B2-EDA3-45A2-A060-AB791ED1DB80}.Release|x86.Build.0 = Release|Any CPU + {C6D371A8-D8EA-4CF4-844E-8C6DFDF61642}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C6D371A8-D8EA-4CF4-844E-8C6DFDF61642}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C6D371A8-D8EA-4CF4-844E-8C6DFDF61642}.Debug|x64.ActiveCfg = Debug|Any CPU + {C6D371A8-D8EA-4CF4-844E-8C6DFDF61642}.Debug|x64.Build.0 = Debug|Any CPU + {C6D371A8-D8EA-4CF4-844E-8C6DFDF61642}.Debug|x86.ActiveCfg = Debug|Any CPU + {C6D371A8-D8EA-4CF4-844E-8C6DFDF61642}.Debug|x86.Build.0 = Debug|Any CPU + {C6D371A8-D8EA-4CF4-844E-8C6DFDF61642}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C6D371A8-D8EA-4CF4-844E-8C6DFDF61642}.Release|Any CPU.Build.0 = Release|Any CPU + {C6D371A8-D8EA-4CF4-844E-8C6DFDF61642}.Release|x64.ActiveCfg = Release|Any CPU + {C6D371A8-D8EA-4CF4-844E-8C6DFDF61642}.Release|x64.Build.0 = Release|Any CPU + {C6D371A8-D8EA-4CF4-844E-8C6DFDF61642}.Release|x86.ActiveCfg = Release|Any CPU + {C6D371A8-D8EA-4CF4-844E-8C6DFDF61642}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/ProjectTemplates/Web.Client.ItemTemplates/Microsoft.DotNet.Web.Client.ItemTemplates.csproj b/src/ProjectTemplates/Web.Client.ItemTemplates/Microsoft.DotNet.Web.Client.ItemTemplates.csproj index 27842cb73b..7ea486e4b2 100644 --- a/src/ProjectTemplates/Web.Client.ItemTemplates/Microsoft.DotNet.Web.Client.ItemTemplates.csproj +++ b/src/ProjectTemplates/Web.Client.ItemTemplates/Microsoft.DotNet.Web.Client.ItemTemplates.csproj @@ -3,7 +3,7 @@ $(DefaultNetCoreTargetFramework) Web Client-Side File Templates for Microsoft Template Engine - true + true diff --git a/src/ProjectTemplates/Web.ItemTemplates/Microsoft.DotNet.Web.ItemTemplates.csproj b/src/ProjectTemplates/Web.ItemTemplates/Microsoft.DotNet.Web.ItemTemplates.csproj index a1f51b94f5..d639fb1e0a 100644 --- a/src/ProjectTemplates/Web.ItemTemplates/Microsoft.DotNet.Web.ItemTemplates.csproj +++ b/src/ProjectTemplates/Web.ItemTemplates/Microsoft.DotNet.Web.ItemTemplates.csproj @@ -3,7 +3,7 @@ $(DefaultNetCoreTargetFramework) Web File Templates for Microsoft Template Engine. - true + true diff --git a/src/ProjectTemplates/Web.ProjectTemplates/Microsoft.DotNet.Web.ProjectTemplates.csproj b/src/ProjectTemplates/Web.ProjectTemplates/Microsoft.DotNet.Web.ProjectTemplates.csproj index ef85eb3657..5e3b743ff6 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/Microsoft.DotNet.Web.ProjectTemplates.csproj +++ b/src/ProjectTemplates/Web.ProjectTemplates/Microsoft.DotNet.Web.ProjectTemplates.csproj @@ -4,7 +4,7 @@ $(DefaultNetCoreTargetFramework) Microsoft.DotNet.Web.ProjectTemplates.$(AspNetCoreMajorVersion).$(AspNetCoreMinorVersion) ASP.NET Core Web Template Pack for Microsoft Template Engine - true + true diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/.template.config/template.json b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/.template.config/template.json index 12fe72720c..66bfdc777b 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/.template.config/template.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/.template.config/template.json @@ -9,10 +9,10 @@ "generatorVersions": "[1.0.0.0-*)", "description": "A project template for creating a Blazor server app that runs server-side inside an ASP.NET Core app and handles user interactions over a SignalR connection. This template can be used for web apps with rich dynamic user interfaces (UIs).", "groupIdentity": "Microsoft.Web.Blazor.Server", - "precedence": "6000", - "identity": "Microsoft.Web.Blazor.Server.CSharp.3.1", + "precedence": "7000", + "identity": "Microsoft.Web.Blazor.Server.CSharp.5.0", "shortName": "blazorserver", - "thirdPartyNotices": "https://aka.ms/aspnetcore/3.1-third-party-notices", + "thirdPartyNotices": "https://aka.ms/aspnetcore/5.0-third-party-notices", "tags": { "language": "C#", "type": "project" @@ -337,12 +337,12 @@ "datatype": "choice", "choices": [ { - "choice": "netcoreapp3.1", - "description": "Target netcoreapp3.1" + "choice": "netcoreapp5.0", + "description": "Target netcoreapp5.0" } ], - "replaces": "netcoreapp3.1", - "defaultValue": "netcoreapp3.1" + "replaces": "netcoreapp5.0", + "defaultValue": "netcoreapp5.0" }, "copyrightYear": { "type": "generated", diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/wwwroot/css/site.css b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/wwwroot/css/site.css index c15c2e1556..f875d35edb 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/wwwroot/css/site.css +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/wwwroot/css/site.css @@ -129,12 +129,12 @@ app { z-index: 1000; } -#blazor-error-ui .dismiss { - cursor: pointer; - position: absolute; - right: 0.75rem; - top: 0.5rem; -} + #blazor-error-ui .dismiss { + cursor: pointer; + position: absolute; + right: 0.75rem; + top: 0.5rem; + } @media (max-width: 767.98px) { .main .top-row:not(.auth) { diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/EmptyWeb-CSharp/.template.config/template.json b/src/ProjectTemplates/Web.ProjectTemplates/content/EmptyWeb-CSharp/.template.config/template.json index 96ac0383be..caf1ab80e6 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/EmptyWeb-CSharp/.template.config/template.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/EmptyWeb-CSharp/.template.config/template.json @@ -9,8 +9,8 @@ "generatorVersions": "[1.0.0.0-*)", "description": "An empty project template for creating an ASP.NET Core application. This template does not have any content in it.", "groupIdentity": "Microsoft.Web.Empty", - "precedence": "6000", - "identity": "Microsoft.Web.Empty.CSharp.3.1", + "precedence": "7000", + "identity": "Microsoft.Web.Empty.CSharp.5.0", "shortName": "web", "tags": { "language": "C#", @@ -86,12 +86,12 @@ "datatype": "choice", "choices": [ { - "choice": "netcoreapp3.1", - "description": "Target netcoreapp3.1" + "choice": "netcoreapp5.0", + "description": "Target netcoreapp5.0" } ], - "replaces": "netcoreapp3.1", - "defaultValue": "netcoreapp3.1" + "replaces": "netcoreapp5.0", + "defaultValue": "netcoreapp5.0" }, "copyrightYear": { "type": "generated", diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/EmptyWeb-FSharp/.template.config/template.json b/src/ProjectTemplates/Web.ProjectTemplates/content/EmptyWeb-FSharp/.template.config/template.json index 1102d7952d..df7b62a169 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/EmptyWeb-FSharp/.template.config/template.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/EmptyWeb-FSharp/.template.config/template.json @@ -8,8 +8,8 @@ "generatorVersions": "[1.0.0.0-*)", "description": "An empty project template for creating an ASP.NET Core application. This template does not have any content in it.", "groupIdentity": "Microsoft.Web.Empty", - "precedence": "6000", - "identity": "Microsoft.Web.Empty.FSharp.3.1", + "precedence": "7000", + "identity": "Microsoft.Web.Empty.FSharp.5.0", "shortName": "web", "tags": { "language": "F#", @@ -82,12 +82,12 @@ "datatype": "choice", "choices": [ { - "choice": "netcoreapp3.1", - "description": "Target netcoreapp3.1" + "choice": "netcoreapp5.0", + "description": "Target netcoreapp5.0" } ], - "replaces": "netcoreapp3.1", - "defaultValue": "netcoreapp3.1" + "replaces": "netcoreapp5.0", + "defaultValue": "netcoreapp5.0" }, "copyrightYear": { "type": "generated", diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/GrpcService-CSharp/.template.config/template.json b/src/ProjectTemplates/Web.ProjectTemplates/content/GrpcService-CSharp/.template.config/template.json index ff8ed336b0..0ca933164a 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/GrpcService-CSharp/.template.config/template.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/GrpcService-CSharp/.template.config/template.json @@ -9,8 +9,8 @@ "generatorVersions": "[1.0.0.0-*)", "description": "A project template for creating a gRPC ASP.NET Core service.", "groupIdentity": "Microsoft.Web.Grpc", - "precedence": "6000", - "identity": "Microsoft.Grpc.Service.CSharp.3.1", + "precedence": "7000", + "identity": "Microsoft.Grpc.Service.CSharp.5.0", "shortName": "grpc", "tags": { "language": "C#", @@ -41,11 +41,11 @@ "datatype": "choice", "choices": [ { - "choice": "netcoreapp3.1", - "description": "Target netcoreapp3.1" + "choice": "netcoreapp5.0", + "description": "Target netcoreapp5.0" } ], - "defaultValue": "netcoreapp3.1" + "defaultValue": "netcoreapp5.0" }, "ExcludeLaunchSettings": { "type": "parameter", diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/GrpcService-CSharp/Properties/launchSettings.json b/src/ProjectTemplates/Web.ProjectTemplates/content/GrpcService-CSharp/Properties/launchSettings.json index edbcd5873c..c0f6adc291 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/GrpcService-CSharp/Properties/launchSettings.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/GrpcService-CSharp/Properties/launchSettings.json @@ -3,7 +3,7 @@ "GrpcService-CSharp": { "commandName": "Project", "launchBrowser": false, - "applicationUrl": "https://localhost:5001", + "applicationUrl": "http://localhost:5000;https://localhost:5001", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/RazorClassLibrary-CSharp/.template.config/template.json b/src/ProjectTemplates/Web.ProjectTemplates/content/RazorClassLibrary-CSharp/.template.config/template.json index 1b4166341a..543e0bdd5d 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/RazorClassLibrary-CSharp/.template.config/template.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/RazorClassLibrary-CSharp/.template.config/template.json @@ -4,15 +4,14 @@ "classifications": [ "Web", "Razor", - "Library", - "Razor Class Library" + "Library" ], "name": "Razor Class Library", "generatorVersions": "[1.0.0.0-*)", "description": "A project for creating a Razor class library that targets .NET Standard", "groupIdentity": "Microsoft.Web.Razor", - "precedence": "6000", - "identity": "Microsoft.Web.Razor.Library.CSharp.3.1", + "precedence": "7000", + "identity": "Microsoft.Web.Razor.Library.CSharp.5.0", "shortName": "razorclasslib", "tags": { "language": "C#", @@ -48,11 +47,11 @@ "datatype": "choice", "choices": [ { - "choice": "netcoreapp3.1", - "description": "Target netcoreapp3.1" + "choice": "netcoreapp5.0", + "description": "Target netcoreapp5.0" } ], - "defaultValue": "netcoreapp3.1" + "defaultValue": "netcoreapp5.0" }, "HostIdentifier": { "type": "bind", diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/.template.config/template.json b/src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/.template.config/template.json index 679e237894..81aacfa7e7 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/.template.config/template.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/.template.config/template.json @@ -10,13 +10,13 @@ "generatorVersions": "[1.0.0.0-*)", "description": "A project template for creating an ASP.NET Core application with example ASP.NET Core Razor Pages content", "groupIdentity": "Microsoft.Web.RazorPages", - "precedence": "6000", - "identity": "Microsoft.Web.RazorPages.CSharp.3.1", + "precedence": "7000", + "identity": "Microsoft.Web.RazorPages.CSharp.5.0", "shortName": [ "webapp", "razor" ], - "thirdPartyNotices": "https://aka.ms/aspnetcore/3.1-third-party-notices", + "thirdPartyNotices": "https://aka.ms/aspnetcore/5.0-third-party-notices", "tags": { "language": "C#", "type": "project" @@ -316,12 +316,12 @@ "datatype": "choice", "choices": [ { - "choice": "netcoreapp3.1", - "description": "Target netcoreapp3.1" + "choice": "netcoreapp5.0", + "description": "Target netcoreapp5.0" } ], - "replaces": "netcoreapp3.1", - "defaultValue": "netcoreapp3.1" + "replaces": "netcoreapp5.0", + "defaultValue": "netcoreapp5.0" }, "copyrightYear": { "type": "generated", diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Shared/_Layout.cshtml b/src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Shared/_Layout.cshtml index ccc07b30f6..835d806c2e 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Shared/_Layout.cshtml +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Shared/_Layout.cshtml @@ -50,6 +50,6 @@ - @RenderSection("Scripts", required: false) + @await RenderSectionAsync("Scripts", required: false) diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/js/site.js b/src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/js/site.js index 3c76e6dc45..ac49c18641 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/js/site.js +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/js/site.js @@ -1,4 +1,4 @@ // Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification // for details on configuring this project to bundle and minify static web assets. -// Write your Javascript code. +// Write your JavaScript code. diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-CSharp/.template.config/template.json b/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-CSharp/.template.config/template.json index 8679ada1c6..d846394fa9 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-CSharp/.template.config/template.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-CSharp/.template.config/template.json @@ -9,10 +9,10 @@ "generatorVersions": "[1.0.0.0-*)", "description": "A project template for creating an ASP.NET Core application with example ASP.NET Core MVC Views and Controllers. This template can also be used for RESTful HTTP services.", "groupIdentity": "Microsoft.Web.Mvc", - "precedence": "6000", - "identity": "Microsoft.Web.Mvc.CSharp.3.1", + "precedence": "7000", + "identity": "Microsoft.Web.Mvc.CSharp.5.0", "shortName": "mvc", - "thirdPartyNotices": "https://aka.ms/aspnetcore/3.1-third-party-notices", + "thirdPartyNotices": "https://aka.ms/aspnetcore/5.0-third-party-notices", "tags": { "language": "C#", "type": "project" @@ -306,12 +306,12 @@ "datatype": "choice", "choices": [ { - "choice": "netcoreapp3.1", - "description": "Target netcoreapp3.1" + "choice": "netcoreapp5.0", + "description": "Target netcoreapp5.0" } ], - "replaces": "netcoreapp3.1", - "defaultValue": "netcoreapp3.1" + "replaces": "netcoreapp5.0", + "defaultValue": "netcoreapp5.0" }, "copyrightYear": { "type": "generated", diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-CSharp/Views/Shared/_Layout.cshtml b/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-CSharp/Views/Shared/_Layout.cshtml index 892d6b9b5d..5234625f8d 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-CSharp/Views/Shared/_Layout.cshtml +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-CSharp/Views/Shared/_Layout.cshtml @@ -48,6 +48,6 @@ - @RenderSection("Scripts", required: false) + @await RenderSectionAsync("Scripts", required: false) diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-FSharp/.template.config/template.json b/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-FSharp/.template.config/template.json index 5ac6316167..779cf7d82c 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-FSharp/.template.config/template.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-FSharp/.template.config/template.json @@ -9,10 +9,10 @@ "generatorVersions": "[1.0.0.0-*)", "description": "A project template for creating an ASP.NET Core application with example ASP.NET Core MVC Views and Controllers. This template can also be used for RESTful HTTP services.", "groupIdentity": "Microsoft.Web.Mvc", - "precedence": "6000", - "identity": "Microsoft.Web.Mvc.FSharp.3.1", + "precedence": "7000", + "identity": "Microsoft.Web.Mvc.FSharp.5.0", "shortName": "mvc", - "thirdPartyNotices": "https://aka.ms/aspnetcore/3.1-third-party-notices", + "thirdPartyNotices": "https://aka.ms/aspnetcore/5.0-third-party-notices", "tags": { "language": "F#", "type": "project" @@ -87,12 +87,12 @@ "datatype": "choice", "choices": [ { - "choice": "netcoreapp3.1", - "description": "Target netcoreapp3.1" + "choice": "netcoreapp5.0", + "description": "Target netcoreapp5.0" } ], - "replaces": "netcoreapp3.1", - "defaultValue": "netcoreapp3.1" + "replaces": "netcoreapp5.0", + "defaultValue": "netcoreapp5.0" }, "copyrightYear": { "type": "generated", diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-FSharp/Views/Shared/_Layout.cshtml b/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-FSharp/Views/Shared/_Layout.cshtml index 6dbe6964a7..b326cdff07 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-FSharp/Views/Shared/_Layout.cshtml +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-FSharp/Views/Shared/_Layout.cshtml @@ -47,6 +47,6 @@ - @RenderSection("Scripts", required: false) + @await RenderSectionAsync("Scripts", required: false) diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/js/site.js b/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/js/site.js index 3c76e6dc45..ac49c18641 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/js/site.js +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/js/site.js @@ -1,4 +1,4 @@ // Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification // for details on configuring this project to bundle and minify static web assets. -// Write your Javascript code. +// Write your JavaScript code. diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/.template.config/template.json b/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/.template.config/template.json index b0facf26da..9c560017be 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/.template.config/template.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/.template.config/template.json @@ -9,8 +9,8 @@ "generatorVersions": "[1.0.0.0-*)", "description": "A project template for creating an ASP.NET Core application with an example Controller for a RESTful HTTP service. This template can also be used for ASP.NET Core MVC Views and Controllers.", "groupIdentity": "Microsoft.Web.WebApi", - "precedence": "6000", - "identity": "Microsoft.Web.WebApi.CSharp.3.1", + "precedence": "7000", + "identity": "Microsoft.Web.WebApi.CSharp.5.0", "shortName": "webapi", "tags": { "language": "C#", @@ -209,12 +209,12 @@ "datatype": "choice", "choices": [ { - "choice": "netcoreapp3.1", - "description": "Target netcoreapp3.1" + "choice": "netcoreapp5.0", + "description": "Target netcoreapp5.0" } ], - "replaces": "netcoreapp3.1", - "defaultValue": "netcoreapp3.1" + "replaces": "netcoreapp5.0", + "defaultValue": "netcoreapp5.0" }, "copyrightYear": { "type": "generated", diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-FSharp/.template.config/template.json b/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-FSharp/.template.config/template.json index 514e77d41b..b3f4e81300 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-FSharp/.template.config/template.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-FSharp/.template.config/template.json @@ -8,8 +8,8 @@ "generatorVersions": "[1.0.0.0-*)", "description": "A project template for creating an ASP.NET Core application with an example Controller for a RESTful HTTP service. This template can also be used for ASP.NET Core MVC Views and Controllers.", "groupIdentity": "Microsoft.Web.WebApi", - "precedence": "6000", - "identity": "Microsoft.Web.WebApi.FSharp.3.1", + "precedence": "7000", + "identity": "Microsoft.Web.WebApi.FSharp.5.0", "shortName": "webapi", "tags": { "language": "F#", @@ -82,12 +82,12 @@ "datatype": "choice", "choices": [ { - "choice": "netcoreapp3.1", - "description": "Target netcoreapp3.1" + "choice": "netcoreapp5.0", + "description": "Target netcoreapp5.0" } ], - "replaces": "netcoreapp3.1", - "defaultValue": "netcoreapp3.1" + "replaces": "netcoreapp5.0", + "defaultValue": "netcoreapp5.0" }, "copyrightYear": { "type": "generated", diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-CSharp/.template.config/template.json b/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-CSharp/.template.config/template.json index 1e9cc2414b..fa3775c564 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-CSharp/.template.config/template.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-CSharp/.template.config/template.json @@ -10,8 +10,8 @@ "generatorVersions": "[1.0.0.0-*)", "description": "An empty project template for creating a worker service.", "groupIdentity": "Microsoft.Worker.Empty", - "precedence": "6000", - "identity": "Microsoft.Worker.Empty.CSharp.3.1", + "precedence": "7000", + "identity": "Microsoft.Worker.Empty.CSharp.5.0", "shortName": "worker", "tags": { "language": "C#", @@ -47,12 +47,12 @@ "datatype": "choice", "choices": [ { - "choice": "netcoreapp3.1", - "description": "Target netcoreapp3.1" + "choice": "netcoreapp5.0", + "description": "Target netcoreapp5.0" } ], - "replaces": "netcoreapp3.1", - "defaultValue": "netcoreapp3.1" + "replaces": "netcoreapp5.0", + "defaultValue": "netcoreapp5.0" }, "copyrightYear": { "type": "generated", diff --git a/src/ProjectTemplates/Web.Spa.ProjectTemplates/Microsoft.DotNet.Web.Spa.ProjectTemplates.csproj b/src/ProjectTemplates/Web.Spa.ProjectTemplates/Microsoft.DotNet.Web.Spa.ProjectTemplates.csproj index 5849cee8bb..e8e0653f3c 100644 --- a/src/ProjectTemplates/Web.Spa.ProjectTemplates/Microsoft.DotNet.Web.Spa.ProjectTemplates.csproj +++ b/src/ProjectTemplates/Web.Spa.ProjectTemplates/Microsoft.DotNet.Web.Spa.ProjectTemplates.csproj @@ -5,7 +5,9 @@ Microsoft.DotNet.Web.Spa.ProjectTemplates.$(AspNetCoreMajorVersion).$(AspNetCoreMinorVersion) Single Page Application templates for ASP.NET Core $(PackageTags);spa - true + true + + true diff --git a/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/Angular-CSharp/.template.config/template.json b/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/Angular-CSharp/.template.config/template.json index 07b982ad9e..fb376267c2 100644 --- a/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/Angular-CSharp/.template.config/template.json +++ b/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/Angular-CSharp/.template.config/template.json @@ -6,8 +6,8 @@ "SPA" ], "groupIdentity": "Microsoft.DotNet.Web.Spa.ProjectTemplates.Angular", - "precedence": "6000", - "identity": "Microsoft.DotNet.Web.Spa.ProjectTemplates.Angular.CSharp.3.1", + "precedence": "7000", + "identity": "Microsoft.DotNet.Web.Spa.ProjectTemplates.Angular.CSharp.5.0", "name": "ASP.NET Core with Angular", "preferNameDirectory": true, "primaryOutputs": [ @@ -177,12 +177,12 @@ "datatype": "choice", "choices": [ { - "choice": "netcoreapp3.1", - "description": "Target netcoreapp3.1" + "choice": "netcoreapp5.0", + "description": "Target netcoreapp5.0" } ], - "replaces": "netcoreapp3.1", - "defaultValue": "netcoreapp3.1" + "replaces": "netcoreapp5.0", + "defaultValue": "netcoreapp5.0" }, "HostIdentifier": { "type": "bind", diff --git a/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/api-authorization/api-authorization.constants.ts b/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/api-authorization/api-authorization.constants.ts index 566d4c2135..4a52fdee4b 100644 --- a/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/api-authorization/api-authorization.constants.ts +++ b/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/api-authorization/api-authorization.constants.ts @@ -23,7 +23,7 @@ export const LoginActions = { let applicationPaths: ApplicationPathsType = { DefaultLoginRedirectPath: '/', - ApiAuthorizationClientConfigurationUrl: `/_configuration/${ApplicationName}`, + ApiAuthorizationClientConfigurationUrl: `_configuration/${ApplicationName}`, Login: `authentication/${LoginActions.Login}`, LoginFailed: `authentication/${LoginActions.LoginFailed}`, LoginCallback: `authentication/${LoginActions.LoginCallback}`, @@ -40,8 +40,8 @@ let applicationPaths: ApplicationPathsType = { LogOutPathComponents: [], LoggedOutPathComponents: [], LogOutCallbackPathComponents: [], - IdentityRegisterPath: '/Identity/Account/Register', - IdentityManagePath: '/Identity/Account/Manage' + IdentityRegisterPath: 'Identity/Account/Register', + IdentityManagePath: 'Identity/Account/Manage' }; applicationPaths = { diff --git a/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/api-authorization/authorize.service.ts b/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/api-authorization/authorize.service.ts index 63e7000069..bb4b0b09db 100644 --- a/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/api-authorization/authorize.service.ts +++ b/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/api-authorization/authorize.service.ts @@ -135,7 +135,7 @@ export class AuthorizeService { await this.userManager.signoutRedirect(this.createArguments(state)); return this.redirect(); } catch (redirectSignOutError) { - console.log('Redirect signout error: ', popupSignOutError); + console.log('Redirect signout error: ', redirectSignOutError); return this.error(redirectSignOutError); } } diff --git a/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/api-authorization/login/login.component.ts b/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/api-authorization/login/login.component.ts index f90d0df380..8d312e32fd 100644 --- a/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/api-authorization/login/login.component.ts +++ b/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/api-authorization/login/login.component.ts @@ -101,7 +101,7 @@ export class LoginComponent implements OnInit { private getReturnUrl(state?: INavigationState): string { const fromQuery = (this.activatedRoute.snapshot.queryParams as INavigationState).returnUrl; - // If the url is comming from the query string, check that is either + // If the url is coming from the query string, check that is either // a relative url or an absolute url if (fromQuery && !(fromQuery.startsWith(`${window.location.origin}/`) || diff --git a/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/api-authorization/logout/logout.component.ts b/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/api-authorization/logout/logout.component.ts index 78969d39bf..e99e88d195 100644 --- a/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/api-authorization/logout/logout.component.ts +++ b/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/api-authorization/logout/logout.component.ts @@ -95,7 +95,7 @@ export class LogoutComponent implements OnInit { private getReturnUrl(state?: INavigationState): string { const fromQuery = (this.activatedRoute.snapshot.queryParams as INavigationState).returnUrl; - // If the url is comming from the query string, check that is either + // If the url is coming from the query string, check that is either // a relative url or an absolute url if (fromQuery && !(fromQuery.startsWith(`${window.location.origin}/`) || diff --git a/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/app.module.ts b/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/app.module.ts index f66fa53203..679b31ce33 100644 --- a/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/app.module.ts +++ b/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/app.module.ts @@ -1,7 +1,11 @@ import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { FormsModule } from '@angular/forms'; +////#if (IndividualLocalAuth) import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http'; +////#else +import { HttpClientModule } from '@angular/common/http'; +////#endif import { RouterModule } from '@angular/router'; import { AppComponent } from './app.component'; diff --git a/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/counter/counter.component.spec.ts b/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/counter/counter.component.spec.ts index 026a91a062..37b350cc1c 100644 --- a/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/counter/counter.component.spec.ts +++ b/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/counter/counter.component.spec.ts @@ -3,7 +3,6 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { CounterComponent } from './counter.component'; describe('CounterComponent', () => { - let component: CounterComponent; let fixture: ComponentFixture; beforeEach(async(() => { @@ -15,7 +14,6 @@ describe('CounterComponent', () => { beforeEach(() => { fixture = TestBed.createComponent(CounterComponent); - component = fixture.componentInstance; fixture.detectChanges(); }); diff --git a/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/test.ts b/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/test.ts index 16317897b1..88492582fb 100644 --- a/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/test.ts +++ b/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/test.ts @@ -7,7 +7,7 @@ import { platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing'; -declare const require: any; +declare const require; // First, initialize the Angular testing environment. getTestBed().initTestEnvironment( diff --git a/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/Angular-CSharp/Controllers/OidcConfigurationController.cs b/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/Angular-CSharp/Controllers/OidcConfigurationController.cs index 75e26da4e9..cdcc89182a 100644 --- a/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/Angular-CSharp/Controllers/OidcConfigurationController.cs +++ b/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/Angular-CSharp/Controllers/OidcConfigurationController.cs @@ -6,12 +6,12 @@ namespace Company.WebApplication1.Controllers { public class OidcConfigurationController : Controller { - private readonly ILogger logger; + private readonly ILogger _logger; - public OidcConfigurationController(IClientRequestParametersProvider clientRequestParametersProvider, ILogger _logger) + public OidcConfigurationController(IClientRequestParametersProvider clientRequestParametersProvider, ILogger logger) { ClientRequestParametersProvider = clientRequestParametersProvider; - logger = _logger; + _logger = logger; } public IClientRequestParametersProvider ClientRequestParametersProvider { get; } diff --git a/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/React-CSharp/.template.config/template.json b/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/React-CSharp/.template.config/template.json index c6e9544e43..4b2e6ad786 100644 --- a/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/React-CSharp/.template.config/template.json +++ b/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/React-CSharp/.template.config/template.json @@ -6,8 +6,8 @@ "SPA" ], "groupIdentity": "Microsoft.DotNet.Web.Spa.ProjectTemplates.React", - "precedence": "6000", - "identity": "Microsoft.DotNet.Web.Spa.ProjectTemplates.React.CSharp.3.1", + "precedence": "7000", + "identity": "Microsoft.DotNet.Web.Spa.ProjectTemplates.React.CSharp.5.0", "name": "ASP.NET Core with React.js", "preferNameDirectory": true, "primaryOutputs": [ @@ -178,12 +178,12 @@ "datatype": "choice", "choices": [ { - "choice": "netcoreapp3.1", - "description": "Target netcoreapp3.1" + "choice": "netcoreapp5.0", + "description": "Target netcoreapp5.0" } ], - "replaces": "netcoreapp3.1", - "defaultValue": "netcoreapp3.1" + "replaces": "netcoreapp5.0", + "defaultValue": "netcoreapp5.0" }, "HostIdentifier": { "type": "bind", diff --git a/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/.env b/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/.env new file mode 100644 index 0000000000..6ce384e5ce --- /dev/null +++ b/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/.env @@ -0,0 +1 @@ +BROWSER=none diff --git a/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/src/components/api-authorization/ApiAuthorizationConstants.js b/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/src/components/api-authorization/ApiAuthorizationConstants.js index 2f7d71f92c..7f8eb1e7a9 100644 --- a/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/src/components/api-authorization/ApiAuthorizationConstants.js +++ b/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/src/components/api-authorization/ApiAuthorizationConstants.js @@ -23,7 +23,7 @@ const prefix = '/authentication'; export const ApplicationPaths = { DefaultLoginRedirectPath: '/', - ApiAuthorizationClientConfigurationUrl: `/_configuration/${ApplicationName}`, + ApiAuthorizationClientConfigurationUrl: `_configuration/${ApplicationName}`, ApiAuthorizationPrefix: prefix, Login: `${prefix}/${LoginActions.Login}`, LoginFailed: `${prefix}/${LoginActions.LoginFailed}`, @@ -33,6 +33,6 @@ export const ApplicationPaths = { LogOut: `${prefix}/${LogoutActions.Logout}`, LoggedOut: `${prefix}/${LogoutActions.LoggedOut}`, LogOutCallback: `${prefix}/${LogoutActions.LogoutCallback}`, - IdentityRegisterPath: '/Identity/Account/Register', - IdentityManagePath: '/Identity/Account/Manage' + IdentityRegisterPath: 'Identity/Account/Register', + IdentityManagePath: 'Identity/Account/Manage' }; diff --git a/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/src/components/api-authorization/Login.js b/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/src/components/api-authorization/Login.js index 7ef201c91c..3954657f30 100644 --- a/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/src/components/api-authorization/Login.js +++ b/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/src/components/api-authorization/Login.js @@ -120,7 +120,7 @@ export class Login extends Component { redirectToApiAuthorizationPath(apiAuthorizationPath) { const redirectUrl = `${window.location.origin}${apiAuthorizationPath}`; // It's important that we do a replace here so that when the user hits the back arrow on the - // browser he gets sent back to where it was on the app instead of to an endpoint on this + // browser they get sent back to where it was on the app instead of to an endpoint on this // component. window.location.replace(redirectUrl); } diff --git a/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/.template.config/template.json b/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/.template.config/template.json index 9c7ce60528..f32b4adac4 100644 --- a/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/.template.config/template.json +++ b/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/.template.config/template.json @@ -6,8 +6,8 @@ "SPA" ], "groupIdentity": "Microsoft.DotNet.Web.Spa.ProjectTemplates.ReactRedux", - "precedence": "6000", - "identity": "Microsoft.DotNet.Web.Spa.ProjectTemplates.ReactRedux.CSharp.3.1", + "precedence": "7000", + "identity": "Microsoft.DotNet.Web.Spa.ProjectTemplates.ReactRedux.CSharp.5.0", "name": "ASP.NET Core with React.js and Redux", "preferNameDirectory": true, "primaryOutputs": [ @@ -87,12 +87,12 @@ "datatype": "choice", "choices": [ { - "choice": "netcoreapp3.1", - "description": "Target netcoreapp3.1" + "choice": "netcoreapp5.0", + "description": "Target netcoreapp5.0" } ], - "replaces": "netcoreapp3.1", - "defaultValue": "netcoreapp3.1" + "replaces": "netcoreapp5.0", + "defaultValue": "netcoreapp5.0" }, "HostIdentifier": { "type": "bind", diff --git a/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/.env b/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/.env new file mode 100644 index 0000000000..6ce384e5ce --- /dev/null +++ b/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/.env @@ -0,0 +1 @@ +BROWSER=none diff --git a/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/.eslintrc.json b/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/.eslintrc.json index f5e132e0e2..40fe7d79bc 100644 --- a/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/.eslintrc.json +++ b/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/.eslintrc.json @@ -3,5 +3,8 @@ "parserOptions": { "ecmaVersion": 6, "sourceType": "module" - } + }, + "plugins": [ + "@typescript-eslint" + ] } \ No newline at end of file diff --git a/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/components/FetchData.tsx b/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/components/FetchData.tsx index 9fed830288..a0c1b7a1e5 100644 --- a/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/components/FetchData.tsx +++ b/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/components/FetchData.tsx @@ -81,4 +81,4 @@ class FetchData extends React.PureComponent { export default connect( (state: ApplicationState) => state.weatherForecasts, // Selects which state properties are merged into the component's props WeatherForecastsStore.actionCreators // Selects which action creators are merged into the component's props -)(FetchData as any); +)(FetchData as any); // eslint-disable-line @typescript-eslint/no-explicit-any diff --git a/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/components/Home.tsx b/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/components/Home.tsx index 5f7d5ff661..d452be0ee3 100644 --- a/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/components/Home.tsx +++ b/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/components/Home.tsx @@ -10,7 +10,7 @@ const Home = () => (
  • React and Redux for client-side code
  • Bootstrap for layout and styling
  • -

    To help you get started, we've also set up:

    +

    To help you get started, we have also set up:

    • Client-side navigation. For example, click Counter then Back to return here.
    • Development server integration. In development mode, the development server from create-react-app runs in the background automatically, so your client-side resources are dynamically built on demand and the page refreshes when you modify any file.
    • diff --git a/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/components/Layout.tsx b/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/components/Layout.tsx index 80ddb46adb..766c65df0f 100644 --- a/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/components/Layout.tsx +++ b/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/components/Layout.tsx @@ -2,11 +2,15 @@ import * as React from 'react'; import { Container } from 'reactstrap'; import NavMenu from './NavMenu'; -export default (props: { children?: React.ReactNode }) => ( - - - - {props.children} - - -); +export default class Layout extends React.PureComponent<{}, { children?: React.ReactNode }> { + public render() { + return ( + + + + {this.props.children} + + + ); + } +} \ No newline at end of file diff --git a/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/store/configureStore.ts b/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/store/configureStore.ts index 68c74c589d..89bb95258c 100644 --- a/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/store/configureStore.ts +++ b/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/store/configureStore.ts @@ -16,7 +16,7 @@ export default function configureStore(history: History, initialState?: Applicat }); const enhancers = []; - const windowIfDefined = typeof window === 'undefined' ? null : window as any; + const windowIfDefined = typeof window === 'undefined' ? null : window as any; // eslint-disable-line @typescript-eslint/no-explicit-any if (windowIfDefined && windowIfDefined.__REDUX_DEVTOOLS_EXTENSION__) { enhancers.push(windowIfDefined.__REDUX_DEVTOOLS_EXTENSION__()); } diff --git a/src/ProjectTemplates/scripts/Run-Angular-Locally.ps1 b/src/ProjectTemplates/scripts/Run-Angular-Locally.ps1 index ba4180de95..ec8993da7e 100644 --- a/src/ProjectTemplates/scripts/Run-Angular-Locally.ps1 +++ b/src/ProjectTemplates/scripts/Run-Angular-Locally.ps1 @@ -9,4 +9,4 @@ $ErrorActionPreference = 'Stop' . $PSScriptRoot\Test-Template.ps1 -Test-Template "angular" "angular" "Microsoft.DotNet.Web.Spa.ProjectTemplates.3.1.3.1.0-dev.nupkg" $true +Test-Template "angular" "angular" "Microsoft.DotNet.Web.Spa.ProjectTemplates.5.0.5.0.0-dev.nupkg" $true diff --git a/src/ProjectTemplates/scripts/Run-Blazor-Locally.ps1 b/src/ProjectTemplates/scripts/Run-Blazor-Locally.ps1 index 7d989b65a3..575036e9c7 100644 --- a/src/ProjectTemplates/scripts/Run-Blazor-Locally.ps1 +++ b/src/ProjectTemplates/scripts/Run-Blazor-Locally.ps1 @@ -10,4 +10,4 @@ $ErrorActionPreference = 'Stop' . $PSScriptRoot\Test-Template.ps1 -Test-Template "blazorserver" "blazorserver" "Microsoft.DotNet.Web.ProjectTemplates.3.1.3.1.0-dev.nupkg" $false +Test-Template "blazorserver" "blazorserver" "Microsoft.DotNet.Web.ProjectTemplates.5.0.5.0.0-dev.nupkg" $false diff --git a/src/ProjectTemplates/scripts/Run-EmptyWeb-Locally.ps1 b/src/ProjectTemplates/scripts/Run-EmptyWeb-Locally.ps1 index 6aeffd7687..d6859c6f72 100644 --- a/src/ProjectTemplates/scripts/Run-EmptyWeb-Locally.ps1 +++ b/src/ProjectTemplates/scripts/Run-EmptyWeb-Locally.ps1 @@ -9,4 +9,4 @@ $ErrorActionPreference = 'Stop' . $PSScriptRoot\Test-Template.ps1 -Test-Template "web" "web" "Microsoft.DotNet.Web.ProjectTemplates.3.1.3.1.0-dev.nupkg" $false +Test-Template "web" "web" "Microsoft.DotNet.Web.ProjectTemplates.5.0.5.0.0-dev.nupkg" $false diff --git a/src/ProjectTemplates/scripts/Run-Razor-Locally.ps1 b/src/ProjectTemplates/scripts/Run-Razor-Locally.ps1 index f77838b435..ecba4fbd8f 100644 --- a/src/ProjectTemplates/scripts/Run-Razor-Locally.ps1 +++ b/src/ProjectTemplates/scripts/Run-Razor-Locally.ps1 @@ -6,4 +6,4 @@ param() . $PSScriptRoot\Test-Template.ps1 -Test-Template "webapp" "webapp -au Individual" "Microsoft.DotNet.Web.ProjectTemplates.3.1.3.1.0-dev.nupkg" $false +Test-Template "webapp" "webapp -au Individual" "Microsoft.DotNet.Web.ProjectTemplates.5.0.5.0.0-dev.nupkg" $false diff --git a/src/ProjectTemplates/scripts/Run-React-Locally.ps1 b/src/ProjectTemplates/scripts/Run-React-Locally.ps1 index 972bfc4ead..8308860edc 100644 --- a/src/ProjectTemplates/scripts/Run-React-Locally.ps1 +++ b/src/ProjectTemplates/scripts/Run-React-Locally.ps1 @@ -9,4 +9,4 @@ $ErrorActionPreference = 'Stop' . $PSScriptRoot\Test-Template.ps1 -Test-Template "react" "react" "Microsoft.DotNet.Web.Spa.ProjectTemplates.3.1.3.1.0-dev.nupkg" $true +Test-Template "react" "react" "Microsoft.DotNet.Web.Spa.ProjectTemplates.5.0.5.0.0-dev.nupkg" $true diff --git a/src/ProjectTemplates/scripts/Run-ReactRedux-Locally.ps1 b/src/ProjectTemplates/scripts/Run-ReactRedux-Locally.ps1 index 4c01f11400..6100d7cacd 100644 --- a/src/ProjectTemplates/scripts/Run-ReactRedux-Locally.ps1 +++ b/src/ProjectTemplates/scripts/Run-ReactRedux-Locally.ps1 @@ -9,4 +9,4 @@ $ErrorActionPreference = 'Stop' . $PSScriptRoot\Test-Template.ps1 -Test-Template "reactredux" "reactredux" "Microsoft.DotNet.Web.Spa.ProjectTemplates.3.1.3.1.0-dev.nupkg" $true +Test-Template "reactredux" "reactredux" "Microsoft.DotNet.Web.Spa.ProjectTemplates.5.0.5.0.0-dev.nupkg" $true diff --git a/src/ProjectTemplates/scripts/Run-Starterweb-Locally.ps1 b/src/ProjectTemplates/scripts/Run-Starterweb-Locally.ps1 index 7971ae01ac..61ad47fbdc 100644 --- a/src/ProjectTemplates/scripts/Run-Starterweb-Locally.ps1 +++ b/src/ProjectTemplates/scripts/Run-Starterweb-Locally.ps1 @@ -9,4 +9,4 @@ $ErrorActionPreference = 'Stop' . $PSScriptRoot\Test-Template.ps1 -Test-Template "mvc" "mvc -au Individual" "Microsoft.DotNet.Web.ProjectTemplates.3.1.3.1.0-dev.nupkg" $false +Test-Template "mvc" "mvc -au Individual" "Microsoft.DotNet.Web.ProjectTemplates.5.0.5.0.0-dev.nupkg" $false diff --git a/src/ProjectTemplates/scripts/Run-Worker-Locally.ps1 b/src/ProjectTemplates/scripts/Run-Worker-Locally.ps1 index 94fd8d012b..e6ff856e7e 100644 --- a/src/ProjectTemplates/scripts/Run-Worker-Locally.ps1 +++ b/src/ProjectTemplates/scripts/Run-Worker-Locally.ps1 @@ -9,4 +9,4 @@ $ErrorActionPreference = 'Stop' . $PSScriptRoot\Test-Template.ps1 -Test-Template "worker" "worker" "Microsoft.DotNet.Web.ProjectTemplates.3.1.3.1.0-dev.nupkg" $false +Test-Template "worker" "worker" "Microsoft.DotNet.Web.ProjectTemplates.5.0.5.0.0-dev.nupkg" $false diff --git a/src/ProjectTemplates/scripts/Test-Template.ps1 b/src/ProjectTemplates/scripts/Test-Template.ps1 index d13b7e452c..5ad25a16d6 100644 --- a/src/ProjectTemplates/scripts/Test-Template.ps1 +++ b/src/ProjectTemplates/scripts/Test-Template.ps1 @@ -32,7 +32,7 @@ function Test-Template($templateName, $templateArgs, $templateNupkg, $isSPA) { $proj = "$tmpDir/$templateName.$extension" $projContent = Get-Content -Path $proj -Raw $projContent = $projContent -replace ('', " - + @@ -42,7 +42,7 @@ function Test-Template($templateName, $templateArgs, $templateNupkg, $isSPA) { $projContent | Set-Content $proj dotnet.exe ef migrations add mvc dotnet.exe publish --configuration Release - dotnet.exe bin\Release\netcoreapp3.1\publish\$templateName.dll + dotnet.exe bin\Release\netcoreapp5.0\publish\$templateName.dll } finally { Pop-Location diff --git a/src/ProjectTemplates/test/BlazorServerTemplateTest.cs b/src/ProjectTemplates/test/BlazorServerTemplateTest.cs index 1c6239d10e..bd17a8fd88 100644 --- a/src/ProjectTemplates/test/BlazorServerTemplateTest.cs +++ b/src/ProjectTemplates/test/BlazorServerTemplateTest.cs @@ -24,7 +24,9 @@ namespace Templates.Test public Project Project { get; private set; } - [Fact] + [ConditionalFact] + [SkipOnHelix("selenium")] + [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/20172")] public async Task BlazorServerTemplateWorks_NoAuth() { Project = await ProjectFactory.GetOrCreateProject("blazorservernoauth", Output); @@ -79,9 +81,10 @@ namespace Templates.Test } } - [Theory] + [ConditionalTheory] [InlineData(true)] [InlineData(false)] + [SkipOnHelix("ef restore no worky")] public async Task BlazorServerTemplateWorks_IndividualAuth(bool useLocalDB) { Project = await ProjectFactory.GetOrCreateProject("blazorserverindividual" + (useLocalDB ? "uld" : ""), Output); diff --git a/src/ProjectTemplates/test/ByteOrderMarkTest.cs b/src/ProjectTemplates/test/ByteOrderMarkTest.cs index df7acd2f4d..76600bf9e2 100644 --- a/src/ProjectTemplates/test/ByteOrderMarkTest.cs +++ b/src/ProjectTemplates/test/ByteOrderMarkTest.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; +using Microsoft.AspNetCore.Testing; using Xunit; using Xunit.Abstractions; @@ -19,7 +20,8 @@ namespace Templates.Test _output = output; } - [Theory] + [ConditionalTheory] + [SkipOnHelix("missing files")] [InlineData("Web.ProjectTemplates")] [InlineData("Web.Spa.ProjectTemplates")] public void JSAndJSONInAllTemplates_ShouldNotContainBOM(string projectName) @@ -60,7 +62,8 @@ namespace Templates.Test Assert.False(filesWithBOMCharactersPresent); } - [Fact] + [ConditionalFact] + [SkipOnHelix("missing files")] public void RazorFilesInWebProjects_ShouldContainBOM() { var projectName = "Web.ProjectTemplates"; diff --git a/src/ProjectTemplates/test/EmptyWebTemplateTest.cs b/src/ProjectTemplates/test/EmptyWebTemplateTest.cs index 56e2e8c105..609ff1879e 100644 --- a/src/ProjectTemplates/test/EmptyWebTemplateTest.cs +++ b/src/ProjectTemplates/test/EmptyWebTemplateTest.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using Templates.Test.Helpers; +using Microsoft.AspNetCore.Testing; using Xunit; using Xunit.Abstractions; @@ -22,7 +23,9 @@ namespace Templates.Test public ITestOutputHelper Output { get; } - [Fact] + [ConditionalFact] + [SkipOnHelix("Cert failures", Queues = "OSX.1014.Amd64;OSX.1014.Amd64.Open")] + [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/20162")] public async Task EmptyWebTemplateCSharp() { await EmtpyTemplateCore(languageOverride: null); @@ -41,6 +44,12 @@ namespace Templates.Test var createResult = await Project.RunDotNetNewAsync("web", language: languageOverride); Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("create/restore", Project, createResult)); + // Avoid the F# compiler. See https://github.com/dotnet/aspnetcore/issues/14022 + if (languageOverride != null) + { + return; + } + var publishResult = await Project.RunDotNetPublishAsync(); Assert.True(0 == publishResult.ExitCode, ErrorMessages.GetFailedProcessMessage("publish", Project, publishResult)); diff --git a/src/ProjectTemplates/test/GrpcTemplateTest.cs b/src/ProjectTemplates/test/GrpcTemplateTest.cs index 4713a7a9c4..707ffb9034 100644 --- a/src/ProjectTemplates/test/GrpcTemplateTest.cs +++ b/src/ProjectTemplates/test/GrpcTemplateTest.cs @@ -2,8 +2,10 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Reflection; using System.Runtime.InteropServices; using System.Threading.Tasks; +using Microsoft.AspNetCore.Testing; using Templates.Test.Helpers; using Xunit; using Xunit.Abstractions; @@ -23,9 +25,16 @@ namespace Templates.Test public ProjectFactoryFixture ProjectFactory { get; } public ITestOutputHelper Output { get; } - [Fact] + [ConditionalFact(Skip = "This test run for over an hour")] + [SkipOnHelix("Not supported queues", Queues = "Windows.7.Amd64;Windows.7.Amd64.Open;OSX.1014.Amd64;OSX.1014.Amd64.Open")] + [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/19716")] public async Task GrpcTemplate() { + // Setup AssemblyTestLog + var assemblyLog = AssemblyTestLog.Create(Assembly.GetExecutingAssembly(), baseDirectory: Project.ArtifactsLogDir); + using var testLog = assemblyLog.StartTestLog(Output, nameof(GrpcTemplateTest), out var loggerFactory); + var logger = loggerFactory.CreateLogger("TestLogger"); + Project = await ProjectFactory.GetOrCreateProject("grpc", Output); var createResult = await Project.RunDotNetNewAsync("grpc"); @@ -37,18 +46,24 @@ namespace Templates.Test var buildResult = await Project.RunDotNetBuildAsync(); Assert.True(0 == buildResult.ExitCode, ErrorMessages.GetFailedProcessMessage("build", Project, buildResult)); - using (var serverProcess = Project.StartBuiltProjectAsync()) + var isOsx = RuntimeInformation.IsOSPlatform(OSPlatform.OSX); + var isWindowsOld = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && Environment.OSVersion.Version < new Version(6, 2); + var unsupported = isOsx || isWindowsOld; + + using (var serverProcess = Project.StartBuiltProjectAsync(hasListeningUri: !unsupported, logger: logger)) { // These templates are HTTPS + HTTP/2 only which is not supported on Mac due to missing ALPN support. - // https://github.com/aspnet/AspNetCore/issues/11061 - if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + // https://github.com/dotnet/aspnetcore/issues/11061 + if (isOsx) { + serverProcess.Process.WaitForExit(assertSuccess: false); Assert.True(serverProcess.Process.HasExited, "built"); Assert.Contains("System.NotSupportedException: HTTP/2 over TLS is not supported on macOS due to missing ALPN support.", ErrorMessages.GetFailedProcessMessageOrEmpty("Run built service", Project, serverProcess.Process)); } - else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && Environment.OSVersion.Version < new Version(6, 2)) + else if (isWindowsOld) { + serverProcess.Process.WaitForExit(assertSuccess: false); Assert.True(serverProcess.Process.HasExited, "built"); Assert.Contains("System.NotSupportedException: HTTP/2 over TLS is not supported on Windows 7 due to missing ALPN support.", ErrorMessages.GetFailedProcessMessageOrEmpty("Run built service", Project, serverProcess.Process)); @@ -61,18 +76,20 @@ namespace Templates.Test } } - using (var aspNetProcess = Project.StartPublishedProjectAsync()) + using (var aspNetProcess = Project.StartPublishedProjectAsync(hasListeningUri: !unsupported)) { // These templates are HTTPS + HTTP/2 only which is not supported on Mac due to missing ALPN support. - // https://github.com/aspnet/AspNetCore/issues/11061 - if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + // https://github.com/dotnet/aspnetcore/issues/11061 + if (isOsx) { + aspNetProcess.Process.WaitForExit(assertSuccess: false); Assert.True(aspNetProcess.Process.HasExited, "published"); Assert.Contains("System.NotSupportedException: HTTP/2 over TLS is not supported on macOS due to missing ALPN support.", ErrorMessages.GetFailedProcessMessageOrEmpty("Run published service", Project, aspNetProcess.Process)); } - else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && Environment.OSVersion.Version < new Version(6, 2)) + else if (isWindowsOld) { + aspNetProcess.Process.WaitForExit(assertSuccess: false); Assert.True(aspNetProcess.Process.HasExited, "published"); Assert.Contains("System.NotSupportedException: HTTP/2 over TLS is not supported on Windows 7 due to missing ALPN support.", ErrorMessages.GetFailedProcessMessageOrEmpty("Run published service", Project, aspNetProcess.Process)); diff --git a/src/ProjectTemplates/test/Helpers/AspNetProcess.cs b/src/ProjectTemplates/test/Helpers/AspNetProcess.cs index c4415bf55f..d90eb24713 100644 --- a/src/ProjectTemplates/test/Helpers/AspNetProcess.cs +++ b/src/ProjectTemplates/test/Helpers/AspNetProcess.cs @@ -11,8 +11,10 @@ using System.Threading.Tasks; using AngleSharp.Dom.Html; using AngleSharp.Parser.Html; using Microsoft.AspNetCore.Certificates.Generation; +using Microsoft.AspNetCore.Internal; using Microsoft.AspNetCore.Server.IntegrationTesting; using Microsoft.Extensions.CommandLineUtils; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using OpenQA.Selenium; using OpenQA.Selenium.Edge; @@ -37,7 +39,8 @@ namespace Templates.Test.Helpers string dllPath, IDictionary environmentVariables, bool published = true, - bool hasListeningUri = true) + bool hasListeningUri = true, + ILogger logger = null) { _output = output; _httpClient = new HttpClient(new HttpClientHandler() @@ -51,19 +54,32 @@ namespace Templates.Test.Helpers Timeout = TimeSpan.FromMinutes(2) }; - var now = DateTimeOffset.Now; - new CertificateManager().EnsureAspNetCoreHttpsDevelopmentCertificate(now, now.AddYears(1)); + EnsureDevelopmentCertificates(); output.WriteLine("Running ASP.NET application..."); var arguments = published ? $"exec {dllPath}" : "run"; + + logger?.LogInformation($"AspNetProcess - process: {DotNetMuxer.MuxerPathOrDefault()} arguments: {arguments}"); + Process = ProcessEx.Run(output, workingDirectory, DotNetMuxer.MuxerPathOrDefault(), arguments, envVars: environmentVariables); + + logger?.LogInformation("AspNetProcess - process started"); + if (hasListeningUri) { + logger?.LogInformation("AspNetProcess - Getting listening uri"); ListeningUri = GetListeningUri(output) ?? throw new InvalidOperationException("Couldn't find the listening URL."); + logger?.LogInformation($"AspNetProcess - Got {ListeningUri.ToString()}"); } } + internal static void EnsureDevelopmentCertificates() + { + var now = DateTimeOffset.Now; + new CertificateManager().EnsureAspNetCoreHttpsDevelopmentCertificate(now, now.AddYears(1)); + } + public void VisitInBrowser(IWebDriver driver) { _output.WriteLine($"Opening browser at {ListeningUri}..."); diff --git a/src/ProjectTemplates/test/Helpers/ErrorMessages.cs b/src/ProjectTemplates/test/Helpers/ErrorMessages.cs index c63f008f83..744ada299b 100644 --- a/src/ProjectTemplates/test/Helpers/ErrorMessages.cs +++ b/src/ProjectTemplates/test/Helpers/ErrorMessages.cs @@ -1,6 +1,8 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using Microsoft.AspNetCore.Internal; + namespace Templates.Test.Helpers { internal static class ErrorMessages diff --git a/src/ProjectTemplates/test/Helpers/PageUrls.cs b/src/ProjectTemplates/test/Helpers/PageUrls.cs index afb0783cbc..deb12fb16c 100644 --- a/src/ProjectTemplates/test/Helpers/PageUrls.cs +++ b/src/ProjectTemplates/test/Helpers/PageUrls.cs @@ -12,6 +12,7 @@ namespace Templates.Test.Helpers public const string LoginUrl = "/Identity/Account/Login"; public const string RegisterUrl = "/Identity/Account/Register"; public const string ForgotPassword = "/Identity/Account/ForgotPassword"; + public const string ResendEmailConfirmation = "/Identity/Account/ResendEmailConfirmation"; public const string ExternalArticle = "https://go.microsoft.com/fwlink/?LinkID=532715"; } } diff --git a/src/ProjectTemplates/test/Helpers/Project.cs b/src/ProjectTemplates/test/Helpers/Project.cs index fc6923ae5c..68642c48f4 100644 --- a/src/ProjectTemplates/test/Helpers/Project.cs +++ b/src/ProjectTemplates/test/Helpers/Project.cs @@ -7,9 +7,12 @@ using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; +using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; +using Microsoft.AspNetCore.Internal; using Microsoft.Extensions.CommandLineUtils; +using Microsoft.Extensions.Logging; using Xunit; using Xunit.Abstractions; using Xunit.Sdk; @@ -21,13 +24,18 @@ namespace Templates.Test.Helpers { private const string _urls = "http://127.0.0.1:0;https://127.0.0.1:0"; - public const string DefaultFramework = "netcoreapp3.1"; - public static bool IsCIEnvironment => typeof(Project).Assembly.GetCustomAttributes() .Any(a => a.Key == "ContinuousIntegrationBuild"); - public static string ArtifactsLogDir => typeof(Project).Assembly.GetCustomAttributes() - .Single(a => a.Key == "ArtifactsLogDir")?.Value; + public static string ArtifactsLogDir => (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("HELIX_DIR"))) + ? GetAssemblyMetadata("ArtifactsLogDir") + : Path.Combine(Environment.GetEnvironmentVariable("HELIX_DIR"), "logs"); + + public static string DotNetEfFullPath => (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("DotNetEfFullPath"))) + ? typeof(ProjectFactoryFixture).Assembly.GetCustomAttributes() + .First(attribute => attribute.Key == "DotNetEfFullPath") + .Value + : Environment.GetEnvironmentVariable("DotNetEfFullPath"); public SemaphoreSlim DotNetNewLock { get; set; } public SemaphoreSlim NodeLock { get; set; } @@ -35,14 +43,16 @@ namespace Templates.Test.Helpers public string ProjectArguments { get; set; } public string ProjectGuid { get; set; } public string TemplateOutputDir { get; set; } - public string TemplateBuildDir => Path.Combine(TemplateOutputDir, "bin", "Debug", DefaultFramework); - public string TemplatePublishDir => Path.Combine(TemplateOutputDir, "bin", "Release", DefaultFramework, "publish"); + public string TargetFramework { get; set; } = GetAssemblyMetadata("Test.DefaultTargetFramework"); + + public string TemplateBuildDir => Path.Combine(TemplateOutputDir, "bin", "Debug", TargetFramework); + public string TemplatePublishDir => Path.Combine(TemplateOutputDir, "bin", "Release", TargetFramework, "publish"); private string TemplateServerDir => Path.Combine(TemplateOutputDir, $"{ProjectName}.Server"); private string TemplateClientDir => Path.Combine(TemplateOutputDir, $"{ProjectName}.Client"); - public string TemplateClientDebugDir => Path.Combine(TemplateClientDir, "bin", "Debug", DefaultFramework); - public string TemplateClientReleaseDir => Path.Combine(TemplateClientDir, "bin", "Release", DefaultFramework, "publish"); - public string TemplateServerReleaseDir => Path.Combine(TemplateServerDir, "bin", "Release", DefaultFramework, "publish"); + public string TemplateClientDebugDir => Path.Combine(TemplateClientDir, "bin", "Debug", TargetFramework); + public string TemplateClientReleaseDir => Path.Combine(TemplateClientDir, "bin", "Release", TargetFramework, "publish"); + public string TemplateServerReleaseDir => Path.Combine(TemplateServerDir, "bin", "Release", TargetFramework, "publish"); public ITestOutputHelper Output { get; set; } public IMessageSink DiagnosticsMessageSink { get; set; } @@ -110,7 +120,7 @@ namespace Templates.Test.Helpers } } - internal async Task RunDotNetPublishAsync(bool takeNodeLock = false, IDictionary packageOptions = null) + internal async Task RunDotNetPublishAsync(bool takeNodeLock = false, IDictionary packageOptions = null, string additionalArgs = null) { Output.WriteLine("Publishing ASP.NET application..."); @@ -121,7 +131,7 @@ namespace Templates.Test.Helpers await effectiveLock.WaitAsync(); try { - var result = ProcessEx.Run(Output, TemplateOutputDir, DotNetMuxer.MuxerPathOrDefault(), $"publish -c Release /bl", packageOptions); + var result = ProcessEx.Run(Output, TemplateOutputDir, DotNetMuxer.MuxerPathOrDefault(), $"publish -c Release /bl {additionalArgs}", packageOptions); await result.Exited; CaptureBinLogOnFailure(result); return result; @@ -132,7 +142,7 @@ namespace Templates.Test.Helpers } } - internal async Task RunDotNetBuildAsync(bool takeNodeLock = false, IDictionary packageOptions = null) + internal async Task RunDotNetBuildAsync(bool takeNodeLock = false, IDictionary packageOptions = null, string additionalArgs = null) { Output.WriteLine("Building ASP.NET application..."); @@ -143,7 +153,7 @@ namespace Templates.Test.Helpers await effectiveLock.WaitAsync(); try { - var result = ProcessEx.Run(Output, TemplateOutputDir, DotNetMuxer.MuxerPathOrDefault(), "build -c Debug /bl", packageOptions); + var result = ProcessEx.Run(Output, TemplateOutputDir, DotNetMuxer.MuxerPathOrDefault(), $"build -c Debug /bl {additionalArgs}", packageOptions); await result.Exited; CaptureBinLogOnFailure(result); return result; @@ -198,7 +208,7 @@ namespace Templates.Test.Helpers return new AspNetProcess(Output, TemplateClientReleaseDir, projectDll, environment); } - internal AspNetProcess StartBuiltProjectAsync(bool hasListeningUri = true) + internal AspNetProcess StartBuiltProjectAsync(bool hasListeningUri = true, ILogger logger = null) { var environment = new Dictionary { @@ -211,7 +221,7 @@ namespace Templates.Test.Helpers }; var projectDll = Path.Combine(TemplateBuildDir, $"{ProjectName}.dll"); - return new AspNetProcess(Output, TemplateOutputDir, projectDll, environment, hasListeningUri: hasListeningUri); + return new AspNetProcess(Output, TemplateOutputDir, projectDll, environment, hasListeningUri: hasListeningUri, logger: logger); } internal AspNetProcess StartPublishedProjectAsync(bool hasListeningUri = true) @@ -242,7 +252,7 @@ namespace Templates.Test.Helpers do { restoreResult = await RestoreAsync(output, workingDirectory); - if (restoreResult.ExitCode == 0) + if (restoreResult.HasExited && restoreResult.ExitCode == 0) { return restoreResult; } @@ -298,20 +308,24 @@ namespace Templates.Test.Helpers internal async Task RunDotNetEfCreateMigrationAsync(string migrationName) { - var assembly = typeof(ProjectFactoryFixture).Assembly; - - var dotNetEfFullPath = assembly.GetCustomAttributes() - .First(attribute => attribute.Key == "DotNetEfFullPath") - .Value; - - var args = $"\"{dotNetEfFullPath}\" --verbose --no-build migrations add {migrationName}"; - + var args = $"--verbose --no-build migrations add {migrationName}"; + // Only run one instance of 'dotnet new' at once, as a workaround for // https://github.com/aspnet/templating/issues/63 await DotNetNewLock.WaitAsync(); try { - var result = ProcessEx.Run(Output, TemplateOutputDir, DotNetMuxer.MuxerPathOrDefault(), args); + var command = DotNetMuxer.MuxerPathOrDefault(); + if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("DotNetEfFullPath"))) + { + args = $"\"{DotNetEfFullPath}\" " + args; + } + else + { + command = "dotnet-ef"; + } + + var result = ProcessEx.Run(Output, TemplateOutputDir, command, args); await result.Exited; return result; } @@ -323,20 +337,24 @@ namespace Templates.Test.Helpers internal async Task RunDotNetEfUpdateDatabaseAsync() { - var assembly = typeof(ProjectFactoryFixture).Assembly; - - var dotNetEfFullPath = assembly.GetCustomAttributes() - .First(attribute => attribute.Key == "DotNetEfFullPath") - .Value; - - var args = $"\"{dotNetEfFullPath}\" --verbose --no-build database update"; + var args = "--verbose --no-build database update"; // Only run one instance of 'dotnet new' at once, as a workaround for // https://github.com/aspnet/templating/issues/63 await DotNetNewLock.WaitAsync(); try { - var result = ProcessEx.Run(Output, TemplateOutputDir, DotNetMuxer.MuxerPathOrDefault(), args); + var command = DotNetMuxer.MuxerPathOrDefault(); + if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("DotNetEfFullPath"))) + { + args = $"\"{DotNetEfFullPath}\" " + args; + } + else + { + command = "dotnet-ef"; + } + + var result = ProcessEx.Run(Output, TemplateOutputDir, command, args); await result.Exited; return result; } @@ -524,5 +542,18 @@ namespace Templates.Test.Helpers } public override string ToString() => $"{ProjectName}: {TemplateOutputDir}"; + + private static string GetAssemblyMetadata(string key) + { + var attribute = typeof(Project).Assembly.GetCustomAttributes() + .FirstOrDefault(a => a.Key == key); + + if (attribute is null) + { + throw new ArgumentException($"AssemblyMetadataAttribute with key {key} was not found."); + } + + return attribute.Value; + } } } diff --git a/src/ProjectTemplates/test/Helpers/ProjectFactoryFixture.cs b/src/ProjectTemplates/test/Helpers/ProjectFactoryFixture.cs index ffd1b0ae4f..9399433be9 100644 --- a/src/ProjectTemplates/test/Helpers/ProjectFactoryFixture.cs +++ b/src/ProjectTemplates/test/Helpers/ProjectFactoryFixture.cs @@ -61,9 +61,11 @@ namespace Templates.Test.Helpers } private static string GetTemplateFolderBasePath(Assembly assembly) => - assembly.GetCustomAttributes() + (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("HELIX_DIR"))) + ? assembly.GetCustomAttributes() .Single(a => a.Key == "TestTemplateCreationFolder") - .Value; + .Value + : Path.Combine(Environment.GetEnvironmentVariable("HELIX_DIR"), "Templates", "BaseFolder"); public void Dispose() { diff --git a/src/ProjectTemplates/test/Helpers/TemplatePackageInstaller.cs b/src/ProjectTemplates/test/Helpers/TemplatePackageInstaller.cs index 2c34259592..5c94fde2b4 100644 --- a/src/ProjectTemplates/test/Helpers/TemplatePackageInstaller.cs +++ b/src/ProjectTemplates/test/Helpers/TemplatePackageInstaller.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Reflection; using System.Threading; using System.Threading.Tasks; +using Microsoft.AspNetCore.Internal; using Microsoft.Extensions.CommandLineUtils; using Xunit; using Xunit.Abstractions; @@ -32,17 +33,22 @@ namespace Templates.Test.Helpers "Microsoft.DotNet.Web.ProjectTemplates.2.2", "Microsoft.DotNet.Web.ProjectTemplates.3.0", "Microsoft.DotNet.Web.ProjectTemplates.3.1", + "Microsoft.DotNet.Web.ProjectTemplates.5.0", "Microsoft.DotNet.Web.Spa.ProjectTemplates.2.1", "Microsoft.DotNet.Web.Spa.ProjectTemplates.2.2", "Microsoft.DotNet.Web.Spa.ProjectTemplates.3.0", "Microsoft.DotNet.Web.Spa.ProjectTemplates.3.1", - "Microsoft.DotNet.Web.Spa.ProjectTemplates" + "Microsoft.DotNet.Web.Spa.ProjectTemplates.5.0", + "Microsoft.DotNet.Web.Spa.ProjectTemplates", + "Microsoft.AspNetCore.Blazor.Templates", }; - public static string CustomHivePath { get; } = typeof(TemplatePackageInstaller) - .Assembly.GetCustomAttributes() - .Single(s => s.Key == "CustomTemplateHivePath").Value; - + public static string CustomHivePath { get; } = (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("helix"))) + ? typeof(TemplatePackageInstaller) + .Assembly.GetCustomAttributes() + .Single(s => s.Key == "CustomTemplateHivePath").Value + : Path.Combine("Hives", ".templateEngine"); + public static async Task EnsureTemplatingEngineInitializedAsync(ITestOutputHelper output) { await InstallerLock.WaitAsync(); @@ -78,11 +84,19 @@ namespace Templates.Test.Helpers private static async Task InstallTemplatePackages(ITestOutputHelper output) { - var builtPackages = Directory.EnumerateFiles( - typeof(TemplatePackageInstaller).Assembly + string packagesDir; + if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("helix"))) + { + packagesDir = "."; + } + else + { + packagesDir = typeof(TemplatePackageInstaller).Assembly .GetCustomAttributes() - .Single(a => a.Key == "ArtifactsShippingPackagesDir").Value, - "*.nupkg") + .Single(a => a.Key == "ArtifactsShippingPackagesDir").Value; + } + + var builtPackages = Directory.EnumerateFiles(packagesDir, "*Templates*.nupkg") .Where(p => _templatePackages.Any(t => Path.GetFileName(p).StartsWith(t, StringComparison.OrdinalIgnoreCase))) .ToArray(); @@ -90,7 +104,7 @@ namespace Templates.Test.Helpers /* * The templates are indexed by path, for example: - &USERPROFILE%\.templateengine\dotnetcli\v3.0.100-preview7-012821\packages\nunit3.dotnetnew.template.1.6.1.nupkg + &USERPROFILE%\.templateengine\dotnetcli\v5.0.100-alpha1-013788\packages\nunit3.dotnetnew.template.1.6.1.nupkg Templates: NUnit 3 Test Project (nunit) C# NUnit 3 Test Item (nunit-test) C# @@ -99,7 +113,7 @@ namespace Templates.Test.Helpers NUnit 3 Test Project (nunit) VB NUnit 3 Test Item (nunit-test) VB Uninstall Command: - dotnet new -u &USERPROFILE%\.templateengine\dotnetcli\v3.0.100-preview7-012821\packages\nunit3.dotnetnew.template.1.6.1.nupkg + dotnet new -u &USERPROFILE%\.templateengine\dotnetcli\v5.0.100-alpha1-013788\packages\nunit3.dotnetnew.template.1.6.1.nupkg * We don't want to construct this path so we'll rely on dotnet new --uninstall --help to construct the uninstall command. */ diff --git a/src/ProjectTemplates/test/IdentityUIPackageTest.cs b/src/ProjectTemplates/test/IdentityUIPackageTest.cs index deede64521..da766229a6 100644 --- a/src/ProjectTemplates/test/IdentityUIPackageTest.cs +++ b/src/ProjectTemplates/test/IdentityUIPackageTest.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.IO; using System.Net; using System.Threading.Tasks; +using Microsoft.AspNetCore.Testing; using Templates.Test.Helpers; using Xunit; using Xunit.Abstractions; @@ -117,8 +118,10 @@ namespace Templates.Test "Identity/lib/jquery-validation-unobtrusive/LICENSE.txt", }; - [Theory] + [ConditionalTheory(Skip = "This test run for over an hour")] [MemberData(nameof(MSBuildIdentityUIPackageOptions))] + [SkipOnHelix("cert failure", Queues = "OSX.1014.Amd64;OSX.1014.Amd64.Open")] + [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/19716")] public async Task IdentityUIPackage_WorksWithDifferentOptions(IDictionary packageOptions, string versionValidator, string[] expectedFiles) { Project = await ProjectFactory.GetOrCreateProject("identityuipackage" + string.Concat(packageOptions.Values), Output); diff --git a/src/ProjectTemplates/test/Infrastructure/Directory.Build.props.in b/src/ProjectTemplates/test/Infrastructure/Directory.Build.props.in index 6186fc2f15..9990532b1d 100644 --- a/src/ProjectTemplates/test/Infrastructure/Directory.Build.props.in +++ b/src/ProjectTemplates/test/Infrastructure/Directory.Build.props.in @@ -1,3 +1,6 @@ + + netcoreapp5.0 + diff --git a/src/ProjectTemplates/test/Infrastructure/TemplateTests.props.in b/src/ProjectTemplates/test/Infrastructure/TemplateTests.props.in index 25fbc524a4..1d3fb7bb2a 100644 --- a/src/ProjectTemplates/test/Infrastructure/TemplateTests.props.in +++ b/src/ProjectTemplates/test/Infrastructure/TemplateTests.props.in @@ -5,8 +5,6 @@ $(MSBuildThisFileDirectory)runtimeconfig.norollforward.json - - ${MicrosoftNETCorePlatformsPackageVersion} @@ -24,15 +22,6 @@ RuntimePackRuntimeIdentifiers="${SupportedRuntimeIdentifiers}" /> - - - - - await MvcTemplateCore(languageOverride: "F#"); - [Fact] + [ConditionalFact] + [SkipOnHelix("cert failure", Queues = "OSX.1014.Amd64;OSX.1014.Amd64.Open")] public async Task MvcTemplate_NoAuthCSharp() => await MvcTemplateCore(languageOverride: null); private async Task MvcTemplateCore(string languageOverride) @@ -46,6 +47,12 @@ namespace Templates.Test Assert.DoesNotContain("Microsoft.EntityFrameworkCore.Tools.DotNet", projectFileContents); Assert.DoesNotContain("Microsoft.Extensions.SecretManager.Tools", projectFileContents); + // Avoid the F# compiler. See https://github.com/dotnet/aspnetcore/issues/14022 + if (languageOverride != null) + { + return; + } + var publishResult = await Project.RunDotNetPublishAsync(); Assert.True(0 == publishResult.ExitCode, ErrorMessages.GetFailedProcessMessage("publish", Project, publishResult)); @@ -97,9 +104,11 @@ namespace Templates.Test } } - [Theory] + [ConditionalTheory(Skip = "This test run for over an hour")] [InlineData(true)] [InlineData(false)] + [SkipOnHelix("cert failure", Queues = "OSX.1014.Amd64;OSX.1014.Amd64.Open")] + [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/19716")] public async Task MvcTemplate_IndividualAuth(bool useLocalDB) { Project = await ProjectFactory.GetOrCreateProject("mvcindividual" + (useLocalDB ? "uld" : ""), Output); @@ -176,6 +185,7 @@ namespace Templates.Test PageUrls.PrivacyUrl, PageUrls.ForgotPassword, PageUrls.RegisterUrl, + PageUrls.ResendEmailConfirmation, PageUrls.ExternalArticle, PageUrls.PrivacyUrl } }, @@ -214,6 +224,7 @@ namespace Templates.Test } [Fact] + [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/19716")] public async Task MvcTemplate_RazorRuntimeCompilation_BuildsAndPublishes() { Project = await ProjectFactory.GetOrCreateProject("mvc_rc", Output); diff --git a/src/ProjectTemplates/test/ProjectTemplates.Tests.csproj b/src/ProjectTemplates/test/ProjectTemplates.Tests.csproj index 5ed506cf28..644ab2fa1c 100644 --- a/src/ProjectTemplates/test/ProjectTemplates.Tests.csproj +++ b/src/ProjectTemplates/test/ProjectTemplates.Tests.csproj @@ -10,10 +10,10 @@ true true - - - - false + true + + + false @@ -23,17 +23,20 @@ $([MSBuild]::EnsureTrailingSlash('$(RepoRoot)'))obj\template-restore\ TemplateTests.props false + true + - + + @@ -49,21 +52,35 @@ + + true + false + + <_Parameter1>DotNetEfFullPath - <_Parameter2>$([MSBuild]::EnsureTrailingSlash('$(NuGetPackageRoot)'))dotnet-ef/$(DotnetEfPackageVersion)/tools/$(DefaultNetCoreTargetFramework)/any/dotnet-ef.dll + <_Parameter2>$([MSBuild]::EnsureTrailingSlash('$(NuGetPackageRoot)'))dotnet-ef/$(DotnetEfPackageVersion)/tools/netcoreapp3.1/any/dotnet-ef.dll <_Parameter1>TestPackageRestorePath <_Parameter2>$(TestPackageRestorePath) + + <_Parameter1>Test.DefaultTargetFramework + <_Parameter2>$(DefaultNetCoreTargetFramework) + <_Parameter1>ContinuousIntegrationBuild <_Parameter2>true + + <_Parameter1>$(PreserveExistingLogsInOutput) + <_Parameter2>$(TargetFramework) + <_Parameter3> + - + $([MSBuild]::NormalizePath('$(OutputPath)$(TestTemplateCreationFolder)')) @@ -81,7 +98,7 @@ <_Parameter1>ArtifactsLogDir <_Parameter2>$([MSBuild]::NormalizeDirectory('$(ArtifactsDir)', 'log')) - + <_Parameter1>ArtifactsNonShippingPackagesDir <_Parameter2>$(ArtifactsNonShippingPackagesDir) diff --git a/src/ProjectTemplates/test/RazorClassLibraryTemplateTest.cs b/src/ProjectTemplates/test/RazorClassLibraryTemplateTest.cs index 0d02a56f8f..b16f8fa345 100644 --- a/src/ProjectTemplates/test/RazorClassLibraryTemplateTest.cs +++ b/src/ProjectTemplates/test/RazorClassLibraryTemplateTest.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Threading.Tasks; +using Microsoft.AspNetCore.Testing; using Templates.Test.Helpers; using Xunit; using Xunit.Abstractions; @@ -41,6 +42,7 @@ namespace Templates.Test } [Fact] + [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/19716")] public async Task RazorClassLibraryTemplateAsync() { Project = await ProjectFactory.GetOrCreateProject("razorclasslib", Output); diff --git a/src/ProjectTemplates/test/RazorPagesTemplateTest.cs b/src/ProjectTemplates/test/RazorPagesTemplateTest.cs index 3de64d6ea7..f7727fbb5a 100644 --- a/src/ProjectTemplates/test/RazorPagesTemplateTest.cs +++ b/src/ProjectTemplates/test/RazorPagesTemplateTest.cs @@ -25,7 +25,8 @@ namespace Templates.Test public ITestOutputHelper Output { get; } - [Fact] + [ConditionalFact] + [SkipOnHelix("Cert failures", Queues = "OSX.1014.Amd64;OSX.1014.Amd64.Open")] public async Task RazorPagesTemplate_NoAuth() { Project = await ProjectFactory.GetOrCreateProject("razorpagesnoauth", Output); @@ -93,9 +94,11 @@ namespace Templates.Test } } - [Theory] + [ConditionalTheory(Skip = "This test run for over an hour")] [InlineData(false)] [InlineData(true)] + [SkipOnHelix("cert failure", Queues = "OSX.1014.Amd64;OSX.1014.Amd64.Open")] + [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/19716")] public async Task RazorPagesTemplate_IndividualAuth(bool useLocalDB) { Project = await ProjectFactory.GetOrCreateProject("razorpagesindividual" + (useLocalDB ? "uld" : ""), Output); @@ -172,6 +175,7 @@ namespace Templates.Test PageUrls.PrivacyUrl, PageUrls.ForgotPassword, PageUrls.RegisterUrl, + PageUrls.ResendEmailConfirmation, PageUrls.ExternalArticle, PageUrls.PrivacyUrl } }, @@ -210,6 +214,7 @@ namespace Templates.Test } [Fact] + [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/19716")] public async Task RazorPagesTemplate_RazorRuntimeCompilation_BuildsAndPublishes() { Project = await ProjectFactory.GetOrCreateProject("razorpages_rc", Output); diff --git a/src/ProjectTemplates/test/SpaTemplateTest/AngularTemplateTest.cs b/src/ProjectTemplates/test/SpaTemplateTest/AngularTemplateTest.cs index e1d5db1338..fc78775189 100644 --- a/src/ProjectTemplates/test/SpaTemplateTest/AngularTemplateTest.cs +++ b/src/ProjectTemplates/test/SpaTemplateTest/AngularTemplateTest.cs @@ -15,15 +15,18 @@ namespace Templates.Test.SpaTemplateTest public AngularTemplateTest(ProjectFactoryFixture projectFactory, BrowserFixture browserFixture, ITestOutputHelper output) : base(projectFactory, browserFixture, output) { } - [Fact] + [ConditionalFact] + [SkipOnHelix("selenium")] public Task AngularTemplate_Works() => SpaTemplateImplAsync("angularnoauth", "angular", useLocalDb: false, usesAuth: false); - [Fact] + [ConditionalFact] + [SkipOnHelix("selenium")] public Task AngularTemplate_IndividualAuth_Works() => SpaTemplateImplAsync("angularindividual", "angular", useLocalDb: false, usesAuth: true); - [Fact] + [ConditionalFact] + [SkipOnHelix("selenium")] public Task AngularTemplate_IndividualAuth_Works_LocalDb() => SpaTemplateImplAsync("angularindividualuld", "angular", useLocalDb: true, usesAuth: true); } diff --git a/src/ProjectTemplates/test/SpaTemplateTest/ReactReduxTemplateTest.cs b/src/ProjectTemplates/test/SpaTemplateTest/ReactReduxTemplateTest.cs index 44d6b67f32..3e32514cc1 100644 --- a/src/ProjectTemplates/test/SpaTemplateTest/ReactReduxTemplateTest.cs +++ b/src/ProjectTemplates/test/SpaTemplateTest/ReactReduxTemplateTest.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.E2ETesting; +using Microsoft.AspNetCore.Testing; using Templates.Test.Helpers; using Xunit; using Xunit.Abstractions; @@ -16,7 +17,8 @@ namespace Templates.Test.SpaTemplateTest { } - [Fact] + [ConditionalFact] + [SkipOnHelix("selenium")] public Task ReactReduxTemplate_Works_NetCore() => SpaTemplateImplAsync("reactredux", "reactredux", useLocalDb: false, usesAuth: false); } diff --git a/src/ProjectTemplates/test/SpaTemplateTest/ReactTemplateTest.cs b/src/ProjectTemplates/test/SpaTemplateTest/ReactTemplateTest.cs index 469e87acd5..d4a1ff1756 100644 --- a/src/ProjectTemplates/test/SpaTemplateTest/ReactTemplateTest.cs +++ b/src/ProjectTemplates/test/SpaTemplateTest/ReactTemplateTest.cs @@ -17,15 +17,19 @@ namespace Templates.Test.SpaTemplateTest { } - [Fact] + [ConditionalFact] + [SkipOnHelix("selenium")] public Task ReactTemplate_Works_NetCore() => SpaTemplateImplAsync("reactnoauth", "react", useLocalDb: false, usesAuth: false); - [Fact] + [QuarantinedTest] + [ConditionalFact(Skip="This test run for over an hour")] + [SkipOnHelix("selenium")] public Task ReactTemplate_IndividualAuth_NetCore() => SpaTemplateImplAsync("reactindividual", "react", useLocalDb: false, usesAuth: true); - [Fact] + [ConditionalFact] + [SkipOnHelix("selenium")] public Task ReactTemplate_IndividualAuth_NetCore_LocalDb() => SpaTemplateImplAsync("reactindividualuld", "react", useLocalDb: true, usesAuth: true); } diff --git a/src/ProjectTemplates/test/SpaTemplateTest/SpaTemplateTestBase.cs b/src/ProjectTemplates/test/SpaTemplateTest/SpaTemplateTestBase.cs index 130533bf15..d702e01be1 100644 --- a/src/ProjectTemplates/test/SpaTemplateTest/SpaTemplateTestBase.cs +++ b/src/ProjectTemplates/test/SpaTemplateTest/SpaTemplateTestBase.cs @@ -10,6 +10,7 @@ using System.Net.Http; using System.Text.RegularExpressions; using System.Threading.Tasks; using Microsoft.AspNetCore.E2ETesting; +using Microsoft.AspNetCore.Internal; using Newtonsoft.Json.Linq; using OpenQA.Selenium; using Templates.Test.Helpers; @@ -313,10 +314,12 @@ namespace Templates.Test.SpaTemplateTest var entries = logs.GetLog(logKind); var badEntries = entries.Where(e => new LogLevel[] { LogLevel.Warning, LogLevel.Severe }.Contains(e.Level)); + // Based on https://github.com/webpack/webpack-dev-server/issues/2134 badEntries = badEntries.Where(e => !e.Message.Contains("failed: WebSocket is closed before the connection is established.") && !e.Message.Contains("[WDS] Disconnected!") - && !e.Message.Contains("Timed out connecting to Chrome, retrying")); + && !e.Message.Contains("Timed out connecting to Chrome, retrying") + && !(e.Message.Contains("jsonp?c=") && e.Message.Contains("Uncaught TypeError:") && e.Message.Contains("is not a function"))); Assert.True(badEntries.Count() == 0, "There were Warnings or Errors from the browser." + Environment.NewLine + string.Join(Environment.NewLine, badEntries)); } diff --git a/src/ProjectTemplates/test/WebApiTemplateTest.cs b/src/ProjectTemplates/test/WebApiTemplateTest.cs index 89d047a06e..2f9a5ce162 100644 --- a/src/ProjectTemplates/test/WebApiTemplateTest.cs +++ b/src/ProjectTemplates/test/WebApiTemplateTest.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using Templates.Test.Helpers; +using Microsoft.AspNetCore.Testing; using Xunit; using Xunit.Abstractions; @@ -25,7 +26,8 @@ namespace Templates.Test [Fact] public async Task WebApiTemplateFSharp() => await WebApiTemplateCore(languageOverride: "F#"); - [Fact] + [ConditionalFact] + [SkipOnHelix("Cert failures", Queues = "OSX.1014.Amd64;OSX.1014.Amd64.Open")] public async Task WebApiTemplateCSharp() => await WebApiTemplateCore(languageOverride: null); private async Task WebApiTemplateCore(string languageOverride) @@ -35,6 +37,12 @@ namespace Templates.Test var createResult = await Project.RunDotNetNewAsync("webapi", language: languageOverride); Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("create/restore", Project, createResult)); + // Avoid the F# compiler. See https://github.com/dotnet/aspnetcore/issues/14022 + if (languageOverride != null) + { + return; + } + var publishResult = await Project.RunDotNetPublishAsync(); Assert.True(0 == publishResult.ExitCode, ErrorMessages.GetFailedProcessMessage("publish", Project, publishResult)); diff --git a/src/ProjectTemplates/test/WorkerTemplateTest.cs b/src/ProjectTemplates/test/WorkerTemplateTest.cs index 738eafc61d..09f444811c 100644 --- a/src/ProjectTemplates/test/WorkerTemplateTest.cs +++ b/src/ProjectTemplates/test/WorkerTemplateTest.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using Templates.Test.Helpers; using Xunit; using Xunit.Abstractions; +using Microsoft.AspNetCore.Testing; namespace Templates.Test { @@ -20,7 +21,8 @@ namespace Templates.Test public ProjectFactoryFixture ProjectFactory { get; } public ITestOutputHelper Output { get; } - [Fact] + [Fact(Skip = "This test run for over an hour")] + [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/19716")] public async Task WorkerTemplateAsync() { Project = await ProjectFactory.GetOrCreateProject("worker", Output); diff --git a/src/ProjectTemplates/test/package.json b/src/ProjectTemplates/test/package.json index 3a1ea190f6..c48c6bd8a4 100644 --- a/src/ProjectTemplates/test/package.json +++ b/src/ProjectTemplates/test/package.json @@ -6,11 +6,11 @@ "private": true, "scripts": { "selenium-standalone": "selenium-standalone", - "prepare": "selenium-standalone install" + "prepare": "selenium-standalone install --config ../../Shared/E2ETesting/selenium-config.json" }, "author": "", "license": "Apache-2.0", "dependencies": { - "selenium-standalone": "^6.15.4" + "selenium-standalone": "^6.17.0" } } diff --git a/src/ProjectTemplates/test/template-baselines.json b/src/ProjectTemplates/test/template-baselines.json index 9152fc62d0..5d70579fb6 100644 --- a/src/ProjectTemplates/test/template-baselines.json +++ b/src/ProjectTemplates/test/template-baselines.json @@ -1215,6 +1215,7 @@ "ClientApp/src/App.test.js", "ClientApp/src/index.js", "ClientApp/src/registerServiceWorker.js", + "ClientApp/.env", "ClientApp/.gitignore", "ClientApp/package-lock.json", "ClientApp/package.json", @@ -1257,6 +1258,7 @@ "ClientApp/src/index.tsx", "ClientApp/src/react-app-env.d.ts", "ClientApp/src/registerServiceWorker.ts", + "ClientApp/.env", "ClientApp/.eslintrc.json", "ClientApp/.gitignore", "ClientApp/package-lock.json", diff --git a/src/ProjectTemplates/xunit.runner.json b/src/ProjectTemplates/xunit.runner.json new file mode 100644 index 0000000000..dfb6dacb88 --- /dev/null +++ b/src/ProjectTemplates/xunit.runner.json @@ -0,0 +1,5 @@ +{ + "longRunningTestSeconds": 60, + "diagnosticMessages": true, + "maxParallelThreads": -1 +} diff --git a/src/Razor/Razor.Runtime/ref/Microsoft.AspNetCore.Razor.Runtime.Manual.cs b/src/Razor/Razor.Runtime/ref/Microsoft.AspNetCore.Razor.Runtime.Manual.cs deleted file mode 100644 index 56b3976d61..0000000000 --- a/src/Razor/Razor.Runtime/ref/Microsoft.AspNetCore.Razor.Runtime.Manual.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -namespace Microsoft.AspNetCore.Razor.Runtime.TagHelpers -{ - public partial class TagHelperExecutionContext - { - internal TagHelperExecutionContext(string tagName, Microsoft.AspNetCore.Razor.TagHelpers.TagMode tagMode) { } - [System.Diagnostics.DebuggerStepThroughAttribute] - internal System.Threading.Tasks.Task GetChildContentAsync(bool useCachedResult, System.Text.Encodings.Web.HtmlEncoder encoder) { throw null; } - } -} diff --git a/src/Razor/Razor.Runtime/ref/Microsoft.AspNetCore.Razor.Runtime.csproj b/src/Razor/Razor.Runtime/ref/Microsoft.AspNetCore.Razor.Runtime.csproj index 06658a3da0..a178a077b8 100644 --- a/src/Razor/Razor.Runtime/ref/Microsoft.AspNetCore.Razor.Runtime.csproj +++ b/src/Razor/Razor.Runtime/ref/Microsoft.AspNetCore.Razor.Runtime.csproj @@ -5,7 +5,6 @@ - diff --git a/src/Razor/Razor.Runtime/ref/Microsoft.AspNetCore.Razor.Runtime.netcoreapp.cs b/src/Razor/Razor.Runtime/ref/Microsoft.AspNetCore.Razor.Runtime.netcoreapp.cs index 9e2eb73d09..c8bd4d24f6 100644 --- a/src/Razor/Razor.Runtime/ref/Microsoft.AspNetCore.Razor.Runtime.netcoreapp.cs +++ b/src/Razor/Razor.Runtime/ref/Microsoft.AspNetCore.Razor.Runtime.netcoreapp.cs @@ -21,9 +21,9 @@ namespace Microsoft.AspNetCore.Razor.Hosting public sealed partial class RazorCompiledItemAttribute : System.Attribute { public RazorCompiledItemAttribute(System.Type type, string kind, string identifier) { } - public string Identifier { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public string Kind { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public System.Type Type { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public string Identifier { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public string Kind { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public System.Type Type { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } } public static partial class RazorCompiledItemExtensions { @@ -40,35 +40,35 @@ namespace Microsoft.AspNetCore.Razor.Hosting public sealed partial class RazorCompiledItemMetadataAttribute : System.Attribute { public RazorCompiledItemMetadataAttribute(string key, string value) { } - public string Key { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public string Value { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public string Key { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public string Value { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } } [System.AttributeUsageAttribute(System.AttributeTargets.Assembly, AllowMultiple=false, Inherited=false)] public sealed partial class RazorConfigurationNameAttribute : System.Attribute { public RazorConfigurationNameAttribute(string configurationName) { } - public string ConfigurationName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public string ConfigurationName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } } [System.AttributeUsageAttribute(System.AttributeTargets.Assembly, AllowMultiple=true, Inherited=false)] public sealed partial class RazorExtensionAssemblyNameAttribute : System.Attribute { public RazorExtensionAssemblyNameAttribute(string extensionName, string assemblyName) { } - public string AssemblyName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public string ExtensionName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public string AssemblyName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public string ExtensionName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } } [System.AttributeUsageAttribute(System.AttributeTargets.Assembly, AllowMultiple=false, Inherited=false)] public sealed partial class RazorLanguageVersionAttribute : System.Attribute { public RazorLanguageVersionAttribute(string languageVersion) { } - public string LanguageVersion { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public string LanguageVersion { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } } [System.AttributeUsageAttribute(System.AttributeTargets.Class, AllowMultiple=true, Inherited=true)] public sealed partial class RazorSourceChecksumAttribute : System.Attribute, Microsoft.AspNetCore.Razor.Hosting.IRazorSourceChecksumMetadata { public RazorSourceChecksumAttribute(string checksumAlgorithm, string checksum, string identifier) { } - public string Checksum { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public string ChecksumAlgorithm { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public string Identifier { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public string Checksum { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public string ChecksumAlgorithm { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public string Identifier { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } } } namespace Microsoft.AspNetCore.Razor.Runtime.TagHelpers @@ -77,9 +77,9 @@ namespace Microsoft.AspNetCore.Razor.Runtime.TagHelpers { public TagHelperExecutionContext(string tagName, Microsoft.AspNetCore.Razor.TagHelpers.TagMode tagMode, System.Collections.Generic.IDictionary items, string uniqueId, System.Func executeChildContentAsync, System.Action startTagHelperWritingScope, System.Func endTagHelperWritingScope) { } public bool ChildContentRetrieved { get { throw null; } } - public Microsoft.AspNetCore.Razor.TagHelpers.TagHelperContext Context { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public System.Collections.Generic.IDictionary Items { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public Microsoft.AspNetCore.Razor.TagHelpers.TagHelperOutput Output { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public Microsoft.AspNetCore.Razor.TagHelpers.TagHelperContext Context { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public System.Collections.Generic.IDictionary Items { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public Microsoft.AspNetCore.Razor.TagHelpers.TagHelperOutput Output { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } public System.Collections.Generic.IList TagHelpers { get { throw null; } } public void Add(Microsoft.AspNetCore.Razor.TagHelpers.ITagHelper tagHelper) { } public void AddHtmlAttribute(Microsoft.AspNetCore.Razor.TagHelpers.TagHelperAttribute attribute) { } diff --git a/src/Razor/Razor/ref/Microsoft.AspNetCore.Razor.csproj b/src/Razor/Razor/ref/Microsoft.AspNetCore.Razor.csproj index bfd4736d1d..8914624864 100644 --- a/src/Razor/Razor/ref/Microsoft.AspNetCore.Razor.csproj +++ b/src/Razor/Razor/ref/Microsoft.AspNetCore.Razor.csproj @@ -8,6 +8,5 @@ - diff --git a/src/Razor/Razor/ref/Microsoft.AspNetCore.Razor.netcoreapp.cs b/src/Razor/Razor/ref/Microsoft.AspNetCore.Razor.netcoreapp.cs index 36d08aa6ae..de54d56596 100644 --- a/src/Razor/Razor/ref/Microsoft.AspNetCore.Razor.netcoreapp.cs +++ b/src/Razor/Razor/ref/Microsoft.AspNetCore.Razor.netcoreapp.cs @@ -26,8 +26,8 @@ namespace Microsoft.AspNetCore.Razor.TagHelpers public HtmlAttributeNameAttribute() { } public HtmlAttributeNameAttribute(string name) { } public string DictionaryAttributePrefix { get { throw null; } set { } } - public bool DictionaryAttributePrefixSet { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public string Name { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public bool DictionaryAttributePrefixSet { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public string Name { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } } [System.AttributeUsageAttribute(System.AttributeTargets.Property, AllowMultiple=false, Inherited=false)] public sealed partial class HtmlAttributeNotBoundAttribute : System.Attribute @@ -47,10 +47,10 @@ namespace Microsoft.AspNetCore.Razor.TagHelpers public const string ElementCatchAllTarget = "*"; public HtmlTargetElementAttribute() { } public HtmlTargetElementAttribute(string tag) { } - public string Attributes { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public string ParentTag { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public string Tag { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public Microsoft.AspNetCore.Razor.TagHelpers.TagStructure TagStructure { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public string Attributes { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public string ParentTag { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public string Tag { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public Microsoft.AspNetCore.Razor.TagHelpers.TagStructure TagStructure { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } public partial interface ITagHelper : Microsoft.AspNetCore.Razor.TagHelpers.ITagHelperComponent { @@ -64,7 +64,7 @@ namespace Microsoft.AspNetCore.Razor.TagHelpers public sealed partial class NullHtmlEncoder : System.Text.Encodings.Web.HtmlEncoder { internal NullHtmlEncoder() { } - public static new Microsoft.AspNetCore.Razor.TagHelpers.NullHtmlEncoder Default { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public static new Microsoft.AspNetCore.Razor.TagHelpers.NullHtmlEncoder Default { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } public override int MaxOutputCharactersPerInputCharacter { get { throw null; } } public override void Encode(System.IO.TextWriter output, char[] value, int startIndex, int characterCount) { } public override void Encode(System.IO.TextWriter output, string value, int startIndex, int characterCount) { } @@ -77,7 +77,7 @@ namespace Microsoft.AspNetCore.Razor.TagHelpers public sealed partial class OutputElementHintAttribute : System.Attribute { public OutputElementHintAttribute(string outputElement) { } - public string OutputElement { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public string OutputElement { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } } public abstract partial class ReadOnlyTagHelperAttributeList : System.Collections.ObjectModel.ReadOnlyCollection { @@ -94,12 +94,12 @@ namespace Microsoft.AspNetCore.Razor.TagHelpers public sealed partial class RestrictChildrenAttribute : System.Attribute { public RestrictChildrenAttribute(string childTag, params string[] childTags) { } - public System.Collections.Generic.IEnumerable ChildTags { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public System.Collections.Generic.IEnumerable ChildTags { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } } public abstract partial class TagHelper : Microsoft.AspNetCore.Razor.TagHelpers.ITagHelper, Microsoft.AspNetCore.Razor.TagHelpers.ITagHelperComponent { protected TagHelper() { } - public virtual int Order { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public virtual int Order { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } public virtual void Init(Microsoft.AspNetCore.Razor.TagHelpers.TagHelperContext context) { } public virtual void Process(Microsoft.AspNetCore.Razor.TagHelpers.TagHelperContext context, Microsoft.AspNetCore.Razor.TagHelpers.TagHelperOutput output) { } public virtual System.Threading.Tasks.Task ProcessAsync(Microsoft.AspNetCore.Razor.TagHelpers.TagHelperContext context, Microsoft.AspNetCore.Razor.TagHelpers.TagHelperOutput output) { throw null; } @@ -109,9 +109,9 @@ namespace Microsoft.AspNetCore.Razor.TagHelpers public TagHelperAttribute(string name) { } public TagHelperAttribute(string name, object value) { } public TagHelperAttribute(string name, object value, Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeValueStyle valueStyle) { } - public string Name { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public object Value { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeValueStyle ValueStyle { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public string Name { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public object Value { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeValueStyle ValueStyle { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } public void CopyTo(Microsoft.AspNetCore.Html.IHtmlContentBuilder destination) { } public bool Equals(Microsoft.AspNetCore.Razor.TagHelpers.TagHelperAttribute other) { throw null; } public override bool Equals(object obj) { throw null; } @@ -174,24 +174,24 @@ namespace Microsoft.AspNetCore.Razor.TagHelpers public TagHelperContext(Microsoft.AspNetCore.Razor.TagHelpers.TagHelperAttributeList allAttributes, System.Collections.Generic.IDictionary items, string uniqueId) { } public TagHelperContext(string tagName, Microsoft.AspNetCore.Razor.TagHelpers.TagHelperAttributeList allAttributes, System.Collections.Generic.IDictionary items, string uniqueId) { } public Microsoft.AspNetCore.Razor.TagHelpers.ReadOnlyTagHelperAttributeList AllAttributes { get { throw null; } } - public System.Collections.Generic.IDictionary Items { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public string TagName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public string UniqueId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public System.Collections.Generic.IDictionary Items { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public string TagName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public string UniqueId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } public void Reinitialize(System.Collections.Generic.IDictionary items, string uniqueId) { } public void Reinitialize(string tagName, System.Collections.Generic.IDictionary items, string uniqueId) { } } public partial class TagHelperOutput : Microsoft.AspNetCore.Html.IHtmlContent, Microsoft.AspNetCore.Html.IHtmlContentContainer { public TagHelperOutput(string tagName, Microsoft.AspNetCore.Razor.TagHelpers.TagHelperAttributeList attributes, System.Func> getChildContentAsync) { } - public Microsoft.AspNetCore.Razor.TagHelpers.TagHelperAttributeList Attributes { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public Microsoft.AspNetCore.Razor.TagHelpers.TagHelperAttributeList Attributes { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } public Microsoft.AspNetCore.Razor.TagHelpers.TagHelperContent Content { get { throw null; } set { } } public bool IsContentModified { get { throw null; } } public Microsoft.AspNetCore.Razor.TagHelpers.TagHelperContent PostContent { get { throw null; } } public Microsoft.AspNetCore.Razor.TagHelpers.TagHelperContent PostElement { get { throw null; } } public Microsoft.AspNetCore.Razor.TagHelpers.TagHelperContent PreContent { get { throw null; } } public Microsoft.AspNetCore.Razor.TagHelpers.TagHelperContent PreElement { get { throw null; } } - public Microsoft.AspNetCore.Razor.TagHelpers.TagMode TagMode { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public string TagName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.AspNetCore.Razor.TagHelpers.TagMode TagMode { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public string TagName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public System.Threading.Tasks.Task GetChildContentAsync() { throw null; } public System.Threading.Tasks.Task GetChildContentAsync(bool useCachedResult) { throw null; } public System.Threading.Tasks.Task GetChildContentAsync(bool useCachedResult, System.Text.Encodings.Web.HtmlEncoder encoder) { throw null; } diff --git a/src/Razor/Razor/src/Microsoft.AspNetCore.Razor.csproj b/src/Razor/Razor/src/Microsoft.AspNetCore.Razor.csproj index 9f8d7443c4..482bcbeeae 100644 --- a/src/Razor/Razor/src/Microsoft.AspNetCore.Razor.csproj +++ b/src/Razor/Razor/src/Microsoft.AspNetCore.Razor.csproj @@ -21,7 +21,8 @@ Microsoft.AspNetCore.Razor.TagHelpers.ITagHelper - + + diff --git a/src/Security/Authentication/Certificate/src/CertificateAuthenticationHandler.cs b/src/Security/Authentication/Certificate/src/CertificateAuthenticationHandler.cs index 68a7abdde0..a33b05ceb8 100644 --- a/src/Security/Authentication/Certificate/src/CertificateAuthenticationHandler.cs +++ b/src/Security/Authentication/Certificate/src/CertificateAuthenticationHandler.cs @@ -167,6 +167,15 @@ namespace Microsoft.AspNetCore.Authentication.Certificate chainPolicy.VerificationFlags |= X509VerificationFlags.IgnoreEndRevocationUnknown; chainPolicy.ExtraStore.Add(certificate); } + else + { + if (Options.CustomTrustStore != null) + { + chainPolicy.CustomTrustStore.AddRange(Options.CustomTrustStore); + } + + chainPolicy.TrustMode = Options.ChainTrustValidationMode; + } if (!Options.ValidateValidityPeriod) { diff --git a/src/Security/Authentication/Certificate/src/CertificateAuthenticationOptions.cs b/src/Security/Authentication/Certificate/src/CertificateAuthenticationOptions.cs index 1b8eebfa6f..70789514ac 100644 --- a/src/Security/Authentication/Certificate/src/CertificateAuthenticationOptions.cs +++ b/src/Security/Authentication/Certificate/src/CertificateAuthenticationOptions.cs @@ -15,10 +15,21 @@ namespace Microsoft.AspNetCore.Authentication.Certificate /// public CertificateTypes AllowedCertificateTypes { get; set; } = CertificateTypes.Chained; + /// + /// Collection of X509 certificates which are trusted components of the certificate chain. + /// + public X509Certificate2Collection CustomTrustStore { get; set; } = new X509Certificate2Collection(); + + /// + /// Method used to validate certificate chains against . + /// + /// This property must be set to to enable to be used in certificate chain validation. + public X509ChainTrustMode ChainTrustValidationMode { get; set; } = X509ChainTrustMode.System; + /// /// Flag indicating whether the client certificate must be suitable for client /// authentication, either via the Client Authentication EKU, or having no EKUs - /// at all. If the certificate chains to a root CA all certificates in the chain must be validate + /// at all. If the certificate chains to a root CA all certificates in the chain must be validated /// for the client authentication EKU. /// public bool ValidateCertificateUse { get; set; } = true; diff --git a/src/Security/Authentication/Certificate/src/Microsoft.AspNetCore.Authentication.Certificate.csproj b/src/Security/Authentication/Certificate/src/Microsoft.AspNetCore.Authentication.Certificate.csproj index cffabda435..86af316bad 100644 --- a/src/Security/Authentication/Certificate/src/Microsoft.AspNetCore.Authentication.Certificate.csproj +++ b/src/Security/Authentication/Certificate/src/Microsoft.AspNetCore.Authentication.Certificate.csproj @@ -6,7 +6,7 @@ $(DefineConstants);SECURITY true aspnetcore;authentication;security;x509;certificate - true + true diff --git a/src/Security/Authentication/Certificate/src/README-IISConfig.png b/src/Security/Authentication/Certificate/src/README-IISConfig.png deleted file mode 100644 index 3af15e9d06..0000000000 Binary files a/src/Security/Authentication/Certificate/src/README-IISConfig.png and /dev/null differ diff --git a/src/Security/Authentication/Certificate/src/README.md b/src/Security/Authentication/Certificate/src/README.md index 542131fdf1..a589666e0a 100644 --- a/src/Security/Authentication/Certificate/src/README.md +++ b/src/Security/Authentication/Certificate/src/README.md @@ -1,234 +1,5 @@ # Microsoft.AspNetCore.Authentication.Certificate - -This project sort of contains an implementation of [Certificate Authentication](https://tools.ietf.org/html/rfc5246#section-7.4.4) for ASP.NET Core. -Certificate authentication happens at the TLS level, long before it ever gets to ASP.NET Core, so, more accurately this is an authentication handler -that validates the certificate and then gives you an event where you can resolve that certificate to a ClaimsPrincipal. -You **must** [configure your host](#hostConfiguration) for certificate authentication, be it IIS, Kestrel, Azure Web Applications or whatever else you're using. - -## Getting started - -First acquire an HTTPS certificate, apply it and then [configure your host](#hostConfiguration) to require certificates. - -In your web application add a reference to the package, then in the `ConfigureServices` method in `startup.cs` call -`app.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme).UseCertificateAuthentication(...);` with your options, -providing a delegate for `OnValidateCertificate` to validate the client certificate sent with requests and turn that information -into an `ClaimsPrincipal`, set it on the `context.Principal` property and call `context.Success()`. - -If you change your scheme name in the options for the authentication handler you need to change the scheme name in -`AddAuthentication()` to ensure it's used on every request which ends in an endpoint that requires authorization. - -If authentication fails this handler will return a `403 (Forbidden)` response rather a `401 (Unauthorized)` as you -might expect - this is because the authentication should happen during the initial TLS connection - by the time it -reaches the handler it's too late, and there's no way to actually upgrade the connection from an anonymous connection -to one with a certificate. - -You must also add `app.UseAuthentication();` in the `Configure` method, otherwise nothing will ever get called. - -For example; - -```c# -public void ConfigureServices(IServiceCollection services) -{ - services.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme) - .AddCertificate(); - // All the other service configuration. -} - -public void Configure(IApplicationBuilder app, IHostingEnvironment env) -{ - app.UseAuthentication(); - - // All the other app configuration. -} -``` - -In the sample above you can see the default way to add certificate authentication. The handler will construct a user principal using the common certificate properties for you. - -## Configuring Certificate Validation - -The `CertificateAuthenticationOptions` handler has some built in validations that are the minimium validations you should perform on -a certificate. Each of these settings are turned on by default. - -### ValidateCertificateChain - -This check validates that the issuer for the certificate is trusted by the application host OS. If -you are going to accept self-signed certificates you must disable this check. - -### ValidateCertificateUse - -This check validates that the certificate presented by the client has the Client Authentication -extended key use, or no EKUs at all (as the specifications say if no EKU is specified then all EKUs -are valid). - -### ValidateValidityPeriod - -This check validates that the certificate is within its validity period. As the handler runs on every -request this ensures that a certificate that was valid when it was presented has not expired during -its current session. - -### RevocationFlag - -A flag which specifies which certificates in the chain are checked for revocation. - -Revocation checks are only performed when the certificate is chained to a root certificate. - -### RevocationMode - -A flag which specifies how revocation checks are performed. -Specifying an on-line check can result in a long delay while the certificate authority is contacted. - -Revocation checks are only performed when the certificate is chained to a root certificate. - -### Can I configure my application to require a certificate only on certain paths? - -Not possible, remember the certificate exchange is done that the start of the HTTPS conversation, -it's done by the host, not the application. Kestrel, IIS, Azure Web Apps don't have any configuration for -this sort of thing. - -# Handler events - -The handler has two events, `OnAuthenticationFailed()`, which is called if an exception happens during authentication and allows you to react, and `OnValidateCertificate()` which is -called after certificate has been validated, passed validation, abut before the default principal has been created. This allows you to perform your own validation, for example -checking if the certificate is one your services knows about, and to construct your own principal. For example, - -```c# -services.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme) - .AddCertificate(options => - { - options.Events = new CertificateAuthenticationEvents - { - OnValidateCertificate = context => - { - var claims = new[] - { - new Claim(ClaimTypes.NameIdentifier, context.ClientCertificate.Subject, ClaimValueTypes.String, context.Options.ClaimsIssuer), - new Claim(ClaimTypes.Name, context.ClientCertificate.Subject, ClaimValueTypes.String, context.Options.ClaimsIssuer) - }; - - context.Principal = new ClaimsPrincipal(new ClaimsIdentity(claims, context.Scheme.Name)); - context.Success(); - - return Task.CompletedTask; - } - }; - }); -``` - -If you find the inbound certificate doesn't meet your extra validation call `context.Fail("failure Reason")` with a failure reason. - -For real functionality you will probably want to call a service registered in DI which talks to a database or other type of -user store. You can grab your service by using the context passed into your delegates, like so - -```c# -services.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme) - .AddCertificate(options => - { - options.Events = new CertificateAuthenticationEvents - { - OnCertificateValidated = context => - { - var validationService = - context.HttpContext.RequestServices.GetService(); - - if (validationService.ValidateCertificate(context.ClientCertificate)) - { - var claims = new[] - { - new Claim(ClaimTypes.NameIdentifier, context.ClientCertificate.Subject, ClaimValueTypes.String, context.Options.ClaimsIssuer), - new Claim(ClaimTypes.Name, context.ClientCertificate.Subject, ClaimValueTypes.String, context.Options.ClaimsIssuer) - }; - - context.Principal = new ClaimsPrincipal(new ClaimsIdentity(claims, context.Scheme.Name)); - context.Success(); - } - - return Task.CompletedTask; - } - }; - }); -``` -Note that conceptually the validation of the certification is an authorization concern, and putting a check on, for example, an issuer or thumbprint in an authorization policy rather -than inside OnCertificateValidated() is perfectly acceptable. - -## Configuring your host to require certificates - -### Kestrel - -In program.cs configure `UseKestrel()` as follows. - -```c# -public static IWebHost BuildWebHost(string[] args) - => WebHost.CreateDefaultBuilder(args) - .UseStartup() - .ConfigureKestrel(options => - { - options.ConfigureHttpsDefaults(opt => - { - opt.ClientCertificateMode = ClientCertificateMode.RequireCertificate; - }); - }) - .Build(); -``` -You must set the `ClientCertificateValidation` delegate to `CertificateValidator.DisableChannelValidation` in order to stop Kestrel using the default OS certificate validation routine and, -instead, letting the authentication handler perform the validation. - -### IIS - -In the IIS Manager - -1. Select your Site in the Connections tab. -2. Double click the SSL Settings in the Features View window. -3. Check the `Require SSL` Check Box and select the `Require` radio button under Client Certificates. - -![Client Certificate Settings in IIS](README-IISConfig.png "Client Certificate Settings in IIS") - -### Azure - -See the [Azure documentation](https://docs.microsoft.com/en-us/azure/app-service/app-service-web-configure-tls-mutual-auth) -to configure Azure Web Apps then add the following to your application startup method, `Configure(IApplicationBuilder app)` add the -following line before the call to `app.UseAuthentication();` - -```c# -app.UseCertificateHeaderForwarding(); -``` - -### Random custom web proxies - -If you're using a proxy which isn't IIS or Azure's Web Apps Application Request Routing you will need to configure your proxy -to forward the certificate it received in an HTTP header. -In your application startup method, `Configure(IApplicationBuilder app)`, add the -following line before the call to `app.UseAuthentication();` - -```c# -app.UseCertificateForwarding(); -``` - -You will also need to configure the Certificate Forwarding middleware to specify the header name. -In your service configuration method, `ConfigureServices(IServiceCollection services)` add -the following code to configure the header the forwarding middleware will build a certificate from; - -```c# -services.AddCertificateForwarding(options => -{ - options.CertificateHeader = "YOUR_CUSTOM_HEADER_NAME"; -}); -``` - -Finally, if your proxy is doing something weird to pass the header on, rather than base 64 encoding it -(looking at you nginx (╯°□°)╯︵ ┻━┻) you can override the converter option to be a func that will -perform the optional conversion, for example - -```c# -services.AddCertificateForwarding(options => -{ - options.CertificateHeader = "YOUR_CUSTOM_HEADER_NAME"; - options.HeaderConverter = (headerValue) => - { - var clientCertificate = - /* some weird conversion logic to create an X509Certificate2 */ - return clientCertificate; - } -}); -``` +This project sort of contains an implementation of [Certificate Authentication](https://tools.ietf.org/html/rfc5246#section-7.4.4) for ASP.NET Core. Certificate authentication happens at the TLS level, long before it ever gets to ASP.NET Core, so, more accurately this is an authentication handler that validates the certificate and then gives you an event where you can resolve that certificate to a ClaimsPrincipal. +For more information, see [Configure certificate authentication in ASP.NET Core](https://docs.microsoft.com/aspnet/core/security/authentication/certauth). diff --git a/src/Security/Authentication/Cookies/ref/Microsoft.AspNetCore.Authentication.Cookies.netcoreapp.cs b/src/Security/Authentication/Cookies/ref/Microsoft.AspNetCore.Authentication.Cookies.netcoreapp.cs index d9457f3861..f3d730a1f9 100644 --- a/src/Security/Authentication/Cookies/ref/Microsoft.AspNetCore.Authentication.Cookies.netcoreapp.cs +++ b/src/Security/Authentication/Cookies/ref/Microsoft.AspNetCore.Authentication.Cookies.netcoreapp.cs @@ -7,8 +7,8 @@ namespace Microsoft.AspNetCore.Authentication.Cookies { public const int DefaultChunkSize = 4050; public ChunkingCookieManager() { } - public int? ChunkSize { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public bool ThrowForPartialCookies { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public int? ChunkSize { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public bool ThrowForPartialCookies { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public void AppendResponseCookie(Microsoft.AspNetCore.Http.HttpContext context, string key, string value, Microsoft.AspNetCore.Http.CookieOptions options) { } public void DeleteCookie(Microsoft.AspNetCore.Http.HttpContext context, string key, Microsoft.AspNetCore.Http.CookieOptions options) { } public string GetRequestCookie(Microsoft.AspNetCore.Http.HttpContext context, string key) { throw null; } @@ -25,14 +25,14 @@ namespace Microsoft.AspNetCore.Authentication.Cookies public partial class CookieAuthenticationEvents { public CookieAuthenticationEvents() { } - public System.Func, System.Threading.Tasks.Task> OnRedirectToAccessDenied { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public System.Func, System.Threading.Tasks.Task> OnRedirectToLogin { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public System.Func, System.Threading.Tasks.Task> OnRedirectToLogout { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public System.Func, System.Threading.Tasks.Task> OnRedirectToReturnUrl { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public System.Func OnSignedIn { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public System.Func OnSigningIn { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public System.Func OnSigningOut { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public System.Func OnValidatePrincipal { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public System.Func, System.Threading.Tasks.Task> OnRedirectToAccessDenied { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public System.Func, System.Threading.Tasks.Task> OnRedirectToLogin { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public System.Func, System.Threading.Tasks.Task> OnRedirectToLogout { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public System.Func, System.Threading.Tasks.Task> OnRedirectToReturnUrl { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public System.Func OnSignedIn { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public System.Func OnSigningIn { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public System.Func OnSigningOut { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public System.Func OnValidatePrincipal { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public virtual System.Threading.Tasks.Task RedirectToAccessDenied(Microsoft.AspNetCore.Authentication.RedirectContext context) { throw null; } public virtual System.Threading.Tasks.Task RedirectToLogin(Microsoft.AspNetCore.Authentication.RedirectContext context) { throw null; } public virtual System.Threading.Tasks.Task RedirectToLogout(Microsoft.AspNetCore.Authentication.RedirectContext context) { throw null; } @@ -64,18 +64,18 @@ namespace Microsoft.AspNetCore.Authentication.Cookies public partial class CookieAuthenticationOptions : Microsoft.AspNetCore.Authentication.AuthenticationSchemeOptions { public CookieAuthenticationOptions() { } - public Microsoft.AspNetCore.Http.PathString AccessDeniedPath { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.AspNetCore.Http.PathString AccessDeniedPath { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public Microsoft.AspNetCore.Http.CookieBuilder Cookie { get { throw null; } set { } } - public Microsoft.AspNetCore.Authentication.Cookies.ICookieManager CookieManager { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public Microsoft.AspNetCore.DataProtection.IDataProtectionProvider DataProtectionProvider { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.AspNetCore.Authentication.Cookies.ICookieManager CookieManager { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public Microsoft.AspNetCore.DataProtection.IDataProtectionProvider DataProtectionProvider { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public new Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationEvents Events { get { throw null; } set { } } - public System.TimeSpan ExpireTimeSpan { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public Microsoft.AspNetCore.Http.PathString LoginPath { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public Microsoft.AspNetCore.Http.PathString LogoutPath { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public string ReturnUrlParameter { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public Microsoft.AspNetCore.Authentication.Cookies.ITicketStore SessionStore { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public bool SlidingExpiration { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public Microsoft.AspNetCore.Authentication.ISecureDataFormat TicketDataFormat { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public System.TimeSpan ExpireTimeSpan { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public Microsoft.AspNetCore.Http.PathString LoginPath { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public Microsoft.AspNetCore.Http.PathString LogoutPath { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public string ReturnUrlParameter { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public Microsoft.AspNetCore.Authentication.Cookies.ITicketStore SessionStore { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public bool SlidingExpiration { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public Microsoft.AspNetCore.Authentication.ISecureDataFormat TicketDataFormat { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } public partial class CookieSignedInContext : Microsoft.AspNetCore.Authentication.PrincipalContext { @@ -84,17 +84,17 @@ namespace Microsoft.AspNetCore.Authentication.Cookies public partial class CookieSigningInContext : Microsoft.AspNetCore.Authentication.PrincipalContext { public CookieSigningInContext(Microsoft.AspNetCore.Http.HttpContext context, Microsoft.AspNetCore.Authentication.AuthenticationScheme scheme, Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationOptions options, System.Security.Claims.ClaimsPrincipal principal, Microsoft.AspNetCore.Authentication.AuthenticationProperties properties, Microsoft.AspNetCore.Http.CookieOptions cookieOptions) : base (default(Microsoft.AspNetCore.Http.HttpContext), default(Microsoft.AspNetCore.Authentication.AuthenticationScheme), default(Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationOptions), default(Microsoft.AspNetCore.Authentication.AuthenticationProperties)) { } - public Microsoft.AspNetCore.Http.CookieOptions CookieOptions { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.AspNetCore.Http.CookieOptions CookieOptions { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } public partial class CookieSigningOutContext : Microsoft.AspNetCore.Authentication.PropertiesContext { public CookieSigningOutContext(Microsoft.AspNetCore.Http.HttpContext context, Microsoft.AspNetCore.Authentication.AuthenticationScheme scheme, Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationOptions options, Microsoft.AspNetCore.Authentication.AuthenticationProperties properties, Microsoft.AspNetCore.Http.CookieOptions cookieOptions) : base (default(Microsoft.AspNetCore.Http.HttpContext), default(Microsoft.AspNetCore.Authentication.AuthenticationScheme), default(Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationOptions), default(Microsoft.AspNetCore.Authentication.AuthenticationProperties)) { } - public Microsoft.AspNetCore.Http.CookieOptions CookieOptions { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.AspNetCore.Http.CookieOptions CookieOptions { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } public partial class CookieValidatePrincipalContext : Microsoft.AspNetCore.Authentication.PrincipalContext { public CookieValidatePrincipalContext(Microsoft.AspNetCore.Http.HttpContext context, Microsoft.AspNetCore.Authentication.AuthenticationScheme scheme, Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationOptions options, Microsoft.AspNetCore.Authentication.AuthenticationTicket ticket) : base (default(Microsoft.AspNetCore.Http.HttpContext), default(Microsoft.AspNetCore.Authentication.AuthenticationScheme), default(Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationOptions), default(Microsoft.AspNetCore.Authentication.AuthenticationProperties)) { } - public bool ShouldRenew { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public bool ShouldRenew { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public void RejectPrincipal() { } public void ReplacePrincipal(System.Security.Claims.ClaimsPrincipal principal) { } } diff --git a/src/Security/Authentication/Cookies/src/CookieAuthenticationEvents.cs b/src/Security/Authentication/Cookies/src/CookieAuthenticationEvents.cs index a6bb4e7d1c..a751c3eb53 100644 --- a/src/Security/Authentication/Cookies/src/CookieAuthenticationEvents.cs +++ b/src/Security/Authentication/Cookies/src/CookieAuthenticationEvents.cs @@ -9,7 +9,7 @@ using Microsoft.Net.Http.Headers; namespace Microsoft.AspNetCore.Authentication.Cookies { /// - /// This default implementation of the ICookieAuthenticationEvents may be used if the + /// This default implementation of the ICookieAuthenticationEvents may be used if the /// application only needs to override a few of the interface methods. This may be used as a base class /// or may be instantiated directly. /// @@ -103,9 +103,9 @@ namespace Microsoft.AspNetCore.Authentication.Cookies private static bool IsAjaxRequest(HttpRequest request) { - return string.Equals(request.Query["X-Requested-With"], "XMLHttpRequest", StringComparison.Ordinal) || - string.Equals(request.Headers["X-Requested-With"], "XMLHttpRequest", StringComparison.Ordinal); - } + return string.Equals(request.Query[HeaderNames.XRequestedWith], "XMLHttpRequest", StringComparison.Ordinal) || + string.Equals(request.Headers[HeaderNames.XRequestedWith], "XMLHttpRequest", StringComparison.Ordinal); + } /// /// Implements the interface method by invoking the related delegate method. diff --git a/src/Security/Authentication/Cookies/src/CookieAuthenticationOptions.cs b/src/Security/Authentication/Cookies/src/CookieAuthenticationOptions.cs index fc4edaa3dd..0248669979 100644 --- a/src/Security/Authentication/Cookies/src/CookieAuthenticationOptions.cs +++ b/src/Security/Authentication/Cookies/src/CookieAuthenticationOptions.cs @@ -39,27 +39,27 @@ namespace Microsoft.AspNetCore.Authentication.Cookies /// Determines the settings used to create the cookie. /// /// - /// defaults to . - /// defaults to true. - /// defaults to . + /// defaults to . + /// defaults to true. + /// defaults to . /// /// /// /// - /// The default value for cookie name is ".AspNetCore.Cookies". - /// This value should be changed if you change the name of the AuthenticationScheme, especially if your + /// The default value for cookie is ".AspNetCore.Cookies". + /// This value should be changed if you change the name of the AuthenticationScheme, especially if your /// system uses the cookie authentication handler multiple times. /// /// - /// determines if the browser should allow the cookie to be attached to same-site or cross-site requests. - /// The default is Lax, which means the cookie is only allowed to be attached to cross-site requests using safe HTTP methods and same-site requests. + /// determines if the browser should allow the cookie to be attached to same-site or cross-site requests. + /// The default is Lax, which means the cookie is only allowed to be attached to cross-site requests using safe HTTP methods and same-site requests. /// /// - /// determines if the browser should allow the cookie to be accessed by client-side javascript. + /// determines if the browser should allow the cookie to be accessed by client-side javascript. /// The default is true, which means the cookie will only be passed to http requests and is not made available to script on the page. /// /// - /// is currently ignored. Use to control lifetime of cookie authentication. + /// is currently ignored. Use to control lifetime of cookie authentication. /// /// public CookieBuilder Cookie @@ -81,8 +81,8 @@ namespace Microsoft.AspNetCore.Authentication.Cookies /// /// The LoginPath property is used by the handler for the redirection target when handling ChallengeAsync. - /// The current url which is added to the LoginPath as a query string parameter named by the ReturnUrlParameter. - /// Once a request to the LoginPath grants a new SignIn identity, the ReturnUrlParameter value is used to redirect + /// The current url which is added to the LoginPath as a query string parameter named by the ReturnUrlParameter. + /// Once a request to the LoginPath grants a new SignIn identity, the ReturnUrlParameter value is used to redirect /// the browser back to the original url. /// public PathString LoginPath { get; set; } @@ -99,7 +99,7 @@ namespace Microsoft.AspNetCore.Authentication.Cookies /// /// The ReturnUrlParameter determines the name of the query string parameter which is appended by the handler - /// when during a Challenge. This is also the query string parameter looked for when a request arrives on the + /// when during a Challenge. This is also the query string parameter looked for when a request arrives on the /// login path or logout path, in order to return to the original url after the action is performed. /// public string ReturnUrlParameter { get; set; } @@ -141,7 +141,7 @@ namespace Microsoft.AspNetCore.Authentication.Cookies /// even if it is passed to the server after the browser should have purged it. /// /// - /// This is separate from the value of , which specifies + /// This is separate from the value of , which specifies /// how long the browser will keep the cookie. /// /// diff --git a/src/Security/Authentication/Core/ref/Microsoft.AspNetCore.Authentication.netcoreapp.cs b/src/Security/Authentication/Core/ref/Microsoft.AspNetCore.Authentication.netcoreapp.cs index ac15a1853d..1a1d21a774 100644 --- a/src/Security/Authentication/Core/ref/Microsoft.AspNetCore.Authentication.netcoreapp.cs +++ b/src/Security/Authentication/Core/ref/Microsoft.AspNetCore.Authentication.netcoreapp.cs @@ -6,15 +6,15 @@ namespace Microsoft.AspNetCore.Authentication public partial class AccessDeniedContext : Microsoft.AspNetCore.Authentication.HandleRequestContext { public AccessDeniedContext(Microsoft.AspNetCore.Http.HttpContext context, Microsoft.AspNetCore.Authentication.AuthenticationScheme scheme, Microsoft.AspNetCore.Authentication.RemoteAuthenticationOptions options) : base (default(Microsoft.AspNetCore.Http.HttpContext), default(Microsoft.AspNetCore.Authentication.AuthenticationScheme), default(Microsoft.AspNetCore.Authentication.RemoteAuthenticationOptions)) { } - public Microsoft.AspNetCore.Http.PathString AccessDeniedPath { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public Microsoft.AspNetCore.Authentication.AuthenticationProperties Properties { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public string ReturnUrl { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public string ReturnUrlParameter { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.AspNetCore.Http.PathString AccessDeniedPath { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public Microsoft.AspNetCore.Authentication.AuthenticationProperties Properties { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public string ReturnUrl { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public string ReturnUrlParameter { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } public partial class AuthenticationBuilder { public AuthenticationBuilder(Microsoft.Extensions.DependencyInjection.IServiceCollection services) { } - public virtual Microsoft.Extensions.DependencyInjection.IServiceCollection Services { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public virtual Microsoft.Extensions.DependencyInjection.IServiceCollection Services { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } public virtual Microsoft.AspNetCore.Authentication.AuthenticationBuilder AddPolicyScheme(string authenticationScheme, string displayName, System.Action configureOptions) { throw null; } public virtual Microsoft.AspNetCore.Authentication.AuthenticationBuilder AddRemoteScheme(string authenticationScheme, string displayName, System.Action configureOptions) where TOptions : Microsoft.AspNetCore.Authentication.RemoteAuthenticationOptions, new() where THandler : Microsoft.AspNetCore.Authentication.RemoteAuthenticationHandler { throw null; } public virtual Microsoft.AspNetCore.Authentication.AuthenticationBuilder AddScheme(string authenticationScheme, System.Action configureOptions) where TOptions : Microsoft.AspNetCore.Authentication.AuthenticationSchemeOptions, new() where THandler : Microsoft.AspNetCore.Authentication.AuthenticationHandler { throw null; } @@ -24,19 +24,19 @@ namespace Microsoft.AspNetCore.Authentication { protected AuthenticationHandler(Microsoft.Extensions.Options.IOptionsMonitor options, Microsoft.Extensions.Logging.ILoggerFactory logger, System.Text.Encodings.Web.UrlEncoder encoder, Microsoft.AspNetCore.Authentication.ISystemClock clock) { } protected virtual string ClaimsIssuer { get { throw null; } } - protected Microsoft.AspNetCore.Authentication.ISystemClock Clock { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - protected Microsoft.AspNetCore.Http.HttpContext Context { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + protected Microsoft.AspNetCore.Authentication.ISystemClock Clock { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + protected Microsoft.AspNetCore.Http.HttpContext Context { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } protected string CurrentUri { get { throw null; } } - protected virtual object Events { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - protected Microsoft.Extensions.Logging.ILogger Logger { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public TOptions Options { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - protected Microsoft.Extensions.Options.IOptionsMonitor OptionsMonitor { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + protected virtual object Events { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + protected Microsoft.Extensions.Logging.ILogger Logger { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public TOptions Options { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + protected Microsoft.Extensions.Options.IOptionsMonitor OptionsMonitor { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } protected Microsoft.AspNetCore.Http.PathString OriginalPath { get { throw null; } } protected Microsoft.AspNetCore.Http.PathString OriginalPathBase { get { throw null; } } protected Microsoft.AspNetCore.Http.HttpRequest Request { get { throw null; } } protected Microsoft.AspNetCore.Http.HttpResponse Response { get { throw null; } } - public Microsoft.AspNetCore.Authentication.AuthenticationScheme Scheme { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - protected System.Text.Encodings.Web.UrlEncoder UrlEncoder { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public Microsoft.AspNetCore.Authentication.AuthenticationScheme Scheme { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + protected System.Text.Encodings.Web.UrlEncoder UrlEncoder { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } [System.Diagnostics.DebuggerStepThroughAttribute] public System.Threading.Tasks.Task AuthenticateAsync() { throw null; } protected string BuildRedirectUri(string targetPath) { throw null; } @@ -61,23 +61,23 @@ namespace Microsoft.AspNetCore.Authentication public partial class AuthenticationMiddleware { public AuthenticationMiddleware(Microsoft.AspNetCore.Http.RequestDelegate next, Microsoft.AspNetCore.Authentication.IAuthenticationSchemeProvider schemes) { } - public Microsoft.AspNetCore.Authentication.IAuthenticationSchemeProvider Schemes { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.AspNetCore.Authentication.IAuthenticationSchemeProvider Schemes { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } [System.Diagnostics.DebuggerStepThroughAttribute] public System.Threading.Tasks.Task Invoke(Microsoft.AspNetCore.Http.HttpContext context) { throw null; } } public partial class AuthenticationSchemeOptions { public AuthenticationSchemeOptions() { } - public string ClaimsIssuer { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public object Events { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public System.Type EventsType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public string ForwardAuthenticate { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public string ForwardChallenge { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public string ForwardDefault { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public System.Func ForwardDefaultSelector { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public string ForwardForbid { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public string ForwardSignIn { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public string ForwardSignOut { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public string ClaimsIssuer { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public object Events { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public System.Type EventsType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public string ForwardAuthenticate { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public string ForwardChallenge { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public string ForwardDefault { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public System.Func ForwardDefaultSelector { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public string ForwardForbid { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public string ForwardSignIn { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public string ForwardSignOut { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public virtual void Validate() { } public virtual void Validate(string scheme) { } } @@ -89,24 +89,24 @@ namespace Microsoft.AspNetCore.Authentication public abstract partial class BaseContext where TOptions : Microsoft.AspNetCore.Authentication.AuthenticationSchemeOptions { protected BaseContext(Microsoft.AspNetCore.Http.HttpContext context, Microsoft.AspNetCore.Authentication.AuthenticationScheme scheme, TOptions options) { } - public Microsoft.AspNetCore.Http.HttpContext HttpContext { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public TOptions Options { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public Microsoft.AspNetCore.Http.HttpContext HttpContext { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public TOptions Options { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } public Microsoft.AspNetCore.Http.HttpRequest Request { get { throw null; } } public Microsoft.AspNetCore.Http.HttpResponse Response { get { throw null; } } - public Microsoft.AspNetCore.Authentication.AuthenticationScheme Scheme { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public Microsoft.AspNetCore.Authentication.AuthenticationScheme Scheme { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } } public partial class HandleRequestContext : Microsoft.AspNetCore.Authentication.BaseContext where TOptions : Microsoft.AspNetCore.Authentication.AuthenticationSchemeOptions { protected HandleRequestContext(Microsoft.AspNetCore.Http.HttpContext context, Microsoft.AspNetCore.Authentication.AuthenticationScheme scheme, TOptions options) : base (default(Microsoft.AspNetCore.Http.HttpContext), default(Microsoft.AspNetCore.Authentication.AuthenticationScheme), default(TOptions)) { } - public Microsoft.AspNetCore.Authentication.HandleRequestResult Result { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]protected set { } } + public Microsoft.AspNetCore.Authentication.HandleRequestResult Result { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] protected set { } } public void HandleResponse() { } public void SkipHandler() { } } public partial class HandleRequestResult : Microsoft.AspNetCore.Authentication.AuthenticateResult { public HandleRequestResult() { } - public bool Handled { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public bool Skipped { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public bool Handled { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public bool Skipped { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } public static new Microsoft.AspNetCore.Authentication.HandleRequestResult Fail(System.Exception failure) { throw null; } public static new Microsoft.AspNetCore.Authentication.HandleRequestResult Fail(System.Exception failure, Microsoft.AspNetCore.Authentication.AuthenticationProperties properties) { throw null; } public static new Microsoft.AspNetCore.Authentication.HandleRequestResult Fail(string failureMessage) { throw null; } @@ -152,12 +152,12 @@ namespace Microsoft.AspNetCore.Authentication public abstract partial class PrincipalContext : Microsoft.AspNetCore.Authentication.PropertiesContext where TOptions : Microsoft.AspNetCore.Authentication.AuthenticationSchemeOptions { protected PrincipalContext(Microsoft.AspNetCore.Http.HttpContext context, Microsoft.AspNetCore.Authentication.AuthenticationScheme scheme, TOptions options, Microsoft.AspNetCore.Authentication.AuthenticationProperties properties) : base (default(Microsoft.AspNetCore.Http.HttpContext), default(Microsoft.AspNetCore.Authentication.AuthenticationScheme), default(TOptions), default(Microsoft.AspNetCore.Authentication.AuthenticationProperties)) { } - public virtual System.Security.Claims.ClaimsPrincipal Principal { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public virtual System.Security.Claims.ClaimsPrincipal Principal { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } public abstract partial class PropertiesContext : Microsoft.AspNetCore.Authentication.BaseContext where TOptions : Microsoft.AspNetCore.Authentication.AuthenticationSchemeOptions { protected PropertiesContext(Microsoft.AspNetCore.Http.HttpContext context, Microsoft.AspNetCore.Authentication.AuthenticationScheme scheme, TOptions options, Microsoft.AspNetCore.Authentication.AuthenticationProperties properties) : base (default(Microsoft.AspNetCore.Http.HttpContext), default(Microsoft.AspNetCore.Authentication.AuthenticationScheme), default(TOptions)) { } - public virtual Microsoft.AspNetCore.Authentication.AuthenticationProperties Properties { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]protected set { } } + public virtual Microsoft.AspNetCore.Authentication.AuthenticationProperties Properties { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] protected set { } } } public partial class PropertiesDataFormat : Microsoft.AspNetCore.Authentication.SecureDataFormat { @@ -166,7 +166,7 @@ namespace Microsoft.AspNetCore.Authentication public partial class PropertiesSerializer : Microsoft.AspNetCore.Authentication.IDataSerializer { public PropertiesSerializer() { } - public static Microsoft.AspNetCore.Authentication.PropertiesSerializer Default { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public static Microsoft.AspNetCore.Authentication.PropertiesSerializer Default { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } public virtual Microsoft.AspNetCore.Authentication.AuthenticationProperties Deserialize(byte[] data) { throw null; } public virtual Microsoft.AspNetCore.Authentication.AuthenticationProperties Read(System.IO.BinaryReader reader) { throw null; } public virtual byte[] Serialize(Microsoft.AspNetCore.Authentication.AuthenticationProperties model) { throw null; } @@ -175,13 +175,13 @@ namespace Microsoft.AspNetCore.Authentication public partial class RedirectContext : Microsoft.AspNetCore.Authentication.PropertiesContext where TOptions : Microsoft.AspNetCore.Authentication.AuthenticationSchemeOptions { public RedirectContext(Microsoft.AspNetCore.Http.HttpContext context, Microsoft.AspNetCore.Authentication.AuthenticationScheme scheme, TOptions options, Microsoft.AspNetCore.Authentication.AuthenticationProperties properties, string redirectUri) : base (default(Microsoft.AspNetCore.Http.HttpContext), default(Microsoft.AspNetCore.Authentication.AuthenticationScheme), default(TOptions), default(Microsoft.AspNetCore.Authentication.AuthenticationProperties)) { } - public string RedirectUri { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public string RedirectUri { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } public abstract partial class RemoteAuthenticationContext : Microsoft.AspNetCore.Authentication.HandleRequestContext where TOptions : Microsoft.AspNetCore.Authentication.AuthenticationSchemeOptions { protected RemoteAuthenticationContext(Microsoft.AspNetCore.Http.HttpContext context, Microsoft.AspNetCore.Authentication.AuthenticationScheme scheme, TOptions options, Microsoft.AspNetCore.Authentication.AuthenticationProperties properties) : base (default(Microsoft.AspNetCore.Http.HttpContext), default(Microsoft.AspNetCore.Authentication.AuthenticationScheme), default(TOptions)) { } - public System.Security.Claims.ClaimsPrincipal Principal { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public virtual Microsoft.AspNetCore.Authentication.AuthenticationProperties Properties { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public System.Security.Claims.ClaimsPrincipal Principal { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public virtual Microsoft.AspNetCore.Authentication.AuthenticationProperties Properties { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public void Fail(System.Exception failure) { } public void Fail(string failureMessage) { } public void Success() { } @@ -189,9 +189,9 @@ namespace Microsoft.AspNetCore.Authentication public partial class RemoteAuthenticationEvents { public RemoteAuthenticationEvents() { } - public System.Func OnAccessDenied { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public System.Func OnRemoteFailure { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public System.Func OnTicketReceived { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public System.Func OnAccessDenied { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public System.Func OnRemoteFailure { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public System.Func OnTicketReceived { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public virtual System.Threading.Tasks.Task AccessDenied(Microsoft.AspNetCore.Authentication.AccessDeniedContext context) { throw null; } public virtual System.Threading.Tasks.Task RemoteFailure(Microsoft.AspNetCore.Authentication.RemoteFailureContext context) { throw null; } public virtual System.Threading.Tasks.Task TicketReceived(Microsoft.AspNetCore.Authentication.TicketReceivedContext context) { throw null; } @@ -217,39 +217,39 @@ namespace Microsoft.AspNetCore.Authentication public partial class RemoteAuthenticationOptions : Microsoft.AspNetCore.Authentication.AuthenticationSchemeOptions { public RemoteAuthenticationOptions() { } - public Microsoft.AspNetCore.Http.PathString AccessDeniedPath { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public System.Net.Http.HttpClient Backchannel { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public System.Net.Http.HttpMessageHandler BackchannelHttpHandler { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public System.TimeSpan BackchannelTimeout { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public Microsoft.AspNetCore.Http.PathString CallbackPath { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.AspNetCore.Http.PathString AccessDeniedPath { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public System.Net.Http.HttpClient Backchannel { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public System.Net.Http.HttpMessageHandler BackchannelHttpHandler { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public System.TimeSpan BackchannelTimeout { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public Microsoft.AspNetCore.Http.PathString CallbackPath { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public Microsoft.AspNetCore.Http.CookieBuilder CorrelationCookie { get { throw null; } set { } } - public Microsoft.AspNetCore.DataProtection.IDataProtectionProvider DataProtectionProvider { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.AspNetCore.DataProtection.IDataProtectionProvider DataProtectionProvider { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public new Microsoft.AspNetCore.Authentication.RemoteAuthenticationEvents Events { get { throw null; } set { } } - public System.TimeSpan RemoteAuthenticationTimeout { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public string ReturnUrlParameter { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public bool SaveTokens { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public string SignInScheme { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public System.TimeSpan RemoteAuthenticationTimeout { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public string ReturnUrlParameter { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public bool SaveTokens { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public string SignInScheme { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public override void Validate() { } public override void Validate(string scheme) { } } public partial class RemoteFailureContext : Microsoft.AspNetCore.Authentication.HandleRequestContext { public RemoteFailureContext(Microsoft.AspNetCore.Http.HttpContext context, Microsoft.AspNetCore.Authentication.AuthenticationScheme scheme, Microsoft.AspNetCore.Authentication.RemoteAuthenticationOptions options, System.Exception failure) : base (default(Microsoft.AspNetCore.Http.HttpContext), default(Microsoft.AspNetCore.Authentication.AuthenticationScheme), default(Microsoft.AspNetCore.Authentication.RemoteAuthenticationOptions)) { } - public System.Exception Failure { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public Microsoft.AspNetCore.Authentication.AuthenticationProperties Properties { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public System.Exception Failure { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public Microsoft.AspNetCore.Authentication.AuthenticationProperties Properties { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } public partial class RequestPathBaseCookieBuilder : Microsoft.AspNetCore.Http.CookieBuilder { public RequestPathBaseCookieBuilder() { } - protected virtual string AdditionalPath { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + protected virtual string AdditionalPath { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } public override Microsoft.AspNetCore.Http.CookieOptions Build(Microsoft.AspNetCore.Http.HttpContext context, System.DateTimeOffset expiresFrom) { throw null; } } public abstract partial class ResultContext : Microsoft.AspNetCore.Authentication.BaseContext where TOptions : Microsoft.AspNetCore.Authentication.AuthenticationSchemeOptions { protected ResultContext(Microsoft.AspNetCore.Http.HttpContext context, Microsoft.AspNetCore.Authentication.AuthenticationScheme scheme, TOptions options) : base (default(Microsoft.AspNetCore.Http.HttpContext), default(Microsoft.AspNetCore.Authentication.AuthenticationScheme), default(TOptions)) { } - public System.Security.Claims.ClaimsPrincipal Principal { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public System.Security.Claims.ClaimsPrincipal Principal { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public Microsoft.AspNetCore.Authentication.AuthenticationProperties Properties { get { throw null; } set { } } - public Microsoft.AspNetCore.Authentication.AuthenticateResult Result { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public Microsoft.AspNetCore.Authentication.AuthenticateResult Result { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } public void Fail(System.Exception failure) { } public void Fail(string failureMessage) { } public void NoResult() { } @@ -287,12 +287,12 @@ namespace Microsoft.AspNetCore.Authentication public partial class TicketReceivedContext : Microsoft.AspNetCore.Authentication.RemoteAuthenticationContext { public TicketReceivedContext(Microsoft.AspNetCore.Http.HttpContext context, Microsoft.AspNetCore.Authentication.AuthenticationScheme scheme, Microsoft.AspNetCore.Authentication.RemoteAuthenticationOptions options, Microsoft.AspNetCore.Authentication.AuthenticationTicket ticket) : base (default(Microsoft.AspNetCore.Http.HttpContext), default(Microsoft.AspNetCore.Authentication.AuthenticationScheme), default(Microsoft.AspNetCore.Authentication.RemoteAuthenticationOptions), default(Microsoft.AspNetCore.Authentication.AuthenticationProperties)) { } - public string ReturnUri { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public string ReturnUri { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } public partial class TicketSerializer : Microsoft.AspNetCore.Authentication.IDataSerializer { public TicketSerializer() { } - public static Microsoft.AspNetCore.Authentication.TicketSerializer Default { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public static Microsoft.AspNetCore.Authentication.TicketSerializer Default { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } public virtual Microsoft.AspNetCore.Authentication.AuthenticationTicket Deserialize(byte[] data) { throw null; } public virtual Microsoft.AspNetCore.Authentication.AuthenticationTicket Read(System.IO.BinaryReader reader) { throw null; } protected virtual System.Security.Claims.Claim ReadClaim(System.IO.BinaryReader reader, System.Security.Claims.ClaimsIdentity identity) { throw null; } diff --git a/src/Security/Authentication/Core/src/RemoteAuthenticationHandler.cs b/src/Security/Authentication/Core/src/RemoteAuthenticationHandler.cs index a8975f11a0..b4d4cbef88 100644 --- a/src/Security/Authentication/Core/src/RemoteAuthenticationHandler.cs +++ b/src/Security/Authentication/Core/src/RemoteAuthenticationHandler.cs @@ -18,8 +18,6 @@ namespace Microsoft.AspNetCore.Authentication private const string CorrelationMarker = "N"; private const string AuthSchemeKey = ".AuthScheme"; - private static readonly RandomNumberGenerator CryptoRandom = RandomNumberGenerator.Create(); - protected string SignInScheme => Options.SignInScheme; /// @@ -194,7 +192,7 @@ namespace Microsoft.AspNetCore.Authentication } var bytes = new byte[32]; - CryptoRandom.GetBytes(bytes); + RandomNumberGenerator.Fill(bytes); var correlationId = Base64UrlTextEncoder.Encode(bytes); var cookieOptions = Options.CorrelationCookie.Build(Context, Clock.UtcNow); diff --git a/src/Security/Authentication/Facebook/src/Microsoft.AspNetCore.Authentication.Facebook.csproj b/src/Security/Authentication/Facebook/src/Microsoft.AspNetCore.Authentication.Facebook.csproj index 19fbb851d7..4977e1892b 100644 --- a/src/Security/Authentication/Facebook/src/Microsoft.AspNetCore.Authentication.Facebook.csproj +++ b/src/Security/Authentication/Facebook/src/Microsoft.AspNetCore.Authentication.Facebook.csproj @@ -6,7 +6,7 @@ $(NoWarn);CS1591 true aspnetcore;authentication;security - true + true diff --git a/src/Security/Authentication/Google/src/Microsoft.AspNetCore.Authentication.Google.csproj b/src/Security/Authentication/Google/src/Microsoft.AspNetCore.Authentication.Google.csproj index 96bc1b8b33..774fa9cac0 100644 --- a/src/Security/Authentication/Google/src/Microsoft.AspNetCore.Authentication.Google.csproj +++ b/src/Security/Authentication/Google/src/Microsoft.AspNetCore.Authentication.Google.csproj @@ -6,7 +6,7 @@ $(NoWarn);CS1591 true aspnetcore;authentication;security - true + true diff --git a/src/Security/Authentication/JwtBearer/samples/JwtBearerSample/Startup.cs b/src/Security/Authentication/JwtBearer/samples/JwtBearerSample/Startup.cs index 65a3e40f7a..4f8831b29e 100644 --- a/src/Security/Authentication/JwtBearer/samples/JwtBearerSample/Startup.cs +++ b/src/Security/Authentication/JwtBearer/samples/JwtBearerSample/Startup.cs @@ -78,7 +78,7 @@ namespace JwtBearerSample todoApp.Run(async context => { var response = context.Response; - if (context.Request.Method.Equals("POST", System.StringComparison.OrdinalIgnoreCase)) + if (HttpMethods.IsPost(context.Request.Method)) { var reader = new StreamReader(context.Request.Body); var body = await reader.ReadToEndAsync(); diff --git a/src/Security/Authentication/JwtBearer/src/JwtBearerOptions.cs b/src/Security/Authentication/JwtBearer/src/JwtBearerOptions.cs index f0e7cbc5de..758added5b 100644 --- a/src/Security/Authentication/JwtBearer/src/JwtBearerOptions.cs +++ b/src/Security/Authentication/JwtBearer/src/JwtBearerOptions.cs @@ -111,5 +111,15 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer /// from returning an error and an error_description in the WWW-Authenticate header. /// public bool IncludeErrorDetails { get; set; } = true; + + /// + /// 1 day is the default time interval that afterwards, will obtain new configuration. + /// + public TimeSpan AutomaticRefreshInterval { get; set; } = ConfigurationManager.DefaultAutomaticRefreshInterval; + + /// + /// The minimum time between retrievals, in the event that a retrieval failed, or that a refresh was explicitly requested. 30 seconds is the default. + /// + public TimeSpan RefreshInterval { get; set; } = ConfigurationManager.DefaultRefreshInterval; } } diff --git a/src/Security/Authentication/JwtBearer/src/JwtBearerPostConfigureOptions.cs b/src/Security/Authentication/JwtBearer/src/JwtBearerPostConfigureOptions.cs index 8829bfac0f..f52cfb2353 100644 --- a/src/Security/Authentication/JwtBearer/src/JwtBearerPostConfigureOptions.cs +++ b/src/Security/Authentication/JwtBearer/src/JwtBearerPostConfigureOptions.cs @@ -55,7 +55,11 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer httpClient.MaxResponseContentBufferSize = 1024 * 1024 * 10; // 10 MB options.ConfigurationManager = new ConfigurationManager(options.MetadataAddress, new OpenIdConnectConfigurationRetriever(), - new HttpDocumentRetriever(httpClient) { RequireHttps = options.RequireHttpsMetadata }); + new HttpDocumentRetriever(httpClient) { RequireHttps = options.RequireHttpsMetadata }) + { + RefreshInterval = options.RefreshInterval, + AutomaticRefreshInterval = options.AutomaticRefreshInterval, + }; } } } diff --git a/src/Security/Authentication/JwtBearer/src/Microsoft.AspNetCore.Authentication.JwtBearer.csproj b/src/Security/Authentication/JwtBearer/src/Microsoft.AspNetCore.Authentication.JwtBearer.csproj index 45391ac2db..efee848b37 100644 --- a/src/Security/Authentication/JwtBearer/src/Microsoft.AspNetCore.Authentication.JwtBearer.csproj +++ b/src/Security/Authentication/JwtBearer/src/Microsoft.AspNetCore.Authentication.JwtBearer.csproj @@ -6,7 +6,7 @@ $(NoWarn);CS1591 true aspnetcore;authentication;security - true + true diff --git a/src/Security/Authentication/MicrosoftAccount/src/Microsoft.AspNetCore.Authentication.MicrosoftAccount.csproj b/src/Security/Authentication/MicrosoftAccount/src/Microsoft.AspNetCore.Authentication.MicrosoftAccount.csproj index 8f7ee4dc44..0f2dc832cc 100644 --- a/src/Security/Authentication/MicrosoftAccount/src/Microsoft.AspNetCore.Authentication.MicrosoftAccount.csproj +++ b/src/Security/Authentication/MicrosoftAccount/src/Microsoft.AspNetCore.Authentication.MicrosoftAccount.csproj @@ -6,7 +6,7 @@ $(NoWarn);CS1591 true aspnetcore;authentication;security - true + true diff --git a/src/Security/Authentication/MicrosoftAccount/src/MicrosoftAccountHandler.cs b/src/Security/Authentication/MicrosoftAccount/src/MicrosoftAccountHandler.cs index 6114b15394..043900b1aa 100644 --- a/src/Security/Authentication/MicrosoftAccount/src/MicrosoftAccountHandler.cs +++ b/src/Security/Authentication/MicrosoftAccount/src/MicrosoftAccountHandler.cs @@ -20,8 +20,6 @@ namespace Microsoft.AspNetCore.Authentication.MicrosoftAccount { public class MicrosoftAccountHandler : OAuthHandler { - private static readonly RandomNumberGenerator CryptoRandom = RandomNumberGenerator.Create(); - public MicrosoftAccountHandler(IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock) { } @@ -64,7 +62,7 @@ namespace Microsoft.AspNetCore.Authentication.MicrosoftAccount if (Options.UsePkce) { var bytes = new byte[32]; - CryptoRandom.GetBytes(bytes); + RandomNumberGenerator.Fill(bytes); var codeVerifier = Base64UrlTextEncoder.Encode(bytes); // Store this for use during the code redemption. diff --git a/src/Security/Authentication/MicrosoftAccount/src/MicrosoftChallengeProperties.cs b/src/Security/Authentication/MicrosoftAccount/src/MicrosoftChallengeProperties.cs index 8625e3f093..4e9737b509 100644 --- a/src/Security/Authentication/MicrosoftAccount/src/MicrosoftChallengeProperties.cs +++ b/src/Security/Authentication/MicrosoftAccount/src/MicrosoftChallengeProperties.cs @@ -4,7 +4,7 @@ using Microsoft.AspNetCore.Authentication.OAuth; namespace Microsoft.AspNetCore.Authentication.MicrosoftAccount { /// - /// See https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow#request-an-authorization-code for reference + /// See https://docs.microsoft.com/azure/active-directory/develop/v2-oauth2-auth-code-flow#request-an-authorization-code for reference /// public class MicrosoftChallengeProperties : OAuthChallengeProperties { diff --git a/src/Security/Authentication/Negotiate/src/Events/AuthenticationFailedContext.cs b/src/Security/Authentication/Negotiate/src/Events/AuthenticationFailedContext.cs index d64c8da43e..4ea083c94d 100644 --- a/src/Security/Authentication/Negotiate/src/Events/AuthenticationFailedContext.cs +++ b/src/Security/Authentication/Negotiate/src/Events/AuthenticationFailedContext.cs @@ -24,7 +24,7 @@ namespace Microsoft.AspNetCore.Authentication.Negotiate : base(context, scheme, options, properties: null) { } /// - /// The exception that occured while processing the authentication. + /// The exception that occurred while processing the authentication. /// public Exception Exception { get; set; } } diff --git a/src/Security/Authentication/Negotiate/src/Internal/ReflectedNegotiateState.cs b/src/Security/Authentication/Negotiate/src/Internal/ReflectedNegotiateState.cs index fb7a6a3a9f..0b827e9dc3 100644 --- a/src/Security/Authentication/Negotiate/src/Internal/ReflectedNegotiateState.cs +++ b/src/Security/Authentication/Negotiate/src/Internal/ReflectedNegotiateState.cs @@ -123,7 +123,7 @@ namespace Microsoft.AspNetCore.Authentication.Negotiate errorCode = SecurityStatusPalErrorCode.UnknownCredentials; } - error = new Exception($"An authentication exception occured (0x{majorStatus:X}/0x{minorStatus:X}).", error); + error = new Exception($"An authentication exception occurred (0x{majorStatus:X}/0x{minorStatus:X}).", error); } if (errorCode == SecurityStatusPalErrorCode.OK diff --git a/src/Security/Authentication/Negotiate/src/Microsoft.AspNetCore.Authentication.Negotiate.csproj b/src/Security/Authentication/Negotiate/src/Microsoft.AspNetCore.Authentication.Negotiate.csproj index cd7ef7ef55..390c449d9f 100644 --- a/src/Security/Authentication/Negotiate/src/Microsoft.AspNetCore.Authentication.Negotiate.csproj +++ b/src/Security/Authentication/Negotiate/src/Microsoft.AspNetCore.Authentication.Negotiate.csproj @@ -5,7 +5,7 @@ $(DefaultNetCoreTargetFramework) true aspnetcore;authentication;security - true + true diff --git a/src/Security/Authentication/Negotiate/src/NegotiateHandler.cs b/src/Security/Authentication/Negotiate/src/NegotiateHandler.cs index c880fb1564..9392d1028a 100644 --- a/src/Security/Authentication/Negotiate/src/NegotiateHandler.cs +++ b/src/Security/Authentication/Negotiate/src/NegotiateHandler.cs @@ -57,7 +57,7 @@ namespace Microsoft.AspNetCore.Authentication.Negotiate /// protected override Task CreateEventsAsync() => Task.FromResult(new NegotiateEvents()); - private bool IsHttp2 => string.Equals("HTTP/2", Request.Protocol, StringComparison.OrdinalIgnoreCase); + private bool IsSupportedProtocol => HttpProtocol.IsHttp11(Request.Protocol) || HttpProtocol.IsHttp10(Request.Protocol); /// /// Intercepts incomplete Negotiate authentication handshakes and continues or completes them. @@ -80,10 +80,10 @@ namespace Microsoft.AspNetCore.Authentication.Negotiate _requestProcessed = true; - if (IsHttp2) + if (!IsSupportedProtocol) { - // HTTP/2 is not supported. Do not throw because this may be running on a server that supports - // both HTTP/1 and HTTP/2. + // HTTP/1.0 and HTTP/1.1 are supported. Do not throw because this may be running on a server that supports + // additional protocols. return false; } @@ -291,7 +291,7 @@ namespace Microsoft.AspNetCore.Authentication.Negotiate throw new InvalidOperationException("AuthenticateAsync must not be called before the UseAuthentication middleware runs."); } - if (IsHttp2) + if (!IsSupportedProtocol) { // Not supported. We don't throw because Negotiate may be set as the default auth // handler on a server that's running HTTP/1 and HTTP/2. We'll challenge HTTP/2 requests diff --git a/src/Security/Authentication/Negotiate/test/Negotiate.FunctionalTest/CrossMachineReadMe.md b/src/Security/Authentication/Negotiate/test/Negotiate.FunctionalTest/CrossMachineReadMe.md index e263a2c5f7..ec83949331 100644 --- a/src/Security/Authentication/Negotiate/test/Negotiate.FunctionalTest/CrossMachineReadMe.md +++ b/src/Security/Authentication/Negotiate/test/Negotiate.FunctionalTest/CrossMachineReadMe.md @@ -1,24 +1,24 @@ Cross Machine Tests -Kerberos can only be tested in a multi-machine environment. On localhost it always falls back to NTLM which has different requirements. Multi-machine is also neccisary for interop testing across OSs. Kerberos also requires domain controler SPN configuration so we can't test it on arbitrary test boxes. +Kerberos can only be tested in a multi-machine environment. On localhost it always falls back to NTLM which has different requirements. Multi-machine is also necessary for interop testing across OSs. Kerberos also requires domain controller SPN configuration so we can't test it on arbitrary test boxes. Test structure: - A remote test server with various endpoints with different authentication restrictions. -- A remote test client with endpoints that execute specific scenarios. The input for these endpoints is theory data. The output is either 200Ok, or a failure code and desciption. +- A remote test client with endpoints that execute specific scenarios. The input for these endpoints is theory data. The output is either 200Ok, or a failure code and description. - The CrossMachineTest class that drives the tests. It invokes the client app with the theory data and confirms the results. -We use these three components beceause it allows us to run the tests from a dev machine or CI agent that is not part of the dedicated test domain/environment. +We use these three components because it allows us to run the tests from a dev machine or CI agent that is not part of the dedicated test domain/environment. (Static) Environment Setup: - Warning, this environment can take a day to set up. That's why we want a static test environment that we can re-use. - Create a Windows server running DNS and Active Directory. Promote it to a domain controller. - Create an SPN on this machine for Windows -> Windows testing. `setspn -S "http/chrross-dc.crkerberos.com" -U administrator` - Future: Can we replace the domain controller with an AAD instance? We'd still want a second windows machine for Windows -> Windows testing, but AAD might be easier to configure. - - https://docs.microsoft.com/en-us/azure/active-directory-domain-services/active-directory-ds-getting-started - - https://docs.microsoft.com/en-us/azure/active-directory-domain-services/active-directory-ds-join-ubuntu-linux-vm - - https://docs.microsoft.com/en-us/azure/active-directory-domain-services/active-directory-ds-enable-kcd + - https://docs.microsoft.com/azure/active-directory-domain-services/active-directory-ds-getting-started + - https://docs.microsoft.com/azure/active-directory-domain-services/active-directory-ds-join-ubuntu-linux-vm + - https://docs.microsoft.com/azure/active-directory-domain-services/active-directory-ds-enable-kcd - Create another Windows machine and join it to the test domain. -- Create a Linux machine and joing it to the domain. Ubuntu 18.04 has been used in the past. +- Create a Linux machine and joining it to the domain. Ubuntu 18.04 has been used in the past. - https://www.safesquid.com/content-filtering/integrating-linux-host-windows-ad-kerberos-sso-authentication - Include an HTTP SPN diff --git a/src/Security/Authentication/Negotiate/test/Negotiate.Test/EventTests.cs b/src/Security/Authentication/Negotiate/test/Negotiate.Test/EventTests.cs index 6c3baef320..bc18f861b5 100644 --- a/src/Security/Authentication/Negotiate/test/Negotiate.Test/EventTests.cs +++ b/src/Security/Authentication/Negotiate/test/Negotiate.Test/EventTests.cs @@ -13,6 +13,7 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.TestHost; +using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Net.Http.Headers; @@ -20,6 +21,7 @@ using Xunit; namespace Microsoft.AspNetCore.Authentication.Negotiate { + [QuarantinedTest] public class EventTests { [Fact] @@ -131,7 +133,7 @@ namespace Microsoft.AspNetCore.Authentication.Negotiate { eventInvoked++; Assert.IsType(context.Exception); - Assert.Equal("A test other error occured", context.Exception.Message); + Assert.Equal("A test other error occurred", context.Exception.Message); return Task.CompletedTask; } }; @@ -140,7 +142,7 @@ namespace Microsoft.AspNetCore.Authentication.Negotiate var ex = await Assert.ThrowsAsync(() => SendAsync(server, "/404", new TestConnection(), "Negotiate OtherError")); - Assert.Equal("A test other error occured", ex.Message); + Assert.Equal("A test other error occurred", ex.Message); Assert.Equal(1, eventInvoked); } @@ -182,7 +184,7 @@ namespace Microsoft.AspNetCore.Authentication.Negotiate { eventInvoked++; Assert.IsType(context.Exception); - Assert.Equal("A test credential error occured", context.Exception.Message); + Assert.Equal("A test credential error occurred", context.Exception.Message); return Task.CompletedTask; } }; @@ -232,7 +234,7 @@ namespace Microsoft.AspNetCore.Authentication.Negotiate { eventInvoked++; Assert.IsType(context.Exception); - Assert.Equal("A test client error occured", context.Exception.Message); + Assert.Equal("A test client error occurred", context.Exception.Message); return Task.CompletedTask; } }; @@ -555,15 +557,15 @@ namespace Microsoft.AspNetCore.Authentication.Negotiate return "ServerKerberosBlob2"; case "CredentialError": errorType = BlobErrorType.CredentialError; - ex = new Exception("A test credential error occured"); + ex = new Exception("A test credential error occurred"); return null; case "ClientError": errorType = BlobErrorType.ClientError; - ex = new Exception("A test client error occured"); + ex = new Exception("A test client error occurred"); return null; case "OtherError": errorType = BlobErrorType.Other; - ex = new Exception("A test other error occured"); + ex = new Exception("A test other error occurred"); return null; default: errorType = BlobErrorType.Other; diff --git a/src/Security/Authentication/Negotiate/test/Negotiate.Test/Microsoft.AspNetCore.Authentication.Negotiate.Test.csproj b/src/Security/Authentication/Negotiate/test/Negotiate.Test/Microsoft.AspNetCore.Authentication.Negotiate.Test.csproj index 7af38dfa4a..3f483fc9e1 100644 --- a/src/Security/Authentication/Negotiate/test/Negotiate.Test/Microsoft.AspNetCore.Authentication.Negotiate.Test.csproj +++ b/src/Security/Authentication/Negotiate/test/Negotiate.Test/Microsoft.AspNetCore.Authentication.Negotiate.Test.csproj @@ -12,6 +12,7 @@ + diff --git a/src/Security/Authentication/Negotiate/test/Negotiate.Test/NegotiateHandlerTests.cs b/src/Security/Authentication/Negotiate/test/Negotiate.Test/NegotiateHandlerTests.cs index d696cd0afd..89685b286b 100644 --- a/src/Security/Authentication/Negotiate/test/Negotiate.Test/NegotiateHandlerTests.cs +++ b/src/Security/Authentication/Negotiate/test/Negotiate.Test/NegotiateHandlerTests.cs @@ -13,6 +13,7 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.TestHost; +using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Net.Http.Headers; @@ -21,6 +22,7 @@ using Xunit.Sdk; namespace Microsoft.AspNetCore.Authentication.Negotiate { + [QuarantinedTest] public class NegotiateHandlerTests { [Fact] @@ -301,7 +303,7 @@ namespace Microsoft.AspNetCore.Authentication.Negotiate var testConnection = new TestConnection(); var ex = await Assert.ThrowsAsync(() => SendAsync(server, "/404", testConnection, "Negotiate OtherError")); - Assert.Equal("A test other error occured", ex.Message); + Assert.Equal("A test other error occurred", ex.Message); } // Single Stage @@ -552,15 +554,15 @@ namespace Microsoft.AspNetCore.Authentication.Negotiate return "ServerKerberosBlob2"; case "CredentialError": errorType = BlobErrorType.CredentialError; - ex = new Exception("A test credential error occured"); + ex = new Exception("A test credential error occurred"); return null; case "ClientError": errorType = BlobErrorType.ClientError; - ex = new Exception("A test client error occured"); + ex = new Exception("A test client error occurred"); return null; case "OtherError": errorType = BlobErrorType.Other; - ex = new Exception("A test other error occured"); + ex = new Exception("A test other error occurred"); return null; default: errorType = BlobErrorType.Other; diff --git a/src/Security/Authentication/Negotiate/test/Negotiate.Test/ServerDeferralTests.cs b/src/Security/Authentication/Negotiate/test/Negotiate.Test/ServerDeferralTests.cs index eba5c12fea..e5cf8ca7b6 100644 --- a/src/Security/Authentication/Negotiate/test/Negotiate.Test/ServerDeferralTests.cs +++ b/src/Security/Authentication/Negotiate/test/Negotiate.Test/ServerDeferralTests.cs @@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.TestHost; +using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Options; @@ -14,6 +15,7 @@ using Xunit; namespace Microsoft.AspNetCore.Authentication.Negotiate { + [QuarantinedTest] public class ServerDeferralTests { [Fact] diff --git a/src/Security/Authentication/OAuth/ref/Microsoft.AspNetCore.Authentication.OAuth.netcoreapp.cs b/src/Security/Authentication/OAuth/ref/Microsoft.AspNetCore.Authentication.OAuth.netcoreapp.cs index 73d5e441da..2301daf1ad 100644 --- a/src/Security/Authentication/OAuth/ref/Microsoft.AspNetCore.Authentication.OAuth.netcoreapp.cs +++ b/src/Security/Authentication/OAuth/ref/Microsoft.AspNetCore.Authentication.OAuth.netcoreapp.cs @@ -31,9 +31,9 @@ namespace Microsoft.AspNetCore.Authentication.OAuth public partial class OAuthCodeExchangeContext { public OAuthCodeExchangeContext(Microsoft.AspNetCore.Authentication.AuthenticationProperties properties, string code, string redirectUri) { } - public string Code { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public Microsoft.AspNetCore.Authentication.AuthenticationProperties Properties { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public string RedirectUri { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public string Code { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public Microsoft.AspNetCore.Authentication.AuthenticationProperties Properties { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public string RedirectUri { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } } public static partial class OAuthConstants { @@ -46,13 +46,13 @@ namespace Microsoft.AspNetCore.Authentication.OAuth { public OAuthCreatingTicketContext(System.Security.Claims.ClaimsPrincipal principal, Microsoft.AspNetCore.Authentication.AuthenticationProperties properties, Microsoft.AspNetCore.Http.HttpContext context, Microsoft.AspNetCore.Authentication.AuthenticationScheme scheme, Microsoft.AspNetCore.Authentication.OAuth.OAuthOptions options, System.Net.Http.HttpClient backchannel, Microsoft.AspNetCore.Authentication.OAuth.OAuthTokenResponse tokens, System.Text.Json.JsonElement user) : base (default(Microsoft.AspNetCore.Http.HttpContext), default(Microsoft.AspNetCore.Authentication.AuthenticationScheme), default(Microsoft.AspNetCore.Authentication.OAuth.OAuthOptions)) { } public string AccessToken { get { throw null; } } - public System.Net.Http.HttpClient Backchannel { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public System.Net.Http.HttpClient Backchannel { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } public System.TimeSpan? ExpiresIn { get { throw null; } } public System.Security.Claims.ClaimsIdentity Identity { get { throw null; } } public string RefreshToken { get { throw null; } } - public Microsoft.AspNetCore.Authentication.OAuth.OAuthTokenResponse TokenResponse { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public Microsoft.AspNetCore.Authentication.OAuth.OAuthTokenResponse TokenResponse { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } public string TokenType { get { throw null; } } - public System.Text.Json.JsonElement User { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public System.Text.Json.JsonElement User { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } public void RunClaimActions() { } public void RunClaimActions(System.Text.Json.JsonElement userData) { } } @@ -63,8 +63,8 @@ namespace Microsoft.AspNetCore.Authentication.OAuth public partial class OAuthEvents : Microsoft.AspNetCore.Authentication.RemoteAuthenticationEvents { public OAuthEvents() { } - public System.Func OnCreatingTicket { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public System.Func, System.Threading.Tasks.Task> OnRedirectToAuthorizationEndpoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public System.Func OnCreatingTicket { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public System.Func, System.Threading.Tasks.Task> OnRedirectToAuthorizationEndpoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public virtual System.Threading.Tasks.Task CreatingTicket(Microsoft.AspNetCore.Authentication.OAuth.OAuthCreatingTicketContext context) { throw null; } public virtual System.Threading.Tasks.Task RedirectToAuthorizationEndpoint(Microsoft.AspNetCore.Authentication.RedirectContext context) { throw null; } } @@ -89,27 +89,27 @@ namespace Microsoft.AspNetCore.Authentication.OAuth public partial class OAuthOptions : Microsoft.AspNetCore.Authentication.RemoteAuthenticationOptions { public OAuthOptions() { } - public string AuthorizationEndpoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public Microsoft.AspNetCore.Authentication.OAuth.Claims.ClaimActionCollection ClaimActions { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public string ClientId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public string ClientSecret { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public string AuthorizationEndpoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public Microsoft.AspNetCore.Authentication.OAuth.Claims.ClaimActionCollection ClaimActions { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public string ClientId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public string ClientSecret { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public new Microsoft.AspNetCore.Authentication.OAuth.OAuthEvents Events { get { throw null; } set { } } - public System.Collections.Generic.ICollection Scope { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public Microsoft.AspNetCore.Authentication.ISecureDataFormat StateDataFormat { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public string TokenEndpoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public bool UsePkce { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public string UserInformationEndpoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public System.Collections.Generic.ICollection Scope { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public Microsoft.AspNetCore.Authentication.ISecureDataFormat StateDataFormat { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public string TokenEndpoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public bool UsePkce { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public string UserInformationEndpoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public override void Validate() { } } public partial class OAuthTokenResponse : System.IDisposable { internal OAuthTokenResponse() { } - public string AccessToken { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public System.Exception Error { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public string ExpiresIn { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public string RefreshToken { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public System.Text.Json.JsonDocument Response { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public string TokenType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public string AccessToken { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public System.Exception Error { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public string ExpiresIn { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public string RefreshToken { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public System.Text.Json.JsonDocument Response { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public string TokenType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public void Dispose() { } public static Microsoft.AspNetCore.Authentication.OAuth.OAuthTokenResponse Failed(System.Exception error) { throw null; } public static Microsoft.AspNetCore.Authentication.OAuth.OAuthTokenResponse Success(System.Text.Json.JsonDocument response) { throw null; } @@ -120,8 +120,8 @@ namespace Microsoft.AspNetCore.Authentication.OAuth.Claims public abstract partial class ClaimAction { public ClaimAction(string claimType, string valueType) { } - public string ClaimType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public string ValueType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public string ClaimType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public string ValueType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } public abstract void Run(System.Text.Json.JsonElement userData, System.Security.Claims.ClaimsIdentity identity, string issuer); } public partial class ClaimActionCollection : System.Collections.Generic.IEnumerable, System.Collections.IEnumerable @@ -136,7 +136,7 @@ namespace Microsoft.AspNetCore.Authentication.OAuth.Claims public partial class CustomJsonClaimAction : Microsoft.AspNetCore.Authentication.OAuth.Claims.ClaimAction { public CustomJsonClaimAction(string claimType, string valueType, System.Func resolver) : base (default(string), default(string)) { } - public System.Func Resolver { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public System.Func Resolver { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } public override void Run(System.Text.Json.JsonElement userData, System.Security.Claims.ClaimsIdentity identity, string issuer) { } } public partial class DeleteClaimAction : Microsoft.AspNetCore.Authentication.OAuth.Claims.ClaimAction @@ -147,13 +147,13 @@ namespace Microsoft.AspNetCore.Authentication.OAuth.Claims public partial class JsonKeyClaimAction : Microsoft.AspNetCore.Authentication.OAuth.Claims.ClaimAction { public JsonKeyClaimAction(string claimType, string valueType, string jsonKey) : base (default(string), default(string)) { } - public string JsonKey { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public string JsonKey { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } public override void Run(System.Text.Json.JsonElement userData, System.Security.Claims.ClaimsIdentity identity, string issuer) { } } public partial class JsonSubKeyClaimAction : Microsoft.AspNetCore.Authentication.OAuth.Claims.JsonKeyClaimAction { public JsonSubKeyClaimAction(string claimType, string valueType, string jsonKey, string subKey) : base (default(string), default(string), default(string)) { } - public string SubKey { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public string SubKey { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } public override void Run(System.Text.Json.JsonElement userData, System.Security.Claims.ClaimsIdentity identity, string issuer) { } } public partial class MapAllClaimsAction : Microsoft.AspNetCore.Authentication.OAuth.Claims.ClaimAction diff --git a/src/Security/Authentication/OAuth/src/OAuthHandler.cs b/src/Security/Authentication/OAuth/src/OAuthHandler.cs index a6bfbbc550..29ef3036f8 100644 --- a/src/Security/Authentication/OAuth/src/OAuthHandler.cs +++ b/src/Security/Authentication/OAuth/src/OAuthHandler.cs @@ -22,7 +22,6 @@ namespace Microsoft.AspNetCore.Authentication.OAuth { public class OAuthHandler : RemoteAuthenticationHandler where TOptions : OAuthOptions, new() { - private static readonly RandomNumberGenerator CryptoRandom = RandomNumberGenerator.Create(); protected HttpClient Backchannel => Options.Backchannel; /// @@ -274,7 +273,7 @@ namespace Microsoft.AspNetCore.Authentication.OAuth if (Options.UsePkce) { var bytes = new byte[32]; - CryptoRandom.GetBytes(bytes); + RandomNumberGenerator.Fill(bytes); var codeVerifier = Base64UrlTextEncoder.Encode(bytes); // Store this for use during the code redemption. diff --git a/src/Security/Authentication/OpenIdConnect/samples/OpenIdConnect.AzureAdSample/Startup.cs b/src/Security/Authentication/OpenIdConnect/samples/OpenIdConnect.AzureAdSample/Startup.cs index f182865949..78e9863f23 100644 --- a/src/Security/Authentication/OpenIdConnect/samples/OpenIdConnect.AzureAdSample/Startup.cs +++ b/src/Security/Authentication/OpenIdConnect/samples/OpenIdConnect.AzureAdSample/Startup.cs @@ -148,7 +148,7 @@ namespace OpenIdConnect.AzureAdSample } catch (Exception ex) { - await response.WriteAsync($"AquireToken error: {ex.Message}"); + await response.WriteAsync($"AcquireToken error: {ex.Message}"); } }); } diff --git a/src/Security/Authentication/OpenIdConnect/samples/OpenIdConnectSample/Startup.cs b/src/Security/Authentication/OpenIdConnect/samples/OpenIdConnectSample/Startup.cs index 5d4a94ec56..c0696ba1a2 100644 --- a/src/Security/Authentication/OpenIdConnect/samples/OpenIdConnectSample/Startup.cs +++ b/src/Security/Authentication/OpenIdConnect/samples/OpenIdConnectSample/Startup.cs @@ -37,18 +37,56 @@ namespace OpenIdConnectSample { if (options.SameSite == SameSiteMode.None) { - var userAgent = httpContext.Request.Headers["User-Agent"]; - // TODO: Use your User Agent library of choice here. - if (userAgent.Contains("CPU iPhone OS 12") // Also covers iPod touch - || userAgent.Contains("iPad; CPU OS 12") - // Safari 12 and 13 are both broken on Mojave - || userAgent.Contains("Macintosh; Intel Mac OS X 10_14")) + var userAgent = httpContext.Request.Headers["User-Agent"].ToString(); + + if (DisallowsSameSiteNone(userAgent)) { options.SameSite = SameSiteMode.Unspecified; } } } + // TODO: Use your User Agent library of choice here. + public static bool DisallowsSameSiteNone(string userAgent) + { + if (string.IsNullOrEmpty(userAgent)) + { + return false; + } + + // Cover all iOS based browsers here. This includes: + // - Safari on iOS 12 for iPhone, iPod Touch, iPad + // - WkWebview on iOS 12 for iPhone, iPod Touch, iPad + // - Chrome on iOS 12 for iPhone, iPod Touch, iPad + // All of which are broken by SameSite=None, because they use the iOS networking stack + if (userAgent.Contains("CPU iPhone OS 12") || userAgent.Contains("iPad; CPU OS 12")) + { + return true; + } + + // Cover Mac OS X based browsers that use the Mac OS networking stack. This includes: + // - Safari on Mac OS X. + // This does not include: + // - Chrome on Mac OS X + // Because they do not use the Mac OS networking stack. + if (userAgent.Contains("Macintosh; Intel Mac OS X 10_14") && + userAgent.Contains("Version/") && userAgent.Contains("Safari")) + { + return true; + } + + // Cover Chrome 50-69, because some versions are broken by SameSite=None, + // and none in this range require it. + // Note: this covers some pre-Chromium Edge versions, + // but pre-Chromium Edge does not require SameSite=None. + if (userAgent.Contains("Chrome/5") || userAgent.Contains("Chrome/6")) + { + return true; + } + + return false; + } + public void ConfigureServices(IServiceCollection services) { JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); diff --git a/src/Security/Authentication/OpenIdConnect/src/Microsoft.AspNetCore.Authentication.OpenIdConnect.csproj b/src/Security/Authentication/OpenIdConnect/src/Microsoft.AspNetCore.Authentication.OpenIdConnect.csproj index 79b6de0fbf..8a6c82928d 100644 --- a/src/Security/Authentication/OpenIdConnect/src/Microsoft.AspNetCore.Authentication.OpenIdConnect.csproj +++ b/src/Security/Authentication/OpenIdConnect/src/Microsoft.AspNetCore.Authentication.OpenIdConnect.csproj @@ -6,7 +6,7 @@ $(NoWarn);CS1591 true aspnetcore;authentication;security - true + true diff --git a/src/Security/Authentication/OpenIdConnect/src/OpenIdConnectHandler.cs b/src/Security/Authentication/OpenIdConnect/src/OpenIdConnectHandler.cs index 65ad366a50..f4767f1e87 100644 --- a/src/Security/Authentication/OpenIdConnect/src/OpenIdConnectHandler.cs +++ b/src/Security/Authentication/OpenIdConnect/src/OpenIdConnectHandler.cs @@ -34,8 +34,6 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect private const string NonceProperty = "N"; private const string HeaderValueEpocDate = "Thu, 01 Jan 1970 00:00:00 GMT"; - private static readonly RandomNumberGenerator CryptoRandom = RandomNumberGenerator.Create(); - private OpenIdConnectConfiguration _configuration; protected HttpClient Backchannel => Options.Backchannel; @@ -78,13 +76,13 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect { OpenIdConnectMessage message = null; - if (string.Equals(Request.Method, "GET", StringComparison.OrdinalIgnoreCase)) + if (HttpMethods.IsGet(Request.Method)) { message = new OpenIdConnectMessage(Request.Query.Select(pair => new KeyValuePair(pair.Key, pair.Value))); } // assumption: if the ContentType is "application/x-www-form-urlencoded" it should be safe to read as it is small. - else if (string.Equals(Request.Method, "POST", StringComparison.OrdinalIgnoreCase) + else if (HttpMethods.IsPost(Request.Method) && !string.IsNullOrEmpty(Request.ContentType) // May have media/type; charset=utf-8, allow partial match. && Request.ContentType.StartsWith("application/x-www-form-urlencoded", StringComparison.OrdinalIgnoreCase) @@ -371,7 +369,7 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect if (Options.UsePkce && Options.ResponseType == OpenIdConnectResponseType.Code) { var bytes = new byte[32]; - CryptoRandom.GetBytes(bytes); + RandomNumberGenerator.Fill(bytes); var codeVerifier = Base64UrlTextEncoder.Encode(bytes); // Store this for use during the code redemption. See RunAuthorizationCodeReceivedEventAsync. @@ -482,7 +480,7 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect OpenIdConnectMessage authorizationResponse = null; - if (string.Equals(Request.Method, "GET", StringComparison.OrdinalIgnoreCase)) + if (HttpMethods.IsGet(Request.Method)) { authorizationResponse = new OpenIdConnectMessage(Request.Query.Select(pair => new KeyValuePair(pair.Key, pair.Value))); @@ -501,7 +499,7 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect } } // assumption: if the ContentType is "application/x-www-form-urlencoded" it should be safe to read as it is small. - else if (string.Equals(Request.Method, "POST", StringComparison.OrdinalIgnoreCase) + else if (HttpMethods.IsPost(Request.Method) && !string.IsNullOrEmpty(Request.ContentType) // May have media/type; charset=utf-8, allow partial match. && Request.ContentType.StartsWith("application/x-www-form-urlencoded", StringComparison.OrdinalIgnoreCase) @@ -912,7 +910,7 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect foreach (var action in Options.ClaimActions) { - action.Run(user.RootElement, identity, ClaimsIssuer); + action.Run(updatedUser.RootElement, identity, ClaimsIssuer); } } } diff --git a/src/Security/Authentication/OpenIdConnect/src/OpenIdConnectOptions.cs b/src/Security/Authentication/OpenIdConnect/src/OpenIdConnectOptions.cs index 7e3366bada..1b86c81901 100644 --- a/src/Security/Authentication/OpenIdConnect/src/OpenIdConnectOptions.cs +++ b/src/Security/Authentication/OpenIdConnect/src/OpenIdConnectOptions.cs @@ -288,7 +288,7 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect /// cookie gets added to the response. /// /// - /// The value of is treated as the prefix to the cookie name, and defaults to . + /// The value of is treated as the prefix to the cookie name, and defaults to . /// public CookieBuilder NonceCookie { @@ -327,5 +327,15 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect return cookieOptions; } } + + /// + /// 1 day is the default time interval that afterwards, will obtain new configuration. + /// + public TimeSpan AutomaticRefreshInterval { get; set; } = ConfigurationManager.DefaultAutomaticRefreshInterval; + + /// + /// The minimum time between retrievals, in the event that a retrieval failed, or that a refresh was explicitly requested. 30 seconds is the default. + /// + public TimeSpan RefreshInterval { get; set; } = ConfigurationManager.DefaultRefreshInterval; } } diff --git a/src/Security/Authentication/OpenIdConnect/src/OpenIdConnectPostConfigureOptions.cs b/src/Security/Authentication/OpenIdConnect/src/OpenIdConnectPostConfigureOptions.cs index b79f1d1edf..f1a39d7081 100644 --- a/src/Security/Authentication/OpenIdConnect/src/OpenIdConnectPostConfigureOptions.cs +++ b/src/Security/Authentication/OpenIdConnect/src/OpenIdConnectPostConfigureOptions.cs @@ -93,7 +93,11 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect } options.ConfigurationManager = new ConfigurationManager(options.MetadataAddress, new OpenIdConnectConfigurationRetriever(), - new HttpDocumentRetriever(options.Backchannel) { RequireHttps = options.RequireHttpsMetadata }); + new HttpDocumentRetriever(options.Backchannel) { RequireHttps = options.RequireHttpsMetadata }) + { + RefreshInterval = options.RefreshInterval, + AutomaticRefreshInterval = options.AutomaticRefreshInterval, + }; } } } diff --git a/src/Security/Authentication/Twitter/src/Microsoft.AspNetCore.Authentication.Twitter.csproj b/src/Security/Authentication/Twitter/src/Microsoft.AspNetCore.Authentication.Twitter.csproj index 51f01cbd3f..c59a0fb276 100644 --- a/src/Security/Authentication/Twitter/src/Microsoft.AspNetCore.Authentication.Twitter.csproj +++ b/src/Security/Authentication/Twitter/src/Microsoft.AspNetCore.Authentication.Twitter.csproj @@ -6,7 +6,7 @@ $(NoWarn);CS1591 true aspnetcore;authentication;security - true + true diff --git a/src/Security/Authentication/Twitter/src/TwitterHandler.cs b/src/Security/Authentication/Twitter/src/TwitterHandler.cs index e963d7a27e..5f09e608b9 100644 --- a/src/Security/Authentication/Twitter/src/TwitterHandler.cs +++ b/src/Security/Authentication/Twitter/src/TwitterHandler.cs @@ -21,8 +21,6 @@ namespace Microsoft.AspNetCore.Authentication.Twitter { public class TwitterHandler : RemoteAuthenticationHandler { - private static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); - private HttpClient Backchannel => Options.Backchannel; /// @@ -301,9 +299,9 @@ namespace Microsoft.AspNetCore.Authentication.Twitter return result; } - private static string GenerateTimeStamp() + private string GenerateTimeStamp() { - var secondsSinceUnixEpocStart = DateTime.UtcNow - Epoch; + var secondsSinceUnixEpocStart = Clock.UtcNow - DateTimeOffset.UnixEpoch; return Convert.ToInt64(secondsSinceUnixEpocStart.TotalSeconds).ToString(CultureInfo.InvariantCulture); } diff --git a/src/Security/Authentication/WsFederation/src/Microsoft.AspNetCore.Authentication.WsFederation.csproj b/src/Security/Authentication/WsFederation/src/Microsoft.AspNetCore.Authentication.WsFederation.csproj index 79ea913ae1..7d1a23fabd 100644 --- a/src/Security/Authentication/WsFederation/src/Microsoft.AspNetCore.Authentication.WsFederation.csproj +++ b/src/Security/Authentication/WsFederation/src/Microsoft.AspNetCore.Authentication.WsFederation.csproj @@ -5,7 +5,7 @@ $(DefaultNetCoreTargetFramework) true aspnetcore;authentication;security - true + true diff --git a/src/Security/Authentication/WsFederation/src/WsFederationHandler.cs b/src/Security/Authentication/WsFederation/src/WsFederationHandler.cs index 27bb332ccf..41b9509f57 100644 --- a/src/Security/Authentication/WsFederation/src/WsFederationHandler.cs +++ b/src/Security/Authentication/WsFederation/src/WsFederationHandler.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; @@ -54,7 +54,7 @@ namespace Microsoft.AspNetCore.Authentication.WsFederation /// /// Overridden to handle remote signout requests /// - /// true if request processing should stop. + /// if request processing should stop. public override Task HandleRequestAsync() { // RemoteSignOutPath and CallbackPath may be the same, fall through if the message doesn't match. diff --git a/src/Security/Authentication/test/CertificateTests.cs b/src/Security/Authentication/test/CertificateTests.cs index fc4d189a1f..bd395c33ff 100644 --- a/src/Security/Authentication/test/CertificateTests.cs +++ b/src/Security/Authentication/test/CertificateTests.cs @@ -48,7 +48,7 @@ namespace Microsoft.AspNetCore.Authentication.Certificate.Test new CertificateAuthenticationOptions { AllowedCertificateTypes = CertificateTypes.SelfSigned, - Events = sucessfulValidationEvents + Events = successfulValidationEvents }, Certificates.SelfSignedValidWithClientEku); @@ -63,7 +63,7 @@ namespace Microsoft.AspNetCore.Authentication.Certificate.Test new CertificateAuthenticationOptions { AllowedCertificateTypes = CertificateTypes.SelfSigned, - Events = sucessfulValidationEvents + Events = successfulValidationEvents }, Certificates.SelfSignedValidWithNoEku); @@ -92,7 +92,7 @@ namespace Microsoft.AspNetCore.Authentication.Certificate.Test new CertificateAuthenticationOptions { AllowedCertificateTypes = CertificateTypes.Chained, - Events = sucessfulValidationEvents + Events = successfulValidationEvents }, Certificates.SelfSignedValidWithNoEku); @@ -107,7 +107,7 @@ namespace Microsoft.AspNetCore.Authentication.Certificate.Test new CertificateAuthenticationOptions { AllowedCertificateTypes = CertificateTypes.SelfSigned, - Events = sucessfulValidationEvents + Events = successfulValidationEvents }, Certificates.SelfSignedValidWithServerEku); @@ -123,7 +123,7 @@ namespace Microsoft.AspNetCore.Authentication.Certificate.Test { AllowedCertificateTypes = CertificateTypes.SelfSigned, ValidateCertificateUse = false, - Events = sucessfulValidationEvents + Events = successfulValidationEvents }, Certificates.SelfSignedValidWithServerEku); @@ -139,7 +139,7 @@ namespace Microsoft.AspNetCore.Authentication.Certificate.Test { AllowedCertificateTypes = CertificateTypes.Chained, ValidateCertificateUse = false, - Events = sucessfulValidationEvents + Events = successfulValidationEvents }, Certificates.SelfSignedValidWithServerEku); @@ -155,7 +155,7 @@ namespace Microsoft.AspNetCore.Authentication.Certificate.Test { AllowedCertificateTypes = CertificateTypes.SelfSigned, ValidateCertificateUse = false, - Events = sucessfulValidationEvents + Events = successfulValidationEvents }, Certificates.SelfSignedExpired); @@ -171,7 +171,7 @@ namespace Microsoft.AspNetCore.Authentication.Certificate.Test { AllowedCertificateTypes = CertificateTypes.SelfSigned, ValidateValidityPeriod = false, - Events = sucessfulValidationEvents + Events = successfulValidationEvents }, Certificates.SelfSignedExpired); @@ -187,7 +187,7 @@ namespace Microsoft.AspNetCore.Authentication.Certificate.Test { AllowedCertificateTypes = CertificateTypes.SelfSigned, ValidateCertificateUse = false, - Events = sucessfulValidationEvents + Events = successfulValidationEvents }, Certificates.SelfSignedNotYetValid); @@ -203,7 +203,7 @@ namespace Microsoft.AspNetCore.Authentication.Certificate.Test { AllowedCertificateTypes = CertificateTypes.SelfSigned, ValidateValidityPeriod = false, - Events = sucessfulValidationEvents + Events = successfulValidationEvents }, Certificates.SelfSignedNotYetValid); @@ -248,13 +248,58 @@ namespace Microsoft.AspNetCore.Authentication.Certificate.Test var server = CreateServer( new CertificateAuthenticationOptions { - Events = sucessfulValidationEvents + Events = successfulValidationEvents }); var response = await server.CreateClient().GetAsync("https://example.com/"); Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode); } + [Fact] + public async Task VerifyUntrustedClientCertEndsUpInForbidden() + { + var server = CreateServer( + new CertificateAuthenticationOptions + { + Events = successfulValidationEvents + }, Certificates.SignedClient); + + var response = await server.CreateClient().GetAsync("https://example.com/"); + Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode); + } + + [Fact] + public async Task VerifyClientCertWithUntrustedRootAndTrustedChainEndsUpInForbidden() + { + var server = CreateServer( + new CertificateAuthenticationOptions + { + Events = successfulValidationEvents, + CustomTrustStore = new X509Certificate2Collection() { Certificates.SignedSecondaryRoot }, + ChainTrustValidationMode = X509ChainTrustMode.CustomRootTrust, + RevocationMode = X509RevocationMode.NoCheck + }, Certificates.SignedClient); + + var response = await server.CreateClient().GetAsync("https://example.com/"); + Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode); + } + + [Fact] + public async Task VerifyValidClientCertWithTrustedChainAuthenticates() + { + var server = CreateServer( + new CertificateAuthenticationOptions + { + Events = successfulValidationEvents, + CustomTrustStore = new X509Certificate2Collection() { Certificates.SelfSignedPrimaryRoot, Certificates.SignedSecondaryRoot }, + ChainTrustValidationMode = X509ChainTrustMode.CustomRootTrust, + RevocationMode = X509RevocationMode.NoCheck + }, Certificates.SignedClient); + + var response = await server.CreateClient().GetAsync("https://example.com/"); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + } + [Fact] public async Task VerifyHeaderIsUsedIfCertIsNotPresent() { @@ -262,9 +307,9 @@ namespace Microsoft.AspNetCore.Authentication.Certificate.Test new CertificateAuthenticationOptions { AllowedCertificateTypes = CertificateTypes.SelfSigned, - Events = sucessfulValidationEvents + Events = successfulValidationEvents }, - wireUpHeaderMiddleware : true); + wireUpHeaderMiddleware: true); var client = server.CreateClient(); client.DefaultRequestHeaders.Add("X-Client-Cert", Convert.ToBase64String(Certificates.SelfSignedValidWithNoEku.RawData)); @@ -278,7 +323,7 @@ namespace Microsoft.AspNetCore.Authentication.Certificate.Test var server = CreateServer( new CertificateAuthenticationOptions { - Events = sucessfulValidationEvents + Events = successfulValidationEvents }, wireUpHeaderMiddleware: true); @@ -295,7 +340,7 @@ namespace Microsoft.AspNetCore.Authentication.Certificate.Test new CertificateAuthenticationOptions { AllowedCertificateTypes = CertificateTypes.SelfSigned, - Events = sucessfulValidationEvents + Events = successfulValidationEvents }, wireUpHeaderMiddleware: true, headerName: "X-ARR-ClientCert"); @@ -312,7 +357,7 @@ namespace Microsoft.AspNetCore.Authentication.Certificate.Test var server = CreateServer( new CertificateAuthenticationOptions { - Events = sucessfulValidationEvents + Events = successfulValidationEvents }, wireUpHeaderMiddleware: true, headerName: "X-ARR-ClientCert"); @@ -534,11 +579,13 @@ namespace Microsoft.AspNetCore.Authentication.Certificate.Test { services.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme).AddCertificate(options => { + options.CustomTrustStore = configureOptions.CustomTrustStore; + options.ChainTrustValidationMode = configureOptions.ChainTrustValidationMode; options.AllowedCertificateTypes = configureOptions.AllowedCertificateTypes; options.Events = configureOptions.Events; options.ValidateCertificateUse = configureOptions.ValidateCertificateUse; - options.RevocationFlag = options.RevocationFlag; - options.RevocationMode = options.RevocationMode; + options.RevocationFlag = configureOptions.RevocationFlag; + options.RevocationMode = configureOptions.RevocationMode; options.ValidateValidityPeriod = configureOptions.ValidateValidityPeriod; }); } @@ -564,7 +611,7 @@ namespace Microsoft.AspNetCore.Authentication.Certificate.Test return server; } - private CertificateAuthenticationEvents sucessfulValidationEvents = new CertificateAuthenticationEvents() + private CertificateAuthenticationEvents successfulValidationEvents = new CertificateAuthenticationEvents() { OnCertificateValidated = context => { @@ -599,6 +646,15 @@ namespace Microsoft.AspNetCore.Authentication.Certificate.Test private static class Certificates { + public static X509Certificate2 SelfSignedPrimaryRoot { get; private set; } = + new X509Certificate2(GetFullyQualifiedFilePath("validSelfSignedPrimaryRootCertificate.cer")); + + public static X509Certificate2 SignedSecondaryRoot { get; private set; } = + new X509Certificate2(GetFullyQualifiedFilePath("validSignedSecondaryRootCertificate.cer")); + + public static X509Certificate2 SignedClient { get; private set; } = + new X509Certificate2(GetFullyQualifiedFilePath("validSignedClientCertificate.cer")); + public static X509Certificate2 SelfSignedValidWithClientEku { get; private set; } = new X509Certificate2(GetFullyQualifiedFilePath("validSelfSignedClientEkuCertificate.cer")); @@ -626,3 +682,4 @@ namespace Microsoft.AspNetCore.Authentication.Certificate.Test } } } + diff --git a/src/Security/Authentication/test/SharedAuthenticationTests.cs b/src/Security/Authentication/test/SharedAuthenticationTests.cs index 36956f8374..834efeaedd 100644 --- a/src/Security/Authentication/test/SharedAuthenticationTests.cs +++ b/src/Security/Authentication/test/SharedAuthenticationTests.cs @@ -203,6 +203,46 @@ namespace Microsoft.AspNetCore.Authentication Assert.Equal(0, forwardDefault.SignOutCount); } + private class RunOnce : IClaimsTransformation + { + public int Ran = 0; + public Task TransformAsync(ClaimsPrincipal principal) + { + Ran++; + return Task.FromResult(new ClaimsPrincipal()); + } + } + + [Fact] + public async Task ForwardAuthenticateOnlyRunsTransformOnceByDefault() + { + var services = new ServiceCollection().AddLogging(); + var transform = new RunOnce(); + var builder = services.AddSingleton(transform).AddAuthentication(o => + { + o.DefaultScheme = DefaultScheme; + o.AddScheme("auth1", "auth1"); + o.AddScheme("specific", "specific"); + }); + RegisterAuth(builder, o => + { + o.ForwardDefault = "auth1"; + o.ForwardAuthenticate = "specific"; + }); + + var specific = new TestHandler(); + services.AddSingleton(specific); + var forwardDefault = new TestHandler2(); + services.AddSingleton(forwardDefault); + + var sp = services.BuildServiceProvider(); + var context = new DefaultHttpContext(); + context.RequestServices = sp; + + await context.AuthenticateAsync(); + Assert.Equal(1, transform.Ran); + } + [Fact] public async Task ForwardAuthenticateWinsOverDefault() { diff --git a/src/Security/Authentication/test/TestCertificates/validSelfSignedPrimaryRootCertificate.cer b/src/Security/Authentication/test/TestCertificates/validSelfSignedPrimaryRootCertificate.cer new file mode 100644 index 0000000000..a7420c8493 --- /dev/null +++ b/src/Security/Authentication/test/TestCertificates/validSelfSignedPrimaryRootCertificate.cer @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDPTCCAiWgAwIBAgIQTmWeCzG8SbRJ0y+osLWwDjANBgkqhkiG9w0BAQsFADAp +MScwJQYDVQQDDB5WYWxpZCBTZWxmIFNpZ25lZCBQcmltYXJ5IFJvb3QwHhcNMjAw +MTIxMDAwMDAwWhcNNDUwMTIxMDAwMDAwWjApMScwJQYDVQQDDB5WYWxpZCBTZWxm +IFNpZ25lZCBQcmltYXJ5IFJvb3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQDMK6v6HsJ19iRlXIRQVBJiy9xnJWBddLjm+2RRkyiiffEitBExiXVyrQ8L +DlegQQH3oJR0xgXwisJctjpHHz54dfbw5LwC9j9EVtu/UgDgK4lo6X3WLNYMJ1pX +xxjGfXcyzGGhcI0KATlyWhWgOrZNFzE+v0KY/LtZvcZ290Y4X7MQLge+V/09Lohx +pj6vsHkpoK8tD8ksJp+O8jk45TXTxs4yo8BRXbIv0oMmuZ9+gVkiaGurCCe/o+nw +vjEQre9oKNFI9KOgen6l1152BVQaXMDd22vemGIz738Scl9kcBQhy1D0dPuL6QV3 +rR8HoNG3i0cuYxB4xgFF5GY2fhQBAgMBAAGjYTBfMA4GA1UdDwEB/wQEAwIBhjAd +BgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDwYDVR0TAQH/BAUwAwEB/zAd +BgNVHQ4EFgQU/cAC4CkVEtEj2UCYMVS/mlFCdBAwDQYJKoZIhvcNAQELBQADggEB +AInTuEG8Kv2aWy7MJg/N/AvEmC7USMgTceFY+bKhVogYCE9m2VAa4Tz5DEEJwYQV +IBnEamQN1eWP/R7dxcg+gIck8TevZC6r7wKMUATCcn9Ti0I0Hxdplts9+YIksJJ7 +GbgyPS3UWnXl2D0374KrqTKSRjEXPOzaNyJ0HB4Pr9bibuSZ6Qc0gSltz7xOPFYS +7cedTqpABJXF6hZM7tDsxPfXmBHDy2sU84yXTQQghmU5S7fLWgy3so4g/DUqxffS +hmYPagc9DsmRGc2CCZz8IHlVc7byZ/NF4FgqB3IATbqYBAw4S/RyKHfWpURie2hC +OtYLcOTzVJG4uD3FGxyXP1k= +-----END CERTIFICATE----- diff --git a/src/Security/Authentication/test/TestCertificates/validSignedClientCertificate.cer b/src/Security/Authentication/test/TestCertificates/validSignedClientCertificate.cer new file mode 100644 index 0000000000..a8034b05a8 --- /dev/null +++ b/src/Security/Authentication/test/TestCertificates/validSignedClientCertificate.cer @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDTTCCAjWgAwIBAgIQaaZ/qIdm4K5JhFdDzzj53DANBgkqhkiG9w0BAQsFADAm +MSQwIgYDVQQDDBtWYWxpZCBTaWduZWQgU2Vjb25kYXJ5IFJvb3QwHhcNMjAwMTIx +MDAwMDAwWhcNMjIwMTIxMDAwMDAwWjAeMRwwGgYDVQQDDBNWYWxpZCBTaWduZWQg +Q2xpZW50MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu3dVA8q1GewW +i1K0Pw0gKqgv92RrX3JI6tTiLbU6FBpFdV63b1M3jgFhUSXJi7L8A/dxh2HwvKBJ +p+4KW7V+aXQXOY8iShQwrIud5IExFdtEjyGVtfFSvfYmDgbfjFKIGswxsLenlfEt +7mp303GH99JVFql1n7S+bib79vKkrjFBqixhnXisXjNlBlfH6kRBYiwQ1Gc5oyib +fZQkfakXo896UwIvQjc0W27c0tiGY6xyGLSesLih2yECADiGa+cc5rnRc7R9/IMB +N7o5gLpbe71WBopI1uq1VSuXwH9xy0bq307dZEMaX0b4SqhkuQHsBVtOV1mYAskE +K3W8VUZy7QIDAQABo38wfTAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYB +BQUHAwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHwYDVR0jBBgwFoAUx9BZtKX7 +/z2mmoO2Ec127GkibsAwHQYDVR0OBBYEFDmtlIR/fVwtDseOG/NNQ/QEv3/PMA0G +CSqGSIb3DQEBCwUAA4IBAQBQMhsmlwF5JKEkfay7uLCH9IrJZGk1ZDxK/qcVaOk5 +mQ4IcCBq+Wp7Hg/D92b5diwlkXJDrYZZ7OHSEcD/PrxUKyZkoBQIvlNKDgmjp0wV +lXYUISZHaXbWZ0XNFAS0KyqoLZ8c2xmhuI21L3hyOoRcoqKleO1kzYfb2seBaRHk +Iu4la0opKGFoI/o7gC9uLrcizpj3SoPF9+vJz/FJmeBbKzKe1zA479a74tjfOODy +LZVbsGDhKRQ02GftFqXRl257hVX+6etQiOePj7S++R1B/QXRjvKrOTMs/NpMLAeK +8uWXSx+boL/8j/3u+65Udh614C5dXSrgDjMGJ7/OchC1 +-----END CERTIFICATE----- diff --git a/src/Security/Authentication/test/TestCertificates/validSignedSecondaryRootCertificate.cer b/src/Security/Authentication/test/TestCertificates/validSignedSecondaryRootCertificate.cer new file mode 100644 index 0000000000..ef31f31a98 --- /dev/null +++ b/src/Security/Authentication/test/TestCertificates/validSignedSecondaryRootCertificate.cer @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDXTCCAkWgAwIBAgIQE2HFYAdh7b5NSBsomRG9cjANBgkqhkiG9w0BAQsFADAp +MScwJQYDVQQDDB5WYWxpZCBTZWxmIFNpZ25lZCBQcmltYXJ5IFJvb3QwHhcNMjAw +MTIxMDAwMDAwWhcNMzUwMTIxMDAwMDAwWjAmMSQwIgYDVQQDDBtWYWxpZCBTaWdu +ZWQgU2Vjb25kYXJ5IFJvb3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +AQCw1Wvr8SeMdXM0ZMN3/NyZhGzXC/IcqJyI1tM1IQNpO9OxJWDkfxGh14ORRZ3f +4pTdXOXRCcPYxHk8d3kuH9EEo78WRrLV4XHw31vGrQkHAPn3ZMl/Qre1mYvzkKbn +DIpScfPYMuqydOvx1YSsTP2G37pNszOAXTkHPPH9smTo/W7Dv/1mnroAru/FU+Hv +zOMqNirIz1EpCEopLeBS41lcohyuCMzHPKJJZOnNbV3wV3AnpEriRLQVNO9WiaGs +Nwj8ffai9M4vncRQ9wLK866lx6iA7istjod9hourKQWC1284pv+RLtIeJaGrpXkV +mSbk9ebabz1fPC2/WgPtd1JVAgMBAAGjgYMwgYAwDgYDVR0PAQH/BAQDAgGGMB0G +A1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAPBgNVHRMBAf8EBTADAQH/MB8G +A1UdIwQYMBaAFP3AAuApFRLRI9lAmDFUv5pRQnQQMB0GA1UdDgQWBBTH0Fm0pfv/ +Paaag7YRzXbsaSJuwDANBgkqhkiG9w0BAQsFAAOCAQEAT5fEUkVP3Allay2ODcjQ +GzM135mV718DS84B4bVDBWr+CW9i89bzYgRZgClTABqddotHEqmLEan/bV4suBSt +QuACy7m39Q8kj/S/ydBhvHx9fxqWnAsacQ+fuAPviBQ11UZB19zWj1zikw1/Xfow +V9OIf4gYtY2aBPyygWN3HwpszhJWQIuFGl4rwqAxli7Wp2eUBXxDtYBHAscsclG4 +1rduhiV5eUZXZ11mbA7KBH9XwWKoFpRza049I0WC+V0PWqlK4H4P0QzCWUlXmTC8 +kN04cnPtyciOlP9J3Uro5xTXaDC0Cge82JmRxnCovGKGBEdjIxMC4nbPB4emmcth +BA== +-----END CERTIFICATE----- diff --git a/src/Security/Authorization/Core/ref/Microsoft.AspNetCore.Authorization.csproj b/src/Security/Authorization/Core/ref/Microsoft.AspNetCore.Authorization.csproj index b0cf327ff4..b4543827da 100644 --- a/src/Security/Authorization/Core/ref/Microsoft.AspNetCore.Authorization.csproj +++ b/src/Security/Authorization/Core/ref/Microsoft.AspNetCore.Authorization.csproj @@ -2,6 +2,7 @@ netstandard2.0;$(DefaultNetCoreTargetFramework) + $(DefaultNetCoreTargetFramework) diff --git a/src/Security/Authorization/Core/ref/Microsoft.AspNetCore.Authorization.netcoreapp.cs b/src/Security/Authorization/Core/ref/Microsoft.AspNetCore.Authorization.netcoreapp.cs index ab30a7c6d5..b3141c4979 100644 --- a/src/Security/Authorization/Core/ref/Microsoft.AspNetCore.Authorization.netcoreapp.cs +++ b/src/Security/Authorization/Core/ref/Microsoft.AspNetCore.Authorization.netcoreapp.cs @@ -11,8 +11,8 @@ namespace Microsoft.AspNetCore.Authorization public partial class AuthorizationFailure { internal AuthorizationFailure() { } - public bool FailCalled { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public System.Collections.Generic.IEnumerable FailedRequirements { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public bool FailCalled { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public System.Collections.Generic.IEnumerable FailedRequirements { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } public static Microsoft.AspNetCore.Authorization.AuthorizationFailure ExplicitFail() { throw null; } public static Microsoft.AspNetCore.Authorization.AuthorizationFailure Failed(System.Collections.Generic.IEnumerable failed) { throw null; } } @@ -22,9 +22,9 @@ namespace Microsoft.AspNetCore.Authorization public virtual bool HasFailed { get { throw null; } } public virtual bool HasSucceeded { get { throw null; } } public virtual System.Collections.Generic.IEnumerable PendingRequirements { get { throw null; } } - public virtual System.Collections.Generic.IEnumerable Requirements { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public virtual object Resource { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public virtual System.Security.Claims.ClaimsPrincipal User { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public virtual System.Collections.Generic.IEnumerable Requirements { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public virtual object Resource { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public virtual System.Security.Claims.ClaimsPrincipal User { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } public virtual void Fail() { } public virtual void Succeed(Microsoft.AspNetCore.Authorization.IAuthorizationRequirement requirement) { } } @@ -45,9 +45,9 @@ namespace Microsoft.AspNetCore.Authorization public partial class AuthorizationOptions { public AuthorizationOptions() { } - public Microsoft.AspNetCore.Authorization.AuthorizationPolicy DefaultPolicy { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public Microsoft.AspNetCore.Authorization.AuthorizationPolicy FallbackPolicy { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public bool InvokeHandlersAfterFailure { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.AspNetCore.Authorization.AuthorizationPolicy DefaultPolicy { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public Microsoft.AspNetCore.Authorization.AuthorizationPolicy FallbackPolicy { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public bool InvokeHandlersAfterFailure { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public void AddPolicy(string name, Microsoft.AspNetCore.Authorization.AuthorizationPolicy policy) { } public void AddPolicy(string name, System.Action configurePolicy) { } public Microsoft.AspNetCore.Authorization.AuthorizationPolicy GetPolicy(string name) { throw null; } @@ -55,8 +55,8 @@ namespace Microsoft.AspNetCore.Authorization public partial class AuthorizationPolicy { public AuthorizationPolicy(System.Collections.Generic.IEnumerable requirements, System.Collections.Generic.IEnumerable authenticationSchemes) { } - public System.Collections.Generic.IReadOnlyList AuthenticationSchemes { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public System.Collections.Generic.IReadOnlyList Requirements { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public System.Collections.Generic.IReadOnlyList AuthenticationSchemes { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public System.Collections.Generic.IReadOnlyList Requirements { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } public static Microsoft.AspNetCore.Authorization.AuthorizationPolicy Combine(params Microsoft.AspNetCore.Authorization.AuthorizationPolicy[] policies) { throw null; } public static Microsoft.AspNetCore.Authorization.AuthorizationPolicy Combine(System.Collections.Generic.IEnumerable policies) { throw null; } [System.Diagnostics.DebuggerStepThroughAttribute] @@ -66,8 +66,8 @@ namespace Microsoft.AspNetCore.Authorization { public AuthorizationPolicyBuilder(Microsoft.AspNetCore.Authorization.AuthorizationPolicy policy) { } public AuthorizationPolicyBuilder(params string[] authenticationSchemes) { } - public System.Collections.Generic.IList AuthenticationSchemes { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public System.Collections.Generic.IList Requirements { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public System.Collections.Generic.IList AuthenticationSchemes { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public System.Collections.Generic.IList Requirements { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public Microsoft.AspNetCore.Authorization.AuthorizationPolicyBuilder AddAuthenticationSchemes(params string[] schemes) { throw null; } public Microsoft.AspNetCore.Authorization.AuthorizationPolicyBuilder AddRequirements(params Microsoft.AspNetCore.Authorization.IAuthorizationRequirement[] requirements) { throw null; } public Microsoft.AspNetCore.Authorization.AuthorizationPolicy Build() { throw null; } @@ -85,8 +85,8 @@ namespace Microsoft.AspNetCore.Authorization public partial class AuthorizationResult { internal AuthorizationResult() { } - public Microsoft.AspNetCore.Authorization.AuthorizationFailure Failure { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public bool Succeeded { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public Microsoft.AspNetCore.Authorization.AuthorizationFailure Failure { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public bool Succeeded { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } public static Microsoft.AspNetCore.Authorization.AuthorizationResult Failed() { throw null; } public static Microsoft.AspNetCore.Authorization.AuthorizationResult Failed(Microsoft.AspNetCore.Authorization.AuthorizationFailure failure) { throw null; } public static Microsoft.AspNetCore.Authorization.AuthorizationResult Success() { throw null; } @@ -103,9 +103,9 @@ namespace Microsoft.AspNetCore.Authorization { public AuthorizeAttribute() { } public AuthorizeAttribute(string policy) { } - public string AuthenticationSchemes { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public string Policy { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public string Roles { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public string AuthenticationSchemes { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public string Policy { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public string Roles { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } public partial class DefaultAuthorizationEvaluator : Microsoft.AspNetCore.Authorization.IAuthorizationEvaluator { @@ -174,32 +174,37 @@ namespace Microsoft.AspNetCore.Authorization.Infrastructure { public AssertionRequirement(System.Func handler) { } public AssertionRequirement(System.Func> handler) { } - public System.Func> Handler { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public System.Func> Handler { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } [System.Diagnostics.DebuggerStepThroughAttribute] public System.Threading.Tasks.Task HandleAsync(Microsoft.AspNetCore.Authorization.AuthorizationHandlerContext context) { throw null; } + public override string ToString() { throw null; } } public partial class ClaimsAuthorizationRequirement : Microsoft.AspNetCore.Authorization.AuthorizationHandler, Microsoft.AspNetCore.Authorization.IAuthorizationRequirement { public ClaimsAuthorizationRequirement(string claimType, System.Collections.Generic.IEnumerable allowedValues) { } - public System.Collections.Generic.IEnumerable AllowedValues { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public string ClaimType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public System.Collections.Generic.IEnumerable AllowedValues { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public string ClaimType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } protected override System.Threading.Tasks.Task HandleRequirementAsync(Microsoft.AspNetCore.Authorization.AuthorizationHandlerContext context, Microsoft.AspNetCore.Authorization.Infrastructure.ClaimsAuthorizationRequirement requirement) { throw null; } + public override string ToString() { throw null; } } public partial class DenyAnonymousAuthorizationRequirement : Microsoft.AspNetCore.Authorization.AuthorizationHandler, Microsoft.AspNetCore.Authorization.IAuthorizationRequirement { public DenyAnonymousAuthorizationRequirement() { } protected override System.Threading.Tasks.Task HandleRequirementAsync(Microsoft.AspNetCore.Authorization.AuthorizationHandlerContext context, Microsoft.AspNetCore.Authorization.Infrastructure.DenyAnonymousAuthorizationRequirement requirement) { throw null; } + public override string ToString() { throw null; } } public partial class NameAuthorizationRequirement : Microsoft.AspNetCore.Authorization.AuthorizationHandler, Microsoft.AspNetCore.Authorization.IAuthorizationRequirement { public NameAuthorizationRequirement(string requiredName) { } - public string RequiredName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public string RequiredName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } protected override System.Threading.Tasks.Task HandleRequirementAsync(Microsoft.AspNetCore.Authorization.AuthorizationHandlerContext context, Microsoft.AspNetCore.Authorization.Infrastructure.NameAuthorizationRequirement requirement) { throw null; } + public override string ToString() { throw null; } } public partial class OperationAuthorizationRequirement : Microsoft.AspNetCore.Authorization.IAuthorizationRequirement { public OperationAuthorizationRequirement() { } - public string Name { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public string Name { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public override string ToString() { throw null; } } public partial class PassThroughAuthorizationHandler : Microsoft.AspNetCore.Authorization.IAuthorizationHandler { @@ -210,8 +215,9 @@ namespace Microsoft.AspNetCore.Authorization.Infrastructure public partial class RolesAuthorizationRequirement : Microsoft.AspNetCore.Authorization.AuthorizationHandler, Microsoft.AspNetCore.Authorization.IAuthorizationRequirement { public RolesAuthorizationRequirement(System.Collections.Generic.IEnumerable allowedRoles) { } - public System.Collections.Generic.IEnumerable AllowedRoles { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public System.Collections.Generic.IEnumerable AllowedRoles { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } protected override System.Threading.Tasks.Task HandleRequirementAsync(Microsoft.AspNetCore.Authorization.AuthorizationHandlerContext context, Microsoft.AspNetCore.Authorization.Infrastructure.RolesAuthorizationRequirement requirement) { throw null; } + public override string ToString() { throw null; } } } namespace Microsoft.Extensions.DependencyInjection diff --git a/src/Security/Authorization/Core/ref/Microsoft.AspNetCore.Authorization.netstandard2.0.cs b/src/Security/Authorization/Core/ref/Microsoft.AspNetCore.Authorization.netstandard2.0.cs index ab30a7c6d5..b3141c4979 100644 --- a/src/Security/Authorization/Core/ref/Microsoft.AspNetCore.Authorization.netstandard2.0.cs +++ b/src/Security/Authorization/Core/ref/Microsoft.AspNetCore.Authorization.netstandard2.0.cs @@ -11,8 +11,8 @@ namespace Microsoft.AspNetCore.Authorization public partial class AuthorizationFailure { internal AuthorizationFailure() { } - public bool FailCalled { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public System.Collections.Generic.IEnumerable FailedRequirements { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public bool FailCalled { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public System.Collections.Generic.IEnumerable FailedRequirements { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } public static Microsoft.AspNetCore.Authorization.AuthorizationFailure ExplicitFail() { throw null; } public static Microsoft.AspNetCore.Authorization.AuthorizationFailure Failed(System.Collections.Generic.IEnumerable failed) { throw null; } } @@ -22,9 +22,9 @@ namespace Microsoft.AspNetCore.Authorization public virtual bool HasFailed { get { throw null; } } public virtual bool HasSucceeded { get { throw null; } } public virtual System.Collections.Generic.IEnumerable PendingRequirements { get { throw null; } } - public virtual System.Collections.Generic.IEnumerable Requirements { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public virtual object Resource { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public virtual System.Security.Claims.ClaimsPrincipal User { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public virtual System.Collections.Generic.IEnumerable Requirements { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public virtual object Resource { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public virtual System.Security.Claims.ClaimsPrincipal User { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } public virtual void Fail() { } public virtual void Succeed(Microsoft.AspNetCore.Authorization.IAuthorizationRequirement requirement) { } } @@ -45,9 +45,9 @@ namespace Microsoft.AspNetCore.Authorization public partial class AuthorizationOptions { public AuthorizationOptions() { } - public Microsoft.AspNetCore.Authorization.AuthorizationPolicy DefaultPolicy { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public Microsoft.AspNetCore.Authorization.AuthorizationPolicy FallbackPolicy { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public bool InvokeHandlersAfterFailure { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.AspNetCore.Authorization.AuthorizationPolicy DefaultPolicy { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public Microsoft.AspNetCore.Authorization.AuthorizationPolicy FallbackPolicy { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public bool InvokeHandlersAfterFailure { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public void AddPolicy(string name, Microsoft.AspNetCore.Authorization.AuthorizationPolicy policy) { } public void AddPolicy(string name, System.Action configurePolicy) { } public Microsoft.AspNetCore.Authorization.AuthorizationPolicy GetPolicy(string name) { throw null; } @@ -55,8 +55,8 @@ namespace Microsoft.AspNetCore.Authorization public partial class AuthorizationPolicy { public AuthorizationPolicy(System.Collections.Generic.IEnumerable requirements, System.Collections.Generic.IEnumerable authenticationSchemes) { } - public System.Collections.Generic.IReadOnlyList AuthenticationSchemes { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public System.Collections.Generic.IReadOnlyList Requirements { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public System.Collections.Generic.IReadOnlyList AuthenticationSchemes { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public System.Collections.Generic.IReadOnlyList Requirements { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } public static Microsoft.AspNetCore.Authorization.AuthorizationPolicy Combine(params Microsoft.AspNetCore.Authorization.AuthorizationPolicy[] policies) { throw null; } public static Microsoft.AspNetCore.Authorization.AuthorizationPolicy Combine(System.Collections.Generic.IEnumerable policies) { throw null; } [System.Diagnostics.DebuggerStepThroughAttribute] @@ -66,8 +66,8 @@ namespace Microsoft.AspNetCore.Authorization { public AuthorizationPolicyBuilder(Microsoft.AspNetCore.Authorization.AuthorizationPolicy policy) { } public AuthorizationPolicyBuilder(params string[] authenticationSchemes) { } - public System.Collections.Generic.IList AuthenticationSchemes { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public System.Collections.Generic.IList Requirements { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public System.Collections.Generic.IList AuthenticationSchemes { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public System.Collections.Generic.IList Requirements { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public Microsoft.AspNetCore.Authorization.AuthorizationPolicyBuilder AddAuthenticationSchemes(params string[] schemes) { throw null; } public Microsoft.AspNetCore.Authorization.AuthorizationPolicyBuilder AddRequirements(params Microsoft.AspNetCore.Authorization.IAuthorizationRequirement[] requirements) { throw null; } public Microsoft.AspNetCore.Authorization.AuthorizationPolicy Build() { throw null; } @@ -85,8 +85,8 @@ namespace Microsoft.AspNetCore.Authorization public partial class AuthorizationResult { internal AuthorizationResult() { } - public Microsoft.AspNetCore.Authorization.AuthorizationFailure Failure { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public bool Succeeded { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public Microsoft.AspNetCore.Authorization.AuthorizationFailure Failure { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public bool Succeeded { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } public static Microsoft.AspNetCore.Authorization.AuthorizationResult Failed() { throw null; } public static Microsoft.AspNetCore.Authorization.AuthorizationResult Failed(Microsoft.AspNetCore.Authorization.AuthorizationFailure failure) { throw null; } public static Microsoft.AspNetCore.Authorization.AuthorizationResult Success() { throw null; } @@ -103,9 +103,9 @@ namespace Microsoft.AspNetCore.Authorization { public AuthorizeAttribute() { } public AuthorizeAttribute(string policy) { } - public string AuthenticationSchemes { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public string Policy { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public string Roles { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public string AuthenticationSchemes { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public string Policy { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public string Roles { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } public partial class DefaultAuthorizationEvaluator : Microsoft.AspNetCore.Authorization.IAuthorizationEvaluator { @@ -174,32 +174,37 @@ namespace Microsoft.AspNetCore.Authorization.Infrastructure { public AssertionRequirement(System.Func handler) { } public AssertionRequirement(System.Func> handler) { } - public System.Func> Handler { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public System.Func> Handler { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } [System.Diagnostics.DebuggerStepThroughAttribute] public System.Threading.Tasks.Task HandleAsync(Microsoft.AspNetCore.Authorization.AuthorizationHandlerContext context) { throw null; } + public override string ToString() { throw null; } } public partial class ClaimsAuthorizationRequirement : Microsoft.AspNetCore.Authorization.AuthorizationHandler, Microsoft.AspNetCore.Authorization.IAuthorizationRequirement { public ClaimsAuthorizationRequirement(string claimType, System.Collections.Generic.IEnumerable allowedValues) { } - public System.Collections.Generic.IEnumerable AllowedValues { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public string ClaimType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public System.Collections.Generic.IEnumerable AllowedValues { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public string ClaimType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } protected override System.Threading.Tasks.Task HandleRequirementAsync(Microsoft.AspNetCore.Authorization.AuthorizationHandlerContext context, Microsoft.AspNetCore.Authorization.Infrastructure.ClaimsAuthorizationRequirement requirement) { throw null; } + public override string ToString() { throw null; } } public partial class DenyAnonymousAuthorizationRequirement : Microsoft.AspNetCore.Authorization.AuthorizationHandler, Microsoft.AspNetCore.Authorization.IAuthorizationRequirement { public DenyAnonymousAuthorizationRequirement() { } protected override System.Threading.Tasks.Task HandleRequirementAsync(Microsoft.AspNetCore.Authorization.AuthorizationHandlerContext context, Microsoft.AspNetCore.Authorization.Infrastructure.DenyAnonymousAuthorizationRequirement requirement) { throw null; } + public override string ToString() { throw null; } } public partial class NameAuthorizationRequirement : Microsoft.AspNetCore.Authorization.AuthorizationHandler, Microsoft.AspNetCore.Authorization.IAuthorizationRequirement { public NameAuthorizationRequirement(string requiredName) { } - public string RequiredName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public string RequiredName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } protected override System.Threading.Tasks.Task HandleRequirementAsync(Microsoft.AspNetCore.Authorization.AuthorizationHandlerContext context, Microsoft.AspNetCore.Authorization.Infrastructure.NameAuthorizationRequirement requirement) { throw null; } + public override string ToString() { throw null; } } public partial class OperationAuthorizationRequirement : Microsoft.AspNetCore.Authorization.IAuthorizationRequirement { public OperationAuthorizationRequirement() { } - public string Name { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public string Name { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public override string ToString() { throw null; } } public partial class PassThroughAuthorizationHandler : Microsoft.AspNetCore.Authorization.IAuthorizationHandler { @@ -210,8 +215,9 @@ namespace Microsoft.AspNetCore.Authorization.Infrastructure public partial class RolesAuthorizationRequirement : Microsoft.AspNetCore.Authorization.AuthorizationHandler, Microsoft.AspNetCore.Authorization.IAuthorizationRequirement { public RolesAuthorizationRequirement(System.Collections.Generic.IEnumerable allowedRoles) { } - public System.Collections.Generic.IEnumerable AllowedRoles { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public System.Collections.Generic.IEnumerable AllowedRoles { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } protected override System.Threading.Tasks.Task HandleRequirementAsync(Microsoft.AspNetCore.Authorization.AuthorizationHandlerContext context, Microsoft.AspNetCore.Authorization.Infrastructure.RolesAuthorizationRequirement requirement) { throw null; } + public override string ToString() { throw null; } } } namespace Microsoft.Extensions.DependencyInjection diff --git a/src/Security/Authorization/Core/src/AssertionRequirement.cs b/src/Security/Authorization/Core/src/AssertionRequirement.cs index 5fa452b733..0c31f57105 100644 --- a/src/Security/Authorization/Core/src/AssertionRequirement.cs +++ b/src/Security/Authorization/Core/src/AssertionRequirement.cs @@ -56,5 +56,10 @@ namespace Microsoft.AspNetCore.Authorization.Infrastructure context.Succeed(this); } } + + public override string ToString() + { + return $"{nameof(Handler)} assertion should evaluate to true."; + } } } diff --git a/src/Security/Authorization/Core/src/AuthorizationPolicy.cs b/src/Security/Authorization/Core/src/AuthorizationPolicy.cs index f1833da954..b7c52722fa 100644 --- a/src/Security/Authorization/Core/src/AuthorizationPolicy.cs +++ b/src/Security/Authorization/Core/src/AuthorizationPolicy.cs @@ -37,7 +37,7 @@ namespace Microsoft.AspNetCore.Authorization throw new ArgumentNullException(nameof(authenticationSchemes)); } - if (requirements.Count() == 0) + if (!requirements.Any()) { throw new InvalidOperationException(Resources.Exception_AuthorizationPolicyEmpty); } @@ -150,7 +150,7 @@ namespace Microsoft.AspNetCore.Authorization } var rolesSplit = authorizeDatum.Roles?.Split(','); - if (rolesSplit != null && rolesSplit.Any()) + if (rolesSplit?.Length > 0) { var trimmedRolesSplit = rolesSplit.Where(r => !string.IsNullOrWhiteSpace(r)).Select(r => r.Trim()); policyBuilder.RequireRole(trimmedRolesSplit); @@ -158,7 +158,7 @@ namespace Microsoft.AspNetCore.Authorization } var authTypesSplit = authorizeDatum.AuthenticationSchemes?.Split(','); - if (authTypesSplit != null && authTypesSplit.Any()) + if (authTypesSplit?.Length > 0) { foreach (var authType in authTypesSplit) { diff --git a/src/Security/Authorization/Core/src/AuthorizationPolicyBuilder.cs b/src/Security/Authorization/Core/src/AuthorizationPolicyBuilder.cs index 2f483e37ce..ffcc339aae 100644 --- a/src/Security/Authorization/Core/src/AuthorizationPolicyBuilder.cs +++ b/src/Security/Authorization/Core/src/AuthorizationPolicyBuilder.cs @@ -130,7 +130,7 @@ namespace Microsoft.AspNetCore.Authorization /// Adds a /// to the current instance. /// - /// The claim type required, which no restrictions on claim value. + /// The claim type required, with no restrictions on claim value. /// A reference to this instance after the operation has completed. public AuthorizationPolicyBuilder RequireClaim(string claimType) { diff --git a/src/Security/Authorization/Core/src/AuthorizationServiceCollectionExtensions.cs b/src/Security/Authorization/Core/src/AuthorizationServiceCollectionExtensions.cs index 0f788cd5ad..57fcf253b7 100644 --- a/src/Security/Authorization/Core/src/AuthorizationServiceCollectionExtensions.cs +++ b/src/Security/Authorization/Core/src/AuthorizationServiceCollectionExtensions.cs @@ -14,7 +14,7 @@ namespace Microsoft.Extensions.DependencyInjection public static class AuthorizationServiceCollectionExtensions { /// - /// Adds authorization services to the specified . + /// Adds authorization services to the specified . /// /// The to add services to. /// The so that additional calls can be chained. @@ -24,7 +24,11 @@ namespace Microsoft.Extensions.DependencyInjection { throw new ArgumentNullException(nameof(services)); } - + + // These services depend on options, and they are used in Blazor WASM, where options + // aren't included by default. + services.AddOptions(); + services.TryAdd(ServiceDescriptor.Transient()); services.TryAdd(ServiceDescriptor.Transient()); services.TryAdd(ServiceDescriptor.Transient()); @@ -35,7 +39,7 @@ namespace Microsoft.Extensions.DependencyInjection } /// - /// Adds authorization services to the specified . + /// Adds authorization services to the specified . /// /// The to add services to. /// An action delegate to configure the provided . diff --git a/src/Security/Authorization/Core/src/ClaimsAuthorizationRequirement.cs b/src/Security/Authorization/Core/src/ClaimsAuthorizationRequirement.cs index 93b1deea6d..f77b74fd74 100644 --- a/src/Security/Authorization/Core/src/ClaimsAuthorizationRequirement.cs +++ b/src/Security/Authorization/Core/src/ClaimsAuthorizationRequirement.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Security.Claims; using System.Threading.Tasks; namespace Microsoft.AspNetCore.Authorization.Infrastructure @@ -69,5 +70,14 @@ namespace Microsoft.AspNetCore.Authorization.Infrastructure } return Task.CompletedTask; } + + public override string ToString() + { + var value = (AllowedValues == null || !AllowedValues.Any()) + ? string.Empty + : $" and Claim.Value is one of the following values: ({string.Join("|", AllowedValues)})"; + + return $"{nameof(ClaimsAuthorizationRequirement)}:Claim.Type={ClaimType}{value}"; + } } } diff --git a/src/Security/Authorization/Core/src/DefaultAuthorizationService.cs b/src/Security/Authorization/Core/src/DefaultAuthorizationService.cs index bc5d571c47..475dea3330 100644 --- a/src/Security/Authorization/Core/src/DefaultAuthorizationService.cs +++ b/src/Security/Authorization/Core/src/DefaultAuthorizationService.cs @@ -102,7 +102,7 @@ namespace Microsoft.AspNetCore.Authorization } else { - _logger.UserAuthorizationFailed(); + _logger.UserAuthorizationFailed(result.Failure); } return result; } @@ -132,4 +132,4 @@ namespace Microsoft.AspNetCore.Authorization return await this.AuthorizeAsync(user, resource, policy); } } -} \ No newline at end of file +} diff --git a/src/Security/Authorization/Core/src/DenyAnonymousAuthorizationRequirement.cs b/src/Security/Authorization/Core/src/DenyAnonymousAuthorizationRequirement.cs index e88cce7aac..35828735a5 100644 --- a/src/Security/Authorization/Core/src/DenyAnonymousAuthorizationRequirement.cs +++ b/src/Security/Authorization/Core/src/DenyAnonymousAuthorizationRequirement.cs @@ -29,5 +29,10 @@ namespace Microsoft.AspNetCore.Authorization.Infrastructure } return Task.CompletedTask; } + + public override string ToString() + { + return $"{nameof(DenyAnonymousAuthorizationRequirement)}:Requires an authenticated user."; + } } } diff --git a/src/Security/Authorization/Core/src/LoggingExtensions.cs b/src/Security/Authorization/Core/src/LoggingExtensions.cs index e31d88161d..ef57f8c09f 100644 --- a/src/Security/Authorization/Core/src/LoggingExtensions.cs +++ b/src/Security/Authorization/Core/src/LoggingExtensions.cs @@ -2,12 +2,13 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using Microsoft.AspNetCore.Authorization; namespace Microsoft.Extensions.Logging { internal static class LoggingExtensions { - private static Action _userAuthorizationFailed; + private static Action _userAuthorizationFailed; private static Action _userAuthorizationSucceeded; static LoggingExtensions() @@ -16,16 +17,22 @@ namespace Microsoft.Extensions.Logging eventId: new EventId(1, "UserAuthorizationSucceeded"), logLevel: LogLevel.Information, formatString: "Authorization was successful."); - _userAuthorizationFailed = LoggerMessage.Define( + _userAuthorizationFailed = LoggerMessage.Define( eventId: new EventId(2, "UserAuthorizationFailed"), logLevel: LogLevel.Information, - formatString: "Authorization failed."); + formatString: "Authorization failed for {0}"); } public static void UserAuthorizationSucceeded(this ILogger logger) => _userAuthorizationSucceeded(logger, null); - public static void UserAuthorizationFailed(this ILogger logger) - => _userAuthorizationFailed(logger, null); + public static void UserAuthorizationFailed(this ILogger logger, AuthorizationFailure failure) + { + var reason = failure.FailCalled + ? "Fail() was explicitly called." + : "These requirements were not met:" + Environment.NewLine + string.Join(Environment.NewLine, failure.FailedRequirements); + + _userAuthorizationFailed(logger, reason, null); + } } } diff --git a/src/Security/Authorization/Core/src/NameAuthorizationRequirement.cs b/src/Security/Authorization/Core/src/NameAuthorizationRequirement.cs index 02ab946fad..36cee10aac 100644 --- a/src/Security/Authorization/Core/src/NameAuthorizationRequirement.cs +++ b/src/Security/Authorization/Core/src/NameAuthorizationRequirement.cs @@ -48,5 +48,10 @@ namespace Microsoft.AspNetCore.Authorization.Infrastructure } return Task.CompletedTask; } + + public override string ToString() + { + return $"{nameof(NameAuthorizationRequirement)}:Requires a user identity with Name equal to {RequiredName}"; + } } } diff --git a/src/Security/Authorization/Core/src/OperationAuthorizationRequirement.cs b/src/Security/Authorization/Core/src/OperationAuthorizationRequirement.cs index c3f16356d3..0e2a6fcf3f 100644 --- a/src/Security/Authorization/Core/src/OperationAuthorizationRequirement.cs +++ b/src/Security/Authorization/Core/src/OperationAuthorizationRequirement.cs @@ -13,5 +13,10 @@ namespace Microsoft.AspNetCore.Authorization.Infrastructure /// The name of this instance of . /// public string Name { get; set; } + + public override string ToString() + { + return $"{nameof(OperationAuthorizationRequirement)}:Name={Name}"; + } } } diff --git a/src/Security/Authorization/Core/src/RolesAuthorizationRequirement.cs b/src/Security/Authorization/Core/src/RolesAuthorizationRequirement.cs index 811e17aacd..e5a2251a14 100644 --- a/src/Security/Authorization/Core/src/RolesAuthorizationRequirement.cs +++ b/src/Security/Authorization/Core/src/RolesAuthorizationRequirement.cs @@ -25,7 +25,7 @@ namespace Microsoft.AspNetCore.Authorization.Infrastructure throw new ArgumentNullException(nameof(allowedRoles)); } - if (allowedRoles.Count() == 0) + if (!allowedRoles.Any()) { throw new InvalidOperationException(Resources.Exception_RoleRequirementEmpty); } @@ -64,5 +64,11 @@ namespace Microsoft.AspNetCore.Authorization.Infrastructure return Task.CompletedTask; } + public override string ToString() + { + var roles = $"User.IsInRole must be true for one of the following roles: ({string.Join("|", AllowedRoles)})"; + + return $"{nameof(RolesAuthorizationRequirement)}:{roles}"; + } } } diff --git a/src/Security/Authorization/Policy/ref/Microsoft.AspNetCore.Authorization.Policy.netcoreapp.cs b/src/Security/Authorization/Policy/ref/Microsoft.AspNetCore.Authorization.Policy.netcoreapp.cs index 571d21a03d..138cd20ea5 100644 --- a/src/Security/Authorization/Policy/ref/Microsoft.AspNetCore.Authorization.Policy.netcoreapp.cs +++ b/src/Security/Authorization/Policy/ref/Microsoft.AspNetCore.Authorization.Policy.netcoreapp.cs @@ -20,9 +20,9 @@ namespace Microsoft.AspNetCore.Authorization.Policy public partial class PolicyAuthorizationResult { internal PolicyAuthorizationResult() { } - public bool Challenged { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public bool Forbidden { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public bool Succeeded { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public bool Challenged { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public bool Forbidden { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public bool Succeeded { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } public static Microsoft.AspNetCore.Authorization.Policy.PolicyAuthorizationResult Challenge() { throw null; } public static Microsoft.AspNetCore.Authorization.Policy.PolicyAuthorizationResult Forbid() { throw null; } public static Microsoft.AspNetCore.Authorization.Policy.PolicyAuthorizationResult Success() { throw null; } diff --git a/src/Security/Authorization/Policy/src/AuthorizationMiddleware.cs b/src/Security/Authorization/Policy/src/AuthorizationMiddleware.cs index 686374d829..af115e9daa 100644 --- a/src/Security/Authorization/Policy/src/AuthorizationMiddleware.cs +++ b/src/Security/Authorization/Policy/src/AuthorizationMiddleware.cs @@ -68,7 +68,7 @@ namespace Microsoft.AspNetCore.Authorization if (authorizeResult.Challenged) { - if (policy.AuthenticationSchemes.Any()) + if (policy.AuthenticationSchemes.Count > 0) { foreach (var scheme in policy.AuthenticationSchemes) { @@ -84,7 +84,7 @@ namespace Microsoft.AspNetCore.Authorization } else if (authorizeResult.Forbidden) { - if (policy.AuthenticationSchemes.Any()) + if (policy.AuthenticationSchemes.Count > 0) { foreach (var scheme in policy.AuthenticationSchemes) { diff --git a/src/Security/Authorization/test/AssertionRequirementsTests.cs b/src/Security/Authorization/test/AssertionRequirementsTests.cs new file mode 100644 index 0000000000..d197860329 --- /dev/null +++ b/src/Security/Authorization/test/AssertionRequirementsTests.cs @@ -0,0 +1,32 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Text; +using Microsoft.AspNetCore.Authorization.Infrastructure; +using Xunit; + +namespace Microsoft.AspNetCore.Authorization.Test +{ + public class AssertionRequirementsTests + { + private AssertionRequirement CreateRequirement() + { + return new AssertionRequirement(context => true); + } + + [Fact] + public void ToString_ShouldReturnFormatValue() + { + // Arrange + var requirement = new AssertionRequirement(context => true); + + // Act + var formattedValue = requirement.ToString(); + + // Assert + Assert.Equal("Handler assertion should evaluate to true.", formattedValue); + } + } +} diff --git a/src/Security/Authorization/test/AuthorizationMiddlewareTests.cs b/src/Security/Authorization/test/AuthorizationMiddlewareTests.cs index 86858f4fe1..075a6ee655 100644 --- a/src/Security/Authorization/test/AuthorizationMiddlewareTests.cs +++ b/src/Security/Authorization/test/AuthorizationMiddlewareTests.cs @@ -53,7 +53,7 @@ namespace Microsoft.AspNetCore.Authorization.Test // Assert Assert.False(next.Called); } - + [Fact] public async Task HasEndpointWithoutAuth_AnonymousUser_Allows() { @@ -135,6 +135,27 @@ namespace Microsoft.AspNetCore.Authorization.Test Assert.True(authenticationService.ChallengeCalled); } + [Fact] + public async Task HasEndpointWithAuth_ChallengesAuthenticationSchemes() + { + // Arrange + var policy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build(); + var policyProvider = new Mock(); + policyProvider.Setup(p => p.GetDefaultPolicyAsync()).ReturnsAsync(policy); + var next = new TestRequestDelegate(); + var authenticationService = new TestAuthenticationService(); + + var middleware = CreateMiddleware(next.Invoke, policyProvider.Object); + var context = GetHttpContext(endpoint: CreateEndpoint(new AuthorizeAttribute() { AuthenticationSchemes = "whatever"}), authenticationService: authenticationService); + + // Act + await middleware.Invoke(context); + + // Assert + Assert.False(next.Called); + Assert.True(authenticationService.ChallengeCalled); + } + [Fact] public async Task HasEndpointWithAuth_AnonymousUser_ChallengePerScheme() { diff --git a/src/Security/Authorization/test/ClaimsAuthorizationRequirementTests.cs b/src/Security/Authorization/test/ClaimsAuthorizationRequirementTests.cs new file mode 100644 index 0000000000..d1bbe69a64 --- /dev/null +++ b/src/Security/Authorization/test/ClaimsAuthorizationRequirementTests.cs @@ -0,0 +1,58 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Text; +using Microsoft.AspNetCore.Authorization.Infrastructure; +using Xunit; + +namespace Microsoft.AspNetCore.Authorization.Test +{ + public class ClaimsAuthorizationRequirementTests + { + public ClaimsAuthorizationRequirement CreateRequirement(string claimType, params string[] allowedValues) + { + return new ClaimsAuthorizationRequirement(claimType, allowedValues); + } + + [Fact] + public void ToString_ShouldReturnAndDescriptionWhenAllowedValuesNotNull() + { + // Arrange + var requirement = CreateRequirement("Custom", "CustomValue1", "CustomValue2"); + + // Act + var formattedValue = requirement.ToString(); + + // Assert + Assert.Equal("ClaimsAuthorizationRequirement:Claim.Type=Custom and Claim.Value is one of the following values: (CustomValue1|CustomValue2)", formattedValue); + } + + [Fact] + public void ToString_ShouldReturnWithoutAllowedDescriptionWhenAllowedValuesIsNull() + { + // Arrange + var requirement = CreateRequirement("Custom", (string[])null); + + // Act + var formattedValue = requirement.ToString(); + + // Assert + Assert.Equal("ClaimsAuthorizationRequirement:Claim.Type=Custom", formattedValue); + } + + [Fact] + public void ToString_ShouldReturnWithoutAllowedDescriptionWhenAllowedValuesIsEmpty() + { + // Arrange + var requirement = CreateRequirement("Custom", Array.Empty()); + + // Act + var formattedValue = requirement.ToString(); + + // Assert + Assert.Equal("ClaimsAuthorizationRequirement:Claim.Type=Custom", formattedValue); + } + } +} diff --git a/src/Security/Authorization/test/DefaultAuthorizationServiceTests.cs b/src/Security/Authorization/test/DefaultAuthorizationServiceTests.cs index d0fe9a62e9..ecd55a4acc 100644 --- a/src/Security/Authorization/test/DefaultAuthorizationServiceTests.cs +++ b/src/Security/Authorization/test/DefaultAuthorizationServiceTests.cs @@ -8,6 +8,7 @@ using System.Security.Claims; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization.Infrastructure; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Xunit; @@ -64,7 +65,8 @@ namespace Microsoft.AspNetCore.Authorization.Test { services.AddAuthorization(options => { - options.AddPolicy("Basic", policy => { + options.AddPolicy("Basic", policy => + { policy.AddAuthenticationSchemes("Basic"); policy.RequireClaim("Permission", "CanViewPage"); }); @@ -710,7 +712,8 @@ namespace Microsoft.AspNetCore.Authorization.Test protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, PassThroughRequirement requirement) { - if (Succeed) { + if (Succeed) + { context.Succeed(requirement); } return Task.FromResult(0); @@ -926,7 +929,6 @@ namespace Microsoft.AspNetCore.Authorization.Test Assert.True((await authorizationService.AuthorizeAsync(user, 2, Operations.Edit)).Succeeded); } - [Fact] public async Task DoesNotCallHandlerWithWrongResourceType() { @@ -1174,5 +1176,104 @@ namespace Microsoft.AspNetCore.Authorization.Test Assert.False((await authorizationService.AuthorizeAsync(null, "Success")).Succeeded); } + public class LogRequirement : IAuthorizationRequirement + { + public override string ToString() + { + return "LogRequirement"; + } + } + + public class DefaultAuthorizationServiceTestLogger : ILogger + { + private Action> _assertion; + + public DefaultAuthorizationServiceTestLogger(Action> assertion) + { + _assertion = assertion; + } + + public IDisposable BeginScope(TState state) + { + throw new NotImplementedException(); + } + + public bool IsEnabled(LogLevel logLevel) + { + return true; + } + + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) + { + _assertion(logLevel, eventId, state, exception, (s, e) => formatter?.Invoke((TState)s, e)); + } + } + + [Fact] + public async Task Authorize_ShouldLogRequirementDetailWhenUnHandled() + { + // Arrange + + static void Assertion(LogLevel level, EventId eventId, object state, Exception exception, Func formatter) + { + Assert.Equal(LogLevel.Information, level); + Assert.Equal(2, eventId.Id); + Assert.Equal("UserAuthorizationFailed", eventId.Name); + var message = formatter(state, exception); + + Assert.Equal("Authorization failed for These requirements were not met:" + Environment.NewLine + "LogRequirement" + Environment.NewLine + "LogRequirement", message); + } + + var authorizationService = BuildAuthorizationService(services => + { + services.AddSingleton>(new DefaultAuthorizationServiceTestLogger(Assertion)); + services.AddAuthorization(options => options.AddPolicy("Log", p => + { + p.Requirements.Add(new LogRequirement()); + p.Requirements.Add(new LogRequirement()); + })); + }); + + var user = new ClaimsPrincipal(); + + // Act + var result = await authorizationService.AuthorizeAsync(user, "Log"); + + // Assert + } + + [Fact] + public async Task Authorize_ShouldLogExplicitFailedWhenFailedCall() + { + // Arrange + + static void Assertion(LogLevel level, EventId eventId, object state, Exception exception, Func formatter) + { + Assert.Equal(LogLevel.Information, level); + Assert.Equal(2, eventId.Id); + Assert.Equal("UserAuthorizationFailed", eventId.Name); + var message = formatter(state, exception); + + Assert.Equal("Authorization failed for Fail() was explicitly called.", message); + } + + var authorizationService = BuildAuthorizationService(services => + { + services.AddSingleton(); + services.AddSingleton>(new DefaultAuthorizationServiceTestLogger(Assertion)); + services.AddAuthorization(options => options.AddPolicy("Log", p => + { + p.Requirements.Add(new LogRequirement()); + p.Requirements.Add(new LogRequirement()); + })); + }); + + var user = new ClaimsPrincipal(); + + // Act + var result = await authorizationService.AuthorizeAsync(user, "Log"); + + // Assert + } } } diff --git a/src/Security/Authorization/test/DenyAnonymousAuthorizationRequirementTests.cs b/src/Security/Authorization/test/DenyAnonymousAuthorizationRequirementTests.cs new file mode 100644 index 0000000000..3d35634758 --- /dev/null +++ b/src/Security/Authorization/test/DenyAnonymousAuthorizationRequirementTests.cs @@ -0,0 +1,32 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Text; +using Microsoft.AspNetCore.Authorization.Infrastructure; +using Xunit; + +namespace Microsoft.AspNetCore.Authorization.Test +{ + public class DenyAnonymousAuthorizationRequirementTests + { + private DenyAnonymousAuthorizationRequirement CreateRequirement() + { + return new DenyAnonymousAuthorizationRequirement(); + } + + [Fact] + public void ToString_ShouldReturnFormatValue() + { + // Arrange + var requirement = CreateRequirement(); + + // Act + var formattedValue = requirement.ToString(); + + // Assert + Assert.Equal("DenyAnonymousAuthorizationRequirement:Requires an authenticated user.", formattedValue); + } + } +} diff --git a/src/Security/Authorization/test/NameAuthorizationRequirementTests.cs b/src/Security/Authorization/test/NameAuthorizationRequirementTests.cs new file mode 100644 index 0000000000..73a9a98144 --- /dev/null +++ b/src/Security/Authorization/test/NameAuthorizationRequirementTests.cs @@ -0,0 +1,32 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Text; +using Microsoft.AspNetCore.Authorization.Infrastructure; +using Xunit; + +namespace Microsoft.AspNetCore.Authorization.Test +{ + public class NameAuthorizationRequirementTests + { + public NameAuthorizationRequirement CreateRequirement(string requiredName) + { + return new NameAuthorizationRequirement(requiredName); + } + + [Fact] + public void ToString_ShouldReturnFormatValue() + { + // Arrange + var requirement = CreateRequirement("Custom"); + + // Act + var formattedValue = requirement.ToString(); + + // Assert + Assert.Equal("NameAuthorizationRequirement:Requires a user identity with Name equal to Custom", formattedValue); + } + } +} diff --git a/src/Security/Authorization/test/OperationAuthorizationRequirementTests.cs b/src/Security/Authorization/test/OperationAuthorizationRequirementTests.cs new file mode 100644 index 0000000000..623d01c98e --- /dev/null +++ b/src/Security/Authorization/test/OperationAuthorizationRequirementTests.cs @@ -0,0 +1,35 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Text; +using Microsoft.AspNetCore.Authorization.Infrastructure; +using Xunit; + +namespace Microsoft.AspNetCore.Authorization.Test +{ + public class OperationAuthorizationRequirementTests + { + private OperationAuthorizationRequirement CreateRequirement(string name) + { + return new OperationAuthorizationRequirement() + { + Name = name + }; + } + + [Fact] + public void ToString_ShouldReturnFormatValue() + { + // Arrange + var requirement = CreateRequirement("Custom"); + + // Act + var formattedValue = requirement.ToString(); + + // Assert + Assert.Equal("OperationAuthorizationRequirement:Name=Custom", formattedValue); + } + } +} diff --git a/src/Security/Authorization/test/RolesAuthorizationRequirementTests.cs b/src/Security/Authorization/test/RolesAuthorizationRequirementTests.cs new file mode 100644 index 0000000000..1b8d5b09c7 --- /dev/null +++ b/src/Security/Authorization/test/RolesAuthorizationRequirementTests.cs @@ -0,0 +1,45 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Text; +using Microsoft.AspNetCore.Authorization.Infrastructure; +using Xunit; + +namespace Microsoft.AspNetCore.Authorization.Test +{ + public class RolesAuthorizationRequirementTests + { + private RolesAuthorizationRequirement CreateRequirement(params string[] allowedRoles) + { + return new RolesAuthorizationRequirement(allowedRoles); + } + + [Fact] + public void ToString_ShouldReturnSplitByBarWhenHasTwoAllowedRoles() + { + // Arrange + var requirement = CreateRequirement("Custom1", "Custom2"); + + // Act + var formattedValue = requirement.ToString(); + + // Assert + Assert.Equal("RolesAuthorizationRequirement:User.IsInRole must be true for one of the following roles: (Custom1|Custom2)", formattedValue); + } + + [Fact] + public void ToString_ShouldReturnUnSplitStringWhenOnlyOneAllowedRoles() + { + // Arrange + var requirement = CreateRequirement("Custom1"); + + // Act + var formattedValue = requirement.ToString(); + + // Assert + Assert.Equal("RolesAuthorizationRequirement:User.IsInRole must be true for one of the following roles: (Custom1)",formattedValue); + } + } +} diff --git a/src/Security/CookiePolicy/ref/Microsoft.AspNetCore.CookiePolicy.netcoreapp.cs b/src/Security/CookiePolicy/ref/Microsoft.AspNetCore.CookiePolicy.netcoreapp.cs index f2e231debc..bd378bd871 100644 --- a/src/Security/CookiePolicy/ref/Microsoft.AspNetCore.CookiePolicy.netcoreapp.cs +++ b/src/Security/CookiePolicy/ref/Microsoft.AspNetCore.CookiePolicy.netcoreapp.cs @@ -11,13 +11,13 @@ namespace Microsoft.AspNetCore.Builder public partial class CookiePolicyOptions { public CookiePolicyOptions() { } - public System.Func CheckConsentNeeded { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public Microsoft.AspNetCore.Http.CookieBuilder ConsentCookie { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public Microsoft.AspNetCore.CookiePolicy.HttpOnlyPolicy HttpOnly { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public Microsoft.AspNetCore.Http.SameSiteMode MinimumSameSitePolicy { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public System.Action OnAppendCookie { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public System.Action OnDeleteCookie { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public Microsoft.AspNetCore.Http.CookieSecurePolicy Secure { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public System.Func CheckConsentNeeded { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public Microsoft.AspNetCore.Http.CookieBuilder ConsentCookie { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public Microsoft.AspNetCore.CookiePolicy.HttpOnlyPolicy HttpOnly { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public Microsoft.AspNetCore.Http.SameSiteMode MinimumSameSitePolicy { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public System.Action OnAppendCookie { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public System.Action OnDeleteCookie { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public Microsoft.AspNetCore.Http.CookieSecurePolicy Secure { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } } namespace Microsoft.AspNetCore.CookiePolicy @@ -25,30 +25,30 @@ namespace Microsoft.AspNetCore.CookiePolicy public partial class AppendCookieContext { public AppendCookieContext(Microsoft.AspNetCore.Http.HttpContext context, Microsoft.AspNetCore.Http.CookieOptions options, string name, string value) { } - public Microsoft.AspNetCore.Http.HttpContext Context { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public string CookieName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public Microsoft.AspNetCore.Http.CookieOptions CookieOptions { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public string CookieValue { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public bool HasConsent { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public bool IsConsentNeeded { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public bool IssueCookie { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.AspNetCore.Http.HttpContext Context { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public string CookieName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public Microsoft.AspNetCore.Http.CookieOptions CookieOptions { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public string CookieValue { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public bool HasConsent { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public bool IsConsentNeeded { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public bool IssueCookie { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } public partial class CookiePolicyMiddleware { public CookiePolicyMiddleware(Microsoft.AspNetCore.Http.RequestDelegate next, Microsoft.Extensions.Options.IOptions options) { } public CookiePolicyMiddleware(Microsoft.AspNetCore.Http.RequestDelegate next, Microsoft.Extensions.Options.IOptions options, Microsoft.Extensions.Logging.ILoggerFactory factory) { } - public Microsoft.AspNetCore.Builder.CookiePolicyOptions Options { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.AspNetCore.Builder.CookiePolicyOptions Options { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public System.Threading.Tasks.Task Invoke(Microsoft.AspNetCore.Http.HttpContext context) { throw null; } } public partial class DeleteCookieContext { public DeleteCookieContext(Microsoft.AspNetCore.Http.HttpContext context, Microsoft.AspNetCore.Http.CookieOptions options, string name) { } - public Microsoft.AspNetCore.Http.HttpContext Context { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public string CookieName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public Microsoft.AspNetCore.Http.CookieOptions CookieOptions { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public bool HasConsent { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public bool IsConsentNeeded { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public bool IssueCookie { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.AspNetCore.Http.HttpContext Context { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public string CookieName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public Microsoft.AspNetCore.Http.CookieOptions CookieOptions { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public bool HasConsent { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public bool IsConsentNeeded { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public bool IssueCookie { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } public enum HttpOnlyPolicy { diff --git a/src/Security/CookiePolicy/src/CookiePolicyOptions.cs b/src/Security/CookiePolicy/src/CookiePolicyOptions.cs index 098bd33483..56e5998808 100644 --- a/src/Security/CookiePolicy/src/CookiePolicyOptions.cs +++ b/src/Security/CookiePolicy/src/CookiePolicyOptions.cs @@ -12,22 +12,10 @@ namespace Microsoft.AspNetCore.Builder /// public class CookiePolicyOptions { - // True (old): https://tools.ietf.org/html/draft-west-first-party-cookies-07#section-3.1 - // False (new): https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-03#section-4.1.1 - internal static bool SuppressSameSiteNone; - - static CookiePolicyOptions() - { - if (AppContext.TryGetSwitch("Microsoft.AspNetCore.SuppressSameSiteNone", out var enabled)) - { - SuppressSameSiteNone = enabled; - } - } - /// /// Affects the cookie's same site attribute. /// - public SameSiteMode MinimumSameSitePolicy { get; set; } = SuppressSameSiteNone ? SameSiteMode.None : SameSiteMode.Unspecified; + public SameSiteMode MinimumSameSitePolicy { get; set; } = SameSiteMode.Unspecified; /// /// Affects whether cookies must be HttpOnly. diff --git a/src/Security/CookiePolicy/src/ResponseCookiesWrapper.cs b/src/Security/CookiePolicy/src/ResponseCookiesWrapper.cs index 2d03c65c3d..2c1b46264e 100644 --- a/src/Security/CookiePolicy/src/ResponseCookiesWrapper.cs +++ b/src/Security/CookiePolicy/src/ResponseCookiesWrapper.cs @@ -115,8 +115,7 @@ namespace Microsoft.AspNetCore.CookiePolicy private bool CheckPolicyRequired() { return !CanTrack - || (CookiePolicyOptions.SuppressSameSiteNone && Options.MinimumSameSitePolicy != SameSiteMode.None) - || (!CookiePolicyOptions.SuppressSameSiteNone && Options.MinimumSameSitePolicy != SameSiteMode.Unspecified) + || Options.MinimumSameSitePolicy != SameSiteMode.Unspecified || Options.HttpOnly != HttpOnlyPolicy.None || Options.Secure != CookieSecurePolicy.None; } diff --git a/src/Security/README.md b/src/Security/README.md index 0ba28c1e97..5ed702c4f2 100644 --- a/src/Security/README.md +++ b/src/Security/README.md @@ -3,9 +3,9 @@ ASP.NET Core Security Contains the security and authorization middlewares for ASP.NET Core. -A list of community projects related to authentication and security for ASP.NET Core are listed in the [documentation](https://docs.microsoft.com/en-us/aspnet/core/security/authentication/community). +A list of community projects related to authentication and security for ASP.NET Core are listed in the [documentation](https://docs.microsoft.com/aspnet/core/security/authentication/community). -See the [ASP.NET Core security documentation](https://docs.microsoft.com/en-us/aspnet/core/security/). +See the [ASP.NET Core security documentation](https://docs.microsoft.com/aspnet/core/security/). ### Notes diff --git a/src/Security/Security.sln b/src/Security/Security.sln index 93250dc8a4..32566c7143 100644 --- a/src/Security/Security.sln +++ b/src/Security/Security.sln @@ -164,8 +164,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Server EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Server.HttpSys", "..\Servers\HttpSys\src\Microsoft.AspNetCore.Server.HttpSys.csproj", "{D6C3C4A9-197B-47B5-8B72-35047CBC4F22}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Net.Http.Headers", "..\Http\Headers\src\Microsoft.Net.Http.Headers.csproj", "{4BB8D7D7-E111-4A86-B6E5-C1201E0DA8CE}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU diff --git a/src/Security/benchmarks/Microsoft.AspNetCore.Security.Performance/Microsoft.AspNetCore.Security.Performance.csproj b/src/Security/benchmarks/Microsoft.AspNetCore.Security.Performance/Microsoft.AspNetCore.Security.Performance.csproj index c6bb271536..65871e5a78 100644 --- a/src/Security/benchmarks/Microsoft.AspNetCore.Security.Performance/Microsoft.AspNetCore.Security.Performance.csproj +++ b/src/Security/benchmarks/Microsoft.AspNetCore.Security.Performance/Microsoft.AspNetCore.Security.Performance.csproj @@ -12,10 +12,11 @@ - + + diff --git a/src/Security/build.sh b/src/Security/build.sh new file mode 100755 index 0000000000..7046bb98a0 --- /dev/null +++ b/src/Security/build.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +set -euo pipefail + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +repo_root="$DIR/../.." +"$repo_root/build.sh" --projects "$DIR/**/*.*proj" "$@" diff --git a/src/Security/samples/Identity.ExternalClaims/Pages/Account/Manage/EnableAuthenticator.cshtml.cs b/src/Security/samples/Identity.ExternalClaims/Pages/Account/Manage/EnableAuthenticator.cshtml.cs index 8d0e937b34..a29b2bbc92 100644 --- a/src/Security/samples/Identity.ExternalClaims/Pages/Account/Manage/EnableAuthenticator.cshtml.cs +++ b/src/Security/samples/Identity.ExternalClaims/Pages/Account/Manage/EnableAuthenticator.cshtml.cs @@ -80,7 +80,7 @@ namespace Identity.ExternalClaims.Pages.Account.Manage return Page(); } - // Strip spaces and hypens + // Strip spaces and hyphens var verificationCode = Input.Code.Replace(" ", string.Empty).Replace("-", string.Empty); var is2faTokenValid = await _userManager.VerifyTwoFactorTokenAsync( diff --git a/src/Security/samples/Identity.ExternalClaims/README.md b/src/Security/samples/Identity.ExternalClaims/README.md index 7a9141075d..70205c0367 100644 --- a/src/Security/samples/Identity.ExternalClaims/README.md +++ b/src/Security/samples/Identity.ExternalClaims/README.md @@ -4,7 +4,7 @@ AuthSamples.Identity.ExternalClaims Sample demonstrating copying over static and dynamic external claims from Google authentication during login: Steps: -1. Configure a google OAuth2 project. See https://docs.microsoft.com/en-us/aspnet/core/security/authentication/social/google-logins?tabs=aspnetcore2x for basic setup using google logins. +1. Configure a google OAuth2 project. See https://docs.microsoft.com/aspnet/core/security/authentication/social/google-logins for basic setup using google logins. 2. Update Startup.cs AddGoogle()'s options with ClientId and ClientSecret for your google app. 3. Run the app and click on the MyClaims tab, this should trigger a redirect to login. 4. Login via the Google button, this should redirect you to google. diff --git a/src/Security/test/AuthSamples.FunctionalTests/AuthSamples.FunctionalTests.csproj b/src/Security/test/AuthSamples.FunctionalTests/AuthSamples.FunctionalTests.csproj index 9808064e24..1753f9f548 100644 --- a/src/Security/test/AuthSamples.FunctionalTests/AuthSamples.FunctionalTests.csproj +++ b/src/Security/test/AuthSamples.FunctionalTests/AuthSamples.FunctionalTests.csproj @@ -4,7 +4,7 @@ $(DefaultNetCoreTargetFramework) false AnyCPU - + true diff --git a/src/Security/test/AuthSamples.FunctionalTests/IdentityExternalClaimsTests.cs b/src/Security/test/AuthSamples.FunctionalTests/IdentityExternalClaimsTests.cs index 955a911486..e216b7d19b 100644 --- a/src/Security/test/AuthSamples.FunctionalTests/IdentityExternalClaimsTests.cs +++ b/src/Security/test/AuthSamples.FunctionalTests/IdentityExternalClaimsTests.cs @@ -29,7 +29,7 @@ namespace AuthSamples.FunctionalTests Assert.Equal(HttpStatusCode.OK, response.StatusCode); } - [Fact(Skip = "https://github.com/aspnet/AspNetCore/issues/8387")] + [Fact(Skip = "https://github.com/dotnet/aspnetcore/issues/8387")] public async Task MyClaimsRedirectsToLoginPageWhenNotLoggedIn() { // Arrange & Act diff --git a/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.csproj b/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.csproj index c6a77eb8ef..262c012733 100644 --- a/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.csproj +++ b/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.csproj @@ -2,24 +2,22 @@ netstandard2.0;netstandard2.1;$(DefaultNetCoreTargetFramework) + $(DefaultNetCoreTargetFramework) - - - diff --git a/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netcoreapp.cs b/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netcoreapp.cs index dbd0a70349..75ed809b6d 100644 --- a/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netcoreapp.cs +++ b/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netcoreapp.cs @@ -8,6 +8,19 @@ namespace Microsoft.AspNetCore.Connections public AddressInUseException(string message) { } public AddressInUseException(string message, System.Exception inner) { } } + public abstract partial class BaseConnectionContext : System.IAsyncDisposable + { + protected BaseConnectionContext() { } + public virtual System.Threading.CancellationToken ConnectionClosed { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public abstract string ConnectionId { get; set; } + public abstract Microsoft.AspNetCore.Http.Features.IFeatureCollection Features { get; } + public abstract System.Collections.Generic.IDictionary Items { get; set; } + public virtual System.Net.EndPoint LocalEndPoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public virtual System.Net.EndPoint RemoteEndPoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public abstract void Abort(); + public abstract void Abort(Microsoft.AspNetCore.Connections.ConnectionAbortedException abortReason); + public virtual System.Threading.Tasks.ValueTask DisposeAsync() { throw null; } + } public partial class ConnectionAbortedException : System.OperationCanceledException { public ConnectionAbortedException() { } @@ -17,7 +30,7 @@ namespace Microsoft.AspNetCore.Connections public partial class ConnectionBuilder : Microsoft.AspNetCore.Connections.IConnectionBuilder { public ConnectionBuilder(System.IServiceProvider applicationServices) { } - public System.IServiceProvider ApplicationServices { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public System.IServiceProvider ApplicationServices { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } public Microsoft.AspNetCore.Connections.ConnectionDelegate Build() { throw null; } public Microsoft.AspNetCore.Connections.IConnectionBuilder Use(System.Func middleware) { throw null; } } @@ -27,19 +40,12 @@ namespace Microsoft.AspNetCore.Connections public static Microsoft.AspNetCore.Connections.IConnectionBuilder Use(this Microsoft.AspNetCore.Connections.IConnectionBuilder connectionBuilder, System.Func, System.Threading.Tasks.Task> middleware) { throw null; } public static Microsoft.AspNetCore.Connections.IConnectionBuilder UseConnectionHandler(this Microsoft.AspNetCore.Connections.IConnectionBuilder connectionBuilder) where TConnectionHandler : Microsoft.AspNetCore.Connections.ConnectionHandler { throw null; } } - public abstract partial class ConnectionContext : System.IAsyncDisposable + public abstract partial class ConnectionContext : Microsoft.AspNetCore.Connections.BaseConnectionContext, System.IAsyncDisposable { protected ConnectionContext() { } - public virtual System.Threading.CancellationToken ConnectionClosed { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public abstract string ConnectionId { get; set; } - public abstract Microsoft.AspNetCore.Http.Features.IFeatureCollection Features { get; } - public abstract System.Collections.Generic.IDictionary Items { get; set; } - public virtual System.Net.EndPoint LocalEndPoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public virtual System.Net.EndPoint RemoteEndPoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public abstract System.IO.Pipelines.IDuplexPipe Transport { get; set; } - public virtual void Abort() { } - public virtual void Abort(Microsoft.AspNetCore.Connections.ConnectionAbortedException abortReason) { } - public virtual System.Threading.Tasks.ValueTask DisposeAsync() { throw null; } + public override void Abort() { } + public override void Abort(Microsoft.AspNetCore.Connections.ConnectionAbortedException abortReason) { } } public delegate System.Threading.Tasks.Task ConnectionDelegate(Microsoft.AspNetCore.Connections.ConnectionContext connection); public abstract partial class ConnectionHandler @@ -51,7 +57,7 @@ namespace Microsoft.AspNetCore.Connections { public ConnectionItems() { } public ConnectionItems(System.Collections.Generic.IDictionary items) { } - public System.Collections.Generic.IDictionary Items { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public System.Collections.Generic.IDictionary Items { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } int System.Collections.Generic.ICollection>.Count { get { throw null; } } bool System.Collections.Generic.ICollection>.IsReadOnly { get { throw null; } } object System.Collections.Generic.IDictionary.this[object key] { get { throw null; } set { } } @@ -79,23 +85,23 @@ namespace Microsoft.AspNetCore.Connections public DefaultConnectionContext() { } public DefaultConnectionContext(string id) { } public DefaultConnectionContext(string id, System.IO.Pipelines.IDuplexPipe transport, System.IO.Pipelines.IDuplexPipe application) { } - public System.IO.Pipelines.IDuplexPipe Application { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public override System.Threading.CancellationToken ConnectionClosed { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public override string ConnectionId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public override Microsoft.AspNetCore.Http.Features.IFeatureCollection Features { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public override System.Collections.Generic.IDictionary Items { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public override System.Net.EndPoint LocalEndPoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public override System.Net.EndPoint RemoteEndPoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public override System.IO.Pipelines.IDuplexPipe Transport { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public System.Security.Claims.ClaimsPrincipal User { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public System.IO.Pipelines.IDuplexPipe Application { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public override System.Threading.CancellationToken ConnectionClosed { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public override string ConnectionId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public override Microsoft.AspNetCore.Http.Features.IFeatureCollection Features { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public override System.Collections.Generic.IDictionary Items { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public override System.Net.EndPoint LocalEndPoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public override System.Net.EndPoint RemoteEndPoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public override System.IO.Pipelines.IDuplexPipe Transport { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public System.Security.Claims.ClaimsPrincipal User { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public override void Abort(Microsoft.AspNetCore.Connections.ConnectionAbortedException abortReason) { } public override System.Threading.Tasks.ValueTask DisposeAsync() { throw null; } } public partial class FileHandleEndPoint : System.Net.EndPoint { public FileHandleEndPoint(ulong fileHandle, Microsoft.AspNetCore.Connections.FileHandleType fileHandleType) { } - public ulong FileHandle { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public Microsoft.AspNetCore.Connections.FileHandleType FileHandleType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public ulong FileHandle { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public Microsoft.AspNetCore.Connections.FileHandleType FileHandleType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } } public enum FileHandleType { @@ -123,6 +129,40 @@ namespace Microsoft.AspNetCore.Connections { System.Threading.Tasks.ValueTask BindAsync(System.Net.EndPoint endpoint, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); } + public partial interface IMultiplexedConnectionBuilder + { + System.IServiceProvider ApplicationServices { get; } + Microsoft.AspNetCore.Connections.MultiplexedConnectionDelegate Build(); + Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder Use(System.Func middleware); + } + public partial interface IMultiplexedConnectionFactory + { + System.Threading.Tasks.ValueTask ConnectAsync(System.Net.EndPoint endpoint, Microsoft.AspNetCore.Http.Features.IFeatureCollection features = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + } + public partial interface IMultiplexedConnectionListener : System.IAsyncDisposable + { + System.Net.EndPoint EndPoint { get; } + System.Threading.Tasks.ValueTask AcceptAsync(Microsoft.AspNetCore.Http.Features.IFeatureCollection features = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + System.Threading.Tasks.ValueTask UnbindAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + } + public partial interface IMultiplexedConnectionListenerFactory + { + System.Threading.Tasks.ValueTask BindAsync(System.Net.EndPoint endpoint, Microsoft.AspNetCore.Http.Features.IFeatureCollection features = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + } + public partial class MultiplexedConnectionBuilder : Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder + { + public MultiplexedConnectionBuilder(System.IServiceProvider applicationServices) { } + public System.IServiceProvider ApplicationServices { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public Microsoft.AspNetCore.Connections.MultiplexedConnectionDelegate Build() { throw null; } + public Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder Use(System.Func middleware) { throw null; } + } + public abstract partial class MultiplexedConnectionContext : Microsoft.AspNetCore.Connections.BaseConnectionContext, System.IAsyncDisposable + { + protected MultiplexedConnectionContext() { } + public abstract System.Threading.Tasks.ValueTask AcceptAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + public abstract System.Threading.Tasks.ValueTask ConnectAsync(Microsoft.AspNetCore.Http.Features.IFeatureCollection features = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + } + public delegate System.Threading.Tasks.Task MultiplexedConnectionDelegate(Microsoft.AspNetCore.Connections.MultiplexedConnectionContext connection); [System.FlagsAttribute] public enum TransferFormat { @@ -132,7 +172,8 @@ namespace Microsoft.AspNetCore.Connections public partial class UriEndPoint : System.Net.EndPoint { public UriEndPoint(System.Uri uri) { } - public System.Uri Uri { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public System.Uri Uri { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public override string ToString() { throw null; } } } namespace Microsoft.AspNetCore.Connections.Features @@ -184,6 +225,19 @@ namespace Microsoft.AspNetCore.Connections.Features { System.Buffers.MemoryPool MemoryPool { get; } } + public partial interface IProtocolErrorCodeFeature + { + long Error { get; set; } + } + public partial interface IStreamDirectionFeature + { + bool CanRead { get; } + bool CanWrite { get; } + } + public partial interface IStreamIdFeature + { + long StreamId { get; } + } public partial interface ITlsHandshakeFeature { System.Security.Authentication.CipherAlgorithmType CipherAlgorithm { get; } diff --git a/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netstandard2.0.cs b/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netstandard2.0.cs index dbd0a70349..75ed809b6d 100644 --- a/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netstandard2.0.cs +++ b/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netstandard2.0.cs @@ -8,6 +8,19 @@ namespace Microsoft.AspNetCore.Connections public AddressInUseException(string message) { } public AddressInUseException(string message, System.Exception inner) { } } + public abstract partial class BaseConnectionContext : System.IAsyncDisposable + { + protected BaseConnectionContext() { } + public virtual System.Threading.CancellationToken ConnectionClosed { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public abstract string ConnectionId { get; set; } + public abstract Microsoft.AspNetCore.Http.Features.IFeatureCollection Features { get; } + public abstract System.Collections.Generic.IDictionary Items { get; set; } + public virtual System.Net.EndPoint LocalEndPoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public virtual System.Net.EndPoint RemoteEndPoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public abstract void Abort(); + public abstract void Abort(Microsoft.AspNetCore.Connections.ConnectionAbortedException abortReason); + public virtual System.Threading.Tasks.ValueTask DisposeAsync() { throw null; } + } public partial class ConnectionAbortedException : System.OperationCanceledException { public ConnectionAbortedException() { } @@ -17,7 +30,7 @@ namespace Microsoft.AspNetCore.Connections public partial class ConnectionBuilder : Microsoft.AspNetCore.Connections.IConnectionBuilder { public ConnectionBuilder(System.IServiceProvider applicationServices) { } - public System.IServiceProvider ApplicationServices { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public System.IServiceProvider ApplicationServices { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } public Microsoft.AspNetCore.Connections.ConnectionDelegate Build() { throw null; } public Microsoft.AspNetCore.Connections.IConnectionBuilder Use(System.Func middleware) { throw null; } } @@ -27,19 +40,12 @@ namespace Microsoft.AspNetCore.Connections public static Microsoft.AspNetCore.Connections.IConnectionBuilder Use(this Microsoft.AspNetCore.Connections.IConnectionBuilder connectionBuilder, System.Func, System.Threading.Tasks.Task> middleware) { throw null; } public static Microsoft.AspNetCore.Connections.IConnectionBuilder UseConnectionHandler(this Microsoft.AspNetCore.Connections.IConnectionBuilder connectionBuilder) where TConnectionHandler : Microsoft.AspNetCore.Connections.ConnectionHandler { throw null; } } - public abstract partial class ConnectionContext : System.IAsyncDisposable + public abstract partial class ConnectionContext : Microsoft.AspNetCore.Connections.BaseConnectionContext, System.IAsyncDisposable { protected ConnectionContext() { } - public virtual System.Threading.CancellationToken ConnectionClosed { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public abstract string ConnectionId { get; set; } - public abstract Microsoft.AspNetCore.Http.Features.IFeatureCollection Features { get; } - public abstract System.Collections.Generic.IDictionary Items { get; set; } - public virtual System.Net.EndPoint LocalEndPoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public virtual System.Net.EndPoint RemoteEndPoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public abstract System.IO.Pipelines.IDuplexPipe Transport { get; set; } - public virtual void Abort() { } - public virtual void Abort(Microsoft.AspNetCore.Connections.ConnectionAbortedException abortReason) { } - public virtual System.Threading.Tasks.ValueTask DisposeAsync() { throw null; } + public override void Abort() { } + public override void Abort(Microsoft.AspNetCore.Connections.ConnectionAbortedException abortReason) { } } public delegate System.Threading.Tasks.Task ConnectionDelegate(Microsoft.AspNetCore.Connections.ConnectionContext connection); public abstract partial class ConnectionHandler @@ -51,7 +57,7 @@ namespace Microsoft.AspNetCore.Connections { public ConnectionItems() { } public ConnectionItems(System.Collections.Generic.IDictionary items) { } - public System.Collections.Generic.IDictionary Items { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public System.Collections.Generic.IDictionary Items { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } int System.Collections.Generic.ICollection>.Count { get { throw null; } } bool System.Collections.Generic.ICollection>.IsReadOnly { get { throw null; } } object System.Collections.Generic.IDictionary.this[object key] { get { throw null; } set { } } @@ -79,23 +85,23 @@ namespace Microsoft.AspNetCore.Connections public DefaultConnectionContext() { } public DefaultConnectionContext(string id) { } public DefaultConnectionContext(string id, System.IO.Pipelines.IDuplexPipe transport, System.IO.Pipelines.IDuplexPipe application) { } - public System.IO.Pipelines.IDuplexPipe Application { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public override System.Threading.CancellationToken ConnectionClosed { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public override string ConnectionId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public override Microsoft.AspNetCore.Http.Features.IFeatureCollection Features { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public override System.Collections.Generic.IDictionary Items { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public override System.Net.EndPoint LocalEndPoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public override System.Net.EndPoint RemoteEndPoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public override System.IO.Pipelines.IDuplexPipe Transport { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public System.Security.Claims.ClaimsPrincipal User { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public System.IO.Pipelines.IDuplexPipe Application { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public override System.Threading.CancellationToken ConnectionClosed { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public override string ConnectionId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public override Microsoft.AspNetCore.Http.Features.IFeatureCollection Features { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public override System.Collections.Generic.IDictionary Items { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public override System.Net.EndPoint LocalEndPoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public override System.Net.EndPoint RemoteEndPoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public override System.IO.Pipelines.IDuplexPipe Transport { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public System.Security.Claims.ClaimsPrincipal User { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public override void Abort(Microsoft.AspNetCore.Connections.ConnectionAbortedException abortReason) { } public override System.Threading.Tasks.ValueTask DisposeAsync() { throw null; } } public partial class FileHandleEndPoint : System.Net.EndPoint { public FileHandleEndPoint(ulong fileHandle, Microsoft.AspNetCore.Connections.FileHandleType fileHandleType) { } - public ulong FileHandle { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public Microsoft.AspNetCore.Connections.FileHandleType FileHandleType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public ulong FileHandle { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public Microsoft.AspNetCore.Connections.FileHandleType FileHandleType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } } public enum FileHandleType { @@ -123,6 +129,40 @@ namespace Microsoft.AspNetCore.Connections { System.Threading.Tasks.ValueTask BindAsync(System.Net.EndPoint endpoint, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); } + public partial interface IMultiplexedConnectionBuilder + { + System.IServiceProvider ApplicationServices { get; } + Microsoft.AspNetCore.Connections.MultiplexedConnectionDelegate Build(); + Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder Use(System.Func middleware); + } + public partial interface IMultiplexedConnectionFactory + { + System.Threading.Tasks.ValueTask ConnectAsync(System.Net.EndPoint endpoint, Microsoft.AspNetCore.Http.Features.IFeatureCollection features = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + } + public partial interface IMultiplexedConnectionListener : System.IAsyncDisposable + { + System.Net.EndPoint EndPoint { get; } + System.Threading.Tasks.ValueTask AcceptAsync(Microsoft.AspNetCore.Http.Features.IFeatureCollection features = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + System.Threading.Tasks.ValueTask UnbindAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + } + public partial interface IMultiplexedConnectionListenerFactory + { + System.Threading.Tasks.ValueTask BindAsync(System.Net.EndPoint endpoint, Microsoft.AspNetCore.Http.Features.IFeatureCollection features = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + } + public partial class MultiplexedConnectionBuilder : Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder + { + public MultiplexedConnectionBuilder(System.IServiceProvider applicationServices) { } + public System.IServiceProvider ApplicationServices { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public Microsoft.AspNetCore.Connections.MultiplexedConnectionDelegate Build() { throw null; } + public Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder Use(System.Func middleware) { throw null; } + } + public abstract partial class MultiplexedConnectionContext : Microsoft.AspNetCore.Connections.BaseConnectionContext, System.IAsyncDisposable + { + protected MultiplexedConnectionContext() { } + public abstract System.Threading.Tasks.ValueTask AcceptAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + public abstract System.Threading.Tasks.ValueTask ConnectAsync(Microsoft.AspNetCore.Http.Features.IFeatureCollection features = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + } + public delegate System.Threading.Tasks.Task MultiplexedConnectionDelegate(Microsoft.AspNetCore.Connections.MultiplexedConnectionContext connection); [System.FlagsAttribute] public enum TransferFormat { @@ -132,7 +172,8 @@ namespace Microsoft.AspNetCore.Connections public partial class UriEndPoint : System.Net.EndPoint { public UriEndPoint(System.Uri uri) { } - public System.Uri Uri { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public System.Uri Uri { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public override string ToString() { throw null; } } } namespace Microsoft.AspNetCore.Connections.Features @@ -184,6 +225,19 @@ namespace Microsoft.AspNetCore.Connections.Features { System.Buffers.MemoryPool MemoryPool { get; } } + public partial interface IProtocolErrorCodeFeature + { + long Error { get; set; } + } + public partial interface IStreamDirectionFeature + { + bool CanRead { get; } + bool CanWrite { get; } + } + public partial interface IStreamIdFeature + { + long StreamId { get; } + } public partial interface ITlsHandshakeFeature { System.Security.Authentication.CipherAlgorithmType CipherAlgorithm { get; } diff --git a/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netstandard2.1.cs b/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netstandard2.1.cs index dbd0a70349..75ed809b6d 100644 --- a/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netstandard2.1.cs +++ b/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netstandard2.1.cs @@ -8,6 +8,19 @@ namespace Microsoft.AspNetCore.Connections public AddressInUseException(string message) { } public AddressInUseException(string message, System.Exception inner) { } } + public abstract partial class BaseConnectionContext : System.IAsyncDisposable + { + protected BaseConnectionContext() { } + public virtual System.Threading.CancellationToken ConnectionClosed { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public abstract string ConnectionId { get; set; } + public abstract Microsoft.AspNetCore.Http.Features.IFeatureCollection Features { get; } + public abstract System.Collections.Generic.IDictionary Items { get; set; } + public virtual System.Net.EndPoint LocalEndPoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public virtual System.Net.EndPoint RemoteEndPoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public abstract void Abort(); + public abstract void Abort(Microsoft.AspNetCore.Connections.ConnectionAbortedException abortReason); + public virtual System.Threading.Tasks.ValueTask DisposeAsync() { throw null; } + } public partial class ConnectionAbortedException : System.OperationCanceledException { public ConnectionAbortedException() { } @@ -17,7 +30,7 @@ namespace Microsoft.AspNetCore.Connections public partial class ConnectionBuilder : Microsoft.AspNetCore.Connections.IConnectionBuilder { public ConnectionBuilder(System.IServiceProvider applicationServices) { } - public System.IServiceProvider ApplicationServices { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public System.IServiceProvider ApplicationServices { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } public Microsoft.AspNetCore.Connections.ConnectionDelegate Build() { throw null; } public Microsoft.AspNetCore.Connections.IConnectionBuilder Use(System.Func middleware) { throw null; } } @@ -27,19 +40,12 @@ namespace Microsoft.AspNetCore.Connections public static Microsoft.AspNetCore.Connections.IConnectionBuilder Use(this Microsoft.AspNetCore.Connections.IConnectionBuilder connectionBuilder, System.Func, System.Threading.Tasks.Task> middleware) { throw null; } public static Microsoft.AspNetCore.Connections.IConnectionBuilder UseConnectionHandler(this Microsoft.AspNetCore.Connections.IConnectionBuilder connectionBuilder) where TConnectionHandler : Microsoft.AspNetCore.Connections.ConnectionHandler { throw null; } } - public abstract partial class ConnectionContext : System.IAsyncDisposable + public abstract partial class ConnectionContext : Microsoft.AspNetCore.Connections.BaseConnectionContext, System.IAsyncDisposable { protected ConnectionContext() { } - public virtual System.Threading.CancellationToken ConnectionClosed { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public abstract string ConnectionId { get; set; } - public abstract Microsoft.AspNetCore.Http.Features.IFeatureCollection Features { get; } - public abstract System.Collections.Generic.IDictionary Items { get; set; } - public virtual System.Net.EndPoint LocalEndPoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public virtual System.Net.EndPoint RemoteEndPoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public abstract System.IO.Pipelines.IDuplexPipe Transport { get; set; } - public virtual void Abort() { } - public virtual void Abort(Microsoft.AspNetCore.Connections.ConnectionAbortedException abortReason) { } - public virtual System.Threading.Tasks.ValueTask DisposeAsync() { throw null; } + public override void Abort() { } + public override void Abort(Microsoft.AspNetCore.Connections.ConnectionAbortedException abortReason) { } } public delegate System.Threading.Tasks.Task ConnectionDelegate(Microsoft.AspNetCore.Connections.ConnectionContext connection); public abstract partial class ConnectionHandler @@ -51,7 +57,7 @@ namespace Microsoft.AspNetCore.Connections { public ConnectionItems() { } public ConnectionItems(System.Collections.Generic.IDictionary items) { } - public System.Collections.Generic.IDictionary Items { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public System.Collections.Generic.IDictionary Items { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } int System.Collections.Generic.ICollection>.Count { get { throw null; } } bool System.Collections.Generic.ICollection>.IsReadOnly { get { throw null; } } object System.Collections.Generic.IDictionary.this[object key] { get { throw null; } set { } } @@ -79,23 +85,23 @@ namespace Microsoft.AspNetCore.Connections public DefaultConnectionContext() { } public DefaultConnectionContext(string id) { } public DefaultConnectionContext(string id, System.IO.Pipelines.IDuplexPipe transport, System.IO.Pipelines.IDuplexPipe application) { } - public System.IO.Pipelines.IDuplexPipe Application { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public override System.Threading.CancellationToken ConnectionClosed { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public override string ConnectionId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public override Microsoft.AspNetCore.Http.Features.IFeatureCollection Features { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public override System.Collections.Generic.IDictionary Items { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public override System.Net.EndPoint LocalEndPoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public override System.Net.EndPoint RemoteEndPoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public override System.IO.Pipelines.IDuplexPipe Transport { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public System.Security.Claims.ClaimsPrincipal User { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public System.IO.Pipelines.IDuplexPipe Application { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public override System.Threading.CancellationToken ConnectionClosed { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public override string ConnectionId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public override Microsoft.AspNetCore.Http.Features.IFeatureCollection Features { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public override System.Collections.Generic.IDictionary Items { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public override System.Net.EndPoint LocalEndPoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public override System.Net.EndPoint RemoteEndPoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public override System.IO.Pipelines.IDuplexPipe Transport { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public System.Security.Claims.ClaimsPrincipal User { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public override void Abort(Microsoft.AspNetCore.Connections.ConnectionAbortedException abortReason) { } public override System.Threading.Tasks.ValueTask DisposeAsync() { throw null; } } public partial class FileHandleEndPoint : System.Net.EndPoint { public FileHandleEndPoint(ulong fileHandle, Microsoft.AspNetCore.Connections.FileHandleType fileHandleType) { } - public ulong FileHandle { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public Microsoft.AspNetCore.Connections.FileHandleType FileHandleType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public ulong FileHandle { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public Microsoft.AspNetCore.Connections.FileHandleType FileHandleType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } } public enum FileHandleType { @@ -123,6 +129,40 @@ namespace Microsoft.AspNetCore.Connections { System.Threading.Tasks.ValueTask BindAsync(System.Net.EndPoint endpoint, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); } + public partial interface IMultiplexedConnectionBuilder + { + System.IServiceProvider ApplicationServices { get; } + Microsoft.AspNetCore.Connections.MultiplexedConnectionDelegate Build(); + Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder Use(System.Func middleware); + } + public partial interface IMultiplexedConnectionFactory + { + System.Threading.Tasks.ValueTask ConnectAsync(System.Net.EndPoint endpoint, Microsoft.AspNetCore.Http.Features.IFeatureCollection features = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + } + public partial interface IMultiplexedConnectionListener : System.IAsyncDisposable + { + System.Net.EndPoint EndPoint { get; } + System.Threading.Tasks.ValueTask AcceptAsync(Microsoft.AspNetCore.Http.Features.IFeatureCollection features = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + System.Threading.Tasks.ValueTask UnbindAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + } + public partial interface IMultiplexedConnectionListenerFactory + { + System.Threading.Tasks.ValueTask BindAsync(System.Net.EndPoint endpoint, Microsoft.AspNetCore.Http.Features.IFeatureCollection features = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + } + public partial class MultiplexedConnectionBuilder : Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder + { + public MultiplexedConnectionBuilder(System.IServiceProvider applicationServices) { } + public System.IServiceProvider ApplicationServices { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public Microsoft.AspNetCore.Connections.MultiplexedConnectionDelegate Build() { throw null; } + public Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder Use(System.Func middleware) { throw null; } + } + public abstract partial class MultiplexedConnectionContext : Microsoft.AspNetCore.Connections.BaseConnectionContext, System.IAsyncDisposable + { + protected MultiplexedConnectionContext() { } + public abstract System.Threading.Tasks.ValueTask AcceptAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + public abstract System.Threading.Tasks.ValueTask ConnectAsync(Microsoft.AspNetCore.Http.Features.IFeatureCollection features = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + } + public delegate System.Threading.Tasks.Task MultiplexedConnectionDelegate(Microsoft.AspNetCore.Connections.MultiplexedConnectionContext connection); [System.FlagsAttribute] public enum TransferFormat { @@ -132,7 +172,8 @@ namespace Microsoft.AspNetCore.Connections public partial class UriEndPoint : System.Net.EndPoint { public UriEndPoint(System.Uri uri) { } - public System.Uri Uri { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public System.Uri Uri { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public override string ToString() { throw null; } } } namespace Microsoft.AspNetCore.Connections.Features @@ -184,6 +225,19 @@ namespace Microsoft.AspNetCore.Connections.Features { System.Buffers.MemoryPool MemoryPool { get; } } + public partial interface IProtocolErrorCodeFeature + { + long Error { get; set; } + } + public partial interface IStreamDirectionFeature + { + bool CanRead { get; } + bool CanWrite { get; } + } + public partial interface IStreamIdFeature + { + long StreamId { get; } + } public partial interface ITlsHandshakeFeature { System.Security.Authentication.CipherAlgorithmType CipherAlgorithm { get; } diff --git a/src/Servers/Connections.Abstractions/src/BaseConnectionContext.cs b/src/Servers/Connections.Abstractions/src/BaseConnectionContext.cs new file mode 100644 index 0000000000..662b8c902e --- /dev/null +++ b/src/Servers/Connections.Abstractions/src/BaseConnectionContext.cs @@ -0,0 +1,65 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Net; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http.Features; + +namespace Microsoft.AspNetCore.Connections +{ + public abstract class BaseConnectionContext : IAsyncDisposable + { + /// + /// Gets or sets a unique identifier to represent this connection in trace logs. + /// + public abstract string ConnectionId { get; set; } + + /// + /// Gets the collection of features provided by the server and middleware available on this connection. + /// + public abstract IFeatureCollection Features { get; } + + /// + /// Gets or sets a key/value collection that can be used to share data within the scope of this connection. + /// + public abstract IDictionary Items { get; set; } + + /// + /// Triggered when the client connection is closed. + /// + public virtual CancellationToken ConnectionClosed { get; set; } + + /// + /// Gets or sets the local endpoint for this connection. + /// + public virtual EndPoint LocalEndPoint { get; set; } + + /// + /// Gets or sets the remote endpoint for this connection. + /// + public virtual EndPoint RemoteEndPoint { get; set; } + + /// + /// Aborts the underlying connection. + /// + public abstract void Abort(); + + /// + /// Aborts the underlying connection. + /// + /// An optional describing the reason the connection is being terminated. + public abstract void Abort(ConnectionAbortedException abortReason); + + /// + /// Releases resources for the underlying connection. + /// + /// A that completes when resources have been released. + public virtual ValueTask DisposeAsync() + { + return default; + } + } +} diff --git a/src/Servers/Connections.Abstractions/src/ConnectionBuilderExtensions.cs b/src/Servers/Connections.Abstractions/src/ConnectionBuilderExtensions.cs index 100917b009..55b0311eb9 100644 --- a/src/Servers/Connections.Abstractions/src/ConnectionBuilderExtensions.cs +++ b/src/Servers/Connections.Abstractions/src/ConnectionBuilderExtensions.cs @@ -40,4 +40,4 @@ namespace Microsoft.AspNetCore.Connections }); } } -} \ No newline at end of file +} diff --git a/src/Servers/Connections.Abstractions/src/ConnectionContext.cs b/src/Servers/Connections.Abstractions/src/ConnectionContext.cs index 947066ca79..02b291c2c8 100644 --- a/src/Servers/Connections.Abstractions/src/ConnectionContext.cs +++ b/src/Servers/Connections.Abstractions/src/ConnectionContext.cs @@ -2,61 +2,26 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Collections.Generic; using System.IO.Pipelines; -using System.Net; -using System.Threading; -using System.Threading.Tasks; using Microsoft.AspNetCore.Connections.Features; -using Microsoft.AspNetCore.Http.Features; namespace Microsoft.AspNetCore.Connections { /// /// Encapsulates all information about an individual connection. /// - public abstract class ConnectionContext : IAsyncDisposable + public abstract class ConnectionContext : BaseConnectionContext, IAsyncDisposable { - /// - /// Gets or sets a unique identifier to represent this connection in trace logs. - /// - public abstract string ConnectionId { get; set; } - - /// - /// Gets the collection of features provided by the server and middleware available on this connection. - /// - public abstract IFeatureCollection Features { get; } - - /// - /// Gets or sets a key/value collection that can be used to share data within the scope of this connection. - /// - public abstract IDictionary Items { get; set; } - /// /// Gets or sets the that can be used to read or write data on this connection. /// public abstract IDuplexPipe Transport { get; set; } - /// - /// Triggered when the client connection is closed. - /// - public virtual CancellationToken ConnectionClosed { get; set; } - - /// - /// Gets or sets the local endpoint for this connection. - /// - public virtual EndPoint LocalEndPoint { get; set; } - - /// - /// Gets or sets the remote endpoint for this connection. - /// - public virtual EndPoint RemoteEndPoint { get; set; } - /// /// Aborts the underlying connection. /// /// An optional describing the reason the connection is being terminated. - public virtual void Abort(ConnectionAbortedException abortReason) + public override void Abort(ConnectionAbortedException abortReason) { // We expect this to be overridden, but this helps maintain back compat // with implementations of ConnectionContext that predate the addition of @@ -67,15 +32,6 @@ namespace Microsoft.AspNetCore.Connections /// /// Aborts the underlying connection. /// - public virtual void Abort() => Abort(new ConnectionAbortedException("The connection was aborted by the application via ConnectionContext.Abort().")); - - /// - /// Releases resources for the underlying connection. - /// - /// A that completes when resources have been released. - public virtual ValueTask DisposeAsync() - { - return default; - } + public override void Abort() => Abort(new ConnectionAbortedException("The connection was aborted by the application via ConnectionContext.Abort().")); } } diff --git a/src/Servers/Connections.Abstractions/src/ConnectionHandler.cs b/src/Servers/Connections.Abstractions/src/ConnectionHandler.cs index e9e208d61a..9bc8ab2902 100644 --- a/src/Servers/Connections.Abstractions/src/ConnectionHandler.cs +++ b/src/Servers/Connections.Abstractions/src/ConnectionHandler.cs @@ -6,15 +6,15 @@ using System.Threading.Tasks; namespace Microsoft.AspNetCore.Connections { /// - /// Represents an end point that multiple connections connect to. For HTTP, endpoints are URLs, for non HTTP it can be a TCP listener (or similar) + /// Represents an endpoint that multiple connections connect to. For HTTP, endpoints are URLs, for non-HTTP it can be a TCP listener (or similar). /// public abstract class ConnectionHandler { /// - /// Called when a new connection is accepted to the endpoint + /// Called when a new connection is accepted to the endpoint. /// /// The new /// A that represents the connection lifetime. When the task completes, the connection is complete. public abstract Task OnConnectedAsync(ConnectionContext connection); } -} \ No newline at end of file +} diff --git a/src/Servers/Connections.Abstractions/src/Features/IProtocolErrorCodeFeature.cs b/src/Servers/Connections.Abstractions/src/Features/IProtocolErrorCodeFeature.cs new file mode 100644 index 0000000000..c47a2485b8 --- /dev/null +++ b/src/Servers/Connections.Abstractions/src/Features/IProtocolErrorCodeFeature.cs @@ -0,0 +1,10 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.AspNetCore.Connections.Features +{ + public interface IProtocolErrorCodeFeature + { + long Error { get; set; } + } +} diff --git a/src/Servers/Connections.Abstractions/src/Features/IStreamDirectionFeature.cs b/src/Servers/Connections.Abstractions/src/Features/IStreamDirectionFeature.cs new file mode 100644 index 0000000000..66f706dbf9 --- /dev/null +++ b/src/Servers/Connections.Abstractions/src/Features/IStreamDirectionFeature.cs @@ -0,0 +1,11 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.AspNetCore.Connections.Features +{ + public interface IStreamDirectionFeature + { + bool CanRead { get; } + bool CanWrite { get; } + } +} diff --git a/src/Servers/Connections.Abstractions/src/Features/IStreamIdFeature.cs b/src/Servers/Connections.Abstractions/src/Features/IStreamIdFeature.cs new file mode 100644 index 0000000000..f86f2ee61e --- /dev/null +++ b/src/Servers/Connections.Abstractions/src/Features/IStreamIdFeature.cs @@ -0,0 +1,10 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.AspNetCore.Connections.Features +{ + public interface IStreamIdFeature + { + long StreamId { get; } + } +} diff --git a/src/Servers/Connections.Abstractions/src/IMulitplexedConnectionListener.cs b/src/Servers/Connections.Abstractions/src/IMulitplexedConnectionListener.cs new file mode 100644 index 0000000000..d867fe0938 --- /dev/null +++ b/src/Servers/Connections.Abstractions/src/IMulitplexedConnectionListener.cs @@ -0,0 +1,37 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Net; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http.Features; + +namespace Microsoft.AspNetCore.Connections +{ + /// + /// Defines an interface that represents a listener bound to a specific . + /// + public interface IMultiplexedConnectionListener : IAsyncDisposable + { + /// + /// The endpoint that was bound. This may differ from the requested endpoint, such as when the caller requested that any free port be selected. + /// + EndPoint EndPoint { get; } + + /// + /// Stops listening for incoming connections. + /// + /// The token to monitor for cancellation requests. + /// A that represents the un-bind operation. + ValueTask UnbindAsync(CancellationToken cancellationToken = default); + + /// + /// Begins an asynchronous operation to accept an incoming connection. + /// + /// A feature collection to pass options when accepting a connection. + /// The token to monitor for cancellation requests. + /// A that completes when a connection is accepted, yielding the representing the connection. + ValueTask AcceptAsync(IFeatureCollection features = null, CancellationToken cancellationToken = default); + } +} diff --git a/src/Servers/Connections.Abstractions/src/IMultiplexedConnectionBuilder.cs b/src/Servers/Connections.Abstractions/src/IMultiplexedConnectionBuilder.cs new file mode 100644 index 0000000000..8f3caf34db --- /dev/null +++ b/src/Servers/Connections.Abstractions/src/IMultiplexedConnectionBuilder.cs @@ -0,0 +1,31 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.AspNetCore.Connections +{ + /// + /// Defines an interface that provides the mechanisms to configure a connection pipeline. + /// + public interface IMultiplexedConnectionBuilder + { + /// + /// Gets the that provides access to the application's service container. + /// + IServiceProvider ApplicationServices { get; } + + /// + /// Adds a middleware delegate to the application's connection pipeline. + /// + /// The middleware delegate. + /// The . + IMultiplexedConnectionBuilder Use(Func middleware); + + /// + /// Builds the delegate used by this application to process connections. + /// + /// The connection handling delegate. + MultiplexedConnectionDelegate Build(); + } +} diff --git a/src/Servers/Connections.Abstractions/src/IMultiplexedConnectionFactory.cs b/src/Servers/Connections.Abstractions/src/IMultiplexedConnectionFactory.cs new file mode 100644 index 0000000000..a3f69f7a68 --- /dev/null +++ b/src/Servers/Connections.Abstractions/src/IMultiplexedConnectionFactory.cs @@ -0,0 +1,27 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Net; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http.Features; + +namespace Microsoft.AspNetCore.Connections +{ + /// + /// A factory abstraction for creating connections to an endpoint. + /// + public interface IMultiplexedConnectionFactory + { + /// + /// Creates a new connection to an endpoint. + /// + /// The to connect to. + /// A feature collection to pass options when connecting. + /// The token to monitor for cancellation requests. The default value is . + /// + /// A that represents the asynchronous connect, yielding the for the new connection when completed. + /// + ValueTask ConnectAsync(EndPoint endpoint, IFeatureCollection features = null, CancellationToken cancellationToken = default); + } +} diff --git a/src/Servers/Connections.Abstractions/src/IMultiplexedConnectionListenerFactory.cs b/src/Servers/Connections.Abstractions/src/IMultiplexedConnectionListenerFactory.cs new file mode 100644 index 0000000000..3b5010beda --- /dev/null +++ b/src/Servers/Connections.Abstractions/src/IMultiplexedConnectionListenerFactory.cs @@ -0,0 +1,25 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Net; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http.Features; + +namespace Microsoft.AspNetCore.Connections +{ + /// + /// Defines an interface that provides the mechanisms for binding to various types of s. + /// + public interface IMultiplexedConnectionListenerFactory + { + /// + /// Creates an bound to the specified . + /// + /// The to bind to. + /// A feature collection to pass options when binding. + /// The token to monitor for cancellation requests. + /// A that completes when the listener has been bound, yielding a representing the new listener. + ValueTask BindAsync(EndPoint endpoint, IFeatureCollection features = null, CancellationToken cancellationToken = default); + } +} diff --git a/src/Servers/Connections.Abstractions/src/Microsoft.AspNetCore.Connections.Abstractions.csproj b/src/Servers/Connections.Abstractions/src/Microsoft.AspNetCore.Connections.Abstractions.csproj index d7040f2445..ddefc4f62c 100644 --- a/src/Servers/Connections.Abstractions/src/Microsoft.AspNetCore.Connections.Abstractions.csproj +++ b/src/Servers/Connections.Abstractions/src/Microsoft.AspNetCore.Connections.Abstractions.csproj @@ -12,8 +12,10 @@ - + + + diff --git a/src/Servers/Connections.Abstractions/src/MultiplexedConnectionBuilder.cs b/src/Servers/Connections.Abstractions/src/MultiplexedConnectionBuilder.cs new file mode 100644 index 0000000000..202f29df5e --- /dev/null +++ b/src/Servers/Connections.Abstractions/src/MultiplexedConnectionBuilder.cs @@ -0,0 +1,43 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Microsoft.AspNetCore.Connections +{ + public class MultiplexedConnectionBuilder : IMultiplexedConnectionBuilder + { + private readonly IList> _components = new List>(); + + public IServiceProvider ApplicationServices { get; } + + public MultiplexedConnectionBuilder(IServiceProvider applicationServices) + { + ApplicationServices = applicationServices; + } + + public IMultiplexedConnectionBuilder Use(Func middleware) + { + _components.Add(middleware); + return this; + } + + public MultiplexedConnectionDelegate Build() + { + MultiplexedConnectionDelegate app = features => + { + return Task.CompletedTask; + }; + + foreach (var component in _components.Reverse()) + { + app = component(app); + } + + return app; + } + } +} diff --git a/src/Servers/Connections.Abstractions/src/MultiplexedConnectionContext.cs b/src/Servers/Connections.Abstractions/src/MultiplexedConnectionContext.cs new file mode 100644 index 0000000000..ce0850d281 --- /dev/null +++ b/src/Servers/Connections.Abstractions/src/MultiplexedConnectionContext.cs @@ -0,0 +1,31 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http.Features; + +namespace Microsoft.AspNetCore.Connections +{ + /// + /// Encapsulates all information about a multiplexed connection. + /// + public abstract class MultiplexedConnectionContext : BaseConnectionContext, IAsyncDisposable + { + /// + /// Asynchronously accept an incoming stream on the connection. + /// + /// + /// + public abstract ValueTask AcceptAsync(CancellationToken cancellationToken = default); + + /// + /// Creates an outbound connection + /// + /// + /// + /// + public abstract ValueTask ConnectAsync(IFeatureCollection features = null, CancellationToken cancellationToken = default); + } +} diff --git a/src/Servers/Connections.Abstractions/src/MultiplexedConnectionDelegate.cs b/src/Servers/Connections.Abstractions/src/MultiplexedConnectionDelegate.cs new file mode 100644 index 0000000000..c85298ea2d --- /dev/null +++ b/src/Servers/Connections.Abstractions/src/MultiplexedConnectionDelegate.cs @@ -0,0 +1,14 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Threading.Tasks; + +namespace Microsoft.AspNetCore.Connections +{ + /// + /// A function that can process a connection. + /// + /// A representing the connection. + /// A that represents the connection lifetime. When the task completes, the connection will be closed. + public delegate Task MultiplexedConnectionDelegate(MultiplexedConnectionContext connection); +} diff --git a/src/Servers/Connections.Abstractions/src/UriEndPoint.cs b/src/Servers/Connections.Abstractions/src/UriEndPoint.cs index 0321d5af2d..7000b86611 100644 --- a/src/Servers/Connections.Abstractions/src/UriEndPoint.cs +++ b/src/Servers/Connections.Abstractions/src/UriEndPoint.cs @@ -24,5 +24,7 @@ namespace Microsoft.AspNetCore.Connections /// The defining the . /// public Uri Uri { get; } + + public override string ToString() => Uri.ToString(); } } diff --git a/src/Servers/HttpSys/HttpSysServer.sln b/src/Servers/HttpSys/HttpSysServer.sln index bdd6b9c522..75f0aad286 100644 --- a/src/Servers/HttpSys/HttpSysServer.sln +++ b/src/Servers/HttpSys/HttpSysServer.sln @@ -24,7 +24,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution version.xml = version.xml EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestClient", "samples\TestClient\TestClient.csproj", "{8B828433-B333-4C19-96AE-00BFFF9D8841}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestClient", "samples\TestClient\TestClient.csproj", "{8B828433-B333-4C19-96AE-00BFFF9D8841}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SelfHostServer", "samples\SelfHostServer\SelfHostServer.csproj", "{1236F93A-AC5C-4A77-9477-C88F040151CA}" EndProject @@ -72,6 +72,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Connec EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QueueSharing", "samples\QueueSharing\QueueSharing.csproj", "{9B58DF76-DC6D-4728-86B7-40087BDDC897}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets", "..\Kestrel\Transport.Sockets\src\Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.csproj", "{33CF53ED-A4BC-4EAA-9EA7-EF5E748A03BB}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore", "..\..\DefaultBuilder\src\Microsoft.AspNetCore.csproj", "{E8880B06-7172-4995-893D-13E87AF00E7B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -318,6 +322,30 @@ Global {9B58DF76-DC6D-4728-86B7-40087BDDC897}.Release|Mixed Platforms.Build.0 = Release|Any CPU {9B58DF76-DC6D-4728-86B7-40087BDDC897}.Release|x86.ActiveCfg = Release|Any CPU {9B58DF76-DC6D-4728-86B7-40087BDDC897}.Release|x86.Build.0 = Release|Any CPU + {33CF53ED-A4BC-4EAA-9EA7-EF5E748A03BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {33CF53ED-A4BC-4EAA-9EA7-EF5E748A03BB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {33CF53ED-A4BC-4EAA-9EA7-EF5E748A03BB}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {33CF53ED-A4BC-4EAA-9EA7-EF5E748A03BB}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {33CF53ED-A4BC-4EAA-9EA7-EF5E748A03BB}.Debug|x86.ActiveCfg = Debug|Any CPU + {33CF53ED-A4BC-4EAA-9EA7-EF5E748A03BB}.Debug|x86.Build.0 = Debug|Any CPU + {33CF53ED-A4BC-4EAA-9EA7-EF5E748A03BB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {33CF53ED-A4BC-4EAA-9EA7-EF5E748A03BB}.Release|Any CPU.Build.0 = Release|Any CPU + {33CF53ED-A4BC-4EAA-9EA7-EF5E748A03BB}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {33CF53ED-A4BC-4EAA-9EA7-EF5E748A03BB}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {33CF53ED-A4BC-4EAA-9EA7-EF5E748A03BB}.Release|x86.ActiveCfg = Release|Any CPU + {33CF53ED-A4BC-4EAA-9EA7-EF5E748A03BB}.Release|x86.Build.0 = Release|Any CPU + {E8880B06-7172-4995-893D-13E87AF00E7B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E8880B06-7172-4995-893D-13E87AF00E7B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E8880B06-7172-4995-893D-13E87AF00E7B}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {E8880B06-7172-4995-893D-13E87AF00E7B}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {E8880B06-7172-4995-893D-13E87AF00E7B}.Debug|x86.ActiveCfg = Debug|Any CPU + {E8880B06-7172-4995-893D-13E87AF00E7B}.Debug|x86.Build.0 = Debug|Any CPU + {E8880B06-7172-4995-893D-13E87AF00E7B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E8880B06-7172-4995-893D-13E87AF00E7B}.Release|Any CPU.Build.0 = Release|Any CPU + {E8880B06-7172-4995-893D-13E87AF00E7B}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {E8880B06-7172-4995-893D-13E87AF00E7B}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {E8880B06-7172-4995-893D-13E87AF00E7B}.Release|x86.ActiveCfg = Release|Any CPU + {E8880B06-7172-4995-893D-13E87AF00E7B}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -344,6 +372,8 @@ Global {D93575B3-BFA3-4523-B060-D268D6A0A66B} = {4DA3C456-5050-4AC0-A554-795F6DEC8660} {00A88B8D-D539-45DD-B071-1E955AF89A4A} = {4DA3C456-5050-4AC0-A554-795F6DEC8660} {9B58DF76-DC6D-4728-86B7-40087BDDC897} = {3A1E31E3-2794-4CA3-B8E2-253E96BDE514} + {33CF53ED-A4BC-4EAA-9EA7-EF5E748A03BB} = {4DA3C456-5050-4AC0-A554-795F6DEC8660} + {E8880B06-7172-4995-893D-13E87AF00E7B} = {4DA3C456-5050-4AC0-A554-795F6DEC8660} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {34B42B42-FA09-41AB-9216-14073990C504} diff --git a/src/Servers/HttpSys/ref/Microsoft.AspNetCore.Server.HttpSys.netcoreapp.cs b/src/Servers/HttpSys/ref/Microsoft.AspNetCore.Server.HttpSys.netcoreapp.cs index 31f097495b..98e383a676 100644 --- a/src/Servers/HttpSys/ref/Microsoft.AspNetCore.Server.HttpSys.netcoreapp.cs +++ b/src/Servers/HttpSys/ref/Microsoft.AspNetCore.Server.HttpSys.netcoreapp.cs @@ -15,6 +15,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys { internal AuthenticationManager() { } public bool AllowAnonymous { get { throw null; } set { } } + public bool AutomaticAuthentication { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public Microsoft.AspNetCore.Server.HttpSys.AuthenticationSchemes Schemes { get { throw null; } set { } } } [System.FlagsAttribute] @@ -50,20 +51,20 @@ namespace Microsoft.AspNetCore.Server.HttpSys public partial class HttpSysOptions { public HttpSysOptions() { } - public bool AllowSynchronousIO { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public Microsoft.AspNetCore.Server.HttpSys.AuthenticationManager Authentication { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public Microsoft.AspNetCore.Server.HttpSys.ClientCertificateMethod ClientCertificateMethod { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public bool EnableResponseCaching { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public bool AllowSynchronousIO { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public Microsoft.AspNetCore.Server.HttpSys.AuthenticationManager Authentication { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public Microsoft.AspNetCore.Server.HttpSys.ClientCertificateMethod ClientCertificateMethod { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public bool EnableResponseCaching { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public Microsoft.AspNetCore.Server.HttpSys.Http503VerbosityLevel Http503Verbosity { get { throw null; } set { } } - public int MaxAccepts { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public int MaxAccepts { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public long? MaxConnections { get { throw null; } set { } } public long? MaxRequestBodySize { get { throw null; } set { } } public long RequestQueueLimit { get { throw null; } set { } } - public Microsoft.AspNetCore.Server.HttpSys.RequestQueueMode RequestQueueMode { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.AspNetCore.Server.HttpSys.RequestQueueMode RequestQueueMode { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public string RequestQueueName { get { throw null; } set { } } - public bool ThrowWriteExceptions { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public Microsoft.AspNetCore.Server.HttpSys.TimeoutManager Timeouts { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public Microsoft.AspNetCore.Server.HttpSys.UrlPrefixCollection UrlPrefixes { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public bool ThrowWriteExceptions { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public Microsoft.AspNetCore.Server.HttpSys.TimeoutManager Timeouts { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public Microsoft.AspNetCore.Server.HttpSys.UrlPrefixCollection UrlPrefixes { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } } public partial interface IHttpSysRequestInfoFeature { @@ -88,13 +89,13 @@ namespace Microsoft.AspNetCore.Server.HttpSys public partial class UrlPrefix { internal UrlPrefix() { } - public string FullPrefix { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public string Host { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public bool IsHttps { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public string Path { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public string Port { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public int PortValue { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public string Scheme { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public string FullPrefix { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public string Host { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public bool IsHttps { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public string Path { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public string Port { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public int PortValue { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public string Scheme { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } public static Microsoft.AspNetCore.Server.HttpSys.UrlPrefix Create(string prefix) { throw null; } public static Microsoft.AspNetCore.Server.HttpSys.UrlPrefix Create(string scheme, string host, int? portValue, string path) { throw null; } public static Microsoft.AspNetCore.Server.HttpSys.UrlPrefix Create(string scheme, string host, string port, string path) { throw null; } diff --git a/src/Servers/HttpSys/samples/SelfHostServer/Program.cs b/src/Servers/HttpSys/samples/SelfHostServer/Program.cs new file mode 100644 index 0000000000..df39ba8da8 --- /dev/null +++ b/src/Servers/HttpSys/samples/SelfHostServer/Program.cs @@ -0,0 +1,32 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Server.HttpSys; +using Microsoft.Extensions.Hosting; + +namespace SelfHostServer +{ + public static class Program + { + public static void Main(string[] args) + { + CreateHostBuilder(args).Build().Run(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseStartup() + .UseHttpSys(options => + { + options.UrlPrefixes.Add("http://localhost:5000"); + // This is a pre-configured IIS express port. See the PackageTags in the csproj. + options.UrlPrefixes.Add("https://localhost:44319"); + options.Authentication.Schemes = AuthenticationSchemes.None; + options.Authentication.AllowAnonymous = true; + }); + }); + } +} diff --git a/src/Servers/HttpSys/samples/SelfHostServer/SelfHostServer.csproj b/src/Servers/HttpSys/samples/SelfHostServer/SelfHostServer.csproj index 35450964c9..a824a8daee 100644 --- a/src/Servers/HttpSys/samples/SelfHostServer/SelfHostServer.csproj +++ b/src/Servers/HttpSys/samples/SelfHostServer/SelfHostServer.csproj @@ -12,7 +12,9 @@ + + diff --git a/src/Servers/HttpSys/samples/SelfHostServer/Startup.cs b/src/Servers/HttpSys/samples/SelfHostServer/Startup.cs index 42f6d11f81..28665f54dc 100644 --- a/src/Servers/HttpSys/samples/SelfHostServer/Startup.cs +++ b/src/Servers/HttpSys/samples/SelfHostServer/Startup.cs @@ -1,3 +1,6 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + using System; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; @@ -28,23 +31,5 @@ namespace SelfHostServer await context.Response.WriteAsync("Hello world from " + context.Request.Host + " at " + DateTime.Now); }); } - - public static void Main(string[] args) - { - var host = new WebHostBuilder() - .ConfigureLogging(factory => factory.AddConsole()) - .UseStartup() - .UseHttpSys(options => - { - options.UrlPrefixes.Add("http://localhost:5000"); - // This is a pre-configured IIS express port. See the PackageTags in the csproj. - options.UrlPrefixes.Add("https://localhost:44319"); - options.Authentication.Schemes = AuthenticationSchemes.None; - options.Authentication.AllowAnonymous = true; - }) - .Build(); - - host.Run(); - } } } diff --git a/src/Servers/HttpSys/samples/TestClient/App.config b/src/Servers/HttpSys/samples/TestClient/App.config deleted file mode 100644 index 2d2a12d81b..0000000000 --- a/src/Servers/HttpSys/samples/TestClient/App.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/src/Servers/HttpSys/samples/TestClient/Program.cs b/src/Servers/HttpSys/samples/TestClient/Program.cs index f57945de42..f81dc83833 100644 --- a/src/Servers/HttpSys/samples/TestClient/Program.cs +++ b/src/Servers/HttpSys/samples/TestClient/Program.cs @@ -11,20 +11,45 @@ namespace TestClient public class Program { private const string Address = - // "http://localhost:5000/public/1kb.txt"; - "https://localhost:9090/public/1kb.txt"; + "http://localhost:5000/public/1kb.txt"; + // "https://localhost:9090/public/1kb.txt"; public static void Main(string[] args) { - WebRequestHandler handler = new WebRequestHandler(); - handler.ServerCertificateValidationCallback = (_, __, ___, ____) => true; + Console.WriteLine("Ready"); + Console.ReadKey(); + + var handler = new HttpClientHandler(); + handler.MaxConnectionsPerServer = 500; + handler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator; // handler.UseDefaultCredentials = true; - handler.Credentials = new NetworkCredential(@"redmond\chrross", "passwird"); HttpClient client = new HttpClient(handler); - /* + RunParallelRequests(client); + + // RunManualRequests(client); + + // RunWebSocketClient().Wait(); + + Console.WriteLine("Done"); + // Console.ReadKey(); + } + + private static void RunManualRequests(HttpClient client) + { + while (true) + { + Console.WriteLine("Press any key to send request"); + Console.ReadKey(); + var result = client.GetAsync(Address).Result; + Console.WriteLine(result); + } + } + + private static void RunParallelRequests(HttpClient client) + { int completionCount = 0; - int iterations = 30000; + int iterations = 100000; for (int i = 0; i < iterations; i++) { client.GetAsync(Address) @@ -34,19 +59,7 @@ namespace TestClient while (completionCount < iterations) { Thread.Sleep(10); - }*/ - - while (true) - { - Console.WriteLine("Press any key to send request"); - Console.ReadKey(); - var result = client.GetAsync(Address).Result; - Console.WriteLine(result); } - - // RunWebSocketClient().Wait(); - // Console.WriteLine("Done"); - // Console.ReadKey(); } public static async Task RunWebSocketClient() diff --git a/src/Servers/HttpSys/samples/TestClient/Properties/AssemblyInfo.cs b/src/Servers/HttpSys/samples/TestClient/Properties/AssemblyInfo.cs deleted file mode 100644 index 249372ae2d..0000000000 --- a/src/Servers/HttpSys/samples/TestClient/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. -// All Rights Reserved -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR -// CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING -// WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF -// TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR -// NON-INFRINGEMENT. -// See the Apache 2 License for the specific language governing -// permissions and limitations under the License. - -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("TestClient")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("TestClient")] -[assembly: AssemblyCopyright("Copyright © 2012")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("8db62eb3-48c0-4049-b33e-271c738140a0")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("0.5")] -[assembly: AssemblyVersion("0.5")] -[assembly: AssemblyFileVersion("0.5.40117.0")] diff --git a/src/Servers/HttpSys/samples/TestClient/TestClient.csproj b/src/Servers/HttpSys/samples/TestClient/TestClient.csproj index c92be92bc8..6d5bb6e338 100644 --- a/src/Servers/HttpSys/samples/TestClient/TestClient.csproj +++ b/src/Servers/HttpSys/samples/TestClient/TestClient.csproj @@ -1,64 +1,13 @@ - - - + + - Debug - AnyCPU - {8B828433-B333-4C19-96AE-00BFFF9D8841} + $(DefaultNetCoreTargetFramework) Exe - Properties - TestClient - TestClient - v4.6 - 512 - ..\..\ - true - - - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 + TestClient.Program + - - - - - - - - - + - - - - - - - - - - - \ No newline at end of file + + diff --git a/src/Servers/HttpSys/src/AsyncAcceptContext.cs b/src/Servers/HttpSys/src/AsyncAcceptContext.cs index 4c0cd86756..5908322598 100644 --- a/src/Servers/HttpSys/src/AsyncAcceptContext.cs +++ b/src/Servers/HttpSys/src/AsyncAcceptContext.cs @@ -3,7 +3,6 @@ using System; using System.Diagnostics.CodeAnalysis; -using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; @@ -18,8 +17,6 @@ namespace Microsoft.AspNetCore.Server.HttpSys private TaskCompletionSource _tcs; private HttpSysListener _server; private NativeRequestContext _nativeRequestContext; - private const int DefaultBufferSize = 4096; - private const int AlignmentPadding = 8; internal AsyncAcceptContext(HttpSysListener server) { @@ -192,32 +189,14 @@ namespace Microsoft.AspNetCore.Server.HttpSys { _nativeRequestContext?.ReleasePins(); _nativeRequestContext?.Dispose(); - //Debug.Assert(size != 0, "unexpected size"); // We can't reuse overlapped objects - uint newSize = size.HasValue ? size.Value : DefaultBufferSize; - var backingBuffer = new byte[newSize + AlignmentPadding]; - var boundHandle = Server.RequestQueue.BoundHandle; var nativeOverlapped = new SafeNativeOverlapped(boundHandle, - boundHandle.AllocateNativeOverlapped(IOCallback, this, backingBuffer)); + boundHandle.AllocateNativeOverlapped(IOCallback, this, pinData: null)); - var requestAddress = Marshal.UnsafeAddrOfPinnedArrayElement(backingBuffer, 0); - - // TODO: - // Apparently the HttpReceiveHttpRequest memory alignment requirements for non - ARM processors - // are different than for ARM processors. We have seen 4 - byte - aligned buffers allocated on - // virtual x64/x86 machines which were accepted by HttpReceiveHttpRequest without errors. In - // these cases the buffer alignment may cause reading values at invalid offset. Setting buffer - // alignment to 0 for now. - // - // _bufferAlignment = (int)(requestAddress.ToInt64() & 0x07); - - var bufferAlignment = 0; - - var nativeRequest = (HttpApiTypes.HTTP_REQUEST*)(requestAddress + bufferAlignment); // nativeRequest - _nativeRequestContext = new NativeRequestContext(nativeOverlapped, bufferAlignment, nativeRequest, backingBuffer, requestId); + _nativeRequestContext = new NativeRequestContext(nativeOverlapped, Server.MemoryPool, size, requestId); } public object AsyncState diff --git a/src/Servers/HttpSys/src/AuthenticationManager.cs b/src/Servers/HttpSys/src/AuthenticationManager.cs index 29f5a3495a..9b54f0ab0f 100644 --- a/src/Servers/HttpSys/src/AuthenticationManager.cs +++ b/src/Servers/HttpSys/src/AuthenticationManager.cs @@ -45,12 +45,22 @@ namespace Microsoft.AspNetCore.Server.HttpSys } } + /// + /// Indicates if anonymous requests will be surfaced to the application or challenged by the server. + /// The default value is true. + /// public bool AllowAnonymous { get { return _allowAnonymous; } set { _allowAnonymous = value; } } + /// + /// If true the server should set HttpContext.User. If false the server will only provide an + /// identity when explicitly requested by the AuthenticationScheme. The default is true. + /// + public bool AutomaticAuthentication { get; set; } = true; + internal void SetUrlGroupSecurity(UrlGroup urlGroup) { Debug.Assert(_urlGroup == null, "SetUrlGroupSecurity called more than once."); diff --git a/src/Servers/HttpSys/src/FeatureContext.cs b/src/Servers/HttpSys/src/FeatureContext.cs index 7cce609f3e..23e174344c 100644 --- a/src/Servers/HttpSys/src/FeatureContext.cs +++ b/src/Servers/HttpSys/src/FeatureContext.cs @@ -35,7 +35,9 @@ namespace Microsoft.AspNetCore.Server.HttpSys IHttpRequestIdentifierFeature, IHttpMaxRequestBodySizeFeature, IHttpBodyControlFeature, - IHttpSysRequestInfoFeature + IHttpSysRequestInfoFeature, + IHttpResponseTrailersFeature, + IHttpResetFeature { private RequestContext _requestContext; private IFeatureCollection _features; @@ -63,6 +65,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys private PipeWriter _pipeWriter; private bool _bodyCompleted; private IHeaderDictionary _responseHeaders; + private IHeaderDictionary _responseTrailers; private Fields _initializedFields; @@ -85,7 +88,11 @@ namespace Microsoft.AspNetCore.Server.HttpSys _query = Request.QueryString; _rawTarget = Request.RawUrl; _scheme = Request.Scheme; - _user = _requestContext.User; + + if (requestContext.Server.Options.Authentication.AutomaticAuthentication) + { + _user = _requestContext.User; + } _responseStream = new ResponseStream(requestContext.Response.Body, OnResponseStart); _responseHeaders = Response.Headers; @@ -174,23 +181,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys { if (IsNotInitialized(Fields.Protocol)) { - var protocol = Request.ProtocolVersion; - if (protocol == Constants.V2) - { - _httpProtocolVersion = "HTTP/2"; - } - else if (protocol == Constants.V1_1) - { - _httpProtocolVersion = "HTTP/1.1"; - } - else if (protocol == Constants.V1_0) - { - _httpProtocolVersion = "HTTP/1.0"; - } - else - { - _httpProtocolVersion = "HTTP/" + protocol.ToString(2); - } + _httpProtocolVersion = HttpProtocol.GetHttpProtocol(Request.ProtocolVersion); SetInitialized(Fields.Protocol); } return _httpProtocolVersion; @@ -358,6 +349,24 @@ namespace Microsoft.AspNetCore.Server.HttpSys return Request.IsHttps ? this : null; } + internal IHttpResponseTrailersFeature GetResponseTrailersFeature() + { + if (Request.ProtocolVersion >= HttpVersion.Version20 && HttpApi.SupportsTrailers) + { + return this; + } + return null; + } + + internal IHttpResetFeature GetResetFeature() + { + if (Request.ProtocolVersion >= HttpVersion.Version20 && HttpApi.SupportsReset) + { + return this; + } + return null; + } + /* TODO: https://github.com/aspnet/HttpSysServer/issues/231 byte[] ITlsTokenBindingFeature.GetProvidedTokenBindingId() => Request.GetProvidedTokenBindingId(); @@ -401,7 +410,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys set { _responseHeaders = value; } } - bool IHttpResponseFeature.HasStarted => Response.HasStarted; + bool IHttpResponseFeature.HasStarted => _responseStarted; void IHttpResponseFeature.OnStarting(Func callback, object state) { @@ -456,6 +465,12 @@ namespace Microsoft.AspNetCore.Server.HttpSys Task IHttpResponseBodyFeature.CompleteAsync() => CompleteAsync(); + void IHttpResetFeature.Reset(int errorCode) + { + _requestContext.SetResetCode(errorCode); + _requestContext.Abort(); + } + internal async Task CompleteAsync() { if (!_responseStarted) @@ -559,6 +574,12 @@ namespace Microsoft.AspNetCore.Server.HttpSys IReadOnlyDictionary> IHttpSysRequestInfoFeature.RequestInfo => Request.RequestInfo; + IHeaderDictionary IHttpResponseTrailersFeature.Trailers + { + get => _responseTrailers ??= Response.Trailers; + set => _responseTrailers = value; + } + internal async Task OnResponseStart() { if (_responseStarted) diff --git a/src/Servers/HttpSys/src/HttpSysListener.cs b/src/Servers/HttpSys/src/HttpSysListener.cs index 24f6c61955..224201d79f 100644 --- a/src/Servers/HttpSys/src/HttpSysListener.cs +++ b/src/Servers/HttpSys/src/HttpSysListener.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Buffers; using System.Collections.Generic; using System.Diagnostics; using System.Runtime.InteropServices; @@ -32,6 +33,8 @@ namespace Microsoft.AspNetCore.Server.HttpSys // 0.5 seconds per request. Respond with a 400 Bad Request. private const int UnknownHeaderLimit = 1000; + internal MemoryPool MemoryPool { get; } = SlabMemoryPoolFactory.Create(); + private volatile State _state; // m_State is set only within lock blocks, but often read outside locks. private ServerSession _serverSession; @@ -61,7 +64,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys Options = options; - Logger = LogHelper.CreateLogger(loggerFactory, typeof(HttpSysListener)); + Logger = loggerFactory.CreateLogger(); _state = State.Stopped; _internalLock = new object(); @@ -89,7 +92,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys _requestQueue?.Dispose(); _urlGroup?.Dispose(); _serverSession?.Dispose(); - LogHelper.LogException(Logger, ".Ctor", exception); + Logger.LogError(LoggerEventIds.HttpSysListenerCtorError, exception, ".Ctor"); throw; } } @@ -132,7 +135,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys { CheckDisposed(); - LogHelper.LogInfo(Logger, "Start"); + Logger.LogTrace(LoggerEventIds.ListenerStarting, "Starting the listener."); // Make sure there are no race conditions between Start/Stop/Abort/Close/Dispose. // Start needs to setup all resources. Abort/Stop must not interfere while Start is @@ -174,7 +177,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys // Make sure the HttpListener instance can't be used if Start() failed. _state = State.Disposed; DisposeInternal(); - LogHelper.LogException(Logger, "Start", exception); + Logger.LogError(LoggerEventIds.ListenerStartError, exception, "Start"); throw; } } @@ -192,6 +195,8 @@ namespace Microsoft.AspNetCore.Server.HttpSys return; } + Logger.LogTrace(LoggerEventIds.ListenerStopping,"Stopping the listener."); + // If this instance created the queue then remove the URL prefixes before shutting down. if (_requestQueue.Created) { @@ -205,7 +210,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys } catch (Exception exception) { - LogHelper.LogException(Logger, "Stop", exception); + Logger.LogError(LoggerEventIds.ListenerStopError, exception, "Stop"); throw; } } @@ -233,14 +238,14 @@ namespace Microsoft.AspNetCore.Server.HttpSys { return; } - LogHelper.LogInfo(Logger, "Dispose"); + Logger.LogTrace(LoggerEventIds.ListenerDisposing, "Disposing the listener."); Stop(); DisposeInternal(); } catch (Exception exception) { - LogHelper.LogException(Logger, "Dispose", exception); + Logger.LogError(LoggerEventIds.ListenerDisposeError, exception, "Dispose"); throw; } finally @@ -295,7 +300,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys } catch (Exception exception) { - LogHelper.LogException(Logger, "GetContextAsync", exception); + Logger.LogError(LoggerEventIds.AcceptError, exception, "AcceptAsync"); throw; } diff --git a/src/Servers/HttpSys/src/LogHelper.cs b/src/Servers/HttpSys/src/LogHelper.cs deleted file mode 100644 index 2a9345d624..0000000000 --- a/src/Servers/HttpSys/src/LogHelper.cs +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Diagnostics; -using Microsoft.Extensions.Logging; - -namespace Microsoft.AspNetCore.Server.HttpSys -{ - internal static class LogHelper - { - internal static ILogger CreateLogger(ILoggerFactory factory, Type type) - { - if (factory == null) - { - return null; - } - - return factory.CreateLogger(type.FullName); - } - - internal static void LogInfo(ILogger logger, string data) - { - if (logger == null) - { - Debug.WriteLine(data); - } - else - { - logger.LogInformation(data); - } - } - - internal static void LogWarning(ILogger logger, string data) - { - if (logger == null) - { - Debug.WriteLine(data); - } - else - { - logger.LogWarning(data); - } - } - - internal static void LogDebug(ILogger logger, string data) - { - if (logger == null) - { - Debug.WriteLine(data); - } - else - { - logger.LogDebug(data); - } - } - - internal static void LogDebug(ILogger logger, string location, string data) - { - if (logger == null) - { - Debug.WriteLine(data); - } - else - { - logger.LogDebug(location + "; " + data); - } - } - - internal static void LogDebug(ILogger logger, string location, Exception exception) - { - if (logger == null) - { - Debug.WriteLine(location + Environment.NewLine + exception.ToString()); - } - else - { - logger.LogDebug(0, exception, location); - } - } - - internal static void LogException(ILogger logger, string location, Exception exception) - { - if (logger == null) - { - Debug.WriteLine(location + Environment.NewLine + exception.ToString()); - } - else - { - logger.LogError(0, exception, location); - } - } - - internal static void LogError(ILogger logger, string location, string message) - { - if (logger == null) - { - Debug.WriteLine(message); - } - else - { - logger.LogError(location + "; " + message); - } - } - } -} diff --git a/src/Servers/HttpSys/src/LoggerEventIds.cs b/src/Servers/HttpSys/src/LoggerEventIds.cs new file mode 100644 index 0000000000..f10096e1b0 --- /dev/null +++ b/src/Servers/HttpSys/src/LoggerEventIds.cs @@ -0,0 +1,58 @@ +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Server.HttpSys +{ + internal static class LoggerEventIds + { + public static EventId HttpSysListenerCtorError = new EventId(1, "HttpSysListenerCtorError"); + public static EventId BindingToDefault = new EventId(2, "BindingToDefault"); + public static EventId ClearedPrefixes = new EventId(3, "ClearedPrefixes"); + public static EventId AcceptErrorStopping = new EventId(4, "AcceptErrorStopping"); + public static EventId AcceptError = new EventId(5, "AcceptError"); + public static EventId RequestProcessError = new EventId(6, "RequestProcessError"); + public static EventId RequestsDrained = new EventId(7, "RequestsDrained"); + public static EventId StopCancelled = new EventId(8, "StopCancelled"); + public static EventId WaitingForRequestsToDrain = new EventId(9, "WaitingForRequestsToDrain"); + public static EventId DisconnectRegistrationError = new EventId(10, "DisconnectRegistrationError"); + public static EventId RegisterDisconnectListener = new EventId(11, "RegisterDisconnectListener"); + public static EventId UnknownDisconnectError = new EventId(12, "UnknownDisconnectError"); + public static EventId DisconnectHandlerError = new EventId(13, "DisconnectHandlerError"); + public static EventId ListenerStarting = new EventId(14, "ListenerStarting"); + public static EventId ListenerDisposeError = new EventId(15, "ListenerDisposeError"); + public static EventId RequestListenerProcessError = new EventId(16, "RequestListenerProcessError"); + public static EventId AttachedToQueue = new EventId(17, "AttachedToQueue"); + public static EventId SetUrlPropertyError = new EventId(18, "SetUrlPropertyError"); + public static EventId RegisteringPrefix = new EventId(19, "RegisteringPrefix"); + public static EventId UnregisteringPrefix = new EventId(20, "UnregisteringPrefix"); + public static EventId CloseUrlGroupError = new EventId(21, "CloseUrlGroupError"); + public static EventId ChannelBindingUnSupported = new EventId(22, "ChannelBindingUnSupported"); + public static EventId ChannelBindingMissing = new EventId(23, "ChannelBindingMissing"); + public static EventId RequestError = new EventId(24, "RequestError"); + public static EventId ErrorInReadingCertificate = new EventId(25, "ErrorInReadingCertificate"); + public static EventId ChannelBindingNeedsHttps = new EventId(26, "ChannelBindingNeedsHttps"); + public static EventId ChannelBindingRetrived = new EventId(27, "ChannelBindingRetrived"); + public static EventId AbortError = new EventId(28, "AbortError"); + public static EventId ErrorWhileRead = new EventId(29, "ErrorWhileRead"); + public static EventId ErrorWhenReadBegun = new EventId(30, "ErrorWhenReadBegun"); + public static EventId ErrorWhenReadAsync = new EventId(31, "ErrorWhenReadAsync"); + public static EventId ErrorWhenFlushAsync = new EventId(32, "ErrorWhenFlushAsync"); + public static EventId FewerBytesThanExpected = new EventId(33, "FewerBytesThanExpected"); + public static EventId WriteError = new EventId(34, "WriteError"); + public static EventId WriteErrorIgnored = new EventId(35, "WriteFlushedIgnored"); + public static EventId WriteFlushCancelled = new EventId(36, "WriteFlushCancelled"); + public static EventId ClearedAddresses = new EventId(37, "ClearedAddresses"); + public static EventId FileSendAsyncError = new EventId(38, "FileSendAsyncError"); + public static EventId FileSendAsyncCancelled = new EventId(39, "FileSendAsyncCancelled"); + public static EventId FileSendAsyncErrorIgnored = new EventId(40, "FileSendAsyncErrorIgnored"); + public static EventId WriteCancelled = new EventId(41, "WriteCancelled"); + public static EventId ListenerStopping = new EventId(42, "ListenerStopping"); + public static EventId ListenerStartError = new EventId(43, "ListenerStartError"); + public static EventId DisconnectTriggered = new EventId(44, "DisconnectTriggered"); + public static EventId ListenerStopError = new EventId(45, "ListenerStopError"); + public static EventId ListenerDisposing = new EventId(46, "ListenerDisposing"); + + + + + } +} diff --git a/src/Servers/HttpSys/src/MessagePump.cs b/src/Servers/HttpSys/src/MessagePump.cs index 9917e69872..f5e90a9876 100644 --- a/src/Servers/HttpSys/src/MessagePump.cs +++ b/src/Servers/HttpSys/src/MessagePump.cs @@ -2,16 +2,18 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; using System.Diagnostics.Contracts; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Hosting.Server.Features; using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.HttpSys.Internal; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using Microsoft.AspNetCore.HttpSys.Internal; namespace Microsoft.AspNetCore.Server.HttpSys { @@ -45,7 +47,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys } _options = options.Value; Listener = new HttpSysListener(_options, loggerFactory); - _logger = LogHelper.CreateLogger(loggerFactory, typeof(MessagePump)); + _logger = loggerFactory.CreateLogger(); if (_options.Authentication.Schemes != AuthenticationSchemes.None) { @@ -74,49 +76,40 @@ namespace Microsoft.AspNetCore.Server.HttpSys } var hostingUrlsPresent = _serverAddresses.Addresses.Count > 0; + var serverAddressCopy = _serverAddresses.Addresses.ToList(); + _serverAddresses.Addresses.Clear(); if (_serverAddresses.PreferHostingUrls && hostingUrlsPresent) { if (_options.UrlPrefixes.Count > 0) { - LogHelper.LogWarning(_logger, $"Overriding endpoints added to {nameof(HttpSysOptions.UrlPrefixes)} since {nameof(IServerAddressesFeature.PreferHostingUrls)} is set to true." + + _logger.LogWarning(LoggerEventIds.ClearedPrefixes, $"Overriding endpoints added to {nameof(HttpSysOptions.UrlPrefixes)} since {nameof(IServerAddressesFeature.PreferHostingUrls)} is set to true." + $" Binding to address(es) '{string.Join(", ", _serverAddresses.Addresses)}' instead. "); Listener.Options.UrlPrefixes.Clear(); } - foreach (var value in _serverAddresses.Addresses) - { - Listener.Options.UrlPrefixes.Add(value); - } + UpdateUrlPrefixes(serverAddressCopy); } else if (_options.UrlPrefixes.Count > 0) { if (hostingUrlsPresent) { - LogHelper.LogWarning(_logger, $"Overriding address(es) '{string.Join(", ", _serverAddresses.Addresses)}'. " + + _logger.LogWarning(LoggerEventIds.ClearedAddresses, $"Overriding address(es) '{string.Join(", ", _serverAddresses.Addresses)}'. " + $"Binding to endpoints added to {nameof(HttpSysOptions.UrlPrefixes)} instead."); _serverAddresses.Addresses.Clear(); } - foreach (var prefix in _options.UrlPrefixes) - { - _serverAddresses.Addresses.Add(prefix.FullPrefix); - } } else if (hostingUrlsPresent) { - foreach (var value in _serverAddresses.Addresses) - { - Listener.Options.UrlPrefixes.Add(value); - } + UpdateUrlPrefixes(serverAddressCopy); } else if (Listener.RequestQueue.Created) { - LogHelper.LogDebug(_logger, $"No listening endpoints were configured. Binding to {Constants.DefaultServerAddress} by default."); + _logger.LogDebug(LoggerEventIds.BindingToDefault, $"No listening endpoints were configured. Binding to {Constants.DefaultServerAddress} by default."); - _serverAddresses.Addresses.Add(Constants.DefaultServerAddress); Listener.Options.UrlPrefixes.Add(Constants.DefaultServerAddress); } // else // Attaching to an existing queue, don't add a default. @@ -130,6 +123,13 @@ namespace Microsoft.AspNetCore.Server.HttpSys Listener.Start(); + // Update server addresses after we start listening as port 0 + // needs to be selected at the point of binding. + foreach (var prefix in _options.UrlPrefixes) + { + _serverAddresses.Addresses.Add(prefix.FullPrefix); + } + ActivateRequestProcessingLimits(); return Task.CompletedTask; @@ -143,6 +143,14 @@ namespace Microsoft.AspNetCore.Server.HttpSys } } + private void UpdateUrlPrefixes(IList serverAddressCopy) + { + foreach (var value in serverAddressCopy) + { + Listener.Options.UrlPrefixes.Add(value); + } + } + // The message pump. // When we start listening for the next request on one thread, we may need to be sure that the // completion continues on another thread as to not block the current request processing. @@ -163,11 +171,11 @@ namespace Microsoft.AspNetCore.Server.HttpSys Contract.Assert(Stopping); if (Stopping) { - LogHelper.LogDebug(_logger, "ListenForNextRequestAsync-Stopping", exception); + _logger.LogDebug(LoggerEventIds.AcceptErrorStopping, exception, "Failed to accept a request, the server is stopping."); } else { - LogHelper.LogException(_logger, "ListenForNextRequestAsync", exception); + _logger.LogError(LoggerEventIds.AcceptError, exception, "Failed to accept a request."); } continue; } @@ -179,7 +187,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys { // Request processing failed to be queued in threadpool // Log the error message, release throttle and move on - LogHelper.LogException(_logger, "ProcessRequestAsync", ex); + _logger.LogError(LoggerEventIds.RequestListenerProcessError, ex, "ProcessRequestAsync"); } } Interlocked.Decrement(ref _acceptorCounts); @@ -216,17 +224,22 @@ namespace Microsoft.AspNetCore.Server.HttpSys } catch (Exception ex) { - LogHelper.LogException(_logger, "ProcessRequestAsync", ex); + _logger.LogError(LoggerEventIds.RequestProcessError, ex, "ProcessRequestAsync"); _application.DisposeContext(context, ex); if (requestContext.Response.HasStarted) { + // HTTP/2 INTERNAL_ERROR = 0x2 https://tools.ietf.org/html/rfc7540#section-7 + // Otherwise the default is Cancel = 0x8. + requestContext.SetResetCode(2); requestContext.Abort(); } else { // We haven't sent a response yet, try to send a 500 Internal Server Error requestContext.Response.Headers.IsReadOnly = false; + requestContext.Response.Trailers.IsReadOnly = false; requestContext.Response.Headers.Clear(); + requestContext.Response.Trailers.Clear(); SetFatalResponse(requestContext, 500); } } @@ -234,14 +247,14 @@ namespace Microsoft.AspNetCore.Server.HttpSys { if (Interlocked.Decrement(ref _outstandingRequests) == 0 && Stopping) { - LogHelper.LogInfo(_logger, "All requests drained."); + _logger.LogInformation(LoggerEventIds.RequestsDrained, "All requests drained."); _shutdownSignal.TrySetResult(0); } } } catch (Exception ex) { - LogHelper.LogException(_logger, "ProcessRequestAsync", ex); + _logger.LogError(LoggerEventIds.RequestError, ex, "ProcessRequestAsync"); requestContext.Abort(); } } @@ -261,7 +274,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys { if (Interlocked.Exchange(ref _shutdownSignalCompleted, 1) == 0) { - LogHelper.LogInfo(_logger, "Canceled, terminating " + _outstandingRequests + " request(s)."); + _logger.LogInformation(LoggerEventIds.StopCancelled, "Canceled, terminating " + _outstandingRequests + " request(s)."); _shutdownSignal.TrySetResult(null); } }); @@ -279,7 +292,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys // Wait for active requests to drain if (_outstandingRequests > 0) { - LogHelper.LogInfo(_logger, "Stopping, waiting for " + _outstandingRequests + " request(s) to drain."); + _logger.LogInformation(LoggerEventIds.WaitingForRequestsToDrain, "Stopping, waiting for " + _outstandingRequests + " request(s) to drain."); RegisterCancelation(); } else diff --git a/src/Servers/HttpSys/src/Microsoft.AspNetCore.Server.HttpSys.csproj b/src/Servers/HttpSys/src/Microsoft.AspNetCore.Server.HttpSys.csproj index 06d5ad8b82..9877d26985 100644 --- a/src/Servers/HttpSys/src/Microsoft.AspNetCore.Server.HttpSys.csproj +++ b/src/Servers/HttpSys/src/Microsoft.AspNetCore.Server.HttpSys.csproj @@ -1,4 +1,4 @@ - + ASP.NET Core HTTP server that uses the Windows HTTP Server API. @@ -13,6 +13,10 @@ + + + + diff --git a/src/Servers/HttpSys/src/NativeInterop/DisconnectListener.cs b/src/Servers/HttpSys/src/NativeInterop/DisconnectListener.cs index ecef12b989..d624808d04 100644 --- a/src/Servers/HttpSys/src/NativeInterop/DisconnectListener.cs +++ b/src/Servers/HttpSys/src/NativeInterop/DisconnectListener.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; @@ -34,7 +34,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys } catch (Win32Exception exception) { - LogHelper.LogException(_logger, "GetConnectionToken", exception); + _logger.LogError(LoggerEventIds.DisconnectRegistrationError, exception, "Unable to register for disconnect notifications."); return CancellationToken.None; } } @@ -54,12 +54,12 @@ namespace Microsoft.AspNetCore.Server.HttpSys { // Race condition on creation has no side effects var cancellation = new ConnectionCancellation(this); - return _connectionCancellationTokens.GetOrAdd(connectionId, cancellation); + return _connectionCancellationTokens.GetOrAdd(connectionId, cancellation); } private unsafe CancellationToken CreateDisconnectToken(ulong connectionId) { - LogHelper.LogDebug(_logger, "CreateDisconnectToken", "Registering connection for disconnect for connection ID: " + connectionId); + _logger.LogDebug(LoggerEventIds.RegisterDisconnectListener, "CreateDisconnectToken; Registering connection for disconnect for connection ID: {0}" , connectionId); // Create a nativeOverlapped callback so we can register for disconnect callback var cts = new CancellationTokenSource(); @@ -70,8 +70,8 @@ namespace Microsoft.AspNetCore.Server.HttpSys nativeOverlapped = new SafeNativeOverlapped(boundHandle, boundHandle.AllocateNativeOverlapped( (errorCode, numBytes, overlappedPtr) => { - LogHelper.LogDebug(_logger, "CreateDisconnectToken", "http.sys disconnect callback fired for connection ID: " + connectionId); - + _logger.LogDebug(LoggerEventIds.DisconnectTriggered, "CreateDisconnectToken; http.sys disconnect callback fired for connection ID: {0}" , connectionId); + // Free the overlapped nativeOverlapped.Dispose(); @@ -84,7 +84,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys } catch (AggregateException exception) { - LogHelper.LogException(_logger, "CreateDisconnectToken Callback", exception); + _logger.LogError(LoggerEventIds.DisconnectHandlerError, exception, "CreateDisconnectToken Callback"); } }, null, null)); @@ -98,7 +98,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys catch (Win32Exception exception) { statusCode = (uint)exception.NativeErrorCode; - LogHelper.LogException(_logger, "CreateDisconnectToken", exception); + _logger.LogError(LoggerEventIds.DisconnectRegistrationError, exception, "CreateDisconnectToken"); } if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_IO_PENDING && @@ -108,7 +108,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys nativeOverlapped.Dispose(); ConnectionCancellation ignored; _connectionCancellationTokens.TryRemove(connectionId, out ignored); - LogHelper.LogDebug(_logger, "HttpWaitForDisconnectEx", new Win32Exception((int)statusCode)); + _logger.LogDebug(LoggerEventIds.UnknownDisconnectError, new Win32Exception((int)statusCode), "HttpWaitForDisconnectEx"); cts.Cancel(); } diff --git a/src/Servers/HttpSys/src/NativeInterop/HttpApi.cs b/src/Servers/HttpSys/src/NativeInterop/HttpApi.cs index c4e23d3b3d..6781465bc2 100644 --- a/src/Servers/HttpSys/src/NativeInterop/HttpApi.cs +++ b/src/Servers/HttpSys/src/NativeInterop/HttpApi.cs @@ -70,6 +70,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys [DllImport(HTTPAPI, ExactSpelling = true, CallingConvention = CallingConvention.StdCall, SetLastError = true)] internal static extern unsafe uint HttpCloseRequestQueue(IntPtr pReqQueueHandle); + internal delegate uint HttpSetRequestPropertyInvoker(SafeHandle requestQueueHandle, ulong requestId, HTTP_REQUEST_PROPERTY propertyId, void* input, uint inputSize, IntPtr overlapped); private static HTTPAPI_VERSION version; @@ -106,6 +107,11 @@ namespace Microsoft.AspNetCore.Server.HttpSys } } + internal static SafeLibraryHandle HttpApiModule { get; private set; } + internal static HttpSetRequestPropertyInvoker HttpSetRequestProperty { get; private set; } + internal static bool SupportsTrailers { get; private set; } + internal static bool SupportsReset { get; private set; } + static HttpApi() { InitHttpApi(2, 0); @@ -119,6 +125,16 @@ namespace Microsoft.AspNetCore.Server.HttpSys var statusCode = HttpInitialize(version, (uint)HTTP_FLAGS.HTTP_INITIALIZE_SERVER, null); supported = statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS; + + if (supported) + { + HttpApiModule = SafeLibraryHandle.Open(HTTPAPI); + HttpSetRequestProperty = HttpApiModule.GetProcAddress("HttpSetRequestProperty", throwIfNotFound: false); + + SupportsReset = HttpSetRequestProperty != null; + // Trailers support was added in the same release as Reset, but there's no method we can export to check it directly. + SupportsTrailers = SupportsReset; + } } private static volatile bool supported; diff --git a/src/Servers/HttpSys/src/NativeInterop/RequestQueue.cs b/src/Servers/HttpSys/src/NativeInterop/RequestQueue.cs index 00ecc78de9..4fb2d60280 100644 --- a/src/Servers/HttpSys/src/NativeInterop/RequestQueue.cs +++ b/src/Servers/HttpSys/src/NativeInterop/RequestQueue.cs @@ -83,7 +83,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys if (!Created) { - _logger.LogInformation("Attached to an existing request queue '{requestQueueName}', some options do not apply.", requestQueueName); + _logger.LogInformation(LoggerEventIds.AttachedToQueue, "Attached to an existing request queue '{requestQueueName}', some options do not apply.", requestQueueName); } } diff --git a/src/Servers/HttpSys/src/NativeInterop/SafeLibraryHandle.cs b/src/Servers/HttpSys/src/NativeInterop/SafeLibraryHandle.cs new file mode 100644 index 0000000000..bf3254c6d6 --- /dev/null +++ b/src/Servers/HttpSys/src/NativeInterop/SafeLibraryHandle.cs @@ -0,0 +1,103 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Runtime.ConstrainedExecution; +using System.Runtime.InteropServices; +using System.Security; +using Microsoft.Win32.SafeHandles; + +namespace Microsoft.AspNetCore.Server.HttpSys +{ + /// + /// Represents a handle to a Windows module (DLL). + /// + internal unsafe sealed class SafeLibraryHandle : SafeHandleZeroOrMinusOneIsInvalid + { + // Called by P/Invoke when returning SafeHandles + private SafeLibraryHandle() + : base(ownsHandle: true) + { + } + + /// + /// Returns a value stating whether the library exports a given proc. + /// + public bool DoesProcExist(string lpProcName) + { + IntPtr pfnProc = UnsafeNativeMethods.GetProcAddress(this, lpProcName); + return (pfnProc != IntPtr.Zero); + } + + /// + /// Gets a delegate pointing to a given export from this library. + /// + public TDelegate GetProcAddress(string lpProcName, bool throwIfNotFound = true) where TDelegate : class + { + IntPtr pfnProc = UnsafeNativeMethods.GetProcAddress(this, lpProcName); + if (pfnProc == IntPtr.Zero) + { + if (throwIfNotFound) + { + UnsafeNativeMethods.ThrowExceptionForLastWin32Error(); + } + else + { + return null; + } + } + + return Marshal.GetDelegateForFunctionPointer(pfnProc); + } + + /// + /// Opens a library. If 'filename' is not a fully-qualified path, the default search path is used. + /// + public static SafeLibraryHandle Open(string filename) + { + const uint LOAD_LIBRARY_SEARCH_SYSTEM32 = 0x00000800U; // from libloaderapi.h + + SafeLibraryHandle handle = UnsafeNativeMethods.LoadLibraryEx(filename, IntPtr.Zero, LOAD_LIBRARY_SEARCH_SYSTEM32); + if (handle == null || handle.IsInvalid) + { + UnsafeNativeMethods.ThrowExceptionForLastWin32Error(); + } + return handle; + } + + // Do not provide a finalizer - SafeHandle's critical finalizer will call ReleaseHandle for you. + protected override bool ReleaseHandle() + { + return UnsafeNativeMethods.FreeLibrary(handle); + } + + [SuppressUnmanagedCodeSecurity] + private static class UnsafeNativeMethods + { + // http://msdn.microsoft.com/en-us/library/ms683152(v=vs.85).aspx + [return: MarshalAs(UnmanagedType.Bool)] + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + [DllImport("kernel32.dll", CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Unicode)] + internal static extern bool FreeLibrary(IntPtr hModule); + + // http://msdn.microsoft.com/en-us/library/ms683212(v=vs.85).aspx + [DllImport("kernel32.dll", CallingConvention = CallingConvention.Winapi, SetLastError = true)] + internal static extern IntPtr GetProcAddress( + [In] SafeLibraryHandle hModule, + [In, MarshalAs(UnmanagedType.LPStr)] string lpProcName); + + // http://msdn.microsoft.com/en-us/library/windows/desktop/ms684179(v=vs.85).aspx + [DllImport("kernel32.dll", EntryPoint = "LoadLibraryExW", CallingConvention = CallingConvention.Winapi, SetLastError = true)] + internal static extern SafeLibraryHandle LoadLibraryEx( + [In, MarshalAs(UnmanagedType.LPWStr)] string lpFileName, + [In] IntPtr hFile, + [In] uint dwFlags); + + internal static void ThrowExceptionForLastWin32Error() + { + int hr = Marshal.GetHRForLastWin32Error(); + Marshal.ThrowExceptionForHR(hr); + } + } + } +} diff --git a/src/Servers/HttpSys/src/NativeInterop/UrlGroup.cs b/src/Servers/HttpSys/src/NativeInterop/UrlGroup.cs index 8f33c7b678..6600cffe60 100644 --- a/src/Servers/HttpSys/src/NativeInterop/UrlGroup.cs +++ b/src/Servers/HttpSys/src/NativeInterop/UrlGroup.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; @@ -52,7 +52,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys } internal void SetProperty(HttpApiTypes.HTTP_SERVER_PROPERTY property, IntPtr info, uint infosize, bool throwOnError = true) - { + { Debug.Assert(info != IntPtr.Zero, "SetUrlGroupProperty called with invalid pointer"); CheckDisposed(); @@ -61,7 +61,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS) { var exception = new HttpSysException((int)statusCode); - LogHelper.LogException(_logger, "SetUrlGroupProperty", exception); + _logger.LogError(LoggerEventIds.SetUrlPropertyError, exception, "SetUrlGroupProperty"); if (throwOnError) { throw exception; @@ -71,9 +71,8 @@ namespace Microsoft.AspNetCore.Server.HttpSys internal void RegisterPrefix(string uriPrefix, int contextId) { - LogHelper.LogInfo(_logger, "Listening on prefix: " + uriPrefix); + _logger.LogDebug(LoggerEventIds.RegisteringPrefix, "Listening on prefix: {0}" , uriPrefix); CheckDisposed(); - var statusCode = HttpApi.HttpAddUrlToUrlGroup(Id, uriPrefix, (ulong)contextId, 0); if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS) @@ -88,10 +87,10 @@ namespace Microsoft.AspNetCore.Server.HttpSys } } } - + internal bool UnregisterPrefix(string uriPrefix) { - LogHelper.LogInfo(_logger, "Stop listening on prefix: " + uriPrefix); + _logger.LogInformation(LoggerEventIds.UnregisteringPrefix, "Stop listening on prefix: {0}" , uriPrefix); CheckDisposed(); var statusCode = HttpApi.HttpRemoveUrlFromUrlGroup(Id, uriPrefix, 0); @@ -118,7 +117,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS) { - LogHelper.LogError(_logger, "CleanupV2Config", "Result: " + statusCode); + _logger.LogError(LoggerEventIds.CloseUrlGroupError, "HttpCloseUrlGroup; Result: {0}" , statusCode); } Id = 0; } diff --git a/src/Servers/HttpSys/src/RequestProcessing/ClientCertLoader.cs b/src/Servers/HttpSys/src/RequestProcessing/ClientCertLoader.cs index 74d7ed902f..8f9bd8132e 100644 --- a/src/Servers/HttpSys/src/RequestProcessing/ClientCertLoader.cs +++ b/src/Servers/HttpSys/src/RequestProcessing/ClientCertLoader.cs @@ -400,13 +400,13 @@ namespace Microsoft.AspNetCore.Server.HttpSys } else if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_INVALID_PARAMETER) { - LogHelper.LogError(logger, "GetChannelBindingFromTls", "Channel binding is not supported."); + logger.LogError(LoggerEventIds.ChannelBindingUnSupported, "GetChannelBindingFromTls; Channel binding is not supported."); return null; // old schannel library which doesn't support CBT } else { // It's up to the consumer to fail if the missing ChannelBinding matters to them. - LogHelper.LogException(logger, "GetChannelBindingFromTls", new HttpSysException((int)statusCode)); + logger.LogError(LoggerEventIds.ChannelBindingMissing, new HttpSysException((int)statusCode), "GetChannelBindingFromTls"); break; } } diff --git a/src/Servers/HttpSys/src/RequestProcessing/Request.cs b/src/Servers/HttpSys/src/RequestProcessing/Request.cs index 049789abea..84fb017393 100644 --- a/src/Servers/HttpSys/src/RequestProcessing/Request.cs +++ b/src/Servers/HttpSys/src/RequestProcessing/Request.cs @@ -293,7 +293,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys Protocol = handshake.Protocol; // The OS considers client and server TLS as different enum values. SslProtocols choose to combine those for some reason. // We need to fill in the client bits so the enum shows the expected protocol. - // https://docs.microsoft.com/en-us/windows/desktop/api/schannel/ns-schannel-_secpkgcontext_connectioninfo + // https://docs.microsoft.com/windows/desktop/api/schannel/ns-schannel-_secpkgcontext_connectioninfo // Compare to https://referencesource.microsoft.com/#System/net/System/Net/SecureProtocols/_SslState.cs,8905d1bf17729de3 #pragma warning disable CS0618 // Type or member is obsolete if ((Protocol & SslProtocols.Ssl2) != 0) @@ -338,11 +338,11 @@ namespace Microsoft.AspNetCore.Server.HttpSys } catch (CryptographicException ce) { - RequestContext.Logger.LogDebug(ce, "An error occurred reading the client certificate."); + RequestContext.Logger.LogDebug(LoggerEventIds.ErrorInReadingCertificate, ce, "An error occurred reading the client certificate."); } catch (SecurityException se) { - RequestContext.Logger.LogDebug(se, "An error occurred reading the client certificate."); + RequestContext.Logger.LogDebug(LoggerEventIds.ErrorInReadingCertificate, se, "An error occurred reading the client certificate."); } } diff --git a/src/Servers/HttpSys/src/RequestProcessing/RequestContext.cs b/src/Servers/HttpSys/src/RequestProcessing/RequestContext.cs index 89405cf538..605288b397 100644 --- a/src/Servers/HttpSys/src/RequestProcessing/RequestContext.cs +++ b/src/Servers/HttpSys/src/RequestProcessing/RequestContext.cs @@ -4,6 +4,7 @@ using System; using System.Diagnostics; using System.IO; +using System.Runtime.CompilerServices; using System.Security.Authentication.ExtendedProtection; using System.Security.Claims; using System.Security.Principal; @@ -119,14 +120,14 @@ namespace Microsoft.AspNetCore.Server.HttpSys { if (!Request.IsHttps) { - LogHelper.LogDebug(Logger, "TryGetChannelBinding", "Channel binding requires HTTPS."); + Logger.LogDebug(LoggerEventIds.ChannelBindingNeedsHttps,"TryGetChannelBinding; Channel binding requires HTTPS."); return false; } value = ClientCertLoader.GetChannelBindingFromTls(Server.RequestQueue, Request.UConnectionId, Logger); Debug.Assert(value != null, "GetChannelBindingFromTls returned null even though OS supposedly supports Extended Protection"); - LogHelper.LogInfo(Logger, "Channel binding retrieved."); + Logger.LogDebug(LoggerEventIds.ChannelBindingRetrived,"Channel binding retrieved."); return value != null; } @@ -176,7 +177,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys } catch (Exception ex) { - LogHelper.LogDebug(Logger, "Abort", ex); + Logger.LogDebug(LoggerEventIds.AbortError, ex, "Abort"); } _requestAbortSource.Dispose(); } @@ -218,5 +219,25 @@ namespace Microsoft.AspNetCore.Server.HttpSys // RequestQueueHandle may have been closed } } + + // You must still call ForceCancelRequest after this. + internal unsafe void SetResetCode(int errorCode) + { + if (!HttpApi.SupportsReset) + { + return; + } + + try + { + var streamError = new HttpApiTypes.HTTP_REQUEST_PROPERTY_STREAM_ERROR() { ErrorCode = (uint)errorCode }; + var statusCode = HttpApi.HttpSetRequestProperty(Server.RequestQueue.Handle, Request.RequestId, HttpApiTypes.HTTP_REQUEST_PROPERTY.HttpRequestPropertyStreamError, (void*)&streamError, + (uint)sizeof(HttpApiTypes.HTTP_REQUEST_PROPERTY_STREAM_ERROR), IntPtr.Zero); + } + catch (ObjectDisposedException) + { + // RequestQueueHandle may have been closed + } + } } } diff --git a/src/Servers/HttpSys/src/RequestProcessing/RequestStream.cs b/src/Servers/HttpSys/src/RequestProcessing/RequestStream.cs index f0bf45d68f..758a060b94 100644 --- a/src/Servers/HttpSys/src/RequestProcessing/RequestStream.cs +++ b/src/Servers/HttpSys/src/RequestProcessing/RequestStream.cs @@ -165,7 +165,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS && statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_HANDLE_EOF) { Exception exception = new IOException(string.Empty, new HttpSysException((int)statusCode)); - LogHelper.LogException(Logger, "Read", exception); + Logger.LogError(LoggerEventIds.ErrorWhileRead, exception, "Read"); Abort(); throw exception; } @@ -242,7 +242,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys } catch (Exception e) { - LogHelper.LogException(Logger, "BeginRead", e); + Logger.LogError(LoggerEventIds.ErrorWhenReadBegun, e, "BeginRead"); asyncResult.Dispose(); throw; } @@ -258,7 +258,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys else { Exception exception = new IOException(string.Empty, new HttpSysException((int)statusCode)); - LogHelper.LogException(Logger, "BeginRead", exception); + Logger.LogError(LoggerEventIds.ErrorWhenReadBegun, exception, "BeginRead"); Abort(); throw exception; } @@ -365,7 +365,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys { asyncResult.Dispose(); Abort(); - LogHelper.LogException(Logger, "ReadAsync", e); + Logger.LogError(LoggerEventIds.ErrorWhenReadAsync, e, "ReadAsync"); throw; } @@ -386,7 +386,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys else { Exception exception = new IOException(string.Empty, new HttpSysException((int)statusCode)); - LogHelper.LogException(Logger, "ReadAsync", exception); + Logger.LogError(LoggerEventIds.ErrorWhenReadAsync, exception, "ReadAsync"); Abort(); throw exception; } diff --git a/src/Servers/HttpSys/src/RequestProcessing/Response.cs b/src/Servers/HttpSys/src/RequestProcessing/Response.cs index 5f29763d2f..3f8dd86f7e 100644 --- a/src/Servers/HttpSys/src/RequestProcessing/Response.cs +++ b/src/Servers/HttpSys/src/RequestProcessing/Response.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Net; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; @@ -28,6 +29,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys private long _expectedBodyLength; private BoundaryType _boundaryType; private HttpApiTypes.HTTP_RESPONSE_V2 _nativeResponse; + private HeaderCollection _trailers; internal Response(RequestContext requestContext) { @@ -142,6 +144,16 @@ namespace Microsoft.AspNetCore.Server.HttpSys public HeaderCollection Headers { get; } + public HeaderCollection Trailers => _trailers ??= new HeaderCollection(checkTrailers: true) { IsReadOnly = BodyIsFinished }; + + internal bool HasTrailers => _trailers?.Count > 0; + + // Trailers are supported on this OS, it's HTTP/2, and the app added a Trailer response header to announce trailers were intended. + // Needed to delay the completion of Content-Length responses. + internal bool TrailersExpected => HasTrailers + || (HttpApi.SupportsTrailers && Request.ProtocolVersion >= HttpVersion.Version20 + && Headers.ContainsKey(HttpKnownHeaderNames.Trailer)); + internal long ExpectedBodyLength { get { return _expectedBodyLength; } @@ -168,6 +180,16 @@ namespace Microsoft.AspNetCore.Server.HttpSys } } + // The response is being finished with or without trailers. Mark them as readonly to inform + // callers if they try to add them too late. E.g. after Content-Length or CompleteAsync(). + internal void MakeTrailersReadOnly() + { + if (_trailers != null) + { + _trailers.IsReadOnly = true; + } + } + internal void Abort() { // Update state for HasStarted. Do not attempt a graceful Dispose. @@ -400,7 +422,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys _boundaryType = BoundaryType.ContentLength; // ComputeLeftToWrite checks for HEAD requests when setting _leftToWrite _expectedBodyLength = responseContentLength.Value; - if (_expectedBodyLength == writeCount && !isHeadRequest) + if (_expectedBodyLength == writeCount && !isHeadRequest && !TrailersExpected) { // A single write with the whole content-length. Http.Sys will set the content-length for us in this scenario. // If we don't remove it then range requests served from cache will have two. @@ -430,6 +452,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys else { // v1.0 and the length cannot be determined, so we must close the connection after writing data + // Or v2.0 and chunking isn't required. keepConnectionAlive = false; _boundaryType = BoundaryType.Close; } @@ -622,6 +645,73 @@ namespace Microsoft.AspNetCore.Server.HttpSys } } + internal unsafe void SerializeTrailers(HttpApiTypes.HTTP_DATA_CHUNK[] dataChunks, int currentChunk, List pins) + { + Debug.Assert(currentChunk == dataChunks.Length - 1); + Debug.Assert(HasTrailers); + MakeTrailersReadOnly(); + var trailerCount = 0; + + foreach (var trailerPair in Trailers) + { + trailerCount += trailerPair.Value.Count; + } + + var pinnedHeaders = new List(); + + var unknownHeaders = new HttpApiTypes.HTTP_UNKNOWN_HEADER[trailerCount]; + var gcHandle = GCHandle.Alloc(unknownHeaders, GCHandleType.Pinned); + pinnedHeaders.Add(gcHandle); + dataChunks[currentChunk].DataChunkType = HttpApiTypes.HTTP_DATA_CHUNK_TYPE.HttpDataChunkTrailers; + dataChunks[currentChunk].trailers.trailerCount = (ushort)trailerCount; + dataChunks[currentChunk].trailers.pTrailers = gcHandle.AddrOfPinnedObject(); + + try + { + var unknownHeadersOffset = 0; + + foreach (var headerPair in Trailers) + { + if (headerPair.Value.Count == 0) + { + continue; + } + + var headerName = headerPair.Key; + var headerValues = headerPair.Value; + + for (int headerValueIndex = 0; headerValueIndex < headerValues.Count; headerValueIndex++) + { + // Add Name + var bytes = HeaderEncoding.GetBytes(headerName); + unknownHeaders[unknownHeadersOffset].NameLength = (ushort)bytes.Length; + gcHandle = GCHandle.Alloc(bytes, GCHandleType.Pinned); + pinnedHeaders.Add(gcHandle); + unknownHeaders[unknownHeadersOffset].pName = (byte*)gcHandle.AddrOfPinnedObject(); + + // Add Value + var headerValue = headerValues[headerValueIndex] ?? string.Empty; + bytes = HeaderEncoding.GetBytes(headerValue); + unknownHeaders[unknownHeadersOffset].RawValueLength = (ushort)bytes.Length; + gcHandle = GCHandle.Alloc(bytes, GCHandleType.Pinned); + pinnedHeaders.Add(gcHandle); + unknownHeaders[unknownHeadersOffset].pRawValue = (byte*)gcHandle.AddrOfPinnedObject(); + unknownHeadersOffset++; + } + } + + Debug.Assert(unknownHeadersOffset == trailerCount); + } + catch + { + FreePinnedHeaders(pinnedHeaders); + throw; + } + + // Success, keep the pins. + pins.AddRange(pinnedHeaders); + } + // Subset of ComputeHeaders internal void SendOpaqueUpgrade() { diff --git a/src/Servers/HttpSys/src/RequestProcessing/ResponseBody.cs b/src/Servers/HttpSys/src/RequestProcessing/ResponseBody.cs index 8f6341db63..6b181cc312 100644 --- a/src/Servers/HttpSys/src/RequestProcessing/ResponseBody.cs +++ b/src/Servers/HttpSys/src/RequestProcessing/ResponseBody.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.IO; +using System.Net; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; @@ -126,9 +127,12 @@ namespace Microsoft.AspNetCore.Server.HttpSys var flags = ComputeLeftToWrite(data.Count, endOfRequest); if (endOfRequest && _leftToWrite > 0) { + if (!RequestContext.DisconnectToken.IsCancellationRequested) + { + // This is logged rather than thrown because it is too late for an exception to be visible in user code. + Logger.LogError(LoggerEventIds.FewerBytesThanExpected, "ResponseStream::Dispose; Fewer bytes were written than were specified in the Content-Length."); + } _requestContext.Abort(); - // This is logged rather than thrown because it is too late for an exception to be visible in user code. - LogHelper.LogError(Logger, "ResponseStream::Dispose", "Fewer bytes were written than were specified in the Content-Length."); return; } @@ -171,14 +175,14 @@ namespace Microsoft.AspNetCore.Server.HttpSys if (ThrowWriteExceptions) { var exception = new IOException(string.Empty, new HttpSysException((int)statusCode)); - LogHelper.LogException(Logger, "Flush", exception); + Logger.LogError(LoggerEventIds.WriteError, exception, "Flush"); Abort(); throw exception; } else { // Abort the request but do not close the stream, let future writes complete silently - LogHelper.LogDebug(Logger, "Flush", $"Ignored write exception: {statusCode}"); + Logger.LogDebug(LoggerEventIds.WriteErrorIgnored, $"Flush; Ignored write exception: {statusCode}"); Abort(dispose: false); } } @@ -187,26 +191,34 @@ namespace Microsoft.AspNetCore.Server.HttpSys private List PinDataBuffers(bool endOfRequest, ArraySegment data, out HttpApiTypes.HTTP_DATA_CHUNK[] dataChunks) { var pins = new List(); + var hasData = data.Count > 0; var chunked = _requestContext.Response.BoundaryType == BoundaryType.Chunked; + var addTrailers = endOfRequest && _requestContext.Response.HasTrailers; + Debug.Assert(!(addTrailers && chunked), "Trailers aren't currently supported for HTTP/1.1 chunking."); var currentChunk = 0; // Figure out how many data chunks - if (chunked && data.Count == 0 && endOfRequest) + if (chunked && !hasData && endOfRequest) { dataChunks = new HttpApiTypes.HTTP_DATA_CHUNK[1]; SetDataChunk(dataChunks, ref currentChunk, pins, new ArraySegment(Helpers.ChunkTerminator)); return pins; } - else if (data.Count == 0) + else if (!hasData && !addTrailers) { // No data dataChunks = new HttpApiTypes.HTTP_DATA_CHUNK[0]; return pins; } - var chunkCount = 1; - if (chunked) + var chunkCount = hasData ? 1 : 0; + if (addTrailers) { + chunkCount++; + } + else if (chunked) // HTTP/1.1 chunking, not currently supported with trailers + { + Debug.Assert(hasData); // Chunk framing chunkCount += 2; @@ -216,6 +228,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys chunkCount += 1; } } + dataChunks = new HttpApiTypes.HTTP_DATA_CHUNK[chunkCount]; if (chunked) @@ -224,7 +237,10 @@ namespace Microsoft.AspNetCore.Server.HttpSys SetDataChunk(dataChunks, ref currentChunk, pins, chunkHeaderBuffer); } - SetDataChunk(dataChunks, ref currentChunk, pins, data); + if (hasData) + { + SetDataChunk(dataChunks, ref currentChunk, pins, data); + } if (chunked) { @@ -236,6 +252,15 @@ namespace Microsoft.AspNetCore.Server.HttpSys } } + if (addTrailers) + { + _requestContext.Response.SerializeTrailers(dataChunks, currentChunk, pins); + } + else if (endOfRequest) + { + _requestContext.Response.MakeTrailersReadOnly(); + } + return pins; } @@ -320,7 +345,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys } catch (Exception e) { - LogHelper.LogException(Logger, "FlushAsync", e); + Logger.LogError(LoggerEventIds.ErrorWhenFlushAsync, e, "FlushAsync"); asyncResult.Dispose(); Abort(); throw; @@ -330,21 +355,21 @@ namespace Microsoft.AspNetCore.Server.HttpSys { if (cancellationToken.IsCancellationRequested) { - LogHelper.LogDebug(Logger, "FlushAsync", $"Write cancelled with error code: {statusCode}"); + Logger.LogDebug(LoggerEventIds.WriteFlushCancelled,$"FlushAsync; Write cancelled with error code: {statusCode}"); asyncResult.Cancel(ThrowWriteExceptions); } else if (ThrowWriteExceptions) { asyncResult.Dispose(); Exception exception = new IOException(string.Empty, new HttpSysException((int)statusCode)); - LogHelper.LogException(Logger, "FlushAsync", exception); + Logger.LogError(LoggerEventIds.ErrorWhenFlushAsync, exception, "FlushAsync"); Abort(); throw exception; } else { // Abort the request but do not close the stream, let future writes complete silently - LogHelper.LogDebug(Logger, "FlushAsync", $"Ignored write exception: {statusCode}"); + Logger.LogDebug(LoggerEventIds.WriteErrorIgnored,$"FlushAsync; Ignored write exception: {statusCode}"); asyncResult.FailSilently(); } } @@ -433,7 +458,8 @@ namespace Microsoft.AspNetCore.Server.HttpSys { flags |= HttpApiTypes.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_DISCONNECT; } - else if (!endOfRequest && _leftToWrite != writeCount) + else if (!endOfRequest + && (_leftToWrite != writeCount || _requestContext.Response.TrailersExpected)) { flags |= HttpApiTypes.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_MORE_DATA; } @@ -444,9 +470,10 @@ namespace Microsoft.AspNetCore.Server.HttpSys // keep track of the data transferred _leftToWrite -= writeCount; } - if (_leftToWrite == 0) + if (_leftToWrite == 0 && !_requestContext.Response.TrailersExpected) { // in this case we already passed 0 as the flag, so we don't need to call HttpSendResponseEntityBody() when we Close() + _requestContext.Response.MakeTrailersReadOnly(); _disposed = true; } // else -1 unlimited @@ -612,7 +639,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys } catch (Exception e) { - LogHelper.LogException(Logger, "SendFileAsync", e); + Logger.LogError(LoggerEventIds.FileSendAsyncError, e, "SendFileAsync"); asyncResult.Dispose(); Abort(); throw; @@ -622,21 +649,21 @@ namespace Microsoft.AspNetCore.Server.HttpSys { if (cancellationToken.IsCancellationRequested) { - LogHelper.LogDebug(Logger, "SendFileAsync", $"Write cancelled with error code: {statusCode}"); + Logger.LogDebug(LoggerEventIds.FileSendAsyncCancelled,$"SendFileAsync; Write cancelled with error code: {statusCode}"); asyncResult.Cancel(ThrowWriteExceptions); } else if (ThrowWriteExceptions) { asyncResult.Dispose(); var exception = new IOException(string.Empty, new HttpSysException((int)statusCode)); - LogHelper.LogException(Logger, "SendFileAsync", exception); + Logger.LogError(LoggerEventIds.FileSendAsyncError, exception, "SendFileAsync"); Abort(); throw exception; } else { // Abort the request but do not close the stream, let future writes complete silently - LogHelper.LogDebug(Logger, "SendFileAsync", $"Ignored write exception: {statusCode}"); + Logger.LogDebug(LoggerEventIds.FileSendAsyncErrorIgnored,$"SendFileAsync; Ignored write exception: {statusCode}"); asyncResult.FailSilently(); } } @@ -666,8 +693,8 @@ namespace Microsoft.AspNetCore.Server.HttpSys { return; } - _disposed = true; FlushInternal(endOfRequest: true); + _disposed = true; } } finally diff --git a/src/Servers/HttpSys/src/RequestProcessing/ResponseStreamAsyncResult.cs b/src/Servers/HttpSys/src/RequestProcessing/ResponseStreamAsyncResult.cs index 32a90e4b51..c3248c3b72 100644 --- a/src/Servers/HttpSys/src/RequestProcessing/ResponseStreamAsyncResult.cs +++ b/src/Servers/HttpSys/src/RequestProcessing/ResponseStreamAsyncResult.cs @@ -8,6 +8,7 @@ using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.HttpSys.Internal; +using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.Server.HttpSys { @@ -232,18 +233,18 @@ namespace Microsoft.AspNetCore.Server.HttpSys { if (asyncResult._cancellationToken.IsCancellationRequested) { - LogHelper.LogDebug(logger, "FlushAsync.IOCompleted", $"Write cancelled with error code: {errorCode}"); + logger.LogDebug(LoggerEventIds.WriteCancelled,$"FlushAsync.IOCompleted; Write cancelled with error code: {errorCode}"); asyncResult.Cancel(asyncResult._responseStream.ThrowWriteExceptions); } else if (asyncResult._responseStream.ThrowWriteExceptions) { var exception = new IOException(string.Empty, new HttpSysException((int)errorCode)); - LogHelper.LogException(logger, "FlushAsync.IOCompleted", exception); + logger.LogError(LoggerEventIds.WriteError, exception, "FlushAsync.IOCompleted"); asyncResult.Fail(exception); } else { - LogHelper.LogDebug(logger, "FlushAsync.IOCompleted", $"Ignored write exception: {errorCode}"); + logger.LogDebug(LoggerEventIds.WriteErrorIgnored, $"FlushAsync.IOCompleted; Ignored write exception: {errorCode}"); asyncResult.FailSilently(); } } @@ -258,7 +259,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys // TODO: Verbose log // for (int i = 0; i < asyncResult._dataChunks.Length; i++) // { - // Logging.Dump(Logging.HttpListener, asyncResult, "Callback", (IntPtr)asyncResult._dataChunks[0].fromMemory.pBuffer, (int)asyncResult._dataChunks[0].fromMemory.BufferLength); + // Logging.Dump(Logging.HttpListener, asyncResult, "Callback", (IntPtr)asyncResult._dataChunks[0].fromMemory.pBuffer, (int)asyncResult._dataChunks[0].fromMemory.BufferLength); // } } asyncResult.Complete(); @@ -266,7 +267,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys } catch (Exception e) { - LogHelper.LogException(logger, "FlushAsync.IOCompleted", e); + logger.LogError(LoggerEventIds.WriteError, e, "FlushAsync.IOCompleted"); asyncResult.Fail(e); } } diff --git a/src/Servers/HttpSys/src/Resources.resx b/src/Servers/HttpSys/src/Resources.resx index 67b954a934..d51faf9fd4 100644 --- a/src/Servers/HttpSys/src/Resources.resx +++ b/src/Servers/HttpSys/src/Resources.resx @@ -148,6 +148,6 @@ The given IAsyncResult does not match this opperation. - An exception occured while running an action registered with {0}. + An exception occurred while running an action registered with {0}. \ No newline at end of file diff --git a/src/Servers/HttpSys/src/StandardFeatureCollection.cs b/src/Servers/HttpSys/src/StandardFeatureCollection.cs index e63155c823..304b39b070 100644 --- a/src/Servers/HttpSys/src/StandardFeatureCollection.cs +++ b/src/Servers/HttpSys/src/StandardFeatureCollection.cs @@ -27,6 +27,8 @@ namespace Microsoft.AspNetCore.Server.HttpSys { typeof(IHttpMaxRequestBodySizeFeature), _identityFunc }, { typeof(IHttpBodyControlFeature), _identityFunc }, { typeof(IHttpSysRequestInfoFeature), _identityFunc }, + { typeof(IHttpResponseTrailersFeature), ctx => ctx.GetResponseTrailersFeature() }, + { typeof(IHttpResetFeature), ctx => ctx.GetResetFeature() }, }; private readonly FeatureContext _featureContext; diff --git a/src/Servers/HttpSys/src/UrlPrefixCollection.cs b/src/Servers/HttpSys/src/UrlPrefixCollection.cs index e38dd9a5d2..ac92c6591d 100644 --- a/src/Servers/HttpSys/src/UrlPrefixCollection.cs +++ b/src/Servers/HttpSys/src/UrlPrefixCollection.cs @@ -4,7 +4,10 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.HttpSys.Internal; namespace Microsoft.AspNetCore.Server.HttpSys { @@ -17,6 +20,12 @@ namespace Microsoft.AspNetCore.Server.HttpSys private UrlGroup _urlGroup; private int _nextId = 1; + // Valid port range of 5000 - 48000. + private const int BasePort = 5000; + private const int MaxPortIndex = 43000; + private const int MaxRetries = 1000; + private static int NextPortIndex; + internal UrlPrefixCollection() { } @@ -166,10 +175,55 @@ namespace Microsoft.AspNetCore.Server.HttpSys { _urlGroup = urlGroup; // go through the uri list and register for each one of them - foreach (var pair in _prefixes) + // Call ToList to avoid modification when enumerating. + foreach (var pair in _prefixes.ToList()) { - // We'll get this index back on each request and use it to look up the prefix to calculate PathBase. - _urlGroup.RegisterPrefix(pair.Value.FullPrefix, pair.Key); + var urlPrefix = pair.Value; + if (urlPrefix.PortValue == 0) + { + if (urlPrefix.IsHttps) + { + throw new InvalidOperationException("Cannot bind to port 0 with https."); + } + + FindHttpPortUnsynchronized(pair.Key, urlPrefix); + } + else + { + // We'll get this index back on each request and use it to look up the prefix to calculate PathBase. + _urlGroup.RegisterPrefix(pair.Value.FullPrefix, pair.Key); + } + } + } + } + + private void FindHttpPortUnsynchronized(int key, UrlPrefix urlPrefix) + { + for (var index = 0; index < MaxRetries; index++) + { + try + { + // Bit of complicated math to always try 3000 ports, starting from NextPortIndex + 5000, + // circling back around if we go above 8000 back to 5000, and so on. + var port = ((index + NextPortIndex) % MaxPortIndex) + BasePort; + + Debug.Assert(port >= 5000 || port < 8000); + + var newPrefix = UrlPrefix.Create(urlPrefix.Scheme, urlPrefix.Host, port, urlPrefix.Path); + _urlGroup.RegisterPrefix(newPrefix.FullPrefix, key); + _prefixes[key] = newPrefix; + + NextPortIndex += index + 1; + return; + } + catch (HttpSysException ex) + { + if ((ex.ErrorCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_ACCESS_DENIED + && ex.ErrorCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SHARING_VIOLATION + && ex.ErrorCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_ALREADY_EXISTS) || index == MaxRetries - 1) + { + throw; + } } } } diff --git a/src/Servers/HttpSys/src/WebHostBuilderHttpSysExtensions.cs b/src/Servers/HttpSys/src/WebHostBuilderHttpSysExtensions.cs index 7b4caaef62..f7c2b9445f 100644 --- a/src/Servers/HttpSys/src/WebHostBuilderHttpSysExtensions.cs +++ b/src/Servers/HttpSys/src/WebHostBuilderHttpSysExtensions.cs @@ -9,16 +9,19 @@ using Microsoft.Extensions.Options; namespace Microsoft.AspNetCore.Hosting { + /// + /// Provides extensions method to use Http.sys as the server for the web host. + /// public static class WebHostBuilderHttpSysExtensions { /// - /// Specify HttpSys as the server to be used by the web host. + /// Specify Http.sys as the server to be used by the web host. /// /// /// The Microsoft.AspNetCore.Hosting.IWebHostBuilder to configure. /// /// - /// The Microsoft.AspNetCore.Hosting.IWebHostBuilder. + /// A reference to the parameter object. /// public static IWebHostBuilder UseHttpSys(this IWebHostBuilder hostBuilder) { @@ -38,16 +41,16 @@ namespace Microsoft.AspNetCore.Hosting } /// - /// Specify HttpSys as the server to be used by the web host. + /// Specify Http.sys as the server to be used by the web host. /// /// /// The Microsoft.AspNetCore.Hosting.IWebHostBuilder to configure. /// /// - /// A callback to configure HttpSys options. + /// A callback to configure Http.sys options. /// /// - /// The Microsoft.AspNetCore.Hosting.IWebHostBuilder. + /// A reference to the parameter object. /// public static IWebHostBuilder UseHttpSys(this IWebHostBuilder hostBuilder, Action options) { diff --git a/src/Servers/HttpSys/startvs.cmd b/src/Servers/HttpSys/startvs.cmd new file mode 100644 index 0000000000..94b00042d2 --- /dev/null +++ b/src/Servers/HttpSys/startvs.cmd @@ -0,0 +1,3 @@ +@ECHO OFF + +%~dp0..\..\..\startvs.cmd %~dp0HttpSysServer.sln diff --git a/src/Servers/HttpSys/test/FunctionalTests/AuthenticationTests.cs b/src/Servers/HttpSys/test/FunctionalTests/AuthenticationTests.cs index be66db9655..2886b2d8d1 100644 --- a/src/Servers/HttpSys/test/FunctionalTests/AuthenticationTests.cs +++ b/src/Servers/HttpSys/test/FunctionalTests/AuthenticationTests.cs @@ -368,6 +368,38 @@ namespace Microsoft.AspNetCore.Server.HttpSys } } + [ConditionalTheory] + [InlineData(AuthenticationSchemes.Negotiate)] + [InlineData(AuthenticationSchemes.NTLM)] + // [InlineData(AuthenticationSchemes.Digest)] // TODO: Not implemented + // [InlineData(AuthenticationSchemes.Basic)] // Doesn't work with default creds + [InlineData(AuthenticationSchemes.Negotiate | AuthenticationSchemes.NTLM | /* AuthenticationSchemes.Digest |*/ AuthenticationSchemes.Basic)] + public async Task AuthTypes_DisableAutomaticAuthentication(AuthenticationSchemes authType) + { + using (var server = Utilities.CreateDynamicHost(out var address, options => + { + options.Authentication.AutomaticAuthentication = false; + options.Authentication.Schemes = authType; + options.Authentication.AllowAnonymous = DenyAnoymous; + }, + async httpContext => + { + Assert.NotNull(httpContext.User); + Assert.NotNull(httpContext.User.Identity); + Assert.False(httpContext.User.Identity.IsAuthenticated); + + var authenticateResult = await httpContext.AuthenticateAsync(HttpSysDefaults.AuthenticationScheme); + + Assert.NotNull(authenticateResult.Principal); + Assert.NotNull(authenticateResult.Principal.Identity); + Assert.True(authenticateResult.Principal.Identity.IsAuthenticated); + })) + { + var response = await SendRequestAsync(address, useDefaultCredentials: true); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + } + } + private async Task SendRequestAsync(string uri, bool useDefaultCredentials = false) { HttpClientHandler handler = new HttpClientHandler(); diff --git a/src/Servers/HttpSys/test/FunctionalTests/Http2Tests.cs b/src/Servers/HttpSys/test/FunctionalTests/Http2Tests.cs new file mode 100644 index 0000000000..57e5722557 --- /dev/null +++ b/src/Servers/HttpSys/test/FunctionalTests/Http2Tests.cs @@ -0,0 +1,661 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.Http2Cat; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2; +using Microsoft.AspNetCore.Testing; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.Net.Http.Headers; +using Xunit; + +namespace Microsoft.AspNetCore.Server.HttpSys.FunctionalTests +{ + public class Http2Tests + { + [ConditionalFact(Skip = "https://github.com/dotnet/aspnetcore/issues/17420")] + [MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win10, SkipReason = "Http2 requires Win10")] + [MaximumOSVersion(OperatingSystems.Windows, WindowsVersions.Win10_19H1, SkipReason = "This is last version without GoAway support")] + public async Task ConnectionClose_NoOSSupport_NoGoAway() + { + using var server = Utilities.CreateDynamicHttpsServer(out var address, httpContext => + { + httpContext.Response.Headers[HeaderNames.Connection] = "close"; + return Task.FromResult(0); + }); + + await new HostBuilder() + .UseHttp2Cat(address, async h2Connection => + { + await h2Connection.InitializeConnectionAsync(); + + h2Connection.Logger.LogInformation("Initialized http2 connection. Starting stream 1."); + + await h2Connection.StartStreamAsync(1, Http2Utilities.BrowserRequestHeaders, endStream: true); + + await h2Connection.ReceiveHeadersAsync(1, endStream: true, decodedHeaders => + { + // HTTP/2 filters out the connection header + Assert.False(decodedHeaders.ContainsKey(HeaderNames.Connection)); + Assert.Equal("200", decodedHeaders[HeaderNames.Status]); + }); + + // Send and receive a second request to ensure there is no GoAway frame on the wire yet. + + await h2Connection.StartStreamAsync(3, Http2Utilities.BrowserRequestHeaders, endStream: true); + + await h2Connection.ReceiveHeadersAsync(3, endStream: true, decodedHeaders => + { + // HTTP/2 filters out the connection header + Assert.False(decodedHeaders.ContainsKey(HeaderNames.Connection)); + Assert.Equal("200", decodedHeaders[HeaderNames.Status]); + }); + + await h2Connection.StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); + + h2Connection.Logger.LogInformation("Connection stopped."); + }) + .Build().RunAsync(); + } + + [ConditionalFact] + [MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win10_19H2, SkipReason = "GoAway support was added in Win10_19H2.")] + public async Task ConnectionClose_OSSupport_SendsGoAway() + { + using var server = Utilities.CreateDynamicHttpsServer(out var address, httpContext => + { + httpContext.Response.Headers[HeaderNames.Connection] = "close"; + return Task.FromResult(0); + }); + + await new HostBuilder() + .UseHttp2Cat(address, async h2Connection => + { + await h2Connection.InitializeConnectionAsync(); + + h2Connection.Logger.LogInformation("Initialized http2 connection. Starting stream 1."); + + await h2Connection.StartStreamAsync(1, Http2Utilities.BrowserRequestHeaders, endStream: true); + + var goAwayFrame = await h2Connection.ReceiveFrameAsync(); + h2Connection.VerifyGoAway(goAwayFrame, int.MaxValue, Http2ErrorCode.NO_ERROR); + + await h2Connection.ReceiveHeadersAsync(1, decodedHeaders => + { + // HTTP/2 filters out the connection header + Assert.False(decodedHeaders.ContainsKey(HeaderNames.Connection)); + Assert.Equal("200", decodedHeaders[HeaderNames.Status]); + }); + + var dataFrame = await h2Connection.ReceiveFrameAsync(); + Http2Utilities.VerifyDataFrame(dataFrame, 1, endOfStream: true, length: 0); + + // Http.Sys doesn't send a final GoAway unless we ignore the first one and send 200 additional streams. + + h2Connection.Logger.LogInformation("Connection stopped."); + }) + .Build().RunAsync(); + } + + [ConditionalFact] + [MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win10_19H2, SkipReason = "GoAway support was added in Win10_19H2.")] + public async Task ConnectionClose_AdditionalRequests_ReceivesSecondGoAway() + { + using var server = Utilities.CreateDynamicHttpsServer(out var address, httpContext => + { + httpContext.Response.Headers[HeaderNames.Connection] = "close"; + return Task.FromResult(0); + }); + + await new HostBuilder() + .UseHttp2Cat(address, async h2Connection => + { + await h2Connection.InitializeConnectionAsync(); + + h2Connection.Logger.LogInformation("Initialized http2 connection. Starting stream 1."); + + var streamId = 1; + await h2Connection.StartStreamAsync(streamId, Http2Utilities.BrowserRequestHeaders, endStream: true); + + var goAwayFrame = await h2Connection.ReceiveFrameAsync(); + h2Connection.VerifyGoAway(goAwayFrame, int.MaxValue, Http2ErrorCode.NO_ERROR); + + await h2Connection.ReceiveHeadersAsync(streamId, decodedHeaders => + { + // HTTP/2 filters out the connection header + Assert.False(decodedHeaders.ContainsKey(HeaderNames.Connection)); + Assert.Equal("200", decodedHeaders[HeaderNames.Status]); + }); + + var dataFrame = await h2Connection.ReceiveFrameAsync(); + Http2Utilities.VerifyDataFrame(dataFrame, streamId, endOfStream: true, length: 0); + + // Http.Sys doesn't send a final GoAway unless we ignore the first one and send 200 additional streams. + + for (var i = 1; i < 200; i++) + { + streamId = 1 + (i * 2); // Odds. + await h2Connection.StartStreamAsync(streamId, Http2Utilities.BrowserRequestHeaders, endStream: true); + + await h2Connection.ReceiveHeadersAsync(streamId, decodedHeaders => + { + // HTTP/2 filters out the connection header + Assert.False(decodedHeaders.ContainsKey(HeaderNames.Connection)); + Assert.Equal("200", decodedHeaders[HeaderNames.Status]); + }); + + dataFrame = await h2Connection.ReceiveFrameAsync(); + Http2Utilities.VerifyDataFrame(dataFrame, streamId, endOfStream: true, length: 0); + } + + streamId = 1 + (200 * 2); // Odds. + await h2Connection.StartStreamAsync(streamId, Http2Utilities.BrowserRequestHeaders, endStream: true); + + // Final GoAway + goAwayFrame = await h2Connection.ReceiveFrameAsync(); + h2Connection.VerifyGoAway(goAwayFrame, streamId, Http2ErrorCode.NO_ERROR); + + // Normal response + await h2Connection.ReceiveHeadersAsync(streamId, decodedHeaders => + { + // HTTP/2 filters out the connection header + Assert.False(decodedHeaders.ContainsKey(HeaderNames.Connection)); + Assert.Equal("200", decodedHeaders[HeaderNames.Status]); + }); + + dataFrame = await h2Connection.ReceiveFrameAsync(); + Http2Utilities.VerifyDataFrame(dataFrame, streamId, endOfStream: true, length: 0); + + h2Connection.Logger.LogInformation("Connection stopped."); + }) + .Build().RunAsync(); + } + + [ConditionalFact] + [MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win10, SkipReason = "Http2 requires Win10")] + public async Task AppException_BeforeHeaders_500() + { + using var server = Utilities.CreateDynamicHttpsServer(out var address, httpContext => + { + throw new Exception("Application exception"); + }); + + await new HostBuilder() + .UseHttp2Cat(address, async h2Connection => + { + await h2Connection.InitializeConnectionAsync(); + + h2Connection.Logger.LogInformation("Initialized http2 connection. Starting stream 1."); + + await h2Connection.StartStreamAsync(1, Http2Utilities.BrowserRequestHeaders, endStream: true); + + await h2Connection.ReceiveHeadersAsync(1, decodedHeaders => + { + Assert.Equal("500", decodedHeaders[HeaderNames.Status]); + }); + + var dataFrame = await h2Connection.ReceiveFrameAsync(); + Http2Utilities.VerifyDataFrame(dataFrame, 1, endOfStream: true, length: 0); + + h2Connection.Logger.LogInformation("Connection stopped."); + }) + .Build().RunAsync(); + } + + [ConditionalFact] + [MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win10, SkipReason = "Http2 requires Win10")] + [MaximumOSVersion(OperatingSystems.Windows, WindowsVersions.Win10_20H1, SkipReason = "This is last version without custom Reset support")] + public async Task AppException_AfterHeaders_PriorOSVersions_ResetCancel() + { + using var server = Utilities.CreateDynamicHttpsServer(out var address, async httpContext => + { + await httpContext.Response.Body.FlushAsync(); + throw new Exception("Application exception"); + }); + + await new HostBuilder() + .UseHttp2Cat(address, async h2Connection => + { + await h2Connection.InitializeConnectionAsync(); + + h2Connection.Logger.LogInformation("Initialized http2 connection. Starting stream 1."); + + await h2Connection.StartStreamAsync(1, Http2Utilities.BrowserRequestHeaders, endStream: true); + + await h2Connection.ReceiveHeadersAsync(1, decodedHeaders => + { + Assert.Equal("200", decodedHeaders[HeaderNames.Status]); + }); + + var resetFrame = await h2Connection.ReceiveFrameAsync(); + Http2Utilities.VerifyResetFrame(resetFrame, expectedStreamId: 1, Http2ErrorCode.CANCEL); + + h2Connection.Logger.LogInformation("Connection stopped."); + }) + .Build().RunAsync(); + } + + [ConditionalFact] + [MinimumOSVersion(OperatingSystems.Windows, "10.0.19529", SkipReason = "Custom Reset support was added in Win10_20H2.")] + public async Task AppException_AfterHeaders_ResetInternalError() + { + using var server = Utilities.CreateDynamicHttpsServer(out var address, async httpContext => + { + await httpContext.Response.Body.FlushAsync(); + throw new Exception("Application exception"); + }); + + await new HostBuilder() + .UseHttp2Cat(address, async h2Connection => + { + await h2Connection.InitializeConnectionAsync(); + + h2Connection.Logger.LogInformation("Initialized http2 connection. Starting stream 1."); + + await h2Connection.StartStreamAsync(1, Http2Utilities.BrowserRequestHeaders, endStream: true); + + await h2Connection.ReceiveHeadersAsync(1, decodedHeaders => + { + Assert.Equal("200", decodedHeaders[HeaderNames.Status]); + }); + + var resetFrame = await h2Connection.ReceiveFrameAsync(); + Http2Utilities.VerifyResetFrame(resetFrame, expectedStreamId: 1, Http2ErrorCode.INTERNAL_ERROR); + + h2Connection.Logger.LogInformation("Connection stopped."); + }) + .Build().RunAsync(); + } + + [ConditionalFact] + public async Task Reset_Http1_NotSupported() + { + using var server = Utilities.CreateDynamicHttpsServer(out var address, httpContext => + { + Assert.Equal("HTTP/1.1", httpContext.Request.Protocol); + var feature = httpContext.Features.Get(); + Assert.Null(feature); + return httpContext.Response.WriteAsync("Hello World"); + }); + + var handler = new HttpClientHandler(); + handler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator; + using HttpClient client = new HttpClient(handler); + client.DefaultRequestVersion = HttpVersion.Version11; + var response = await client.GetStringAsync(address); + Assert.Equal("Hello World", response); + } + + [ConditionalFact] + [MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win10, SkipReason = "Http2 requires Win10")] + [MaximumOSVersion(OperatingSystems.Windows, WindowsVersions.Win10_20H1, SkipReason = "This is last version without Reset support")] + public async Task Reset_PriorOSVersions_NotSupported() + { + using var server = Utilities.CreateDynamicHttpsServer(out var address, httpContext => + { + Assert.Equal("HTTP/2", httpContext.Request.Protocol); + var feature = httpContext.Features.Get(); + Assert.Null(feature); + return httpContext.Response.WriteAsync("Hello World"); + }); + + var handler = new HttpClientHandler(); + handler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator; + using HttpClient client = new HttpClient(handler); + client.DefaultRequestVersion = HttpVersion.Version20; + var response = await client.GetStringAsync(address); + Assert.Equal("Hello World", response); + } + + [ConditionalFact] + [MinimumOSVersion(OperatingSystems.Windows, "10.0.19529", SkipReason = "Reset support was added in Win10_20H2.")] + public async Task Reset_BeforeResponse_Resets() + { + var appResult = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + using var server = Utilities.CreateDynamicHttpsServer(out var address, httpContext => + { + try + { + Assert.Equal("HTTP/2", httpContext.Request.Protocol); + var feature = httpContext.Features.Get(); + Assert.NotNull(feature); + feature.Reset(1111); // Custom + appResult.SetResult(0); + } + catch (Exception ex) + { + appResult.SetException(ex); + } + return Task.FromResult(0); + }); + + await new HostBuilder() + .UseHttp2Cat(address, async h2Connection => + { + await h2Connection.InitializeConnectionAsync(); + + h2Connection.Logger.LogInformation("Initialized http2 connection. Starting stream 1."); + + await h2Connection.StartStreamAsync(1, Http2Utilities.BrowserRequestHeaders, endStream: true); + + var resetFrame = await h2Connection.ReceiveFrameAsync(); + Http2Utilities.VerifyResetFrame(resetFrame, expectedStreamId: 1, expectedErrorCode: (Http2ErrorCode)1111); + + // Any app errors? + Assert.Equal(0, await appResult.Task.DefaultTimeout()); + + h2Connection.Logger.LogInformation("Connection stopped."); + }) + .Build().RunAsync(); + } + + [ConditionalFact] + [MinimumOSVersion(OperatingSystems.Windows, "10.0.19529", SkipReason = "Reset support was added in Win10_20H2.")] + public async Task Reset_AfterResponseHeaders_Resets() + { + var appResult = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + using var server = Utilities.CreateDynamicHttpsServer(out var address, async httpContext => + { + try + { + Assert.Equal("HTTP/2", httpContext.Request.Protocol); + var feature = httpContext.Features.Get(); + Assert.NotNull(feature); + await httpContext.Response.Body.FlushAsync(); + feature.Reset(1111); // Custom + appResult.SetResult(0); + } + catch (Exception ex) + { + appResult.SetException(ex); + } + }); + + await new HostBuilder() + .UseHttp2Cat(address, async h2Connection => + { + await h2Connection.InitializeConnectionAsync(); + + h2Connection.Logger.LogInformation("Initialized http2 connection. Starting stream 1."); + + await h2Connection.StartStreamAsync(1, Http2Utilities.BrowserRequestHeaders, endStream: true); + + // Any app errors? + Assert.Equal(0, await appResult.Task.DefaultTimeout()); + + await h2Connection.ReceiveHeadersAsync(1, decodedHeaders => + { + Assert.Equal("200", decodedHeaders[HeaderNames.Status]); + }); + + var resetFrame = await h2Connection.ReceiveFrameAsync(); + Http2Utilities.VerifyResetFrame(resetFrame, expectedStreamId: 1, expectedErrorCode: (Http2ErrorCode)1111); + + h2Connection.Logger.LogInformation("Connection stopped."); + }) + .Build().RunAsync(); + } + + [ConditionalFact] + [MinimumOSVersion(OperatingSystems.Windows, "10.0.19529", SkipReason = "Reset support was added in Win10_20H2.")] + public async Task Reset_DurringResponseBody_Resets() + { + var appResult = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + using var server = Utilities.CreateDynamicHttpsServer(out var address, async httpContext => + { + try + { + Assert.Equal("HTTP/2", httpContext.Request.Protocol); + var feature = httpContext.Features.Get(); + Assert.NotNull(feature); + await httpContext.Response.WriteAsync("Hello World"); + feature.Reset(1111); // Custom + appResult.SetResult(0); + } + catch (Exception ex) + { + appResult.SetException(ex); + } + }); + + await new HostBuilder() + .UseHttp2Cat(address, async h2Connection => + { + await h2Connection.InitializeConnectionAsync(); + + h2Connection.Logger.LogInformation("Initialized http2 connection. Starting stream 1."); + + await h2Connection.StartStreamAsync(1, Http2Utilities.BrowserRequestHeaders, endStream: true); + + // Any app errors? + Assert.Equal(0, await appResult.Task.DefaultTimeout()); + + await h2Connection.ReceiveHeadersAsync(1, decodedHeaders => + { + Assert.Equal("200", decodedHeaders[HeaderNames.Status]); + }); + + var dataFrame = await h2Connection.ReceiveFrameAsync(); + Http2Utilities.VerifyDataFrame(dataFrame, 1, endOfStream: false, length: 11); + + var resetFrame = await h2Connection.ReceiveFrameAsync(); + Http2Utilities.VerifyResetFrame(resetFrame, expectedStreamId: 1, expectedErrorCode: (Http2ErrorCode)1111); + + h2Connection.Logger.LogInformation("Connection stopped."); + }) + .Build().RunAsync(); + } + + [ConditionalFact] + [MinimumOSVersion(OperatingSystems.Windows, "10.0.19529", SkipReason = "Reset support was added in Win10_20H2.")] + public async Task Reset_AfterCompleteAsync_NoReset() + { + var appResult = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + using var server = Utilities.CreateDynamicHttpsServer(out var address, async httpContext => + { + try + { + Assert.Equal("HTTP/2", httpContext.Request.Protocol); + var feature = httpContext.Features.Get(); + Assert.NotNull(feature); + await httpContext.Response.WriteAsync("Hello World"); + await httpContext.Response.CompleteAsync(); + // The request and response are fully complete, the reset doesn't get sent. + feature.Reset(1111); + appResult.SetResult(0); + } + catch (Exception ex) + { + appResult.SetException(ex); + } + }); + + await new HostBuilder() + .UseHttp2Cat(address, async h2Connection => + { + await h2Connection.InitializeConnectionAsync(); + + h2Connection.Logger.LogInformation("Initialized http2 connection. Starting stream 1."); + + await h2Connection.StartStreamAsync(1, Http2Utilities.BrowserRequestHeaders, endStream: true); + + // Any app errors? + Assert.Equal(0, await appResult.Task.DefaultTimeout()); + + await h2Connection.ReceiveHeadersAsync(1, decodedHeaders => + { + Assert.Equal("200", decodedHeaders[HeaderNames.Status]); + }); + + var dataFrame = await h2Connection.ReceiveFrameAsync(); + Http2Utilities.VerifyDataFrame(dataFrame, 1, endOfStream: false, length: 11); + + dataFrame = await h2Connection.ReceiveFrameAsync(); + Http2Utilities.VerifyDataFrame(dataFrame, 1, endOfStream: true, length: 0); + + h2Connection.Logger.LogInformation("Connection stopped."); + }) + .Build().RunAsync(); + } + + [ConditionalFact] + [MinimumOSVersion(OperatingSystems.Windows, "10.0.19529", SkipReason = "Reset support was added in Win10_20H2.")] + public async Task Reset_BeforeRequestBody_Resets() + { + var appResult = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + using var server = Utilities.CreateDynamicHttpsServer(out var address, async httpContext => + { + try + { + Assert.Equal("HTTP/2", httpContext.Request.Protocol); + var feature = httpContext.Features.Get(); + Assert.NotNull(feature); + var readTask = httpContext.Request.Body.ReadAsync(new byte[10], 0, 10); + + feature.Reset(1111); + + await Assert.ThrowsAsync(() => readTask); + + appResult.SetResult(0); + } + catch (Exception ex) + { + appResult.SetException(ex); + } + }); + + await new HostBuilder() + .UseHttp2Cat(address, async h2Connection => + { + await h2Connection.InitializeConnectionAsync(); + + h2Connection.Logger.LogInformation("Initialized http2 connection. Starting stream 1."); + + await h2Connection.StartStreamAsync(1, Http2Utilities.PostRequestHeaders, endStream: false); + + // Any app errors? + Assert.Equal(0, await appResult.Task.DefaultTimeout()); + + var resetFrame = await h2Connection.ReceiveFrameAsync(); + Http2Utilities.VerifyResetFrame(resetFrame, expectedStreamId: 1, expectedErrorCode: (Http2ErrorCode)1111); + + h2Connection.Logger.LogInformation("Connection stopped."); + }) + .Build().RunAsync(); + } + + [ConditionalFact] + [MinimumOSVersion(OperatingSystems.Windows, "10.0.19529", SkipReason = "Reset support was added in Win10_20H2.")] + public async Task Reset_DurringRequestBody_Resets() + { + var appResult = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + using var server = Utilities.CreateDynamicHttpsServer(out var address, async httpContext => + { + try + { + Assert.Equal("HTTP/2", httpContext.Request.Protocol); + var feature = httpContext.Features.Get(); + Assert.NotNull(feature); + + var read = await httpContext.Request.Body.ReadAsync(new byte[10], 0, 10); + Assert.Equal(10, read); + + var readTask = httpContext.Request.Body.ReadAsync(new byte[10], 0, 10); + feature.Reset(1111); + await Assert.ThrowsAsync(() => readTask); + + appResult.SetResult(0); + } + catch (Exception ex) + { + appResult.SetException(ex); + } + }); + + await new HostBuilder() + .UseHttp2Cat(address, async h2Connection => + { + await h2Connection.InitializeConnectionAsync(); + + h2Connection.Logger.LogInformation("Initialized http2 connection. Starting stream 1."); + + await h2Connection.StartStreamAsync(1, Http2Utilities.PostRequestHeaders, endStream: false); + await h2Connection.SendDataAsync(1, new byte[10], endStream: false); + + // Any app errors? + Assert.Equal(0, await appResult.Task.DefaultTimeout()); + + var resetFrame = await h2Connection.ReceiveFrameAsync(); + Http2Utilities.VerifyResetFrame(resetFrame, expectedStreamId: 1, expectedErrorCode: (Http2ErrorCode)1111); + + h2Connection.Logger.LogInformation("Connection stopped."); + }) + .Build().RunAsync(); + } + + [ConditionalFact] + [MinimumOSVersion(OperatingSystems.Windows, "10.0.19529", SkipReason = "Reset support was added in Win10_20H2.")] + public async Task Reset_CompleteAsyncDurringRequestBody_Resets() + { + var appResult = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + using var server = Utilities.CreateDynamicHttpsServer(out var address, async httpContext => + { + try + { + Assert.Equal("HTTP/2", httpContext.Request.Protocol); + var feature = httpContext.Features.Get(); + Assert.NotNull(feature); + + var read = await httpContext.Request.Body.ReadAsync(new byte[10], 0, 10); + Assert.Equal(10, read); + + var readTask = httpContext.Request.Body.ReadAsync(new byte[10], 0, 10); + await httpContext.Response.CompleteAsync(); + feature.Reset((int)Http2ErrorCode.NO_ERROR); // GRPC does this + await Assert.ThrowsAsync(() => readTask); + + appResult.SetResult(0); + } + catch (Exception ex) + { + appResult.SetException(ex); + } + }); + + await new HostBuilder() + .UseHttp2Cat(address, async h2Connection => + { + await h2Connection.InitializeConnectionAsync(); + + h2Connection.Logger.LogInformation("Initialized http2 connection. Starting stream 1."); + + await h2Connection.StartStreamAsync(1, Http2Utilities.PostRequestHeaders, endStream: false); + await h2Connection.SendDataAsync(1, new byte[10], endStream: false); + + // Any app errors? + Assert.Equal(0, await appResult.Task.DefaultTimeout()); + + await h2Connection.ReceiveHeadersAsync(1, decodedHeaders => + { + Assert.Equal("200", decodedHeaders[HeaderNames.Status]); + }); + + var dataFrame = await h2Connection.ReceiveFrameAsync(); + Http2Utilities.VerifyDataFrame(dataFrame, 1, endOfStream: true, length: 0); + + var resetFrame = await h2Connection.ReceiveFrameAsync(); + Http2Utilities.VerifyResetFrame(resetFrame, expectedStreamId: 1, expectedErrorCode: Http2ErrorCode.NO_ERROR); + + h2Connection.Logger.LogInformation("Connection stopped."); + }) + .Build().RunAsync(); + } + } +} diff --git a/src/Servers/HttpSys/test/FunctionalTests/HttpsTests.cs b/src/Servers/HttpSys/test/FunctionalTests/HttpsTests.cs index 6bbaeb13cb..43d6005bc1 100644 --- a/src/Servers/HttpSys/test/FunctionalTests/HttpsTests.cs +++ b/src/Servers/HttpSys/test/FunctionalTests/HttpsTests.cs @@ -3,7 +3,6 @@ using System; using System.IO; -using System.Linq; using System.Net.Http; using System.Runtime.InteropServices; using System.Security.Authentication; @@ -20,12 +19,9 @@ using Xunit; namespace Microsoft.AspNetCore.Server.HttpSys { - // Flaky doesn't support classes :( - // https://github.com/aspnet/Extensions/issues/1568 public class HttpsTests { [ConditionalFact] - [Flaky("https://github.com/aspnet/AspNetCore-Internal/issues/2209", FlakyOn.Helix.All)] public async Task Https_200OK_Success() { using (Utilities.CreateDynamicHttpsServer(out var address, httpContext => @@ -39,7 +35,6 @@ namespace Microsoft.AspNetCore.Server.HttpSys } [ConditionalFact] - [Flaky("https://github.com/aspnet/AspNetCore-Internal/issues/2209", FlakyOn.Helix.All)] public async Task Https_SendHelloWorld_Success() { using (Utilities.CreateDynamicHttpsServer(out var address, httpContext => @@ -55,7 +50,6 @@ namespace Microsoft.AspNetCore.Server.HttpSys } [ConditionalFact] - [Flaky("https://github.com/aspnet/AspNetCore-Internal/issues/2209", FlakyOn.Helix.All)] public async Task Https_EchoHelloWorld_Success() { using (Utilities.CreateDynamicHttpsServer(out var address, async httpContext => @@ -73,7 +67,6 @@ namespace Microsoft.AspNetCore.Server.HttpSys } [ConditionalFact] - [Flaky("https://github.com/aspnet/AspNetCore-Internal/issues/2209", FlakyOn.Helix.All)] public async Task Https_ClientCertNotSent_ClientCertNotPresent() { using (Utilities.CreateDynamicHttpsServer(out var address, async httpContext => @@ -110,8 +103,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys } [ConditionalFact] - [Flaky("https://github.com/aspnet/AspNetCore-Internal/issues/2209", FlakyOn.Helix.All)] - [OSDontSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7, WindowsVersions.Win2008R2)] + [MaximumOSVersion(OperatingSystems.Windows, WindowsVersions.Win7)] public async Task Https_SkipsITlsHandshakeFeatureOnWin7() { using (Utilities.CreateDynamicHttpsServer(out var address, async httpContext => @@ -133,8 +125,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys } [ConditionalFact] - [Flaky("https://github.com/aspnet/AspNetCore-Internal/issues/2209", FlakyOn.Helix.All)] - [OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7, WindowsVersions.Win2008R2)] + [MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win8)] public async Task Https_SetsITlsHandshakeFeature() { using (Utilities.CreateDynamicHttpsServer(out var address, async httpContext => @@ -164,7 +155,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys } [ConditionalFact] - [OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7, WindowsVersions.Win2008R2)] + [MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win8)] public async Task Https_ITlsHandshakeFeature_MatchesIHttpSysExtensionInfoFeature() { using (Utilities.CreateDynamicHttpsServer(out var address, async httpContext => @@ -206,16 +197,14 @@ namespace Microsoft.AspNetCore.Server.HttpSys private async Task SendRequestAsync(string uri, X509Certificate cert = null) { - var handler = new WinHttpHandler(); - handler.ServerCertificateValidationCallback = (a, b, c, d) => true; + var handler = new HttpClientHandler(); + handler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator; if (cert != null) { handler.ClientCertificates.Add(cert); } - using (HttpClient client = new HttpClient(handler)) - { - return await client.GetStringAsync(uri); - } + using HttpClient client = new HttpClient(handler); + return await client.GetStringAsync(uri); } private async Task SendRequestAsync(string uri, string upload) diff --git a/src/Servers/HttpSys/test/FunctionalTests/Listener/RequestBodyTests.cs b/src/Servers/HttpSys/test/FunctionalTests/Listener/RequestBodyTests.cs index 6a7d29d654..8e2860cf69 100644 --- a/src/Servers/HttpSys/test/FunctionalTests/Listener/RequestBodyTests.cs +++ b/src/Servers/HttpSys/test/FunctionalTests/Listener/RequestBodyTests.cs @@ -17,7 +17,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys.Listener public class RequestBodyTests { [ConditionalFact] - [Flaky("https://github.com/aspnet/AspNetCore-Internal/issues/1826", FlakyOn.All)] + [QuarantinedTest("https://github.com/dotnet/aspnetcore-internal/issues/1826")] public async Task RequestBody_SyncReadDisabledByDefault_WorksWhenEnabled() { string address; @@ -142,7 +142,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys.Listener } [ConditionalFact] - [Flaky("https://github.com/aspnet/AspNetCore-Internal/issues/2206", FlakyOn.All)] + [QuarantinedTest("https://github.com/dotnet/aspnetcore-internal/issues/2206")] public async Task RequestBody_ReadAsyncPartialBodyAndExpiredTimeout_Canceled() { StaggardContent content = new StaggardContent(); diff --git a/src/Servers/HttpSys/test/FunctionalTests/MessagePumpTests.cs b/src/Servers/HttpSys/test/FunctionalTests/MessagePumpTests.cs index 250ff9c9be..0e5270b82b 100644 --- a/src/Servers/HttpSys/test/FunctionalTests/MessagePumpTests.cs +++ b/src/Servers/HttpSys/test/FunctionalTests/MessagePumpTests.cs @@ -77,7 +77,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys [InlineData("invalid address")] [InlineData("")] [InlineData(null)] - public void OverridingIServerAdressesFeatureWithDirectConfiguration_WarnsOnStart(string serverAddress) + public void OverridingIServerAddressesFeatureWithDirectConfiguration_WarnsOnStart(string serverAddress) { var overrideAddress = "http://localhost:11002/"; @@ -94,7 +94,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys } [ConditionalFact] - public void UseIServerAdressesFeature_WhenNoDirectConfiguration() + public void UseIServerAddressesFeature_WhenNoDirectConfiguration() { var serverAddress = "http://localhost:11001/"; @@ -114,7 +114,8 @@ namespace Microsoft.AspNetCore.Server.HttpSys { server.StartAsync(new DummyApplication(), CancellationToken.None).Wait(); - Assert.Equal(Constants.DefaultServerAddress, server.Features.Get().Addresses.Single()); + // Trailing slash is added when put in UrlPrefix. + Assert.StartsWith(Constants.DefaultServerAddress, server.Features.Get().Addresses.Single()); } } diff --git a/src/Servers/HttpSys/test/FunctionalTests/Microsoft.AspNetCore.Server.HttpSys.FunctionalTests.csproj b/src/Servers/HttpSys/test/FunctionalTests/Microsoft.AspNetCore.Server.HttpSys.FunctionalTests.csproj index 281d455f5e..d0a2c7558b 100644 --- a/src/Servers/HttpSys/test/FunctionalTests/Microsoft.AspNetCore.Server.HttpSys.FunctionalTests.csproj +++ b/src/Servers/HttpSys/test/FunctionalTests/Microsoft.AspNetCore.Server.HttpSys.FunctionalTests.csproj @@ -6,8 +6,19 @@ true + + + + + + + + + + + @@ -22,4 +33,15 @@ + + + Microsoft.AspNetCore.Server.SharedStrings + + + + System.Net.Http.SR + + + + diff --git a/src/Servers/HttpSys/test/FunctionalTests/OSDontSkipConditionAttribute.cs b/src/Servers/HttpSys/test/FunctionalTests/OSDontSkipConditionAttribute.cs deleted file mode 100644 index ee257df989..0000000000 --- a/src/Servers/HttpSys/test/FunctionalTests/OSDontSkipConditionAttribute.cs +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.InteropServices; - -namespace Microsoft.AspNetCore.Testing -{ - // Skip except on a specific OS and version - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Assembly, AllowMultiple = true)] - public class OSDontSkipConditionAttribute : Attribute, ITestCondition - { - private readonly OperatingSystems _includedOperatingSystem; - private readonly IEnumerable _includedVersions; - private readonly OperatingSystems _osPlatform; - private readonly string _osVersion; - - public OSDontSkipConditionAttribute(OperatingSystems operatingSystem, params string[] versions) : - this( - operatingSystem, - GetCurrentOS(), - GetCurrentOSVersion(), - versions) - { - } - - // to enable unit testing - internal OSDontSkipConditionAttribute( - OperatingSystems operatingSystem, OperatingSystems osPlatform, string osVersion, params string[] versions) - { - _includedOperatingSystem = operatingSystem; - _includedVersions = versions ?? Enumerable.Empty(); - _osPlatform = osPlatform; - _osVersion = osVersion; - } - - public bool IsMet - { - get - { - var currentOSInfo = new OSInfo() - { - OperatingSystem = _osPlatform, - Version = _osVersion, - }; - - var skip = (_includedOperatingSystem & currentOSInfo.OperatingSystem) != currentOSInfo.OperatingSystem; - if (!skip && _includedVersions.Any()) - { - skip = !_includedVersions.Any(inc => _osVersion.StartsWith(inc, StringComparison.OrdinalIgnoreCase)); - } - - // Since a test would be excuted only if 'IsMet' is true, return false if we want to skip - return !skip; - } - } - - public string SkipReason { get; set; } = "Test cannot run on this operating system."; - - static private OperatingSystems GetCurrentOS() - { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - return OperatingSystems.Windows; - } - else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - { - return OperatingSystems.Linux; - } - else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) - { - return OperatingSystems.MacOSX; - } - throw new PlatformNotSupportedException(); - } - - static private string GetCurrentOSVersion() - { - // currently not used on other OS's - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - return Environment.OSVersion.Version.ToString(); - } - else - { - return string.Empty; - } - } - - private class OSInfo - { - public OperatingSystems OperatingSystem { get; set; } - - public string Version { get; set; } - } - } -} \ No newline at end of file diff --git a/src/Servers/HttpSys/test/FunctionalTests/OpaqueUpgradeTests.cs b/src/Servers/HttpSys/test/FunctionalTests/OpaqueUpgradeTests.cs index b722f3ab1f..a2c39a4c47 100644 --- a/src/Servers/HttpSys/test/FunctionalTests/OpaqueUpgradeTests.cs +++ b/src/Servers/HttpSys/test/FunctionalTests/OpaqueUpgradeTests.cs @@ -6,7 +6,6 @@ using System.IO; using System.Net.Http; using System.Net.Sockets; using System.Text; -using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; @@ -18,7 +17,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys public class OpaqueUpgradeTests { [ConditionalFact] - [OSDontSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7, WindowsVersions.Win2008R2)] + [MaximumOSVersion(OperatingSystems.Windows, WindowsVersions.Win7)] public async Task OpaqueUpgrade_DownLevel_FeatureIsAbsent() { using (Utilities.CreateHttpServer(out var address, httpContext => @@ -44,7 +43,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys } [ConditionalFact] - [OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7, WindowsVersions.Win2008R2)] + [MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win8)] public async Task OpaqueUpgrade_SupportKeys_Present() { string address; @@ -71,7 +70,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys } [ConditionalFact] - [OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7, WindowsVersions.Win2008R2)] + [MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win8)] public async Task OpaqueUpgrade_AfterHeadersSent_Throws() { bool? upgradeThrew = null; @@ -101,7 +100,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys } [ConditionalFact] - [OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7, WindowsVersions.Win2008R2)] + [MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win8)] public async Task OpaqueUpgrade_GetUpgrade_Success() { var upgraded = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); @@ -123,7 +122,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys } [ConditionalFact] - [OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7, WindowsVersions.Win2008R2)] + [MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win8)] public async Task OpaqueUpgrade_GetUpgrade_NotAffectedByMaxRequestBodyLimit() { var upgraded = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); @@ -155,7 +154,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys } [ConditionalFact] - [OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7, WindowsVersions.Win2008R2)] + [MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win8)] public async Task OpaqueUpgrade_WithOnStarting_CallbackCalled() { var callbackCalled = false; @@ -184,7 +183,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys } [ConditionalTheory] - [OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7, WindowsVersions.Win2008R2)] + [MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win8)] // See HTTP_VERB for known verbs [InlineData("UNKNOWN", null)] [InlineData("INVALID", null)] @@ -242,7 +241,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys } [ConditionalTheory] - [OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7, WindowsVersions.Win2008R2)] + [MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win8)] // Http.Sys returns a 411 Length Required if PUT or POST does not specify content-length or chunked. [InlineData("POST", "Content-Length: 10")] [InlineData("POST", "Transfer-Encoding: chunked")] diff --git a/src/Servers/HttpSys/test/FunctionalTests/ResponseBodyTests.cs b/src/Servers/HttpSys/test/FunctionalTests/ResponseBodyTests.cs index 4a45a81a5d..bd73e39946 100644 --- a/src/Servers/HttpSys/test/FunctionalTests/ResponseBodyTests.cs +++ b/src/Servers/HttpSys/test/FunctionalTests/ResponseBodyTests.cs @@ -30,6 +30,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys return Task.CompletedTask; }); await httpContext.Response.StartAsync(); + Assert.True(httpContext.Response.HasStarted); Assert.True(httpContext.Response.Headers.IsReadOnly); await startingTcs.Task.WithTimeout(); await httpContext.Response.WriteAsync("Hello World"); @@ -58,6 +59,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys return Task.CompletedTask; }); await httpContext.Response.CompleteAsync(); + Assert.True(httpContext.Response.HasStarted); Assert.True(httpContext.Response.Headers.IsReadOnly); await startingTcs.Task.WithTimeout(); await responseReceived.Task.WithTimeout(); diff --git a/src/Servers/HttpSys/test/FunctionalTests/ResponseCachingTests.cs b/src/Servers/HttpSys/test/FunctionalTests/ResponseCachingTests.cs index bda37a76c5..1112420ed6 100644 --- a/src/Servers/HttpSys/test/FunctionalTests/ResponseCachingTests.cs +++ b/src/Servers/HttpSys/test/FunctionalTests/ResponseCachingTests.cs @@ -45,7 +45,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys.FunctionalTests } [ConditionalFact] - [Flaky("https://github.com/aspnet/AspNetCore-Internal/issues/2135", FlakyOn.All)] + [QuarantinedTest("https://github.com/dotnet/aspnetcore-internal/issues/2135")] public async Task Caching_JustPublic_NotCached() { var requestCount = 1; @@ -66,7 +66,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys.FunctionalTests } [ConditionalFact] - [OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win2008R2, WindowsVersions.Win7, SkipReason = "Content type not required for caching on Win7 and Win2008R2.")] + [MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win8, SkipReason = "Content type not required for caching on Win7.")] public async Task Caching_WithoutContentType_NotCached() { var requestCount = 1; @@ -86,7 +86,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys.FunctionalTests } [ConditionalFact] - [Flaky("https://github.com/aspnet/AspNetCore-Internal/issues/2207", FlakyOn.All)] + [QuarantinedTest("https://github.com/dotnet/aspnetcore-internal/issues/2207")] public async Task Caching_WithoutContentType_Cached_OnWin7AndWin2008R2() { if (Utilities.IsWin8orLater) @@ -237,7 +237,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys.FunctionalTests [ConditionalTheory] [InlineData("0")] [InlineData("-1")] - [Flaky("https://github.com/aspnet/AspNetCore-Internal/issues/2208", FlakyOn.All)] + [QuarantinedTest("https://github.com/dotnet/aspnetcore-internal/issues/2208")] public async Task Caching_InvalidExpires_NotCached(string expiresValue) { var requestCount = 1; @@ -378,7 +378,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys.FunctionalTests } [ConditionalFact] - [Flaky("https://github.com/aspnet/AspNetCore-Internal/issues/2209", FlakyOn.All)] + [QuarantinedTest("https://github.com/dotnet/aspnetcore-internal/issues/2209")] public async Task Caching_VariousStatusCodes_Cached() { var requestCount = 1; diff --git a/src/Servers/HttpSys/test/FunctionalTests/ResponseTrailersTests.cs b/src/Servers/HttpSys/test/FunctionalTests/ResponseTrailersTests.cs new file mode 100644 index 0000000000..75f43587b7 --- /dev/null +++ b/src/Servers/HttpSys/test/FunctionalTests/ResponseTrailersTests.cs @@ -0,0 +1,368 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.HttpSys.Internal; +using Microsoft.AspNetCore.Testing; +using Microsoft.Extensions.Primitives; +using Microsoft.Net.Http.Headers; +using Xunit; + +namespace Microsoft.AspNetCore.Server.HttpSys +{ + public class ResponseTrailersTests + { + [ConditionalFact] + public async Task ResponseTrailers_HTTP11_TrailersNotAvailable() + { + using (Utilities.CreateDynamicHttpsServer(out var address, httpContext => + { + Assert.Equal("HTTP/1.1", httpContext.Request.Protocol); + Assert.False(httpContext.Response.SupportsTrailers()); + return Task.FromResult(0); + })) + { + var response = await SendRequestAsync(address, http2: false); + response.EnsureSuccessStatusCode(); + Assert.Equal(HttpVersion.Version11, response.Version); + Assert.Empty(response.TrailingHeaders); + } + } + + [ConditionalFact] + [MinimumOSVersion(OperatingSystems.Windows, "10.0.19529", SkipReason = "Requires HTTP/2 Trailers support.")] + public async Task ResponseTrailers_HTTP2_TrailersAvailable() + { + using (Utilities.CreateDynamicHttpsServer(out var address, httpContext => + { + Assert.Equal("HTTP/2", httpContext.Request.Protocol); + Assert.True(httpContext.Response.SupportsTrailers()); + return Task.FromResult(0); + })) + { + var response = await SendRequestAsync(address); + response.EnsureSuccessStatusCode(); + Assert.Equal(HttpVersion.Version20, response.Version); + Assert.Empty(response.TrailingHeaders); + } + } + + [ConditionalFact] + [MinimumOSVersion(OperatingSystems.Windows, "10.0.19529", SkipReason = "Requires HTTP/2 Trailers support.")] + public async Task ResponseTrailers_ProhibitedTrailers_Blocked() + { + using (Utilities.CreateDynamicHttpsServer(out var address, httpContext => + { + Assert.True(httpContext.Response.SupportsTrailers()); + foreach (var header in HeaderCollection.DisallowedTrailers) + { + Assert.Throws(() => httpContext.Response.AppendTrailer(header, "value")); + } + return Task.FromResult(0); + })) + { + var response = await SendRequestAsync(address); + response.EnsureSuccessStatusCode(); + Assert.Equal(HttpVersion.Version20, response.Version); + Assert.Empty(response.TrailingHeaders); + } + } + + [ConditionalFact] + [MinimumOSVersion(OperatingSystems.Windows, "10.0.19529", SkipReason = "Requires HTTP/2 Trailers support.")] + public async Task ResponseTrailers_NoBody_TrailersSent() + { + using (Utilities.CreateDynamicHttpsServer(out var address, httpContext => + { + httpContext.Response.DeclareTrailer("trailername"); + httpContext.Response.AppendTrailer("trailername", "TrailerValue"); + return Task.FromResult(0); + })) + { + var response = await SendRequestAsync(address); + response.EnsureSuccessStatusCode(); + Assert.Equal(HttpVersion.Version20, response.Version); + Assert.NotEmpty(response.TrailingHeaders); + Assert.Equal("TrailerValue", response.TrailingHeaders.GetValues("TrailerName").Single()); + } + } + + [ConditionalFact] + [MinimumOSVersion(OperatingSystems.Windows, "10.0.19529", SkipReason = "Requires HTTP/2 Trailers support.")] + public async Task ResponseTrailers_WithBody_TrailersSent() + { + using (Utilities.CreateDynamicHttpsServer(out var address, async httpContext => + { + await httpContext.Response.WriteAsync("Hello World"); + httpContext.Response.AppendTrailer("TrailerName", "Trailer Value"); + })) + { + var response = await SendRequestAsync(address); + response.EnsureSuccessStatusCode(); + Assert.Equal(HttpVersion.Version20, response.Version); + Assert.Equal("Hello World", await response.Content.ReadAsStringAsync()); + Assert.NotEmpty(response.TrailingHeaders); + Assert.Equal("Trailer Value", response.TrailingHeaders.GetValues("TrailerName").Single()); + } + } + + [ConditionalFact] + [MinimumOSVersion(OperatingSystems.Windows, "10.0.19529", SkipReason = "Requires HTTP/2 Trailers support.")] + public async Task ResponseTrailers_WithContentLengthBody_TrailersNotSent() + { + var body = "Hello World"; + var responseFinished = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + using (Utilities.CreateDynamicHttpsServer(out var address, async httpContext => + { + httpContext.Response.ContentLength = body.Length; + await httpContext.Response.WriteAsync(body); + try + { + Assert.Throws(() => httpContext.Response.AppendTrailer("TrailerName", "Trailer Value")); + responseFinished.SetResult(0); + } + catch (Exception ex) + { + responseFinished.SetException(ex); + } + })) + { + var response = await SendRequestAsync(address); + response.EnsureSuccessStatusCode(); + Assert.Equal(HttpVersion.Version20, response.Version); + Assert.Equal(body.Length.ToString(CultureInfo.InvariantCulture), response.Content.Headers.GetValues(HeaderNames.ContentLength).Single()); + Assert.Equal(body, await response.Content.ReadAsStringAsync()); + Assert.Empty(response.TrailingHeaders); + await responseFinished.Task; + } + } + + [ConditionalFact] + [MinimumOSVersion(OperatingSystems.Windows, "10.0.19529", SkipReason = "Requires HTTP/2 Trailers support.")] + public async Task ResponseTrailers_WithTrailersBeforeContentLengthBody_TrailersSent() + { + var body = "Hello World"; + using (Utilities.CreateDynamicHttpsServer(out var address, async httpContext => + { + httpContext.Response.ContentLength = body.Length * 2; + await httpContext.Response.WriteAsync(body); + httpContext.Response.AppendTrailer("TrailerName", "Trailer Value"); + await httpContext.Response.WriteAsync(body); + })) + { + var response = await SendRequestAsync(address); + response.EnsureSuccessStatusCode(); + Assert.Equal(HttpVersion.Version20, response.Version); + // Avoid HttpContent's automatic content-length calculation. + Assert.True(response.Content.Headers.TryGetValues(HeaderNames.ContentLength, out var contentLength), HeaderNames.ContentLength); + Assert.Equal((2 * body.Length).ToString(CultureInfo.InvariantCulture), contentLength.First()); + Assert.Equal(body + body, await response.Content.ReadAsStringAsync()); + Assert.NotEmpty(response.TrailingHeaders); + Assert.Equal("Trailer Value", response.TrailingHeaders.GetValues("TrailerName").Single()); + } + } + + [ConditionalFact] + [MinimumOSVersion(OperatingSystems.Windows, "10.0.19529", SkipReason = "Requires HTTP/2 Trailers support.")] + public async Task ResponseTrailers_WithContentLengthBodyAndDeclared_TrailersSent() + { + var body = "Hello World"; + using (Utilities.CreateDynamicHttpsServer(out var address, async httpContext => + { + httpContext.Response.ContentLength = body.Length; + httpContext.Response.DeclareTrailer("TrailerName"); + await httpContext.Response.WriteAsync(body); + httpContext.Response.AppendTrailer("TrailerName", "Trailer Value"); + })) + { + var response = await SendRequestAsync(address); + response.EnsureSuccessStatusCode(); + Assert.Equal(HttpVersion.Version20, response.Version); + // Avoid HttpContent's automatic content-length calculation. + Assert.True(response.Content.Headers.TryGetValues(HeaderNames.ContentLength, out var contentLength), HeaderNames.ContentLength); + Assert.Equal(body.Length.ToString(CultureInfo.InvariantCulture), contentLength.First()); + Assert.Equal("TrailerName", response.Headers.Trailer.Single()); + Assert.Equal(body, await response.Content.ReadAsStringAsync()); + Assert.NotEmpty(response.TrailingHeaders); + Assert.Equal("Trailer Value", response.TrailingHeaders.GetValues("TrailerName").Single()); + } + } + + [ConditionalFact] + [MinimumOSVersion(OperatingSystems.Windows, "10.0.19529", SkipReason = "Requires HTTP/2 Trailers support.")] + public async Task ResponseTrailers_WithContentLengthBodyAndDeclaredButMissingTrailers_Completes() + { + var body = "Hello World"; + using (Utilities.CreateDynamicHttpsServer(out var address, async httpContext => + { + httpContext.Response.ContentLength = body.Length; + httpContext.Response.DeclareTrailer("TrailerName"); + await httpContext.Response.WriteAsync(body); + // If we declare trailers but don't send any make sure it completes anyways. + })) + { + var response = await SendRequestAsync(address); + response.EnsureSuccessStatusCode(); + Assert.Equal(HttpVersion.Version20, response.Version); + // Avoid HttpContent's automatic content-length calculation. + Assert.True(response.Content.Headers.TryGetValues(HeaderNames.ContentLength, out var contentLength), HeaderNames.ContentLength); + Assert.Equal(body.Length.ToString(CultureInfo.InvariantCulture), contentLength.First()); + Assert.Equal("TrailerName", response.Headers.Trailer.Single()); + Assert.Equal(body, await response.Content.ReadAsStringAsync()); + Assert.Empty(response.TrailingHeaders); + } + } + + [ConditionalFact] + [MinimumOSVersion(OperatingSystems.Windows, "10.0.19529", SkipReason = "Requires HTTP/2 Trailers support.")] + public async Task ResponseTrailers_CompleteAsyncNoBody_TrailersSent() + { + var trailersReceived = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + using (Utilities.CreateDynamicHttpsServer(out var address, async httpContext => + { + httpContext.Response.AppendTrailer("trailername", "TrailerValue"); + await httpContext.Response.CompleteAsync(); + await trailersReceived.Task.WithTimeout(); + })) + { + var response = await SendRequestAsync(address); + response.EnsureSuccessStatusCode(); + Assert.Equal(HttpVersion.Version20, response.Version); + Assert.NotEmpty(response.TrailingHeaders); + Assert.Equal("TrailerValue", response.TrailingHeaders.GetValues("TrailerName").Single()); + trailersReceived.SetResult(0); + } + } + + [ConditionalFact] + [MinimumOSVersion(OperatingSystems.Windows, "10.0.19529", SkipReason = "Requires HTTP/2 Trailers support.")] + public async Task ResponseTrailers_CompleteAsyncWithBody_TrailersSent() + { + var trailersReceived = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + using (Utilities.CreateDynamicHttpsServer(out var address, async httpContext => + { + await httpContext.Response.WriteAsync("Hello World"); + httpContext.Response.AppendTrailer("TrailerName", "Trailer Value"); + await httpContext.Response.CompleteAsync(); + await trailersReceived.Task.WithTimeout(); + })) + { + var response = await SendRequestAsync(address); + response.EnsureSuccessStatusCode(); + Assert.Equal(HttpVersion.Version20, response.Version); + Assert.Equal("Hello World", await response.Content.ReadAsStringAsync()); + Assert.NotEmpty(response.TrailingHeaders); + Assert.Equal("Trailer Value", response.TrailingHeaders.GetValues("TrailerName").Single()); + trailersReceived.SetResult(0); + } + } + + [ConditionalFact] + [MinimumOSVersion(OperatingSystems.Windows, "10.0.19529", SkipReason = "Requires HTTP/2 Trailers support.")] + public async Task ResponseTrailers_MultipleValues_SentAsSeparateHeaders() + { + using (Utilities.CreateDynamicHttpsServer(out var address, httpContext => + { + httpContext.Response.AppendTrailer("trailername", new StringValues(new[] { "TrailerValue0", "TrailerValue1" })); + return Task.FromResult(0); + })) + { + var response = await SendRequestAsync(address); + response.EnsureSuccessStatusCode(); + Assert.Equal(HttpVersion.Version20, response.Version); + Assert.NotEmpty(response.TrailingHeaders); + // We can't actually assert they are sent as separate headers using HttpClient, we'd have to write a lower level test + // that read the header frames directly. + Assert.Equal(new[] { "TrailerValue0", "TrailerValue1" }, response.TrailingHeaders.GetValues("TrailerName")); + } + } + + [ConditionalFact] + [MinimumOSVersion(OperatingSystems.Windows, "10.0.19529", SkipReason = "Requires HTTP/2 Trailers support.")] + public async Task ResponseTrailers_LargeTrailers_Success() + { + var values = new[] { + new string('a', 1024), + new string('b', 1024 * 4), + new string('c', 1024 * 8), + new string('d', 1024 * 16), + new string('e', 1024 * 32), + new string('f', 1024 * 64 - 1) }; // Max header size + + using (Utilities.CreateDynamicHttpsServer(out var address, httpContext => + { + httpContext.Response.AppendTrailer("ThisIsALongerHeaderNameThatStillWorksForReals", new StringValues(values)); + return Task.FromResult(0); + })) + { + var response = await SendRequestAsync(address); + response.EnsureSuccessStatusCode(); + Assert.Equal(HttpVersion.Version20, response.Version); + Assert.NotEmpty(response.TrailingHeaders); + // We can't actually assert they are sent in multiple frames using HttpClient, we'd have to write a lower level test + // that read the header frames directly. We at least verify that really large values work. + Assert.Equal(values, response.TrailingHeaders.GetValues("ThisIsALongerHeaderNameThatStillWorksForReals")); + } + } + + [ConditionalTheory, MemberData(nameof(NullHeaderData))] + [MinimumOSVersion(OperatingSystems.Windows, "10.0.19529", SkipReason = "Requires HTTP/2 Trailers support.")] + public async Task ResponseTrailers_NullValues_Ignored(string headerName, StringValues headerValue, StringValues expectedValue) + { + using (Utilities.CreateDynamicHttpsServer(out var address, httpContext => + { + httpContext.Response.AppendTrailer(headerName, headerValue); + return Task.FromResult(0); + })) + { + HttpResponseMessage response = await SendRequestAsync(address); + response.EnsureSuccessStatusCode(); + var headers = response.TrailingHeaders; + + if (StringValues.IsNullOrEmpty(expectedValue)) + { + Assert.False(headers.Contains(headerName)); + } + else + { + Assert.True(headers.Contains(headerName)); + Assert.Equal(headers.GetValues(headerName), expectedValue); + } + } + } + + public static TheoryData NullHeaderData + { + get + { + var dataset = new TheoryData(); + + dataset.Add("NullString", (string)null, (string)null); + dataset.Add("EmptyString", "", ""); + dataset.Add("NullStringArray", new string[] { null }, ""); + dataset.Add("EmptyStringArray", new string[] { "" }, ""); + dataset.Add("MixedStringArray", new string[] { null, "" }, new string[] { "", "" }); + dataset.Add("WithValidStrings", new string[] { null, "Value", "" }, new string[] { "", "Value", "" }); + + return dataset; + } + } + + private async Task SendRequestAsync(string uri, bool http2 = true) + { + var handler = new HttpClientHandler(); + handler.MaxResponseHeadersLength = 128; + handler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator; + using HttpClient client = new HttpClient(handler); + client.DefaultRequestVersion = http2 ? HttpVersion.Version20 : HttpVersion.Version11; + return await client.GetAsync(uri); + } + } +} diff --git a/src/Servers/HttpSys/test/FunctionalTests/ServerTests.cs b/src/Servers/HttpSys/test/FunctionalTests/ServerTests.cs index 04228c6da6..fbea889de4 100644 --- a/src/Servers/HttpSys/test/FunctionalTests/ServerTests.cs +++ b/src/Servers/HttpSys/test/FunctionalTests/ServerTests.cs @@ -131,7 +131,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys } [ConditionalFact] - [Flaky("https://github.com/aspnet/AspNetCore-Internal/issues/2267", FlakyOn.All)] + [QuarantinedTest("https://github.com/dotnet/aspnetcore-internal/issues/2267")] public async Task Server_ShutdownDuringRequest_Success() { Task responseTask; diff --git a/src/Servers/HttpSys/test/FunctionalTests/Utilities.cs b/src/Servers/HttpSys/test/FunctionalTests/Utilities.cs index 8ba9e7b39a..a347ce427f 100644 --- a/src/Servers/HttpSys/test/FunctionalTests/Utilities.cs +++ b/src/Servers/HttpSys/test/FunctionalTests/Utilities.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication; @@ -21,11 +22,8 @@ namespace Microsoft.AspNetCore.Server.HttpSys { // When tests projects are run in parallel, overlapping port ranges can cause a race condition when looking for free // ports during dynamic port allocation. - private const int BasePort = 5001; - private const int MaxPort = 8000; private const int BaseHttpsPort = 44300; private const int MaxHttpsPort = 44399; - private static int NextPort = BasePort; private static int NextHttpsPort = BaseHttpsPort; private static object PortLock = new object(); internal static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(15); @@ -84,39 +82,26 @@ namespace Microsoft.AspNetCore.Server.HttpSys internal static IWebHost CreateDynamicHost(string basePath, out string root, out string baseAddress, Action configureOptions, RequestDelegate app) { - lock (PortLock) - { - while (NextPort < MaxPort) + var prefix = UrlPrefix.Create("http", "localhost", "0", basePath); + + var builder = new WebHostBuilder() + .UseHttpSys(options => { - var port = NextPort++; - var prefix = UrlPrefix.Create("http", "localhost", port, basePath); - root = prefix.Scheme + "://" + prefix.Host + ":" + prefix.Port; - baseAddress = prefix.ToString(); + options.UrlPrefixes.Add(prefix); + configureOptions(options); + }) + .Configure(appBuilder => appBuilder.Run(app)); - var builder = new WebHostBuilder() - .UseHttpSys(options => - { - options.UrlPrefixes.Add(prefix); - configureOptions(options); - }) - .Configure(appBuilder => appBuilder.Run(app)); + var host = builder.Build(); - var host = builder.Build(); + host.Start(); + var options = host.Services.GetRequiredService>(); + prefix = options.Value.UrlPrefixes.First(); // Has new port + root = prefix.Scheme + "://" + prefix.Host + ":" + prefix.Port; + baseAddress = prefix.ToString(); - try - { - host.Start(); - return host; - } - catch (HttpSysException) - { - } - - } - NextPort = BasePort; - } - throw new Exception("Failed to locate a free port."); + return host; } internal static MessagePump CreatePump() @@ -131,30 +116,17 @@ namespace Microsoft.AspNetCore.Server.HttpSys internal static IServer CreateDynamicHttpServer(string basePath, out string root, out string baseAddress, Action configureOptions, RequestDelegate app) { - lock (PortLock) - { - while (NextPort < MaxPort) - { + var prefix = UrlPrefix.Create("http", "localhost", "0", basePath); - var port = NextPort++; - var prefix = UrlPrefix.Create("http", "localhost", port, basePath); - root = prefix.Scheme + "://" + prefix.Host + ":" + prefix.Port; - baseAddress = prefix.ToString(); + var server = CreatePump(configureOptions); + server.Features.Get().Addresses.Add(prefix.ToString()); + server.StartAsync(new DummyApplication(app), CancellationToken.None).Wait(); - var server = CreatePump(configureOptions); - server.Features.Get().Addresses.Add(baseAddress); - try - { - server.StartAsync(new DummyApplication(app), CancellationToken.None).Wait(); - return server; - } - catch (HttpSysException) - { - } - } - NextPort = BasePort; - } - throw new Exception("Failed to locate a free port."); + prefix = server.Listener.Options.UrlPrefixes.First(); // Has new port + root = prefix.Scheme + "://" + prefix.Host + ":" + prefix.Port; + baseAddress = prefix.ToString(); + + return server; } internal static IServer CreateDynamicHttpsServer(out string baseAddress, RequestDelegate app) diff --git a/src/Servers/HttpSys/test/Tests/UrlPrefixTests.cs b/src/Servers/HttpSys/test/Tests/UrlPrefixTests.cs index 8614ac36db..20d0f0713f 100644 --- a/src/Servers/HttpSys/test/Tests/UrlPrefixTests.cs +++ b/src/Servers/HttpSys/test/Tests/UrlPrefixTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; diff --git a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/HandlerResolver.cpp b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/HandlerResolver.cpp index bb42023988..b7c7fbf972 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/HandlerResolver.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/HandlerResolver.cpp @@ -56,7 +56,7 @@ HandlerResolver::LoadRequestHandlerAssembly(const IHttpApplication &pApplication { if (pConfiguration.QueryHostingModel() == APP_HOSTING_MODEL::HOSTING_IN_PROCESS) { - errorContext.generalErrorType = "ANCM In-Process Handler Load Failure"; + errorContext.generalErrorType = "ASP.NET Core IIS hosting failure (in-process)"; std::unique_ptr options; RETURN_IF_FAILED(HostFxrResolutionResult::Create( @@ -86,7 +86,7 @@ HandlerResolver::LoadRequestHandlerAssembly(const IHttpApplication &pApplication } else { - errorContext.generalErrorType = "ANCM Out-Of-Process Handler Load Failure"; + errorContext.generalErrorType = "ASP.NET Core IIS hosting failure (out-of-process)"; if (FAILED_LOG(hr = FindNativeAssemblyFromGlobalLocation(pConfiguration, pstrHandlerDllName, handlerDllPath))) { @@ -136,8 +136,8 @@ HandlerResolver::GetApplicationFactory(const IHttpApplication& pApplication, std errorContext.detailedErrorContent = to_multi_byte_string(format(ASPNETCORE_EVENT_MIXED_HOSTING_MODEL_ERROR_MSG, pApplication.GetApplicationId(), options.QueryHostingModel()), CP_UTF8); errorContext.statusCode = 500i16; errorContext.subStatusCode = 34i16; - errorContext.generalErrorType = "ANCM Mixed Hosting Models Not Supported"; - errorContext.errorReason = "Select a different application pool to create another application."; + errorContext.generalErrorType = "ASP.NET Core does not support mixing hosting models"; + errorContext.errorReason = "Select a different app pool to host this app."; EventLog::Error( ASPNETCORE_EVENT_MIXED_HOSTING_MODEL_ERROR, @@ -154,8 +154,8 @@ HandlerResolver::GetApplicationFactory(const IHttpApplication& pApplication, std errorContext.statusCode = 500i16; errorContext.subStatusCode = 35i16; - errorContext.generalErrorType = "ANCM Multiple In-Process Applications in same Process"; - errorContext.errorReason = "Select a different application pool to create another in-process application."; + errorContext.generalErrorType = "ASP.NET Core does not support multiple apps in the same app pool"; + errorContext.errorReason = "Select a different app pool to host this app."; EventLog::Error( ASPNETCORE_EVENT_DUPLICATED_INPROCESS_APP, @@ -251,8 +251,8 @@ try errorContext.detailedErrorContent = "Could not load hostfxr.dll."; errorContext.statusCode = 500i16; errorContext.subStatusCode = 32i16; - errorContext.generalErrorType = "ANCM Failed to Load dll"; - errorContext.errorReason = "The application was likely published for a different bitness than w3wp.exe/iisexpress.exe is running as."; + errorContext.generalErrorType = "Failed to load .NET Core host"; + errorContext.errorReason = "The app was likely published for a different bitness than w3wp.exe/iisexpress.exe is running as."; throw; } { @@ -302,7 +302,7 @@ try errorContext.statusCode = 500i16; errorContext.subStatusCode = 31i16; - errorContext.generalErrorType = "ANCM Failed to Find Native Dependencies"; + errorContext.generalErrorType = "Failed to load ASP.NET Core runtime"; errorContext.errorReason = "The specified version of Microsoft.NetCore.App or Microsoft.AspNetCore.App was not found."; EventLog::Error( @@ -347,7 +347,7 @@ try // This only occurs if the request handler isn't referenced by the app, which rarely happens if they are targeting the shared framework. errorContext.statusCode = 500i16; errorContext.subStatusCode = 33i16; - errorContext.generalErrorType = "ANCM Request Handler Load Failure"; + errorContext.generalErrorType = "Failed to load ASP.NET Core request handler"; errorContext.detailedErrorContent = to_multi_byte_string(format(ASPNETCORE_EVENT_INPROCESS_RH_REFERENCE_MSG, handlerDllPath.empty() ? s_pwzAspnetcoreInProcessRequestHandlerName : handlerDllPath.c_str()), diff --git a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/ShimOptions.cpp b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/ShimOptions.cpp index 208d4a1eaa..353b0be788 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/ShimOptions.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/ShimOptions.cpp @@ -54,6 +54,11 @@ ShimOptions::ShimOptions(const ConfigurationSource &configurationSource) : .value_or(environmentVariables[CS_ASPNETCORE_ENVIRONMENT]); const auto dotnetEnvironment = Environment::GetEnvironmentVariableValue(CS_DOTNET_ENVIRONMENT) .value_or(environmentVariables[CS_DOTNET_ENVIRONMENT]); + // We prefer the environment variables for LAUNCHER_PATH and LAUNCHER_ARGS + m_strProcessPath = Environment::GetEnvironmentVariableValue(CS_ANCM_LAUNCHER_PATH) + .value_or(m_strProcessPath); + m_strArguments = Environment::GetEnvironmentVariableValue(CS_ANCM_LAUNCHER_ARGS) + .value_or(m_strArguments); auto detailedErrorsEnabled = equals_ignore_case(L"1", detailedErrors) || equals_ignore_case(L"true", detailedErrors); auto aspnetCoreEnvironmentEnabled = equals_ignore_case(L"Development", aspnetCoreEnvironment); diff --git a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/ConfigurationLoadException.h b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/ConfigurationLoadException.h index 0aade37061..792f053a53 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/ConfigurationLoadException.h +++ b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/ConfigurationLoadException.h @@ -9,7 +9,7 @@ class ConfigurationLoadException: public std::runtime_error { public: ConfigurationLoadException(std::wstring msg) - : runtime_error("Configuration load exception has occured"), message(std::move(msg)) + : runtime_error("Configuration load exception has occurred"), message(std::move(msg)) { } diff --git a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/ConfigurationSection.h b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/ConfigurationSection.h index ae02dd3faa..8c9cece3e3 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/ConfigurationSection.h +++ b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/ConfigurationSection.h @@ -33,6 +33,8 @@ #define CS_ASPNETCORE_DETAILEDERRORS L"ASPNETCORE_DETAILEDERRORS" #define CS_ASPNETCORE_ENVIRONMENT L"ASPNETCORE_ENVIRONMENT" #define CS_DOTNET_ENVIRONMENT L"DOTNET_ENVIRONMENT" +#define CS_ANCM_LAUNCHER_PATH L"ANCM_LAUNCHER_PATH" +#define CS_ANCM_LAUNCHER_ARGS L"ANCM_LAUNCHER_ARGS" class ConfigurationSection: NonCopyable { diff --git a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/HostFxrResolver.cpp b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/HostFxrResolver.cpp index 45e3699fad..01b6ecfa7b 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/HostFxrResolver.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/HostFxrResolver.cpp @@ -95,11 +95,11 @@ HostFxrResolver::GetHostFxrParameters( if (!is_regular_file(applicationDllPath)) { errorContext.subStatusCode = 38; - errorContext.errorReason = "Application DLL not found. Confirm the application dll is present. Single-file deployments are not supported in IIS."; - errorContext.generalErrorType = "ANCM Application DLL Not Found"; - errorContext.detailedErrorContent = format("Application DLL was not found at %s.", to_multi_byte_string(applicationDllPath, CP_UTF8).c_str()); + errorContext.errorReason = "The app couldn't be found. Confirm the app's main DLL is present. Single-file deployments are not supported in IIS."; + errorContext.generalErrorType = "Failed to locate ASP.NET Core app"; + errorContext.detailedErrorContent = format("Application was not found at %s.", to_multi_byte_string(applicationDllPath, CP_UTF8).c_str()); throw InvalidOperationException( - format(L"Application DLL was not found at %s. Confirm the application dll is present. Single-file deployments are not supported in IIS.", + format(L"The app couldn't be found at %s. Confirm the app's main DLL is present. Single-file deployments are not supported in IIS.", applicationDllPath.c_str())); } @@ -145,9 +145,10 @@ HostFxrResolver::GetHostFxrParameters( } BOOL -HostFxrResolver::IsDotnetExecutable(const std::filesystem::path & dotnetPath) +HostFxrResolver::IsDotnetExecutable(const std::filesystem::path& dotnetPath) { - return ends_with(dotnetPath, L"dotnet.exe", true); + std::wstring filename = dotnetPath.filename().wstring(); + return equals_ignore_case(filename, L"dotnet.exe"); } void diff --git a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/config_utility.h b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/config_utility.h index 05fd514bea..517f00d15e 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/config_utility.h +++ b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/config_utility.h @@ -15,6 +15,7 @@ class ConfigUtility #define CS_ASPNETCORE_HANDLER_VERSION L"handlerVersion" #define CS_ASPNETCORE_DEBUG_FILE L"debugFile" #define CS_ASPNETCORE_ENABLE_OUT_OF_PROCESS_CONSOLE_REDIRECTION L"enableOutOfProcessConsoleRedirection" + #define CS_ASPNETCORE_FORWARD_RESPONSE_CONNECTION_HEADER L"forwardResponseConnectionHeader" #define CS_ASPNETCORE_DEBUG_LEVEL L"debugLevel" #define CS_ASPNETCORE_HANDLER_SETTINGS_NAME L"name" #define CS_ASPNETCORE_HANDLER_SETTINGS_VALUE L"value" @@ -48,6 +49,13 @@ public: return FindKeyValuePair(pElement, CS_ASPNETCORE_ENABLE_OUT_OF_PROCESS_CONSOLE_REDIRECTION, strEnableOutOfProcessConsoleRedirection); } + static + HRESULT + FindForwardResponseConnectionHeader(IAppHostElement* pElement, STRU& strForwardResponseConnectionHeader) + { + return FindKeyValuePair(pElement, CS_ASPNETCORE_FORWARD_RESPONSE_CONNECTION_HEADER, strForwardResponseConnectionHeader); + } + private: static HRESULT diff --git a/src/Servers/IIS/AspNetCoreModuleV2/CommonLibTests/CommonLibTests.vcxproj b/src/Servers/IIS/AspNetCoreModuleV2/CommonLibTests/CommonLibTests.vcxproj index 091a99e221..9f24c25f67 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/CommonLibTests/CommonLibTests.vcxproj +++ b/src/Servers/IIS/AspNetCoreModuleV2/CommonLibTests/CommonLibTests.vcxproj @@ -1,4 +1,7 @@  + + false + Debug @@ -43,6 +46,7 @@ + @@ -71,6 +75,17 @@ {d57ea297-6dc2-4bc0-8c91-334863327863} + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + @@ -83,6 +98,7 @@ EnableFastChecks MultiThreadedDebug Level3 + true $(MSBuildThisFileDirectory)include;%(AdditionalIncludeDirectories);..\RequestHandlerLib;..\IISLib;..\CommonLib;$(GoogleTestSubmoduleRoot)googletest\include;$(GoogleTestSubmoduleRoot)googlemock\include;...\AspNetCore\Inc;..\InProcessRequestHandler\ /D "_SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING" stdcpp17 @@ -110,6 +126,7 @@ EnableFastChecks MultiThreadedDebug Level3 + true $(MSBuildThisFileDirectory)include;%(AdditionalIncludeDirectories);..\RequestHandlerLib;..\IISLib;..\CommonLib;$(GoogleTestSubmoduleRoot)googletest\include;$(GoogleTestSubmoduleRoot)googlemock\include;...\AspNetCore\Inc;..\InProcessRequestHandler\ /D "_SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING" stdcpp17 @@ -135,6 +152,7 @@ stdafx.h MultiThreaded Level3 + true ProgramDatabase $(MSBuildThisFileDirectory)include;%(AdditionalIncludeDirectories);..\RequestHandlerLib;..\IISLib;..\CommonLib;$(GoogleTestSubmoduleRoot)googletest\include;$(GoogleTestSubmoduleRoot)googlemock\include;...\AspNetCore\Inc;..\InProcessRequestHandler\ /D "_SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING" @@ -164,6 +182,7 @@ stdafx.h MultiThreaded Level3 + true ProgramDatabase $(MSBuildThisFileDirectory)include;%(AdditionalIncludeDirectories);..\RequestHandlerLib;..\IISLib;..\CommonLib;$(GoogleTestSubmoduleRoot)googletest\include;$(GoogleTestSubmoduleRoot)googlemock\include;...\AspNetCore\Inc;..\InProcessRequestHandler\ /D "_SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING" diff --git a/src/Servers/IIS/AspNetCoreModuleV2/CommonLibTests/Fake/hello-dotnet.dll b/src/Servers/IIS/AspNetCoreModuleV2/CommonLibTests/Fake/hello-dotnet.dll new file mode 100644 index 0000000000..88a3d89dd6 --- /dev/null +++ b/src/Servers/IIS/AspNetCoreModuleV2/CommonLibTests/Fake/hello-dotnet.dll @@ -0,0 +1 @@ +this a is faked hello-dotnet.dll used for tests diff --git a/src/Servers/IIS/AspNetCoreModuleV2/CommonLibTests/Fake/hello-dotnet.exe b/src/Servers/IIS/AspNetCoreModuleV2/CommonLibTests/Fake/hello-dotnet.exe new file mode 100644 index 0000000000..18f1759669 --- /dev/null +++ b/src/Servers/IIS/AspNetCoreModuleV2/CommonLibTests/Fake/hello-dotnet.exe @@ -0,0 +1 @@ +this a is faked hello-dotnet.exe used for tests diff --git a/src/Servers/IIS/AspNetCoreModuleV2/CommonLibTests/Fake/hostfxr.dll b/src/Servers/IIS/AspNetCoreModuleV2/CommonLibTests/Fake/hostfxr.dll new file mode 100644 index 0000000000..e96043e8c4 --- /dev/null +++ b/src/Servers/IIS/AspNetCoreModuleV2/CommonLibTests/Fake/hostfxr.dll @@ -0,0 +1 @@ +this a is faked hostfxr.dll used for tests diff --git a/src/Servers/IIS/AspNetCoreModuleV2/CommonLibTests/dotnet_exe_path_tests.cpp b/src/Servers/IIS/AspNetCoreModuleV2/CommonLibTests/dotnet_exe_path_tests.cpp new file mode 100644 index 0000000000..b7b334059a --- /dev/null +++ b/src/Servers/IIS/AspNetCoreModuleV2/CommonLibTests/dotnet_exe_path_tests.cpp @@ -0,0 +1,43 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +#include "stdafx.h" + +#include +#include "fakeclasses.h" +#include "HostFxrResolver.h" + +using ::testing::_; +using ::testing::NiceMock; + +// Externals defined in inprocess +namespace InprocessTests +{ + + TEST(Dotnet_EXE_Path_Tests, EndWith_dotnet) + { + std::filesystem::path hostFxrDllPath; + std::vector arguments; + ErrorContext errorContext; + auto currentPath = std::filesystem::current_path(); + auto appPath= currentPath /= L"Fake"; + auto processPath = L"hello-dotnet"; + auto args = L"-a --tag t -x"; + std::filesystem::path knownDotnetLocation=L"C:/Program Files/dotnet"; + // expected no exception should be thrown + HostFxrResolver::GetHostFxrParameters( + processPath, + appPath, + args, + hostFxrDllPath, + knownDotnetLocation, + arguments, + errorContext); + + ASSERT_TRUE(ends_with(arguments[0], L"\\Fake\\hello-dotnet.exe", true)); + ASSERT_STREQ(arguments[1].c_str(), L"-a"); + ASSERT_STREQ(arguments[2].c_str(), L"--tag"); + ASSERT_STREQ(arguments[3].c_str(), L"t"); + ASSERT_STREQ(arguments[4].c_str(), L"-x"); + } +} diff --git a/src/Servers/IIS/AspNetCoreModuleV2/IISLib/hashtable.h b/src/Servers/IIS/AspNetCoreModuleV2/IISLib/hashtable.h index 9319e5643d..cde38374fe 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/IISLib/hashtable.h +++ b/src/Servers/IIS/AspNetCoreModuleV2/IISLib/hashtable.h @@ -406,7 +406,7 @@ HASH_TABLE<_Record,_Key>::InsertRecord( ) /*++ This method inserts a node for this record and also empty nodes for paths - in the heirarchy leading upto this path + in the hierarchy leading upto this path The insert is done under only a read-lock - this is possible by keeping the hashes in a bucket in increasing order and using interlocked operations diff --git a/src/Servers/IIS/AspNetCoreModuleV2/IISLib/percpu.h b/src/Servers/IIS/AspNetCoreModuleV2/IISLib/percpu.h index 07828830d7..7b92e58daf 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/IISLib/percpu.h +++ b/src/Servers/IIS/AspNetCoreModuleV2/IISLib/percpu.h @@ -82,7 +82,7 @@ private: ); // - // Pointer to the begining of the inlined array. + // Pointer to the beginning of the inlined array. // PVOID m_pVariables; SIZE_T m_Alignment; @@ -104,7 +104,7 @@ PER_CPU::Create( DWORD ObjectCacheLineSize = 0; DWORD NumberOfProcessors = 0; PER_CPU * pInstance = NULL; - + hr = GetProcessorInformation(&CacheLineSize, &NumberOfProcessors); if (FAILED(hr)) @@ -143,7 +143,7 @@ PER_CPU::Create( // The array start in the 2nd cache line. // pInstance->m_pVariables = reinterpret_cast(pInstance) + CacheLineSize; - + // // Pass a disposer for disposing initialized items in case of failure. // diff --git a/src/Servers/IIS/AspNetCoreModuleV2/IISLib/stringa.cpp b/src/Servers/IIS/AspNetCoreModuleV2/IISLib/stringa.cpp index 29da773bca..5b1d0adbee 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/IISLib/stringa.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/IISLib/stringa.cpp @@ -1637,7 +1637,7 @@ Arguments: Return Value: - The index for the first character occurence in the string. + The index for the first character occurrence in the string. -1 if not found. @@ -1684,7 +1684,7 @@ Arguments: Return Value: - The index for the first character occurence in the string. + The index for the first character occurrence in the string. -1 if not found. @@ -1733,7 +1733,7 @@ Arguments: Return Value: - The index for the last character occurence in the string. + The index for the last character occurrence in the string. -1 if not found. diff --git a/src/Servers/IIS/AspNetCoreModuleV2/IISLib/stringu.cpp b/src/Servers/IIS/AspNetCoreModuleV2/IISLib/stringu.cpp index 74f8595482..c83096b97a 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/IISLib/stringu.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/IISLib/stringu.cpp @@ -1065,7 +1065,7 @@ Arguments: Return Value: - The index for the first character occurence in the string. + The index for the first character occurrence in the string. -1 if not found. @@ -1112,7 +1112,7 @@ Arguments: Return Value: - The index for the first character occurence in the string. + The index for the first character occurrence in the string. -1 if not found. @@ -1161,7 +1161,7 @@ Arguments: Return Value: - The index for the last character occurence in the string. + The index for the last character occurrence in the string. -1 if not found. diff --git a/src/Servers/IIS/AspNetCoreModuleV2/IISLib/treehash.h b/src/Servers/IIS/AspNetCoreModuleV2/IISLib/treehash.h index baa50726ce..087e8abf74 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/IISLib/treehash.h +++ b/src/Servers/IIS/AspNetCoreModuleV2/IISLib/treehash.h @@ -417,7 +417,7 @@ TREE_HASH_TABLE<_Record>::AddNodeInternal( TREE_HASH_NODE<_Record> ** ppNewNode ) /*++ - Return value is HRESULT indicating sucess or failure + Return value is HRESULT indicating success or failure pszPath, dwHash, pRecord - path, hash value and record to be inserted pParentNode - this will be the parent of the node being inserted ppNewNode - on successful return, the new node created and inserted @@ -519,7 +519,7 @@ TREE_HASH_TABLE<_Record>::InsertRecord( ) /*++ This method inserts a node for this record and also empty nodes for paths - in the heirarchy leading upto this path + in the hierarchy leading upto this path The insert is done under only a read-lock - this is possible by keeping the hashes in a bucket in increasing order and using interlocked operations diff --git a/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/InProcessOptions.cpp b/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/InProcessOptions.cpp index 01ec10f6f6..c2ff5e0a7d 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/InProcessOptions.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/InProcessOptions.cpp @@ -4,6 +4,7 @@ #include "InProcessOptions.h" #include "InvalidOperationException.h" #include "EventLog.h" +#include "Environment.h" HRESULT InProcessOptions::Create( IHttpServer& pServer, @@ -51,6 +52,11 @@ InProcessOptions::InProcessOptions(const ConfigurationSource &configurationSourc auto const aspNetCoreSection = configurationSource.GetRequiredSection(CS_ASPNETCORE_SECTION); m_strArguments = aspNetCoreSection->GetString(CS_ASPNETCORE_PROCESS_ARGUMENTS).value_or(CS_ASPNETCORE_PROCESS_ARGUMENTS_DEFAULT); m_strProcessPath = aspNetCoreSection->GetRequiredString(CS_ASPNETCORE_PROCESS_EXE_PATH); + // We prefer the environment variables for LAUNCHER_PATH and LAUNCHER_ARGS + m_strProcessPath = Environment::GetEnvironmentVariableValue(CS_ANCM_LAUNCHER_PATH) + .value_or(m_strProcessPath); + m_strArguments = Environment::GetEnvironmentVariableValue(CS_ANCM_LAUNCHER_ARGS) + .value_or(m_strArguments); m_fStdoutLogEnabled = aspNetCoreSection->GetRequiredBool(CS_ASPNETCORE_STDOUT_LOG_ENABLED); m_struStdoutLogFile = aspNetCoreSection->GetRequiredString(CS_ASPNETCORE_STDOUT_LOG_FILE); m_fDisableStartUpErrorPage = aspNetCoreSection->GetRequiredBool(CS_ASPNETCORE_DISABLE_START_UP_ERROR_PAGE); diff --git a/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/dllmain.cpp b/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/dllmain.cpp index fe7f9d10a0..29d4ad614f 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/dllmain.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/dllmain.cpp @@ -125,8 +125,8 @@ CreateApplication( ErrorContext errorContext; errorContext.statusCode = 500; errorContext.subStatusCode = 30; - errorContext.generalErrorType = "ANCM In-Process Start Failure"; - errorContext.errorReason = "
      • The application failed to start
      • The application started but then stopped
      • The application started but threw an exception during startup
      "; + errorContext.generalErrorType = "ASP.NET Core app failed to start"; + errorContext.errorReason = "
      • The app failed to start
      • The app started but then stopped
      • The app started but threw an exception during startup
      "; if (!FAILED_LOG(hr = IN_PROCESS_APPLICATION::Start(*pServer, pSite, *pHttpApplication, pParameters, nParameters, inProcessApplication, errorContext))) { diff --git a/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp b/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp index be2e9055b7..4f86cde3b8 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp @@ -165,8 +165,8 @@ IN_PROCESS_APPLICATION::LoadManagedApplication(ErrorContext& errorContext) // If server wasn't initialized in time shut application down without waiting for CLR thread to exit errorContext.statusCode = 500; errorContext.subStatusCode = 37; - errorContext.generalErrorType = "ANCM Failed to Start Within Startup Time Limit"; - errorContext.errorReason = format("ANCM failed to start after %d milliseconds", m_pConfig->QueryStartupTimeLimitInMS()); + errorContext.generalErrorType = "ASP.NET Core app failed to start within startup time limit"; + errorContext.errorReason = format("ASP.NET Core app failed to start after %d milliseconds", m_pConfig->QueryStartupTimeLimitInMS()); m_waitForShutdown = false; StopClr(); @@ -195,7 +195,7 @@ IN_PROCESS_APPLICATION::ExecuteApplication() auto context = std::make_shared(); - ErrorContext errorContext; // unused + ErrorContext errorContext; // unused if (s_fMainCallback == nullptr) { @@ -247,15 +247,15 @@ IN_PROCESS_APPLICATION::ExecuteApplication() auto startupReturnCode = context->m_hostFxr.InitializeForApp(context->m_argc, context->m_argv.get(), m_dotnetExeKnownLocation); if (startupReturnCode != 0) { - throw InvalidOperationException(format(L"Error occured when initializing inprocess application, Return code: 0x%x", startupReturnCode)); + throw InvalidOperationException(format(L"Error occurred when initializing in-process application, Return code: 0x%x", startupReturnCode)); } if (m_pConfig->QueryCallStartupHook()) { PWSTR startupHookValue = NULL; - // Will get property not found if the enviroment variable isn't set. + // Will get property not found if the environment variable isn't set. context->m_hostFxr.GetRuntimePropertyValue(DOTNETCORE_STARTUP_HOOK, &startupHookValue); - + if (startupHookValue == NULL) { RETURN_IF_NOT_ZERO(context->m_hostFxr.SetRuntimePropertyValue(DOTNETCORE_STARTUP_HOOK, ASPNETCORE_STARTUP_ASSEMBLY)); diff --git a/src/Servers/IIS/AspNetCoreModuleV2/OutOfProcessRequestHandler/forwardinghandler.cpp b/src/Servers/IIS/AspNetCoreModuleV2/OutOfProcessRequestHandler/forwardinghandler.cpp index 4fbf2abf40..82737cf24b 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/OutOfProcessRequestHandler/forwardinghandler.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/OutOfProcessRequestHandler/forwardinghandler.cpp @@ -55,6 +55,7 @@ FORWARDING_HANDLER::FORWARDING_HANDLER( LOG_TRACE(L"FORWARDING_HANDLER::FORWARDING_HANDLER"); m_fWebSocketSupported = m_pApplication->QueryWebsocketStatus(); + m_fForwardResponseConnectionHeader = m_pApplication->QueryConfig()->QueryForwardResponseConnectionHeader()->Equals(L"true", /* ignoreCase */ 1); InitializeSRWLock(&m_RequestLock); } @@ -2212,10 +2213,14 @@ FORWARDING_HANDLER::SetStatusAndHeaders( break; } __fallthrough; - case HttpHeaderConnection: case HttpHeaderDate: continue; - + case HttpHeaderConnection: + if (!m_fForwardResponseConnectionHeader) + { + continue; + } + break; case HttpHeaderServer: fServerHeaderPresent = TRUE; break; diff --git a/src/Servers/IIS/AspNetCoreModuleV2/OutOfProcessRequestHandler/forwardinghandler.h b/src/Servers/IIS/AspNetCoreModuleV2/OutOfProcessRequestHandler/forwardinghandler.h index cc855dfcf2..c564a7b4f0 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/OutOfProcessRequestHandler/forwardinghandler.h +++ b/src/Servers/IIS/AspNetCoreModuleV2/OutOfProcessRequestHandler/forwardinghandler.h @@ -179,6 +179,7 @@ private: HINTERNET m_hRequest; FORWARDING_REQUEST_STATUS m_RequestStatus; + BOOL m_fForwardResponseConnectionHeader; BOOL m_fWebSocketEnabled; BOOL m_fWebSocketSupported; BOOL m_fResponseHeadersReceivedAndSet; diff --git a/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/environmentvariablehash.h b/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/environmentvariablehash.h index 82541f1bdf..248ca1aa93 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/environmentvariablehash.h +++ b/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/environmentvariablehash.h @@ -13,6 +13,7 @@ #define ASPNETCORE_IIS_AUTH_BASIC L"basic;" #define ASPNETCORE_IIS_AUTH_ANONYMOUS L"anonymous;" #define ASPNETCORE_IIS_AUTH_NONE L"none" +#define ANCM_PREFER_ENVIRONMENT_VARIABLES_ENV_STR L"ANCM_PREFER_ENVIRONMENT_VARIABLES" // // The key used for hash-table lookups, consists of the port on which the http process is created. diff --git a/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/environmentvariablehelpers.h b/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/environmentvariablehelpers.h index 2842dc0245..3a6fef9335 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/environmentvariablehelpers.h +++ b/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/environmentvariablehelpers.h @@ -78,9 +78,27 @@ public: environmentVariables.insert_or_assign(HOSTING_STARTUP_ASSEMBLIES_ENV_STR, hostingStartupValues); } + auto preferEnvironmentVariablesSetting = Environment::GetEnvironmentVariableValue(ANCM_PREFER_ENVIRONMENT_VARIABLES_ENV_STR).value_or(L"false"); + auto preferEnvironmentVariables = equals_ignore_case(L"1", preferEnvironmentVariablesSetting) || equals_ignore_case(L"true", preferEnvironmentVariablesSetting); + for (auto& environmentVariable : environmentVariables) { - environmentVariable.second = Environment::ExpandEnvironmentVariables(environmentVariable.second); + if (preferEnvironmentVariables) + { + auto env = Environment::GetEnvironmentVariableValue(environmentVariable.first); + if (env.has_value()) + { + environmentVariable.second = env.value(); + } + else + { + environmentVariable.second = Environment::ExpandEnvironmentVariables(environmentVariable.second); + } + } + else + { + environmentVariable.second = Environment::ExpandEnvironmentVariables(environmentVariable.second); + } } return environmentVariables; diff --git a/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/requesthandler_config.cpp b/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/requesthandler_config.cpp index d3a705e9a2..c32bc51ced 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/requesthandler_config.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/requesthandler_config.cpp @@ -7,6 +7,7 @@ #include "environmentvariablehash.h" #include "exceptions.h" #include "config_utility.h" +#include "Environment.h" REQUESTHANDLER_CONFIG::~REQUESTHANDLER_CONFIG() { @@ -101,6 +102,8 @@ REQUESTHANDLER_CONFIG::Populate( BSTR bstrBasicAuthSection = NULL; BSTR bstrAnonymousAuthSection = NULL; BSTR bstrAspNetCoreSection = NULL; + std::optional launcherPathEnv; + std::optional launcherArgsEnv; pAdminManager = pHttpServer->GetAdminManager(); try @@ -248,12 +251,47 @@ REQUESTHANDLER_CONFIG::Populate( goto Finished; } - hr = GetElementStringProperty(pAspNetCoreElement, - CS_ASPNETCORE_PROCESS_EXE_PATH, - &m_struProcessPath); - if (FAILED(hr)) + // We prefer the environment variables for LAUNCHER_PATH and LAUNCHER_ARGS + try { - goto Finished; + launcherPathEnv = Environment::GetEnvironmentVariableValue(CS_ANCM_LAUNCHER_PATH); + launcherArgsEnv = Environment::GetEnvironmentVariableValue(CS_ANCM_LAUNCHER_ARGS); + } + catch(...) + { + FINISHED_IF_FAILED(E_FAIL); + } + + if (launcherPathEnv.has_value()) + { + hr = m_struProcessPath.Copy(launcherPathEnv.value().c_str()); + FINISHED_IF_FAILED(hr); + } + else + { + hr = GetElementStringProperty(pAspNetCoreElement, + CS_ASPNETCORE_PROCESS_EXE_PATH, + &m_struProcessPath); + if (FAILED(hr)) + { + goto Finished; + } + } + + if (launcherArgsEnv.has_value()) + { + hr = m_struArguments.Copy(launcherArgsEnv.value().c_str()); + FINISHED_IF_FAILED(hr); + } + else + { + hr = GetElementStringProperty(pAspNetCoreElement, + CS_ASPNETCORE_PROCESS_ARGUMENTS, + &m_struArguments); + if (FAILED(hr)) + { + goto Finished; + } } hr = GetElementStringProperty(pAspNetCoreElement, @@ -281,14 +319,6 @@ REQUESTHANDLER_CONFIG::Populate( goto Finished; } - hr = GetElementStringProperty(pAspNetCoreElement, - CS_ASPNETCORE_PROCESS_ARGUMENTS, - &m_struArguments); - if (FAILED(hr)) - { - goto Finished; - } - hr = GetElementDWORDProperty(pAspNetCoreElement, CS_ASPNETCORE_RAPID_FAILS_PER_MINUTE, &m_dwRapidFailsPerMinute); @@ -385,6 +415,12 @@ REQUESTHANDLER_CONFIG::Populate( goto Finished; } + hr = ConfigUtility::FindForwardResponseConnectionHeader(pAspNetCoreElement, m_struForwardResponseConnectionHeader); + if (FAILED(hr)) + { + goto Finished; + } + Finished: if (pAspNetCoreElement != NULL) diff --git a/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/requesthandler_config.h b/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/requesthandler_config.h index 5f06c12695..1552e8d075 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/requesthandler_config.h +++ b/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/requesthandler_config.h @@ -224,6 +224,12 @@ public: return !m_fEnableOutOfProcessConsoleRedirection.Equals(L"false", 1); } + STRU* + QueryForwardResponseConnectionHeader() + { + return &m_struForwardResponseConnectionHeader; + } + protected: // @@ -255,6 +261,7 @@ protected: STRU m_struApplicationPhysicalPath; STRU m_struApplicationVirtualPath; STRU m_struConfigPath; + STRU m_struForwardResponseConnectionHeader; BOOL m_fStdoutLogEnabled; BOOL m_fForwardWindowsAuthToken; BOOL m_fDisableStartUpErrorPage; diff --git a/src/Servers/IIS/IIS/benchmarks/IIS.Performance/FirstRequestConfig.cs b/src/Servers/IIS/IIS/benchmarks/IIS.Performance/FirstRequestConfig.cs index 727746871f..c048b28f8d 100644 --- a/src/Servers/IIS/IIS/benchmarks/IIS.Performance/FirstRequestConfig.cs +++ b/src/Servers/IIS/IIS/benchmarks/IIS.Performance/FirstRequestConfig.cs @@ -27,10 +27,10 @@ namespace BenchmarkDotNet.Attributes Add(JitOptimizationsValidator.FailOnError); - Add(Job.Core + Add(Job.Default .With(CsProjCoreToolchain.From(NetCoreAppSettings.NetCoreApp21)) .With(new GcMode { Server = true }) - .WithTargetCount(10) + .WithIterationCount(10) .WithInvocationCount(1) .WithUnrollFactor(1) .With(RunStrategy.ColdStart)); diff --git a/src/Servers/IIS/IIS/benchmarks/IIS.Performance/IIS.Performance.csproj b/src/Servers/IIS/IIS/benchmarks/IIS.Performance/IIS.Performance.csproj index 9435906632..2abb302273 100644 --- a/src/Servers/IIS/IIS/benchmarks/IIS.Performance/IIS.Performance.csproj +++ b/src/Servers/IIS/IIS/benchmarks/IIS.Performance/IIS.Performance.csproj @@ -33,8 +33,9 @@ - + +
      diff --git a/src/Servers/IIS/IIS/benchmarks/IIS.Performance/StartupTimeBenchmark.cs b/src/Servers/IIS/IIS/benchmarks/IIS.Performance/StartupTimeBenchmark.cs index 3cb8d3a1c8..dd561126d6 100644 --- a/src/Servers/IIS/IIS/benchmarks/IIS.Performance/StartupTimeBenchmark.cs +++ b/src/Servers/IIS/IIS/benchmarks/IIS.Performance/StartupTimeBenchmark.cs @@ -21,7 +21,7 @@ namespace Microsoft.AspNetCore.Server.IIS.Performance public void Setup() { // Deployers do not work in distributed environments -// see https://github.com/aspnet/AspNetCore/issues/10268 and https://github.com/aspnet/Extensions/issues/1697 +// see https://github.com/dotnet/aspnetcore/issues/10268 and https://github.com/dotnet/extensions/issues/1697 #pragma warning disable 0618 var deploymentParameters = new DeploymentParameters(Path.Combine(TestPathUtilities.GetSolutionRootDirectory("IISIntegration"), "test/testassets/InProcessWebSite"), ServerType.IISExpress, diff --git a/src/Servers/IIS/IIS/ref/Microsoft.AspNetCore.Server.IIS.csproj b/src/Servers/IIS/IIS/ref/Microsoft.AspNetCore.Server.IIS.csproj index d83e4e0301..a470dc92b4 100644 --- a/src/Servers/IIS/IIS/ref/Microsoft.AspNetCore.Server.IIS.csproj +++ b/src/Servers/IIS/IIS/ref/Microsoft.AspNetCore.Server.IIS.csproj @@ -13,7 +13,6 @@ - diff --git a/src/Servers/IIS/IIS/ref/Microsoft.AspNetCore.Server.IIS.netcoreapp.cs b/src/Servers/IIS/IIS/ref/Microsoft.AspNetCore.Server.IIS.netcoreapp.cs index 635097115d..479bfe8e6c 100644 --- a/src/Servers/IIS/IIS/ref/Microsoft.AspNetCore.Server.IIS.netcoreapp.cs +++ b/src/Servers/IIS/IIS/ref/Microsoft.AspNetCore.Server.IIS.netcoreapp.cs @@ -6,9 +6,9 @@ namespace Microsoft.AspNetCore.Builder public partial class IISServerOptions { public IISServerOptions() { } - public bool AllowSynchronousIO { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public string AuthenticationDisplayName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public bool AutomaticAuthentication { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public bool AllowSynchronousIO { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public string AuthenticationDisplayName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public bool AutomaticAuthentication { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public long? MaxRequestBodySize { get { throw null; } set { } } } } @@ -24,7 +24,7 @@ namespace Microsoft.AspNetCore.Server.IIS public sealed partial class BadHttpRequestException : System.IO.IOException { internal BadHttpRequestException() { } - public int StatusCode { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public int StatusCode { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } } public static partial class HttpContextExtensions { diff --git a/src/Servers/IIS/IIS/src/Core/DuplexStream.cs b/src/Servers/IIS/IIS/src/Core/DuplexStream.cs index 8ff01c778f..73dbd4cbb9 100644 --- a/src/Servers/IIS/IIS/src/Core/DuplexStream.cs +++ b/src/Servers/IIS/IIS/src/Core/DuplexStream.cs @@ -60,9 +60,29 @@ namespace Microsoft.AspNetCore.Server.IIS.Core return _requestBody.ReadAsync(buffer, offset, count, cancellationToken); } + public override ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default) + { + return _requestBody.ReadAsync(buffer, cancellationToken); + } + public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { return _responseBody.WriteAsync(buffer, offset, count, cancellationToken); } + + public override ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default) + { + return _responseBody.WriteAsync(buffer, cancellationToken); + } + + public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) + { + return _requestBody.CopyToAsync(destination, bufferSize, cancellationToken); + } + + public override Task FlushAsync(CancellationToken cancellationToken) + { + return _responseBody.FlushAsync(cancellationToken); + } } } diff --git a/src/Servers/IIS/IIS/src/Core/HttpRequestStream.cs b/src/Servers/IIS/IIS/src/Core/HttpRequestStream.cs index 9fa5d7405e..8d90fd69a5 100644 --- a/src/Servers/IIS/IIS/src/Core/HttpRequestStream.cs +++ b/src/Servers/IIS/IIS/src/Core/HttpRequestStream.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.IO; using System.Runtime.ExceptionServices; using System.Threading; using System.Threading.Tasks; @@ -35,40 +36,12 @@ namespace Microsoft.AspNetCore.Server.IIS.Core public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state) { - var task = ReadAsync(buffer, offset, count, default(CancellationToken), state); - if (callback != null) - { - task.ContinueWith(t => callback.Invoke(t)); - } - return task; + return TaskToApm.Begin(ReadAsync(buffer, offset, count), callback, state); } public override int EndRead(IAsyncResult asyncResult) { - return ((Task)asyncResult).GetAwaiter().GetResult(); - } - - private Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken, object state) - { - var tcs = new TaskCompletionSource(state); - var task = ReadAsync(buffer, offset, count, cancellationToken); - task.ContinueWith((task2, state2) => - { - var tcs2 = (TaskCompletionSource)state2; - if (task2.IsCanceled) - { - tcs2.SetCanceled(); - } - else if (task2.IsFaulted) - { - tcs2.SetException(task2.Exception); - } - else - { - tcs2.SetResult(task2.Result); - } - }, tcs, cancellationToken); - return tcs.Task; + return TaskToApm.End(asyncResult); } public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) @@ -97,6 +70,18 @@ namespace Microsoft.AspNetCore.Server.IIS.Core } } + public override async Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) + { + try + { + await _body.CopyToAsync(destination, cancellationToken); + } + catch (ConnectionAbortedException ex) + { + throw new TaskCanceledException("The request was aborted", ex); + } + } + public void StartAcceptingReads(IISHttpContext body) { // Only start if not aborted diff --git a/src/Servers/IIS/IIS/src/Core/HttpResponseStream.cs b/src/Servers/IIS/IIS/src/Core/HttpResponseStream.cs index 739b6b16f5..b1d0c1f22a 100644 --- a/src/Servers/IIS/IIS/src/Core/HttpResponseStream.cs +++ b/src/Servers/IIS/IIS/src/Core/HttpResponseStream.cs @@ -24,7 +24,7 @@ namespace Microsoft.AspNetCore.Server.IIS.Core public override void Flush() { - FlushAsync(default(CancellationToken)).GetAwaiter().GetResult(); + FlushAsync(default).GetAwaiter().GetResult(); } public override Task FlushAsync(CancellationToken cancellationToken) @@ -41,45 +41,17 @@ namespace Microsoft.AspNetCore.Server.IIS.Core throw new InvalidOperationException(CoreStrings.SynchronousWritesDisallowed); } - WriteAsync(buffer, offset, count, default(CancellationToken)).GetAwaiter().GetResult(); + WriteAsync(buffer, offset, count, default).GetAwaiter().GetResult(); } public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state) { - var task = WriteAsync(buffer, offset, count, default(CancellationToken), state); - if (callback != null) - { - task.ContinueWith(t => callback.Invoke(t)); - } - return task; + return TaskToApm.Begin(WriteAsync(buffer, offset, count), callback, state); } public override void EndWrite(IAsyncResult asyncResult) { - ((Task)asyncResult).GetAwaiter().GetResult(); - } - - private Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken, object state) - { - var tcs = new TaskCompletionSource(state); - var task = WriteAsync(buffer, offset, count, cancellationToken); - task.ContinueWith((task2, state2) => - { - var tcs2 = (TaskCompletionSource)state2; - if (task2.IsCanceled) - { - tcs2.SetCanceled(); - } - else if (task2.IsFaulted) - { - tcs2.SetException(task2.Exception); - } - else - { - tcs2.SetResult(null); - } - }, tcs, cancellationToken); - return tcs.Task; + TaskToApm.End(asyncResult); } public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) diff --git a/src/Servers/IIS/IIS/src/Core/IISHttpContext.FeatureCollection.cs b/src/Servers/IIS/IIS/src/Core/IISHttpContext.FeatureCollection.cs index cab956dd0a..1cdfc4be32 100644 --- a/src/Servers/IIS/IIS/src/Core/IISHttpContext.FeatureCollection.cs +++ b/src/Servers/IIS/IIS/src/Core/IISHttpContext.FeatureCollection.cs @@ -15,6 +15,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Http.Features.Authentication; +using Microsoft.AspNetCore.HttpSys.Internal; using Microsoft.AspNetCore.Server.IIS.Core.IO; using Microsoft.AspNetCore.WebUtilities; using Microsoft.Extensions.Logging; @@ -37,7 +38,7 @@ namespace Microsoft.AspNetCore.Server.IIS.Core // then the list of `implementedFeatures` in the generated code project MUST also be updated. private int _featureRevision; - private string _httpProtocolVersion = null; + private string _httpProtocolVersion; private X509Certificate2 _certificate; private List> MaybeExtra; @@ -86,30 +87,8 @@ namespace Microsoft.AspNetCore.Server.IIS.Core string IHttpRequestFeature.Protocol { - get - { - if (_httpProtocolVersion == null) - { - var protocol = HttpVersion; - if (protocol.Major == 1 && protocol.Minor == 1) - { - _httpProtocolVersion = "HTTP/1.1"; - } - else if (protocol.Major == 1 && protocol.Minor == 0) - { - _httpProtocolVersion = "HTTP/1.0"; - } - else - { - _httpProtocolVersion = "HTTP/" + protocol.ToString(2); - } - } - return _httpProtocolVersion; - } - set - { - _httpProtocolVersion = value; - } + get => _httpProtocolVersion ??= HttpProtocol.GetHttpProtocol(HttpVersion); + set => _httpProtocolVersion = value; } string IHttpRequestFeature.Scheme diff --git a/src/Servers/IIS/IIS/src/Core/IISHttpContext.IO.cs b/src/Servers/IIS/IIS/src/Core/IISHttpContext.IO.cs index 68141058c5..5f831eb01a 100644 --- a/src/Servers/IIS/IIS/src/Core/IISHttpContext.IO.cs +++ b/src/Servers/IIS/IIS/src/Core/IISHttpContext.IO.cs @@ -3,11 +3,10 @@ using System; using System.Buffers; -using System.Net.Http; +using System.IO; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; namespace Microsoft.AspNetCore.Server.IIS.Core @@ -54,6 +53,16 @@ namespace Microsoft.AspNetCore.Server.IIS.Core } } + internal Task CopyToAsync(Stream destination, CancellationToken cancellationToken) + { + if (!HasStartedConsumingRequestBody) + { + InitializeRequestIO(); + } + + return _bodyInputPipe.Reader.CopyToAsync(destination, cancellationToken); + } + /// /// Writes data to the output pipe. /// diff --git a/src/Servers/IIS/IIS/src/Core/IISHttpContext.cs b/src/Servers/IIS/IIS/src/Core/IISHttpContext.cs index cae357e776..74f0cbae6b 100644 --- a/src/Servers/IIS/IIS/src/Core/IISHttpContext.cs +++ b/src/Servers/IIS/IIS/src/Core/IISHttpContext.cs @@ -409,7 +409,7 @@ namespace Microsoft.AspNetCore.Server.IIS.Core } } - public abstract Task ProcessRequestAsync(); + public abstract Task ProcessRequestAsync(); public void OnStarting(Func callback, object state) { @@ -599,10 +599,9 @@ namespace Microsoft.AspNetCore.Server.IIS.Core private async Task HandleRequest() { - bool successfulRequest = false; try { - successfulRequest = await ProcessRequestAsync(); + await ProcessRequestAsync(); } catch (Exception ex) { @@ -610,19 +609,9 @@ namespace Microsoft.AspNetCore.Server.IIS.Core } finally { - // Post completion after completing the request to resume the state machine - PostCompletion(ConvertRequestCompletionResults(successfulRequest)); - - // Dispose the context Dispose(); } } - - private static NativeMethods.REQUEST_NOTIFICATION_STATUS ConvertRequestCompletionResults(bool success) - { - return success ? NativeMethods.REQUEST_NOTIFICATION_STATUS.RQ_NOTIFICATION_CONTINUE - : NativeMethods.REQUEST_NOTIFICATION_STATUS.RQ_NOTIFICATION_FINISH_REQUEST; - } } } diff --git a/src/Servers/IIS/IIS/src/Core/IISHttpContextOfT.cs b/src/Servers/IIS/IIS/src/Core/IISHttpContextOfT.cs index 372eadfa71..060f105233 100644 --- a/src/Servers/IIS/IIS/src/Core/IISHttpContextOfT.cs +++ b/src/Servers/IIS/IIS/src/Core/IISHttpContextOfT.cs @@ -21,32 +21,33 @@ namespace Microsoft.AspNetCore.Server.IIS.Core _application = application; } - public override async Task ProcessRequestAsync() + public override async Task ProcessRequestAsync() { - InitializeContext(); - var context = default(TContext); var success = true; try { - context = _application.CreateContext(this); + InitializeContext(); + + try + { + context = _application.CreateContext(this); + + await _application.ProcessRequestAsync(context); + } + catch (BadHttpRequestException ex) + { + SetBadRequestState(ex); + ReportApplicationError(ex); + success = false; + } + catch (Exception ex) + { + ReportApplicationError(ex); + success = false; + } - await _application.ProcessRequestAsync(context); - } - catch (BadHttpRequestException ex) - { - SetBadRequestState(ex); - ReportApplicationError(ex); - success = false; - } - catch (Exception ex) - { - ReportApplicationError(ex); - success = false; - } - finally - { await CompleteResponseBodyAsync(); _streams.Stop(); @@ -56,36 +57,18 @@ namespace Microsoft.AspNetCore.Server.IIS.Core // Dispose } - if (_onCompleted != null) + if (!_requestAborted) { - await FireOnCompleted(); + await ProduceEnd(); + } + else if (!HasResponseStarted && _requestRejectedException == null) + { + // If the request was aborted and no response was sent, there's no + // meaningful status code to log. + StatusCode = 0; + success = false; } - } - if (!_requestAborted) - { - await ProduceEnd(); - } - else if (!HasResponseStarted && _requestRejectedException == null) - { - // If the request was aborted and no response was sent, there's no - // meaningful status code to log. - StatusCode = 0; - success = false; - } - - try - { - _application.DisposeContext(context, _applicationException); - } - catch (Exception ex) - { - // TODO Log this - _applicationException = _applicationException ?? ex; - success = false; - } - finally - { // Complete response writer and request reader pipe sides _bodyOutput.Dispose(); _bodyInputPipe?.Reader.Complete(); @@ -104,7 +87,36 @@ namespace Microsoft.AspNetCore.Server.IIS.Core await _readBodyTask; } } - return success; + catch (Exception ex) + { + success = false; + ReportApplicationError(ex); + } + finally + { + // We're done with anything that touches the request or response, unblock the client. + PostCompletion(ConvertRequestCompletionResults(success)); + + if (_onCompleted != null) + { + await FireOnCompleted(); + } + + try + { + _application.DisposeContext(context, _applicationException); + } + catch (Exception ex) + { + ReportApplicationError(ex); + } + } + } + + private static NativeMethods.REQUEST_NOTIFICATION_STATUS ConvertRequestCompletionResults(bool success) + { + return success ? NativeMethods.REQUEST_NOTIFICATION_STATUS.RQ_NOTIFICATION_CONTINUE + : NativeMethods.REQUEST_NOTIFICATION_STATUS.RQ_NOTIFICATION_FINISH_REQUEST; } } } diff --git a/src/Servers/IIS/IIS/src/Core/OutputProducer.cs b/src/Servers/IIS/IIS/src/Core/OutputProducer.cs index 2dee24f7b9..7d295c6614 100644 --- a/src/Servers/IIS/IIS/src/Core/OutputProducer.cs +++ b/src/Servers/IIS/IIS/src/Core/OutputProducer.cs @@ -20,7 +20,7 @@ namespace Microsoft.AspNetCore.Server.IIS.Core private readonly Pipe _pipe; // https://github.com/dotnet/corefxlab/issues/1334 - // https://github.com/aspnet/AspNetCore/issues/8843 + // https://github.com/dotnet/aspnetcore/issues/8843 // Pipelines don't support multiple awaiters on flush. This is temporary until it does. // _lastFlushTask field should only be get or set under the _flushLock. private readonly object _flushLock = new object(); diff --git a/src/Servers/IIS/IIS/src/Microsoft.AspNetCore.Server.IIS.csproj b/src/Servers/IIS/IIS/src/Microsoft.AspNetCore.Server.IIS.csproj index c645b12741..a5998c3fa0 100644 --- a/src/Servers/IIS/IIS/src/Microsoft.AspNetCore.Server.IIS.csproj +++ b/src/Servers/IIS/IIS/src/Microsoft.AspNetCore.Server.IIS.csproj @@ -14,11 +14,13 @@ + + @@ -38,7 +40,6 @@ - diff --git a/src/Servers/IIS/IIS/src/StartupHook.cs b/src/Servers/IIS/IIS/src/StartupHook.cs index 5f8f3edf5a..10fe17b4d2 100644 --- a/src/Servers/IIS/IIS/src/StartupHook.cs +++ b/src/Servers/IIS/IIS/src/StartupHook.cs @@ -81,6 +81,7 @@ internal class StartupHook var exceptionDetailProvider = new ExceptionDetailsProvider( new PhysicalFileProvider(contentRoot), + logger: null, sourceCodeLineCount: 6); // The startup hook is only present when detailed errors are allowed, so diff --git a/src/Servers/IIS/IIS/test/Common.FunctionalTests/AppOfflineTests.cs b/src/Servers/IIS/IIS/test/Common.FunctionalTests/AppOfflineTests.cs index 4bd29675ae..89c44e719f 100644 --- a/src/Servers/IIS/IIS/test/Common.FunctionalTests/AppOfflineTests.cs +++ b/src/Servers/IIS/IIS/test/Common.FunctionalTests/AppOfflineTests.cs @@ -77,7 +77,7 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests DeletePublishOutput(deploymentResult); } - [ConditionalFact(Skip = "https://github.com/aspnet/AspNetCore/issues/3835")] + [ConditionalFact(Skip = "https://github.com/dotnet/aspnetcore/issues/3835")] public async Task AppOfflineDroppedWhileSiteFailedToStartInRequestHandler_SiteStops_InProcess() { var deploymentResult = await DeployApp(HostingModel.InProcess); @@ -211,7 +211,7 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests [ConditionalTheory] [InlineData(HostingModel.InProcess)] [InlineData(HostingModel.OutOfProcess)] - [Flaky("https://github.com/aspnet/AspNetCore/issues/7075", FlakyOn.All)] + [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/7075")] public async Task AppOfflineAddedAndRemovedStress(HostingModel hostingModel) { var deploymentResult = await AssertStarts(hostingModel); diff --git a/src/Servers/IIS/IIS/test/Common.FunctionalTests/BasicAuthTests.cs b/src/Servers/IIS/IIS/test/Common.FunctionalTests/BasicAuthTests.cs index 6130cdf882..36520369a6 100644 --- a/src/Servers/IIS/IIS/test/Common.FunctionalTests/BasicAuthTests.cs +++ b/src/Servers/IIS/IIS/test/Common.FunctionalTests/BasicAuthTests.cs @@ -23,7 +23,7 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests public static TestMatrix TestVariants => TestMatrix.ForServers(DeployerSelector.ServerType) - .WithTfms(Tfm.NetCoreApp31) + .WithTfms(Tfm.NetCoreApp50) .WithApplicationTypes(ApplicationType.Portable) .WithAllHostingModels(); diff --git a/src/Servers/IIS/IIS/test/Common.FunctionalTests/ClientCertificateTests.cs b/src/Servers/IIS/IIS/test/Common.FunctionalTests/ClientCertificateTests.cs index ccc4ea6458..b0847f84a1 100644 --- a/src/Servers/IIS/IIS/test/Common.FunctionalTests/ClientCertificateTests.cs +++ b/src/Servers/IIS/IIS/test/Common.FunctionalTests/ClientCertificateTests.cs @@ -29,13 +29,13 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests public static TestMatrix TestVariants => TestMatrix.ForServers(DeployerSelector.ServerType) - .WithTfms(Tfm.NetCoreApp31) + .WithTfms(Tfm.NetCoreApp50) .WithAllApplicationTypes() .WithAllHostingModels(); [ConditionalTheory] [MemberData(nameof(TestVariants))] - [OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7, WindowsVersions.Win2008R2)] + [MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win8)] public Task HttpsNoClientCert_NoClientCert(TestVariant variant) { return ClientCertTest(variant, sendClientCert: false); @@ -43,7 +43,7 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests [ConditionalTheory] [MemberData(nameof(TestVariants))] - [OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7, WindowsVersions.Win2008R2)] + [MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win8)] public Task HttpsClientCert_GetCertInformation(TestVariant variant) { return ClientCertTest(variant, sendClientCert: true); @@ -54,7 +54,7 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests var port = TestPortHelper.GetNextSSLPort(); var deploymentParameters = Fixture.GetBaseDeploymentParameters(variant); deploymentParameters.ApplicationBaseUriHint = $"https://localhost:{port}/"; - deploymentParameters.AddHttpsToServerConfig(); + deploymentParameters.AddHttpsWithClientCertToServerConfig(); var handler = new HttpClientHandler { diff --git a/src/Servers/IIS/IIS/test/Common.FunctionalTests/CommonStartupTests.cs b/src/Servers/IIS/IIS/test/Common.FunctionalTests/CommonStartupTests.cs index 82df45fdbf..38f4dae818 100644 --- a/src/Servers/IIS/IIS/test/Common.FunctionalTests/CommonStartupTests.cs +++ b/src/Servers/IIS/IIS/test/Common.FunctionalTests/CommonStartupTests.cs @@ -19,7 +19,7 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests public static TestMatrix TestVariants => TestMatrix.ForServers(DeployerSelector.ServerType) - .WithTfms(Tfm.NetCoreApp31) + .WithTfms(Tfm.NetCoreApp50) .WithAllApplicationTypes() .WithAllHostingModels(); diff --git a/src/Servers/IIS/IIS/test/Common.FunctionalTests/ConfigurationChangeTests.cs b/src/Servers/IIS/IIS/test/Common.FunctionalTests/ConfigurationChangeTests.cs index 386b749747..66ffde7f20 100644 --- a/src/Servers/IIS/IIS/test/Common.FunctionalTests/ConfigurationChangeTests.cs +++ b/src/Servers/IIS/IIS/test/Common.FunctionalTests/ConfigurationChangeTests.cs @@ -79,7 +79,7 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests [ConditionalTheory] [InlineData(HostingModel.InProcess)] [InlineData(HostingModel.OutOfProcess)] - [Flaky("https://github.com/aspnet/AspNetCore-Internal/issues/1794", FlakyOn.All)] + [QuarantinedTest("https://github.com/dotnet/aspnetcore-internal/issues/1794")] public async Task ConfigurationTouchedStress(HostingModel hostingModel) { var deploymentResult = await DeployAsync(Fixture.GetBaseDeploymentParameters(hostingModel)); diff --git a/src/Servers/IIS/IIS/test/Common.FunctionalTests/HttpsTests.cs b/src/Servers/IIS/IIS/test/Common.FunctionalTests/HttpsTests.cs index c93faacdc0..5b2323223a 100644 --- a/src/Servers/IIS/IIS/test/Common.FunctionalTests/HttpsTests.cs +++ b/src/Servers/IIS/IIS/test/Common.FunctionalTests/HttpsTests.cs @@ -26,13 +26,13 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests public static TestMatrix TestVariants => TestMatrix.ForServers(DeployerSelector.ServerType) - .WithTfms(Tfm.NetCoreApp31) + .WithTfms(Tfm.NetCoreApp50) .WithAllApplicationTypes() .WithAllHostingModels(); [ConditionalTheory] [MemberData(nameof(TestVariants))] - [OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7, WindowsVersions.Win2008R2)] + [MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win8)] public async Task HttpsHelloWorld(TestVariant variant) { var port = TestPortHelper.GetNextSSLPort(); @@ -80,6 +80,7 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests var deploymentParameters = Fixture.GetBaseDeploymentParameters(HostingModel.InProcess); deploymentParameters.ApplicationBaseUriHint = $"https://localhost:{port}/"; deploymentParameters.AddHttpsToServerConfig(); + deploymentParameters.SetWindowsAuth(false); deploymentParameters.AddServerConfigAction( (element, root) => { element.Descendants("site").Single().Element("application").SetAttributeValue("path", "/" + appName); @@ -88,11 +89,29 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests var deploymentResult = await DeployAsync(deploymentParameters); var client = CreateNonValidatingClient(deploymentResult); - Assert.Equal(deploymentParameters.ApplicationBaseUriHint + appName, await client.GetStringAsync($"/{appName}/ServerAddresses")); } [ConditionalFact] + [RequiresNewHandler] + [MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win10)] + public async Task CheckProtocolIsHttp2() + { + var port = TestPortHelper.GetNextSSLPort(); + var deploymentParameters = Fixture.GetBaseDeploymentParameters(HostingModel.InProcess); + deploymentParameters.ApplicationBaseUriHint = $"https://localhost:{port}/"; + deploymentParameters.AddHttpsToServerConfig(); + deploymentParameters.SetWindowsAuth(false); + + var deploymentResult = await DeployAsync(deploymentParameters); + var client = CreateNonValidatingClient(deploymentResult); + client.DefaultRequestVersion = HttpVersion.Version20; + + Assert.Equal("HTTP/2", await client.GetStringAsync($"/CheckProtocol")); + } + + [ConditionalFact] + [RequiresNewHandler] [RequiresNewShim] public async Task AncmHttpsPortCanBeOverriden() { @@ -188,6 +207,35 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests Assert.Equal("NOVALUE", await client.GetStringAsync("/ANCM_HTTPS_PORT")); } + [ConditionalFact] + [RequiresNewHandler] + [RequiresNewShim] + public async Task SetsConnectionCloseHeader() + { + // Only tests OutOfProcess as the Connection header is removed for out of process and not inprocess. + // This test checks a quirk to allow setting the Connection header. + var deploymentParameters = Fixture.GetBaseDeploymentParameters(HostingModel.OutOfProcess); + + deploymentParameters.HandlerSettings["forwardResponseConnectionHeader"] = "true"; + var deploymentResult = await DeployAsync(deploymentParameters); + + var response = await deploymentResult.HttpClient.GetAsync("ConnectionClose"); + Assert.Equal(true, response.Headers.ConnectionClose); + } + + [ConditionalFact] + [RequiresNewHandler] + [RequiresNewShim] + public async Task ConnectionCloseIsNotPropagated() + { + var deploymentParameters = Fixture.GetBaseDeploymentParameters(HostingModel.OutOfProcess); + + var deploymentResult = await DeployAsync(deploymentParameters); + + var response = await deploymentResult.HttpClient.GetAsync("ConnectionClose"); + Assert.Null(response.Headers.ConnectionClose); + } + private static HttpClient CreateNonValidatingClient(IISDeploymentResult deploymentResult) { var handler = new HttpClientHandler diff --git a/src/Servers/IIS/IIS/test/Common.FunctionalTests/Inprocess/EnvironmentVariableTests.cs b/src/Servers/IIS/IIS/test/Common.FunctionalTests/Inprocess/EnvironmentVariableTests.cs index 7f8d003eb7..2a8140aec5 100644 --- a/src/Servers/IIS/IIS/test/Common.FunctionalTests/Inprocess/EnvironmentVariableTests.cs +++ b/src/Servers/IIS/IIS/test/Common.FunctionalTests/Inprocess/EnvironmentVariableTests.cs @@ -126,5 +126,22 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests.InProcess deploymentParameters.WebConfigBasedEnvironmentVariables["OtherVariable"] = "%TestVariable%;Hello"; Assert.Equal("World;Hello", await GetStringAsync(deploymentParameters, "/GetEnvironmentVariable?name=OtherVariable")); } + + [ConditionalTheory] + [RequiresIIS(IISCapability.PoolEnvironmentVariables)] + [RequiresNewHandler] + [RequiresNewShim] + [InlineData(HostingModel.InProcess)] + [InlineData(HostingModel.OutOfProcess)] + public async Task PreferEnvironmentVariablesOverWebConfigWhenConfigured(HostingModel hostingModel) + { + var deploymentParameters = Fixture.GetBaseDeploymentParameters(hostingModel); + + var environment = "Development"; + deploymentParameters.EnvironmentVariables["ANCM_PREFER_ENVIRONMENT_VARIABLES"] = "true"; + deploymentParameters.EnvironmentVariables["ASPNETCORE_ENVIRONMENT"] = environment; + deploymentParameters.WebConfigBasedEnvironmentVariables.Add("ASPNETCORE_ENVIRONMENT", "Debug"); + Assert.Equal(environment, await GetStringAsync(deploymentParameters, "/GetEnvironmentVariable?name=ASPNETCORE_ENVIRONMENT")); + } } } diff --git a/src/Servers/IIS/IIS/test/Common.FunctionalTests/Inprocess/ErrorPagesTests.cs b/src/Servers/IIS/IIS/test/Common.FunctionalTests/Inprocess/ErrorPagesTests.cs index a1d540a8b3..5104520781 100644 --- a/src/Servers/IIS/IIS/test/Common.FunctionalTests/Inprocess/ErrorPagesTests.cs +++ b/src/Servers/IIS/IIS/test/Common.FunctionalTests/Inprocess/ErrorPagesTests.cs @@ -31,7 +31,7 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests.InProcess StopServer(); var responseString = await response.Content.ReadAsStringAsync(); - Assert.Contains("HTTP Error 500.0 - ANCM In-Process Handler Load Failure", responseString); + Assert.Contains("500.0", responseString); VerifyNoExtraTrailingBytes(responseString); await AssertLink(response); @@ -71,7 +71,7 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests.InProcess StopServer(); var responseString = await response.Content.ReadAsStringAsync(); - Assert.Contains("HTTP Error 500.0 - ANCM Out-Of-Process Handler Load Failure", responseString); + Assert.Contains("500.0", responseString); VerifyNoExtraTrailingBytes(responseString); await AssertLink(response); @@ -94,7 +94,7 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests.InProcess StopServer(); var responseString = await response.Content.ReadAsStringAsync(); - Assert.Contains("HTTP Error 500.30 - ANCM In-Process Start Failure", responseString); + Assert.Contains("500.30", responseString); VerifyNoExtraTrailingBytes(responseString); await AssertLink(response); diff --git a/src/Servers/IIS/IIS/test/Common.FunctionalTests/Inprocess/FrebTests.cs b/src/Servers/IIS/IIS/test/Common.FunctionalTests/Inprocess/FrebTests.cs index 2e2866e98e..7573fc021d 100644 --- a/src/Servers/IIS/IIS/test/Common.FunctionalTests/Inprocess/FrebTests.cs +++ b/src/Servers/IIS/IIS/test/Common.FunctionalTests/Inprocess/FrebTests.cs @@ -80,7 +80,7 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests.InProcess // I think this test is flaky due to freb file not being created quickly enough. // Adding extra logging, marking as flaky, and repeating should help [ConditionalFact] - [Flaky("https://github.com/aspnet/AspNetCore-Internal/issues/2570", FlakyOn.Helix.All)] + [QuarantinedTest("https://github.com/dotnet/aspnetcore-internal/issues/2570")] [Repeat(10)] [RequiresIIS(IISCapability.FailedRequestTracingModule)] public async Task CheckFrebDisconnect() diff --git a/src/Servers/IIS/IIS/test/Common.FunctionalTests/Inprocess/ResponseBodyTests.cs b/src/Servers/IIS/IIS/test/Common.FunctionalTests/Inprocess/ResponseBodyTests.cs index d45024441e..d1867c8044 100644 --- a/src/Servers/IIS/IIS/test/Common.FunctionalTests/Inprocess/ResponseBodyTests.cs +++ b/src/Servers/IIS/IIS/test/Common.FunctionalTests/Inprocess/ResponseBodyTests.cs @@ -30,5 +30,12 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests.InProcess { Assert.Equal(20, (await _fixture.Client.GetByteArrayAsync($"/FlushedPipeAndThenUnflushedPipe")).Length); } + + [ConditionalFact] + [RequiresNewHandler] + public async Task ResponseBodyTest_BodyCompletionNotBlockedByOnCompleted() + { + Assert.Equal("SlowOnCompleted", await _fixture.Client.GetStringAsync($"/SlowOnCompleted")); + } } } diff --git a/src/Servers/IIS/IIS/test/Common.FunctionalTests/Inprocess/StartupExceptionTests.cs b/src/Servers/IIS/IIS/test/Common.FunctionalTests/Inprocess/StartupExceptionTests.cs index cb7fc11d29..4dcd67f63c 100644 --- a/src/Servers/IIS/IIS/test/Common.FunctionalTests/Inprocess/StartupExceptionTests.cs +++ b/src/Servers/IIS/IIS/test/Common.FunctionalTests/Inprocess/StartupExceptionTests.cs @@ -91,7 +91,7 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests.InProcess Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode); var responseText = await response.Content.ReadAsStringAsync(); - Assert.Contains("500.30 - ANCM In-Process Start Failure", responseText); + Assert.Contains("500.30", responseText); } } } diff --git a/src/Servers/IIS/IIS/test/Common.FunctionalTests/Inprocess/StartupTests.cs b/src/Servers/IIS/IIS/test/Common.FunctionalTests/Inprocess/StartupTests.cs index 1c070242e0..5f6fed86e9 100644 --- a/src/Servers/IIS/IIS/test/Common.FunctionalTests/Inprocess/StartupTests.cs +++ b/src/Servers/IIS/IIS/test/Common.FunctionalTests/Inprocess/StartupTests.cs @@ -62,8 +62,14 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests.InProcess StopServer(); EventLogHelpers.VerifyEventLogEvent(deploymentResult, EventLogHelpers.UnableToStart(deploymentResult, subError), Logger); - - Assert.Contains("HTTP Error 500.0 - ANCM In-Process Handler Load Failure", await response.Content.ReadAsStringAsync()); + if (DeployerSelector.HasNewShim) + { + Assert.Contains("500.0", await response.Content.ReadAsStringAsync()); + } + else + { + Assert.Contains("500.0", await response.Content.ReadAsStringAsync()); + } } [ConditionalFact] @@ -113,7 +119,7 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests.InProcess [SkipIfNotAdmin] [RequiresNewShim] [RequiresIIS(IISCapability.PoolEnvironmentVariables)] - [SkipOnHelix("https://github.com/aspnet/AspNetCore-Internal/issues/2221")] + [SkipOnHelix("https://github.com/dotnet/aspnetcore-internal/issues/2221")] public async Task StartsWithDotnetInstallLocation(RuntimeArchitecture runtimeArchitecture) { var deploymentParameters = Fixture.GetBaseDeploymentParameters(); @@ -172,7 +178,7 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests.InProcess public static TestMatrix TestVariants => TestMatrix.ForServers(DeployerSelector.ServerType) - .WithTfms(Tfm.NetCoreApp31) + .WithTfms(Tfm.NetCoreApp50) .WithAllApplicationTypes() .WithAncmV2InProcess(); @@ -206,6 +212,7 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests.InProcess } [ConditionalFact] + [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/20153")] public async Task DetectsOverriddenServer() { var deploymentParameters = Fixture.GetBaseDeploymentParameters(Fixture.InProcessTestSite); @@ -223,6 +230,7 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests.InProcess } [ConditionalFact] + [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/20153")] public async Task LogsStartupExceptionExitError() { var deploymentParameters = Fixture.GetBaseDeploymentParameters(Fixture.InProcessTestSite); @@ -271,7 +279,7 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests.InProcess if (DeployerSelector.HasNewShim) { - await AssertSiteFailsToStartWithInProcessStaticContent(deploymentResult, "HTTP Error 500.32 - ANCM Failed to Load dll"); + await AssertSiteFailsToStartWithInProcessStaticContent(deploymentResult, "500.32"); } else { @@ -314,11 +322,11 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests.InProcess if (DeployerSelector.HasNewShim) { - await AssertSiteFailsToStartWithInProcessStaticContent(deploymentResult, "500.32 - ANCM Failed to Load dll"); + await AssertSiteFailsToStartWithInProcessStaticContent(deploymentResult, "500.32"); } else { - await AssertSiteFailsToStartWithInProcessStaticContent(deploymentResult, "500.0 - ANCM In-Process Handler Load Failure"); + await AssertSiteFailsToStartWithInProcessStaticContent(deploymentResult, "500.0"); } } @@ -335,7 +343,7 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests.InProcess if (DeployerSelector.HasNewShim) { - await AssertSiteFailsToStartWithInProcessStaticContent(deploymentResult, "HTTP Error 500.32 - ANCM Failed to Load dll"); + await AssertSiteFailsToStartWithInProcessStaticContent(deploymentResult, "500.32"); } else { @@ -354,7 +362,7 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests.InProcess Helpers.ModifyFrameworkVersionInRuntimeConfig(deploymentResult); if (DeployerSelector.HasNewShim) { - await AssertSiteFailsToStartWithInProcessStaticContent(deploymentResult, "HTTP Error 500.31 - ANCM Failed to Find Native Dependencies"); + await AssertSiteFailsToStartWithInProcessStaticContent(deploymentResult, "500.31"); } else { @@ -374,7 +382,7 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests.InProcess File.Delete(Path.Combine(deploymentResult.ContentRoot, "InProcessWebSite.dll")); if (DeployerSelector.HasNewShim) { - await AssertSiteFailsToStartWithInProcessStaticContent(deploymentResult, "HTTP Error 500.38 - ANCM Application DLL Not Found"); + await AssertSiteFailsToStartWithInProcessStaticContent(deploymentResult, "500.38"); } else { @@ -396,7 +404,7 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests.InProcess Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode); var responseContent = await response.Content.ReadAsStringAsync(); - Assert.Contains("HTTP Error 500.31 - ANCM Failed to Find Native Dependencies", responseContent); + Assert.Contains("500.31", responseContent); Assert.Contains("The framework 'Microsoft.NETCore.App', version '2.9.9'", responseContent); } else @@ -416,14 +424,14 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests.InProcess if (DeployerSelector.HasNewShim && DeployerSelector.HasNewHandler) { - await AssertSiteFailsToStartWithInProcessStaticContent(deploymentResult, "HTTP Error 500.33 - ANCM Request Handler Load Failure "); + await AssertSiteFailsToStartWithInProcessStaticContent(deploymentResult, "500.33"); EventLogHelpers.VerifyEventLogEvent(deploymentResult, EventLogHelpers.InProcessFailedToFindRequestHandler(deploymentResult), Logger); } else if (DeployerSelector.HasNewShim) { // Forwards compat tests fail earlier due to a error with the M.AspNetCore.Server.IIS package. - await AssertSiteFailsToStartWithInProcessStaticContent(deploymentResult, "HTTP Error 500.31 - ANCM Failed to Find Native Dependencies"); + await AssertSiteFailsToStartWithInProcessStaticContent(deploymentResult, "500.31"); EventLogHelpers.VerifyEventLogEvent(deploymentResult, EventLogHelpers.InProcessFailedToFindNativeDependencies(deploymentResult), Logger); } @@ -462,7 +470,7 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests.InProcess if (DeployerSelector.HasNewHandler) { var responseContent = await response.Content.ReadAsStringAsync(); - Assert.Contains("ANCM Failed to Start Within Startup Time Limit", responseContent); + Assert.Contains("500.37", responseContent); } } } @@ -701,6 +709,7 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests.InProcess [InlineData("DOTNET_ENVIRONMENT", "deVelopment")] [InlineData("ASPNETCORE_DETAILEDERRORS", "1")] [InlineData("ASPNETCORE_DETAILEDERRORS", "TRUE")] + [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/20153")] public async Task ExceptionIsLoggedToEventLogAndPutInResponseWhenDeveloperExceptionPageIsEnabled(string environmentVariable, string value) { var deploymentParameters = Fixture.GetBaseDeploymentParameters(); @@ -725,6 +734,7 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests.InProcess [ConditionalFact] [RequiresNewHandler] + [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/20153")] public async Task ExceptionIsLoggedToEventLogAndPutInResponseWhenDeveloperExceptionPageIsEnabledViaWebConfig() { var deploymentParameters = Fixture.GetBaseDeploymentParameters(); @@ -752,6 +762,7 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests.InProcess [RequiresNewHandler] [InlineData("ThrowInStartup")] [InlineData("ThrowInStartupGenericHost")] + [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/20153")] public async Task ExceptionIsLoggedToEventLogAndPutInResponseDuringHostingStartupProcess(string startupType) { var deploymentParameters = Fixture.GetBaseDeploymentParameters(); @@ -765,7 +776,6 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests.InProcess Assert.Contains("InvalidOperationException", content); Assert.Contains("TestSite.Program.Main", content); Assert.Contains("From Configure", content); - Assert.DoesNotContain("ANCM In-Process Start Failure", content); StopServer(); @@ -775,6 +785,7 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests.InProcess [ConditionalFact] [RequiresIIS(IISCapability.PoolEnvironmentVariables)] [RequiresNewHandler] + [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/20153")] public async Task ExceptionIsNotLoggedToResponseWhenStartupHookIsDisabled() { var deploymentParameters = Fixture.GetBaseDeploymentParameters(); @@ -797,6 +808,7 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests.InProcess [ConditionalFact] [RequiresNewHandler] + [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/20153")] public async Task ExceptionIsLoggedToEventLogDoesNotWriteToResponse() { var deploymentParameters = Fixture.GetBaseDeploymentParameters(); @@ -875,6 +887,39 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests.InProcess Assert.True(result.IsSuccessStatusCode); } + [ConditionalTheory] + [RequiresIIS(IISCapability.PoolEnvironmentVariables)] + [RequiresNewShim] + [RequiresNewHandler] + [InlineData(HostingModel.InProcess)] + [InlineData(HostingModel.OutOfProcess)] + public async Task EnvironmentVariableForLauncherPathIsPreferred(HostingModel hostingModel) + { + var deploymentParameters = Fixture.GetBaseDeploymentParameters(hostingModel); + + deploymentParameters.EnvironmentVariables["ANCM_LAUNCHER_PATH"] = _dotnetLocation; + deploymentParameters.WebConfigActionList.Add(WebConfigHelpers.AddOrModifyAspNetCoreSection("processPath", "nope")); + + await StartAsync(deploymentParameters); + } + + [ConditionalTheory] + [RequiresIIS(IISCapability.PoolEnvironmentVariables)] + [RequiresNewShim] + [RequiresNewHandler] + [InlineData(HostingModel.InProcess)] + [InlineData(HostingModel.OutOfProcess)] + public async Task EnvironmentVariableForLauncherArgsIsPreferred(HostingModel hostingModel) + { + var deploymentParameters = Fixture.GetBaseDeploymentParameters(hostingModel); + using var publishedApp = await deploymentParameters.ApplicationPublisher.Publish(deploymentParameters, LoggerFactory.CreateLogger("test")); + + deploymentParameters.EnvironmentVariables["ANCM_LAUNCHER_ARGS"] = Path.ChangeExtension(Path.Combine(publishedApp.Path, deploymentParameters.ApplicationPublisher.ApplicationPath), ".dll"); + deploymentParameters.WebConfigActionList.Add(WebConfigHelpers.AddOrModifyAspNetCoreSection("arguments", "nope")); + + await StartAsync(deploymentParameters); + } + private static void VerifyDotnetRuntimeEventLog(IISDeploymentResult deploymentResult) { var entries = GetEventLogsFromDotnetRuntime(deploymentResult); @@ -932,7 +977,7 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests.InProcess private Task AssertSiteFailsToStartWithInProcessStaticContent(IISDeploymentResult deploymentResult) { - return AssertSiteFailsToStartWithInProcessStaticContent(deploymentResult, "HTTP Error 500.0 - ANCM In-Process Handler Load Failure"); + return AssertSiteFailsToStartWithInProcessStaticContent(deploymentResult, "500.0"); } private async Task AssertSiteFailsToStartWithInProcessStaticContent(IISDeploymentResult deploymentResult, string error) diff --git a/src/Servers/IIS/IIS/test/Common.FunctionalTests/Inprocess/SynchronousReadAndWriteTests.cs b/src/Servers/IIS/IIS/test/Common.FunctionalTests/Inprocess/SynchronousReadAndWriteTests.cs index 034cc48fd7..e896acc67c 100644 --- a/src/Servers/IIS/IIS/test/Common.FunctionalTests/Inprocess/SynchronousReadAndWriteTests.cs +++ b/src/Servers/IIS/IIS/test/Common.FunctionalTests/Inprocess/SynchronousReadAndWriteTests.cs @@ -22,6 +22,7 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests.InProcess } [ConditionalFact] + [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/7341")] public async Task ReadAndWriteSynchronously() { for (int i = 0; i < 100; i++) diff --git a/src/Servers/IIS/IIS/test/Common.FunctionalTests/LogFileTests.cs b/src/Servers/IIS/IIS/test/Common.FunctionalTests/LogFileTests.cs index 70f6a6b8b4..0a60187315 100644 --- a/src/Servers/IIS/IIS/test/Common.FunctionalTests/LogFileTests.cs +++ b/src/Servers/IIS/IIS/test/Common.FunctionalTests/LogFileTests.cs @@ -21,7 +21,7 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests public static TestMatrix TestVariants => TestMatrix.ForServers(DeployerSelector.ServerType) - .WithTfms(Tfm.NetCoreApp31) + .WithTfms(Tfm.NetCoreApp50) .WithAllApplicationTypes() .WithAllHostingModels(); @@ -171,7 +171,7 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests [ConditionalTheory] [MemberData(nameof(TestVariants))] - [Flaky("https://github.com/aspnet/AspNetCore-Internal/issues/2200", FlakyOn.All)] + [QuarantinedTest("https://github.com/dotnet/aspnetcore-internal/issues/2200")] public async Task CheckUTF8File(TestVariant variant) { var path = "CheckConsoleFunctions"; diff --git a/src/Servers/IIS/IIS/test/Common.FunctionalTests/MultiApplicationTests.cs b/src/Servers/IIS/IIS/test/Common.FunctionalTests/MultiApplicationTests.cs index eb94b683f1..15a9354108 100644 --- a/src/Servers/IIS/IIS/test/Common.FunctionalTests/MultiApplicationTests.cs +++ b/src/Servers/IIS/IIS/test/Common.FunctionalTests/MultiApplicationTests.cs @@ -50,7 +50,7 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests if (DeployerSelector.HasNewShim) { - Assert.Contains("500.35 - ANCM Multiple In-Process Applications in same Process", await result2.Content.ReadAsStringAsync()); + Assert.Contains("500.35", await result2.Content.ReadAsStringAsync()); } EventLogHelpers.VerifyEventLogEvent(result, EventLogHelpers.OnlyOneAppPerAppPool(), Logger); @@ -77,7 +77,7 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests if (DeployerSelector.HasNewShim) { - Assert.Contains("500.34 - ANCM Mixed Hosting Models Not Supported", await result2.Content.ReadAsStringAsync()); + Assert.Contains("500.34", await result2.Content.ReadAsStringAsync()); } EventLogHelpers.VerifyEventLogEvent(result, "Mixed hosting model is not supported.", Logger); diff --git a/src/Servers/IIS/IIS/test/Common.FunctionalTests/OutOfProcess/AspNetCorePortTests.cs b/src/Servers/IIS/IIS/test/Common.FunctionalTests/OutOfProcess/AspNetCorePortTests.cs index 8557b89f94..969fc2c05b 100644 --- a/src/Servers/IIS/IIS/test/Common.FunctionalTests/OutOfProcess/AspNetCorePortTests.cs +++ b/src/Servers/IIS/IIS/test/Common.FunctionalTests/OutOfProcess/AspNetCorePortTests.cs @@ -29,7 +29,7 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests.OutOfProcess public static TestMatrix TestVariants => TestMatrix.ForServers(DeployerSelector.ServerType) - .WithTfms(Tfm.NetCoreApp31) + .WithTfms(Tfm.NetCoreApp50) .WithApplicationTypes(ApplicationType.Portable); public static IEnumerable InvalidTestVariants diff --git a/src/Servers/IIS/IIS/test/Common.FunctionalTests/OutOfProcess/GlobalVersionTests.cs b/src/Servers/IIS/IIS/test/Common.FunctionalTests/OutOfProcess/GlobalVersionTests.cs index 7837d47dce..e94c4503fb 100644 --- a/src/Servers/IIS/IIS/test/Common.FunctionalTests/OutOfProcess/GlobalVersionTests.cs +++ b/src/Servers/IIS/IIS/test/Common.FunctionalTests/OutOfProcess/GlobalVersionTests.cs @@ -83,7 +83,7 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests.OutOfProcess var response = await deploymentResult.HttpClient.GetAsync(_helloWorldRequest); Assert.False(response.IsSuccessStatusCode); var responseString = await response.Content.ReadAsStringAsync(); - Assert.Contains("HTTP Error 500.0 - ANCM Out-Of-Process Handler Load Failure", responseString); + Assert.Contains("500.0", responseString); } [ConditionalTheory] diff --git a/src/Servers/IIS/IIS/test/Common.FunctionalTests/OutOfProcess/HelloWorldTest.cs b/src/Servers/IIS/IIS/test/Common.FunctionalTests/OutOfProcess/HelloWorldTest.cs index deaf45f95a..2054011422 100644 --- a/src/Servers/IIS/IIS/test/Common.FunctionalTests/OutOfProcess/HelloWorldTest.cs +++ b/src/Servers/IIS/IIS/test/Common.FunctionalTests/OutOfProcess/HelloWorldTest.cs @@ -23,7 +23,7 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests.OutOfProcess public static TestMatrix TestVariants => TestMatrix.ForServers(DeployerSelector.ServerType) - .WithTfms(Tfm.NetCoreApp31) + .WithTfms(Tfm.NetCoreApp50) .WithAllApplicationTypes(); [ConditionalTheory] diff --git a/src/Servers/IIS/IIS/test/Common.FunctionalTests/PublishedApplicationPublisher.cs b/src/Servers/IIS/IIS/test/Common.FunctionalTests/PublishedApplicationPublisher.cs index 8bf370e640..bbcb510945 100644 --- a/src/Servers/IIS/IIS/test/Common.FunctionalTests/PublishedApplicationPublisher.cs +++ b/src/Servers/IIS/IIS/test/Common.FunctionalTests/PublishedApplicationPublisher.cs @@ -43,7 +43,7 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests private string GetProjectReferencePublishLocation(DeploymentParameters deploymentParameters) { // Deployers do not work in distributed environments -// see https://github.com/aspnet/AspNetCore/issues/10268 and https://github.com/aspnet/Extensions/issues/1697 +// see https://github.com/dotnet/aspnetcore/issues/10268 and https://github.com/dotnet/extensions/issues/1697 #pragma warning disable 0618 var testAssetsBasePath = Path.Combine(TestPathUtilities.GetSolutionRootDirectory("IISIntegration"), "IIS", "test", "testassets", _applicationName); #pragma warning restore 0618 diff --git a/src/Servers/IIS/IIS/test/Common.FunctionalTests/PublishedSitesFixture.cs b/src/Servers/IIS/IIS/test/Common.FunctionalTests/PublishedSitesFixture.cs index a0fd00f869..70a7606e31 100644 --- a/src/Servers/IIS/IIS/test/Common.FunctionalTests/PublishedSitesFixture.cs +++ b/src/Servers/IIS/IIS/test/Common.FunctionalTests/PublishedSitesFixture.cs @@ -48,7 +48,7 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests RuntimeFlavor = RuntimeFlavor.CoreClr, RuntimeArchitecture = RuntimeArchitecture.x64, HostingModel = hostingModel, - TargetFramework = Tfm.NetCoreApp31 + TargetFramework = Tfm.NetCoreApp50 }); } diff --git a/src/Servers/IIS/IIS/test/Common.FunctionalTests/Utilities/FunctionalTestsBase.cs b/src/Servers/IIS/IIS/test/Common.FunctionalTests/Utilities/FunctionalTestsBase.cs index e15863c0fb..1817d27fd5 100644 --- a/src/Servers/IIS/IIS/test/Common.FunctionalTests/Utilities/FunctionalTestsBase.cs +++ b/src/Servers/IIS/IIS/test/Common.FunctionalTests/Utilities/FunctionalTestsBase.cs @@ -6,6 +6,7 @@ using System.IO; using System.Threading.Tasks; using Microsoft.AspNetCore.Server.IIS.FunctionalTests; using Microsoft.AspNetCore.Server.IntegrationTesting.IIS; +using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.Logging.Testing; using Xunit.Abstractions; diff --git a/src/Servers/IIS/IIS/test/Common.FunctionalTests/Utilities/IISTestSiteFixture.cs b/src/Servers/IIS/IIS/test/Common.FunctionalTests/Utilities/IISTestSiteFixture.cs index 645e595cbf..5c3829c09e 100644 --- a/src/Servers/IIS/IIS/test/Common.FunctionalTests/Utilities/IISTestSiteFixture.cs +++ b/src/Servers/IIS/IIS/test/Common.FunctionalTests/Utilities/IISTestSiteFixture.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Net.Http; using Microsoft.AspNetCore.Server.IntegrationTesting; using Microsoft.AspNetCore.Server.IntegrationTesting.IIS; +using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Testing; @@ -85,7 +86,7 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests { RuntimeArchitecture = RuntimeArchitecture.x64, RuntimeFlavor = RuntimeFlavor.CoreClr, - TargetFramework = Tfm.NetCoreApp31, + TargetFramework = Tfm.NetCoreApp50, HostingModel = HostingModel.InProcess, PublishApplicationBeforeDeployment = true, ApplicationPublisher = new PublishedApplicationPublisher(Helpers.GetInProcessTestSitesName()), diff --git a/src/Servers/IIS/IIS/test/Common.FunctionalTests/WindowsAuthTests.cs b/src/Servers/IIS/IIS/test/Common.FunctionalTests/WindowsAuthTests.cs index f9716c2a26..e22f96dadc 100644 --- a/src/Servers/IIS/IIS/test/Common.FunctionalTests/WindowsAuthTests.cs +++ b/src/Servers/IIS/IIS/test/Common.FunctionalTests/WindowsAuthTests.cs @@ -21,7 +21,7 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests public static TestMatrix TestVariants => TestMatrix.ForServers(DeployerSelector.ServerType) - .WithTfms(Tfm.NetCoreApp31) + .WithTfms(Tfm.NetCoreApp50) .WithApplicationTypes(ApplicationType.Portable) .WithAllHostingModels(); diff --git a/src/Servers/IIS/IIS/test/IIS.FunctionalTests/IIS.FunctionalTests.csproj b/src/Servers/IIS/IIS/test/IIS.FunctionalTests/IIS.FunctionalTests.csproj index a7858c63b2..5d689a6bee 100644 --- a/src/Servers/IIS/IIS/test/IIS.FunctionalTests/IIS.FunctionalTests.csproj +++ b/src/Servers/IIS/IIS/test/IIS.FunctionalTests/IIS.FunctionalTests.csproj @@ -1,6 +1,5 @@  - $(DefaultNetCoreTargetFramework) IIS.FunctionalTests @@ -24,7 +23,6 @@ - diff --git a/src/Servers/IIS/IIS/test/IIS.NewHandler.FunctionalTests/IIS.NewHandler.FunctionalTests.csproj b/src/Servers/IIS/IIS/test/IIS.NewHandler.FunctionalTests/IIS.NewHandler.FunctionalTests.csproj index 9a74ecd31d..530f6f8bbe 100644 --- a/src/Servers/IIS/IIS/test/IIS.NewHandler.FunctionalTests/IIS.NewHandler.FunctionalTests.csproj +++ b/src/Servers/IIS/IIS/test/IIS.NewHandler.FunctionalTests/IIS.NewHandler.FunctionalTests.csproj @@ -30,7 +30,6 @@ - diff --git a/src/Servers/IIS/IIS/test/IIS.NewShim.FunctionalTests/IIS.NewShim.FunctionalTests.csproj b/src/Servers/IIS/IIS/test/IIS.NewShim.FunctionalTests/IIS.NewShim.FunctionalTests.csproj index 03adce1d09..07b99fa8d5 100644 --- a/src/Servers/IIS/IIS/test/IIS.NewShim.FunctionalTests/IIS.NewShim.FunctionalTests.csproj +++ b/src/Servers/IIS/IIS/test/IIS.NewShim.FunctionalTests/IIS.NewShim.FunctionalTests.csproj @@ -26,7 +26,6 @@ - diff --git a/src/Servers/IIS/IIS/test/IIS.Shared.FunctionalTests/RequiresIISAttribute.cs b/src/Servers/IIS/IIS/test/IIS.Shared.FunctionalTests/RequiresIISAttribute.cs index fad488d484..d6fc23a803 100644 --- a/src/Servers/IIS/IIS/test/IIS.Shared.FunctionalTests/RequiresIISAttribute.cs +++ b/src/Servers/IIS/IIS/test/IIS.Shared.FunctionalTests/RequiresIISAttribute.cs @@ -61,6 +61,11 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests var ancmConfigPath = Path.Combine(Environment.SystemDirectory, "inetsrv", "config", "schema", "aspnetcore_schema.xml"); + if (!File.Exists(ancmConfigPath)) + { + ancmConfigPath = Path.Combine(Environment.SystemDirectory, "inetsrv", "config", "schema", "aspnetcore_schema_v2.xml"); + } + if (!File.Exists(ancmConfigPath) && !SkipInVSTSAttribute.RunningInVSTS) { _skipReasonStatic = "IIS Schema is not installed."; diff --git a/src/Servers/IIS/IIS/test/IIS.Tests/ClientDisconnectTests.cs b/src/Servers/IIS/IIS/test/IIS.Tests/ClientDisconnectTests.cs index b662fd4732..354b8a2229 100644 --- a/src/Servers/IIS/IIS/test/IIS.Tests/ClientDisconnectTests.cs +++ b/src/Servers/IIS/IIS/test/IIS.Tests/ClientDisconnectTests.cs @@ -14,7 +14,7 @@ using Xunit; namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests { [SkipIfHostableWebCoreNotAvailable] - [OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7, "https://github.com/aspnet/IISIntegration/issues/866")] + [MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win8, SkipReason = "https://github.com/aspnet/IISIntegration/issues/866")] public class ClientDisconnectTests : StrictTestServerTests { [ConditionalFact] @@ -225,7 +225,7 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests } [ConditionalFact] - [Flaky("https://github.com/aspnet/AspNetCore-Internal/issues/1817", FlakyOn.AzP.Windows)] + [QuarantinedTest("https://github.com/dotnet/aspnetcore-internal/issues/1817")] public async Task ReaderThrowsResetExceptionOnInvalidBody() { var requestStartedCompletionSource = CreateTaskCompletionSource(); diff --git a/src/Servers/IIS/IIS/test/IIS.Tests/ConnectionIdFeatureTests.cs b/src/Servers/IIS/IIS/test/IIS.Tests/ConnectionIdFeatureTests.cs index 8efb9601a3..7847fffccb 100644 --- a/src/Servers/IIS/IIS/test/IIS.Tests/ConnectionIdFeatureTests.cs +++ b/src/Servers/IIS/IIS/test/IIS.Tests/ConnectionIdFeatureTests.cs @@ -9,7 +9,7 @@ using Xunit; namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests { [SkipIfHostableWebCoreNotAvailable] - [OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7, "https://github.com/aspnet/IISIntegration/issues/866")] + [MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win8, SkipReason = "https://github.com/aspnet/IISIntegration/issues/866")] public class ConnectionIdFeatureTests : StrictTestServerTests { [ConditionalFact] diff --git a/src/Servers/IIS/IIS/test/IIS.Tests/HttpBodyControlFeatureTests.cs b/src/Servers/IIS/IIS/test/IIS.Tests/HttpBodyControlFeatureTests.cs index 981a3e6d45..1700351f6b 100644 --- a/src/Servers/IIS/IIS/test/IIS.Tests/HttpBodyControlFeatureTests.cs +++ b/src/Servers/IIS/IIS/test/IIS.Tests/HttpBodyControlFeatureTests.cs @@ -10,7 +10,7 @@ using Xunit; namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests { [SkipIfHostableWebCoreNotAvailable] - [OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7, "https://github.com/aspnet/IISIntegration/issues/866")] + [MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win8, SkipReason = "https://github.com/aspnet/IISIntegration/issues/866")] public class HttpBodyControlFeatureTests : StrictTestServerTests { [ConditionalFact] diff --git a/src/Servers/IIS/IIS/test/IIS.Tests/IIS.Tests.csproj b/src/Servers/IIS/IIS/test/IIS.Tests/IIS.Tests.csproj index dda87b811a..1b54904dee 100644 --- a/src/Servers/IIS/IIS/test/IIS.Tests/IIS.Tests.csproj +++ b/src/Servers/IIS/IIS/test/IIS.Tests/IIS.Tests.csproj @@ -17,7 +17,6 @@ - diff --git a/src/Servers/IIS/IIS/test/IIS.Tests/MaxRequestBodySizeTests.cs b/src/Servers/IIS/IIS/test/IIS.Tests/MaxRequestBodySizeTests.cs index 8bac31d82c..1f46ed1b4b 100644 --- a/src/Servers/IIS/IIS/test/IIS.Tests/MaxRequestBodySizeTests.cs +++ b/src/Servers/IIS/IIS/test/IIS.Tests/MaxRequestBodySizeTests.cs @@ -15,7 +15,7 @@ using Xunit; namespace IIS.Tests { [SkipIfHostableWebCoreNotAvailable] - [OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7, "https://github.com/aspnet/IISIntegration/issues/866")] + [MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win8, SkipReason = "https://github.com/aspnet/IISIntegration/issues/866")] public class MaxRequestBodySizeTests : LoggedTest { [ConditionalFact] diff --git a/src/Servers/IIS/IIS/test/IIS.Tests/ResponseAbortTests.cs b/src/Servers/IIS/IIS/test/IIS.Tests/ResponseAbortTests.cs index 4b59d31298..c240382137 100644 --- a/src/Servers/IIS/IIS/test/IIS.Tests/ResponseAbortTests.cs +++ b/src/Servers/IIS/IIS/test/IIS.Tests/ResponseAbortTests.cs @@ -12,7 +12,7 @@ using Xunit; namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests { [SkipIfHostableWebCoreNotAvailable] - [OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7, "https://github.com/aspnet/IISIntegration/issues/866")] + [MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win8, SkipReason = "https://github.com/aspnet/IISIntegration/issues/866")] public class ResponseAbortTests : StrictTestServerTests { [ConditionalFact] diff --git a/src/Servers/IIS/IIS/test/IIS.Tests/StrictTestServerTests.cs b/src/Servers/IIS/IIS/test/IIS.Tests/StrictTestServerTests.cs index e1564b8cfe..a7a6573546 100644 --- a/src/Servers/IIS/IIS/test/IIS.Tests/StrictTestServerTests.cs +++ b/src/Servers/IIS/IIS/test/IIS.Tests/StrictTestServerTests.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Threading.Tasks; +using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Testing; using Xunit; diff --git a/src/Servers/IIS/IIS/test/IIS.Tests/TestServerTest.cs b/src/Servers/IIS/IIS/test/IIS.Tests/TestServerTest.cs index e350d679c2..55a4ad6995 100644 --- a/src/Servers/IIS/IIS/test/IIS.Tests/TestServerTest.cs +++ b/src/Servers/IIS/IIS/test/IIS.Tests/TestServerTest.cs @@ -10,7 +10,7 @@ using Xunit; namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests { [SkipIfHostableWebCoreNotAvailable] - [OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7, "https://github.com/aspnet/IISIntegration/issues/866")] + [MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win8, SkipReason = "https://github.com/aspnet/IISIntegration/issues/866")] public class TestServerTest : StrictTestServerTests { [ConditionalFact] diff --git a/src/Servers/IIS/IIS/test/IISExpress.FunctionalTests/IISExpress.FunctionalTests.csproj b/src/Servers/IIS/IIS/test/IISExpress.FunctionalTests/IISExpress.FunctionalTests.csproj index 23a5ecdf71..d874d0093e 100644 --- a/src/Servers/IIS/IIS/test/IISExpress.FunctionalTests/IISExpress.FunctionalTests.csproj +++ b/src/Servers/IIS/IIS/test/IISExpress.FunctionalTests/IISExpress.FunctionalTests.csproj @@ -28,7 +28,6 @@ - diff --git a/src/Servers/IIS/IIS/test/IISExpress.FunctionalTests/InProcess/WebSocketTests.cs b/src/Servers/IIS/IIS/test/IISExpress.FunctionalTests/InProcess/WebSocketTests.cs index 1ebc0981bd..4e75d204c8 100644 --- a/src/Servers/IIS/IIS/test/IISExpress.FunctionalTests/InProcess/WebSocketTests.cs +++ b/src/Servers/IIS/IIS/test/IISExpress.FunctionalTests/InProcess/WebSocketTests.cs @@ -13,7 +13,7 @@ using Xunit; namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests { [Collection(IISTestSiteCollection.Name)] - [OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7, SkipReason = "No supported on this platform")] + [MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win8, SkipReason = "No WebSocket supported on Win7")] public class WebSocketsTests { private readonly string _webSocketUri; diff --git a/src/Servers/IIS/IIS/test/IISExpress.FunctionalTests/OutOfProcess/NtlmAuthentationTest.cs b/src/Servers/IIS/IIS/test/IISExpress.FunctionalTests/OutOfProcess/NtlmAuthentationTest.cs index 17f557a338..c63c777c80 100644 --- a/src/Servers/IIS/IIS/test/IISExpress.FunctionalTests/OutOfProcess/NtlmAuthentationTest.cs +++ b/src/Servers/IIS/IIS/test/IISExpress.FunctionalTests/OutOfProcess/NtlmAuthentationTest.cs @@ -27,7 +27,7 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests public static TestMatrix TestVariants => TestMatrix.ForServers(DeployerSelector.ServerType) - .WithTfms(Tfm.NetCoreApp31); + .WithTfms(Tfm.NetCoreApp50); [ConditionalTheory] [RequiresIIS(IISCapability.WindowsAuthentication)] diff --git a/src/Servers/IIS/IIS/test/testassets/InProcessWebSite/Startup.cs b/src/Servers/IIS/IIS/test/testassets/InProcessWebSite/Startup.cs index 7336dda665..95ae863ffd 100644 --- a/src/Servers/IIS/IIS/test/testassets/InProcessWebSite/Startup.cs +++ b/src/Servers/IIS/IIS/test/testassets/InProcessWebSite/Startup.cs @@ -69,6 +69,11 @@ namespace TestSite await ctx.Response.WriteAsync(string.Join(",", serverAddresses.Addresses)); } + private async Task CheckProtocol(HttpContext ctx) + { + await ctx.Response.WriteAsync(ctx.Request.Protocol); + } + private async Task ConsoleWrite(HttpContext ctx) { Console.WriteLine("TEST MESSAGE"); @@ -145,6 +150,12 @@ namespace TestSite return Task.CompletedTask; } + public Task ConnectionClose(HttpContext context) + { + context.Response.Headers["connection"] = "close"; + return Task.CompletedTask; + } + public Task OverrideServer(HttpContext context) { context.Response.Headers["Server"] = "MyServer/7.8"; @@ -1005,5 +1016,12 @@ namespace TestSite await context.Response.WriteAsync(httpsPort.HasValue ? httpsPort.Value.ToString() : "NOVALUE"); } + + public async Task SlowOnCompleted(HttpContext context) + { + // This shouldn't block the response or the server from shutting down. + context.Response.OnCompleted(() => Task.Delay(TimeSpan.FromMinutes(5))); + await context.Response.WriteAsync("SlowOnCompleted"); + } } } diff --git a/src/Servers/IIS/IISIntegration/ref/Microsoft.AspNetCore.Server.IISIntegration.netcoreapp.cs b/src/Servers/IIS/IISIntegration/ref/Microsoft.AspNetCore.Server.IISIntegration.netcoreapp.cs index 9d45cf24cf..d8f4fce669 100644 --- a/src/Servers/IIS/IISIntegration/ref/Microsoft.AspNetCore.Server.IISIntegration.netcoreapp.cs +++ b/src/Servers/IIS/IISIntegration/ref/Microsoft.AspNetCore.Server.IISIntegration.netcoreapp.cs @@ -6,9 +6,9 @@ namespace Microsoft.AspNetCore.Builder public partial class IISOptions { public IISOptions() { } - public string AuthenticationDisplayName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public bool AutomaticAuthentication { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public bool ForwardClientCertificate { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public string AuthenticationDisplayName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public bool AutomaticAuthentication { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public bool ForwardClientCertificate { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } } namespace Microsoft.AspNetCore.Hosting diff --git a/src/Servers/IIS/IntegrationTesting.IIS/src/IISDeployer.cs b/src/Servers/IIS/IntegrationTesting.IIS/src/IISDeployer.cs index acfcaecc42..a25c4a0d5e 100644 --- a/src/Servers/IIS/IntegrationTesting.IIS/src/IISDeployer.cs +++ b/src/Servers/IIS/IntegrationTesting.IIS/src/IISDeployer.cs @@ -334,7 +334,7 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS if (DeploymentParameters.RuntimeArchitecture == RuntimeArchitecture.x86) { - pool.SetAttributeValue("enable32BitAppOnWin64", "true");; + pool.SetAttributeValue("enable32BitAppOnWin64", "true"); } RunServerConfigActions(config, contentRoot); diff --git a/src/Servers/IIS/IntegrationTesting.IIS/src/IISDeploymentParameterExtensions.cs b/src/Servers/IIS/IntegrationTesting.IIS/src/IISDeploymentParameterExtensions.cs index 90f76fd8c2..89b50d5450 100644 --- a/src/Servers/IIS/IntegrationTesting.IIS/src/IISDeploymentParameterExtensions.cs +++ b/src/Servers/IIS/IntegrationTesting.IIS/src/IISDeploymentParameterExtensions.cs @@ -27,6 +27,21 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS } public static void AddHttpsToServerConfig(this IISDeploymentParameters parameters) + { + parameters.AddServerConfigAction( + element => + { + element.Descendants("binding") + .Single() + .SetAttributeValue("protocol", "https"); + + element.Descendants("access") + .Single() + .SetAttributeValue("sslFlags", "None"); + }); + } + + public static void AddHttpsWithClientCertToServerConfig(this IISDeploymentParameters parameters) { parameters.AddServerConfigAction( element => diff --git a/src/Servers/IIS/build/testsite.props b/src/Servers/IIS/build/testsite.props index 066f50aca8..4ff733d387 100644 --- a/src/Servers/IIS/build/testsite.props +++ b/src/Servers/IIS/build/testsite.props @@ -32,7 +32,7 @@ -h "$(IISAppHostConfig)" aspnetcorev2_inprocess.dll - + $(RepoRoot).dotnet\dotnet.exe $(RepoRoot).dotnet\$(NativePlatform)\dotnet.exe diff --git a/src/Servers/Kestrel/Core/ref/Microsoft.AspNetCore.Server.Kestrel.Core.Manual.cs b/src/Servers/Kestrel/Core/ref/Microsoft.AspNetCore.Server.Kestrel.Core.Manual.cs deleted file mode 100644 index 4dec82338a..0000000000 --- a/src/Servers/Kestrel/Core/ref/Microsoft.AspNetCore.Server.Kestrel.Core.Manual.cs +++ /dev/null @@ -1,1957 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -namespace Microsoft.AspNetCore.Server.Kestrel.Core -{ - public partial class KestrelServer : Microsoft.AspNetCore.Hosting.Server.IServer, System.IDisposable - { - internal KestrelServer(Microsoft.AspNetCore.Connections.IConnectionListenerFactory transportFactory, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.ServiceContext serviceContext) { } - } - public sealed partial class BadHttpRequestException : System.IO.IOException - { - internal Microsoft.Extensions.Primitives.StringValues AllowedHeader { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - internal Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.RequestRejectionReason Reason { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]internal static Microsoft.AspNetCore.Server.Kestrel.Core.BadHttpRequestException GetException(Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.RequestRejectionReason reason) { throw null; } - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]internal static Microsoft.AspNetCore.Server.Kestrel.Core.BadHttpRequestException GetException(Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.RequestRejectionReason reason, string detail) { throw null; } - [System.Diagnostics.StackTraceHiddenAttribute] - internal static void Throw(Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.RequestRejectionReason reason) { } - [System.Diagnostics.StackTraceHiddenAttribute] - internal static void Throw(Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.RequestRejectionReason reason, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpMethod method) { } - [System.Diagnostics.StackTraceHiddenAttribute] - internal static void Throw(Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.RequestRejectionReason reason, Microsoft.Extensions.Primitives.StringValues detail) { } - [System.Diagnostics.StackTraceHiddenAttribute] - internal static void Throw(Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.RequestRejectionReason reason, string detail) { } - } - internal sealed partial class LocalhostListenOptions : Microsoft.AspNetCore.Server.Kestrel.Core.ListenOptions - { - internal LocalhostListenOptions(int port) : base (default(System.Net.IPEndPoint)) { } - [System.Diagnostics.DebuggerStepThroughAttribute] - internal override System.Threading.Tasks.Task BindAsync(Microsoft.AspNetCore.Server.Kestrel.Core.Internal.AddressBindContext context) { throw null; } - internal Microsoft.AspNetCore.Server.Kestrel.Core.ListenOptions Clone(System.Net.IPAddress address) { throw null; } - internal override string GetDisplayName() { throw null; } - } - internal sealed partial class AnyIPListenOptions : Microsoft.AspNetCore.Server.Kestrel.Core.ListenOptions - { - internal AnyIPListenOptions(int port) : base (default(System.Net.IPEndPoint)) { } - [System.Diagnostics.DebuggerStepThroughAttribute] - internal override System.Threading.Tasks.Task BindAsync(Microsoft.AspNetCore.Server.Kestrel.Core.Internal.AddressBindContext context) { throw null; } - } - public partial class KestrelServerOptions - { - internal System.Security.Cryptography.X509Certificates.X509Certificate2 DefaultCertificate { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - internal bool IsDevCertLoaded { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - internal bool Latin1RequestHeaders { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - internal System.Collections.Generic.List ListenOptions { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - internal void ApplyDefaultCert(Microsoft.AspNetCore.Server.Kestrel.Https.HttpsConnectionAdapterOptions httpsOptions) { } - internal void ApplyEndpointDefaults(Microsoft.AspNetCore.Server.Kestrel.Core.ListenOptions listenOptions) { } - internal void ApplyHttpsDefaults(Microsoft.AspNetCore.Server.Kestrel.Https.HttpsConnectionAdapterOptions httpsOptions) { } - } - internal static partial class CoreStrings - { - internal static string AddressBindingFailed { get { throw null; } } - internal static string ArgumentOutOfRange { get { throw null; } } - internal static string AuthenticationFailed { get { throw null; } } - internal static string AuthenticationTimedOut { get { throw null; } } - internal static string BadRequest { get { throw null; } } - internal static string BadRequest_BadChunkSizeData { get { throw null; } } - internal static string BadRequest_BadChunkSuffix { get { throw null; } } - internal static string BadRequest_ChunkedRequestIncomplete { get { throw null; } } - internal static string BadRequest_FinalTransferCodingNotChunked { get { throw null; } } - internal static string BadRequest_HeadersExceedMaxTotalSize { get { throw null; } } - internal static string BadRequest_InvalidCharactersInHeaderName { get { throw null; } } - internal static string BadRequest_InvalidContentLength_Detail { get { throw null; } } - internal static string BadRequest_InvalidHostHeader { get { throw null; } } - internal static string BadRequest_InvalidHostHeader_Detail { get { throw null; } } - internal static string BadRequest_InvalidRequestHeadersNoCRLF { get { throw null; } } - internal static string BadRequest_InvalidRequestHeader_Detail { get { throw null; } } - internal static string BadRequest_InvalidRequestLine { get { throw null; } } - internal static string BadRequest_InvalidRequestLine_Detail { get { throw null; } } - internal static string BadRequest_InvalidRequestTarget_Detail { get { throw null; } } - internal static string BadRequest_LengthRequired { get { throw null; } } - internal static string BadRequest_LengthRequiredHttp10 { get { throw null; } } - internal static string BadRequest_MalformedRequestInvalidHeaders { get { throw null; } } - internal static string BadRequest_MethodNotAllowed { get { throw null; } } - internal static string BadRequest_MissingHostHeader { get { throw null; } } - internal static string BadRequest_MultipleContentLengths { get { throw null; } } - internal static string BadRequest_MultipleHostHeaders { get { throw null; } } - internal static string BadRequest_RequestBodyTimeout { get { throw null; } } - internal static string BadRequest_RequestBodyTooLarge { get { throw null; } } - internal static string BadRequest_RequestHeadersTimeout { get { throw null; } } - internal static string BadRequest_RequestLineTooLong { get { throw null; } } - internal static string BadRequest_TooManyHeaders { get { throw null; } } - internal static string BadRequest_UnexpectedEndOfRequestContent { get { throw null; } } - internal static string BadRequest_UnrecognizedHTTPVersion { get { throw null; } } - internal static string BadRequest_UpgradeRequestCannotHavePayload { get { throw null; } } - internal static string BigEndianNotSupported { get { throw null; } } - internal static string BindingToDefaultAddress { get { throw null; } } - internal static string BindingToDefaultAddresses { get { throw null; } } - internal static string CannotUpgradeNonUpgradableRequest { get { throw null; } } - internal static string CertNotFoundInStore { get { throw null; } } - internal static string ConcurrentTimeoutsNotSupported { get { throw null; } } - internal static string ConfigureHttpsFromMethodCall { get { throw null; } } - internal static string ConfigurePathBaseFromMethodCall { get { throw null; } } - internal static string ConnectionAbortedByApplication { get { throw null; } } - internal static string ConnectionAbortedByClient { get { throw null; } } - internal static string ConnectionAbortedDuringServerShutdown { get { throw null; } } - internal static string ConnectionOrStreamAbortedByCancellationToken { get { throw null; } } - internal static string ConnectionShutdownError { get { throw null; } } - internal static string ConnectionTimedBecauseResponseMininumDataRateNotSatisfied { get { throw null; } } - internal static string ConnectionTimedOutByServer { get { throw null; } } - internal static System.Globalization.CultureInfo Culture { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - internal static string DynamicPortOnLocalhostNotSupported { get { throw null; } } - internal static string EndpointAlreadyInUse { get { throw null; } } - internal static string EndPointHttp2NotNegotiated { get { throw null; } } - internal static string EndpointMissingUrl { get { throw null; } } - internal static string EndPointRequiresAtLeastOneProtocol { get { throw null; } } - internal static string FallbackToIPv4Any { get { throw null; } } - internal static string GreaterThanZeroRequired { get { throw null; } } - internal static string HeaderNotAllowedOnResponse { get { throw null; } } - internal static string HeadersAreReadOnly { get { throw null; } } - internal static string HPackErrorDynamicTableSizeUpdateNotAtBeginningOfHeaderBlock { get { throw null; } } - internal static string HPackErrorDynamicTableSizeUpdateTooLarge { get { throw null; } } - internal static string HPackErrorIncompleteHeaderBlock { get { throw null; } } - internal static string HPackErrorIndexOutOfRange { get { throw null; } } - internal static string HPackErrorIntegerTooBig { get { throw null; } } - internal static string HPackErrorNotEnoughBuffer { get { throw null; } } - internal static string HPackHuffmanError { get { throw null; } } - internal static string HPackHuffmanErrorDestinationTooSmall { get { throw null; } } - internal static string HPackHuffmanErrorEOS { get { throw null; } } - internal static string HPackHuffmanErrorIncomplete { get { throw null; } } - internal static string HPackStringLengthTooLarge { get { throw null; } } - internal static string Http2ConnectionFaulted { get { throw null; } } - internal static string Http2ErrorConnectionSpecificHeaderField { get { throw null; } } - internal static string Http2ErrorConnectMustNotSendSchemeOrPath { get { throw null; } } - internal static string Http2ErrorContinuationWithNoHeaders { get { throw null; } } - internal static string Http2ErrorDuplicatePseudoHeaderField { get { throw null; } } - internal static string Http2ErrorFlowControlWindowExceeded { get { throw null; } } - internal static string Http2ErrorFrameOverLimit { get { throw null; } } - internal static string Http2ErrorHeaderNameUppercase { get { throw null; } } - internal static string Http2ErrorHeadersInterleaved { get { throw null; } } - internal static string Http2ErrorHeadersWithTrailersNoEndStream { get { throw null; } } - internal static string Http2ErrorInitialWindowSizeInvalid { get { throw null; } } - internal static string Http2ErrorInvalidPreface { get { throw null; } } - internal static string Http2ErrorMaxStreams { get { throw null; } } - internal static string Http2ErrorMethodInvalid { get { throw null; } } - internal static string Http2ErrorMinTlsVersion { get { throw null; } } - internal static string Http2ErrorMissingMandatoryPseudoHeaderFields { get { throw null; } } - internal static string Http2ErrorPaddingTooLong { get { throw null; } } - internal static string Http2ErrorPseudoHeaderFieldAfterRegularHeaders { get { throw null; } } - internal static string Http2ErrorPushPromiseReceived { get { throw null; } } - internal static string Http2ErrorResponsePseudoHeaderField { get { throw null; } } - internal static string Http2ErrorSettingsAckLengthNotZero { get { throw null; } } - internal static string Http2ErrorSettingsLengthNotMultipleOfSix { get { throw null; } } - internal static string Http2ErrorSettingsParameterOutOfRange { get { throw null; } } - internal static string Http2ErrorStreamAborted { get { throw null; } } - internal static string Http2ErrorStreamClosed { get { throw null; } } - internal static string Http2ErrorStreamHalfClosedRemote { get { throw null; } } - internal static string Http2ErrorStreamIdEven { get { throw null; } } - internal static string Http2ErrorStreamIdle { get { throw null; } } - internal static string Http2ErrorStreamIdNotZero { get { throw null; } } - internal static string Http2ErrorStreamIdZero { get { throw null; } } - internal static string Http2ErrorStreamSelfDependency { get { throw null; } } - internal static string Http2ErrorTrailerNameUppercase { get { throw null; } } - internal static string Http2ErrorTrailersContainPseudoHeaderField { get { throw null; } } - internal static string Http2ErrorUnexpectedFrameLength { get { throw null; } } - internal static string Http2ErrorUnknownPseudoHeaderField { get { throw null; } } - internal static string Http2ErrorWindowUpdateIncrementZero { get { throw null; } } - internal static string Http2ErrorWindowUpdateSizeInvalid { get { throw null; } } - internal static string Http2MinDataRateNotSupported { get { throw null; } } - internal static string HTTP2NoTlsOsx { get { throw null; } } - internal static string HTTP2NoTlsWin7 { get { throw null; } } - internal static string Http2StreamAborted { get { throw null; } } - internal static string Http2StreamErrorAfterHeaders { get { throw null; } } - internal static string Http2StreamErrorLessDataThanLength { get { throw null; } } - internal static string Http2StreamErrorMoreDataThanLength { get { throw null; } } - internal static string Http2StreamErrorPathInvalid { get { throw null; } } - internal static string Http2StreamErrorSchemeMismatch { get { throw null; } } - internal static string Http2StreamResetByApplication { get { throw null; } } - internal static string Http2StreamResetByClient { get { throw null; } } - internal static string Http2TellClientToCalmDown { get { throw null; } } - internal static string InvalidAsciiOrControlChar { get { throw null; } } - internal static string InvalidContentLength_InvalidNumber { get { throw null; } } - internal static string InvalidEmptyHeaderName { get { throw null; } } - internal static string InvalidServerCertificateEku { get { throw null; } } - internal static string InvalidUrl { get { throw null; } } - internal static string KeyAlreadyExists { get { throw null; } } - internal static string MaxRequestBodySizeCannotBeModifiedAfterRead { get { throw null; } } - internal static string MaxRequestBodySizeCannotBeModifiedForUpgradedRequests { get { throw null; } } - internal static string MaxRequestBufferSmallerThanRequestHeaderBuffer { get { throw null; } } - internal static string MaxRequestBufferSmallerThanRequestLineBuffer { get { throw null; } } - internal static string MinimumGracePeriodRequired { get { throw null; } } - internal static string MultipleCertificateSources { get { throw null; } } - internal static string NetworkInterfaceBindingFailed { get { throw null; } } - internal static string NoCertSpecifiedNoDevelopmentCertificateFound { get { throw null; } } - internal static string NonNegativeNumberOrNullRequired { get { throw null; } } - internal static string NonNegativeNumberRequired { get { throw null; } } - internal static string NonNegativeTimeSpanRequired { get { throw null; } } - internal static string OverridingWithKestrelOptions { get { throw null; } } - internal static string OverridingWithPreferHostingUrls { get { throw null; } } - internal static string ParameterReadOnlyAfterResponseStarted { get { throw null; } } - internal static string PositiveFiniteTimeSpanRequired { get { throw null; } } - internal static string PositiveNumberOrNullMinDataRateRequired { get { throw null; } } - internal static string PositiveNumberOrNullRequired { get { throw null; } } - internal static string PositiveNumberRequired { get { throw null; } } - internal static string PositiveTimeSpanRequired { get { throw null; } } - internal static string PositiveTimeSpanRequired1 { get { throw null; } } - internal static string ProtocolSelectionFailed { get { throw null; } } - internal static string RequestProcessingAborted { get { throw null; } } - internal static string RequestProcessingEndError { get { throw null; } } - internal static string RequestTrailersNotAvailable { get { throw null; } } - internal static System.Resources.ResourceManager ResourceManager { get { throw null; } } - internal static string ResponseStreamWasUpgraded { get { throw null; } } - internal static string ServerAlreadyStarted { get { throw null; } } - internal static string ServerCertificateRequired { get { throw null; } } - internal static string ServerShutdownDuringConnectionInitialization { get { throw null; } } - internal static string StartAsyncBeforeGetMemory { get { throw null; } } - internal static string SynchronousReadsDisallowed { get { throw null; } } - internal static string SynchronousWritesDisallowed { get { throw null; } } - internal static string TooFewBytesWritten { get { throw null; } } - internal static string TooManyBytesWritten { get { throw null; } } - internal static string UnableToConfigureHttpsBindings { get { throw null; } } - internal static string UnhandledApplicationException { get { throw null; } } - internal static string UnixSocketPathMustBeAbsolute { get { throw null; } } - internal static string UnknownTransportMode { get { throw null; } } - internal static string UnsupportedAddressScheme { get { throw null; } } - internal static string UpgradeCannotBeCalledMultipleTimes { get { throw null; } } - internal static string UpgradedConnectionLimitReached { get { throw null; } } - internal static string WritingToResponseBodyAfterResponseCompleted { get { throw null; } } - internal static string WritingToResponseBodyNotSupported { get { throw null; } } - internal static string FormatAddressBindingFailed(object address) { throw null; } - internal static string FormatArgumentOutOfRange(object min, object max) { throw null; } - internal static string FormatBadRequest_FinalTransferCodingNotChunked(object detail) { throw null; } - internal static string FormatBadRequest_InvalidContentLength_Detail(object detail) { throw null; } - internal static string FormatBadRequest_InvalidHostHeader_Detail(object detail) { throw null; } - internal static string FormatBadRequest_InvalidRequestHeader_Detail(object detail) { throw null; } - internal static string FormatBadRequest_InvalidRequestLine_Detail(object detail) { throw null; } - internal static string FormatBadRequest_InvalidRequestTarget_Detail(object detail) { throw null; } - internal static string FormatBadRequest_LengthRequired(object detail) { throw null; } - internal static string FormatBadRequest_LengthRequiredHttp10(object detail) { throw null; } - internal static string FormatBadRequest_UnrecognizedHTTPVersion(object detail) { throw null; } - internal static string FormatBindingToDefaultAddress(object address) { throw null; } - internal static string FormatBindingToDefaultAddresses(object address0, object address1) { throw null; } - internal static string FormatCertNotFoundInStore(object subject, object storeLocation, object storeName, object allowInvalid) { throw null; } - internal static string FormatConfigureHttpsFromMethodCall(object methodName) { throw null; } - internal static string FormatConfigurePathBaseFromMethodCall(object methodName) { throw null; } - internal static string FormatEndpointAlreadyInUse(object endpoint) { throw null; } - internal static string FormatEndpointMissingUrl(object endpointName) { throw null; } - internal static string FormatFallbackToIPv4Any(object port) { throw null; } - internal static string FormatHeaderNotAllowedOnResponse(object name, object statusCode) { throw null; } - internal static string FormatHPackErrorDynamicTableSizeUpdateTooLarge(object size, object maxSize) { throw null; } - internal static string FormatHPackErrorIndexOutOfRange(object index) { throw null; } - internal static string FormatHPackStringLengthTooLarge(object length, object maxStringLength) { throw null; } - internal static string FormatHttp2ErrorFrameOverLimit(object size, object limit) { throw null; } - internal static string FormatHttp2ErrorHeadersInterleaved(object frameType, object streamId, object headersStreamId) { throw null; } - internal static string FormatHttp2ErrorMethodInvalid(object method) { throw null; } - internal static string FormatHttp2ErrorMinTlsVersion(object protocol) { throw null; } - internal static string FormatHttp2ErrorPaddingTooLong(object frameType) { throw null; } - internal static string FormatHttp2ErrorSettingsParameterOutOfRange(object parameter) { throw null; } - internal static string FormatHttp2ErrorStreamAborted(object frameType, object streamId) { throw null; } - internal static string FormatHttp2ErrorStreamClosed(object frameType, object streamId) { throw null; } - internal static string FormatHttp2ErrorStreamHalfClosedRemote(object frameType, object streamId) { throw null; } - internal static string FormatHttp2ErrorStreamIdEven(object frameType, object streamId) { throw null; } - internal static string FormatHttp2ErrorStreamIdle(object frameType, object streamId) { throw null; } - internal static string FormatHttp2ErrorStreamIdNotZero(object frameType) { throw null; } - internal static string FormatHttp2ErrorStreamIdZero(object frameType) { throw null; } - internal static string FormatHttp2ErrorStreamSelfDependency(object frameType, object streamId) { throw null; } - internal static string FormatHttp2ErrorUnexpectedFrameLength(object frameType, object expectedLength) { throw null; } - internal static string FormatHttp2StreamErrorPathInvalid(object path) { throw null; } - internal static string FormatHttp2StreamErrorSchemeMismatch(object requestScheme, object transportScheme) { throw null; } - internal static string FormatHttp2StreamResetByApplication(object errorCode) { throw null; } - internal static string FormatInvalidAsciiOrControlChar(object character) { throw null; } - internal static string FormatInvalidContentLength_InvalidNumber(object value) { throw null; } - internal static string FormatInvalidServerCertificateEku(object thumbprint) { throw null; } - internal static string FormatInvalidUrl(object url) { throw null; } - internal static string FormatMaxRequestBufferSmallerThanRequestHeaderBuffer(object requestBufferSize, object requestHeaderSize) { throw null; } - internal static string FormatMaxRequestBufferSmallerThanRequestLineBuffer(object requestBufferSize, object requestLineSize) { throw null; } - internal static string FormatMinimumGracePeriodRequired(object heartbeatInterval) { throw null; } - internal static string FormatMultipleCertificateSources(object endpointName) { throw null; } - internal static string FormatNetworkInterfaceBindingFailed(object address, object interfaceName, object error) { throw null; } - internal static string FormatOverridingWithKestrelOptions(object addresses, object methodName) { throw null; } - internal static string FormatOverridingWithPreferHostingUrls(object settingName, object addresses) { throw null; } - internal static string FormatParameterReadOnlyAfterResponseStarted(object name) { throw null; } - internal static string FormatTooFewBytesWritten(object written, object expected) { throw null; } - internal static string FormatTooManyBytesWritten(object written, object expected) { throw null; } - internal static string FormatUnknownTransportMode(object mode) { throw null; } - internal static string FormatUnsupportedAddressScheme(object address) { throw null; } - internal static string FormatWritingToResponseBodyNotSupported(object statusCode) { throw null; } - } - - public partial class ListenOptions : Microsoft.AspNetCore.Connections.IConnectionBuilder - { - internal readonly System.Collections.Generic.List> _middleware; - internal ListenOptions(System.Net.IPEndPoint endPoint) { } - internal ListenOptions(string socketPath) { } - internal ListenOptions(ulong fileHandle) { } - internal ListenOptions(ulong fileHandle, Microsoft.AspNetCore.Connections.FileHandleType handleType) { } - public Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerOptions KestrelServerOptions { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]internal set { } } - internal System.Net.EndPoint EndPoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - internal bool IsHttp { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - internal bool IsTls { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - internal string Scheme { get { throw null; } } - internal virtual string GetDisplayName() { throw null; } - [System.Diagnostics.DebuggerStepThroughAttribute] - internal virtual System.Threading.Tasks.Task BindAsync(Microsoft.AspNetCore.Server.Kestrel.Core.Internal.AddressBindContext context) { throw null; } - } -} - -namespace Microsoft.AspNetCore.Server.Kestrel.Https.Internal -{ - internal partial class HttpsConnectionMiddleware - { - public HttpsConnectionMiddleware(Microsoft.AspNetCore.Connections.ConnectionDelegate next, Microsoft.AspNetCore.Server.Kestrel.Https.HttpsConnectionAdapterOptions options) { } - public HttpsConnectionMiddleware(Microsoft.AspNetCore.Connections.ConnectionDelegate next, Microsoft.AspNetCore.Server.Kestrel.Https.HttpsConnectionAdapterOptions options, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { } - public System.Threading.Tasks.Task OnConnectionAsync(Microsoft.AspNetCore.Connections.ConnectionContext context) { throw null; } - } -} -namespace Microsoft.AspNetCore.Server.Kestrel.Https -{ - public static partial class CertificateLoader - { - internal static bool DoesCertificateHaveAnAccessiblePrivateKey(System.Security.Cryptography.X509Certificates.X509Certificate2 certificate) { throw null; } - internal static bool IsCertificateAllowedForServerAuth(System.Security.Cryptography.X509Certificates.X509Certificate2 certificate) { throw null; } - } -} -namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal -{ - internal static partial class MemoryPoolExtensions - { - public static int GetMinimumAllocSize(this System.Buffers.MemoryPool pool) { throw null; } - public static int GetMinimumSegmentSize(this System.Buffers.MemoryPool pool) { throw null; } - } - internal partial class DuplexPipeStreamAdapter : Microsoft.AspNetCore.Server.Kestrel.Core.Internal.DuplexPipeStream, System.IO.Pipelines.IDuplexPipe where TStream : System.IO.Stream - { - public DuplexPipeStreamAdapter(System.IO.Pipelines.IDuplexPipe duplexPipe, System.Func createStream) : base (default(System.IO.Pipelines.PipeReader), default(System.IO.Pipelines.PipeWriter), default(bool)) { } - public DuplexPipeStreamAdapter(System.IO.Pipelines.IDuplexPipe duplexPipe, System.IO.Pipelines.StreamPipeReaderOptions readerOptions, System.IO.Pipelines.StreamPipeWriterOptions writerOptions, System.Func createStream) : base (default(System.IO.Pipelines.PipeReader), default(System.IO.Pipelines.PipeWriter), default(bool)) { } - public System.IO.Pipelines.PipeReader Input { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public System.IO.Pipelines.PipeWriter Output { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public TStream Stream { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - protected override void Dispose(bool disposing) { } - public override System.Threading.Tasks.ValueTask DisposeAsync() { throw null; } - } - internal partial class DuplexPipeStream : System.IO.Stream - { - public DuplexPipeStream(System.IO.Pipelines.PipeReader input, System.IO.Pipelines.PipeWriter output, bool throwOnCancelled = false) { } - public override bool CanRead { get { throw null; } } - public override bool CanSeek { get { throw null; } } - public override bool CanWrite { get { throw null; } } - public override long Length { get { throw null; } } - public override long Position { get { throw null; } set { } } - public override System.IAsyncResult BeginRead(byte[] buffer, int offset, int count, System.AsyncCallback callback, object state) { throw null; } - public override System.IAsyncResult BeginWrite(byte[] buffer, int offset, int count, System.AsyncCallback callback, object state) { throw null; } - public void CancelPendingRead() { } - public override int EndRead(System.IAsyncResult asyncResult) { throw null; } - public override void EndWrite(System.IAsyncResult asyncResult) { } - public override void Flush() { } - public override System.Threading.Tasks.Task FlushAsync(System.Threading.CancellationToken cancellationToken) { throw null; } - public override int Read(byte[] buffer, int offset, int count) { throw null; } - public override System.Threading.Tasks.Task ReadAsync(byte[] buffer, int offset, int count, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } - public override System.Threading.Tasks.ValueTask ReadAsync(System.Memory destination, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } - public override long Seek(long offset, System.IO.SeekOrigin origin) { throw null; } - public override void SetLength(long value) { } - public override void Write(byte[] buffer, int offset, int count) { } - public override System.Threading.Tasks.Task WriteAsync(byte[] buffer, int offset, int count, System.Threading.CancellationToken cancellationToken) { throw null; } - public override System.Threading.Tasks.ValueTask WriteAsync(System.ReadOnlyMemory source, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } - } - internal partial class ConnectionLimitMiddleware - { - internal ConnectionLimitMiddleware(Microsoft.AspNetCore.Connections.ConnectionDelegate next, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.ResourceCounter concurrentConnectionCounter, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.IKestrelTrace trace) { } - public ConnectionLimitMiddleware(Microsoft.AspNetCore.Connections.ConnectionDelegate next, long connectionLimit, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.IKestrelTrace trace) { } - public System.Threading.Tasks.Task OnConnectionAsync(Microsoft.AspNetCore.Connections.ConnectionContext connection) { throw null; } - } - internal static partial class HttpConnectionBuilderExtensions - { - public static Microsoft.AspNetCore.Connections.IConnectionBuilder UseHttpServer(this Microsoft.AspNetCore.Connections.IConnectionBuilder builder, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.ServiceContext serviceContext, Microsoft.AspNetCore.Hosting.Server.IHttpApplication application, Microsoft.AspNetCore.Server.Kestrel.Core.HttpProtocols protocols) { throw null; } - } - internal partial class HttpConnection : Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.ITimeoutHandler - { - public HttpConnection(Microsoft.AspNetCore.Server.Kestrel.Core.Internal.HttpConnectionContext context) { } - internal void Initialize(Microsoft.AspNetCore.Server.Kestrel.Core.Internal.IRequestProcessor requestProcessor) { } - public void OnTimeout(Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.TimeoutReason reason) { } - [System.Diagnostics.DebuggerStepThroughAttribute] - public System.Threading.Tasks.Task ProcessRequestsAsync(Microsoft.AspNetCore.Hosting.Server.IHttpApplication httpApplication) { throw null; } - } - internal partial class ConnectionDispatcher - { - public ConnectionDispatcher(Microsoft.AspNetCore.Server.Kestrel.Core.Internal.ServiceContext serviceContext, Microsoft.AspNetCore.Connections.ConnectionDelegate connectionDelegate) { } - public System.Threading.Tasks.Task StartAcceptingConnections(Microsoft.AspNetCore.Connections.IConnectionListener listener) { throw null; } - } - internal partial class ServerAddressesFeature : Microsoft.AspNetCore.Hosting.Server.Features.IServerAddressesFeature - { - public ServerAddressesFeature() { } - public System.Collections.Generic.ICollection Addresses { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public bool PreferHostingUrls { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - } - internal partial class AddressBindContext - { - public AddressBindContext() { } - public System.Collections.Generic.ICollection Addresses { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public System.Func CreateBinding { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public System.Collections.Generic.List ListenOptions { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public Microsoft.Extensions.Logging.ILogger Logger { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerOptions ServerOptions { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - } - internal partial class AddressBinder - { - public AddressBinder() { } - [System.Diagnostics.DebuggerStepThroughAttribute] - public static System.Threading.Tasks.Task BindAsync(Microsoft.AspNetCore.Hosting.Server.Features.IServerAddressesFeature addresses, Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerOptions serverOptions, Microsoft.Extensions.Logging.ILogger logger, System.Func createBinding) { throw null; } - [System.Diagnostics.DebuggerStepThroughAttribute] - internal static System.Threading.Tasks.Task BindEndpointAsync(Microsoft.AspNetCore.Server.Kestrel.Core.ListenOptions endpoint, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.AddressBindContext context) { throw null; } - internal static Microsoft.AspNetCore.Server.Kestrel.Core.ListenOptions ParseAddress(string address, out bool https) { throw null; } - protected internal static bool TryCreateIPEndPoint(Microsoft.AspNetCore.Http.BindingAddress address, out System.Net.IPEndPoint endpoint) { throw null; } - } - internal partial class EndpointConfig - { - public EndpointConfig() { } - public Microsoft.AspNetCore.Server.Kestrel.Core.Internal.CertificateConfig Certificate { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public Microsoft.Extensions.Configuration.IConfigurationSection ConfigSection { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public string Name { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public Microsoft.AspNetCore.Server.Kestrel.Core.HttpProtocols? Protocols { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public string Url { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - } - internal partial class EndpointDefaults - { - public EndpointDefaults() { } - public Microsoft.Extensions.Configuration.IConfigurationSection ConfigSection { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public Microsoft.AspNetCore.Server.Kestrel.Core.HttpProtocols? Protocols { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - } - internal partial class CertificateConfig - { - public CertificateConfig(Microsoft.Extensions.Configuration.IConfigurationSection configSection) { } - public bool? AllowInvalid { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public Microsoft.Extensions.Configuration.IConfigurationSection ConfigSection { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public bool IsFileCert { get { throw null; } } - public bool IsStoreCert { get { throw null; } } - public string Location { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public string Password { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public string Path { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public string Store { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public string Subject { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - } - internal partial class ConfigurationReader - { - public ConfigurationReader(Microsoft.Extensions.Configuration.IConfiguration configuration) { } - public System.Collections.Generic.IDictionary Certificates { get { throw null; } } - public Microsoft.AspNetCore.Server.Kestrel.Core.Internal.EndpointDefaults EndpointDefaults { get { throw null; } } - public System.Collections.Generic.IEnumerable Endpoints { get { throw null; } } - public bool Latin1RequestHeaders { get { throw null; } } - } - internal partial class HttpConnectionContext - { - public HttpConnectionContext() { } - public Microsoft.AspNetCore.Connections.ConnectionContext ConnectionContext { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public Microsoft.AspNetCore.Http.Features.IFeatureCollection ConnectionFeatures { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public string ConnectionId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public System.Net.IPEndPoint LocalEndPoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public System.Buffers.MemoryPool MemoryPool { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public Microsoft.AspNetCore.Server.Kestrel.Core.HttpProtocols Protocols { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public System.Net.IPEndPoint RemoteEndPoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public Microsoft.AspNetCore.Server.Kestrel.Core.Internal.ServiceContext ServiceContext { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.ITimeoutControl TimeoutControl { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public System.IO.Pipelines.IDuplexPipe Transport { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - } - internal partial interface IRequestProcessor - { - void Abort(Microsoft.AspNetCore.Connections.ConnectionAbortedException ex); - void HandleReadDataRateTimeout(); - void HandleRequestHeadersTimeout(); - void OnInputOrOutputCompleted(); - System.Threading.Tasks.Task ProcessRequestsAsync(Microsoft.AspNetCore.Hosting.Server.IHttpApplication application); - void StopProcessingNextRequest(); - void Tick(System.DateTimeOffset now); - } - internal partial class KestrelServerOptionsSetup : Microsoft.Extensions.Options.IConfigureOptions - { - public KestrelServerOptionsSetup(System.IServiceProvider services) { } - public void Configure(Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerOptions options) { } - } - internal partial class ServiceContext - { - public ServiceContext() { } - public Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.ConnectionManager ConnectionManager { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.DateHeaderValueManager DateHeaderValueManager { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.Heartbeat Heartbeat { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.IHttpParser HttpParser { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.IKestrelTrace Log { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public System.IO.Pipelines.PipeScheduler Scheduler { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerOptions ServerOptions { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.ISystemClock SystemClock { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - } -} - -namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http -{ - internal sealed partial class Http1ContentLengthMessageBody : Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.Http1MessageBody - { - public Http1ContentLengthMessageBody(bool keepAlive, long contentLength, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.Http1Connection context) : base (default(Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.Http1Connection)) { } - public override void AdvanceTo(System.SequencePosition consumed) { } - public override void AdvanceTo(System.SequencePosition consumed, System.SequencePosition examined) { } - public override void CancelPendingRead() { } - public override void Complete(System.Exception exception) { } - public override System.Threading.Tasks.Task ConsumeAsync() { throw null; } - protected override void OnReadStarting() { } - protected override System.Threading.Tasks.Task OnStopAsync() { throw null; } - public override System.Threading.Tasks.ValueTask ReadAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } - [System.Diagnostics.DebuggerStepThroughAttribute] - public override System.Threading.Tasks.ValueTask ReadAsyncInternal(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } - public override bool TryRead(out System.IO.Pipelines.ReadResult readResult) { throw null; } - public override bool TryReadInternal(out System.IO.Pipelines.ReadResult readResult) { throw null; } - } - internal static partial class ReasonPhrases - { - public static byte[] ToStatusBytes(int statusCode, string reasonPhrase = null) { throw null; } - } - internal static partial class PathNormalizer - { - public unsafe static bool ContainsDotSegments(byte* start, byte* end) { throw null; } - public static string DecodePath(System.Span path, bool pathEncoded, string rawTarget, int queryLength) { throw null; } - public unsafe static int RemoveDotSegments(byte* start, byte* end) { throw null; } - public static int RemoveDotSegments(System.Span input) { throw null; } - } - internal enum RequestRejectionReason - { - UnrecognizedHTTPVersion = 0, - InvalidRequestLine = 1, - InvalidRequestHeader = 2, - InvalidRequestHeadersNoCRLF = 3, - MalformedRequestInvalidHeaders = 4, - InvalidContentLength = 5, - MultipleContentLengths = 6, - UnexpectedEndOfRequestContent = 7, - BadChunkSuffix = 8, - BadChunkSizeData = 9, - ChunkedRequestIncomplete = 10, - InvalidRequestTarget = 11, - InvalidCharactersInHeaderName = 12, - RequestLineTooLong = 13, - HeadersExceedMaxTotalSize = 14, - TooManyHeaders = 15, - RequestBodyTooLarge = 16, - RequestHeadersTimeout = 17, - RequestBodyTimeout = 18, - FinalTransferCodingNotChunked = 19, - LengthRequired = 20, - LengthRequiredHttp10 = 21, - OptionsMethodRequired = 22, - ConnectMethodRequired = 23, - MissingHostHeader = 24, - MultipleHostHeaders = 25, - InvalidHostHeader = 26, - UpgradeRequestCannotHavePayload = 27, - RequestBodyExceedsContentLength = 28, - } - internal static partial class ChunkWriter - { - public static int BeginChunkBytes(int dataCount, System.Span span) { throw null; } - internal static int GetPrefixBytesForChunk(int length, out bool sliceOneByte) { throw null; } - internal static int WriteBeginChunkBytes(this ref System.Buffers.BufferWriter start, int dataCount) { throw null; } - internal static void WriteEndChunkBytes(this ref System.Buffers.BufferWriter start) { } - } - internal sealed partial class HttpRequestPipeReader : System.IO.Pipelines.PipeReader - { - public HttpRequestPipeReader() { } - public void Abort(System.Exception error = null) { } - public override void AdvanceTo(System.SequencePosition consumed) { } - public override void AdvanceTo(System.SequencePosition consumed, System.SequencePosition examined) { } - public override void CancelPendingRead() { } - public override void Complete(System.Exception exception = null) { } - public override System.Threading.Tasks.ValueTask ReadAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } - public void StartAcceptingReads(Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.MessageBody body) { } - public void StopAcceptingReads() { } - public override bool TryRead(out System.IO.Pipelines.ReadResult result) { throw null; } - } - internal sealed partial class HttpRequestStream : System.IO.Stream - { - public HttpRequestStream(Microsoft.AspNetCore.Http.Features.IHttpBodyControlFeature bodyControl, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpRequestPipeReader pipeReader) { } - public override bool CanRead { get { throw null; } } - public override bool CanSeek { get { throw null; } } - public override bool CanWrite { get { throw null; } } - public override long Length { get { throw null; } } - public override long Position { get { throw null; } set { } } - public override int WriteTimeout { get { throw null; } set { } } - public override System.IAsyncResult BeginRead(byte[] buffer, int offset, int count, System.AsyncCallback callback, object state) { throw null; } - public override System.Threading.Tasks.Task CopyToAsync(System.IO.Stream destination, int bufferSize, System.Threading.CancellationToken cancellationToken) { throw null; } - public override int EndRead(System.IAsyncResult asyncResult) { throw null; } - public override void Flush() { } - public override System.Threading.Tasks.Task FlushAsync(System.Threading.CancellationToken cancellationToken) { throw null; } - public override int Read(byte[] buffer, int offset, int count) { throw null; } - public override System.Threading.Tasks.Task ReadAsync(byte[] buffer, int offset, int count, System.Threading.CancellationToken cancellationToken) { throw null; } - public override System.Threading.Tasks.ValueTask ReadAsync(System.Memory destination, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } - public override long Seek(long offset, System.IO.SeekOrigin origin) { throw null; } - public override void SetLength(long value) { } - public override void Write(byte[] buffer, int offset, int count) { } - public override System.Threading.Tasks.Task WriteAsync(byte[] buffer, int offset, int count, System.Threading.CancellationToken cancellationToken) { throw null; } - } - internal abstract partial class Http1MessageBody : Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.MessageBody - { - protected bool _completed; - protected readonly Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.Http1Connection _context; - protected Http1MessageBody(Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.Http1Connection context) : base (default(Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol)) { } - protected void CheckCompletedReadResult(System.IO.Pipelines.ReadResult result) { } - public static Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.MessageBody For(Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpVersion httpVersion, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpRequestHeaders headers, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.Http1Connection context) { throw null; } - protected override System.Threading.Tasks.Task OnConsumeAsync() { throw null; } - public abstract System.Threading.Tasks.ValueTask ReadAsyncInternal(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); - protected void ThrowIfCompleted() { } - public abstract bool TryReadInternal(out System.IO.Pipelines.ReadResult readResult); - } - internal partial interface IHttpOutputAborter - { - void Abort(Microsoft.AspNetCore.Connections.ConnectionAbortedException abortReason); - } - internal partial class Http1OutputProducer : Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.IHttpOutputAborter, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.IHttpOutputProducer, System.IDisposable - { - public Http1OutputProducer(System.IO.Pipelines.PipeWriter pipeWriter, string connectionId, Microsoft.AspNetCore.Connections.ConnectionContext connectionContext, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.IKestrelTrace log, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.ITimeoutControl timeoutControl, Microsoft.AspNetCore.Server.Kestrel.Core.Features.IHttpMinResponseDataRateFeature minResponseDataRateFeature, System.Buffers.MemoryPool memoryPool) { } - public void Abort(Microsoft.AspNetCore.Connections.ConnectionAbortedException error) { } - public void Advance(int bytes) { } - public void CancelPendingFlush() { } - public void Dispose() { } - public System.Threading.Tasks.ValueTask FirstWriteAsync(int statusCode, string reasonPhrase, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpResponseHeaders responseHeaders, bool autoChunk, System.ReadOnlySpan buffer, System.Threading.CancellationToken cancellationToken) { throw null; } - public System.Threading.Tasks.ValueTask FirstWriteChunkedAsync(int statusCode, string reasonPhrase, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpResponseHeaders responseHeaders, bool autoChunk, System.ReadOnlySpan buffer, System.Threading.CancellationToken cancellationToken) { throw null; } - public System.Threading.Tasks.ValueTask FlushAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } - public System.Memory GetMemory(int sizeHint = 0) { throw null; } - public System.Span GetSpan(int sizeHint = 0) { throw null; } - public void Reset() { } - public void Stop() { } - public System.Threading.Tasks.ValueTask Write100ContinueAsync() { throw null; } - public System.Threading.Tasks.ValueTask WriteChunkAsync(System.ReadOnlySpan buffer, System.Threading.CancellationToken cancellationToken) { throw null; } - public System.Threading.Tasks.Task WriteDataAsync(System.ReadOnlySpan buffer, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } - public System.Threading.Tasks.ValueTask WriteDataToPipeAsync(System.ReadOnlySpan buffer, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } - public void WriteResponseHeaders(int statusCode, string reasonPhrase, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpResponseHeaders responseHeaders, bool autoChunk, bool appComplete) { } - public System.Threading.Tasks.ValueTask WriteStreamSuffixAsync() { throw null; } - } - internal sealed partial class HttpResponseStream : System.IO.Stream - { - public HttpResponseStream(Microsoft.AspNetCore.Http.Features.IHttpBodyControlFeature bodyControl, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpResponsePipeWriter pipeWriter) { } - public override bool CanRead { get { throw null; } } - public override bool CanSeek { get { throw null; } } - public override bool CanWrite { get { throw null; } } - public override long Length { get { throw null; } } - public override long Position { get { throw null; } set { } } - public override int ReadTimeout { get { throw null; } set { } } - public override System.IAsyncResult BeginWrite(byte[] buffer, int offset, int count, System.AsyncCallback callback, object state) { throw null; } - public override void EndWrite(System.IAsyncResult asyncResult) { } - public override void Flush() { } - public override System.Threading.Tasks.Task FlushAsync(System.Threading.CancellationToken cancellationToken) { throw null; } - public override int Read(byte[] buffer, int offset, int count) { throw null; } - public override System.Threading.Tasks.Task ReadAsync(byte[] buffer, int offset, int count, System.Threading.CancellationToken cancellationToken) { throw null; } - public override long Seek(long offset, System.IO.SeekOrigin origin) { throw null; } - public override void SetLength(long value) { } - public override void Write(byte[] buffer, int offset, int count) { } - public override System.Threading.Tasks.Task WriteAsync(byte[] buffer, int offset, int count, System.Threading.CancellationToken cancellationToken) { throw null; } - public override System.Threading.Tasks.ValueTask WriteAsync(System.ReadOnlyMemory source, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } - } - internal sealed partial class HttpResponsePipeWriter : System.IO.Pipelines.PipeWriter - { - public HttpResponsePipeWriter(Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.IHttpResponseControl pipeControl) { } - public void Abort() { } - public override void Advance(int bytes) { } - public override void CancelPendingFlush() { } - public override void Complete(System.Exception exception = null) { } - public override System.Threading.Tasks.ValueTask FlushAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } - public override System.Memory GetMemory(int sizeHint = 0) { throw null; } - public override System.Span GetSpan(int sizeHint = 0) { throw null; } - public void StartAcceptingWrites() { } - public System.Threading.Tasks.Task StopAcceptingWritesAsync() { throw null; } - public override System.Threading.Tasks.ValueTask WriteAsync(System.ReadOnlyMemory source, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } - } - internal partial class DateHeaderValueManager : Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.IHeartbeatHandler - { - public DateHeaderValueManager() { } - public Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.DateHeaderValueManager.DateHeaderValues GetDateHeaderValues() { throw null; } - public void OnHeartbeat(System.DateTimeOffset now) { } - public partial class DateHeaderValues - { - public byte[] Bytes; - public string String; - public DateHeaderValues() { } - } - } - [System.FlagsAttribute] - internal enum ConnectionOptions - { - None = 0, - Close = 1, - KeepAlive = 2, - Upgrade = 4, - } - internal abstract partial class HttpHeaders : Microsoft.AspNetCore.Http.IHeaderDictionary, System.Collections.Generic.ICollection>, System.Collections.Generic.IDictionary, System.Collections.Generic.IEnumerable>, System.Collections.IEnumerable - { - protected System.Collections.Generic.Dictionary MaybeUnknown; - protected long _bits; - protected long? _contentLength; - protected bool _isReadOnly; - protected HttpHeaders() { } - public long? ContentLength { get { throw null; } set { } } - public int Count { get { throw null; } } - Microsoft.Extensions.Primitives.StringValues Microsoft.AspNetCore.Http.IHeaderDictionary.this[string key] { get { throw null; } set { } } - bool System.Collections.Generic.ICollection>.IsReadOnly { get { throw null; } } - Microsoft.Extensions.Primitives.StringValues System.Collections.Generic.IDictionary.this[string key] { get { throw null; } set { } } - System.Collections.Generic.ICollection System.Collections.Generic.IDictionary.Keys { get { throw null; } } - System.Collections.Generic.ICollection System.Collections.Generic.IDictionary.Values { get { throw null; } } - protected System.Collections.Generic.Dictionary Unknown { get { throw null; } } - protected virtual bool AddValueFast(string key, Microsoft.Extensions.Primitives.StringValues value) { throw null; } - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]protected static Microsoft.Extensions.Primitives.StringValues AppendValue(Microsoft.Extensions.Primitives.StringValues existing, string append) { throw null; } - protected virtual void ClearFast() { } - protected virtual bool CopyToFast(System.Collections.Generic.KeyValuePair[] array, int arrayIndex) { throw null; } - protected virtual int GetCountFast() { throw null; } - protected virtual System.Collections.Generic.IEnumerator> GetEnumeratorFast() { throw null; } - public static Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.TransferCoding GetFinalTransferCoding(Microsoft.Extensions.Primitives.StringValues transferEncoding) { throw null; } - public static Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.ConnectionOptions ParseConnection(Microsoft.Extensions.Primitives.StringValues connection) { throw null; } - protected virtual bool RemoveFast(string key) { throw null; } - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]protected bool RemoveUnknown(string key) { throw null; } - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]public void Reset() { } - public void SetReadOnly() { } - protected virtual void SetValueFast(string key, Microsoft.Extensions.Primitives.StringValues value) { } - void System.Collections.Generic.ICollection>.Add(System.Collections.Generic.KeyValuePair item) { } - void System.Collections.Generic.ICollection>.Clear() { } - bool System.Collections.Generic.ICollection>.Contains(System.Collections.Generic.KeyValuePair item) { throw null; } - void System.Collections.Generic.ICollection>.CopyTo(System.Collections.Generic.KeyValuePair[] array, int arrayIndex) { } - bool System.Collections.Generic.ICollection>.Remove(System.Collections.Generic.KeyValuePair item) { throw null; } - void System.Collections.Generic.IDictionary.Add(string key, Microsoft.Extensions.Primitives.StringValues value) { } - bool System.Collections.Generic.IDictionary.ContainsKey(string key) { throw null; } - bool System.Collections.Generic.IDictionary.Remove(string key) { throw null; } - bool System.Collections.Generic.IDictionary.TryGetValue(string key, out Microsoft.Extensions.Primitives.StringValues value) { throw null; } - System.Collections.Generic.IEnumerator> System.Collections.Generic.IEnumerable>.GetEnumerator() { throw null; } - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; } - protected static void ThrowArgumentException() { } - protected static void ThrowDuplicateKeyException() { } - protected static void ThrowHeadersReadOnlyException() { } - protected static void ThrowKeyNotFoundException() { } - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]protected bool TryGetUnknown(string key, ref Microsoft.Extensions.Primitives.StringValues value) { throw null; } - protected virtual bool TryGetValueFast(string key, out Microsoft.Extensions.Primitives.StringValues value) { throw null; } - public static void ValidateHeaderNameCharacters(string headerCharacters) { } - public static void ValidateHeaderValueCharacters(Microsoft.Extensions.Primitives.StringValues headerValues) { } - public static void ValidateHeaderValueCharacters(string headerCharacters) { } - } - internal abstract partial class HttpProtocol : Microsoft.AspNetCore.Http.Features.IEndpointFeature, Microsoft.AspNetCore.Http.Features.IFeatureCollection, Microsoft.AspNetCore.Http.Features.IHttpBodyControlFeature, Microsoft.AspNetCore.Http.Features.IHttpConnectionFeature, Microsoft.AspNetCore.Http.Features.IHttpMaxRequestBodySizeFeature, Microsoft.AspNetCore.Http.Features.IHttpRequestFeature, Microsoft.AspNetCore.Http.Features.IHttpRequestIdentifierFeature, Microsoft.AspNetCore.Http.Features.IHttpRequestLifetimeFeature, Microsoft.AspNetCore.Http.Features.IHttpRequestTrailersFeature, Microsoft.AspNetCore.Http.Features.IHttpResponseBodyFeature, Microsoft.AspNetCore.Http.Features.IHttpResponseFeature, Microsoft.AspNetCore.Http.Features.IHttpUpgradeFeature, Microsoft.AspNetCore.Http.Features.IRequestBodyPipeFeature, Microsoft.AspNetCore.Http.Features.IRouteValuesFeature, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.IHttpResponseControl, System.Collections.Generic.IEnumerable>, System.Collections.IEnumerable - { - protected System.Exception _applicationException; - protected Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.BodyControl _bodyControl; - protected Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpVersion _httpVersion; - protected volatile bool _keepAlive; - protected string _methodText; - protected string _parsedPath; - protected string _parsedQueryString; - protected string _parsedRawTarget; - protected Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.RequestProcessingStatus _requestProcessingStatus; - public HttpProtocol(Microsoft.AspNetCore.Server.Kestrel.Core.Internal.HttpConnectionContext context) { } - public bool AllowSynchronousIO { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public Microsoft.AspNetCore.Http.Features.IFeatureCollection ConnectionFeatures { get { throw null; } } - protected string ConnectionId { get { throw null; } } - public string ConnectionIdFeature { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public bool HasFlushedHeaders { get { throw null; } } - public bool HasResponseCompleted { get { throw null; } } - public bool HasResponseStarted { get { throw null; } } - public bool HasStartedConsumingRequestBody { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - protected Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpRequestHeaders HttpRequestHeaders { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.IHttpResponseControl HttpResponseControl { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - protected Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpResponseHeaders HttpResponseHeaders { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public string HttpVersion { get { throw null; } [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]set { } } - public bool IsUpgradableRequest { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public bool IsUpgraded { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public System.Net.IPAddress LocalIpAddress { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public int LocalPort { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - protected Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.IKestrelTrace Log { get { throw null; } } - public long? MaxRequestBodySize { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpMethod Method { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - Microsoft.AspNetCore.Http.Endpoint Microsoft.AspNetCore.Http.Features.IEndpointFeature.Endpoint { get { throw null; } set { } } - bool Microsoft.AspNetCore.Http.Features.IFeatureCollection.IsReadOnly { get { throw null; } } - object Microsoft.AspNetCore.Http.Features.IFeatureCollection.this[System.Type key] { get { throw null; } set { } } - int Microsoft.AspNetCore.Http.Features.IFeatureCollection.Revision { get { throw null; } } - bool Microsoft.AspNetCore.Http.Features.IHttpBodyControlFeature.AllowSynchronousIO { get { throw null; } set { } } - string Microsoft.AspNetCore.Http.Features.IHttpConnectionFeature.ConnectionId { get { throw null; } set { } } - System.Net.IPAddress Microsoft.AspNetCore.Http.Features.IHttpConnectionFeature.LocalIpAddress { get { throw null; } set { } } - int Microsoft.AspNetCore.Http.Features.IHttpConnectionFeature.LocalPort { get { throw null; } set { } } - System.Net.IPAddress Microsoft.AspNetCore.Http.Features.IHttpConnectionFeature.RemoteIpAddress { get { throw null; } set { } } - int Microsoft.AspNetCore.Http.Features.IHttpConnectionFeature.RemotePort { get { throw null; } set { } } - bool Microsoft.AspNetCore.Http.Features.IHttpMaxRequestBodySizeFeature.IsReadOnly { get { throw null; } } - long? Microsoft.AspNetCore.Http.Features.IHttpMaxRequestBodySizeFeature.MaxRequestBodySize { get { throw null; } set { } } - System.IO.Stream Microsoft.AspNetCore.Http.Features.IHttpRequestFeature.Body { get { throw null; } set { } } - Microsoft.AspNetCore.Http.IHeaderDictionary Microsoft.AspNetCore.Http.Features.IHttpRequestFeature.Headers { get { throw null; } set { } } - string Microsoft.AspNetCore.Http.Features.IHttpRequestFeature.Method { get { throw null; } set { } } - string Microsoft.AspNetCore.Http.Features.IHttpRequestFeature.Path { get { throw null; } set { } } - string Microsoft.AspNetCore.Http.Features.IHttpRequestFeature.PathBase { get { throw null; } set { } } - string Microsoft.AspNetCore.Http.Features.IHttpRequestFeature.Protocol { get { throw null; } set { } } - string Microsoft.AspNetCore.Http.Features.IHttpRequestFeature.QueryString { get { throw null; } set { } } - string Microsoft.AspNetCore.Http.Features.IHttpRequestFeature.RawTarget { get { throw null; } set { } } - string Microsoft.AspNetCore.Http.Features.IHttpRequestFeature.Scheme { get { throw null; } set { } } - string Microsoft.AspNetCore.Http.Features.IHttpRequestIdentifierFeature.TraceIdentifier { get { throw null; } set { } } - System.Threading.CancellationToken Microsoft.AspNetCore.Http.Features.IHttpRequestLifetimeFeature.RequestAborted { get { throw null; } set { } } - bool Microsoft.AspNetCore.Http.Features.IHttpRequestTrailersFeature.Available { get { throw null; } } - Microsoft.AspNetCore.Http.IHeaderDictionary Microsoft.AspNetCore.Http.Features.IHttpRequestTrailersFeature.Trailers { get { throw null; } } - System.IO.Stream Microsoft.AspNetCore.Http.Features.IHttpResponseBodyFeature.Stream { get { throw null; } } - System.IO.Pipelines.PipeWriter Microsoft.AspNetCore.Http.Features.IHttpResponseBodyFeature.Writer { get { throw null; } } - System.IO.Stream Microsoft.AspNetCore.Http.Features.IHttpResponseFeature.Body { get { throw null; } set { } } - bool Microsoft.AspNetCore.Http.Features.IHttpResponseFeature.HasStarted { get { throw null; } } - Microsoft.AspNetCore.Http.IHeaderDictionary Microsoft.AspNetCore.Http.Features.IHttpResponseFeature.Headers { get { throw null; } set { } } - string Microsoft.AspNetCore.Http.Features.IHttpResponseFeature.ReasonPhrase { get { throw null; } set { } } - int Microsoft.AspNetCore.Http.Features.IHttpResponseFeature.StatusCode { get { throw null; } set { } } - bool Microsoft.AspNetCore.Http.Features.IHttpUpgradeFeature.IsUpgradableRequest { get { throw null; } } - System.IO.Pipelines.PipeReader Microsoft.AspNetCore.Http.Features.IRequestBodyPipeFeature.Reader { get { throw null; } } - Microsoft.AspNetCore.Routing.RouteValueDictionary Microsoft.AspNetCore.Http.Features.IRouteValuesFeature.RouteValues { get { throw null; } set { } } - public Microsoft.AspNetCore.Server.Kestrel.Core.MinDataRate MinRequestBodyDataRate { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.IHttpOutputProducer Output { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]protected set { } } - public string Path { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public string PathBase { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public string QueryString { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public string RawTarget { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public string ReasonPhrase { get { throw null; } set { } } - public System.Net.IPAddress RemoteIpAddress { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public int RemotePort { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public System.Threading.CancellationToken RequestAborted { get { throw null; } set { } } - public System.IO.Stream RequestBody { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public System.IO.Pipelines.PipeReader RequestBodyPipeReader { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public Microsoft.AspNetCore.Http.IHeaderDictionary RequestHeaders { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public Microsoft.AspNetCore.Http.IHeaderDictionary RequestTrailers { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public bool RequestTrailersAvailable { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public System.IO.Stream ResponseBody { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public System.IO.Pipelines.PipeWriter ResponseBodyPipeWriter { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public Microsoft.AspNetCore.Http.IHeaderDictionary ResponseHeaders { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpResponseTrailers ResponseTrailers { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public string Scheme { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - protected Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerOptions ServerOptions { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public Microsoft.AspNetCore.Server.Kestrel.Core.Internal.ServiceContext ServiceContext { get { throw null; } } - public int StatusCode { get { throw null; } set { } } - public Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.ITimeoutControl TimeoutControl { get { throw null; } } - public string TraceIdentifier { get { throw null; } set { } } - protected void AbortRequest() { } - public void Advance(int bytes) { } - protected abstract void ApplicationAbort(); - protected virtual bool BeginRead(out System.Threading.Tasks.ValueTask awaitable) { throw null; } - protected virtual void BeginRequestProcessing() { } - public void CancelPendingFlush() { } - public System.Threading.Tasks.Task CompleteAsync(System.Exception exception = null) { throw null; } - protected abstract Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.MessageBody CreateMessageBody(); - protected abstract string CreateRequestId(); - protected System.Threading.Tasks.Task FireOnCompleted() { throw null; } - protected System.Threading.Tasks.Task FireOnStarting() { throw null; } - public System.Threading.Tasks.Task FlushAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } - public System.Threading.Tasks.ValueTask FlushPipeAsync(System.Threading.CancellationToken cancellationToken) { throw null; } - public System.Memory GetMemory(int sizeHint = 0) { throw null; } - public System.Span GetSpan(int sizeHint = 0) { throw null; } - public void HandleNonBodyResponseWrite() { } - public void InitializeBodyControl(Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.MessageBody messageBody) { } - public System.Threading.Tasks.Task InitializeResponseAsync(int firstWriteByteCount) { throw null; } - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)][System.Diagnostics.DebuggerStepThroughAttribute] - public System.Threading.Tasks.Task InitializeResponseAwaited(System.Threading.Tasks.Task startingTask, int firstWriteByteCount) { throw null; } - TFeature Microsoft.AspNetCore.Http.Features.IFeatureCollection.Get() { throw null; } - void Microsoft.AspNetCore.Http.Features.IFeatureCollection.Set(TFeature feature) { } - void Microsoft.AspNetCore.Http.Features.IHttpRequestLifetimeFeature.Abort() { } - System.Threading.Tasks.Task Microsoft.AspNetCore.Http.Features.IHttpResponseBodyFeature.CompleteAsync() { throw null; } - void Microsoft.AspNetCore.Http.Features.IHttpResponseBodyFeature.DisableBuffering() { } - System.Threading.Tasks.Task Microsoft.AspNetCore.Http.Features.IHttpResponseBodyFeature.SendFileAsync(string path, long offset, long? count, System.Threading.CancellationToken cancellation) { throw null; } - System.Threading.Tasks.Task Microsoft.AspNetCore.Http.Features.IHttpResponseBodyFeature.StartAsync(System.Threading.CancellationToken cancellationToken) { throw null; } - void Microsoft.AspNetCore.Http.Features.IHttpResponseFeature.OnCompleted(System.Func callback, object state) { } - void Microsoft.AspNetCore.Http.Features.IHttpResponseFeature.OnStarting(System.Func callback, object state) { } - [System.Diagnostics.DebuggerStepThroughAttribute] - System.Threading.Tasks.Task Microsoft.AspNetCore.Http.Features.IHttpUpgradeFeature.UpgradeAsync() { throw null; } - public void OnCompleted(System.Func callback, object state) { } - protected virtual void OnErrorAfterResponseStarted() { } - public void OnHeader(System.Span name, System.Span value) { } - public void OnHeadersComplete() { } - protected virtual void OnRequestProcessingEnded() { } - protected virtual void OnRequestProcessingEnding() { } - protected abstract void OnReset(); - public void OnStarting(System.Func callback, object state) { } - public void OnTrailer(System.Span name, System.Span value) { } - public void OnTrailersComplete() { } - protected void PoisonRequestBodyStream(System.Exception abortReason) { } - [System.Diagnostics.DebuggerStepThroughAttribute] - public System.Threading.Tasks.Task ProcessRequestsAsync(Microsoft.AspNetCore.Hosting.Server.IHttpApplication application) { throw null; } - public void ProduceContinue() { } - protected System.Threading.Tasks.Task ProduceEnd() { throw null; } - public void ReportApplicationError(System.Exception ex) { } - public void Reset() { } - internal void ResetFeatureCollection() { } - protected void ResetHttp1Features() { } - protected void ResetHttp2Features() { } - internal void ResetState() { } - public void SetBadRequestState(Microsoft.AspNetCore.Server.Kestrel.Core.BadHttpRequestException ex) { } - System.Collections.Generic.IEnumerator> System.Collections.Generic.IEnumerable>.GetEnumerator() { throw null; } - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; } - [System.Diagnostics.StackTraceHiddenAttribute] - public void ThrowRequestTargetRejected(System.Span target) { } - protected abstract bool TryParseRequest(System.IO.Pipelines.ReadResult result, out bool endConnection); - protected System.Threading.Tasks.Task TryProduceInvalidRequestResponse() { throw null; } - protected bool VerifyResponseContentLength(out System.Exception ex) { throw null; } - public System.Threading.Tasks.Task WriteAsync(System.ReadOnlyMemory data, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } - [System.Diagnostics.DebuggerStepThroughAttribute] - public System.Threading.Tasks.ValueTask WriteAsyncAwaited(System.Threading.Tasks.Task initializeTask, System.ReadOnlyMemory data, System.Threading.CancellationToken cancellationToken) { throw null; } - public System.Threading.Tasks.ValueTask WritePipeAsync(System.ReadOnlyMemory data, System.Threading.CancellationToken cancellationToken) { throw null; } - } - internal sealed partial class HttpRequestHeaders : Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpHeaders - { - public HttpRequestHeaders(bool reuseHeaderValues = true, bool useLatin1 = false) { } - public bool HasConnection { get { throw null; } } - public bool HasTransferEncoding { get { throw null; } } - public Microsoft.Extensions.Primitives.StringValues HeaderAccept { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderAcceptCharset { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderAcceptEncoding { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderAcceptLanguage { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderAccessControlRequestHeaders { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderAccessControlRequestMethod { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderAllow { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderAuthorization { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderCacheControl { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderConnection { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderContentEncoding { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderContentLanguage { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderContentLength { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderContentLocation { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderContentMD5 { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderContentRange { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderContentType { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderCookie { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderCorrelationContext { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderDate { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderDNT { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderExpect { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderExpires { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderFrom { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderHost { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderIfMatch { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderIfModifiedSince { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderIfNoneMatch { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderIfRange { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderIfUnmodifiedSince { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderKeepAlive { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderLastModified { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderMaxForwards { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderOrigin { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderPragma { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderProxyAuthorization { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderRange { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderReferer { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderRequestId { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderTE { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderTraceParent { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderTraceState { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderTrailer { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderTransferEncoding { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderTranslate { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderUpgrade { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderUpgradeInsecureRequests { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderUserAgent { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderVia { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderWarning { get { throw null; } set { } } - public int HostCount { get { throw null; } } - protected override bool AddValueFast(string key, Microsoft.Extensions.Primitives.StringValues value) { throw null; } - public void Append(System.Span name, System.Span value) { } - protected override void ClearFast() { } - protected override bool CopyToFast(System.Collections.Generic.KeyValuePair[] array, int arrayIndex) { throw null; } - protected override int GetCountFast() { throw null; } - public Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpRequestHeaders.Enumerator GetEnumerator() { throw null; } - protected override System.Collections.Generic.IEnumerator> GetEnumeratorFast() { throw null; } - public void OnHeadersComplete() { } - protected override bool RemoveFast(string key) { throw null; } - protected override void SetValueFast(string key, Microsoft.Extensions.Primitives.StringValues value) { } - protected override bool TryGetValueFast(string key, out Microsoft.Extensions.Primitives.StringValues value) { throw null; } - [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] - public partial struct Enumerator : System.Collections.Generic.IEnumerator>, System.Collections.IEnumerator, System.IDisposable - { - private readonly Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpRequestHeaders _collection; - private readonly long _bits; - private int _next; - private System.Collections.Generic.KeyValuePair _current; - private readonly bool _hasUnknown; - private System.Collections.Generic.Dictionary.Enumerator _unknownEnumerator; - internal Enumerator(Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpRequestHeaders collection) { throw null; } - public System.Collections.Generic.KeyValuePair Current { get { throw null; } } - object System.Collections.IEnumerator.Current { get { throw null; } } - public void Dispose() { } - public bool MoveNext() { throw null; } - public void Reset() { } - } - } - internal sealed partial class HttpResponseHeaders : Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpHeaders - { - public HttpResponseHeaders() { } - public bool HasConnection { get { throw null; } } - public bool HasDate { get { throw null; } } - public bool HasServer { get { throw null; } } - public bool HasTransferEncoding { get { throw null; } } - public Microsoft.Extensions.Primitives.StringValues HeaderAcceptRanges { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderAccessControlAllowCredentials { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderAccessControlAllowHeaders { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderAccessControlAllowMethods { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderAccessControlAllowOrigin { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderAccessControlExposeHeaders { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderAccessControlMaxAge { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderAge { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderAllow { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderCacheControl { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderConnection { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderContentEncoding { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderContentLanguage { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderContentLength { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderContentLocation { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderContentMD5 { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderContentRange { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderContentType { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderDate { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderETag { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderExpires { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderKeepAlive { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderLastModified { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderLocation { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderPragma { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderProxyAuthenticate { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderRetryAfter { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderServer { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderSetCookie { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderTrailer { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderTransferEncoding { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderUpgrade { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderVary { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderVia { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderWarning { get { throw null; } set { } } - public Microsoft.Extensions.Primitives.StringValues HeaderWWWAuthenticate { get { throw null; } set { } } - protected override bool AddValueFast(string key, Microsoft.Extensions.Primitives.StringValues value) { throw null; } - protected override void ClearFast() { } - internal void CopyTo(ref System.Buffers.BufferWriter buffer) { } - internal void CopyToFast(ref System.Buffers.BufferWriter output) { } - protected override bool CopyToFast(System.Collections.Generic.KeyValuePair[] array, int arrayIndex) { throw null; } - protected override int GetCountFast() { throw null; } - public Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpResponseHeaders.Enumerator GetEnumerator() { throw null; } - protected override System.Collections.Generic.IEnumerator> GetEnumeratorFast() { throw null; } - protected override bool RemoveFast(string key) { throw null; } - public void SetRawConnection(Microsoft.Extensions.Primitives.StringValues value, byte[] raw) { } - public void SetRawDate(Microsoft.Extensions.Primitives.StringValues value, byte[] raw) { } - public void SetRawServer(Microsoft.Extensions.Primitives.StringValues value, byte[] raw) { } - public void SetRawTransferEncoding(Microsoft.Extensions.Primitives.StringValues value, byte[] raw) { } - protected override void SetValueFast(string key, Microsoft.Extensions.Primitives.StringValues value) { } - protected override bool TryGetValueFast(string key, out Microsoft.Extensions.Primitives.StringValues value) { throw null; } - [System.Runtime.CompilerServices.CompilerGeneratedAttribute] - [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] - public partial struct Enumerator : System.Collections.Generic.IEnumerator>, System.Collections.IEnumerator, System.IDisposable - { - private readonly Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpResponseHeaders _collection; - private readonly long _bits; - private int _next; - private System.Collections.Generic.KeyValuePair _current; - private readonly bool _hasUnknown; - private System.Collections.Generic.Dictionary.Enumerator _unknownEnumerator; - internal Enumerator(Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpResponseHeaders collection) { throw null; } - public System.Collections.Generic.KeyValuePair Current { get { throw null; } } - object System.Collections.IEnumerator.Current { get { throw null; } } - public void Dispose() { } - public bool MoveNext() { throw null; } - public void Reset() { } - } - } - internal partial class HttpResponseTrailers : Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpHeaders - { - public HttpResponseTrailers() { } - public Microsoft.Extensions.Primitives.StringValues HeaderETag { get { throw null; } set { } } - protected override bool AddValueFast(string key, Microsoft.Extensions.Primitives.StringValues value) { throw null; } - protected override void ClearFast() { } - protected override bool CopyToFast(System.Collections.Generic.KeyValuePair[] array, int arrayIndex) { throw null; } - protected override int GetCountFast() { throw null; } - public Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpResponseTrailers.Enumerator GetEnumerator() { throw null; } - protected override System.Collections.Generic.IEnumerator> GetEnumeratorFast() { throw null; } - protected override bool RemoveFast(string key) { throw null; } - protected override void SetValueFast(string key, Microsoft.Extensions.Primitives.StringValues value) { } - protected override bool TryGetValueFast(string key, out Microsoft.Extensions.Primitives.StringValues value) { throw null; } - [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] - public partial struct Enumerator : System.Collections.Generic.IEnumerator>, System.Collections.IEnumerator, System.IDisposable - { - private readonly Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpResponseTrailers _collection; - private readonly long _bits; - private int _next; - private System.Collections.Generic.KeyValuePair _current; - private readonly bool _hasUnknown; - private System.Collections.Generic.Dictionary.Enumerator _unknownEnumerator; - internal Enumerator(Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpResponseTrailers collection) { throw null; } - public System.Collections.Generic.KeyValuePair Current { get { throw null; } } - object System.Collections.IEnumerator.Current { get { throw null; } } - public void Dispose() { } - public bool MoveNext() { throw null; } - public void Reset() { } - } - } - internal partial class Http1Connection : Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol, Microsoft.AspNetCore.Server.Kestrel.Core.Features.IHttpMinRequestBodyDataRateFeature, Microsoft.AspNetCore.Server.Kestrel.Core.Features.IHttpMinResponseDataRateFeature, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.IRequestProcessor - { - protected readonly long _keepAliveTicks; - public Http1Connection(Microsoft.AspNetCore.Server.Kestrel.Core.Internal.HttpConnectionContext context) : base (default(Microsoft.AspNetCore.Server.Kestrel.Core.Internal.HttpConnectionContext)) { } - public System.IO.Pipelines.PipeReader Input { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public System.Buffers.MemoryPool MemoryPool { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - Microsoft.AspNetCore.Server.Kestrel.Core.MinDataRate Microsoft.AspNetCore.Server.Kestrel.Core.Features.IHttpMinRequestBodyDataRateFeature.MinDataRate { get { throw null; } set { } } - Microsoft.AspNetCore.Server.Kestrel.Core.MinDataRate Microsoft.AspNetCore.Server.Kestrel.Core.Features.IHttpMinResponseDataRateFeature.MinDataRate { get { throw null; } set { } } - public Microsoft.AspNetCore.Server.Kestrel.Core.MinDataRate MinResponseDataRate { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public bool RequestTimedOut { get { throw null; } } - public void Abort(Microsoft.AspNetCore.Connections.ConnectionAbortedException abortReason) { } - protected override void ApplicationAbort() { } - protected override bool BeginRead(out System.Threading.Tasks.ValueTask awaitable) { throw null; } - protected override void BeginRequestProcessing() { } - protected override Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.MessageBody CreateMessageBody() { throw null; } - protected override string CreateRequestId() { throw null; } - internal void EnsureHostHeaderExists() { } - public void HandleReadDataRateTimeout() { } - public void HandleRequestHeadersTimeout() { } - void Microsoft.AspNetCore.Server.Kestrel.Core.Internal.IRequestProcessor.Tick(System.DateTimeOffset now) { } - public void OnInputOrOutputCompleted() { } - protected override void OnRequestProcessingEnded() { } - protected override void OnRequestProcessingEnding() { } - protected override void OnReset() { } - public void OnStartLine(Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpMethod method, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpVersion version, System.Span target, System.Span path, System.Span query, System.Span customMethod, bool pathEncoded) { } - public void ParseRequest(in System.Buffers.ReadOnlySequence buffer, out System.SequencePosition consumed, out System.SequencePosition examined) { throw null; } - public void SendTimeoutResponse() { } - public void StopProcessingNextRequest() { } - public bool TakeMessageHeaders(in System.Buffers.ReadOnlySequence buffer, bool trailers, out System.SequencePosition consumed, out System.SequencePosition examined) { throw null; } - public bool TakeStartLine(in System.Buffers.ReadOnlySequence buffer, out System.SequencePosition consumed, out System.SequencePosition examined) { throw null; } - protected override bool TryParseRequest(System.IO.Pipelines.ReadResult result, out bool endConnection) { throw null; } - } - [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] - internal readonly partial struct Http1ParsingHandler : Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.IHttpHeadersHandler, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.IHttpRequestLineHandler - { - public readonly Http1Connection Connection; - public readonly bool Trailers; - public Http1ParsingHandler(Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.Http1Connection connection) { throw null; } - public Http1ParsingHandler(Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.Http1Connection connection, bool trailers) { throw null; } - public void OnHeader(System.Span name, System.Span value) { } - public void OnHeadersComplete() { } - public void OnStartLine(Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpMethod method, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpVersion version, System.Span target, System.Span path, System.Span query, System.Span customMethod, bool pathEncoded) { } - } - internal partial interface IHttpOutputProducer - { - void Advance(int bytes); - void CancelPendingFlush(); - System.Threading.Tasks.ValueTask FirstWriteAsync(int statusCode, string reasonPhrase, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpResponseHeaders responseHeaders, bool autoChunk, System.ReadOnlySpan data, System.Threading.CancellationToken cancellationToken); - System.Threading.Tasks.ValueTask FirstWriteChunkedAsync(int statusCode, string reasonPhrase, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpResponseHeaders responseHeaders, bool autoChunk, System.ReadOnlySpan data, System.Threading.CancellationToken cancellationToken); - System.Threading.Tasks.ValueTask FlushAsync(System.Threading.CancellationToken cancellationToken); - System.Memory GetMemory(int sizeHint = 0); - System.Span GetSpan(int sizeHint = 0); - void Reset(); - void Stop(); - System.Threading.Tasks.ValueTask Write100ContinueAsync(); - System.Threading.Tasks.ValueTask WriteChunkAsync(System.ReadOnlySpan data, System.Threading.CancellationToken cancellationToken); - System.Threading.Tasks.Task WriteDataAsync(System.ReadOnlySpan data, System.Threading.CancellationToken cancellationToken); - System.Threading.Tasks.ValueTask WriteDataToPipeAsync(System.ReadOnlySpan data, System.Threading.CancellationToken cancellationToken); - void WriteResponseHeaders(int statusCode, string reasonPhrase, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpResponseHeaders responseHeaders, bool autoChunk, bool appCompleted); - System.Threading.Tasks.ValueTask WriteStreamSuffixAsync(); - } - internal partial interface IHttpResponseControl - { - void Advance(int bytes); - void CancelPendingFlush(); - System.Threading.Tasks.Task CompleteAsync(System.Exception exception = null); - System.Threading.Tasks.ValueTask FlushPipeAsync(System.Threading.CancellationToken cancellationToken); - System.Memory GetMemory(int sizeHint = 0); - System.Span GetSpan(int sizeHint = 0); - void ProduceContinue(); - System.Threading.Tasks.ValueTask WritePipeAsync(System.ReadOnlyMemory source, System.Threading.CancellationToken cancellationToken); - } - internal abstract partial class MessageBody - { - protected long _alreadyTimedBytes; - protected bool _backpressure; - protected long _examinedUnconsumedBytes; - protected bool _timingEnabled; - protected MessageBody(Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol context) { } - public virtual bool IsEmpty { get { throw null; } } - protected Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.IKestrelTrace Log { get { throw null; } } - public bool RequestKeepAlive { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]protected set { } } - public bool RequestUpgrade { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]protected set { } } - public static Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.MessageBody ZeroContentLengthClose { get { throw null; } } - public static Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.MessageBody ZeroContentLengthKeepAlive { get { throw null; } } - protected void AddAndCheckConsumedBytes(long consumedBytes) { } - public abstract void AdvanceTo(System.SequencePosition consumed); - public abstract void AdvanceTo(System.SequencePosition consumed, System.SequencePosition examined); - public abstract void CancelPendingRead(); - public abstract void Complete(System.Exception exception); - public virtual System.Threading.Tasks.Task ConsumeAsync() { throw null; } - protected void CountBytesRead(long bytesInReadResult) { } - protected long OnAdvance(System.IO.Pipelines.ReadResult readResult, System.SequencePosition consumed, System.SequencePosition examined) { throw null; } - protected virtual System.Threading.Tasks.Task OnConsumeAsync() { throw null; } - protected virtual void OnDataRead(long bytesRead) { } - protected virtual void OnReadStarted() { } - protected virtual void OnReadStarting() { } - protected virtual System.Threading.Tasks.Task OnStopAsync() { throw null; } - public abstract System.Threading.Tasks.ValueTask ReadAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); - protected System.Threading.Tasks.ValueTask StartTimingReadAsync(System.Threading.Tasks.ValueTask readAwaitable, System.Threading.CancellationToken cancellationToken) { throw null; } - public virtual System.Threading.Tasks.Task StopAsync() { throw null; } - protected void StopTimingRead(long bytesInReadResult) { } - protected void TryProduceContinue() { } - public abstract bool TryRead(out System.IO.Pipelines.ReadResult readResult); - protected void TryStart() { } - protected void TryStop() { } - } - internal enum RequestProcessingStatus - { - RequestPending = 0, - ParsingRequestLine = 1, - ParsingHeaders = 2, - AppStarted = 3, - HeadersCommitted = 4, - HeadersFlushed = 5, - ResponseCompleted = 6, - } - [System.FlagsAttribute] - internal enum TransferCoding - { - None = 0, - Chunked = 1, - Other = 2, - } -} - -namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 -{ - internal static partial class Http2FrameReader - { - public const int HeaderLength = 9; - public const int SettingSize = 6; - public static int GetPayloadFieldsLength(Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.Http2Frame frame) { throw null; } - public static bool ReadFrame(in System.Buffers.ReadOnlySequence readableBuffer, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.Http2Frame frame, uint maxFrameSize, out System.Buffers.ReadOnlySequence framePayload) { throw null; } - public static System.Collections.Generic.IList ReadSettings(in System.Buffers.ReadOnlySequence payload) { throw null; } - } - internal static partial class Bitshifter - { - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]public static uint ReadUInt24BigEndian(System.ReadOnlySpan source) { throw null; } - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]public static uint ReadUInt31BigEndian(System.ReadOnlySpan source) { throw null; } - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]public static void WriteUInt24BigEndian(System.Span destination, uint value) { } - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]public static void WriteUInt31BigEndian(System.Span destination, uint value) { } - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]public static void WriteUInt31BigEndian(System.Span destination, uint value, bool preserveHighestBit) { } - } - internal partial class Http2Connection : Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.IHttpHeadersHandler, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.IHttp2StreamLifetimeHandler, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.IRequestProcessor - { - public Http2Connection(Microsoft.AspNetCore.Server.Kestrel.Core.Internal.HttpConnectionContext context) { } - public static byte[] ClientPreface { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public Microsoft.AspNetCore.Http.Features.IFeatureCollection ConnectionFeatures { get { throw null; } } - public string ConnectionId { get { throw null; } } - public System.IO.Pipelines.PipeReader Input { get { throw null; } } - public Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerLimits Limits { get { throw null; } } - public Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.IKestrelTrace Log { get { throw null; } } - internal Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.Http2PeerSettings ServerSettings { get { throw null; } } - public Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.ISystemClock SystemClock { get { throw null; } } - public Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.ITimeoutControl TimeoutControl { get { throw null; } } - public void Abort(Microsoft.AspNetCore.Connections.ConnectionAbortedException ex) { } - public void DecrementActiveClientStreamCount() { } - public void HandleReadDataRateTimeout() { } - public void HandleRequestHeadersTimeout() { } - public void IncrementActiveClientStreamCount() { } - [System.Diagnostics.DebuggerStepThroughAttribute] - public System.Threading.Tasks.Task ProcessRequestsAsync(Microsoft.AspNetCore.Hosting.Server.IHttpApplication application) { throw null; } - void Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.IHttp2StreamLifetimeHandler.OnStreamCompleted(Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.Http2Stream stream) { } - void Microsoft.AspNetCore.Server.Kestrel.Core.Internal.IRequestProcessor.Tick(System.DateTimeOffset now) { } - public void OnHeader(System.Span name, System.Span value) { } - public void OnHeadersComplete() { } - public void OnInputOrOutputCompleted() { } - public void StopProcessingNextRequest() { } - public void StopProcessingNextRequest(bool serverInitiated) { } - } - internal partial interface IHttp2StreamLifetimeHandler - { - void DecrementActiveClientStreamCount(); - void OnStreamCompleted(Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.Http2Stream stream); - } - internal partial class Http2FrameWriter - { - public Http2FrameWriter(System.IO.Pipelines.PipeWriter outputPipeWriter, Microsoft.AspNetCore.Connections.ConnectionContext connectionContext, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.Http2Connection http2Connection, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.FlowControl.OutputFlowControl connectionOutputFlowControl, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.ITimeoutControl timeoutControl, Microsoft.AspNetCore.Server.Kestrel.Core.MinDataRate minResponseDataRate, string connectionId, System.Buffers.MemoryPool memoryPool, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.IKestrelTrace log) { } - public void Abort(Microsoft.AspNetCore.Connections.ConnectionAbortedException error) { } - public void AbortPendingStreamDataWrites(Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.FlowControl.StreamOutputFlowControl flowControl) { } - public void Complete() { } - public System.Threading.Tasks.ValueTask FlushAsync(Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.IHttpOutputAborter outputAborter, System.Threading.CancellationToken cancellationToken) { throw null; } - public bool TryUpdateConnectionWindow(int bytes) { throw null; } - public bool TryUpdateStreamWindow(Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.FlowControl.StreamOutputFlowControl flowControl, int bytes) { throw null; } - public void UpdateMaxFrameSize(uint maxFrameSize) { } - public System.Threading.Tasks.ValueTask Write100ContinueAsync(int streamId) { throw null; } - public System.Threading.Tasks.ValueTask WriteDataAsync(int streamId, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.FlowControl.StreamOutputFlowControl flowControl, in System.Buffers.ReadOnlySequence data, bool endStream) { throw null; } - public System.Threading.Tasks.ValueTask WriteGoAwayAsync(int lastStreamId, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.Http2ErrorCode errorCode) { throw null; } - internal static void WriteHeader(Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.Http2Frame frame, System.IO.Pipelines.PipeWriter output) { } - public System.Threading.Tasks.ValueTask WritePingAsync(Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.Http2PingFrameFlags flags, in System.Buffers.ReadOnlySequence payload) { throw null; } - public void WriteResponseHeaders(int streamId, int statusCode, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.Http2HeadersFrameFlags headerFrameFlags, Microsoft.AspNetCore.Http.IHeaderDictionary headers) { } - public System.Threading.Tasks.ValueTask WriteResponseTrailers(int streamId, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpResponseTrailers headers) { throw null; } - public System.Threading.Tasks.ValueTask WriteRstStreamAsync(int streamId, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.Http2ErrorCode errorCode) { throw null; } - internal static void WriteSettings(System.Collections.Generic.IList settings, System.Span destination) { } - public System.Threading.Tasks.ValueTask WriteSettingsAckAsync() { throw null; } - public System.Threading.Tasks.ValueTask WriteSettingsAsync(System.Collections.Generic.IList settings) { throw null; } - public System.Threading.Tasks.ValueTask WriteWindowUpdateAsync(int streamId, int sizeIncrement) { throw null; } - } - internal abstract partial class Http2Stream : Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol, Microsoft.AspNetCore.Http.Features.IHttpResetFeature, Microsoft.AspNetCore.Http.Features.IHttpResponseTrailersFeature, Microsoft.AspNetCore.Server.Kestrel.Core.Features.IHttp2StreamIdFeature, Microsoft.AspNetCore.Server.Kestrel.Core.Features.IHttpMinRequestBodyDataRateFeature, System.Threading.IThreadPoolWorkItem - { - public Http2Stream(Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.Http2StreamContext context) : base (default(Microsoft.AspNetCore.Server.Kestrel.Core.Internal.HttpConnectionContext)) { } - internal long DrainExpirationTicks { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public bool EndStreamReceived { get { throw null; } } - public long? InputRemaining { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]internal set { } } - Microsoft.AspNetCore.Http.IHeaderDictionary Microsoft.AspNetCore.Http.Features.IHttpResponseTrailersFeature.Trailers { get { throw null; } set { } } - int Microsoft.AspNetCore.Server.Kestrel.Core.Features.IHttp2StreamIdFeature.StreamId { get { throw null; } } - Microsoft.AspNetCore.Server.Kestrel.Core.MinDataRate Microsoft.AspNetCore.Server.Kestrel.Core.Features.IHttpMinRequestBodyDataRateFeature.MinDataRate { get { throw null; } set { } } - public bool ReceivedEmptyRequestBody { get { throw null; } } - public System.IO.Pipelines.Pipe RequestBodyPipe { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public bool RequestBodyStarted { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - internal bool RstStreamReceived { get { throw null; } } - public int StreamId { get { throw null; } } - public void Abort(System.IO.IOException abortReason) { } - public void AbortRstStreamReceived() { } - protected override void ApplicationAbort() { } - protected override Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.MessageBody CreateMessageBody() { throw null; } - protected override string CreateRequestId() { throw null; } - public void DecrementActiveClientStreamCount() { } - public abstract void Execute(); - void Microsoft.AspNetCore.Http.Features.IHttpResetFeature.Reset(int errorCode) { } - public System.Threading.Tasks.Task OnDataAsync(Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.Http2Frame dataFrame, in System.Buffers.ReadOnlySequence payload) { throw null; } - public void OnDataRead(int bytesRead) { } - public void OnEndStreamReceived() { } - protected override void OnErrorAfterResponseStarted() { } - protected override void OnRequestProcessingEnded() { } - protected override void OnReset() { } - internal void ResetAndAbort(Microsoft.AspNetCore.Connections.ConnectionAbortedException abortReason, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.Http2ErrorCode error) { } - protected override bool TryParseRequest(System.IO.Pipelines.ReadResult result, out bool endConnection) { throw null; } - public bool TryUpdateOutputWindow(int bytes) { throw null; } - } - internal sealed partial class Http2StreamContext : Microsoft.AspNetCore.Server.Kestrel.Core.Internal.HttpConnectionContext - { - public Http2StreamContext() { } - public Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.Http2PeerSettings ClientPeerSettings { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.FlowControl.InputFlowControl ConnectionInputFlowControl { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.FlowControl.OutputFlowControl ConnectionOutputFlowControl { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.Http2FrameWriter FrameWriter { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.Http2PeerSettings ServerPeerSettings { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public int StreamId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.IHttp2StreamLifetimeHandler StreamLifetimeHandler { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - } - internal sealed partial class Http2ConnectionErrorException : System.Exception - { - public Http2ConnectionErrorException(string message, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.Http2ErrorCode errorCode) { } - public Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.Http2ErrorCode ErrorCode { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - } - [System.FlagsAttribute] - internal enum Http2ContinuationFrameFlags : byte - { - NONE = (byte)0, - END_HEADERS = (byte)4, - } - [System.FlagsAttribute] - internal enum Http2DataFrameFlags : byte - { - NONE = (byte)0, - END_STREAM = (byte)1, - PADDED = (byte)8, - } - internal enum Http2ErrorCode : uint - { - NO_ERROR = (uint)0, - PROTOCOL_ERROR = (uint)1, - INTERNAL_ERROR = (uint)2, - FLOW_CONTROL_ERROR = (uint)3, - SETTINGS_TIMEOUT = (uint)4, - STREAM_CLOSED = (uint)5, - FRAME_SIZE_ERROR = (uint)6, - REFUSED_STREAM = (uint)7, - CANCEL = (uint)8, - COMPRESSION_ERROR = (uint)9, - CONNECT_ERROR = (uint)10, - ENHANCE_YOUR_CALM = (uint)11, - INADEQUATE_SECURITY = (uint)12, - HTTP_1_1_REQUIRED = (uint)13, - } - internal partial class Http2Frame - { - public Http2Frame() { } - public bool ContinuationEndHeaders { get { throw null; } } - public Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.Http2ContinuationFrameFlags ContinuationFlags { get { throw null; } set { } } - public bool DataEndStream { get { throw null; } } - public Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.Http2DataFrameFlags DataFlags { get { throw null; } set { } } - public bool DataHasPadding { get { throw null; } } - public byte DataPadLength { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public int DataPayloadLength { get { throw null; } } - public byte Flags { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.Http2ErrorCode GoAwayErrorCode { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public int GoAwayLastStreamId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public bool HeadersEndHeaders { get { throw null; } } - public bool HeadersEndStream { get { throw null; } } - public Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.Http2HeadersFrameFlags HeadersFlags { get { throw null; } set { } } - public bool HeadersHasPadding { get { throw null; } } - public bool HeadersHasPriority { get { throw null; } } - public byte HeadersPadLength { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public int HeadersPayloadLength { get { throw null; } } - public byte HeadersPriorityWeight { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public int HeadersStreamDependency { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public int PayloadLength { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public bool PingAck { get { throw null; } } - public Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.Http2PingFrameFlags PingFlags { get { throw null; } set { } } - public bool PriorityIsExclusive { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public int PriorityStreamDependency { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public byte PriorityWeight { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.Http2ErrorCode RstStreamErrorCode { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public bool SettingsAck { get { throw null; } } - public Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.Http2SettingsFrameFlags SettingsFlags { get { throw null; } set { } } - public int StreamId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.Http2FrameType Type { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public int WindowUpdateSizeIncrement { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public void PrepareContinuation(Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.Http2ContinuationFrameFlags flags, int streamId) { } - public void PrepareData(int streamId, byte? padLength = default(byte?)) { } - public void PrepareGoAway(int lastStreamId, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.Http2ErrorCode errorCode) { } - public void PrepareHeaders(Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.Http2HeadersFrameFlags flags, int streamId) { } - public void PreparePing(Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.Http2PingFrameFlags flags) { } - public void PreparePriority(int streamId, int streamDependency, bool exclusive, byte weight) { } - public void PrepareRstStream(int streamId, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.Http2ErrorCode errorCode) { } - public void PrepareSettings(Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.Http2SettingsFrameFlags flags) { } - public void PrepareWindowUpdate(int streamId, int sizeIncrement) { } - internal object ShowFlags() { throw null; } - public override string ToString() { throw null; } - } - internal enum Http2FrameType : byte - { - DATA = (byte)0, - HEADERS = (byte)1, - PRIORITY = (byte)2, - RST_STREAM = (byte)3, - SETTINGS = (byte)4, - PUSH_PROMISE = (byte)5, - PING = (byte)6, - GOAWAY = (byte)7, - WINDOW_UPDATE = (byte)8, - CONTINUATION = (byte)9, - } - [System.FlagsAttribute] - internal enum Http2HeadersFrameFlags : byte - { - NONE = (byte)0, - END_STREAM = (byte)1, - END_HEADERS = (byte)4, - PADDED = (byte)8, - PRIORITY = (byte)32, - } - [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] - internal readonly partial struct Http2PeerSetting - { - private readonly int _dummyPrimitive; - public Http2PeerSetting(Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.Http2SettingsParameter parameter, uint value) { throw null; } - public Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.Http2SettingsParameter Parameter { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public uint Value { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - } - internal partial class Http2PeerSettings - { - public const bool DefaultEnablePush = true; - public const uint DefaultHeaderTableSize = (uint)4096; - public const uint DefaultInitialWindowSize = (uint)65535; - public const uint DefaultMaxConcurrentStreams = (uint)4294967295; - public const uint DefaultMaxFrameSize = (uint)16384; - public const uint DefaultMaxHeaderListSize = (uint)4294967295; - internal const int MaxAllowedMaxFrameSize = 16777215; - public const uint MaxWindowSize = (uint)2147483647; - internal const int MinAllowedMaxFrameSize = 16384; - public Http2PeerSettings() { } - public bool EnablePush { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public uint HeaderTableSize { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public uint InitialWindowSize { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public uint MaxConcurrentStreams { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public uint MaxFrameSize { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public uint MaxHeaderListSize { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - internal System.Collections.Generic.IList GetNonProtocolDefaults() { throw null; } - public void Update(System.Collections.Generic.IList settings) { } - } - [System.FlagsAttribute] - internal enum Http2PingFrameFlags : byte - { - NONE = (byte)0, - ACK = (byte)1, - } - [System.FlagsAttribute] - internal enum Http2SettingsFrameFlags : byte - { - NONE = (byte)0, - ACK = (byte)1, - } - internal enum Http2SettingsParameter : ushort - { - SETTINGS_HEADER_TABLE_SIZE = (ushort)1, - SETTINGS_ENABLE_PUSH = (ushort)2, - SETTINGS_MAX_CONCURRENT_STREAMS = (ushort)3, - SETTINGS_INITIAL_WINDOW_SIZE = (ushort)4, - SETTINGS_MAX_FRAME_SIZE = (ushort)5, - SETTINGS_MAX_HEADER_LIST_SIZE = (ushort)6, - } - internal sealed partial class Http2StreamErrorException : System.Exception - { - public Http2StreamErrorException(int streamId, string message, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.Http2ErrorCode errorCode) { } - public Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.Http2ErrorCode ErrorCode { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public int StreamId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - } -} - -namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.FlowControl -{ - internal partial class OutputFlowControlAwaitable : System.Runtime.CompilerServices.ICriticalNotifyCompletion, System.Runtime.CompilerServices.INotifyCompletion - { - public OutputFlowControlAwaitable() { } - public bool IsCompleted { get { throw null; } } - public void Complete() { } - public Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.FlowControl.OutputFlowControlAwaitable GetAwaiter() { throw null; } - public void GetResult() { } - public void OnCompleted(System.Action continuation) { } - public void UnsafeOnCompleted(System.Action continuation) { } - } - internal partial class StreamOutputFlowControl - { - public StreamOutputFlowControl(Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.FlowControl.OutputFlowControl connectionLevelFlowControl, uint initialWindowSize) { } - public int Available { get { throw null; } } - public bool IsAborted { get { throw null; } } - public void Abort() { } - public void Advance(int bytes) { } - public int AdvanceUpToAndWait(long bytes, out Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.FlowControl.OutputFlowControlAwaitable awaitable) { throw null; } - public bool TryUpdateWindow(int bytes) { throw null; } - } - internal partial class OutputFlowControl - { - public OutputFlowControl(uint initialWindowSize) { } - public Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.FlowControl.OutputFlowControlAwaitable AvailabilityAwaitable { get { throw null; } } - public int Available { get { throw null; } } - public bool IsAborted { get { throw null; } } - public void Abort() { } - public void Advance(int bytes) { } - public bool TryUpdateWindow(int bytes) { throw null; } - } - [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] - internal partial struct FlowControl - { - private int _dummyPrimitive; - public FlowControl(uint initialWindowSize) { throw null; } - public int Available { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public bool IsAborted { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public void Abort() { } - public void Advance(int bytes) { } - public bool TryUpdateWindow(int bytes) { throw null; } - } - internal partial class InputFlowControl - { - public InputFlowControl(uint initialWindowSize, uint minWindowSizeIncrement) { } - public bool IsAvailabilityLow { get { throw null; } } - public int Abort() { throw null; } - public void StopWindowUpdates() { } - public bool TryAdvance(int bytes) { throw null; } - public bool TryUpdateWindow(int bytes, out int updateSize) { throw null; } - } -} - -namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack -{ - internal sealed partial class HuffmanDecodingException : System.Exception - { - public HuffmanDecodingException(string message) { } - } - internal static partial class IntegerEncoder - { - public static bool Encode(int i, int n, System.Span buffer, out int length) { throw null; } - } - internal partial class IntegerDecoder - { - public IntegerDecoder() { } - public bool BeginTryDecode(byte b, int prefixLength, out int result) { throw null; } - public static void ThrowIntegerTooBigException() { } - public bool TryDecode(byte b, out int result) { throw null; } - } - internal partial class Huffman - { - public Huffman() { } - public static int Decode(System.ReadOnlySpan src, System.Span dst) { throw null; } - internal static int DecodeValue(uint data, int validBits, out int decodedBits) { throw null; } - public static (uint encoded, int bitLength) Encode(int data) { throw null; } - } - [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] - internal readonly partial struct HeaderField - { - public const int RfcOverhead = 32; - private readonly object _dummy; - public HeaderField(System.Span name, System.Span value) { throw null; } - public int Length { get { throw null; } } - public byte[] Name { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public byte[] Value { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public static int GetLength(int nameLength, int valueLength) { throw null; } - } - internal partial class HPackEncoder - { - public HPackEncoder() { } - public bool BeginEncode(System.Collections.Generic.IEnumerable> headers, System.Span buffer, out int length) { throw null; } - public bool BeginEncode(int statusCode, System.Collections.Generic.IEnumerable> headers, System.Span buffer, out int length) { throw null; } - public bool Encode(System.Span buffer, out int length) { throw null; } - } - internal partial class DynamicTable - { - public DynamicTable(int maxSize) { } - public int Count { get { throw null; } } - public Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack.HeaderField this[int index] { get { throw null; } } - public int MaxSize { get { throw null; } } - public int Size { get { throw null; } } - public void Insert(System.Span name, System.Span value) { } - public void Resize(int maxSize) { } - } - internal partial class HPackDecoder - { - public HPackDecoder(int maxDynamicTableSize, int maxRequestHeaderFieldSize) { } - internal HPackDecoder(int maxDynamicTableSize, int maxRequestHeaderFieldSize, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack.DynamicTable dynamicTable) { } - public void Decode(in System.Buffers.ReadOnlySequence data, bool endHeaders, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.IHttpHeadersHandler handler) { } - } - internal sealed partial class HPackDecodingException : System.Exception - { - public HPackDecodingException(string message) { } - public HPackDecodingException(string message, System.Exception innerException) { } - } - internal sealed partial class HPackEncodingException : System.Exception - { - public HPackEncodingException(string message) { } - public HPackEncodingException(string message, System.Exception innerException) { } - } -} - -namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure -{ - internal static partial class Constants - { - public static readonly string DefaultServerAddress; - public static readonly string DefaultServerHttpsAddress; - public const int MaxExceptionDetailSize = 128; - public const string PipeDescriptorPrefix = "pipefd:"; - public static readonly System.TimeSpan RequestBodyDrainTimeout; - public const string ServerName = "Kestrel"; - public const string SocketDescriptorPrefix = "sockfd:"; - public const string UnixPipeHostPrefix = "unix:/"; - } - internal static partial class HttpUtilities - { - public const string Http10Version = "HTTP/1.0"; - public const string Http11Version = "HTTP/1.1"; - public const string Http2Version = "HTTP/2"; - public const string HttpsUriScheme = "https://"; - public const string HttpUriScheme = "http://"; - public static string GetAsciiStringEscaped(this System.Span span, int maxChars) { throw null; } - public static string GetAsciiStringNonNullCharacters(this System.Span span) { throw null; } - public static string GetHeaderName(this System.Span span) { throw null; } - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]public static bool GetKnownHttpScheme(this System.Span span, out Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpScheme knownScheme) { throw null; } - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]internal unsafe static Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpMethod GetKnownMethod(byte* data, int length, out int methodLength) { throw null; } - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]public static bool GetKnownMethod(this System.Span span, out Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpMethod method, out int length) { throw null; } - public static Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpMethod GetKnownMethod(string value) { throw null; } - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]internal unsafe static Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpVersion GetKnownVersion(byte* location, int length) { throw null; } - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]public static bool GetKnownVersion(this System.Span span, out Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpVersion knownVersion, out byte length) { throw null; } - public static string GetRequestHeaderStringNonNullCharacters(this System.Span span, bool useLatin1) { throw null; } - public static bool IsHostHeaderValid(string hostText) { throw null; } - public static string MethodToString(Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpMethod method) { throw null; } - public static string SchemeToString(Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpScheme scheme) { throw null; } - public static string VersionToString(Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpVersion httpVersion) { throw null; } - } - internal abstract partial class WriteOnlyStream : System.IO.Stream - { - protected WriteOnlyStream() { } - public override bool CanRead { get { throw null; } } - public override bool CanWrite { get { throw null; } } - public override int ReadTimeout { get { throw null; } set { } } - public override int Read(byte[] buffer, int offset, int count) { throw null; } - public override System.Threading.Tasks.Task ReadAsync(byte[] buffer, int offset, int count, System.Threading.CancellationToken cancellationToken) { throw null; } - } - internal sealed partial class ThrowingWasUpgradedWriteOnlyStream : Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.WriteOnlyStream - { - public ThrowingWasUpgradedWriteOnlyStream() { } - public override bool CanSeek { get { throw null; } } - public override long Length { get { throw null; } } - public override long Position { get { throw null; } set { } } - public override void Flush() { } - public override long Seek(long offset, System.IO.SeekOrigin origin) { throw null; } - public override void SetLength(long value) { } - public override void Write(byte[] buffer, int offset, int count) { } - public override System.Threading.Tasks.Task WriteAsync(byte[] buffer, int offset, int count, System.Threading.CancellationToken cancellationToken) { throw null; } - } - internal partial class Disposable : System.IDisposable - { - public Disposable(System.Action dispose) { } - public void Dispose() { } - protected virtual void Dispose(bool disposing) { } - } - internal sealed partial class DebuggerWrapper : Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.IDebugger - { - public bool IsAttached { get { throw null; } } - public static Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.IDebugger Singleton { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - } - internal partial class SystemClock : Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.ISystemClock - { - public SystemClock() { } - public System.DateTimeOffset UtcNow { get { throw null; } } - public long UtcNowTicks { get { throw null; } } - public System.DateTimeOffset UtcNowUnsynchronized { get { throw null; } } - } - internal partial class HeartbeatManager : Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.IHeartbeatHandler, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.ISystemClock - { - public HeartbeatManager(Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.ConnectionManager connectionManager) { } - public System.DateTimeOffset UtcNow { get { throw null; } } - public long UtcNowTicks { get { throw null; } } - public System.DateTimeOffset UtcNowUnsynchronized { get { throw null; } } - public void OnHeartbeat(System.DateTimeOffset now) { } - } - - internal partial class StringUtilities - { - public StringUtilities() { } - public static bool BytesOrdinalEqualsStringAndAscii(string previousValue, System.Span newValue) { throw null; } - public static string ConcatAsHexSuffix(string str, char separator, uint number) { throw null; } - public unsafe static bool TryGetAsciiString(byte* input, char* output, int count) { throw null; } - public unsafe static bool TryGetLatin1String(byte* input, char* output, int count) { throw null; } - } - internal partial class TimeoutControl : Microsoft.AspNetCore.Server.Kestrel.Core.Features.IConnectionTimeoutFeature, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.ITimeoutControl - { - public TimeoutControl(Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.ITimeoutHandler timeoutHandler) { } - internal Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.IDebugger Debugger { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.TimeoutReason TimerReason { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public void BytesRead(long count) { } - public void BytesWrittenToBuffer(Microsoft.AspNetCore.Server.Kestrel.Core.MinDataRate minRate, long count) { } - public void CancelTimeout() { } - internal void Initialize(long nowTicks) { } - public void InitializeHttp2(Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.FlowControl.InputFlowControl connectionInputFlowControl) { } - void Microsoft.AspNetCore.Server.Kestrel.Core.Features.IConnectionTimeoutFeature.ResetTimeout(System.TimeSpan timeSpan) { } - void Microsoft.AspNetCore.Server.Kestrel.Core.Features.IConnectionTimeoutFeature.SetTimeout(System.TimeSpan timeSpan) { } - public void ResetTimeout(long ticks, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.TimeoutReason timeoutReason) { } - public void SetTimeout(long ticks, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.TimeoutReason timeoutReason) { } - public void StartRequestBody(Microsoft.AspNetCore.Server.Kestrel.Core.MinDataRate minRate) { } - public void StartTimingRead() { } - public void StartTimingWrite() { } - public void StopRequestBody() { } - public void StopTimingRead() { } - public void StopTimingWrite() { } - public void Tick(System.DateTimeOffset now) { } - } - internal partial class KestrelTrace : Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.IKestrelTrace, Microsoft.Extensions.Logging.ILogger - { - public KestrelTrace(Microsoft.Extensions.Logging.ILogger logger) { } - public virtual void ApplicationAbortedConnection(string connectionId, string traceIdentifier) { } - public virtual void ApplicationError(string connectionId, string traceIdentifier, System.Exception ex) { } - public virtual void ApplicationNeverCompleted(string connectionId) { } - public virtual System.IDisposable BeginScope(TState state) { throw null; } - public virtual void ConnectionAccepted(string connectionId) { } - public virtual void ConnectionBadRequest(string connectionId, Microsoft.AspNetCore.Server.Kestrel.Core.BadHttpRequestException ex) { } - public virtual void ConnectionDisconnect(string connectionId) { } - public virtual void ConnectionHeadResponseBodyWrite(string connectionId, long count) { } - public virtual void ConnectionKeepAlive(string connectionId) { } - public virtual void ConnectionPause(string connectionId) { } - public virtual void ConnectionRejected(string connectionId) { } - public virtual void ConnectionResume(string connectionId) { } - public virtual void ConnectionStart(string connectionId) { } - public virtual void ConnectionStop(string connectionId) { } - public virtual void HeartbeatSlow(System.TimeSpan interval, System.DateTimeOffset now) { } - public virtual void HPackDecodingError(string connectionId, int streamId, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack.HPackDecodingException ex) { } - public virtual void HPackEncodingError(string connectionId, int streamId, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack.HPackEncodingException ex) { } - public virtual void Http2ConnectionClosed(string connectionId, int highestOpenedStreamId) { } - public virtual void Http2ConnectionClosing(string connectionId) { } - public virtual void Http2ConnectionError(string connectionId, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.Http2ConnectionErrorException ex) { } - public void Http2FrameReceived(string connectionId, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.Http2Frame frame) { } - public void Http2FrameSending(string connectionId, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.Http2Frame frame) { } - public virtual void Http2StreamError(string connectionId, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.Http2StreamErrorException ex) { } - public void Http2StreamResetAbort(string traceIdentifier, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.Http2ErrorCode error, Microsoft.AspNetCore.Connections.ConnectionAbortedException abortReason) { } - public virtual bool IsEnabled(Microsoft.Extensions.Logging.LogLevel logLevel) { throw null; } - public virtual void Log(Microsoft.Extensions.Logging.LogLevel logLevel, Microsoft.Extensions.Logging.EventId eventId, TState state, System.Exception exception, System.Func formatter) { } - public virtual void NotAllConnectionsAborted() { } - public virtual void NotAllConnectionsClosedGracefully() { } - public virtual void RequestBodyDone(string connectionId, string traceIdentifier) { } - public virtual void RequestBodyDrainTimedOut(string connectionId, string traceIdentifier) { } - public virtual void RequestBodyMinimumDataRateNotSatisfied(string connectionId, string traceIdentifier, double rate) { } - public virtual void RequestBodyNotEntirelyRead(string connectionId, string traceIdentifier) { } - public virtual void RequestBodyStart(string connectionId, string traceIdentifier) { } - public virtual void RequestProcessingError(string connectionId, System.Exception ex) { } - public virtual void ResponseMinimumDataRateNotSatisfied(string connectionId, string traceIdentifier) { } - } - internal partial class BodyControl - { - public BodyControl(Microsoft.AspNetCore.Http.Features.IHttpBodyControlFeature bodyControl, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.IHttpResponseControl responseControl) { } - public void Abort(System.Exception error) { } - public (System.IO.Stream request, System.IO.Stream response, System.IO.Pipelines.PipeReader reader, System.IO.Pipelines.PipeWriter writer) Start(Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.MessageBody body) { throw null; } - public System.Threading.Tasks.Task StopAsync() { throw null; } - public System.IO.Stream Upgrade() { throw null; } - } - internal partial class ConnectionManager - { - public ConnectionManager(Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.IKestrelTrace trace, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.ResourceCounter upgradedConnections) { } - public ConnectionManager(Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.IKestrelTrace trace, long? upgradedConnectionLimit) { } - public Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.ResourceCounter UpgradedConnectionCount { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - [System.Diagnostics.DebuggerStepThroughAttribute] - public System.Threading.Tasks.Task AbortAllConnectionsAsync() { throw null; } - public void AddConnection(long id, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.KestrelConnection connection) { } - [System.Diagnostics.DebuggerStepThroughAttribute] - public System.Threading.Tasks.Task CloseAllConnectionsAsync(System.Threading.CancellationToken token) { throw null; } - public void RemoveConnection(long id) { } - public void Walk(System.Action callback) { } - } - internal partial class Heartbeat : System.IDisposable - { - public static readonly System.TimeSpan Interval; - public Heartbeat(Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.IHeartbeatHandler[] callbacks, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.ISystemClock systemClock, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.IDebugger debugger, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.IKestrelTrace trace) { } - public void Dispose() { } - internal void OnHeartbeat() { } - public void Start() { } - } - internal partial interface IDebugger - { - bool IsAttached { get; } - } - internal partial interface IHeartbeatHandler - { - void OnHeartbeat(System.DateTimeOffset now); - } - internal partial interface IKestrelTrace : Microsoft.Extensions.Logging.ILogger - { - void ApplicationAbortedConnection(string connectionId, string traceIdentifier); - void ApplicationError(string connectionId, string traceIdentifier, System.Exception ex); - void ApplicationNeverCompleted(string connectionId); - void ConnectionAccepted(string connectionId); - void ConnectionBadRequest(string connectionId, Microsoft.AspNetCore.Server.Kestrel.Core.BadHttpRequestException ex); - void ConnectionDisconnect(string connectionId); - void ConnectionHeadResponseBodyWrite(string connectionId, long count); - void ConnectionKeepAlive(string connectionId); - void ConnectionPause(string connectionId); - void ConnectionRejected(string connectionId); - void ConnectionResume(string connectionId); - void ConnectionStart(string connectionId); - void ConnectionStop(string connectionId); - void HeartbeatSlow(System.TimeSpan interval, System.DateTimeOffset now); - void HPackDecodingError(string connectionId, int streamId, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack.HPackDecodingException ex); - void HPackEncodingError(string connectionId, int streamId, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack.HPackEncodingException ex); - void Http2ConnectionClosed(string connectionId, int highestOpenedStreamId); - void Http2ConnectionClosing(string connectionId); - void Http2ConnectionError(string connectionId, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.Http2ConnectionErrorException ex); - void Http2FrameReceived(string connectionId, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.Http2Frame frame); - void Http2FrameSending(string connectionId, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.Http2Frame frame); - void Http2StreamError(string connectionId, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.Http2StreamErrorException ex); - void Http2StreamResetAbort(string traceIdentifier, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.Http2ErrorCode error, Microsoft.AspNetCore.Connections.ConnectionAbortedException abortReason); - void NotAllConnectionsAborted(); - void NotAllConnectionsClosedGracefully(); - void RequestBodyDone(string connectionId, string traceIdentifier); - void RequestBodyDrainTimedOut(string connectionId, string traceIdentifier); - void RequestBodyMinimumDataRateNotSatisfied(string connectionId, string traceIdentifier, double rate); - void RequestBodyNotEntirelyRead(string connectionId, string traceIdentifier); - void RequestBodyStart(string connectionId, string traceIdentifier); - void RequestProcessingError(string connectionId, System.Exception ex); - void ResponseMinimumDataRateNotSatisfied(string connectionId, string traceIdentifier); - } - internal partial interface ISystemClock - { - System.DateTimeOffset UtcNow { get; } - long UtcNowTicks { get; } - System.DateTimeOffset UtcNowUnsynchronized { get; } - } - internal partial interface ITimeoutControl - { - Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.TimeoutReason TimerReason { get; } - void BytesRead(long count); - void BytesWrittenToBuffer(Microsoft.AspNetCore.Server.Kestrel.Core.MinDataRate minRate, long count); - void CancelTimeout(); - void InitializeHttp2(Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.FlowControl.InputFlowControl connectionInputFlowControl); - void ResetTimeout(long ticks, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.TimeoutReason timeoutReason); - void SetTimeout(long ticks, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.TimeoutReason timeoutReason); - void StartRequestBody(Microsoft.AspNetCore.Server.Kestrel.Core.MinDataRate minRate); - void StartTimingRead(); - void StartTimingWrite(); - void StopRequestBody(); - void StopTimingRead(); - void StopTimingWrite(); - } - internal partial interface ITimeoutHandler - { - void OnTimeout(Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.TimeoutReason reason); - } - internal partial class KestrelConnection : Microsoft.AspNetCore.Connections.Features.IConnectionCompleteFeature, Microsoft.AspNetCore.Connections.Features.IConnectionHeartbeatFeature, Microsoft.AspNetCore.Connections.Features.IConnectionLifetimeNotificationFeature, System.Threading.IThreadPoolWorkItem - { - public KestrelConnection(long id, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.ServiceContext serviceContext, Microsoft.AspNetCore.Connections.ConnectionDelegate connectionDelegate, Microsoft.AspNetCore.Connections.ConnectionContext connectionContext, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.IKestrelTrace logger) { } - public System.Threading.CancellationToken ConnectionClosedRequested { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public System.Threading.Tasks.Task ExecutionTask { get { throw null; } } - public Microsoft.AspNetCore.Connections.ConnectionContext TransportConnection { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public void Complete() { } - [System.Diagnostics.DebuggerStepThroughAttribute] - internal System.Threading.Tasks.Task ExecuteAsync() { throw null; } - public System.Threading.Tasks.Task FireOnCompletedAsync() { throw null; } - void Microsoft.AspNetCore.Connections.Features.IConnectionCompleteFeature.OnCompleted(System.Func callback, object state) { } - public void OnHeartbeat(System.Action action, object state) { } - public void RequestClose() { } - void System.Threading.IThreadPoolWorkItem.Execute() { } - public void TickHeartbeat() { } - } - internal abstract partial class ResourceCounter - { - protected ResourceCounter() { } - public static Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.ResourceCounter Unlimited { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public static Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.ResourceCounter Quota(long amount) { throw null; } - public abstract void ReleaseOne(); - public abstract bool TryLockOne(); - internal partial class FiniteCounter : Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.ResourceCounter - { - public FiniteCounter(long max) { } - internal long Count { get { throw null; } set { } } - public override void ReleaseOne() { } - public override bool TryLockOne() { throw null; } - } - } - internal enum TimeoutReason - { - None = 0, - KeepAlive = 1, - RequestHeaders = 2, - ReadDataRate = 3, - WriteDataRate = 4, - RequestBodyDrain = 5, - TimeoutFeature = 6, - } -} -namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure -{ - [System.Diagnostics.Tracing.EventSourceAttribute(Name="Microsoft-AspNetCore-Server-Kestrel")] - internal sealed partial class KestrelEventSource : System.Diagnostics.Tracing.EventSource - { - public static readonly Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.KestrelEventSource Log; - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)][System.Diagnostics.Tracing.EventAttribute(5, Level=System.Diagnostics.Tracing.EventLevel.Verbose)] - public void ConnectionRejected(string connectionId) { } - [System.Diagnostics.Tracing.NonEventAttribute] - public void ConnectionStart(Microsoft.AspNetCore.Connections.ConnectionContext connection) { } - [System.Diagnostics.Tracing.NonEventAttribute] - public void ConnectionStop(Microsoft.AspNetCore.Connections.ConnectionContext connection) { } - [System.Diagnostics.Tracing.NonEventAttribute] - public void RequestStart(Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol httpProtocol) { } - [System.Diagnostics.Tracing.NonEventAttribute] - public void RequestStop(Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol httpProtocol) { } - } -} -namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.PipeWriterHelpers -{ - internal sealed partial class ConcurrentPipeWriter : System.IO.Pipelines.PipeWriter - { - public ConcurrentPipeWriter(System.IO.Pipelines.PipeWriter innerPipeWriter, System.Buffers.MemoryPool pool, object sync) { } - public void Abort() { } - public override void Advance(int bytes) { } - public override void CancelPendingFlush() { } - public override void Complete(System.Exception exception = null) { } - public override System.Threading.Tasks.ValueTask FlushAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } - public override System.Memory GetMemory(int sizeHint = 0) { throw null; } - public override System.Span GetSpan(int sizeHint = 0) { throw null; } - } -} -namespace System.Buffers -{ - internal static partial class BufferExtensions - { - public static System.ArraySegment GetArray(this System.Memory buffer) { throw null; } - public static System.ArraySegment GetArray(this System.ReadOnlyMemory memory) { throw null; } - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]public static System.ReadOnlySpan ToSpan(this in System.Buffers.ReadOnlySequence buffer) { throw null; } - internal static void WriteAsciiNoValidation(this ref System.Buffers.BufferWriter buffer, string data) { } - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]internal static void WriteNumeric(this ref System.Buffers.BufferWriter buffer, ulong number) { } - } - [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] - internal ref partial struct BufferWriter where T : System.Buffers.IBufferWriter - { - private T _output; - private System.Span _span; - private int _buffered; - private long _bytesCommitted; - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]public BufferWriter(T output) { throw null; } - public long BytesCommitted { get { throw null; } } - public System.Span Span { get { throw null; } } - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]public void Advance(int count) { } - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]public void Commit() { } - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]public void Ensure(int count = 1) { } - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]public void Write(System.ReadOnlySpan source) { } - } -} - -namespace System.Diagnostics -{ - [System.AttributeUsageAttribute(System.AttributeTargets.Class | System.AttributeTargets.Constructor | System.AttributeTargets.Method | System.AttributeTargets.Struct, Inherited=false)] - internal sealed partial class StackTraceHiddenAttribute : System.Attribute - { - public StackTraceHiddenAttribute() { } - } -} diff --git a/src/Servers/Kestrel/Core/ref/Microsoft.AspNetCore.Server.Kestrel.Core.csproj b/src/Servers/Kestrel/Core/ref/Microsoft.AspNetCore.Server.Kestrel.Core.csproj index ff37850346..9f8403c50e 100644 --- a/src/Servers/Kestrel/Core/ref/Microsoft.AspNetCore.Server.Kestrel.Core.csproj +++ b/src/Servers/Kestrel/Core/ref/Microsoft.AspNetCore.Server.Kestrel.Core.csproj @@ -5,7 +5,6 @@ - diff --git a/src/Servers/Kestrel/Core/ref/Microsoft.AspNetCore.Server.Kestrel.Core.netcoreapp.cs b/src/Servers/Kestrel/Core/ref/Microsoft.AspNetCore.Server.Kestrel.Core.netcoreapp.cs index 1bb12c2437..6673afaa1c 100644 --- a/src/Servers/Kestrel/Core/ref/Microsoft.AspNetCore.Server.Kestrel.Core.netcoreapp.cs +++ b/src/Servers/Kestrel/Core/ref/Microsoft.AspNetCore.Server.Kestrel.Core.netcoreapp.cs @@ -34,16 +34,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel public partial class EndpointConfiguration { internal EndpointConfiguration() { } - public Microsoft.Extensions.Configuration.IConfigurationSection ConfigSection { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public Microsoft.AspNetCore.Server.Kestrel.Https.HttpsConnectionAdapterOptions HttpsOptions { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public bool IsHttps { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public Microsoft.AspNetCore.Server.Kestrel.Core.ListenOptions ListenOptions { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public Microsoft.Extensions.Configuration.IConfigurationSection ConfigSection { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public Microsoft.AspNetCore.Server.Kestrel.Https.HttpsConnectionAdapterOptions HttpsOptions { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public bool IsHttps { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public Microsoft.AspNetCore.Server.Kestrel.Core.ListenOptions ListenOptions { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } } public partial class KestrelConfigurationLoader { internal KestrelConfigurationLoader() { } - public Microsoft.Extensions.Configuration.IConfiguration Configuration { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerOptions Options { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public Microsoft.Extensions.Configuration.IConfiguration Configuration { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerOptions Options { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } public Microsoft.AspNetCore.Server.Kestrel.KestrelConfigurationLoader AnyIPEndpoint(int port) { throw null; } public Microsoft.AspNetCore.Server.Kestrel.KestrelConfigurationLoader AnyIPEndpoint(int port, System.Action configure) { throw null; } public Microsoft.AspNetCore.Server.Kestrel.KestrelConfigurationLoader Endpoint(System.Net.IPAddress address, int port) { throw null; } @@ -65,7 +65,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core public sealed partial class BadHttpRequestException : System.IO.IOException { internal BadHttpRequestException() { } - public int StatusCode { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public int StatusCode { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } } public partial class Http2Limits { @@ -77,6 +77,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core public int MaxRequestHeaderFieldSize { get { throw null; } set { } } public int MaxStreamsPerConnection { get { throw null; } set { } } } + public partial class Http3Limits + { + public Http3Limits() { } + public int HeaderTableSize { get { throw null; } set { } } + public int MaxRequestHeaderFieldSize { get { throw null; } set { } } + } [System.FlagsAttribute] public enum HttpProtocols { @@ -84,11 +90,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core Http1 = 1, Http2 = 2, Http1AndHttp2 = 3, + Http3 = 4, + Http1AndHttp2AndHttp3 = 7, } public partial class KestrelServer : Microsoft.AspNetCore.Hosting.Server.IServer, System.IDisposable { - public KestrelServer(Microsoft.Extensions.Options.IOptions options, Microsoft.AspNetCore.Connections.IConnectionListenerFactory transportFactory, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { } - public Microsoft.AspNetCore.Http.Features.IFeatureCollection Features { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public KestrelServer(Microsoft.Extensions.Options.IOptions options, System.Collections.Generic.IEnumerable transportFactories, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { } + public KestrelServer(Microsoft.Extensions.Options.IOptions options, System.Collections.Generic.IEnumerable transportFactories, System.Collections.Generic.IEnumerable multiplexedFactories, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { } + public Microsoft.AspNetCore.Http.Features.IFeatureCollection Features { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } public Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerOptions Options { get { throw null; } } public void Dispose() { } [System.Diagnostics.DebuggerStepThroughAttribute] @@ -99,7 +108,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core public partial class KestrelServerLimits { public KestrelServerLimits() { } - public Microsoft.AspNetCore.Server.Kestrel.Core.Http2Limits Http2 { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public Microsoft.AspNetCore.Server.Kestrel.Core.Http2Limits Http2 { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public Microsoft.AspNetCore.Server.Kestrel.Core.Http3Limits Http3 { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } public System.TimeSpan KeepAliveTimeout { get { throw null; } set { } } public long? MaxConcurrentConnections { get { throw null; } set { } } public long? MaxConcurrentUpgradedConnections { get { throw null; } set { } } @@ -109,19 +119,20 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core public int MaxRequestHeadersTotalSize { get { throw null; } set { } } public int MaxRequestLineSize { get { throw null; } set { } } public long? MaxResponseBufferSize { get { throw null; } set { } } - public Microsoft.AspNetCore.Server.Kestrel.Core.MinDataRate MinRequestBodyDataRate { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public Microsoft.AspNetCore.Server.Kestrel.Core.MinDataRate MinResponseDataRate { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.AspNetCore.Server.Kestrel.Core.MinDataRate MinRequestBodyDataRate { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public Microsoft.AspNetCore.Server.Kestrel.Core.MinDataRate MinResponseDataRate { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public System.TimeSpan RequestHeadersTimeout { get { throw null; } set { } } } public partial class KestrelServerOptions { public KestrelServerOptions() { } - public bool AddServerHeader { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public bool AllowSynchronousIO { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public System.IServiceProvider ApplicationServices { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public Microsoft.AspNetCore.Server.Kestrel.KestrelConfigurationLoader ConfigurationLoader { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public bool DisableStringReuse { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerLimits Limits { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public bool AddServerHeader { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public bool AllowSynchronousIO { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public System.IServiceProvider ApplicationServices { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public Microsoft.AspNetCore.Server.Kestrel.KestrelConfigurationLoader ConfigurationLoader { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public bool DisableStringReuse { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public bool EnableAltSvc { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerLimits Limits { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } public Microsoft.AspNetCore.Server.Kestrel.KestrelConfigurationLoader Configure() { throw null; } public Microsoft.AspNetCore.Server.Kestrel.KestrelConfigurationLoader Configure(Microsoft.Extensions.Configuration.IConfiguration config) { throw null; } public void ConfigureEndpointDefaults(System.Action configureOptions) { } @@ -139,23 +150,26 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core public void ListenUnixSocket(string socketPath) { } public void ListenUnixSocket(string socketPath, System.Action configure) { } } - public partial class ListenOptions : Microsoft.AspNetCore.Connections.IConnectionBuilder + public partial class ListenOptions : Microsoft.AspNetCore.Connections.IConnectionBuilder, Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder { internal ListenOptions() { } public System.IServiceProvider ApplicationServices { get { throw null; } } public ulong FileHandle { get { throw null; } } public System.Net.IPEndPoint IPEndPoint { get { throw null; } } - public Microsoft.AspNetCore.Server.Kestrel.Core.HttpProtocols Protocols { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerOptions KestrelServerOptions { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public Microsoft.AspNetCore.Server.Kestrel.Core.HttpProtocols Protocols { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public string SocketPath { get { throw null; } } public Microsoft.AspNetCore.Connections.ConnectionDelegate Build() { throw null; } + Microsoft.AspNetCore.Connections.MultiplexedConnectionDelegate Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder.Build() { throw null; } + Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder.Use(System.Func middleware) { throw null; } public override string ToString() { throw null; } public Microsoft.AspNetCore.Connections.IConnectionBuilder Use(System.Func middleware) { throw null; } } public partial class MinDataRate { public MinDataRate(double bytesPerSecond, System.TimeSpan gracePeriod) { } - public double BytesPerSecond { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public System.TimeSpan GracePeriod { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public double BytesPerSecond { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public System.TimeSpan GracePeriod { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } } } namespace Microsoft.AspNetCore.Server.Kestrel.Core.Features @@ -203,11 +217,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http Custom = (byte)9, None = (byte)255, } - public partial class HttpParser : Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.IHttpParser where TRequestHandler : Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.IHttpHeadersHandler, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.IHttpRequestLineHandler + public partial class HttpParser where TRequestHandler : Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.IHttpHeadersHandler, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.IHttpRequestLineHandler { public HttpParser() { } public HttpParser(bool showErrorDetails) { } - bool Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.IHttpParser.ParseRequestLine(TRequestHandler handler, in System.Buffers.ReadOnlySequence buffer, out System.SequencePosition consumed, out System.SequencePosition examined) { throw null; } public bool ParseHeaders(TRequestHandler handler, ref System.Buffers.SequenceReader reader) { throw null; } public bool ParseRequestLine(TRequestHandler handler, in System.Buffers.ReadOnlySequence buffer, out System.SequencePosition consumed, out System.SequencePosition examined) { throw null; } } @@ -223,16 +236,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http Http10 = 0, Http11 = 1, Http2 = 2, + Http3 = 3, } public partial interface IHttpHeadersHandler { - void OnHeader(System.Span name, System.Span value); - void OnHeadersComplete(); - } - public partial interface IHttpParser where TRequestHandler : Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.IHttpHeadersHandler, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.IHttpRequestLineHandler - { - bool ParseHeaders(TRequestHandler handler, ref System.Buffers.SequenceReader reader); - bool ParseRequestLine(TRequestHandler handler, in System.Buffers.ReadOnlySequence buffer, out System.SequencePosition consumed, out System.SequencePosition examined); + void OnHeader(System.ReadOnlySpan name, System.ReadOnlySpan value); + void OnHeadersComplete(bool endStream); + void OnStaticIndexedHeader(int index); + void OnStaticIndexedHeader(int index, System.ReadOnlySpan value); } public partial interface IHttpRequestLineHandler { @@ -254,14 +265,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Https public partial class HttpsConnectionAdapterOptions { public HttpsConnectionAdapterOptions() { } - public bool CheckCertificateRevocation { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public Microsoft.AspNetCore.Server.Kestrel.Https.ClientCertificateMode ClientCertificateMode { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public System.Func ClientCertificateValidation { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public bool CheckCertificateRevocation { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public Microsoft.AspNetCore.Server.Kestrel.Https.ClientCertificateMode ClientCertificateMode { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public System.Func ClientCertificateValidation { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public System.TimeSpan HandshakeTimeout { get { throw null; } set { } } - public System.Action OnAuthenticate { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public System.Security.Cryptography.X509Certificates.X509Certificate2 ServerCertificate { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public System.Func ServerCertificateSelector { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public System.Security.Authentication.SslProtocols SslProtocols { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public System.Action OnAuthenticate { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public System.Security.Cryptography.X509Certificates.X509Certificate2 ServerCertificate { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public System.Func ServerCertificateSelector { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public System.Security.Authentication.SslProtocols SslProtocols { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public void AllowAnyClientCertificate() { } } } diff --git a/src/Servers/Kestrel/Core/src/BadHttpRequestException.cs b/src/Servers/Kestrel/Core/src/BadHttpRequestException.cs index 929a408778..16f7ab0fce 100644 --- a/src/Servers/Kestrel/Core/src/BadHttpRequestException.cs +++ b/src/Servers/Kestrel/Core/src/BadHttpRequestException.cs @@ -139,6 +139,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core BadHttpRequestException ex; switch (reason) { + case RequestRejectionReason.TlsOverHttpError: + ex = new BadHttpRequestException(CoreStrings.HttpParserTlsOverHttpError, StatusCodes.Status400BadRequest, reason); + break; case RequestRejectionReason.InvalidRequestLine: ex = new BadHttpRequestException(CoreStrings.FormatBadRequest_InvalidRequestLine_Detail(detail), StatusCodes.Status400BadRequest, reason); break; diff --git a/src/Servers/Kestrel/Core/src/CoreStrings.resx b/src/Servers/Kestrel/Core/src/CoreStrings.resx index 59bd610f1e..f84ed1d2ce 100644 --- a/src/Servers/Kestrel/Core/src/CoreStrings.resx +++ b/src/Servers/Kestrel/Core/src/CoreStrings.resx @@ -249,9 +249,6 @@ No listening endpoints were configured. Binding to {address} by default. - - HTTPS endpoints can only be configured using {methodName}. - A path base can only be configured using {methodName}. @@ -261,9 +258,6 @@ Failed to bind to address {endpoint}: address already in use. - - Invalid URL: '{url}'. - Unable to bind to {address} on the {interfaceName} interface: '{error}'. @@ -288,9 +282,6 @@ {name} cannot be set because the response has already started. - - Request processing didn't complete within the shutdown timeout. - Response Content-Length mismatch: too few bytes written ({written} of {expected}). @@ -330,9 +321,6 @@ Value must be a positive TimeSpan. - - Value must be a non-negative TimeSpan. - The request body rate enforcement grace period must be greater than {heartbeatInterval} second. @@ -357,30 +345,6 @@ HTTP/2 over TLS was not negotiated on an HTTP/2-only endpoint. - - A dynamic table size of {size} octets is greater than the configured maximum size of {maxSize} octets. - - - Index {index} is outside the bounds of the header field table. - - - Input data could not be fully decoded. - - - Input data contains the EOS symbol. - - - The destination buffer is not large enough to store the decoded data. - - - Huffman decoding error. - - - Decoded string length of {length} octets is greater than the configured maximum length of {maxStringLength} octets. - - - The header block was incomplete and could not be fully decoded. - The client sent a {frameType} frame with even stream ID {streamId}. @@ -459,9 +423,6 @@ Request headers contain connection-specific header field. - - Unable to configure default https bindings because no IDefaultHttpsProvider service was provided. - Failed to authenticate HTTPS connection. @@ -471,9 +432,6 @@ Certificate {thumbprint} cannot be used as an SSL server certificate. It has an Extended Key Usage extension but the usages do not include Server Authentication (OID 1.3.6.1.5.5.7.3.1). - - Value must be a positive TimeSpan. - The server certificate parameter is required. @@ -569,36 +527,36 @@ For more information on configuring HTTPS see https://go.microsoft.com/fwlink/?l A new stream was refused because this connection has reached its stream limit. + + CONNECT requests must not send :scheme or :path headers. + + + The request :scheme header '{requestScheme}' does not match the transport scheme '{transportScheme}'. + + + The Method '{method}' is invalid. + + + The request :path is invalid: '{path}' + + + Less data received than specified in the Content-Length header. + + + More data received than specified in the Content-Length header. + A value greater than zero is required. A value between {min} and {max} is required. - - Dynamic tables size update did not occur at the beginning of the first header block. - - - The given buffer was too small to encode any headers. - - - The decoded integer exceeds the maximum value of Int32.MaxValue. - The client closed the connection. A frame of type {frameType} was received after stream {streamId} was reset or aborted. - - HTTP protocol selection failed. - - - Server shutdown started during connection initialization. - - - Cannot call GetMemory() until response has started. Call HttpResponse.StartAsync() before calling GetMemory(). - This feature is not supported for HTTP/2 requests except to disable it entirely by setting the rate to null. @@ -617,7 +575,28 @@ For more information on configuring HTTPS see https://go.microsoft.com/fwlink/?l A new stream was refused because this connection has too many streams that haven't finished processing. This may happen if many streams are aborted but not yet cleaned up. + + Detected a TLS handshake to an endpoint that does not have TLS enabled. + The ASP.NET Core developer certificate is in an invalid state. To fix this issue, run the following commands 'dotnet dev-certs https --clean' and 'dotnet dev-certs https' to remove all existing ASP.NET Core development certificates and create a new untrusted developer certificate. On macOS or Windows, use 'dotnet dev-certs https --trust' to trust the new certificate. + + Index {index} is outside the bounds of the header field table. + + + The decoded integer exceeds the maximum value of Int32.MaxValue. + + + Huffman decoding error. + + + Decoded string length of {length} octets is greater than the configured maximum length of {maxStringLength} octets. + + + Quic transport not found when using HTTP/3. + + + Unable to resolve service for type 'Microsoft.AspNetCore.Connections.IConnectionListenerFactory' while attempting to activate 'Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServer'. + \ No newline at end of file diff --git a/src/Servers/Kestrel/Core/src/Http3Limits.cs b/src/Servers/Kestrel/Core/src/Http3Limits.cs new file mode 100644 index 0000000000..fa82ede8c3 --- /dev/null +++ b/src/Servers/Kestrel/Core/src/Http3Limits.cs @@ -0,0 +1,53 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.AspNetCore.Server.Kestrel.Core +{ + public class Http3Limits + { + private int _headerTableSize = 4096; + private int _maxRequestHeaderFieldSize = 8192; + + /// + /// Limits the size of the header compression table, in octets, the HPACK decoder on the server can use. + /// + /// Value must be greater than 0, defaults to 4096 + /// + /// + public int HeaderTableSize + { + get => _headerTableSize; + set + { + if (value <= 0) + { + throw new ArgumentOutOfRangeException(nameof(value), value, CoreStrings.GreaterThanZeroRequired); + } + + _headerTableSize = value; + } + } + + /// + /// Indicates the size of the maximum allowed size of a request header field sequence. This limit applies to both name and value sequences in their compressed and uncompressed representations. + /// + /// Value must be greater than 0, defaults to 8192 + /// + /// + public int MaxRequestHeaderFieldSize + { + get => _maxRequestHeaderFieldSize; + set + { + if (value <= 0) + { + throw new ArgumentOutOfRangeException(nameof(value), value, CoreStrings.GreaterThanZeroRequired); + } + + _maxRequestHeaderFieldSize = value; + } + } + } +} diff --git a/src/Servers/Kestrel/Core/src/HttpProtocols.cs b/src/Servers/Kestrel/Core/src/HttpProtocols.cs index 09524bf156..294ae6ae69 100644 --- a/src/Servers/Kestrel/Core/src/HttpProtocols.cs +++ b/src/Servers/Kestrel/Core/src/HttpProtocols.cs @@ -12,5 +12,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core Http1 = 0x1, Http2 = 0x2, Http1AndHttp2 = Http1 | Http2, + Http3 = 0x4, + Http1AndHttp2AndHttp3 = Http1 | Http2 | Http3 } } diff --git a/src/Servers/Kestrel/Core/src/Internal/ConnectionDispatcher.cs b/src/Servers/Kestrel/Core/src/Internal/ConnectionDispatcher.cs index df08a8d320..a373c240bf 100644 --- a/src/Servers/Kestrel/Core/src/Internal/ConnectionDispatcher.cs +++ b/src/Servers/Kestrel/Core/src/Internal/ConnectionDispatcher.cs @@ -53,7 +53,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal // Add the connection to the connection manager before we queue it for execution var id = Interlocked.Increment(ref _lastConnectionId); - var kestrelConnection = new KestrelConnection(id, _serviceContext, _connectionDelegate, connection, Log); + var kestrelConnection = new KestrelConnection(id, _serviceContext, c => _connectionDelegate(c), connection, Log); _serviceContext.ConnectionManager.AddConnection(id, kestrelConnection); diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/BufferExtensions.cs b/src/Servers/Kestrel/Core/src/Internal/Http/BufferExtensions.cs deleted file mode 100644 index f94d422062..0000000000 --- a/src/Servers/Kestrel/Core/src/Internal/Http/BufferExtensions.cs +++ /dev/null @@ -1,272 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Buffers; -using System.IO.Pipelines; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace System.Buffers -{ - internal static class BufferExtensions - { - private const int _maxULongByteLength = 20; - - [ThreadStatic] - private static byte[] _numericBytesScratch; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ReadOnlySpan ToSpan(in this ReadOnlySequence buffer) - { - if (buffer.IsSingleSegment) - { - return buffer.First.Span; - } - return buffer.ToArray(); - } - - public static ArraySegment GetArray(this Memory buffer) - { - return ((ReadOnlyMemory)buffer).GetArray(); - } - - public static ArraySegment GetArray(this ReadOnlyMemory memory) - { - if (!MemoryMarshal.TryGetArray(memory, out var result)) - { - throw new InvalidOperationException("Buffer backed by array was expected"); - } - return result; - } - - internal static unsafe void WriteAsciiNoValidation(ref this BufferWriter buffer, string data) - { - if (string.IsNullOrEmpty(data)) - { - return; - } - - var dest = buffer.Span; - var destLength = dest.Length; - var sourceLength = data.Length; - - // Fast path, try copying to the available memory directly - if (sourceLength <= destLength) - { - fixed (char* input = data) - fixed (byte* output = dest) - { - EncodeAsciiCharsToBytes(input, output, sourceLength); - } - - buffer.Advance(sourceLength); - } - else - { - WriteAsciiMultiWrite(ref buffer, data); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static unsafe void WriteNumeric(ref this BufferWriter buffer, ulong number) - { - const byte AsciiDigitStart = (byte)'0'; - - var span = buffer.Span; - var bytesLeftInBlock = span.Length; - - // Fast path, try copying to the available memory directly - var simpleWrite = true; - fixed (byte* output = span) - { - var start = output; - if (number < 10 && bytesLeftInBlock >= 1) - { - *(start) = (byte)(((uint)number) + AsciiDigitStart); - buffer.Advance(1); - } - else if (number < 100 && bytesLeftInBlock >= 2) - { - var val = (uint)number; - var tens = (byte)((val * 205u) >> 11); // div10, valid to 1028 - - *(start) = (byte)(tens + AsciiDigitStart); - *(start + 1) = (byte)(val - (tens * 10) + AsciiDigitStart); - buffer.Advance(2); - } - else if (number < 1000 && bytesLeftInBlock >= 3) - { - var val = (uint)number; - var digit0 = (byte)((val * 41u) >> 12); // div100, valid to 1098 - var digits01 = (byte)((val * 205u) >> 11); // div10, valid to 1028 - - *(start) = (byte)(digit0 + AsciiDigitStart); - *(start + 1) = (byte)(digits01 - (digit0 * 10) + AsciiDigitStart); - *(start + 2) = (byte)(val - (digits01 * 10) + AsciiDigitStart); - buffer.Advance(3); - } - else - { - simpleWrite = false; - } - } - - if (!simpleWrite) - { - WriteNumericMultiWrite(ref buffer, number); - } - } - - [MethodImpl(MethodImplOptions.NoInlining)] - private static void WriteNumericMultiWrite(ref this BufferWriter buffer, ulong number) - { - const byte AsciiDigitStart = (byte)'0'; - - var value = number; - var position = _maxULongByteLength; - var byteBuffer = NumericBytesScratch; - do - { - // Consider using Math.DivRem() if available - var quotient = value / 10; - byteBuffer[--position] = (byte)(AsciiDigitStart + (value - quotient * 10)); // 0x30 = '0' - value = quotient; - } - while (value != 0); - - var length = _maxULongByteLength - position; - buffer.Write(new ReadOnlySpan(byteBuffer, position, length)); - } - - [MethodImpl(MethodImplOptions.NoInlining)] - private unsafe static void WriteAsciiMultiWrite(ref this BufferWriter buffer, string data) - { - var remaining = data.Length; - - fixed (char* input = data) - { - var inputSlice = input; - - while (remaining > 0) - { - var writable = Math.Min(remaining, buffer.Span.Length); - - if (writable == 0) - { - buffer.Ensure(); - continue; - } - - fixed (byte* output = buffer.Span) - { - EncodeAsciiCharsToBytes(inputSlice, output, writable); - } - - inputSlice += writable; - remaining -= writable; - - buffer.Advance(writable); - } - } - } - - private static unsafe void EncodeAsciiCharsToBytes(char* input, byte* output, int length) - { - // Note: Not BIGENDIAN or check for non-ascii - const int Shift16Shift24 = (1 << 16) | (1 << 24); - const int Shift8Identity = (1 << 8) | (1); - - // Encode as bytes up to the first non-ASCII byte and return count encoded - int i = 0; - // Use Intrinsic switch - if (IntPtr.Size == 8) // 64 bit - { - if (length < 4) goto trailing; - - int unaligned = (int)(((ulong)input) & 0x7) >> 1; - // Unaligned chars - for (; i < unaligned; i++) - { - char ch = *(input + i); - *(output + i) = (byte)ch; // Cast convert - } - - // Aligned - int ulongDoubleCount = (length - i) & ~0x7; - for (; i < ulongDoubleCount; i += 8) - { - ulong inputUlong0 = *(ulong*)(input + i); - ulong inputUlong1 = *(ulong*)(input + i + 4); - // Pack 16 ASCII chars into 16 bytes - *(uint*)(output + i) = - ((uint)((inputUlong0 * Shift16Shift24) >> 24) & 0xffff) | - ((uint)((inputUlong0 * Shift8Identity) >> 24) & 0xffff0000); - *(uint*)(output + i + 4) = - ((uint)((inputUlong1 * Shift16Shift24) >> 24) & 0xffff) | - ((uint)((inputUlong1 * Shift8Identity) >> 24) & 0xffff0000); - } - if (length - 4 > i) - { - ulong inputUlong = *(ulong*)(input + i); - // Pack 8 ASCII chars into 8 bytes - *(uint*)(output + i) = - ((uint)((inputUlong * Shift16Shift24) >> 24) & 0xffff) | - ((uint)((inputUlong * Shift8Identity) >> 24) & 0xffff0000); - i += 4; - } - - trailing: - for (; i < length; i++) - { - char ch = *(input + i); - *(output + i) = (byte)ch; // Cast convert - } - } - else // 32 bit - { - // Unaligned chars - if ((unchecked((int)input) & 0x2) != 0) - { - char ch = *input; - i = 1; - *(output) = (byte)ch; // Cast convert - } - - // Aligned - int uintCount = (length - i) & ~0x3; - for (; i < uintCount; i += 4) - { - uint inputUint0 = *(uint*)(input + i); - uint inputUint1 = *(uint*)(input + i + 2); - // Pack 4 ASCII chars into 4 bytes - *(ushort*)(output + i) = (ushort)(inputUint0 | (inputUint0 >> 8)); - *(ushort*)(output + i + 2) = (ushort)(inputUint1 | (inputUint1 >> 8)); - } - if (length - 1 > i) - { - uint inputUint = *(uint*)(input + i); - // Pack 2 ASCII chars into 2 bytes - *(ushort*)(output + i) = (ushort)(inputUint | (inputUint >> 8)); - i += 2; - } - - if (i < length) - { - char ch = *(input + i); - *(output + i) = (byte)ch; // Cast convert - } - } - } - - private static byte[] NumericBytesScratch => _numericBytesScratch ?? CreateNumericBytesScratch(); - - [MethodImpl(MethodImplOptions.NoInlining)] - private static byte[] CreateNumericBytesScratch() - { - var bytes = new byte[_maxULongByteLength]; - _numericBytesScratch = bytes; - return bytes; - } - } -} diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/ChunkWriter.cs b/src/Servers/Kestrel/Core/src/Internal/Http/ChunkWriter.cs index 75186e8ad6..6decc434a2 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/ChunkWriter.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/ChunkWriter.cs @@ -4,15 +4,11 @@ using System; using System.Buffers; using System.IO.Pipelines; -using System.Text; -using System.Runtime.CompilerServices; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { internal static class ChunkWriter { - private static readonly byte[] _hex = Encoding.ASCII.GetBytes("0123456789abcdef"); - public static int BeginChunkBytes(int dataCount, Span span) { // Determine the most-significant non-zero nibble @@ -27,14 +23,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http count = (total >> 2) + 3; - var offset = 0; - ref var startHex = ref _hex[0]; + // This must be explicity typed as ReadOnlySpan + // It then becomes a non-allocating mapping to the data section of the assembly. + // For more information see https://vcsjones.dev/2019/02/01/csharp-readonly-span-bytes-static + ReadOnlySpan hex = new byte[16] { (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', (byte)'6', (byte)'7', (byte)'8', (byte)'9', (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f' }; + var offset = 0; for (shift = total; shift >= 0; shift -= 4) { - // Using Unsafe.Add to elide the bounds check on _hex as the & 0x0f definately - // constrains it to the range 0x0 - 0xf, matching the bounds of the array - span[offset] = Unsafe.Add(ref startHex, ((dataCount >> shift) & 0x0f)); + // Uses dotnet/runtime#1644 to elide the bounds check on hex as the & 0x0f definitely + // constrains it to the range 0x0 - 0xf, matching the bounds of the array. + span[offset] = hex[(dataCount >> shift) & 0x0f]; offset++; } @@ -54,7 +53,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http // bytes for the chunked prefix, so we would have to copy once we call advance. Therefore, to avoid this scenario, // we slice the memory by one byte. - // See https://gist.github.com/halter73/af2b9f78978f83813b19e187c4e5309e if you would like to tweek the algorithm at all. + // See https://gist.github.com/halter73/af2b9f78978f83813b19e187c4e5309e if you would like to tweak the algorithm at all. if (length <= 65544) { diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/DateHeaderValueManager.cs b/src/Servers/Kestrel/Core/src/Internal/Http/DateHeaderValueManager.cs index 371033b330..85b9ad8b61 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/DateHeaderValueManager.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/DateHeaderValueManager.cs @@ -14,7 +14,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http /// internal class DateHeaderValueManager : IHeartbeatHandler { - private static readonly byte[] _datePreambleBytes = Encoding.ASCII.GetBytes("\r\nDate: "); + // This uses C# compiler's ability to refer to static data directly. For more information see https://vcsjones.dev/2019/02/01/csharp-readonly-span-bytes-static + private static ReadOnlySpan DatePreambleBytes => new byte[8] { (byte)'\r', (byte)'\n', (byte)'D', (byte)'a', (byte)'t', (byte)'e', (byte)':', (byte)' ' }; private DateHeaderValues _dateValues; @@ -38,9 +39,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http private void SetDateValues(DateTimeOffset value) { var dateValue = HeaderUtilities.FormatDate(value); - var dateBytes = new byte[_datePreambleBytes.Length + dateValue.Length]; - Buffer.BlockCopy(_datePreambleBytes, 0, dateBytes, 0, _datePreambleBytes.Length); - Encoding.ASCII.GetBytes(dateValue, 0, dateValue.Length, dateBytes, _datePreambleBytes.Length); + var dateBytes = new byte[DatePreambleBytes.Length + dateValue.Length]; + DatePreambleBytes.CopyTo(dateBytes); + Encoding.ASCII.GetBytes(dateValue, dateBytes.AsSpan(DatePreambleBytes.Length)); var dateValues = new DateHeaderValues { diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/Http1ChunkedEncodingMessageBody.cs b/src/Servers/Kestrel/Core/src/Internal/Http/Http1ChunkedEncodingMessageBody.cs index 374ab1144f..9c87b3697e 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/Http1ChunkedEncodingMessageBody.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/Http1ChunkedEncodingMessageBody.cs @@ -222,7 +222,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { if (readableBuffer.IsSingleSegment) { - writableBuffer.Write(readableBuffer.First.Span); + writableBuffer.Write(readableBuffer.FirstSpan); } else { diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs index d703bc71fe..832d48cdce 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs @@ -44,8 +44,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http private int _remainingRequestHeadersBytesAllowed; public Http1Connection(HttpConnectionContext context) - : base(context) { + Initialize(context); + _context = context; _parser = ServiceContext.HttpParser; _keepAliveTicks = ServerOptions.Limits.KeepAliveTimeout.Ticks; diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/Http1MessageBody.cs b/src/Servers/Kestrel/Core/src/Internal/Http/Http1MessageBody.cs index 4b5d5b0872..c5993cd7cc 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/Http1MessageBody.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/Http1MessageBody.cs @@ -16,8 +16,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http protected readonly Http1Connection _context; protected bool _completed; - protected Http1MessageBody(Http1Connection context) - : base(context) + protected Http1MessageBody(Http1Connection context) : base(context) { _context = context; } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/Http1ParsingHandler.cs b/src/Servers/Kestrel/Core/src/Internal/Http/Http1ParsingHandler.cs index b4c67c13a3..322e46190d 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/Http1ParsingHandler.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/Http1ParsingHandler.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Net.Http; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { @@ -22,7 +23,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http Trailers = trailers; } - public void OnHeader(Span name, Span value) + public void OnHeader(ReadOnlySpan name, ReadOnlySpan value) { if (Trailers) { @@ -34,7 +35,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } } - public void OnHeadersComplete() + public void OnHeadersComplete(bool endStream) { if (Trailers) { @@ -48,5 +49,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http public void OnStartLine(HttpMethod method, HttpVersion version, Span target, Span path, Span query, Span customMethod, bool pathEncoded) => Connection.OnStartLine(method, version, target, path, query, customMethod, pathEncoded); + + public void OnStaticIndexedHeader(int index) + { + throw new NotImplementedException(); + } + + public void OnStaticIndexedHeader(int index, ReadOnlySpan value) + { + throw new NotImplementedException(); + } } } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/HttpHeaders.Generated.cs b/src/Servers/Kestrel/Core/src/Internal/Http/HttpHeaders.Generated.cs index aa22d87a86..516d7c32ec 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/HttpHeaders.Generated.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/HttpHeaders.Generated.cs @@ -14,6 +14,81 @@ using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { + internal enum KnownHeaderType + { + Unknown, + Accept, + AcceptCharset, + AcceptEncoding, + AcceptLanguage, + AcceptRanges, + AccessControlAllowCredentials, + AccessControlAllowHeaders, + AccessControlAllowMethods, + AccessControlAllowOrigin, + AccessControlExposeHeaders, + AccessControlMaxAge, + AccessControlRequestHeaders, + AccessControlRequestMethod, + Age, + Allow, + AltSvc, + Authority, + Authorization, + CacheControl, + Connection, + ContentEncoding, + ContentLanguage, + ContentLength, + ContentLocation, + ContentMD5, + ContentRange, + ContentType, + Cookie, + CorrelationContext, + Date, + DNT, + ETag, + Expect, + Expires, + From, + Host, + IfMatch, + IfModifiedSince, + IfNoneMatch, + IfRange, + IfUnmodifiedSince, + KeepAlive, + LastModified, + Location, + MaxForwards, + Method, + Origin, + Path, + Pragma, + ProxyAuthenticate, + ProxyAuthorization, + Range, + Referer, + RequestId, + RetryAfter, + Scheme, + Server, + SetCookie, + TE, + TraceParent, + TraceState, + Trailer, + TransferEncoding, + Translate, + Upgrade, + UpgradeInsecureRequests, + UserAgent, + Vary, + Via, + Warning, + WWWAuthenticate, + } internal partial class HttpRequestHeaders { @@ -347,12 +422,80 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http _headers._LastModified = value; } } - public StringValues HeaderAccept + public StringValues HeaderAuthority { get { StringValues value = default; if ((_bits & 0x80000L) != 0) + { + value = _headers._Authority; + } + return value; + } + set + { + _bits |= 0x80000L; + _headers._Authority = value; + } + } + public StringValues HeaderMethod + { + get + { + StringValues value = default; + if ((_bits & 0x100000L) != 0) + { + value = _headers._Method; + } + return value; + } + set + { + _bits |= 0x100000L; + _headers._Method = value; + } + } + public StringValues HeaderPath + { + get + { + StringValues value = default; + if ((_bits & 0x200000L) != 0) + { + value = _headers._Path; + } + return value; + } + set + { + _bits |= 0x200000L; + _headers._Path = value; + } + } + public StringValues HeaderScheme + { + get + { + StringValues value = default; + if ((_bits & 0x400000L) != 0) + { + value = _headers._Scheme; + } + return value; + } + set + { + _bits |= 0x400000L; + _headers._Scheme = value; + } + } + public StringValues HeaderAccept + { + get + { + StringValues value = default; + if ((_bits & 0x800000L) != 0) { value = _headers._Accept; } @@ -360,7 +503,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } set { - _bits |= 0x80000L; + _bits |= 0x800000L; _headers._Accept = value; } } @@ -369,7 +512,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http get { StringValues value = default; - if ((_bits & 0x100000L) != 0) + if ((_bits & 0x1000000L) != 0) { value = _headers._AcceptCharset; } @@ -377,7 +520,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } set { - _bits |= 0x100000L; + _bits |= 0x1000000L; _headers._AcceptCharset = value; } } @@ -386,7 +529,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http get { StringValues value = default; - if ((_bits & 0x200000L) != 0) + if ((_bits & 0x2000000L) != 0) { value = _headers._AcceptEncoding; } @@ -394,7 +537,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } set { - _bits |= 0x200000L; + _bits |= 0x2000000L; _headers._AcceptEncoding = value; } } @@ -403,7 +546,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http get { StringValues value = default; - if ((_bits & 0x400000L) != 0) + if ((_bits & 0x4000000L) != 0) { value = _headers._AcceptLanguage; } @@ -411,7 +554,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } set { - _bits |= 0x400000L; + _bits |= 0x4000000L; _headers._AcceptLanguage = value; } } @@ -420,7 +563,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http get { StringValues value = default; - if ((_bits & 0x800000L) != 0) + if ((_bits & 0x8000000L) != 0) { value = _headers._Authorization; } @@ -428,7 +571,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } set { - _bits |= 0x800000L; + _bits |= 0x8000000L; _headers._Authorization = value; } } @@ -437,7 +580,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http get { StringValues value = default; - if ((_bits & 0x1000000L) != 0) + if ((_bits & 0x10000000L) != 0) { value = _headers._Cookie; } @@ -445,7 +588,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } set { - _bits |= 0x1000000L; + _bits |= 0x10000000L; _headers._Cookie = value; } } @@ -454,7 +597,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http get { StringValues value = default; - if ((_bits & 0x2000000L) != 0) + if ((_bits & 0x20000000L) != 0) { value = _headers._Expect; } @@ -462,7 +605,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } set { - _bits |= 0x2000000L; + _bits |= 0x20000000L; _headers._Expect = value; } } @@ -471,7 +614,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http get { StringValues value = default; - if ((_bits & 0x4000000L) != 0) + if ((_bits & 0x40000000L) != 0) { value = _headers._From; } @@ -479,7 +622,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } set { - _bits |= 0x4000000L; + _bits |= 0x40000000L; _headers._From = value; } } @@ -488,7 +631,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http get { StringValues value = default; - if ((_bits & 0x8000000L) != 0) + if ((_bits & 0x80000000L) != 0) { value = _headers._Host; } @@ -496,7 +639,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } set { - _bits |= 0x8000000L; + _bits |= 0x80000000L; _headers._Host = value; } } @@ -505,7 +648,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http get { StringValues value = default; - if ((_bits & 0x10000000L) != 0) + if ((_bits & 0x100000000L) != 0) { value = _headers._IfMatch; } @@ -513,7 +656,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } set { - _bits |= 0x10000000L; + _bits |= 0x100000000L; _headers._IfMatch = value; } } @@ -522,7 +665,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http get { StringValues value = default; - if ((_bits & 0x20000000L) != 0) + if ((_bits & 0x200000000L) != 0) { value = _headers._IfModifiedSince; } @@ -530,7 +673,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } set { - _bits |= 0x20000000L; + _bits |= 0x200000000L; _headers._IfModifiedSince = value; } } @@ -539,7 +682,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http get { StringValues value = default; - if ((_bits & 0x40000000L) != 0) + if ((_bits & 0x400000000L) != 0) { value = _headers._IfNoneMatch; } @@ -547,7 +690,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } set { - _bits |= 0x40000000L; + _bits |= 0x400000000L; _headers._IfNoneMatch = value; } } @@ -556,7 +699,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http get { StringValues value = default; - if ((_bits & 0x80000000L) != 0) + if ((_bits & 0x800000000L) != 0) { value = _headers._IfRange; } @@ -564,7 +707,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } set { - _bits |= 0x80000000L; + _bits |= 0x800000000L; _headers._IfRange = value; } } @@ -573,7 +716,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http get { StringValues value = default; - if ((_bits & 0x100000000L) != 0) + if ((_bits & 0x1000000000L) != 0) { value = _headers._IfUnmodifiedSince; } @@ -581,7 +724,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } set { - _bits |= 0x100000000L; + _bits |= 0x1000000000L; _headers._IfUnmodifiedSince = value; } } @@ -590,7 +733,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http get { StringValues value = default; - if ((_bits & 0x200000000L) != 0) + if ((_bits & 0x2000000000L) != 0) { value = _headers._MaxForwards; } @@ -598,7 +741,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } set { - _bits |= 0x200000000L; + _bits |= 0x2000000000L; _headers._MaxForwards = value; } } @@ -607,7 +750,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http get { StringValues value = default; - if ((_bits & 0x400000000L) != 0) + if ((_bits & 0x4000000000L) != 0) { value = _headers._ProxyAuthorization; } @@ -615,7 +758,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } set { - _bits |= 0x400000000L; + _bits |= 0x4000000000L; _headers._ProxyAuthorization = value; } } @@ -624,7 +767,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http get { StringValues value = default; - if ((_bits & 0x800000000L) != 0) + if ((_bits & 0x8000000000L) != 0) { value = _headers._Referer; } @@ -632,7 +775,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } set { - _bits |= 0x800000000L; + _bits |= 0x8000000000L; _headers._Referer = value; } } @@ -641,7 +784,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http get { StringValues value = default; - if ((_bits & 0x1000000000L) != 0) + if ((_bits & 0x10000000000L) != 0) { value = _headers._Range; } @@ -649,7 +792,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } set { - _bits |= 0x1000000000L; + _bits |= 0x10000000000L; _headers._Range = value; } } @@ -658,7 +801,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http get { StringValues value = default; - if ((_bits & 0x2000000000L) != 0) + if ((_bits & 0x20000000000L) != 0) { value = _headers._TE; } @@ -666,7 +809,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } set { - _bits |= 0x2000000000L; + _bits |= 0x20000000000L; _headers._TE = value; } } @@ -675,7 +818,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http get { StringValues value = default; - if ((_bits & 0x4000000000L) != 0) + if ((_bits & 0x40000000000L) != 0) { value = _headers._Translate; } @@ -683,7 +826,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } set { - _bits |= 0x4000000000L; + _bits |= 0x40000000000L; _headers._Translate = value; } } @@ -692,7 +835,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http get { StringValues value = default; - if ((_bits & 0x8000000000L) != 0) + if ((_bits & 0x80000000000L) != 0) { value = _headers._UserAgent; } @@ -700,7 +843,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } set { - _bits |= 0x8000000000L; + _bits |= 0x80000000000L; _headers._UserAgent = value; } } @@ -709,7 +852,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http get { StringValues value = default; - if ((_bits & 0x10000000000L) != 0) + if ((_bits & 0x100000000000L) != 0) { value = _headers._DNT; } @@ -717,7 +860,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } set { - _bits |= 0x10000000000L; + _bits |= 0x100000000000L; _headers._DNT = value; } } @@ -726,7 +869,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http get { StringValues value = default; - if ((_bits & 0x20000000000L) != 0) + if ((_bits & 0x200000000000L) != 0) { value = _headers._UpgradeInsecureRequests; } @@ -734,7 +877,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } set { - _bits |= 0x20000000000L; + _bits |= 0x200000000000L; _headers._UpgradeInsecureRequests = value; } } @@ -743,7 +886,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http get { StringValues value = default; - if ((_bits & 0x40000000000L) != 0) + if ((_bits & 0x400000000000L) != 0) { value = _headers._RequestId; } @@ -751,7 +894,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } set { - _bits |= 0x40000000000L; + _bits |= 0x400000000000L; _headers._RequestId = value; } } @@ -760,7 +903,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http get { StringValues value = default; - if ((_bits & 0x80000000000L) != 0) + if ((_bits & 0x800000000000L) != 0) { value = _headers._CorrelationContext; } @@ -768,7 +911,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } set { - _bits |= 0x80000000000L; + _bits |= 0x800000000000L; _headers._CorrelationContext = value; } } @@ -777,7 +920,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http get { StringValues value = default; - if ((_bits & 0x100000000000L) != 0) + if ((_bits & 0x1000000000000L) != 0) { value = _headers._TraceParent; } @@ -785,7 +928,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } set { - _bits |= 0x100000000000L; + _bits |= 0x1000000000000L; _headers._TraceParent = value; } } @@ -794,7 +937,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http get { StringValues value = default; - if ((_bits & 0x200000000000L) != 0) + if ((_bits & 0x2000000000000L) != 0) { value = _headers._TraceState; } @@ -802,7 +945,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } set { - _bits |= 0x200000000000L; + _bits |= 0x2000000000000L; _headers._TraceState = value; } } @@ -811,7 +954,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http get { StringValues value = default; - if ((_bits & 0x400000000000L) != 0) + if ((_bits & 0x4000000000000L) != 0) { value = _headers._Origin; } @@ -819,7 +962,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } set { - _bits |= 0x400000000000L; + _bits |= 0x4000000000000L; _headers._Origin = value; } } @@ -828,7 +971,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http get { StringValues value = default; - if ((_bits & 0x800000000000L) != 0) + if ((_bits & 0x8000000000000L) != 0) { value = _headers._AccessControlRequestMethod; } @@ -836,7 +979,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } set { - _bits |= 0x800000000000L; + _bits |= 0x8000000000000L; _headers._AccessControlRequestMethod = value; } } @@ -845,7 +988,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http get { StringValues value = default; - if ((_bits & 0x1000000000000L) != 0) + if ((_bits & 0x10000000000000L) != 0) { value = _headers._AccessControlRequestHeaders; } @@ -853,7 +996,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } set { - _bits |= 0x1000000000000L; + _bits |= 0x10000000000000L; _headers._AccessControlRequestHeaders = value; } } @@ -888,7 +1031,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { if (ReferenceEquals(HeaderNames.TE, key)) { - if ((_bits & 0x2000000000L) != 0) + if ((_bits & 0x20000000000L) != 0) { value = _headers._TE; return true; @@ -898,7 +1041,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if (HeaderNames.TE.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x2000000000L) != 0) + if ((_bits & 0x20000000000L) != 0) { value = _headers._TE; return true; @@ -920,7 +1063,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.DNT, key)) { - if ((_bits & 0x10000000000L) != 0) + if ((_bits & 0x100000000000L) != 0) { value = _headers._DNT; return true; @@ -939,7 +1082,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.DNT.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x10000000000L) != 0) + if ((_bits & 0x100000000000L) != 0) { value = _headers._DNT; return true; @@ -952,7 +1095,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { if (ReferenceEquals(HeaderNames.Host, key)) { - if ((_bits & 0x8000000L) != 0) + if ((_bits & 0x80000000L) != 0) { value = _headers._Host; return true; @@ -970,7 +1113,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.From, key)) { - if ((_bits & 0x4000000L) != 0) + if ((_bits & 0x40000000L) != 0) { value = _headers._From; return true; @@ -980,7 +1123,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if (HeaderNames.Host.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x8000000L) != 0) + if ((_bits & 0x80000000L) != 0) { value = _headers._Host; return true; @@ -998,7 +1141,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.From.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x4000000L) != 0) + if ((_bits & 0x40000000L) != 0) { value = _headers._From; return true; @@ -1009,6 +1152,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } case 5: { + if (ReferenceEquals(HeaderNames.Path, key)) + { + if ((_bits & 0x200000L) != 0) + { + value = _headers._Path; + return true; + } + return false; + } if (ReferenceEquals(HeaderNames.Allow, key)) { if ((_bits & 0x400L) != 0) @@ -1020,7 +1172,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.Range, key)) { - if ((_bits & 0x1000000000L) != 0) + if ((_bits & 0x10000000000L) != 0) { value = _headers._Range; return true; @@ -1028,6 +1180,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http return false; } + if (HeaderNames.Path.Equals(key, StringComparison.OrdinalIgnoreCase)) + { + if ((_bits & 0x200000L) != 0) + { + value = _headers._Path; + return true; + } + return false; + } if (HeaderNames.Allow.Equals(key, StringComparison.OrdinalIgnoreCase)) { if ((_bits & 0x400L) != 0) @@ -1039,7 +1200,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.Range.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x1000000000L) != 0) + if ((_bits & 0x10000000000L) != 0) { value = _headers._Range; return true; @@ -1052,7 +1213,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { if (ReferenceEquals(HeaderNames.Accept, key)) { - if ((_bits & 0x80000L) != 0) + if ((_bits & 0x800000L) != 0) { value = _headers._Accept; return true; @@ -1070,7 +1231,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.Cookie, key)) { - if ((_bits & 0x1000000L) != 0) + if ((_bits & 0x10000000L) != 0) { value = _headers._Cookie; return true; @@ -1079,7 +1240,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.Expect, key)) { - if ((_bits & 0x2000000L) != 0) + if ((_bits & 0x20000000L) != 0) { value = _headers._Expect; return true; @@ -1088,7 +1249,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.Origin, key)) { - if ((_bits & 0x400000000000L) != 0) + if ((_bits & 0x4000000000000L) != 0) { value = _headers._Origin; return true; @@ -1098,7 +1259,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if (HeaderNames.Accept.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x80000L) != 0) + if ((_bits & 0x800000L) != 0) { value = _headers._Accept; return true; @@ -1116,7 +1277,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.Cookie.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x1000000L) != 0) + if ((_bits & 0x10000000L) != 0) { value = _headers._Cookie; return true; @@ -1125,7 +1286,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.Expect.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x2000000L) != 0) + if ((_bits & 0x20000000L) != 0) { value = _headers._Expect; return true; @@ -1134,7 +1295,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.Origin.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x400000000000L) != 0) + if ((_bits & 0x4000000000000L) != 0) { value = _headers._Origin; return true; @@ -1145,6 +1306,24 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } case 7: { + if (ReferenceEquals(HeaderNames.Method, key)) + { + if ((_bits & 0x100000L) != 0) + { + value = _headers._Method; + return true; + } + return false; + } + if (ReferenceEquals(HeaderNames.Scheme, key)) + { + if ((_bits & 0x400000L) != 0) + { + value = _headers._Scheme; + return true; + } + return false; + } if (ReferenceEquals(HeaderNames.Trailer, key)) { if ((_bits & 0x20L) != 0) @@ -1183,7 +1362,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.Referer, key)) { - if ((_bits & 0x800000000L) != 0) + if ((_bits & 0x8000000000L) != 0) { value = _headers._Referer; return true; @@ -1191,6 +1370,24 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http return false; } + if (HeaderNames.Method.Equals(key, StringComparison.OrdinalIgnoreCase)) + { + if ((_bits & 0x100000L) != 0) + { + value = _headers._Method; + return true; + } + return false; + } + if (HeaderNames.Scheme.Equals(key, StringComparison.OrdinalIgnoreCase)) + { + if ((_bits & 0x400000L) != 0) + { + value = _headers._Scheme; + return true; + } + return false; + } if (HeaderNames.Trailer.Equals(key, StringComparison.OrdinalIgnoreCase)) { if ((_bits & 0x20L) != 0) @@ -1229,7 +1426,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.Referer.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x800000000L) != 0) + if ((_bits & 0x8000000000L) != 0) { value = _headers._Referer; return true; @@ -1242,7 +1439,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { if (ReferenceEquals(HeaderNames.IfMatch, key)) { - if ((_bits & 0x10000000L) != 0) + if ((_bits & 0x100000000L) != 0) { value = _headers._IfMatch; return true; @@ -1251,7 +1448,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.IfRange, key)) { - if ((_bits & 0x80000000L) != 0) + if ((_bits & 0x800000000L) != 0) { value = _headers._IfRange; return true; @@ -1261,7 +1458,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if (HeaderNames.IfMatch.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x10000000L) != 0) + if ((_bits & 0x100000000L) != 0) { value = _headers._IfMatch; return true; @@ -1270,7 +1467,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.IfRange.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x80000000L) != 0) + if ((_bits & 0x800000000L) != 0) { value = _headers._IfRange; return true; @@ -1283,7 +1480,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { if (ReferenceEquals(HeaderNames.Translate, key)) { - if ((_bits & 0x4000000000L) != 0) + if ((_bits & 0x40000000000L) != 0) { value = _headers._Translate; return true; @@ -1293,7 +1490,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if (HeaderNames.Translate.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x4000000000L) != 0) + if ((_bits & 0x40000000000L) != 0) { value = _headers._Translate; return true; @@ -1313,9 +1510,18 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } return false; } + if (ReferenceEquals(HeaderNames.Authority, key)) + { + if ((_bits & 0x80000L) != 0) + { + value = _headers._Authority; + return true; + } + return false; + } if (ReferenceEquals(HeaderNames.UserAgent, key)) { - if ((_bits & 0x8000000000L) != 0) + if ((_bits & 0x80000000000L) != 0) { value = _headers._UserAgent; return true; @@ -1333,7 +1539,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.RequestId, key)) { - if ((_bits & 0x40000000000L) != 0) + if ((_bits & 0x400000000000L) != 0) { value = _headers._RequestId; return true; @@ -1342,7 +1548,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.TraceState, key)) { - if ((_bits & 0x200000000000L) != 0) + if ((_bits & 0x2000000000000L) != 0) { value = _headers._TraceState; return true; @@ -1359,9 +1565,18 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } return false; } + if (HeaderNames.Authority.Equals(key, StringComparison.OrdinalIgnoreCase)) + { + if ((_bits & 0x80000L) != 0) + { + value = _headers._Authority; + return true; + } + return false; + } if (HeaderNames.UserAgent.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x8000000000L) != 0) + if ((_bits & 0x80000000000L) != 0) { value = _headers._UserAgent; return true; @@ -1379,7 +1594,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.RequestId.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x40000000000L) != 0) + if ((_bits & 0x400000000000L) != 0) { value = _headers._RequestId; return true; @@ -1388,7 +1603,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.TraceState.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x200000000000L) != 0) + if ((_bits & 0x2000000000000L) != 0) { value = _headers._TraceState; return true; @@ -1410,7 +1625,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.TraceParent, key)) { - if ((_bits & 0x100000000000L) != 0) + if ((_bits & 0x1000000000000L) != 0) { value = _headers._TraceParent; return true; @@ -1429,7 +1644,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.TraceParent.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x100000000000L) != 0) + if ((_bits & 0x1000000000000L) != 0) { value = _headers._TraceParent; return true; @@ -1451,7 +1666,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.MaxForwards, key)) { - if ((_bits & 0x200000000L) != 0) + if ((_bits & 0x2000000000L) != 0) { value = _headers._MaxForwards; return true; @@ -1470,7 +1685,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.MaxForwards.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x200000000L) != 0) + if ((_bits & 0x2000000000L) != 0) { value = _headers._MaxForwards; return true; @@ -1510,7 +1725,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.Authorization, key)) { - if ((_bits & 0x800000L) != 0) + if ((_bits & 0x8000000L) != 0) { value = _headers._Authorization; return true; @@ -1519,7 +1734,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.IfNoneMatch, key)) { - if ((_bits & 0x40000000L) != 0) + if ((_bits & 0x400000000L) != 0) { value = _headers._IfNoneMatch; return true; @@ -1556,7 +1771,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.Authorization.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x800000L) != 0) + if ((_bits & 0x8000000L) != 0) { value = _headers._Authorization; return true; @@ -1565,7 +1780,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.IfNoneMatch.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x40000000L) != 0) + if ((_bits & 0x400000000L) != 0) { value = _headers._IfNoneMatch; return true; @@ -1578,7 +1793,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { if (ReferenceEquals(HeaderNames.AcceptCharset, key)) { - if ((_bits & 0x100000L) != 0) + if ((_bits & 0x1000000L) != 0) { value = _headers._AcceptCharset; return true; @@ -1597,7 +1812,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if (HeaderNames.AcceptCharset.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x100000L) != 0) + if ((_bits & 0x1000000L) != 0) { value = _headers._AcceptCharset; return true; @@ -1619,7 +1834,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { if (ReferenceEquals(HeaderNames.AcceptEncoding, key)) { - if ((_bits & 0x200000L) != 0) + if ((_bits & 0x2000000L) != 0) { value = _headers._AcceptEncoding; return true; @@ -1628,7 +1843,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.AcceptLanguage, key)) { - if ((_bits & 0x400000L) != 0) + if ((_bits & 0x4000000L) != 0) { value = _headers._AcceptLanguage; return true; @@ -1638,7 +1853,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if (HeaderNames.AcceptEncoding.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x200000L) != 0) + if ((_bits & 0x2000000L) != 0) { value = _headers._AcceptEncoding; return true; @@ -1647,7 +1862,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.AcceptLanguage.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x400000L) != 0) + if ((_bits & 0x4000000L) != 0) { value = _headers._AcceptLanguage; return true; @@ -1728,7 +1943,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.IfModifiedSince, key)) { - if ((_bits & 0x20000000L) != 0) + if ((_bits & 0x200000000L) != 0) { value = _headers._IfModifiedSince; return true; @@ -1747,7 +1962,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.IfModifiedSince.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x20000000L) != 0) + if ((_bits & 0x200000000L) != 0) { value = _headers._IfModifiedSince; return true; @@ -1760,7 +1975,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { if (ReferenceEquals(HeaderNames.IfUnmodifiedSince, key)) { - if ((_bits & 0x100000000L) != 0) + if ((_bits & 0x1000000000L) != 0) { value = _headers._IfUnmodifiedSince; return true; @@ -1769,7 +1984,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.ProxyAuthorization, key)) { - if ((_bits & 0x400000000L) != 0) + if ((_bits & 0x4000000000L) != 0) { value = _headers._ProxyAuthorization; return true; @@ -1778,7 +1993,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.CorrelationContext, key)) { - if ((_bits & 0x80000000000L) != 0) + if ((_bits & 0x800000000000L) != 0) { value = _headers._CorrelationContext; return true; @@ -1788,7 +2003,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if (HeaderNames.IfUnmodifiedSince.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x100000000L) != 0) + if ((_bits & 0x1000000000L) != 0) { value = _headers._IfUnmodifiedSince; return true; @@ -1797,7 +2012,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.ProxyAuthorization.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x400000000L) != 0) + if ((_bits & 0x4000000000L) != 0) { value = _headers._ProxyAuthorization; return true; @@ -1806,7 +2021,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.CorrelationContext.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x80000000000L) != 0) + if ((_bits & 0x800000000000L) != 0) { value = _headers._CorrelationContext; return true; @@ -1819,7 +2034,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { if (ReferenceEquals(HeaderNames.UpgradeInsecureRequests, key)) { - if ((_bits & 0x20000000000L) != 0) + if ((_bits & 0x200000000000L) != 0) { value = _headers._UpgradeInsecureRequests; return true; @@ -1829,7 +2044,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if (HeaderNames.UpgradeInsecureRequests.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x20000000000L) != 0) + if ((_bits & 0x200000000000L) != 0) { value = _headers._UpgradeInsecureRequests; return true; @@ -1842,7 +2057,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { if (ReferenceEquals(HeaderNames.AccessControlRequestMethod, key)) { - if ((_bits & 0x800000000000L) != 0) + if ((_bits & 0x8000000000000L) != 0) { value = _headers._AccessControlRequestMethod; return true; @@ -1852,7 +2067,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if (HeaderNames.AccessControlRequestMethod.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x800000000000L) != 0) + if ((_bits & 0x8000000000000L) != 0) { value = _headers._AccessControlRequestMethod; return true; @@ -1865,7 +2080,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { if (ReferenceEquals(HeaderNames.AccessControlRequestHeaders, key)) { - if ((_bits & 0x1000000000000L) != 0) + if ((_bits & 0x10000000000000L) != 0) { value = _headers._AccessControlRequestHeaders; return true; @@ -1875,7 +2090,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if (HeaderNames.AccessControlRequestHeaders.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x1000000000000L) != 0) + if ((_bits & 0x10000000000000L) != 0) { value = _headers._AccessControlRequestHeaders; return true; @@ -1897,14 +2112,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { if (ReferenceEquals(HeaderNames.TE, key)) { - _bits |= 0x2000000000L; + _bits |= 0x20000000000L; _headers._TE = value; return; } if (HeaderNames.TE.Equals(key, StringComparison.OrdinalIgnoreCase)) { - _bits |= 0x2000000000L; + _bits |= 0x20000000000L; _headers._TE = value; return; } @@ -1920,7 +2135,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.DNT, key)) { - _bits |= 0x10000000000L; + _bits |= 0x100000000000L; _headers._DNT = value; return; } @@ -1933,7 +2148,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.DNT.Equals(key, StringComparison.OrdinalIgnoreCase)) { - _bits |= 0x10000000000L; + _bits |= 0x100000000000L; _headers._DNT = value; return; } @@ -1943,7 +2158,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { if (ReferenceEquals(HeaderNames.Host, key)) { - _bits |= 0x8000000L; + _bits |= 0x80000000L; _headers._Host = value; return; } @@ -1955,14 +2170,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.From, key)) { - _bits |= 0x4000000L; + _bits |= 0x40000000L; _headers._From = value; return; } if (HeaderNames.Host.Equals(key, StringComparison.OrdinalIgnoreCase)) { - _bits |= 0x8000000L; + _bits |= 0x80000000L; _headers._Host = value; return; } @@ -1974,7 +2189,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.From.Equals(key, StringComparison.OrdinalIgnoreCase)) { - _bits |= 0x4000000L; + _bits |= 0x40000000L; _headers._From = value; return; } @@ -1982,6 +2197,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } case 5: { + if (ReferenceEquals(HeaderNames.Path, key)) + { + _bits |= 0x200000L; + _headers._Path = value; + return; + } if (ReferenceEquals(HeaderNames.Allow, key)) { _bits |= 0x400L; @@ -1990,11 +2211,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.Range, key)) { - _bits |= 0x1000000000L; + _bits |= 0x10000000000L; _headers._Range = value; return; } + if (HeaderNames.Path.Equals(key, StringComparison.OrdinalIgnoreCase)) + { + _bits |= 0x200000L; + _headers._Path = value; + return; + } if (HeaderNames.Allow.Equals(key, StringComparison.OrdinalIgnoreCase)) { _bits |= 0x400L; @@ -2003,7 +2230,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.Range.Equals(key, StringComparison.OrdinalIgnoreCase)) { - _bits |= 0x1000000000L; + _bits |= 0x10000000000L; _headers._Range = value; return; } @@ -2013,7 +2240,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { if (ReferenceEquals(HeaderNames.Accept, key)) { - _bits |= 0x80000L; + _bits |= 0x800000L; _headers._Accept = value; return; } @@ -2025,26 +2252,26 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.Cookie, key)) { - _bits |= 0x1000000L; + _bits |= 0x10000000L; _headers._Cookie = value; return; } if (ReferenceEquals(HeaderNames.Expect, key)) { - _bits |= 0x2000000L; + _bits |= 0x20000000L; _headers._Expect = value; return; } if (ReferenceEquals(HeaderNames.Origin, key)) { - _bits |= 0x400000000000L; + _bits |= 0x4000000000000L; _headers._Origin = value; return; } if (HeaderNames.Accept.Equals(key, StringComparison.OrdinalIgnoreCase)) { - _bits |= 0x80000L; + _bits |= 0x800000L; _headers._Accept = value; return; } @@ -2056,19 +2283,19 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.Cookie.Equals(key, StringComparison.OrdinalIgnoreCase)) { - _bits |= 0x1000000L; + _bits |= 0x10000000L; _headers._Cookie = value; return; } if (HeaderNames.Expect.Equals(key, StringComparison.OrdinalIgnoreCase)) { - _bits |= 0x2000000L; + _bits |= 0x20000000L; _headers._Expect = value; return; } if (HeaderNames.Origin.Equals(key, StringComparison.OrdinalIgnoreCase)) { - _bits |= 0x400000000000L; + _bits |= 0x4000000000000L; _headers._Origin = value; return; } @@ -2076,6 +2303,18 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } case 7: { + if (ReferenceEquals(HeaderNames.Method, key)) + { + _bits |= 0x100000L; + _headers._Method = value; + return; + } + if (ReferenceEquals(HeaderNames.Scheme, key)) + { + _bits |= 0x400000L; + _headers._Scheme = value; + return; + } if (ReferenceEquals(HeaderNames.Trailer, key)) { _bits |= 0x20L; @@ -2102,11 +2341,23 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.Referer, key)) { - _bits |= 0x800000000L; + _bits |= 0x8000000000L; _headers._Referer = value; return; } + if (HeaderNames.Method.Equals(key, StringComparison.OrdinalIgnoreCase)) + { + _bits |= 0x100000L; + _headers._Method = value; + return; + } + if (HeaderNames.Scheme.Equals(key, StringComparison.OrdinalIgnoreCase)) + { + _bits |= 0x400000L; + _headers._Scheme = value; + return; + } if (HeaderNames.Trailer.Equals(key, StringComparison.OrdinalIgnoreCase)) { _bits |= 0x20L; @@ -2133,7 +2384,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.Referer.Equals(key, StringComparison.OrdinalIgnoreCase)) { - _bits |= 0x800000000L; + _bits |= 0x8000000000L; _headers._Referer = value; return; } @@ -2143,26 +2394,26 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { if (ReferenceEquals(HeaderNames.IfMatch, key)) { - _bits |= 0x10000000L; + _bits |= 0x100000000L; _headers._IfMatch = value; return; } if (ReferenceEquals(HeaderNames.IfRange, key)) { - _bits |= 0x80000000L; + _bits |= 0x800000000L; _headers._IfRange = value; return; } if (HeaderNames.IfMatch.Equals(key, StringComparison.OrdinalIgnoreCase)) { - _bits |= 0x10000000L; + _bits |= 0x100000000L; _headers._IfMatch = value; return; } if (HeaderNames.IfRange.Equals(key, StringComparison.OrdinalIgnoreCase)) { - _bits |= 0x80000000L; + _bits |= 0x800000000L; _headers._IfRange = value; return; } @@ -2172,14 +2423,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { if (ReferenceEquals(HeaderNames.Translate, key)) { - _bits |= 0x4000000000L; + _bits |= 0x40000000000L; _headers._Translate = value; return; } if (HeaderNames.Translate.Equals(key, StringComparison.OrdinalIgnoreCase)) { - _bits |= 0x4000000000L; + _bits |= 0x40000000000L; _headers._Translate = value; return; } @@ -2193,9 +2444,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http _headers._Connection = value; return; } + if (ReferenceEquals(HeaderNames.Authority, key)) + { + _bits |= 0x80000L; + _headers._Authority = value; + return; + } if (ReferenceEquals(HeaderNames.UserAgent, key)) { - _bits |= 0x8000000000L; + _bits |= 0x80000000000L; _headers._UserAgent = value; return; } @@ -2207,13 +2464,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.RequestId, key)) { - _bits |= 0x40000000000L; + _bits |= 0x400000000000L; _headers._RequestId = value; return; } if (ReferenceEquals(HeaderNames.TraceState, key)) { - _bits |= 0x200000000000L; + _bits |= 0x2000000000000L; _headers._TraceState = value; return; } @@ -2224,9 +2481,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http _headers._Connection = value; return; } + if (HeaderNames.Authority.Equals(key, StringComparison.OrdinalIgnoreCase)) + { + _bits |= 0x80000L; + _headers._Authority = value; + return; + } if (HeaderNames.UserAgent.Equals(key, StringComparison.OrdinalIgnoreCase)) { - _bits |= 0x8000000000L; + _bits |= 0x80000000000L; _headers._UserAgent = value; return; } @@ -2238,13 +2501,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.RequestId.Equals(key, StringComparison.OrdinalIgnoreCase)) { - _bits |= 0x40000000000L; + _bits |= 0x400000000000L; _headers._RequestId = value; return; } if (HeaderNames.TraceState.Equals(key, StringComparison.OrdinalIgnoreCase)) { - _bits |= 0x200000000000L; + _bits |= 0x2000000000000L; _headers._TraceState = value; return; } @@ -2260,7 +2523,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.TraceParent, key)) { - _bits |= 0x100000000000L; + _bits |= 0x1000000000000L; _headers._TraceParent = value; return; } @@ -2273,7 +2536,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.TraceParent.Equals(key, StringComparison.OrdinalIgnoreCase)) { - _bits |= 0x100000000000L; + _bits |= 0x1000000000000L; _headers._TraceParent = value; return; } @@ -2289,7 +2552,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.MaxForwards, key)) { - _bits |= 0x200000000L; + _bits |= 0x2000000000L; _headers._MaxForwards = value; return; } @@ -2302,7 +2565,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.MaxForwards.Equals(key, StringComparison.OrdinalIgnoreCase)) { - _bits |= 0x200000000L; + _bits |= 0x2000000000L; _headers._MaxForwards = value; return; } @@ -2330,13 +2593,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.Authorization, key)) { - _bits |= 0x800000L; + _bits |= 0x8000000L; _headers._Authorization = value; return; } if (ReferenceEquals(HeaderNames.IfNoneMatch, key)) { - _bits |= 0x40000000L; + _bits |= 0x400000000L; _headers._IfNoneMatch = value; return; } @@ -2361,13 +2624,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.Authorization.Equals(key, StringComparison.OrdinalIgnoreCase)) { - _bits |= 0x800000L; + _bits |= 0x8000000L; _headers._Authorization = value; return; } if (HeaderNames.IfNoneMatch.Equals(key, StringComparison.OrdinalIgnoreCase)) { - _bits |= 0x40000000L; + _bits |= 0x400000000L; _headers._IfNoneMatch = value; return; } @@ -2377,7 +2640,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { if (ReferenceEquals(HeaderNames.AcceptCharset, key)) { - _bits |= 0x100000L; + _bits |= 0x1000000L; _headers._AcceptCharset = value; return; } @@ -2389,7 +2652,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if (HeaderNames.AcceptCharset.Equals(key, StringComparison.OrdinalIgnoreCase)) { - _bits |= 0x100000L; + _bits |= 0x1000000L; _headers._AcceptCharset = value; return; } @@ -2404,26 +2667,26 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { if (ReferenceEquals(HeaderNames.AcceptEncoding, key)) { - _bits |= 0x200000L; + _bits |= 0x2000000L; _headers._AcceptEncoding = value; return; } if (ReferenceEquals(HeaderNames.AcceptLanguage, key)) { - _bits |= 0x400000L; + _bits |= 0x4000000L; _headers._AcceptLanguage = value; return; } if (HeaderNames.AcceptEncoding.Equals(key, StringComparison.OrdinalIgnoreCase)) { - _bits |= 0x200000L; + _bits |= 0x2000000L; _headers._AcceptEncoding = value; return; } if (HeaderNames.AcceptLanguage.Equals(key, StringComparison.OrdinalIgnoreCase)) { - _bits |= 0x400000L; + _bits |= 0x4000000L; _headers._AcceptLanguage = value; return; } @@ -2480,7 +2743,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.IfModifiedSince, key)) { - _bits |= 0x20000000L; + _bits |= 0x200000000L; _headers._IfModifiedSince = value; return; } @@ -2493,7 +2756,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.IfModifiedSince.Equals(key, StringComparison.OrdinalIgnoreCase)) { - _bits |= 0x20000000L; + _bits |= 0x200000000L; _headers._IfModifiedSince = value; return; } @@ -2503,38 +2766,38 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { if (ReferenceEquals(HeaderNames.IfUnmodifiedSince, key)) { - _bits |= 0x100000000L; + _bits |= 0x1000000000L; _headers._IfUnmodifiedSince = value; return; } if (ReferenceEquals(HeaderNames.ProxyAuthorization, key)) { - _bits |= 0x400000000L; + _bits |= 0x4000000000L; _headers._ProxyAuthorization = value; return; } if (ReferenceEquals(HeaderNames.CorrelationContext, key)) { - _bits |= 0x80000000000L; + _bits |= 0x800000000000L; _headers._CorrelationContext = value; return; } if (HeaderNames.IfUnmodifiedSince.Equals(key, StringComparison.OrdinalIgnoreCase)) { - _bits |= 0x100000000L; + _bits |= 0x1000000000L; _headers._IfUnmodifiedSince = value; return; } if (HeaderNames.ProxyAuthorization.Equals(key, StringComparison.OrdinalIgnoreCase)) { - _bits |= 0x400000000L; + _bits |= 0x4000000000L; _headers._ProxyAuthorization = value; return; } if (HeaderNames.CorrelationContext.Equals(key, StringComparison.OrdinalIgnoreCase)) { - _bits |= 0x80000000000L; + _bits |= 0x800000000000L; _headers._CorrelationContext = value; return; } @@ -2544,14 +2807,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { if (ReferenceEquals(HeaderNames.UpgradeInsecureRequests, key)) { - _bits |= 0x20000000000L; + _bits |= 0x200000000000L; _headers._UpgradeInsecureRequests = value; return; } if (HeaderNames.UpgradeInsecureRequests.Equals(key, StringComparison.OrdinalIgnoreCase)) { - _bits |= 0x20000000000L; + _bits |= 0x200000000000L; _headers._UpgradeInsecureRequests = value; return; } @@ -2561,14 +2824,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { if (ReferenceEquals(HeaderNames.AccessControlRequestMethod, key)) { - _bits |= 0x800000000000L; + _bits |= 0x8000000000000L; _headers._AccessControlRequestMethod = value; return; } if (HeaderNames.AccessControlRequestMethod.Equals(key, StringComparison.OrdinalIgnoreCase)) { - _bits |= 0x800000000000L; + _bits |= 0x8000000000000L; _headers._AccessControlRequestMethod = value; return; } @@ -2578,14 +2841,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { if (ReferenceEquals(HeaderNames.AccessControlRequestHeaders, key)) { - _bits |= 0x1000000000000L; + _bits |= 0x10000000000000L; _headers._AccessControlRequestHeaders = value; return; } if (HeaderNames.AccessControlRequestHeaders.Equals(key, StringComparison.OrdinalIgnoreCase)) { - _bits |= 0x1000000000000L; + _bits |= 0x10000000000000L; _headers._AccessControlRequestHeaders = value; return; } @@ -2604,9 +2867,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { if (ReferenceEquals(HeaderNames.TE, key)) { - if ((_bits & 0x2000000000L) == 0) + if ((_bits & 0x20000000000L) == 0) { - _bits |= 0x2000000000L; + _bits |= 0x20000000000L; _headers._TE = value; return true; } @@ -2615,9 +2878,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if (HeaderNames.TE.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x2000000000L) == 0) + if ((_bits & 0x20000000000L) == 0) { - _bits |= 0x2000000000L; + _bits |= 0x20000000000L; _headers._TE = value; return true; } @@ -2639,9 +2902,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.DNT, key)) { - if ((_bits & 0x10000000000L) == 0) + if ((_bits & 0x100000000000L) == 0) { - _bits |= 0x10000000000L; + _bits |= 0x100000000000L; _headers._DNT = value; return true; } @@ -2660,9 +2923,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.DNT.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x10000000000L) == 0) + if ((_bits & 0x100000000000L) == 0) { - _bits |= 0x10000000000L; + _bits |= 0x100000000000L; _headers._DNT = value; return true; } @@ -2674,9 +2937,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { if (ReferenceEquals(HeaderNames.Host, key)) { - if ((_bits & 0x8000000L) == 0) + if ((_bits & 0x80000000L) == 0) { - _bits |= 0x8000000L; + _bits |= 0x80000000L; _headers._Host = value; return true; } @@ -2694,9 +2957,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.From, key)) { - if ((_bits & 0x4000000L) == 0) + if ((_bits & 0x40000000L) == 0) { - _bits |= 0x4000000L; + _bits |= 0x40000000L; _headers._From = value; return true; } @@ -2705,9 +2968,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if (HeaderNames.Host.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x8000000L) == 0) + if ((_bits & 0x80000000L) == 0) { - _bits |= 0x8000000L; + _bits |= 0x80000000L; _headers._Host = value; return true; } @@ -2725,9 +2988,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.From.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x4000000L) == 0) + if ((_bits & 0x40000000L) == 0) { - _bits |= 0x4000000L; + _bits |= 0x40000000L; _headers._From = value; return true; } @@ -2737,6 +3000,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } case 5: { + if (ReferenceEquals(HeaderNames.Path, key)) + { + if ((_bits & 0x200000L) == 0) + { + _bits |= 0x200000L; + _headers._Path = value; + return true; + } + return false; + } if (ReferenceEquals(HeaderNames.Allow, key)) { if ((_bits & 0x400L) == 0) @@ -2749,15 +3022,25 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.Range, key)) { - if ((_bits & 0x1000000000L) == 0) + if ((_bits & 0x10000000000L) == 0) { - _bits |= 0x1000000000L; + _bits |= 0x10000000000L; _headers._Range = value; return true; } return false; } + if (HeaderNames.Path.Equals(key, StringComparison.OrdinalIgnoreCase)) + { + if ((_bits & 0x200000L) == 0) + { + _bits |= 0x200000L; + _headers._Path = value; + return true; + } + return false; + } if (HeaderNames.Allow.Equals(key, StringComparison.OrdinalIgnoreCase)) { if ((_bits & 0x400L) == 0) @@ -2770,9 +3053,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.Range.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x1000000000L) == 0) + if ((_bits & 0x10000000000L) == 0) { - _bits |= 0x1000000000L; + _bits |= 0x10000000000L; _headers._Range = value; return true; } @@ -2784,9 +3067,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { if (ReferenceEquals(HeaderNames.Accept, key)) { - if ((_bits & 0x80000L) == 0) + if ((_bits & 0x800000L) == 0) { - _bits |= 0x80000L; + _bits |= 0x800000L; _headers._Accept = value; return true; } @@ -2804,9 +3087,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.Cookie, key)) { - if ((_bits & 0x1000000L) == 0) + if ((_bits & 0x10000000L) == 0) { - _bits |= 0x1000000L; + _bits |= 0x10000000L; _headers._Cookie = value; return true; } @@ -2814,9 +3097,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.Expect, key)) { - if ((_bits & 0x2000000L) == 0) + if ((_bits & 0x20000000L) == 0) { - _bits |= 0x2000000L; + _bits |= 0x20000000L; _headers._Expect = value; return true; } @@ -2824,9 +3107,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.Origin, key)) { - if ((_bits & 0x400000000000L) == 0) + if ((_bits & 0x4000000000000L) == 0) { - _bits |= 0x400000000000L; + _bits |= 0x4000000000000L; _headers._Origin = value; return true; } @@ -2835,9 +3118,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if (HeaderNames.Accept.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x80000L) == 0) + if ((_bits & 0x800000L) == 0) { - _bits |= 0x80000L; + _bits |= 0x800000L; _headers._Accept = value; return true; } @@ -2855,9 +3138,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.Cookie.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x1000000L) == 0) + if ((_bits & 0x10000000L) == 0) { - _bits |= 0x1000000L; + _bits |= 0x10000000L; _headers._Cookie = value; return true; } @@ -2865,9 +3148,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.Expect.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x2000000L) == 0) + if ((_bits & 0x20000000L) == 0) { - _bits |= 0x2000000L; + _bits |= 0x20000000L; _headers._Expect = value; return true; } @@ -2875,9 +3158,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.Origin.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x400000000000L) == 0) + if ((_bits & 0x4000000000000L) == 0) { - _bits |= 0x400000000000L; + _bits |= 0x4000000000000L; _headers._Origin = value; return true; } @@ -2887,6 +3170,26 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } case 7: { + if (ReferenceEquals(HeaderNames.Method, key)) + { + if ((_bits & 0x100000L) == 0) + { + _bits |= 0x100000L; + _headers._Method = value; + return true; + } + return false; + } + if (ReferenceEquals(HeaderNames.Scheme, key)) + { + if ((_bits & 0x400000L) == 0) + { + _bits |= 0x400000L; + _headers._Scheme = value; + return true; + } + return false; + } if (ReferenceEquals(HeaderNames.Trailer, key)) { if ((_bits & 0x20L) == 0) @@ -2929,15 +3232,35 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.Referer, key)) { - if ((_bits & 0x800000000L) == 0) + if ((_bits & 0x8000000000L) == 0) { - _bits |= 0x800000000L; + _bits |= 0x8000000000L; _headers._Referer = value; return true; } return false; } + if (HeaderNames.Method.Equals(key, StringComparison.OrdinalIgnoreCase)) + { + if ((_bits & 0x100000L) == 0) + { + _bits |= 0x100000L; + _headers._Method = value; + return true; + } + return false; + } + if (HeaderNames.Scheme.Equals(key, StringComparison.OrdinalIgnoreCase)) + { + if ((_bits & 0x400000L) == 0) + { + _bits |= 0x400000L; + _headers._Scheme = value; + return true; + } + return false; + } if (HeaderNames.Trailer.Equals(key, StringComparison.OrdinalIgnoreCase)) { if ((_bits & 0x20L) == 0) @@ -2980,9 +3303,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.Referer.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x800000000L) == 0) + if ((_bits & 0x8000000000L) == 0) { - _bits |= 0x800000000L; + _bits |= 0x8000000000L; _headers._Referer = value; return true; } @@ -2994,9 +3317,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { if (ReferenceEquals(HeaderNames.IfMatch, key)) { - if ((_bits & 0x10000000L) == 0) + if ((_bits & 0x100000000L) == 0) { - _bits |= 0x10000000L; + _bits |= 0x100000000L; _headers._IfMatch = value; return true; } @@ -3004,9 +3327,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.IfRange, key)) { - if ((_bits & 0x80000000L) == 0) + if ((_bits & 0x800000000L) == 0) { - _bits |= 0x80000000L; + _bits |= 0x800000000L; _headers._IfRange = value; return true; } @@ -3015,9 +3338,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if (HeaderNames.IfMatch.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x10000000L) == 0) + if ((_bits & 0x100000000L) == 0) { - _bits |= 0x10000000L; + _bits |= 0x100000000L; _headers._IfMatch = value; return true; } @@ -3025,9 +3348,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.IfRange.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x80000000L) == 0) + if ((_bits & 0x800000000L) == 0) { - _bits |= 0x80000000L; + _bits |= 0x800000000L; _headers._IfRange = value; return true; } @@ -3039,9 +3362,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { if (ReferenceEquals(HeaderNames.Translate, key)) { - if ((_bits & 0x4000000000L) == 0) + if ((_bits & 0x40000000000L) == 0) { - _bits |= 0x4000000000L; + _bits |= 0x40000000000L; _headers._Translate = value; return true; } @@ -3050,9 +3373,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if (HeaderNames.Translate.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x4000000000L) == 0) + if ((_bits & 0x40000000000L) == 0) { - _bits |= 0x4000000000L; + _bits |= 0x40000000000L; _headers._Translate = value; return true; } @@ -3072,11 +3395,21 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } return false; } + if (ReferenceEquals(HeaderNames.Authority, key)) + { + if ((_bits & 0x80000L) == 0) + { + _bits |= 0x80000L; + _headers._Authority = value; + return true; + } + return false; + } if (ReferenceEquals(HeaderNames.UserAgent, key)) { - if ((_bits & 0x8000000000L) == 0) + if ((_bits & 0x80000000000L) == 0) { - _bits |= 0x8000000000L; + _bits |= 0x80000000000L; _headers._UserAgent = value; return true; } @@ -3094,9 +3427,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.RequestId, key)) { - if ((_bits & 0x40000000000L) == 0) + if ((_bits & 0x400000000000L) == 0) { - _bits |= 0x40000000000L; + _bits |= 0x400000000000L; _headers._RequestId = value; return true; } @@ -3104,9 +3437,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.TraceState, key)) { - if ((_bits & 0x200000000000L) == 0) + if ((_bits & 0x2000000000000L) == 0) { - _bits |= 0x200000000000L; + _bits |= 0x2000000000000L; _headers._TraceState = value; return true; } @@ -3123,11 +3456,21 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } return false; } + if (HeaderNames.Authority.Equals(key, StringComparison.OrdinalIgnoreCase)) + { + if ((_bits & 0x80000L) == 0) + { + _bits |= 0x80000L; + _headers._Authority = value; + return true; + } + return false; + } if (HeaderNames.UserAgent.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x8000000000L) == 0) + if ((_bits & 0x80000000000L) == 0) { - _bits |= 0x8000000000L; + _bits |= 0x80000000000L; _headers._UserAgent = value; return true; } @@ -3145,9 +3488,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.RequestId.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x40000000000L) == 0) + if ((_bits & 0x400000000000L) == 0) { - _bits |= 0x40000000000L; + _bits |= 0x400000000000L; _headers._RequestId = value; return true; } @@ -3155,9 +3498,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.TraceState.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x200000000000L) == 0) + if ((_bits & 0x2000000000000L) == 0) { - _bits |= 0x200000000000L; + _bits |= 0x2000000000000L; _headers._TraceState = value; return true; } @@ -3179,9 +3522,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.TraceParent, key)) { - if ((_bits & 0x100000000000L) == 0) + if ((_bits & 0x1000000000000L) == 0) { - _bits |= 0x100000000000L; + _bits |= 0x1000000000000L; _headers._TraceParent = value; return true; } @@ -3200,9 +3543,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.TraceParent.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x100000000000L) == 0) + if ((_bits & 0x1000000000000L) == 0) { - _bits |= 0x100000000000L; + _bits |= 0x1000000000000L; _headers._TraceParent = value; return true; } @@ -3224,9 +3567,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.MaxForwards, key)) { - if ((_bits & 0x200000000L) == 0) + if ((_bits & 0x2000000000L) == 0) { - _bits |= 0x200000000L; + _bits |= 0x2000000000L; _headers._MaxForwards = value; return true; } @@ -3245,9 +3588,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.MaxForwards.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x200000000L) == 0) + if ((_bits & 0x2000000000L) == 0) { - _bits |= 0x200000000L; + _bits |= 0x2000000000L; _headers._MaxForwards = value; return true; } @@ -3289,9 +3632,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.Authorization, key)) { - if ((_bits & 0x800000L) == 0) + if ((_bits & 0x8000000L) == 0) { - _bits |= 0x800000L; + _bits |= 0x8000000L; _headers._Authorization = value; return true; } @@ -3299,9 +3642,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.IfNoneMatch, key)) { - if ((_bits & 0x40000000L) == 0) + if ((_bits & 0x400000000L) == 0) { - _bits |= 0x40000000L; + _bits |= 0x400000000L; _headers._IfNoneMatch = value; return true; } @@ -3340,9 +3683,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.Authorization.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x800000L) == 0) + if ((_bits & 0x8000000L) == 0) { - _bits |= 0x800000L; + _bits |= 0x8000000L; _headers._Authorization = value; return true; } @@ -3350,9 +3693,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.IfNoneMatch.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x40000000L) == 0) + if ((_bits & 0x400000000L) == 0) { - _bits |= 0x40000000L; + _bits |= 0x400000000L; _headers._IfNoneMatch = value; return true; } @@ -3364,9 +3707,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { if (ReferenceEquals(HeaderNames.AcceptCharset, key)) { - if ((_bits & 0x100000L) == 0) + if ((_bits & 0x1000000L) == 0) { - _bits |= 0x100000L; + _bits |= 0x1000000L; _headers._AcceptCharset = value; return true; } @@ -3384,9 +3727,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if (HeaderNames.AcceptCharset.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x100000L) == 0) + if ((_bits & 0x1000000L) == 0) { - _bits |= 0x100000L; + _bits |= 0x1000000L; _headers._AcceptCharset = value; return true; } @@ -3407,9 +3750,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { if (ReferenceEquals(HeaderNames.AcceptEncoding, key)) { - if ((_bits & 0x200000L) == 0) + if ((_bits & 0x2000000L) == 0) { - _bits |= 0x200000L; + _bits |= 0x2000000L; _headers._AcceptEncoding = value; return true; } @@ -3417,9 +3760,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.AcceptLanguage, key)) { - if ((_bits & 0x400000L) == 0) + if ((_bits & 0x4000000L) == 0) { - _bits |= 0x400000L; + _bits |= 0x4000000L; _headers._AcceptLanguage = value; return true; } @@ -3428,9 +3771,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if (HeaderNames.AcceptEncoding.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x200000L) == 0) + if ((_bits & 0x2000000L) == 0) { - _bits |= 0x200000L; + _bits |= 0x2000000L; _headers._AcceptEncoding = value; return true; } @@ -3438,9 +3781,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.AcceptLanguage.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x400000L) == 0) + if ((_bits & 0x4000000L) == 0) { - _bits |= 0x400000L; + _bits |= 0x4000000L; _headers._AcceptLanguage = value; return true; } @@ -3527,9 +3870,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.IfModifiedSince, key)) { - if ((_bits & 0x20000000L) == 0) + if ((_bits & 0x200000000L) == 0) { - _bits |= 0x20000000L; + _bits |= 0x200000000L; _headers._IfModifiedSince = value; return true; } @@ -3548,9 +3891,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.IfModifiedSince.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x20000000L) == 0) + if ((_bits & 0x200000000L) == 0) { - _bits |= 0x20000000L; + _bits |= 0x200000000L; _headers._IfModifiedSince = value; return true; } @@ -3562,9 +3905,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { if (ReferenceEquals(HeaderNames.IfUnmodifiedSince, key)) { - if ((_bits & 0x100000000L) == 0) + if ((_bits & 0x1000000000L) == 0) { - _bits |= 0x100000000L; + _bits |= 0x1000000000L; _headers._IfUnmodifiedSince = value; return true; } @@ -3572,9 +3915,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.ProxyAuthorization, key)) { - if ((_bits & 0x400000000L) == 0) + if ((_bits & 0x4000000000L) == 0) { - _bits |= 0x400000000L; + _bits |= 0x4000000000L; _headers._ProxyAuthorization = value; return true; } @@ -3582,9 +3925,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.CorrelationContext, key)) { - if ((_bits & 0x80000000000L) == 0) + if ((_bits & 0x800000000000L) == 0) { - _bits |= 0x80000000000L; + _bits |= 0x800000000000L; _headers._CorrelationContext = value; return true; } @@ -3593,9 +3936,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if (HeaderNames.IfUnmodifiedSince.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x100000000L) == 0) + if ((_bits & 0x1000000000L) == 0) { - _bits |= 0x100000000L; + _bits |= 0x1000000000L; _headers._IfUnmodifiedSince = value; return true; } @@ -3603,9 +3946,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.ProxyAuthorization.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x400000000L) == 0) + if ((_bits & 0x4000000000L) == 0) { - _bits |= 0x400000000L; + _bits |= 0x4000000000L; _headers._ProxyAuthorization = value; return true; } @@ -3613,9 +3956,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.CorrelationContext.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x80000000000L) == 0) + if ((_bits & 0x800000000000L) == 0) { - _bits |= 0x80000000000L; + _bits |= 0x800000000000L; _headers._CorrelationContext = value; return true; } @@ -3627,9 +3970,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { if (ReferenceEquals(HeaderNames.UpgradeInsecureRequests, key)) { - if ((_bits & 0x20000000000L) == 0) + if ((_bits & 0x200000000000L) == 0) { - _bits |= 0x20000000000L; + _bits |= 0x200000000000L; _headers._UpgradeInsecureRequests = value; return true; } @@ -3638,9 +3981,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if (HeaderNames.UpgradeInsecureRequests.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x20000000000L) == 0) + if ((_bits & 0x200000000000L) == 0) { - _bits |= 0x20000000000L; + _bits |= 0x200000000000L; _headers._UpgradeInsecureRequests = value; return true; } @@ -3652,9 +3995,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { if (ReferenceEquals(HeaderNames.AccessControlRequestMethod, key)) { - if ((_bits & 0x800000000000L) == 0) + if ((_bits & 0x8000000000000L) == 0) { - _bits |= 0x800000000000L; + _bits |= 0x8000000000000L; _headers._AccessControlRequestMethod = value; return true; } @@ -3663,9 +4006,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if (HeaderNames.AccessControlRequestMethod.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x800000000000L) == 0) + if ((_bits & 0x8000000000000L) == 0) { - _bits |= 0x800000000000L; + _bits |= 0x8000000000000L; _headers._AccessControlRequestMethod = value; return true; } @@ -3677,9 +4020,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { if (ReferenceEquals(HeaderNames.AccessControlRequestHeaders, key)) { - if ((_bits & 0x1000000000000L) == 0) + if ((_bits & 0x10000000000000L) == 0) { - _bits |= 0x1000000000000L; + _bits |= 0x10000000000000L; _headers._AccessControlRequestHeaders = value; return true; } @@ -3688,9 +4031,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if (HeaderNames.AccessControlRequestHeaders.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x1000000000000L) == 0) + if ((_bits & 0x10000000000000L) == 0) { - _bits |= 0x1000000000000L; + _bits |= 0x10000000000000L; _headers._AccessControlRequestHeaders = value; return true; } @@ -3711,9 +4054,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { if (ReferenceEquals(HeaderNames.TE, key)) { - if ((_bits & 0x2000000000L) != 0) + if ((_bits & 0x20000000000L) != 0) { - _bits &= ~0x2000000000L; + _bits &= ~0x20000000000L; _headers._TE = default(StringValues); return true; } @@ -3722,9 +4065,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if (HeaderNames.TE.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x2000000000L) != 0) + if ((_bits & 0x20000000000L) != 0) { - _bits &= ~0x2000000000L; + _bits &= ~0x20000000000L; _headers._TE = default(StringValues); return true; } @@ -3746,9 +4089,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.DNT, key)) { - if ((_bits & 0x10000000000L) != 0) + if ((_bits & 0x100000000000L) != 0) { - _bits &= ~0x10000000000L; + _bits &= ~0x100000000000L; _headers._DNT = default(StringValues); return true; } @@ -3767,9 +4110,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.DNT.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x10000000000L) != 0) + if ((_bits & 0x100000000000L) != 0) { - _bits &= ~0x10000000000L; + _bits &= ~0x100000000000L; _headers._DNT = default(StringValues); return true; } @@ -3781,9 +4124,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { if (ReferenceEquals(HeaderNames.Host, key)) { - if ((_bits & 0x8000000L) != 0) + if ((_bits & 0x80000000L) != 0) { - _bits &= ~0x8000000L; + _bits &= ~0x80000000L; _headers._Host = default(StringValues); return true; } @@ -3801,9 +4144,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.From, key)) { - if ((_bits & 0x4000000L) != 0) + if ((_bits & 0x40000000L) != 0) { - _bits &= ~0x4000000L; + _bits &= ~0x40000000L; _headers._From = default(StringValues); return true; } @@ -3812,9 +4155,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if (HeaderNames.Host.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x8000000L) != 0) + if ((_bits & 0x80000000L) != 0) { - _bits &= ~0x8000000L; + _bits &= ~0x80000000L; _headers._Host = default(StringValues); return true; } @@ -3832,9 +4175,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.From.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x4000000L) != 0) + if ((_bits & 0x40000000L) != 0) { - _bits &= ~0x4000000L; + _bits &= ~0x40000000L; _headers._From = default(StringValues); return true; } @@ -3844,6 +4187,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } case 5: { + if (ReferenceEquals(HeaderNames.Path, key)) + { + if ((_bits & 0x200000L) != 0) + { + _bits &= ~0x200000L; + _headers._Path = default(StringValues); + return true; + } + return false; + } if (ReferenceEquals(HeaderNames.Allow, key)) { if ((_bits & 0x400L) != 0) @@ -3856,15 +4209,25 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.Range, key)) { - if ((_bits & 0x1000000000L) != 0) + if ((_bits & 0x10000000000L) != 0) { - _bits &= ~0x1000000000L; + _bits &= ~0x10000000000L; _headers._Range = default(StringValues); return true; } return false; } + if (HeaderNames.Path.Equals(key, StringComparison.OrdinalIgnoreCase)) + { + if ((_bits & 0x200000L) != 0) + { + _bits &= ~0x200000L; + _headers._Path = default(StringValues); + return true; + } + return false; + } if (HeaderNames.Allow.Equals(key, StringComparison.OrdinalIgnoreCase)) { if ((_bits & 0x400L) != 0) @@ -3877,9 +4240,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.Range.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x1000000000L) != 0) + if ((_bits & 0x10000000000L) != 0) { - _bits &= ~0x1000000000L; + _bits &= ~0x10000000000L; _headers._Range = default(StringValues); return true; } @@ -3891,9 +4254,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { if (ReferenceEquals(HeaderNames.Accept, key)) { - if ((_bits & 0x80000L) != 0) + if ((_bits & 0x800000L) != 0) { - _bits &= ~0x80000L; + _bits &= ~0x800000L; _headers._Accept = default(StringValues); return true; } @@ -3911,9 +4274,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.Cookie, key)) { - if ((_bits & 0x1000000L) != 0) + if ((_bits & 0x10000000L) != 0) { - _bits &= ~0x1000000L; + _bits &= ~0x10000000L; _headers._Cookie = default(StringValues); return true; } @@ -3921,9 +4284,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.Expect, key)) { - if ((_bits & 0x2000000L) != 0) + if ((_bits & 0x20000000L) != 0) { - _bits &= ~0x2000000L; + _bits &= ~0x20000000L; _headers._Expect = default(StringValues); return true; } @@ -3931,9 +4294,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.Origin, key)) { - if ((_bits & 0x400000000000L) != 0) + if ((_bits & 0x4000000000000L) != 0) { - _bits &= ~0x400000000000L; + _bits &= ~0x4000000000000L; _headers._Origin = default(StringValues); return true; } @@ -3942,9 +4305,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if (HeaderNames.Accept.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x80000L) != 0) + if ((_bits & 0x800000L) != 0) { - _bits &= ~0x80000L; + _bits &= ~0x800000L; _headers._Accept = default(StringValues); return true; } @@ -3962,9 +4325,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.Cookie.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x1000000L) != 0) + if ((_bits & 0x10000000L) != 0) { - _bits &= ~0x1000000L; + _bits &= ~0x10000000L; _headers._Cookie = default(StringValues); return true; } @@ -3972,9 +4335,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.Expect.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x2000000L) != 0) + if ((_bits & 0x20000000L) != 0) { - _bits &= ~0x2000000L; + _bits &= ~0x20000000L; _headers._Expect = default(StringValues); return true; } @@ -3982,9 +4345,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.Origin.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x400000000000L) != 0) + if ((_bits & 0x4000000000000L) != 0) { - _bits &= ~0x400000000000L; + _bits &= ~0x4000000000000L; _headers._Origin = default(StringValues); return true; } @@ -3994,6 +4357,26 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } case 7: { + if (ReferenceEquals(HeaderNames.Method, key)) + { + if ((_bits & 0x100000L) != 0) + { + _bits &= ~0x100000L; + _headers._Method = default(StringValues); + return true; + } + return false; + } + if (ReferenceEquals(HeaderNames.Scheme, key)) + { + if ((_bits & 0x400000L) != 0) + { + _bits &= ~0x400000L; + _headers._Scheme = default(StringValues); + return true; + } + return false; + } if (ReferenceEquals(HeaderNames.Trailer, key)) { if ((_bits & 0x20L) != 0) @@ -4036,15 +4419,35 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.Referer, key)) { - if ((_bits & 0x800000000L) != 0) + if ((_bits & 0x8000000000L) != 0) { - _bits &= ~0x800000000L; + _bits &= ~0x8000000000L; _headers._Referer = default(StringValues); return true; } return false; } + if (HeaderNames.Method.Equals(key, StringComparison.OrdinalIgnoreCase)) + { + if ((_bits & 0x100000L) != 0) + { + _bits &= ~0x100000L; + _headers._Method = default(StringValues); + return true; + } + return false; + } + if (HeaderNames.Scheme.Equals(key, StringComparison.OrdinalIgnoreCase)) + { + if ((_bits & 0x400000L) != 0) + { + _bits &= ~0x400000L; + _headers._Scheme = default(StringValues); + return true; + } + return false; + } if (HeaderNames.Trailer.Equals(key, StringComparison.OrdinalIgnoreCase)) { if ((_bits & 0x20L) != 0) @@ -4087,9 +4490,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.Referer.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x800000000L) != 0) + if ((_bits & 0x8000000000L) != 0) { - _bits &= ~0x800000000L; + _bits &= ~0x8000000000L; _headers._Referer = default(StringValues); return true; } @@ -4101,9 +4504,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { if (ReferenceEquals(HeaderNames.IfMatch, key)) { - if ((_bits & 0x10000000L) != 0) + if ((_bits & 0x100000000L) != 0) { - _bits &= ~0x10000000L; + _bits &= ~0x100000000L; _headers._IfMatch = default(StringValues); return true; } @@ -4111,9 +4514,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.IfRange, key)) { - if ((_bits & 0x80000000L) != 0) + if ((_bits & 0x800000000L) != 0) { - _bits &= ~0x80000000L; + _bits &= ~0x800000000L; _headers._IfRange = default(StringValues); return true; } @@ -4122,9 +4525,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if (HeaderNames.IfMatch.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x10000000L) != 0) + if ((_bits & 0x100000000L) != 0) { - _bits &= ~0x10000000L; + _bits &= ~0x100000000L; _headers._IfMatch = default(StringValues); return true; } @@ -4132,9 +4535,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.IfRange.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x80000000L) != 0) + if ((_bits & 0x800000000L) != 0) { - _bits &= ~0x80000000L; + _bits &= ~0x800000000L; _headers._IfRange = default(StringValues); return true; } @@ -4146,9 +4549,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { if (ReferenceEquals(HeaderNames.Translate, key)) { - if ((_bits & 0x4000000000L) != 0) + if ((_bits & 0x40000000000L) != 0) { - _bits &= ~0x4000000000L; + _bits &= ~0x40000000000L; _headers._Translate = default(StringValues); return true; } @@ -4157,9 +4560,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if (HeaderNames.Translate.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x4000000000L) != 0) + if ((_bits & 0x40000000000L) != 0) { - _bits &= ~0x4000000000L; + _bits &= ~0x40000000000L; _headers._Translate = default(StringValues); return true; } @@ -4179,11 +4582,21 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } return false; } + if (ReferenceEquals(HeaderNames.Authority, key)) + { + if ((_bits & 0x80000L) != 0) + { + _bits &= ~0x80000L; + _headers._Authority = default(StringValues); + return true; + } + return false; + } if (ReferenceEquals(HeaderNames.UserAgent, key)) { - if ((_bits & 0x8000000000L) != 0) + if ((_bits & 0x80000000000L) != 0) { - _bits &= ~0x8000000000L; + _bits &= ~0x80000000000L; _headers._UserAgent = default(StringValues); return true; } @@ -4201,9 +4614,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.RequestId, key)) { - if ((_bits & 0x40000000000L) != 0) + if ((_bits & 0x400000000000L) != 0) { - _bits &= ~0x40000000000L; + _bits &= ~0x400000000000L; _headers._RequestId = default(StringValues); return true; } @@ -4211,9 +4624,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.TraceState, key)) { - if ((_bits & 0x200000000000L) != 0) + if ((_bits & 0x2000000000000L) != 0) { - _bits &= ~0x200000000000L; + _bits &= ~0x2000000000000L; _headers._TraceState = default(StringValues); return true; } @@ -4230,11 +4643,21 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } return false; } + if (HeaderNames.Authority.Equals(key, StringComparison.OrdinalIgnoreCase)) + { + if ((_bits & 0x80000L) != 0) + { + _bits &= ~0x80000L; + _headers._Authority = default(StringValues); + return true; + } + return false; + } if (HeaderNames.UserAgent.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x8000000000L) != 0) + if ((_bits & 0x80000000000L) != 0) { - _bits &= ~0x8000000000L; + _bits &= ~0x80000000000L; _headers._UserAgent = default(StringValues); return true; } @@ -4252,9 +4675,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.RequestId.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x40000000000L) != 0) + if ((_bits & 0x400000000000L) != 0) { - _bits &= ~0x40000000000L; + _bits &= ~0x400000000000L; _headers._RequestId = default(StringValues); return true; } @@ -4262,9 +4685,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.TraceState.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x200000000000L) != 0) + if ((_bits & 0x2000000000000L) != 0) { - _bits &= ~0x200000000000L; + _bits &= ~0x2000000000000L; _headers._TraceState = default(StringValues); return true; } @@ -4286,9 +4709,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.TraceParent, key)) { - if ((_bits & 0x100000000000L) != 0) + if ((_bits & 0x1000000000000L) != 0) { - _bits &= ~0x100000000000L; + _bits &= ~0x1000000000000L; _headers._TraceParent = default(StringValues); return true; } @@ -4307,9 +4730,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.TraceParent.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x100000000000L) != 0) + if ((_bits & 0x1000000000000L) != 0) { - _bits &= ~0x100000000000L; + _bits &= ~0x1000000000000L; _headers._TraceParent = default(StringValues); return true; } @@ -4331,9 +4754,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.MaxForwards, key)) { - if ((_bits & 0x200000000L) != 0) + if ((_bits & 0x2000000000L) != 0) { - _bits &= ~0x200000000L; + _bits &= ~0x2000000000L; _headers._MaxForwards = default(StringValues); return true; } @@ -4352,9 +4775,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.MaxForwards.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x200000000L) != 0) + if ((_bits & 0x2000000000L) != 0) { - _bits &= ~0x200000000L; + _bits &= ~0x2000000000L; _headers._MaxForwards = default(StringValues); return true; } @@ -4396,9 +4819,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.Authorization, key)) { - if ((_bits & 0x800000L) != 0) + if ((_bits & 0x8000000L) != 0) { - _bits &= ~0x800000L; + _bits &= ~0x8000000L; _headers._Authorization = default(StringValues); return true; } @@ -4406,9 +4829,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.IfNoneMatch, key)) { - if ((_bits & 0x40000000L) != 0) + if ((_bits & 0x400000000L) != 0) { - _bits &= ~0x40000000L; + _bits &= ~0x400000000L; _headers._IfNoneMatch = default(StringValues); return true; } @@ -4447,9 +4870,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.Authorization.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x800000L) != 0) + if ((_bits & 0x8000000L) != 0) { - _bits &= ~0x800000L; + _bits &= ~0x8000000L; _headers._Authorization = default(StringValues); return true; } @@ -4457,9 +4880,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.IfNoneMatch.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x40000000L) != 0) + if ((_bits & 0x400000000L) != 0) { - _bits &= ~0x40000000L; + _bits &= ~0x400000000L; _headers._IfNoneMatch = default(StringValues); return true; } @@ -4471,9 +4894,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { if (ReferenceEquals(HeaderNames.AcceptCharset, key)) { - if ((_bits & 0x100000L) != 0) + if ((_bits & 0x1000000L) != 0) { - _bits &= ~0x100000L; + _bits &= ~0x1000000L; _headers._AcceptCharset = default(StringValues); return true; } @@ -4491,9 +4914,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if (HeaderNames.AcceptCharset.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x100000L) != 0) + if ((_bits & 0x1000000L) != 0) { - _bits &= ~0x100000L; + _bits &= ~0x1000000L; _headers._AcceptCharset = default(StringValues); return true; } @@ -4514,9 +4937,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { if (ReferenceEquals(HeaderNames.AcceptEncoding, key)) { - if ((_bits & 0x200000L) != 0) + if ((_bits & 0x2000000L) != 0) { - _bits &= ~0x200000L; + _bits &= ~0x2000000L; _headers._AcceptEncoding = default(StringValues); return true; } @@ -4524,9 +4947,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.AcceptLanguage, key)) { - if ((_bits & 0x400000L) != 0) + if ((_bits & 0x4000000L) != 0) { - _bits &= ~0x400000L; + _bits &= ~0x4000000L; _headers._AcceptLanguage = default(StringValues); return true; } @@ -4535,9 +4958,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if (HeaderNames.AcceptEncoding.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x200000L) != 0) + if ((_bits & 0x2000000L) != 0) { - _bits &= ~0x200000L; + _bits &= ~0x2000000L; _headers._AcceptEncoding = default(StringValues); return true; } @@ -4545,9 +4968,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.AcceptLanguage.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x400000L) != 0) + if ((_bits & 0x4000000L) != 0) { - _bits &= ~0x400000L; + _bits &= ~0x4000000L; _headers._AcceptLanguage = default(StringValues); return true; } @@ -4634,9 +5057,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.IfModifiedSince, key)) { - if ((_bits & 0x20000000L) != 0) + if ((_bits & 0x200000000L) != 0) { - _bits &= ~0x20000000L; + _bits &= ~0x200000000L; _headers._IfModifiedSince = default(StringValues); return true; } @@ -4655,9 +5078,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.IfModifiedSince.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x20000000L) != 0) + if ((_bits & 0x200000000L) != 0) { - _bits &= ~0x20000000L; + _bits &= ~0x200000000L; _headers._IfModifiedSince = default(StringValues); return true; } @@ -4669,9 +5092,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { if (ReferenceEquals(HeaderNames.IfUnmodifiedSince, key)) { - if ((_bits & 0x100000000L) != 0) + if ((_bits & 0x1000000000L) != 0) { - _bits &= ~0x100000000L; + _bits &= ~0x1000000000L; _headers._IfUnmodifiedSince = default(StringValues); return true; } @@ -4679,9 +5102,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.ProxyAuthorization, key)) { - if ((_bits & 0x400000000L) != 0) + if ((_bits & 0x4000000000L) != 0) { - _bits &= ~0x400000000L; + _bits &= ~0x4000000000L; _headers._ProxyAuthorization = default(StringValues); return true; } @@ -4689,9 +5112,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.CorrelationContext, key)) { - if ((_bits & 0x80000000000L) != 0) + if ((_bits & 0x800000000000L) != 0) { - _bits &= ~0x80000000000L; + _bits &= ~0x800000000000L; _headers._CorrelationContext = default(StringValues); return true; } @@ -4700,9 +5123,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if (HeaderNames.IfUnmodifiedSince.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x100000000L) != 0) + if ((_bits & 0x1000000000L) != 0) { - _bits &= ~0x100000000L; + _bits &= ~0x1000000000L; _headers._IfUnmodifiedSince = default(StringValues); return true; } @@ -4710,9 +5133,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.ProxyAuthorization.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x400000000L) != 0) + if ((_bits & 0x4000000000L) != 0) { - _bits &= ~0x400000000L; + _bits &= ~0x4000000000L; _headers._ProxyAuthorization = default(StringValues); return true; } @@ -4720,9 +5143,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.CorrelationContext.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x80000000000L) != 0) + if ((_bits & 0x800000000000L) != 0) { - _bits &= ~0x80000000000L; + _bits &= ~0x800000000000L; _headers._CorrelationContext = default(StringValues); return true; } @@ -4734,9 +5157,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { if (ReferenceEquals(HeaderNames.UpgradeInsecureRequests, key)) { - if ((_bits & 0x20000000000L) != 0) + if ((_bits & 0x200000000000L) != 0) { - _bits &= ~0x20000000000L; + _bits &= ~0x200000000000L; _headers._UpgradeInsecureRequests = default(StringValues); return true; } @@ -4745,9 +5168,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if (HeaderNames.UpgradeInsecureRequests.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x20000000000L) != 0) + if ((_bits & 0x200000000000L) != 0) { - _bits &= ~0x20000000000L; + _bits &= ~0x200000000000L; _headers._UpgradeInsecureRequests = default(StringValues); return true; } @@ -4759,9 +5182,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { if (ReferenceEquals(HeaderNames.AccessControlRequestMethod, key)) { - if ((_bits & 0x800000000000L) != 0) + if ((_bits & 0x8000000000000L) != 0) { - _bits &= ~0x800000000000L; + _bits &= ~0x8000000000000L; _headers._AccessControlRequestMethod = default(StringValues); return true; } @@ -4770,9 +5193,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if (HeaderNames.AccessControlRequestMethod.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x800000000000L) != 0) + if ((_bits & 0x8000000000000L) != 0) { - _bits &= ~0x800000000000L; + _bits &= ~0x8000000000000L; _headers._AccessControlRequestMethod = default(StringValues); return true; } @@ -4784,9 +5207,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { if (ReferenceEquals(HeaderNames.AccessControlRequestHeaders, key)) { - if ((_bits & 0x1000000000000L) != 0) + if ((_bits & 0x10000000000000L) != 0) { - _bits &= ~0x1000000000000L; + _bits &= ~0x10000000000000L; _headers._AccessControlRequestHeaders = default(StringValues); return true; } @@ -4795,9 +5218,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if (HeaderNames.AccessControlRequestHeaders.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x1000000000000L) != 0) + if ((_bits & 0x10000000000000L) != 0) { - _bits &= ~0x1000000000000L; + _bits &= ~0x10000000000000L; _headers._AccessControlRequestHeaders = default(StringValues); return true; } @@ -4825,7 +5248,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if ((tempBits & 0x80000L) != 0) { - _headers._Accept = default; + _headers._Authority = default; if((tempBits & ~0x80000L) == 0) { return; @@ -4833,24 +5256,64 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http tempBits &= ~0x80000L; } - if ((tempBits & 0x8000000L) != 0) + if ((tempBits & 0x100000L) != 0) { - _headers._Host = default; - if((tempBits & ~0x8000000L) == 0) + _headers._Method = default; + if((tempBits & ~0x100000L) == 0) { return; } - tempBits &= ~0x8000000L; + tempBits &= ~0x100000L; } - if ((tempBits & 0x8000000000L) != 0) + if ((tempBits & 0x200000L) != 0) { - _headers._UserAgent = default; - if((tempBits & ~0x8000000000L) == 0) + _headers._Path = default; + if((tempBits & ~0x200000L) == 0) { return; } - tempBits &= ~0x8000000000L; + tempBits &= ~0x200000L; + } + + if ((tempBits & 0x400000L) != 0) + { + _headers._Scheme = default; + if((tempBits & ~0x400000L) == 0) + { + return; + } + tempBits &= ~0x400000L; + } + + if ((tempBits & 0x800000L) != 0) + { + _headers._Accept = default; + if((tempBits & ~0x800000L) == 0) + { + return; + } + tempBits &= ~0x800000L; + } + + if ((tempBits & 0x80000000L) != 0) + { + _headers._Host = default; + if((tempBits & ~0x80000000L) == 0) + { + return; + } + tempBits &= ~0x80000000L; + } + + if ((tempBits & 0x80000000000L) != 0) + { + _headers._UserAgent = default; + if((tempBits & ~0x80000000000L) == 0) + { + return; + } + tempBits &= ~0x80000000000L; } if ((tempBits & 0x1L) != 0) @@ -5033,49 +5496,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http tempBits &= ~0x40000L; } - if ((tempBits & 0x100000L) != 0) - { - _headers._AcceptCharset = default; - if((tempBits & ~0x100000L) == 0) - { - return; - } - tempBits &= ~0x100000L; - } - - if ((tempBits & 0x200000L) != 0) - { - _headers._AcceptEncoding = default; - if((tempBits & ~0x200000L) == 0) - { - return; - } - tempBits &= ~0x200000L; - } - - if ((tempBits & 0x400000L) != 0) - { - _headers._AcceptLanguage = default; - if((tempBits & ~0x400000L) == 0) - { - return; - } - tempBits &= ~0x400000L; - } - - if ((tempBits & 0x800000L) != 0) - { - _headers._Authorization = default; - if((tempBits & ~0x800000L) == 0) - { - return; - } - tempBits &= ~0x800000L; - } - if ((tempBits & 0x1000000L) != 0) { - _headers._Cookie = default; + _headers._AcceptCharset = default; if((tempBits & ~0x1000000L) == 0) { return; @@ -5085,7 +5508,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if ((tempBits & 0x2000000L) != 0) { - _headers._Expect = default; + _headers._AcceptEncoding = default; if((tempBits & ~0x2000000L) == 0) { return; @@ -5095,7 +5518,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if ((tempBits & 0x4000000L) != 0) { - _headers._From = default; + _headers._AcceptLanguage = default; if((tempBits & ~0x4000000L) == 0) { return; @@ -5103,9 +5526,19 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http tempBits &= ~0x4000000L; } + if ((tempBits & 0x8000000L) != 0) + { + _headers._Authorization = default; + if((tempBits & ~0x8000000L) == 0) + { + return; + } + tempBits &= ~0x8000000L; + } + if ((tempBits & 0x10000000L) != 0) { - _headers._IfMatch = default; + _headers._Cookie = default; if((tempBits & ~0x10000000L) == 0) { return; @@ -5115,7 +5548,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if ((tempBits & 0x20000000L) != 0) { - _headers._IfModifiedSince = default; + _headers._Expect = default; if((tempBits & ~0x20000000L) == 0) { return; @@ -5125,7 +5558,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if ((tempBits & 0x40000000L) != 0) { - _headers._IfNoneMatch = default; + _headers._From = default; if((tempBits & ~0x40000000L) == 0) { return; @@ -5133,19 +5566,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http tempBits &= ~0x40000000L; } - if ((tempBits & 0x80000000L) != 0) - { - _headers._IfRange = default; - if((tempBits & ~0x80000000L) == 0) - { - return; - } - tempBits &= ~0x80000000L; - } - if ((tempBits & 0x100000000L) != 0) { - _headers._IfUnmodifiedSince = default; + _headers._IfMatch = default; if((tempBits & ~0x100000000L) == 0) { return; @@ -5155,7 +5578,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if ((tempBits & 0x200000000L) != 0) { - _headers._MaxForwards = default; + _headers._IfModifiedSince = default; if((tempBits & ~0x200000000L) == 0) { return; @@ -5165,7 +5588,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if ((tempBits & 0x400000000L) != 0) { - _headers._ProxyAuthorization = default; + _headers._IfNoneMatch = default; if((tempBits & ~0x400000000L) == 0) { return; @@ -5175,7 +5598,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if ((tempBits & 0x800000000L) != 0) { - _headers._Referer = default; + _headers._IfRange = default; if((tempBits & ~0x800000000L) == 0) { return; @@ -5185,7 +5608,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if ((tempBits & 0x1000000000L) != 0) { - _headers._Range = default; + _headers._IfUnmodifiedSince = default; if((tempBits & ~0x1000000000L) == 0) { return; @@ -5195,7 +5618,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if ((tempBits & 0x2000000000L) != 0) { - _headers._TE = default; + _headers._MaxForwards = default; if((tempBits & ~0x2000000000L) == 0) { return; @@ -5205,7 +5628,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if ((tempBits & 0x4000000000L) != 0) { - _headers._Translate = default; + _headers._ProxyAuthorization = default; if((tempBits & ~0x4000000000L) == 0) { return; @@ -5213,9 +5636,19 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http tempBits &= ~0x4000000000L; } + if ((tempBits & 0x8000000000L) != 0) + { + _headers._Referer = default; + if((tempBits & ~0x8000000000L) == 0) + { + return; + } + tempBits &= ~0x8000000000L; + } + if ((tempBits & 0x10000000000L) != 0) { - _headers._DNT = default; + _headers._Range = default; if((tempBits & ~0x10000000000L) == 0) { return; @@ -5225,7 +5658,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if ((tempBits & 0x20000000000L) != 0) { - _headers._UpgradeInsecureRequests = default; + _headers._TE = default; if((tempBits & ~0x20000000000L) == 0) { return; @@ -5235,7 +5668,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if ((tempBits & 0x40000000000L) != 0) { - _headers._RequestId = default; + _headers._Translate = default; if((tempBits & ~0x40000000000L) == 0) { return; @@ -5243,19 +5676,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http tempBits &= ~0x40000000000L; } - if ((tempBits & 0x80000000000L) != 0) - { - _headers._CorrelationContext = default; - if((tempBits & ~0x80000000000L) == 0) - { - return; - } - tempBits &= ~0x80000000000L; - } - if ((tempBits & 0x100000000000L) != 0) { - _headers._TraceParent = default; + _headers._DNT = default; if((tempBits & ~0x100000000000L) == 0) { return; @@ -5265,7 +5688,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if ((tempBits & 0x200000000000L) != 0) { - _headers._TraceState = default; + _headers._UpgradeInsecureRequests = default; if((tempBits & ~0x200000000000L) == 0) { return; @@ -5275,7 +5698,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if ((tempBits & 0x400000000000L) != 0) { - _headers._Origin = default; + _headers._RequestId = default; if((tempBits & ~0x400000000000L) == 0) { return; @@ -5285,7 +5708,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if ((tempBits & 0x800000000000L) != 0) { - _headers._AccessControlRequestMethod = default; + _headers._CorrelationContext = default; if((tempBits & ~0x800000000000L) == 0) { return; @@ -5295,7 +5718,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if ((tempBits & 0x1000000000000L) != 0) { - _headers._AccessControlRequestHeaders = default; + _headers._TraceParent = default; if((tempBits & ~0x1000000000000L) == 0) { return; @@ -5303,6 +5726,46 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http tempBits &= ~0x1000000000000L; } + if ((tempBits & 0x2000000000000L) != 0) + { + _headers._TraceState = default; + if((tempBits & ~0x2000000000000L) == 0) + { + return; + } + tempBits &= ~0x2000000000000L; + } + + if ((tempBits & 0x4000000000000L) != 0) + { + _headers._Origin = default; + if((tempBits & ~0x4000000000000L) == 0) + { + return; + } + tempBits &= ~0x4000000000000L; + } + + if ((tempBits & 0x8000000000000L) != 0) + { + _headers._AccessControlRequestMethod = default; + if((tempBits & ~0x8000000000000L) == 0) + { + return; + } + tempBits &= ~0x8000000000000L; + } + + if ((tempBits & 0x10000000000000L) != 0) + { + _headers._AccessControlRequestHeaders = default; + if((tempBits & ~0x10000000000000L) == 0) + { + return; + } + tempBits &= ~0x10000000000000L; + } + } protected override bool CopyToFast(KeyValuePair[] array, int arrayIndex) @@ -5489,7 +5952,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { return false; } - array[arrayIndex] = new KeyValuePair(HeaderNames.Accept, _headers._Accept); + array[arrayIndex] = new KeyValuePair(HeaderNames.Authority, _headers._Authority); ++arrayIndex; } if ((_bits & 0x100000L) != 0) @@ -5498,7 +5961,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { return false; } - array[arrayIndex] = new KeyValuePair(HeaderNames.AcceptCharset, _headers._AcceptCharset); + array[arrayIndex] = new KeyValuePair(HeaderNames.Method, _headers._Method); ++arrayIndex; } if ((_bits & 0x200000L) != 0) @@ -5507,7 +5970,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { return false; } - array[arrayIndex] = new KeyValuePair(HeaderNames.AcceptEncoding, _headers._AcceptEncoding); + array[arrayIndex] = new KeyValuePair(HeaderNames.Path, _headers._Path); ++arrayIndex; } if ((_bits & 0x400000L) != 0) @@ -5516,7 +5979,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { return false; } - array[arrayIndex] = new KeyValuePair(HeaderNames.AcceptLanguage, _headers._AcceptLanguage); + array[arrayIndex] = new KeyValuePair(HeaderNames.Scheme, _headers._Scheme); ++arrayIndex; } if ((_bits & 0x800000L) != 0) @@ -5525,7 +5988,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { return false; } - array[arrayIndex] = new KeyValuePair(HeaderNames.Authorization, _headers._Authorization); + array[arrayIndex] = new KeyValuePair(HeaderNames.Accept, _headers._Accept); ++arrayIndex; } if ((_bits & 0x1000000L) != 0) @@ -5534,7 +5997,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { return false; } - array[arrayIndex] = new KeyValuePair(HeaderNames.Cookie, _headers._Cookie); + array[arrayIndex] = new KeyValuePair(HeaderNames.AcceptCharset, _headers._AcceptCharset); ++arrayIndex; } if ((_bits & 0x2000000L) != 0) @@ -5543,7 +6006,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { return false; } - array[arrayIndex] = new KeyValuePair(HeaderNames.Expect, _headers._Expect); + array[arrayIndex] = new KeyValuePair(HeaderNames.AcceptEncoding, _headers._AcceptEncoding); ++arrayIndex; } if ((_bits & 0x4000000L) != 0) @@ -5552,7 +6015,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { return false; } - array[arrayIndex] = new KeyValuePair(HeaderNames.From, _headers._From); + array[arrayIndex] = new KeyValuePair(HeaderNames.AcceptLanguage, _headers._AcceptLanguage); ++arrayIndex; } if ((_bits & 0x8000000L) != 0) @@ -5561,7 +6024,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { return false; } - array[arrayIndex] = new KeyValuePair(HeaderNames.Host, _headers._Host); + array[arrayIndex] = new KeyValuePair(HeaderNames.Authorization, _headers._Authorization); ++arrayIndex; } if ((_bits & 0x10000000L) != 0) @@ -5570,7 +6033,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { return false; } - array[arrayIndex] = new KeyValuePair(HeaderNames.IfMatch, _headers._IfMatch); + array[arrayIndex] = new KeyValuePair(HeaderNames.Cookie, _headers._Cookie); ++arrayIndex; } if ((_bits & 0x20000000L) != 0) @@ -5579,7 +6042,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { return false; } - array[arrayIndex] = new KeyValuePair(HeaderNames.IfModifiedSince, _headers._IfModifiedSince); + array[arrayIndex] = new KeyValuePair(HeaderNames.Expect, _headers._Expect); ++arrayIndex; } if ((_bits & 0x40000000L) != 0) @@ -5588,7 +6051,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { return false; } - array[arrayIndex] = new KeyValuePair(HeaderNames.IfNoneMatch, _headers._IfNoneMatch); + array[arrayIndex] = new KeyValuePair(HeaderNames.From, _headers._From); ++arrayIndex; } if ((_bits & 0x80000000L) != 0) @@ -5597,7 +6060,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { return false; } - array[arrayIndex] = new KeyValuePair(HeaderNames.IfRange, _headers._IfRange); + array[arrayIndex] = new KeyValuePair(HeaderNames.Host, _headers._Host); ++arrayIndex; } if ((_bits & 0x100000000L) != 0) @@ -5606,7 +6069,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { return false; } - array[arrayIndex] = new KeyValuePair(HeaderNames.IfUnmodifiedSince, _headers._IfUnmodifiedSince); + array[arrayIndex] = new KeyValuePair(HeaderNames.IfMatch, _headers._IfMatch); ++arrayIndex; } if ((_bits & 0x200000000L) != 0) @@ -5615,7 +6078,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { return false; } - array[arrayIndex] = new KeyValuePair(HeaderNames.MaxForwards, _headers._MaxForwards); + array[arrayIndex] = new KeyValuePair(HeaderNames.IfModifiedSince, _headers._IfModifiedSince); ++arrayIndex; } if ((_bits & 0x400000000L) != 0) @@ -5624,7 +6087,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { return false; } - array[arrayIndex] = new KeyValuePair(HeaderNames.ProxyAuthorization, _headers._ProxyAuthorization); + array[arrayIndex] = new KeyValuePair(HeaderNames.IfNoneMatch, _headers._IfNoneMatch); ++arrayIndex; } if ((_bits & 0x800000000L) != 0) @@ -5633,7 +6096,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { return false; } - array[arrayIndex] = new KeyValuePair(HeaderNames.Referer, _headers._Referer); + array[arrayIndex] = new KeyValuePair(HeaderNames.IfRange, _headers._IfRange); ++arrayIndex; } if ((_bits & 0x1000000000L) != 0) @@ -5642,7 +6105,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { return false; } - array[arrayIndex] = new KeyValuePair(HeaderNames.Range, _headers._Range); + array[arrayIndex] = new KeyValuePair(HeaderNames.IfUnmodifiedSince, _headers._IfUnmodifiedSince); ++arrayIndex; } if ((_bits & 0x2000000000L) != 0) @@ -5651,7 +6114,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { return false; } - array[arrayIndex] = new KeyValuePair(HeaderNames.TE, _headers._TE); + array[arrayIndex] = new KeyValuePair(HeaderNames.MaxForwards, _headers._MaxForwards); ++arrayIndex; } if ((_bits & 0x4000000000L) != 0) @@ -5660,7 +6123,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { return false; } - array[arrayIndex] = new KeyValuePair(HeaderNames.Translate, _headers._Translate); + array[arrayIndex] = new KeyValuePair(HeaderNames.ProxyAuthorization, _headers._ProxyAuthorization); ++arrayIndex; } if ((_bits & 0x8000000000L) != 0) @@ -5669,7 +6132,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { return false; } - array[arrayIndex] = new KeyValuePair(HeaderNames.UserAgent, _headers._UserAgent); + array[arrayIndex] = new KeyValuePair(HeaderNames.Referer, _headers._Referer); ++arrayIndex; } if ((_bits & 0x10000000000L) != 0) @@ -5678,7 +6141,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { return false; } - array[arrayIndex] = new KeyValuePair(HeaderNames.DNT, _headers._DNT); + array[arrayIndex] = new KeyValuePair(HeaderNames.Range, _headers._Range); ++arrayIndex; } if ((_bits & 0x20000000000L) != 0) @@ -5687,7 +6150,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { return false; } - array[arrayIndex] = new KeyValuePair(HeaderNames.UpgradeInsecureRequests, _headers._UpgradeInsecureRequests); + array[arrayIndex] = new KeyValuePair(HeaderNames.TE, _headers._TE); ++arrayIndex; } if ((_bits & 0x40000000000L) != 0) @@ -5696,7 +6159,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { return false; } - array[arrayIndex] = new KeyValuePair(HeaderNames.RequestId, _headers._RequestId); + array[arrayIndex] = new KeyValuePair(HeaderNames.Translate, _headers._Translate); ++arrayIndex; } if ((_bits & 0x80000000000L) != 0) @@ -5705,7 +6168,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { return false; } - array[arrayIndex] = new KeyValuePair(HeaderNames.CorrelationContext, _headers._CorrelationContext); + array[arrayIndex] = new KeyValuePair(HeaderNames.UserAgent, _headers._UserAgent); ++arrayIndex; } if ((_bits & 0x100000000000L) != 0) @@ -5714,7 +6177,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { return false; } - array[arrayIndex] = new KeyValuePair(HeaderNames.TraceParent, _headers._TraceParent); + array[arrayIndex] = new KeyValuePair(HeaderNames.DNT, _headers._DNT); ++arrayIndex; } if ((_bits & 0x200000000000L) != 0) @@ -5723,7 +6186,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { return false; } - array[arrayIndex] = new KeyValuePair(HeaderNames.TraceState, _headers._TraceState); + array[arrayIndex] = new KeyValuePair(HeaderNames.UpgradeInsecureRequests, _headers._UpgradeInsecureRequests); ++arrayIndex; } if ((_bits & 0x400000000000L) != 0) @@ -5732,7 +6195,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { return false; } - array[arrayIndex] = new KeyValuePair(HeaderNames.Origin, _headers._Origin); + array[arrayIndex] = new KeyValuePair(HeaderNames.RequestId, _headers._RequestId); ++arrayIndex; } if ((_bits & 0x800000000000L) != 0) @@ -5741,10 +6204,46 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { return false; } - array[arrayIndex] = new KeyValuePair(HeaderNames.AccessControlRequestMethod, _headers._AccessControlRequestMethod); + array[arrayIndex] = new KeyValuePair(HeaderNames.CorrelationContext, _headers._CorrelationContext); ++arrayIndex; } if ((_bits & 0x1000000000000L) != 0) + { + if (arrayIndex == array.Length) + { + return false; + } + array[arrayIndex] = new KeyValuePair(HeaderNames.TraceParent, _headers._TraceParent); + ++arrayIndex; + } + if ((_bits & 0x2000000000000L) != 0) + { + if (arrayIndex == array.Length) + { + return false; + } + array[arrayIndex] = new KeyValuePair(HeaderNames.TraceState, _headers._TraceState); + ++arrayIndex; + } + if ((_bits & 0x4000000000000L) != 0) + { + if (arrayIndex == array.Length) + { + return false; + } + array[arrayIndex] = new KeyValuePair(HeaderNames.Origin, _headers._Origin); + ++arrayIndex; + } + if ((_bits & 0x8000000000000L) != 0) + { + if (arrayIndex == array.Length) + { + return false; + } + array[arrayIndex] = new KeyValuePair(HeaderNames.AccessControlRequestMethod, _headers._AccessControlRequestMethod); + ++arrayIndex; + } + if ((_bits & 0x10000000000000L) != 0) { if (arrayIndex == array.Length) { @@ -5768,7 +6267,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } [MethodImpl(MethodImplOptions.AggressiveOptimization)] - public unsafe void Append(Span name, Span value) + public unsafe void Append(ReadOnlySpan name, ReadOnlySpan value) { ref byte nameStart = ref MemoryMarshal.GetReference(name); ref StringValues values = ref Unsafe.AsRef(null); @@ -5780,7 +6279,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http case 2: if (((Unsafe.ReadUnaligned(ref nameStart) & 0xdfdfu) == 0x4554u)) { - flag = 0x2000000000L; + flag = 0x20000000000L; values = ref _headers._TE; } break; @@ -5788,7 +6287,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http var firstTerm3 = (Unsafe.ReadUnaligned(ref nameStart) & 0xdfdfu); if ((firstTerm3 == 0x4e44u) && ((Unsafe.AddByteOffset(ref nameStart, (IntPtr)2) & 0xdfu) == 0x54u)) { - flag = 0x10000000000L; + flag = 0x100000000000L; values = ref _headers._DNT; } else if ((firstTerm3 == 0x4956u) && ((Unsafe.AddByteOffset(ref nameStart, (IntPtr)2) & 0xdfu) == 0x41u)) @@ -5801,7 +6300,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http var firstTerm4 = (Unsafe.ReadUnaligned(ref nameStart) & 0xdfdfdfdfu); if ((firstTerm4 == 0x54534f48u)) { - flag = 0x8000000L; + flag = 0x80000000L; values = ref _headers._Host; } else if ((firstTerm4 == 0x45544144u)) @@ -5811,20 +6310,24 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } else if ((firstTerm4 == 0x4d4f5246u)) { - flag = 0x4000000L; + flag = 0x40000000L; values = ref _headers._From; } break; case 5: - var firstTerm5 = (Unsafe.ReadUnaligned(ref nameStart) & 0xdfdfdfdfu); - if ((firstTerm5 == 0x4f4c4c41u) && ((Unsafe.AddByteOffset(ref nameStart, (IntPtr)4) & 0xdfu) == 0x57u)) + if (((Unsafe.ReadUnaligned(ref nameStart) & 0xdfdfdfffu) == 0x5441503au) && ((Unsafe.AddByteOffset(ref nameStart, (IntPtr)4) & 0xdfu) == 0x48u)) + { + flag = 0x200000L; + values = ref _headers._Path; + } + else if (((Unsafe.ReadUnaligned(ref nameStart) & 0xdfdfdfdfu) == 0x4f4c4c41u) && ((Unsafe.AddByteOffset(ref nameStart, (IntPtr)4) & 0xdfu) == 0x57u)) { flag = 0x400L; values = ref _headers._Allow; } - else if ((firstTerm5 == 0x474e4152u) && ((Unsafe.AddByteOffset(ref nameStart, (IntPtr)4) & 0xdfu) == 0x45u)) + else if (((Unsafe.ReadUnaligned(ref nameStart) & 0xdfdfdfdfu) == 0x474e4152u) && ((Unsafe.AddByteOffset(ref nameStart, (IntPtr)4) & 0xdfu) == 0x45u)) { - flag = 0x1000000000L; + flag = 0x10000000000L; values = ref _headers._Range; } break; @@ -5832,22 +6335,22 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http var firstTerm6 = (Unsafe.ReadUnaligned(ref nameStart) & 0xdfdfdfdfu); if ((firstTerm6 == 0x45434341u) && ((Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)(2 * sizeof(ushort)))) & 0xdfdfu) == 0x5450u)) { - flag = 0x80000L; + flag = 0x800000L; values = ref _headers._Accept; } else if ((firstTerm6 == 0x4b4f4f43u) && ((Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)(2 * sizeof(ushort)))) & 0xdfdfu) == 0x4549u)) { - flag = 0x1000000L; + flag = 0x10000000L; values = ref _headers._Cookie; } else if ((firstTerm6 == 0x45505845u) && ((Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)(2 * sizeof(ushort)))) & 0xdfdfu) == 0x5443u)) { - flag = 0x2000000L; + flag = 0x20000000L; values = ref _headers._Expect; } else if ((firstTerm6 == 0x4749524fu) && ((Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)(2 * sizeof(ushort)))) & 0xdfdfu) == 0x4e49u)) { - flag = 0x400000000000L; + flag = 0x4000000000000L; values = ref _headers._Origin; } else if ((firstTerm6 == 0x47415250u) && ((Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)(2 * sizeof(ushort)))) & 0xdfdfu) == 0x414du)) @@ -5857,28 +6360,37 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } break; case 7: - var firstTerm7 = (Unsafe.ReadUnaligned(ref nameStart) & 0xdfdfdfdfu); - if ((firstTerm7 == 0x49505845u) && ((Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)(2 * sizeof(ushort)))) & 0xdfdfu) == 0x4552u) && ((Unsafe.AddByteOffset(ref nameStart, (IntPtr)6) & 0xdfu) == 0x53u)) + if (((Unsafe.ReadUnaligned(ref nameStart) & 0xdfdfdfffu) == 0x54454d3au) && ((Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)(2 * sizeof(ushort)))) & 0xdfdfu) == 0x4f48u) && ((Unsafe.AddByteOffset(ref nameStart, (IntPtr)6) & 0xdfu) == 0x44u)) + { + flag = 0x100000L; + values = ref _headers._Method; + } + else if (((Unsafe.ReadUnaligned(ref nameStart) & 0xdfdfdfffu) == 0x4843533au) && ((Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)(2 * sizeof(ushort)))) & 0xdfdfu) == 0x4d45u) && ((Unsafe.AddByteOffset(ref nameStart, (IntPtr)6) & 0xdfu) == 0x45u)) + { + flag = 0x400000L; + values = ref _headers._Scheme; + } + else if (((Unsafe.ReadUnaligned(ref nameStart) & 0xdfdfdfdfu) == 0x49505845u) && ((Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)(2 * sizeof(ushort)))) & 0xdfdfu) == 0x4552u) && ((Unsafe.AddByteOffset(ref nameStart, (IntPtr)6) & 0xdfu) == 0x53u)) { flag = 0x20000L; values = ref _headers._Expires; } - else if ((firstTerm7 == 0x45464552u) && ((Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)(2 * sizeof(ushort)))) & 0xdfdfu) == 0x4552u) && ((Unsafe.AddByteOffset(ref nameStart, (IntPtr)6) & 0xdfu) == 0x52u)) + else if (((Unsafe.ReadUnaligned(ref nameStart) & 0xdfdfdfdfu) == 0x45464552u) && ((Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)(2 * sizeof(ushort)))) & 0xdfdfu) == 0x4552u) && ((Unsafe.AddByteOffset(ref nameStart, (IntPtr)6) & 0xdfu) == 0x52u)) { - flag = 0x800000000L; + flag = 0x8000000000L; values = ref _headers._Referer; } - else if ((firstTerm7 == 0x49415254u) && ((Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)(2 * sizeof(ushort)))) & 0xdfdfu) == 0x454cu) && ((Unsafe.AddByteOffset(ref nameStart, (IntPtr)6) & 0xdfu) == 0x52u)) + else if (((Unsafe.ReadUnaligned(ref nameStart) & 0xdfdfdfdfu) == 0x49415254u) && ((Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)(2 * sizeof(ushort)))) & 0xdfdfu) == 0x454cu) && ((Unsafe.AddByteOffset(ref nameStart, (IntPtr)6) & 0xdfu) == 0x52u)) { flag = 0x20L; values = ref _headers._Trailer; } - else if ((firstTerm7 == 0x52475055u) && ((Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)(2 * sizeof(ushort)))) & 0xdfdfu) == 0x4441u) && ((Unsafe.AddByteOffset(ref nameStart, (IntPtr)6) & 0xdfu) == 0x45u)) + else if (((Unsafe.ReadUnaligned(ref nameStart) & 0xdfdfdfdfu) == 0x52475055u) && ((Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)(2 * sizeof(ushort)))) & 0xdfdfu) == 0x4441u) && ((Unsafe.AddByteOffset(ref nameStart, (IntPtr)6) & 0xdfu) == 0x45u)) { flag = 0x80L; values = ref _headers._Upgrade; } - else if ((firstTerm7 == 0x4e524157u) && ((Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)(2 * sizeof(ushort)))) & 0xdfdfu) == 0x4e49u) && ((Unsafe.AddByteOffset(ref nameStart, (IntPtr)6) & 0xdfu) == 0x47u)) + else if (((Unsafe.ReadUnaligned(ref nameStart) & 0xdfdfdfdfu) == 0x4e524157u) && ((Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)(2 * sizeof(ushort)))) & 0xdfdfu) == 0x4e49u) && ((Unsafe.AddByteOffset(ref nameStart, (IntPtr)6) & 0xdfu) == 0x47u)) { flag = 0x200L; values = ref _headers._Warning; @@ -5888,31 +6400,36 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http var firstTerm8 = (Unsafe.ReadUnaligned(ref nameStart) & 0xdfdfdfdfdfffdfdfuL); if ((firstTerm8 == 0x484354414d2d4649uL)) { - flag = 0x10000000L; + flag = 0x100000000L; values = ref _headers._IfMatch; } else if ((firstTerm8 == 0x45474e41522d4649uL)) { - flag = 0x80000000L; + flag = 0x800000000L; values = ref _headers._IfRange; } break; case 9: if (((Unsafe.ReadUnaligned(ref nameStart) & 0xdfdfdfdfdfdfdfdfuL) == 0x54414c534e415254uL) && ((Unsafe.AddByteOffset(ref nameStart, (IntPtr)8) & 0xdfu) == 0x45u)) { - flag = 0x4000000000L; + flag = 0x40000000000L; values = ref _headers._Translate; } break; case 10: - if (((Unsafe.ReadUnaligned(ref nameStart) & 0xdfdfdfdfdfdfdfdfuL) == 0x495443454e4e4f43uL) && ((Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)(4 * sizeof(ushort)))) & 0xdfdfu) == 0x4e4fu)) + if (((Unsafe.ReadUnaligned(ref nameStart) & 0xdfdfdfdfdfdfdfffuL) == 0x49524f485455413auL) && ((Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)(4 * sizeof(ushort)))) & 0xdfdfu) == 0x5954u)) + { + flag = 0x80000L; + values = ref _headers._Authority; + } + else if (((Unsafe.ReadUnaligned(ref nameStart) & 0xdfdfdfdfdfdfdfdfuL) == 0x495443454e4e4f43uL) && ((Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)(4 * sizeof(ushort)))) & 0xdfdfu) == 0x4e4fu)) { flag = 0x2L; values = ref _headers._Connection; } else if (((Unsafe.ReadUnaligned(ref nameStart) & 0xdfdfdfffdfdfdfdfuL) == 0x4547412d52455355uL) && ((Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)(4 * sizeof(ushort)))) & 0xdfdfu) == 0x544eu)) { - flag = 0x8000000000L; + flag = 0x80000000000L; values = ref _headers._UserAgent; } else if (((Unsafe.ReadUnaligned(ref nameStart) & 0xdfdfdfffdfdfdfdfuL) == 0x494c412d5045454buL) && ((Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)(4 * sizeof(ushort)))) & 0xdfdfu) == 0x4556u)) @@ -5922,12 +6439,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } else if (((Unsafe.ReadUnaligned(ref nameStart) & 0xffdfdfdfdfdfdfdfuL) == 0x2d54534555514552uL) && ((Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)(4 * sizeof(ushort)))) & 0xdfdfu) == 0x4449u)) { - flag = 0x40000000000L; + flag = 0x400000000000L; values = ref _headers._RequestId; } else if (((Unsafe.ReadUnaligned(ref nameStart) & 0xdfdfdfdfdfdfdfdfuL) == 0x4154534543415254uL) && ((Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)(4 * sizeof(ushort)))) & 0xdfdfu) == 0x4554u)) { - flag = 0x200000000000L; + flag = 0x2000000000000L; values = ref _headers._TraceState; } break; @@ -5939,7 +6456,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } else if (((Unsafe.ReadUnaligned(ref nameStart) & 0xdfdfdfdfdfdfdfdfuL) == 0x5241504543415254uL) && ((Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)(4 * sizeof(ushort)))) & 0xdfdfu) == 0x4e45u) && ((Unsafe.AddByteOffset(ref nameStart, (IntPtr)10) & 0xdfu) == 0x54u)) { - flag = 0x100000000000L; + flag = 0x1000000000000L; values = ref _headers._TraceParent; } break; @@ -5951,14 +6468,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } else if (((Unsafe.ReadUnaligned(ref nameStart) & 0xdfdfdfdfffdfdfdfuL) == 0x57524f462d58414duL) && ((Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)(2 * sizeof(uint)))) & 0xdfdfdfdfu) == 0x53445241u)) { - flag = 0x200000000L; + flag = 0x2000000000L; values = ref _headers._MaxForwards; } break; case 13: if (((Unsafe.ReadUnaligned(ref nameStart) & 0xdfdfdfdfdfdfdfdfuL) == 0x5a49524f48545541uL) && ((Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)(2 * sizeof(uint)))) & 0xdfdfdfdfu) == 0x4f495441u) && ((Unsafe.AddByteOffset(ref nameStart, (IntPtr)12) & 0xdfu) == 0x4eu)) { - flag = 0x800000L; + flag = 0x8000000L; values = ref _headers._Authorization; } else if (((Unsafe.ReadUnaligned(ref nameStart) & 0xdfdfffdfdfdfdfdfuL) == 0x4f432d4548434143uL) && ((Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)(2 * sizeof(uint)))) & 0xdfdfdfdfu) == 0x4f52544eu) && ((Unsafe.AddByteOffset(ref nameStart, (IntPtr)12) & 0xdfu) == 0x4cu)) @@ -5973,7 +6490,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } else if (((Unsafe.ReadUnaligned(ref nameStart) & 0xffdfdfdfdfffdfdfuL) == 0x2d454e4f4e2d4649uL) && ((Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)(2 * sizeof(uint)))) & 0xdfdfdfdfu) == 0x4354414du) && ((Unsafe.AddByteOffset(ref nameStart, (IntPtr)12) & 0xdfu) == 0x48u)) { - flag = 0x40000000L; + flag = 0x400000000L; values = ref _headers._IfNoneMatch; } else if (((Unsafe.ReadUnaligned(ref nameStart) & 0xdfdfdfffdfdfdfdfuL) == 0x444f4d2d5453414cuL) && ((Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)(2 * sizeof(uint)))) & 0xdfdfdfdfu) == 0x45494649u) && ((Unsafe.AddByteOffset(ref nameStart, (IntPtr)12) & 0xdfu) == 0x44u)) @@ -5985,7 +6502,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http case 14: if (((Unsafe.ReadUnaligned(ref nameStart) & 0xdfffdfdfdfdfdfdfuL) == 0x432d545045434341uL) && ((Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)(2 * sizeof(uint)))) & 0xdfdfdfdfu) == 0x53524148u) && ((Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)(6 * sizeof(ushort)))) & 0xdfdfu) == 0x5445u)) { - flag = 0x100000L; + flag = 0x1000000L; values = ref _headers._AcceptCharset; } else if (((Unsafe.ReadUnaligned(ref nameStart) & 0xffdfdfdfdfdfdfdfuL) == 0x2d544e45544e4f43uL) && ((Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)(2 * sizeof(uint)))) & 0xdfdfdfdfu) == 0x474e454cu) && ((Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)(6 * sizeof(ushort)))) & 0xdfdfu) == 0x4854u)) @@ -5998,12 +6515,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http var firstTerm15 = (Unsafe.ReadUnaligned(ref nameStart) & 0xdfffdfdfdfdfdfdfuL); if ((firstTerm15 == 0x452d545045434341uL) && ((Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)(2 * sizeof(uint)))) & 0xdfdfdfdfu) == 0x444f434eu) && ((Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)(6 * sizeof(ushort)))) & 0xdfdfu) == 0x4e49u) && ((Unsafe.AddByteOffset(ref nameStart, (IntPtr)14) & 0xdfu) == 0x47u)) { - flag = 0x200000L; + flag = 0x2000000L; values = ref _headers._AcceptEncoding; } else if ((firstTerm15 == 0x4c2d545045434341uL) && ((Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)(2 * sizeof(uint)))) & 0xdfdfdfdfu) == 0x55474e41u) && ((Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)(6 * sizeof(ushort)))) & 0xdfdfu) == 0x4741u) && ((Unsafe.AddByteOffset(ref nameStart, (IntPtr)14) & 0xdfu) == 0x45u)) { - flag = 0x400000L; + flag = 0x4000000L; values = ref _headers._AcceptLanguage; } break; @@ -6031,7 +6548,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http case 17: if (((Unsafe.ReadUnaligned(ref nameStart) & 0xdfdfdfdfdfffdfdfuL) == 0x4649444f4d2d4649uL) && ((Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)sizeof(ulong))) & 0xdfdfdfdfffdfdfdfuL) == 0x434e49532d444549uL) && ((Unsafe.AddByteOffset(ref nameStart, (IntPtr)16) & 0xdfu) == 0x45u)) { - flag = 0x20000000L; + flag = 0x200000000L; values = ref _headers._IfModifiedSince; } else if (((Unsafe.ReadUnaligned(ref nameStart) & 0xdfdfdfdfdfdfdfdfuL) == 0x524546534e415254uL) && ((Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)sizeof(ulong))) & 0xdfdfdfdfdfdfdfffuL) == 0x4e49444f434e452duL) && ((Unsafe.AddByteOffset(ref nameStart, (IntPtr)16) & 0xdfu) == 0x47u)) @@ -6043,38 +6560,38 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http case 19: if (((Unsafe.ReadUnaligned(ref nameStart) & 0xdfdfdfdfdfdfdfdfuL) == 0x54414c4552524f43uL) && ((Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)sizeof(ulong))) & 0xdfdfdfdfffdfdfdfuL) == 0x544e4f432d4e4f49uL) && ((Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)(8 * sizeof(ushort)))) & 0xdfdfu) == 0x5845u) && ((Unsafe.AddByteOffset(ref nameStart, (IntPtr)18) & 0xdfu) == 0x54u)) { - flag = 0x80000000000L; + flag = 0x800000000000L; values = ref _headers._CorrelationContext; } else if (((Unsafe.ReadUnaligned(ref nameStart) & 0xdfdfdfdfdfffdfdfuL) == 0x444f4d4e552d4649uL) && ((Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)sizeof(ulong))) & 0xdfdfffdfdfdfdfdfuL) == 0x49532d4445494649uL) && ((Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)(8 * sizeof(ushort)))) & 0xdfdfu) == 0x434eu) && ((Unsafe.AddByteOffset(ref nameStart, (IntPtr)18) & 0xdfu) == 0x45u)) { - flag = 0x100000000L; + flag = 0x1000000000L; values = ref _headers._IfUnmodifiedSince; } else if (((Unsafe.ReadUnaligned(ref nameStart) & 0xdfdfffdfdfdfdfdfuL) == 0x55412d59584f5250uL) && ((Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)sizeof(ulong))) & 0xdfdfdfdfdfdfdfdfuL) == 0x54415a49524f4854uL) && ((Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)(8 * sizeof(ushort)))) & 0xdfdfu) == 0x4f49u) && ((Unsafe.AddByteOffset(ref nameStart, (IntPtr)18) & 0xdfu) == 0x4eu)) { - flag = 0x400000000L; + flag = 0x4000000000L; values = ref _headers._ProxyAuthorization; } break; case 25: if (((Unsafe.ReadUnaligned(ref nameStart) & 0xffdfdfdfdfdfdfdfuL) == 0x2d45444152475055uL) && ((Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)sizeof(ulong))) & 0xdfdfdfdfdfdfdfdfuL) == 0x4552554345534e49uL) && ((Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)(2 * sizeof(ulong)))) & 0xdfdfdfdfdfdfdfffuL) == 0x545345555145522duL) && ((Unsafe.AddByteOffset(ref nameStart, (IntPtr)24) & 0xdfu) == 0x53u)) { - flag = 0x20000000000L; + flag = 0x200000000000L; values = ref _headers._UpgradeInsecureRequests; } break; case 29: if (((Unsafe.ReadUnaligned(ref nameStart) & 0xdfffdfdfdfdfdfdfuL) == 0x432d535345434341uL) && ((Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)sizeof(ulong))) & 0xdfffdfdfdfdfdfdfuL) == 0x522d4c4f52544e4fuL) && ((Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)(2 * sizeof(ulong)))) & 0xdfffdfdfdfdfdfdfuL) == 0x4d2d545345555145uL) && ((Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)(6 * sizeof(uint)))) & 0xdfdfdfdfu) == 0x4f485445u) && ((Unsafe.AddByteOffset(ref nameStart, (IntPtr)28) & 0xdfu) == 0x44u)) { - flag = 0x800000000000L; + flag = 0x8000000000000L; values = ref _headers._AccessControlRequestMethod; } break; case 30: if (((Unsafe.ReadUnaligned(ref nameStart) & 0xdfffdfdfdfdfdfdfuL) == 0x432d535345434341uL) && ((Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)sizeof(ulong))) & 0xdfffdfdfdfdfdfdfuL) == 0x522d4c4f52544e4fuL) && ((Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)(2 * sizeof(ulong)))) & 0xdfffdfdfdfdfdfdfuL) == 0x482d545345555145uL) && ((Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)(6 * sizeof(uint)))) & 0xdfdfdfdfu) == 0x45444145u) && ((Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)(14 * sizeof(ushort)))) & 0xdfdfu) == 0x5352u)) { - flag = 0x1000000000000L; + flag = 0x10000000000000L; values = ref _headers._AccessControlRequestHeaders; } break; @@ -6105,7 +6622,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } // We didn't have a previous matching header value, or have already added a header, so get the string for this value. - var valueStr = value.GetRequestHeaderStringNonNullCharacters(_useLatin1); + var valueStr = value.GetRequestHeaderStringNonNullCharacters(UseLatin1); if ((_bits & flag) == 0) { // We didn't already have a header set, so add a new one. @@ -6123,7 +6640,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http // The header was not one of the "known" headers. // Convert value to string first, because passing two spans causes 8 bytes stack zeroing in // this method with rep stosd, which is slower than necessary. - var valueStr = value.GetRequestHeaderStringNonNullCharacters(_useLatin1); + var valueStr = value.GetRequestHeaderStringNonNullCharacters(UseLatin1); AppendUnknownHeaders(name, valueStr); } } @@ -6149,6 +6666,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http public StringValues _ContentRange; public StringValues _Expires; public StringValues _LastModified; + public StringValues _Authority; + public StringValues _Method; + public StringValues _Path; + public StringValues _Scheme; public StringValues _Accept; public StringValues _AcceptCharset; public StringValues _AcceptEncoding; @@ -6228,66 +6749,74 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http case 18: goto HeaderLastModified; case 19: - goto HeaderAccept; + goto HeaderAuthority; case 20: - goto HeaderAcceptCharset; + goto HeaderMethod; case 21: - goto HeaderAcceptEncoding; + goto HeaderPath; case 22: - goto HeaderAcceptLanguage; + goto HeaderScheme; case 23: - goto HeaderAuthorization; + goto HeaderAccept; case 24: - goto HeaderCookie; + goto HeaderAcceptCharset; case 25: - goto HeaderExpect; + goto HeaderAcceptEncoding; case 26: - goto HeaderFrom; + goto HeaderAcceptLanguage; case 27: - goto HeaderHost; + goto HeaderAuthorization; case 28: - goto HeaderIfMatch; + goto HeaderCookie; case 29: - goto HeaderIfModifiedSince; + goto HeaderExpect; case 30: - goto HeaderIfNoneMatch; + goto HeaderFrom; case 31: - goto HeaderIfRange; + goto HeaderHost; case 32: - goto HeaderIfUnmodifiedSince; + goto HeaderIfMatch; case 33: - goto HeaderMaxForwards; + goto HeaderIfModifiedSince; case 34: - goto HeaderProxyAuthorization; + goto HeaderIfNoneMatch; case 35: - goto HeaderReferer; + goto HeaderIfRange; case 36: - goto HeaderRange; + goto HeaderIfUnmodifiedSince; case 37: - goto HeaderTE; + goto HeaderMaxForwards; case 38: - goto HeaderTranslate; + goto HeaderProxyAuthorization; case 39: - goto HeaderUserAgent; + goto HeaderReferer; case 40: - goto HeaderDNT; + goto HeaderRange; case 41: - goto HeaderUpgradeInsecureRequests; + goto HeaderTE; case 42: - goto HeaderRequestId; + goto HeaderTranslate; case 43: - goto HeaderCorrelationContext; + goto HeaderUserAgent; case 44: - goto HeaderTraceParent; + goto HeaderDNT; case 45: - goto HeaderTraceState; + goto HeaderUpgradeInsecureRequests; case 46: - goto HeaderOrigin; + goto HeaderRequestId; case 47: - goto HeaderAccessControlRequestMethod; + goto HeaderCorrelationContext; case 48: - goto HeaderAccessControlRequestHeaders; + goto HeaderTraceParent; case 49: + goto HeaderTraceState; + case 50: + goto HeaderOrigin; + case 51: + goto HeaderAccessControlRequestMethod; + case 52: + goto HeaderAccessControlRequestHeaders; + case 53: goto HeaderContentLength; default: goto ExtraHeaders; @@ -6297,6 +6826,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if ((_bits & 0x1L) != 0) { _current = new KeyValuePair(HeaderNames.CacheControl, _collection._headers._CacheControl); + _currentKnownType = KnownHeaderType.CacheControl; _next = 1; return true; } @@ -6304,6 +6834,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if ((_bits & 0x2L) != 0) { _current = new KeyValuePair(HeaderNames.Connection, _collection._headers._Connection); + _currentKnownType = KnownHeaderType.Connection; _next = 2; return true; } @@ -6311,6 +6842,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if ((_bits & 0x4L) != 0) { _current = new KeyValuePair(HeaderNames.Date, _collection._headers._Date); + _currentKnownType = KnownHeaderType.Date; _next = 3; return true; } @@ -6318,6 +6850,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if ((_bits & 0x8L) != 0) { _current = new KeyValuePair(HeaderNames.KeepAlive, _collection._headers._KeepAlive); + _currentKnownType = KnownHeaderType.KeepAlive; _next = 4; return true; } @@ -6325,6 +6858,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if ((_bits & 0x10L) != 0) { _current = new KeyValuePair(HeaderNames.Pragma, _collection._headers._Pragma); + _currentKnownType = KnownHeaderType.Pragma; _next = 5; return true; } @@ -6332,6 +6866,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if ((_bits & 0x20L) != 0) { _current = new KeyValuePair(HeaderNames.Trailer, _collection._headers._Trailer); + _currentKnownType = KnownHeaderType.Trailer; _next = 6; return true; } @@ -6339,6 +6874,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if ((_bits & 0x40L) != 0) { _current = new KeyValuePair(HeaderNames.TransferEncoding, _collection._headers._TransferEncoding); + _currentKnownType = KnownHeaderType.TransferEncoding; _next = 7; return true; } @@ -6346,6 +6882,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if ((_bits & 0x80L) != 0) { _current = new KeyValuePair(HeaderNames.Upgrade, _collection._headers._Upgrade); + _currentKnownType = KnownHeaderType.Upgrade; _next = 8; return true; } @@ -6353,6 +6890,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if ((_bits & 0x100L) != 0) { _current = new KeyValuePair(HeaderNames.Via, _collection._headers._Via); + _currentKnownType = KnownHeaderType.Via; _next = 9; return true; } @@ -6360,6 +6898,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if ((_bits & 0x200L) != 0) { _current = new KeyValuePair(HeaderNames.Warning, _collection._headers._Warning); + _currentKnownType = KnownHeaderType.Warning; _next = 10; return true; } @@ -6367,6 +6906,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if ((_bits & 0x400L) != 0) { _current = new KeyValuePair(HeaderNames.Allow, _collection._headers._Allow); + _currentKnownType = KnownHeaderType.Allow; _next = 11; return true; } @@ -6374,6 +6914,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if ((_bits & 0x800L) != 0) { _current = new KeyValuePair(HeaderNames.ContentType, _collection._headers._ContentType); + _currentKnownType = KnownHeaderType.ContentType; _next = 12; return true; } @@ -6381,6 +6922,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if ((_bits & 0x1000L) != 0) { _current = new KeyValuePair(HeaderNames.ContentEncoding, _collection._headers._ContentEncoding); + _currentKnownType = KnownHeaderType.ContentEncoding; _next = 13; return true; } @@ -6388,6 +6930,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if ((_bits & 0x2000L) != 0) { _current = new KeyValuePair(HeaderNames.ContentLanguage, _collection._headers._ContentLanguage); + _currentKnownType = KnownHeaderType.ContentLanguage; _next = 14; return true; } @@ -6395,6 +6938,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if ((_bits & 0x4000L) != 0) { _current = new KeyValuePair(HeaderNames.ContentLocation, _collection._headers._ContentLocation); + _currentKnownType = KnownHeaderType.ContentLocation; _next = 15; return true; } @@ -6402,6 +6946,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if ((_bits & 0x8000L) != 0) { _current = new KeyValuePair(HeaderNames.ContentMD5, _collection._headers._ContentMD5); + _currentKnownType = KnownHeaderType.ContentMD5; _next = 16; return true; } @@ -6409,6 +6954,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if ((_bits & 0x10000L) != 0) { _current = new KeyValuePair(HeaderNames.ContentRange, _collection._headers._ContentRange); + _currentKnownType = KnownHeaderType.ContentRange; _next = 17; return true; } @@ -6416,6 +6962,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if ((_bits & 0x20000L) != 0) { _current = new KeyValuePair(HeaderNames.Expires, _collection._headers._Expires); + _currentKnownType = KnownHeaderType.Expires; _next = 18; return true; } @@ -6423,233 +6970,299 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if ((_bits & 0x40000L) != 0) { _current = new KeyValuePair(HeaderNames.LastModified, _collection._headers._LastModified); + _currentKnownType = KnownHeaderType.LastModified; _next = 19; return true; } - HeaderAccept: // case 19 + HeaderAuthority: // case 19 if ((_bits & 0x80000L) != 0) { - _current = new KeyValuePair(HeaderNames.Accept, _collection._headers._Accept); + _current = new KeyValuePair(HeaderNames.Authority, _collection._headers._Authority); + _currentKnownType = KnownHeaderType.Authority; _next = 20; return true; } - HeaderAcceptCharset: // case 20 + HeaderMethod: // case 20 if ((_bits & 0x100000L) != 0) { - _current = new KeyValuePair(HeaderNames.AcceptCharset, _collection._headers._AcceptCharset); + _current = new KeyValuePair(HeaderNames.Method, _collection._headers._Method); + _currentKnownType = KnownHeaderType.Method; _next = 21; return true; } - HeaderAcceptEncoding: // case 21 + HeaderPath: // case 21 if ((_bits & 0x200000L) != 0) { - _current = new KeyValuePair(HeaderNames.AcceptEncoding, _collection._headers._AcceptEncoding); + _current = new KeyValuePair(HeaderNames.Path, _collection._headers._Path); + _currentKnownType = KnownHeaderType.Path; _next = 22; return true; } - HeaderAcceptLanguage: // case 22 + HeaderScheme: // case 22 if ((_bits & 0x400000L) != 0) { - _current = new KeyValuePair(HeaderNames.AcceptLanguage, _collection._headers._AcceptLanguage); + _current = new KeyValuePair(HeaderNames.Scheme, _collection._headers._Scheme); + _currentKnownType = KnownHeaderType.Scheme; _next = 23; return true; } - HeaderAuthorization: // case 23 + HeaderAccept: // case 23 if ((_bits & 0x800000L) != 0) { - _current = new KeyValuePair(HeaderNames.Authorization, _collection._headers._Authorization); + _current = new KeyValuePair(HeaderNames.Accept, _collection._headers._Accept); + _currentKnownType = KnownHeaderType.Accept; _next = 24; return true; } - HeaderCookie: // case 24 + HeaderAcceptCharset: // case 24 if ((_bits & 0x1000000L) != 0) { - _current = new KeyValuePair(HeaderNames.Cookie, _collection._headers._Cookie); + _current = new KeyValuePair(HeaderNames.AcceptCharset, _collection._headers._AcceptCharset); + _currentKnownType = KnownHeaderType.AcceptCharset; _next = 25; return true; } - HeaderExpect: // case 25 + HeaderAcceptEncoding: // case 25 if ((_bits & 0x2000000L) != 0) { - _current = new KeyValuePair(HeaderNames.Expect, _collection._headers._Expect); + _current = new KeyValuePair(HeaderNames.AcceptEncoding, _collection._headers._AcceptEncoding); + _currentKnownType = KnownHeaderType.AcceptEncoding; _next = 26; return true; } - HeaderFrom: // case 26 + HeaderAcceptLanguage: // case 26 if ((_bits & 0x4000000L) != 0) { - _current = new KeyValuePair(HeaderNames.From, _collection._headers._From); + _current = new KeyValuePair(HeaderNames.AcceptLanguage, _collection._headers._AcceptLanguage); + _currentKnownType = KnownHeaderType.AcceptLanguage; _next = 27; return true; } - HeaderHost: // case 27 + HeaderAuthorization: // case 27 if ((_bits & 0x8000000L) != 0) { - _current = new KeyValuePair(HeaderNames.Host, _collection._headers._Host); + _current = new KeyValuePair(HeaderNames.Authorization, _collection._headers._Authorization); + _currentKnownType = KnownHeaderType.Authorization; _next = 28; return true; } - HeaderIfMatch: // case 28 + HeaderCookie: // case 28 if ((_bits & 0x10000000L) != 0) { - _current = new KeyValuePair(HeaderNames.IfMatch, _collection._headers._IfMatch); + _current = new KeyValuePair(HeaderNames.Cookie, _collection._headers._Cookie); + _currentKnownType = KnownHeaderType.Cookie; _next = 29; return true; } - HeaderIfModifiedSince: // case 29 + HeaderExpect: // case 29 if ((_bits & 0x20000000L) != 0) { - _current = new KeyValuePair(HeaderNames.IfModifiedSince, _collection._headers._IfModifiedSince); + _current = new KeyValuePair(HeaderNames.Expect, _collection._headers._Expect); + _currentKnownType = KnownHeaderType.Expect; _next = 30; return true; } - HeaderIfNoneMatch: // case 30 + HeaderFrom: // case 30 if ((_bits & 0x40000000L) != 0) { - _current = new KeyValuePair(HeaderNames.IfNoneMatch, _collection._headers._IfNoneMatch); + _current = new KeyValuePair(HeaderNames.From, _collection._headers._From); + _currentKnownType = KnownHeaderType.From; _next = 31; return true; } - HeaderIfRange: // case 31 + HeaderHost: // case 31 if ((_bits & 0x80000000L) != 0) { - _current = new KeyValuePair(HeaderNames.IfRange, _collection._headers._IfRange); + _current = new KeyValuePair(HeaderNames.Host, _collection._headers._Host); + _currentKnownType = KnownHeaderType.Host; _next = 32; return true; } - HeaderIfUnmodifiedSince: // case 32 + HeaderIfMatch: // case 32 if ((_bits & 0x100000000L) != 0) { - _current = new KeyValuePair(HeaderNames.IfUnmodifiedSince, _collection._headers._IfUnmodifiedSince); + _current = new KeyValuePair(HeaderNames.IfMatch, _collection._headers._IfMatch); + _currentKnownType = KnownHeaderType.IfMatch; _next = 33; return true; } - HeaderMaxForwards: // case 33 + HeaderIfModifiedSince: // case 33 if ((_bits & 0x200000000L) != 0) { - _current = new KeyValuePair(HeaderNames.MaxForwards, _collection._headers._MaxForwards); + _current = new KeyValuePair(HeaderNames.IfModifiedSince, _collection._headers._IfModifiedSince); + _currentKnownType = KnownHeaderType.IfModifiedSince; _next = 34; return true; } - HeaderProxyAuthorization: // case 34 + HeaderIfNoneMatch: // case 34 if ((_bits & 0x400000000L) != 0) { - _current = new KeyValuePair(HeaderNames.ProxyAuthorization, _collection._headers._ProxyAuthorization); + _current = new KeyValuePair(HeaderNames.IfNoneMatch, _collection._headers._IfNoneMatch); + _currentKnownType = KnownHeaderType.IfNoneMatch; _next = 35; return true; } - HeaderReferer: // case 35 + HeaderIfRange: // case 35 if ((_bits & 0x800000000L) != 0) { - _current = new KeyValuePair(HeaderNames.Referer, _collection._headers._Referer); + _current = new KeyValuePair(HeaderNames.IfRange, _collection._headers._IfRange); + _currentKnownType = KnownHeaderType.IfRange; _next = 36; return true; } - HeaderRange: // case 36 + HeaderIfUnmodifiedSince: // case 36 if ((_bits & 0x1000000000L) != 0) { - _current = new KeyValuePair(HeaderNames.Range, _collection._headers._Range); + _current = new KeyValuePair(HeaderNames.IfUnmodifiedSince, _collection._headers._IfUnmodifiedSince); + _currentKnownType = KnownHeaderType.IfUnmodifiedSince; _next = 37; return true; } - HeaderTE: // case 37 + HeaderMaxForwards: // case 37 if ((_bits & 0x2000000000L) != 0) { - _current = new KeyValuePair(HeaderNames.TE, _collection._headers._TE); + _current = new KeyValuePair(HeaderNames.MaxForwards, _collection._headers._MaxForwards); + _currentKnownType = KnownHeaderType.MaxForwards; _next = 38; return true; } - HeaderTranslate: // case 38 + HeaderProxyAuthorization: // case 38 if ((_bits & 0x4000000000L) != 0) { - _current = new KeyValuePair(HeaderNames.Translate, _collection._headers._Translate); + _current = new KeyValuePair(HeaderNames.ProxyAuthorization, _collection._headers._ProxyAuthorization); + _currentKnownType = KnownHeaderType.ProxyAuthorization; _next = 39; return true; } - HeaderUserAgent: // case 39 + HeaderReferer: // case 39 if ((_bits & 0x8000000000L) != 0) { - _current = new KeyValuePair(HeaderNames.UserAgent, _collection._headers._UserAgent); + _current = new KeyValuePair(HeaderNames.Referer, _collection._headers._Referer); + _currentKnownType = KnownHeaderType.Referer; _next = 40; return true; } - HeaderDNT: // case 40 + HeaderRange: // case 40 if ((_bits & 0x10000000000L) != 0) { - _current = new KeyValuePair(HeaderNames.DNT, _collection._headers._DNT); + _current = new KeyValuePair(HeaderNames.Range, _collection._headers._Range); + _currentKnownType = KnownHeaderType.Range; _next = 41; return true; } - HeaderUpgradeInsecureRequests: // case 41 + HeaderTE: // case 41 if ((_bits & 0x20000000000L) != 0) { - _current = new KeyValuePair(HeaderNames.UpgradeInsecureRequests, _collection._headers._UpgradeInsecureRequests); + _current = new KeyValuePair(HeaderNames.TE, _collection._headers._TE); + _currentKnownType = KnownHeaderType.TE; _next = 42; return true; } - HeaderRequestId: // case 42 + HeaderTranslate: // case 42 if ((_bits & 0x40000000000L) != 0) { - _current = new KeyValuePair(HeaderNames.RequestId, _collection._headers._RequestId); + _current = new KeyValuePair(HeaderNames.Translate, _collection._headers._Translate); + _currentKnownType = KnownHeaderType.Translate; _next = 43; return true; } - HeaderCorrelationContext: // case 43 + HeaderUserAgent: // case 43 if ((_bits & 0x80000000000L) != 0) { - _current = new KeyValuePair(HeaderNames.CorrelationContext, _collection._headers._CorrelationContext); + _current = new KeyValuePair(HeaderNames.UserAgent, _collection._headers._UserAgent); + _currentKnownType = KnownHeaderType.UserAgent; _next = 44; return true; } - HeaderTraceParent: // case 44 + HeaderDNT: // case 44 if ((_bits & 0x100000000000L) != 0) { - _current = new KeyValuePair(HeaderNames.TraceParent, _collection._headers._TraceParent); + _current = new KeyValuePair(HeaderNames.DNT, _collection._headers._DNT); + _currentKnownType = KnownHeaderType.DNT; _next = 45; return true; } - HeaderTraceState: // case 45 + HeaderUpgradeInsecureRequests: // case 45 if ((_bits & 0x200000000000L) != 0) { - _current = new KeyValuePair(HeaderNames.TraceState, _collection._headers._TraceState); + _current = new KeyValuePair(HeaderNames.UpgradeInsecureRequests, _collection._headers._UpgradeInsecureRequests); + _currentKnownType = KnownHeaderType.UpgradeInsecureRequests; _next = 46; return true; } - HeaderOrigin: // case 46 + HeaderRequestId: // case 46 if ((_bits & 0x400000000000L) != 0) { - _current = new KeyValuePair(HeaderNames.Origin, _collection._headers._Origin); + _current = new KeyValuePair(HeaderNames.RequestId, _collection._headers._RequestId); + _currentKnownType = KnownHeaderType.RequestId; _next = 47; return true; } - HeaderAccessControlRequestMethod: // case 47 + HeaderCorrelationContext: // case 47 if ((_bits & 0x800000000000L) != 0) { - _current = new KeyValuePair(HeaderNames.AccessControlRequestMethod, _collection._headers._AccessControlRequestMethod); + _current = new KeyValuePair(HeaderNames.CorrelationContext, _collection._headers._CorrelationContext); + _currentKnownType = KnownHeaderType.CorrelationContext; _next = 48; return true; } - HeaderAccessControlRequestHeaders: // case 48 + HeaderTraceParent: // case 48 if ((_bits & 0x1000000000000L) != 0) { - _current = new KeyValuePair(HeaderNames.AccessControlRequestHeaders, _collection._headers._AccessControlRequestHeaders); + _current = new KeyValuePair(HeaderNames.TraceParent, _collection._headers._TraceParent); + _currentKnownType = KnownHeaderType.TraceParent; _next = 49; return true; } - HeaderContentLength: // case 49 + HeaderTraceState: // case 49 + if ((_bits & 0x2000000000000L) != 0) + { + _current = new KeyValuePair(HeaderNames.TraceState, _collection._headers._TraceState); + _currentKnownType = KnownHeaderType.TraceState; + _next = 50; + return true; + } + HeaderOrigin: // case 50 + if ((_bits & 0x4000000000000L) != 0) + { + _current = new KeyValuePair(HeaderNames.Origin, _collection._headers._Origin); + _currentKnownType = KnownHeaderType.Origin; + _next = 51; + return true; + } + HeaderAccessControlRequestMethod: // case 51 + if ((_bits & 0x8000000000000L) != 0) + { + _current = new KeyValuePair(HeaderNames.AccessControlRequestMethod, _collection._headers._AccessControlRequestMethod); + _currentKnownType = KnownHeaderType.AccessControlRequestMethod; + _next = 52; + return true; + } + HeaderAccessControlRequestHeaders: // case 52 + if ((_bits & 0x10000000000000L) != 0) + { + _current = new KeyValuePair(HeaderNames.AccessControlRequestHeaders, _collection._headers._AccessControlRequestHeaders); + _currentKnownType = KnownHeaderType.AccessControlRequestHeaders; + _next = 53; + return true; + } + HeaderContentLength: // case 53 if (_collection._contentLength.HasValue) { _current = new KeyValuePair(HeaderNames.ContentLength, HeaderUtilities.FormatNonNegativeInt64(_collection._contentLength.Value)); - _next = 50; + _currentKnownType = KnownHeaderType.ContentLength; + _next = 54; return true; } ExtraHeaders: if (!_hasUnknown || !_unknownEnumerator.MoveNext()) { _current = default(KeyValuePair); + _currentKnownType = default; return false; } _current = _unknownEnumerator.Current; + _currentKnownType = KnownHeaderType.Unknown; return true; } } @@ -6659,14 +7272,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { private static ReadOnlySpan HeaderBytes => new byte[] { - 13,10,67,97,99,104,101,45,67,111,110,116,114,111,108,58,32,13,10,67,111,110,110,101,99,116,105,111,110,58,32,13,10,68,97,116,101,58,32,13,10,75,101,101,112,45,65,108,105,118,101,58,32,13,10,80,114,97,103,109,97,58,32,13,10,84,114,97,105,108,101,114,58,32,13,10,84,114,97,110,115,102,101,114,45,69,110,99,111,100,105,110,103,58,32,13,10,85,112,103,114,97,100,101,58,32,13,10,86,105,97,58,32,13,10,87,97,114,110,105,110,103,58,32,13,10,65,108,108,111,119,58,32,13,10,67,111,110,116,101,110,116,45,84,121,112,101,58,32,13,10,67,111,110,116,101,110,116,45,69,110,99,111,100,105,110,103,58,32,13,10,67,111,110,116,101,110,116,45,76,97,110,103,117,97,103,101,58,32,13,10,67,111,110,116,101,110,116,45,76,111,99,97,116,105,111,110,58,32,13,10,67,111,110,116,101,110,116,45,77,68,53,58,32,13,10,67,111,110,116,101,110,116,45,82,97,110,103,101,58,32,13,10,69,120,112,105,114,101,115,58,32,13,10,76,97,115,116,45,77,111,100,105,102,105,101,100,58,32,13,10,65,99,99,101,112,116,45,82,97,110,103,101,115,58,32,13,10,65,103,101,58,32,13,10,69,84,97,103,58,32,13,10,76,111,99,97,116,105,111,110,58,32,13,10,80,114,111,120,121,45,65,117,116,104,101,110,116,105,99,97,116,101,58,32,13,10,82,101,116,114,121,45,65,102,116,101,114,58,32,13,10,83,101,114,118,101,114,58,32,13,10,83,101,116,45,67,111,111,107,105,101,58,32,13,10,86,97,114,121,58,32,13,10,87,87,87,45,65,117,116,104,101,110,116,105,99,97,116,101,58,32,13,10,65,99,99,101,115,115,45,67,111,110,116,114,111,108,45,65,108,108,111,119,45,67,114,101,100,101,110,116,105,97,108,115,58,32,13,10,65,99,99,101,115,115,45,67,111,110,116,114,111,108,45,65,108,108,111,119,45,72,101,97,100,101,114,115,58,32,13,10,65,99,99,101,115,115,45,67,111,110,116,114,111,108,45,65,108,108,111,119,45,77,101,116,104,111,100,115,58,32,13,10,65,99,99,101,115,115,45,67,111,110,116,114,111,108,45,65,108,108,111,119,45,79,114,105,103,105,110,58,32,13,10,65,99,99,101,115,115,45,67,111,110,116,114,111,108,45,69,120,112,111,115,101,45,72,101,97,100,101,114,115,58,32,13,10,65,99,99,101,115,115,45,67,111,110,116,114,111,108,45,77,97,120,45,65,103,101,58,32,13,10,67,111,110,116,101,110,116,45,76,101,110,103,116,104,58,32, + 13,10,67,97,99,104,101,45,67,111,110,116,114,111,108,58,32,13,10,67,111,110,110,101,99,116,105,111,110,58,32,13,10,68,97,116,101,58,32,13,10,75,101,101,112,45,65,108,105,118,101,58,32,13,10,80,114,97,103,109,97,58,32,13,10,84,114,97,105,108,101,114,58,32,13,10,84,114,97,110,115,102,101,114,45,69,110,99,111,100,105,110,103,58,32,13,10,85,112,103,114,97,100,101,58,32,13,10,86,105,97,58,32,13,10,87,97,114,110,105,110,103,58,32,13,10,65,108,108,111,119,58,32,13,10,67,111,110,116,101,110,116,45,84,121,112,101,58,32,13,10,67,111,110,116,101,110,116,45,69,110,99,111,100,105,110,103,58,32,13,10,67,111,110,116,101,110,116,45,76,97,110,103,117,97,103,101,58,32,13,10,67,111,110,116,101,110,116,45,76,111,99,97,116,105,111,110,58,32,13,10,67,111,110,116,101,110,116,45,77,68,53,58,32,13,10,67,111,110,116,101,110,116,45,82,97,110,103,101,58,32,13,10,69,120,112,105,114,101,115,58,32,13,10,76,97,115,116,45,77,111,100,105,102,105,101,100,58,32,13,10,65,99,99,101,112,116,45,82,97,110,103,101,115,58,32,13,10,65,103,101,58,32,13,10,65,108,116,45,83,118,99,58,32,13,10,69,84,97,103,58,32,13,10,76,111,99,97,116,105,111,110,58,32,13,10,80,114,111,120,121,45,65,117,116,104,101,110,116,105,99,97,116,101,58,32,13,10,82,101,116,114,121,45,65,102,116,101,114,58,32,13,10,83,101,114,118,101,114,58,32,13,10,83,101,116,45,67,111,111,107,105,101,58,32,13,10,86,97,114,121,58,32,13,10,87,87,87,45,65,117,116,104,101,110,116,105,99,97,116,101,58,32,13,10,65,99,99,101,115,115,45,67,111,110,116,114,111,108,45,65,108,108,111,119,45,67,114,101,100,101,110,116,105,97,108,115,58,32,13,10,65,99,99,101,115,115,45,67,111,110,116,114,111,108,45,65,108,108,111,119,45,72,101,97,100,101,114,115,58,32,13,10,65,99,99,101,115,115,45,67,111,110,116,114,111,108,45,65,108,108,111,119,45,77,101,116,104,111,100,115,58,32,13,10,65,99,99,101,115,115,45,67,111,110,116,114,111,108,45,65,108,108,111,119,45,79,114,105,103,105,110,58,32,13,10,65,99,99,101,115,115,45,67,111,110,116,114,111,108,45,69,120,112,111,115,101,45,72,101,97,100,101,114,115,58,32,13,10,65,99,99,101,115,115,45,67,111,110,116,114,111,108,45,77,97,120,45,65,103,101,58,32,13,10,67,111,110,116,101,110,116,45,76,101,110,103,116,104,58,32, }; private HeaderReferences _headers; public bool HasConnection => (_bits & 0x2L) != 0; public bool HasDate => (_bits & 0x4L) != 0; public bool HasTransferEncoding => (_bits & 0x40L) != 0; - public bool HasServer => (_bits & 0x2000000L) != 0; + public bool HasServer => (_bits & 0x4000000L) != 0; public StringValues HeaderCacheControl @@ -7029,12 +7642,29 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http _headers._Age = value; } } - public StringValues HeaderETag + public StringValues HeaderAltSvc { get { StringValues value = default; if ((_bits & 0x200000L) != 0) + { + value = _headers._AltSvc; + } + return value; + } + set + { + _bits |= 0x200000L; + _headers._AltSvc = value; + } + } + public StringValues HeaderETag + { + get + { + StringValues value = default; + if ((_bits & 0x400000L) != 0) { value = _headers._ETag; } @@ -7042,7 +7672,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } set { - _bits |= 0x200000L; + _bits |= 0x400000L; _headers._ETag = value; } } @@ -7051,7 +7681,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http get { StringValues value = default; - if ((_bits & 0x400000L) != 0) + if ((_bits & 0x800000L) != 0) { value = _headers._Location; } @@ -7059,7 +7689,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } set { - _bits |= 0x400000L; + _bits |= 0x800000L; _headers._Location = value; } } @@ -7068,7 +7698,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http get { StringValues value = default; - if ((_bits & 0x800000L) != 0) + if ((_bits & 0x1000000L) != 0) { value = _headers._ProxyAuthenticate; } @@ -7076,7 +7706,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } set { - _bits |= 0x800000L; + _bits |= 0x1000000L; _headers._ProxyAuthenticate = value; } } @@ -7085,7 +7715,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http get { StringValues value = default; - if ((_bits & 0x1000000L) != 0) + if ((_bits & 0x2000000L) != 0) { value = _headers._RetryAfter; } @@ -7093,7 +7723,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } set { - _bits |= 0x1000000L; + _bits |= 0x2000000L; _headers._RetryAfter = value; } } @@ -7102,7 +7732,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http get { StringValues value = default; - if ((_bits & 0x2000000L) != 0) + if ((_bits & 0x4000000L) != 0) { value = _headers._Server; } @@ -7110,7 +7740,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } set { - _bits |= 0x2000000L; + _bits |= 0x4000000L; _headers._Server = value; _headers._rawServer = null; } @@ -7120,7 +7750,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http get { StringValues value = default; - if ((_bits & 0x4000000L) != 0) + if ((_bits & 0x8000000L) != 0) { value = _headers._SetCookie; } @@ -7128,7 +7758,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } set { - _bits |= 0x4000000L; + _bits |= 0x8000000L; _headers._SetCookie = value; } } @@ -7137,7 +7767,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http get { StringValues value = default; - if ((_bits & 0x8000000L) != 0) + if ((_bits & 0x10000000L) != 0) { value = _headers._Vary; } @@ -7145,7 +7775,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } set { - _bits |= 0x8000000L; + _bits |= 0x10000000L; _headers._Vary = value; } } @@ -7154,7 +7784,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http get { StringValues value = default; - if ((_bits & 0x10000000L) != 0) + if ((_bits & 0x20000000L) != 0) { value = _headers._WWWAuthenticate; } @@ -7162,7 +7792,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } set { - _bits |= 0x10000000L; + _bits |= 0x20000000L; _headers._WWWAuthenticate = value; } } @@ -7171,7 +7801,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http get { StringValues value = default; - if ((_bits & 0x20000000L) != 0) + if ((_bits & 0x40000000L) != 0) { value = _headers._AccessControlAllowCredentials; } @@ -7179,7 +7809,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } set { - _bits |= 0x20000000L; + _bits |= 0x40000000L; _headers._AccessControlAllowCredentials = value; } } @@ -7188,7 +7818,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http get { StringValues value = default; - if ((_bits & 0x40000000L) != 0) + if ((_bits & 0x80000000L) != 0) { value = _headers._AccessControlAllowHeaders; } @@ -7196,7 +7826,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } set { - _bits |= 0x40000000L; + _bits |= 0x80000000L; _headers._AccessControlAllowHeaders = value; } } @@ -7205,7 +7835,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http get { StringValues value = default; - if ((_bits & 0x80000000L) != 0) + if ((_bits & 0x100000000L) != 0) { value = _headers._AccessControlAllowMethods; } @@ -7213,7 +7843,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } set { - _bits |= 0x80000000L; + _bits |= 0x100000000L; _headers._AccessControlAllowMethods = value; } } @@ -7222,7 +7852,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http get { StringValues value = default; - if ((_bits & 0x100000000L) != 0) + if ((_bits & 0x200000000L) != 0) { value = _headers._AccessControlAllowOrigin; } @@ -7230,7 +7860,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } set { - _bits |= 0x100000000L; + _bits |= 0x200000000L; _headers._AccessControlAllowOrigin = value; } } @@ -7239,7 +7869,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http get { StringValues value = default; - if ((_bits & 0x200000000L) != 0) + if ((_bits & 0x400000000L) != 0) { value = _headers._AccessControlExposeHeaders; } @@ -7247,7 +7877,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } set { - _bits |= 0x200000000L; + _bits |= 0x400000000L; _headers._AccessControlExposeHeaders = value; } } @@ -7256,7 +7886,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http get { StringValues value = default; - if ((_bits & 0x400000000L) != 0) + if ((_bits & 0x800000000L) != 0) { value = _headers._AccessControlMaxAge; } @@ -7264,7 +7894,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } set { - _bits |= 0x400000000L; + _bits |= 0x800000000L; _headers._AccessControlMaxAge = value; } } @@ -7305,7 +7935,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } public void SetRawServer(StringValues value, byte[] raw) { - _bits |= 0x2000000L; + _bits |= 0x4000000L; _headers._Server = value; _headers._rawServer = raw; } @@ -7373,7 +8003,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.ETag, key)) { - if ((_bits & 0x200000L) != 0) + if ((_bits & 0x400000L) != 0) { value = _headers._ETag; return true; @@ -7382,7 +8012,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.Vary, key)) { - if ((_bits & 0x8000000L) != 0) + if ((_bits & 0x10000000L) != 0) { value = _headers._Vary; return true; @@ -7401,7 +8031,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.ETag.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x200000L) != 0) + if ((_bits & 0x400000L) != 0) { value = _headers._ETag; return true; @@ -7410,7 +8040,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.Vary.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x8000000L) != 0) + if ((_bits & 0x10000000L) != 0) { value = _headers._Vary; return true; @@ -7446,7 +8076,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { if (ReferenceEquals(HeaderNames.Server, key)) { - if ((_bits & 0x2000000L) != 0) + if ((_bits & 0x4000000L) != 0) { value = _headers._Server; return true; @@ -7465,7 +8095,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if (HeaderNames.Server.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x2000000L) != 0) + if ((_bits & 0x4000000L) != 0) { value = _headers._Server; return true; @@ -7521,6 +8151,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } return false; } + if (ReferenceEquals(HeaderNames.AltSvc, key)) + { + if ((_bits & 0x200000L) != 0) + { + value = _headers._AltSvc; + return true; + } + return false; + } if (HeaderNames.Trailer.Equals(key, StringComparison.OrdinalIgnoreCase)) { @@ -7558,13 +8197,22 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } return false; } + if (HeaderNames.AltSvc.Equals(key, StringComparison.OrdinalIgnoreCase)) + { + if ((_bits & 0x200000L) != 0) + { + value = _headers._AltSvc; + return true; + } + return false; + } break; } case 8: { if (ReferenceEquals(HeaderNames.Location, key)) { - if ((_bits & 0x400000L) != 0) + if ((_bits & 0x800000L) != 0) { value = _headers._Location; return true; @@ -7574,7 +8222,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if (HeaderNames.Location.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x400000L) != 0) + if ((_bits & 0x800000L) != 0) { value = _headers._Location; return true; @@ -7605,7 +8253,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.SetCookie, key)) { - if ((_bits & 0x4000000L) != 0) + if ((_bits & 0x8000000L) != 0) { value = _headers._SetCookie; return true; @@ -7633,7 +8281,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.SetCookie.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x4000000L) != 0) + if ((_bits & 0x8000000L) != 0) { value = _headers._SetCookie; return true; @@ -7655,7 +8303,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.RetryAfter, key)) { - if ((_bits & 0x1000000L) != 0) + if ((_bits & 0x2000000L) != 0) { value = _headers._RetryAfter; return true; @@ -7674,7 +8322,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.RetryAfter.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x1000000L) != 0) + if ((_bits & 0x2000000L) != 0) { value = _headers._RetryAfter; return true; @@ -7837,7 +8485,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.WWWAuthenticate, key)) { - if ((_bits & 0x10000000L) != 0) + if ((_bits & 0x20000000L) != 0) { value = _headers._WWWAuthenticate; return true; @@ -7874,7 +8522,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.WWWAuthenticate.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x10000000L) != 0) + if ((_bits & 0x20000000L) != 0) { value = _headers._WWWAuthenticate; return true; @@ -7910,7 +8558,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { if (ReferenceEquals(HeaderNames.ProxyAuthenticate, key)) { - if ((_bits & 0x800000L) != 0) + if ((_bits & 0x1000000L) != 0) { value = _headers._ProxyAuthenticate; return true; @@ -7920,7 +8568,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if (HeaderNames.ProxyAuthenticate.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x800000L) != 0) + if ((_bits & 0x1000000L) != 0) { value = _headers._ProxyAuthenticate; return true; @@ -7933,7 +8581,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { if (ReferenceEquals(HeaderNames.AccessControlMaxAge, key)) { - if ((_bits & 0x400000000L) != 0) + if ((_bits & 0x800000000L) != 0) { value = _headers._AccessControlMaxAge; return true; @@ -7943,7 +8591,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if (HeaderNames.AccessControlMaxAge.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x400000000L) != 0) + if ((_bits & 0x800000000L) != 0) { value = _headers._AccessControlMaxAge; return true; @@ -7956,7 +8604,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { if (ReferenceEquals(HeaderNames.AccessControlAllowOrigin, key)) { - if ((_bits & 0x100000000L) != 0) + if ((_bits & 0x200000000L) != 0) { value = _headers._AccessControlAllowOrigin; return true; @@ -7966,7 +8614,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if (HeaderNames.AccessControlAllowOrigin.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x100000000L) != 0) + if ((_bits & 0x200000000L) != 0) { value = _headers._AccessControlAllowOrigin; return true; @@ -7979,7 +8627,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { if (ReferenceEquals(HeaderNames.AccessControlAllowHeaders, key)) { - if ((_bits & 0x40000000L) != 0) + if ((_bits & 0x80000000L) != 0) { value = _headers._AccessControlAllowHeaders; return true; @@ -7988,7 +8636,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.AccessControlAllowMethods, key)) { - if ((_bits & 0x80000000L) != 0) + if ((_bits & 0x100000000L) != 0) { value = _headers._AccessControlAllowMethods; return true; @@ -7998,7 +8646,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if (HeaderNames.AccessControlAllowHeaders.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x40000000L) != 0) + if ((_bits & 0x80000000L) != 0) { value = _headers._AccessControlAllowHeaders; return true; @@ -8007,7 +8655,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.AccessControlAllowMethods.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x80000000L) != 0) + if ((_bits & 0x100000000L) != 0) { value = _headers._AccessControlAllowMethods; return true; @@ -8020,7 +8668,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { if (ReferenceEquals(HeaderNames.AccessControlExposeHeaders, key)) { - if ((_bits & 0x200000000L) != 0) + if ((_bits & 0x400000000L) != 0) { value = _headers._AccessControlExposeHeaders; return true; @@ -8030,7 +8678,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if (HeaderNames.AccessControlExposeHeaders.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x200000000L) != 0) + if ((_bits & 0x400000000L) != 0) { value = _headers._AccessControlExposeHeaders; return true; @@ -8043,7 +8691,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { if (ReferenceEquals(HeaderNames.AccessControlAllowCredentials, key)) { - if ((_bits & 0x20000000L) != 0) + if ((_bits & 0x40000000L) != 0) { value = _headers._AccessControlAllowCredentials; return true; @@ -8053,7 +8701,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if (HeaderNames.AccessControlAllowCredentials.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x20000000L) != 0) + if ((_bits & 0x40000000L) != 0) { value = _headers._AccessControlAllowCredentials; return true; @@ -8112,13 +8760,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.ETag, key)) { - _bits |= 0x200000L; + _bits |= 0x400000L; _headers._ETag = value; return; } if (ReferenceEquals(HeaderNames.Vary, key)) { - _bits |= 0x8000000L; + _bits |= 0x10000000L; _headers._Vary = value; return; } @@ -8132,13 +8780,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.ETag.Equals(key, StringComparison.OrdinalIgnoreCase)) { - _bits |= 0x200000L; + _bits |= 0x400000L; _headers._ETag = value; return; } if (HeaderNames.Vary.Equals(key, StringComparison.OrdinalIgnoreCase)) { - _bits |= 0x8000000L; + _bits |= 0x10000000L; _headers._Vary = value; return; } @@ -8165,7 +8813,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { if (ReferenceEquals(HeaderNames.Server, key)) { - _bits |= 0x2000000L; + _bits |= 0x4000000L; _headers._Server = value; _headers._rawServer = null; return; @@ -8179,7 +8827,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if (HeaderNames.Server.Equals(key, StringComparison.OrdinalIgnoreCase)) { - _bits |= 0x2000000L; + _bits |= 0x4000000L; _headers._Server = value; _headers._rawServer = null; return; @@ -8218,6 +8866,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http _headers._Expires = value; return; } + if (ReferenceEquals(HeaderNames.AltSvc, key)) + { + _bits |= 0x200000L; + _headers._AltSvc = value; + return; + } if (HeaderNames.Trailer.Equals(key, StringComparison.OrdinalIgnoreCase)) { @@ -8243,20 +8897,26 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http _headers._Expires = value; return; } + if (HeaderNames.AltSvc.Equals(key, StringComparison.OrdinalIgnoreCase)) + { + _bits |= 0x200000L; + _headers._AltSvc = value; + return; + } break; } case 8: { if (ReferenceEquals(HeaderNames.Location, key)) { - _bits |= 0x400000L; + _bits |= 0x800000L; _headers._Location = value; return; } if (HeaderNames.Location.Equals(key, StringComparison.OrdinalIgnoreCase)) { - _bits |= 0x400000L; + _bits |= 0x800000L; _headers._Location = value; return; } @@ -8279,7 +8939,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.SetCookie, key)) { - _bits |= 0x4000000L; + _bits |= 0x8000000L; _headers._SetCookie = value; return; } @@ -8299,7 +8959,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.SetCookie.Equals(key, StringComparison.OrdinalIgnoreCase)) { - _bits |= 0x4000000L; + _bits |= 0x8000000L; _headers._SetCookie = value; return; } @@ -8315,7 +8975,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.RetryAfter, key)) { - _bits |= 0x1000000L; + _bits |= 0x2000000L; _headers._RetryAfter = value; return; } @@ -8328,7 +8988,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.RetryAfter.Equals(key, StringComparison.OrdinalIgnoreCase)) { - _bits |= 0x1000000L; + _bits |= 0x2000000L; _headers._RetryAfter = value; return; } @@ -8441,7 +9101,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.WWWAuthenticate, key)) { - _bits |= 0x10000000L; + _bits |= 0x20000000L; _headers._WWWAuthenticate = value; return; } @@ -8466,7 +9126,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.WWWAuthenticate.Equals(key, StringComparison.OrdinalIgnoreCase)) { - _bits |= 0x10000000L; + _bits |= 0x20000000L; _headers._WWWAuthenticate = value; return; } @@ -8495,14 +9155,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { if (ReferenceEquals(HeaderNames.ProxyAuthenticate, key)) { - _bits |= 0x800000L; + _bits |= 0x1000000L; _headers._ProxyAuthenticate = value; return; } if (HeaderNames.ProxyAuthenticate.Equals(key, StringComparison.OrdinalIgnoreCase)) { - _bits |= 0x800000L; + _bits |= 0x1000000L; _headers._ProxyAuthenticate = value; return; } @@ -8512,14 +9172,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { if (ReferenceEquals(HeaderNames.AccessControlMaxAge, key)) { - _bits |= 0x400000000L; + _bits |= 0x800000000L; _headers._AccessControlMaxAge = value; return; } if (HeaderNames.AccessControlMaxAge.Equals(key, StringComparison.OrdinalIgnoreCase)) { - _bits |= 0x400000000L; + _bits |= 0x800000000L; _headers._AccessControlMaxAge = value; return; } @@ -8529,14 +9189,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { if (ReferenceEquals(HeaderNames.AccessControlAllowOrigin, key)) { - _bits |= 0x100000000L; + _bits |= 0x200000000L; _headers._AccessControlAllowOrigin = value; return; } if (HeaderNames.AccessControlAllowOrigin.Equals(key, StringComparison.OrdinalIgnoreCase)) { - _bits |= 0x100000000L; + _bits |= 0x200000000L; _headers._AccessControlAllowOrigin = value; return; } @@ -8546,26 +9206,26 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { if (ReferenceEquals(HeaderNames.AccessControlAllowHeaders, key)) { - _bits |= 0x40000000L; + _bits |= 0x80000000L; _headers._AccessControlAllowHeaders = value; return; } if (ReferenceEquals(HeaderNames.AccessControlAllowMethods, key)) { - _bits |= 0x80000000L; + _bits |= 0x100000000L; _headers._AccessControlAllowMethods = value; return; } if (HeaderNames.AccessControlAllowHeaders.Equals(key, StringComparison.OrdinalIgnoreCase)) { - _bits |= 0x40000000L; + _bits |= 0x80000000L; _headers._AccessControlAllowHeaders = value; return; } if (HeaderNames.AccessControlAllowMethods.Equals(key, StringComparison.OrdinalIgnoreCase)) { - _bits |= 0x80000000L; + _bits |= 0x100000000L; _headers._AccessControlAllowMethods = value; return; } @@ -8575,14 +9235,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { if (ReferenceEquals(HeaderNames.AccessControlExposeHeaders, key)) { - _bits |= 0x200000000L; + _bits |= 0x400000000L; _headers._AccessControlExposeHeaders = value; return; } if (HeaderNames.AccessControlExposeHeaders.Equals(key, StringComparison.OrdinalIgnoreCase)) { - _bits |= 0x200000000L; + _bits |= 0x400000000L; _headers._AccessControlExposeHeaders = value; return; } @@ -8592,14 +9252,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { if (ReferenceEquals(HeaderNames.AccessControlAllowCredentials, key)) { - _bits |= 0x20000000L; + _bits |= 0x40000000L; _headers._AccessControlAllowCredentials = value; return; } if (HeaderNames.AccessControlAllowCredentials.Equals(key, StringComparison.OrdinalIgnoreCase)) { - _bits |= 0x20000000L; + _bits |= 0x40000000L; _headers._AccessControlAllowCredentials = value; return; } @@ -8675,9 +9335,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.ETag, key)) { - if ((_bits & 0x200000L) == 0) + if ((_bits & 0x400000L) == 0) { - _bits |= 0x200000L; + _bits |= 0x400000L; _headers._ETag = value; return true; } @@ -8685,9 +9345,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.Vary, key)) { - if ((_bits & 0x8000000L) == 0) + if ((_bits & 0x10000000L) == 0) { - _bits |= 0x8000000L; + _bits |= 0x10000000L; _headers._Vary = value; return true; } @@ -8707,9 +9367,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.ETag.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x200000L) == 0) + if ((_bits & 0x400000L) == 0) { - _bits |= 0x200000L; + _bits |= 0x400000L; _headers._ETag = value; return true; } @@ -8717,9 +9377,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.Vary.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x8000000L) == 0) + if ((_bits & 0x10000000L) == 0) { - _bits |= 0x8000000L; + _bits |= 0x10000000L; _headers._Vary = value; return true; } @@ -8756,9 +9416,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { if (ReferenceEquals(HeaderNames.Server, key)) { - if ((_bits & 0x2000000L) == 0) + if ((_bits & 0x4000000L) == 0) { - _bits |= 0x2000000L; + _bits |= 0x4000000L; _headers._Server = value; _headers._rawServer = null; return true; @@ -8778,9 +9438,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if (HeaderNames.Server.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x2000000L) == 0) + if ((_bits & 0x4000000L) == 0) { - _bits |= 0x2000000L; + _bits |= 0x4000000L; _headers._Server = value; _headers._rawServer = null; return true; @@ -8841,6 +9501,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } return false; } + if (ReferenceEquals(HeaderNames.AltSvc, key)) + { + if ((_bits & 0x200000L) == 0) + { + _bits |= 0x200000L; + _headers._AltSvc = value; + return true; + } + return false; + } if (HeaderNames.Trailer.Equals(key, StringComparison.OrdinalIgnoreCase)) { @@ -8882,15 +9552,25 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } return false; } + if (HeaderNames.AltSvc.Equals(key, StringComparison.OrdinalIgnoreCase)) + { + if ((_bits & 0x200000L) == 0) + { + _bits |= 0x200000L; + _headers._AltSvc = value; + return true; + } + return false; + } break; } case 8: { if (ReferenceEquals(HeaderNames.Location, key)) { - if ((_bits & 0x400000L) == 0) + if ((_bits & 0x800000L) == 0) { - _bits |= 0x400000L; + _bits |= 0x800000L; _headers._Location = value; return true; } @@ -8899,9 +9579,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if (HeaderNames.Location.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x400000L) == 0) + if ((_bits & 0x800000L) == 0) { - _bits |= 0x400000L; + _bits |= 0x800000L; _headers._Location = value; return true; } @@ -8934,9 +9614,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.SetCookie, key)) { - if ((_bits & 0x4000000L) == 0) + if ((_bits & 0x8000000L) == 0) { - _bits |= 0x4000000L; + _bits |= 0x8000000L; _headers._SetCookie = value; return true; } @@ -8966,9 +9646,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.SetCookie.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x4000000L) == 0) + if ((_bits & 0x8000000L) == 0) { - _bits |= 0x4000000L; + _bits |= 0x8000000L; _headers._SetCookie = value; return true; } @@ -8990,9 +9670,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.RetryAfter, key)) { - if ((_bits & 0x1000000L) == 0) + if ((_bits & 0x2000000L) == 0) { - _bits |= 0x1000000L; + _bits |= 0x2000000L; _headers._RetryAfter = value; return true; } @@ -9011,9 +9691,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.RetryAfter.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x1000000L) == 0) + if ((_bits & 0x2000000L) == 0) { - _bits |= 0x1000000L; + _bits |= 0x2000000L; _headers._RetryAfter = value; return true; } @@ -9188,9 +9868,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.WWWAuthenticate, key)) { - if ((_bits & 0x10000000L) == 0) + if ((_bits & 0x20000000L) == 0) { - _bits |= 0x10000000L; + _bits |= 0x20000000L; _headers._WWWAuthenticate = value; return true; } @@ -9229,9 +9909,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.WWWAuthenticate.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x10000000L) == 0) + if ((_bits & 0x20000000L) == 0) { - _bits |= 0x10000000L; + _bits |= 0x20000000L; _headers._WWWAuthenticate = value; return true; } @@ -9270,9 +9950,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { if (ReferenceEquals(HeaderNames.ProxyAuthenticate, key)) { - if ((_bits & 0x800000L) == 0) + if ((_bits & 0x1000000L) == 0) { - _bits |= 0x800000L; + _bits |= 0x1000000L; _headers._ProxyAuthenticate = value; return true; } @@ -9281,9 +9961,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if (HeaderNames.ProxyAuthenticate.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x800000L) == 0) + if ((_bits & 0x1000000L) == 0) { - _bits |= 0x800000L; + _bits |= 0x1000000L; _headers._ProxyAuthenticate = value; return true; } @@ -9295,9 +9975,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { if (ReferenceEquals(HeaderNames.AccessControlMaxAge, key)) { - if ((_bits & 0x400000000L) == 0) + if ((_bits & 0x800000000L) == 0) { - _bits |= 0x400000000L; + _bits |= 0x800000000L; _headers._AccessControlMaxAge = value; return true; } @@ -9306,9 +9986,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if (HeaderNames.AccessControlMaxAge.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x400000000L) == 0) + if ((_bits & 0x800000000L) == 0) { - _bits |= 0x400000000L; + _bits |= 0x800000000L; _headers._AccessControlMaxAge = value; return true; } @@ -9320,9 +10000,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { if (ReferenceEquals(HeaderNames.AccessControlAllowOrigin, key)) { - if ((_bits & 0x100000000L) == 0) + if ((_bits & 0x200000000L) == 0) { - _bits |= 0x100000000L; + _bits |= 0x200000000L; _headers._AccessControlAllowOrigin = value; return true; } @@ -9331,9 +10011,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if (HeaderNames.AccessControlAllowOrigin.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x100000000L) == 0) + if ((_bits & 0x200000000L) == 0) { - _bits |= 0x100000000L; + _bits |= 0x200000000L; _headers._AccessControlAllowOrigin = value; return true; } @@ -9345,9 +10025,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { if (ReferenceEquals(HeaderNames.AccessControlAllowHeaders, key)) { - if ((_bits & 0x40000000L) == 0) + if ((_bits & 0x80000000L) == 0) { - _bits |= 0x40000000L; + _bits |= 0x80000000L; _headers._AccessControlAllowHeaders = value; return true; } @@ -9355,9 +10035,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.AccessControlAllowMethods, key)) { - if ((_bits & 0x80000000L) == 0) + if ((_bits & 0x100000000L) == 0) { - _bits |= 0x80000000L; + _bits |= 0x100000000L; _headers._AccessControlAllowMethods = value; return true; } @@ -9366,9 +10046,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if (HeaderNames.AccessControlAllowHeaders.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x40000000L) == 0) + if ((_bits & 0x80000000L) == 0) { - _bits |= 0x40000000L; + _bits |= 0x80000000L; _headers._AccessControlAllowHeaders = value; return true; } @@ -9376,9 +10056,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.AccessControlAllowMethods.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x80000000L) == 0) + if ((_bits & 0x100000000L) == 0) { - _bits |= 0x80000000L; + _bits |= 0x100000000L; _headers._AccessControlAllowMethods = value; return true; } @@ -9390,9 +10070,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { if (ReferenceEquals(HeaderNames.AccessControlExposeHeaders, key)) { - if ((_bits & 0x200000000L) == 0) + if ((_bits & 0x400000000L) == 0) { - _bits |= 0x200000000L; + _bits |= 0x400000000L; _headers._AccessControlExposeHeaders = value; return true; } @@ -9401,9 +10081,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if (HeaderNames.AccessControlExposeHeaders.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x200000000L) == 0) + if ((_bits & 0x400000000L) == 0) { - _bits |= 0x200000000L; + _bits |= 0x400000000L; _headers._AccessControlExposeHeaders = value; return true; } @@ -9415,9 +10095,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { if (ReferenceEquals(HeaderNames.AccessControlAllowCredentials, key)) { - if ((_bits & 0x20000000L) == 0) + if ((_bits & 0x40000000L) == 0) { - _bits |= 0x20000000L; + _bits |= 0x40000000L; _headers._AccessControlAllowCredentials = value; return true; } @@ -9426,9 +10106,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if (HeaderNames.AccessControlAllowCredentials.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x20000000L) == 0) + if ((_bits & 0x40000000L) == 0) { - _bits |= 0x20000000L; + _bits |= 0x40000000L; _headers._AccessControlAllowCredentials = value; return true; } @@ -9505,9 +10185,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.ETag, key)) { - if ((_bits & 0x200000L) != 0) + if ((_bits & 0x400000L) != 0) { - _bits &= ~0x200000L; + _bits &= ~0x400000L; _headers._ETag = default(StringValues); return true; } @@ -9515,9 +10195,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.Vary, key)) { - if ((_bits & 0x8000000L) != 0) + if ((_bits & 0x10000000L) != 0) { - _bits &= ~0x8000000L; + _bits &= ~0x10000000L; _headers._Vary = default(StringValues); return true; } @@ -9537,9 +10217,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.ETag.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x200000L) != 0) + if ((_bits & 0x400000L) != 0) { - _bits &= ~0x200000L; + _bits &= ~0x400000L; _headers._ETag = default(StringValues); return true; } @@ -9547,9 +10227,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.Vary.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x8000000L) != 0) + if ((_bits & 0x10000000L) != 0) { - _bits &= ~0x8000000L; + _bits &= ~0x10000000L; _headers._Vary = default(StringValues); return true; } @@ -9586,9 +10266,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { if (ReferenceEquals(HeaderNames.Server, key)) { - if ((_bits & 0x2000000L) != 0) + if ((_bits & 0x4000000L) != 0) { - _bits &= ~0x2000000L; + _bits &= ~0x4000000L; _headers._Server = default(StringValues); _headers._rawServer = null; return true; @@ -9608,9 +10288,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if (HeaderNames.Server.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x2000000L) != 0) + if ((_bits & 0x4000000L) != 0) { - _bits &= ~0x2000000L; + _bits &= ~0x4000000L; _headers._Server = default(StringValues); _headers._rawServer = null; return true; @@ -9671,6 +10351,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } return false; } + if (ReferenceEquals(HeaderNames.AltSvc, key)) + { + if ((_bits & 0x200000L) != 0) + { + _bits &= ~0x200000L; + _headers._AltSvc = default(StringValues); + return true; + } + return false; + } if (HeaderNames.Trailer.Equals(key, StringComparison.OrdinalIgnoreCase)) { @@ -9712,15 +10402,25 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } return false; } + if (HeaderNames.AltSvc.Equals(key, StringComparison.OrdinalIgnoreCase)) + { + if ((_bits & 0x200000L) != 0) + { + _bits &= ~0x200000L; + _headers._AltSvc = default(StringValues); + return true; + } + return false; + } break; } case 8: { if (ReferenceEquals(HeaderNames.Location, key)) { - if ((_bits & 0x400000L) != 0) + if ((_bits & 0x800000L) != 0) { - _bits &= ~0x400000L; + _bits &= ~0x800000L; _headers._Location = default(StringValues); return true; } @@ -9729,9 +10429,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if (HeaderNames.Location.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x400000L) != 0) + if ((_bits & 0x800000L) != 0) { - _bits &= ~0x400000L; + _bits &= ~0x800000L; _headers._Location = default(StringValues); return true; } @@ -9764,9 +10464,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.SetCookie, key)) { - if ((_bits & 0x4000000L) != 0) + if ((_bits & 0x8000000L) != 0) { - _bits &= ~0x4000000L; + _bits &= ~0x8000000L; _headers._SetCookie = default(StringValues); return true; } @@ -9796,9 +10496,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.SetCookie.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x4000000L) != 0) + if ((_bits & 0x8000000L) != 0) { - _bits &= ~0x4000000L; + _bits &= ~0x8000000L; _headers._SetCookie = default(StringValues); return true; } @@ -9820,9 +10520,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.RetryAfter, key)) { - if ((_bits & 0x1000000L) != 0) + if ((_bits & 0x2000000L) != 0) { - _bits &= ~0x1000000L; + _bits &= ~0x2000000L; _headers._RetryAfter = default(StringValues); return true; } @@ -9841,9 +10541,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.RetryAfter.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x1000000L) != 0) + if ((_bits & 0x2000000L) != 0) { - _bits &= ~0x1000000L; + _bits &= ~0x2000000L; _headers._RetryAfter = default(StringValues); return true; } @@ -10018,9 +10718,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.WWWAuthenticate, key)) { - if ((_bits & 0x10000000L) != 0) + if ((_bits & 0x20000000L) != 0) { - _bits &= ~0x10000000L; + _bits &= ~0x20000000L; _headers._WWWAuthenticate = default(StringValues); return true; } @@ -10059,9 +10759,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.WWWAuthenticate.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x10000000L) != 0) + if ((_bits & 0x20000000L) != 0) { - _bits &= ~0x10000000L; + _bits &= ~0x20000000L; _headers._WWWAuthenticate = default(StringValues); return true; } @@ -10100,9 +10800,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { if (ReferenceEquals(HeaderNames.ProxyAuthenticate, key)) { - if ((_bits & 0x800000L) != 0) + if ((_bits & 0x1000000L) != 0) { - _bits &= ~0x800000L; + _bits &= ~0x1000000L; _headers._ProxyAuthenticate = default(StringValues); return true; } @@ -10111,9 +10811,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if (HeaderNames.ProxyAuthenticate.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x800000L) != 0) + if ((_bits & 0x1000000L) != 0) { - _bits &= ~0x800000L; + _bits &= ~0x1000000L; _headers._ProxyAuthenticate = default(StringValues); return true; } @@ -10125,9 +10825,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { if (ReferenceEquals(HeaderNames.AccessControlMaxAge, key)) { - if ((_bits & 0x400000000L) != 0) + if ((_bits & 0x800000000L) != 0) { - _bits &= ~0x400000000L; + _bits &= ~0x800000000L; _headers._AccessControlMaxAge = default(StringValues); return true; } @@ -10136,9 +10836,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if (HeaderNames.AccessControlMaxAge.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x400000000L) != 0) + if ((_bits & 0x800000000L) != 0) { - _bits &= ~0x400000000L; + _bits &= ~0x800000000L; _headers._AccessControlMaxAge = default(StringValues); return true; } @@ -10150,9 +10850,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { if (ReferenceEquals(HeaderNames.AccessControlAllowOrigin, key)) { - if ((_bits & 0x100000000L) != 0) + if ((_bits & 0x200000000L) != 0) { - _bits &= ~0x100000000L; + _bits &= ~0x200000000L; _headers._AccessControlAllowOrigin = default(StringValues); return true; } @@ -10161,9 +10861,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if (HeaderNames.AccessControlAllowOrigin.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x100000000L) != 0) + if ((_bits & 0x200000000L) != 0) { - _bits &= ~0x100000000L; + _bits &= ~0x200000000L; _headers._AccessControlAllowOrigin = default(StringValues); return true; } @@ -10175,9 +10875,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { if (ReferenceEquals(HeaderNames.AccessControlAllowHeaders, key)) { - if ((_bits & 0x40000000L) != 0) + if ((_bits & 0x80000000L) != 0) { - _bits &= ~0x40000000L; + _bits &= ~0x80000000L; _headers._AccessControlAllowHeaders = default(StringValues); return true; } @@ -10185,9 +10885,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (ReferenceEquals(HeaderNames.AccessControlAllowMethods, key)) { - if ((_bits & 0x80000000L) != 0) + if ((_bits & 0x100000000L) != 0) { - _bits &= ~0x80000000L; + _bits &= ~0x100000000L; _headers._AccessControlAllowMethods = default(StringValues); return true; } @@ -10196,9 +10896,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if (HeaderNames.AccessControlAllowHeaders.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x40000000L) != 0) + if ((_bits & 0x80000000L) != 0) { - _bits &= ~0x40000000L; + _bits &= ~0x80000000L; _headers._AccessControlAllowHeaders = default(StringValues); return true; } @@ -10206,9 +10906,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } if (HeaderNames.AccessControlAllowMethods.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x80000000L) != 0) + if ((_bits & 0x100000000L) != 0) { - _bits &= ~0x80000000L; + _bits &= ~0x100000000L; _headers._AccessControlAllowMethods = default(StringValues); return true; } @@ -10220,9 +10920,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { if (ReferenceEquals(HeaderNames.AccessControlExposeHeaders, key)) { - if ((_bits & 0x200000000L) != 0) + if ((_bits & 0x400000000L) != 0) { - _bits &= ~0x200000000L; + _bits &= ~0x400000000L; _headers._AccessControlExposeHeaders = default(StringValues); return true; } @@ -10231,9 +10931,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if (HeaderNames.AccessControlExposeHeaders.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x200000000L) != 0) + if ((_bits & 0x400000000L) != 0) { - _bits &= ~0x200000000L; + _bits &= ~0x400000000L; _headers._AccessControlExposeHeaders = default(StringValues); return true; } @@ -10245,9 +10945,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { if (ReferenceEquals(HeaderNames.AccessControlAllowCredentials, key)) { - if ((_bits & 0x20000000L) != 0) + if ((_bits & 0x40000000L) != 0) { - _bits &= ~0x20000000L; + _bits &= ~0x40000000L; _headers._AccessControlAllowCredentials = default(StringValues); return true; } @@ -10256,9 +10956,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if (HeaderNames.AccessControlAllowCredentials.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x20000000L) != 0) + if ((_bits & 0x40000000L) != 0) { - _bits &= ~0x20000000L; + _bits &= ~0x40000000L; _headers._AccessControlAllowCredentials = default(StringValues); return true; } @@ -10312,14 +11012,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http tempBits &= ~0x800L; } - if ((tempBits & 0x2000000L) != 0) + if ((tempBits & 0x4000000L) != 0) { _headers._Server = default; - if((tempBits & ~0x2000000L) == 0) + if((tempBits & ~0x4000000L) == 0) { return; } - tempBits &= ~0x2000000L; + tempBits &= ~0x4000000L; } if ((tempBits & 0x1L) != 0) @@ -10504,7 +11204,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if ((tempBits & 0x200000L) != 0) { - _headers._ETag = default; + _headers._AltSvc = default; if((tempBits & ~0x200000L) == 0) { return; @@ -10514,7 +11214,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if ((tempBits & 0x400000L) != 0) { - _headers._Location = default; + _headers._ETag = default; if((tempBits & ~0x400000L) == 0) { return; @@ -10524,7 +11224,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if ((tempBits & 0x800000L) != 0) { - _headers._ProxyAuthenticate = default; + _headers._Location = default; if((tempBits & ~0x800000L) == 0) { return; @@ -10534,7 +11234,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if ((tempBits & 0x1000000L) != 0) { - _headers._RetryAfter = default; + _headers._ProxyAuthenticate = default; if((tempBits & ~0x1000000L) == 0) { return; @@ -10542,19 +11242,19 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http tempBits &= ~0x1000000L; } - if ((tempBits & 0x4000000L) != 0) + if ((tempBits & 0x2000000L) != 0) { - _headers._SetCookie = default; - if((tempBits & ~0x4000000L) == 0) + _headers._RetryAfter = default; + if((tempBits & ~0x2000000L) == 0) { return; } - tempBits &= ~0x4000000L; + tempBits &= ~0x2000000L; } if ((tempBits & 0x8000000L) != 0) { - _headers._Vary = default; + _headers._SetCookie = default; if((tempBits & ~0x8000000L) == 0) { return; @@ -10564,7 +11264,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if ((tempBits & 0x10000000L) != 0) { - _headers._WWWAuthenticate = default; + _headers._Vary = default; if((tempBits & ~0x10000000L) == 0) { return; @@ -10574,7 +11274,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if ((tempBits & 0x20000000L) != 0) { - _headers._AccessControlAllowCredentials = default; + _headers._WWWAuthenticate = default; if((tempBits & ~0x20000000L) == 0) { return; @@ -10584,7 +11284,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if ((tempBits & 0x40000000L) != 0) { - _headers._AccessControlAllowHeaders = default; + _headers._AccessControlAllowCredentials = default; if((tempBits & ~0x40000000L) == 0) { return; @@ -10594,7 +11294,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if ((tempBits & 0x80000000L) != 0) { - _headers._AccessControlAllowMethods = default; + _headers._AccessControlAllowHeaders = default; if((tempBits & ~0x80000000L) == 0) { return; @@ -10604,7 +11304,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if ((tempBits & 0x100000000L) != 0) { - _headers._AccessControlAllowOrigin = default; + _headers._AccessControlAllowMethods = default; if((tempBits & ~0x100000000L) == 0) { return; @@ -10614,7 +11314,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if ((tempBits & 0x200000000L) != 0) { - _headers._AccessControlExposeHeaders = default; + _headers._AccessControlAllowOrigin = default; if((tempBits & ~0x200000000L) == 0) { return; @@ -10624,7 +11324,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if ((tempBits & 0x400000000L) != 0) { - _headers._AccessControlMaxAge = default; + _headers._AccessControlExposeHeaders = default; if((tempBits & ~0x400000000L) == 0) { return; @@ -10632,6 +11332,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http tempBits &= ~0x400000000L; } + if ((tempBits & 0x800000000L) != 0) + { + _headers._AccessControlMaxAge = default; + if((tempBits & ~0x800000000L) == 0) + { + return; + } + tempBits &= ~0x800000000L; + } + } protected override bool CopyToFast(KeyValuePair[] array, int arrayIndex) @@ -10836,7 +11546,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { return false; } - array[arrayIndex] = new KeyValuePair(HeaderNames.ETag, _headers._ETag); + array[arrayIndex] = new KeyValuePair(HeaderNames.AltSvc, _headers._AltSvc); ++arrayIndex; } if ((_bits & 0x400000L) != 0) @@ -10845,7 +11555,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { return false; } - array[arrayIndex] = new KeyValuePair(HeaderNames.Location, _headers._Location); + array[arrayIndex] = new KeyValuePair(HeaderNames.ETag, _headers._ETag); ++arrayIndex; } if ((_bits & 0x800000L) != 0) @@ -10854,7 +11564,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { return false; } - array[arrayIndex] = new KeyValuePair(HeaderNames.ProxyAuthenticate, _headers._ProxyAuthenticate); + array[arrayIndex] = new KeyValuePair(HeaderNames.Location, _headers._Location); ++arrayIndex; } if ((_bits & 0x1000000L) != 0) @@ -10863,7 +11573,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { return false; } - array[arrayIndex] = new KeyValuePair(HeaderNames.RetryAfter, _headers._RetryAfter); + array[arrayIndex] = new KeyValuePair(HeaderNames.ProxyAuthenticate, _headers._ProxyAuthenticate); ++arrayIndex; } if ((_bits & 0x2000000L) != 0) @@ -10872,7 +11582,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { return false; } - array[arrayIndex] = new KeyValuePair(HeaderNames.Server, _headers._Server); + array[arrayIndex] = new KeyValuePair(HeaderNames.RetryAfter, _headers._RetryAfter); ++arrayIndex; } if ((_bits & 0x4000000L) != 0) @@ -10881,7 +11591,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { return false; } - array[arrayIndex] = new KeyValuePair(HeaderNames.SetCookie, _headers._SetCookie); + array[arrayIndex] = new KeyValuePair(HeaderNames.Server, _headers._Server); ++arrayIndex; } if ((_bits & 0x8000000L) != 0) @@ -10890,7 +11600,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { return false; } - array[arrayIndex] = new KeyValuePair(HeaderNames.Vary, _headers._Vary); + array[arrayIndex] = new KeyValuePair(HeaderNames.SetCookie, _headers._SetCookie); ++arrayIndex; } if ((_bits & 0x10000000L) != 0) @@ -10899,7 +11609,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { return false; } - array[arrayIndex] = new KeyValuePair(HeaderNames.WWWAuthenticate, _headers._WWWAuthenticate); + array[arrayIndex] = new KeyValuePair(HeaderNames.Vary, _headers._Vary); ++arrayIndex; } if ((_bits & 0x20000000L) != 0) @@ -10908,7 +11618,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { return false; } - array[arrayIndex] = new KeyValuePair(HeaderNames.AccessControlAllowCredentials, _headers._AccessControlAllowCredentials); + array[arrayIndex] = new KeyValuePair(HeaderNames.WWWAuthenticate, _headers._WWWAuthenticate); ++arrayIndex; } if ((_bits & 0x40000000L) != 0) @@ -10917,7 +11627,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { return false; } - array[arrayIndex] = new KeyValuePair(HeaderNames.AccessControlAllowHeaders, _headers._AccessControlAllowHeaders); + array[arrayIndex] = new KeyValuePair(HeaderNames.AccessControlAllowCredentials, _headers._AccessControlAllowCredentials); ++arrayIndex; } if ((_bits & 0x80000000L) != 0) @@ -10926,7 +11636,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { return false; } - array[arrayIndex] = new KeyValuePair(HeaderNames.AccessControlAllowMethods, _headers._AccessControlAllowMethods); + array[arrayIndex] = new KeyValuePair(HeaderNames.AccessControlAllowHeaders, _headers._AccessControlAllowHeaders); ++arrayIndex; } if ((_bits & 0x100000000L) != 0) @@ -10935,7 +11645,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { return false; } - array[arrayIndex] = new KeyValuePair(HeaderNames.AccessControlAllowOrigin, _headers._AccessControlAllowOrigin); + array[arrayIndex] = new KeyValuePair(HeaderNames.AccessControlAllowMethods, _headers._AccessControlAllowMethods); ++arrayIndex; } if ((_bits & 0x200000000L) != 0) @@ -10944,10 +11654,19 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { return false; } - array[arrayIndex] = new KeyValuePair(HeaderNames.AccessControlExposeHeaders, _headers._AccessControlExposeHeaders); + array[arrayIndex] = new KeyValuePair(HeaderNames.AccessControlAllowOrigin, _headers._AccessControlAllowOrigin); ++arrayIndex; } if ((_bits & 0x400000000L) != 0) + { + if (arrayIndex == array.Length) + { + return false; + } + array[arrayIndex] = new KeyValuePair(HeaderNames.AccessControlExposeHeaders, _headers._AccessControlExposeHeaders); + ++arrayIndex; + } + if ((_bits & 0x800000000L) != 0) { if (arrayIndex == array.Length) { @@ -11030,9 +11749,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } goto case 3; case 3: // Header: "Server" - if ((tempBits & 0x2000000L) != 0) + if ((tempBits & 0x4000000L) != 0) { - tempBits ^= 0x2000000L; + tempBits ^= 0x4000000L; if (_headers._rawServer != null) { output.Write(_headers._rawServer); @@ -11040,7 +11759,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http else { values = ref _headers._Server; - keyStart = 350; + keyStart = 361; keyLength = 10; next = 4; break; // OutputHeader @@ -11051,7 +11770,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if ((tempBits & 0x8000000000000000L) != 0) { tempBits ^= 0x8000000000000000L; - output.Write(HeaderBytes.Slice(592, 18)); + output.Write(HeaderBytes.Slice(603, 18)); output.WriteNumeric((ulong)ContentLength.Value); if (tempBits == 0) { @@ -11264,148 +11983,159 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http break; // OutputHeader } goto case 23; - case 23: // Header: "ETag" + case 23: // Header: "Alt-Svc" if ((tempBits & 0x200000L) != 0) { tempBits ^= 0x200000L; - values = ref _headers._ETag; + values = ref _headers._AltSvc; keyStart = 293; - keyLength = 8; + keyLength = 11; next = 24; break; // OutputHeader } goto case 24; - case 24: // Header: "Location" + case 24: // Header: "ETag" if ((tempBits & 0x400000L) != 0) { tempBits ^= 0x400000L; - values = ref _headers._Location; - keyStart = 301; - keyLength = 12; + values = ref _headers._ETag; + keyStart = 304; + keyLength = 8; next = 25; break; // OutputHeader } goto case 25; - case 25: // Header: "Proxy-Authenticate" + case 25: // Header: "Location" if ((tempBits & 0x800000L) != 0) { tempBits ^= 0x800000L; - values = ref _headers._ProxyAuthenticate; - keyStart = 313; - keyLength = 22; + values = ref _headers._Location; + keyStart = 312; + keyLength = 12; next = 26; break; // OutputHeader } goto case 26; - case 26: // Header: "Retry-After" + case 26: // Header: "Proxy-Authenticate" if ((tempBits & 0x1000000L) != 0) { tempBits ^= 0x1000000L; - values = ref _headers._RetryAfter; - keyStart = 335; - keyLength = 15; + values = ref _headers._ProxyAuthenticate; + keyStart = 324; + keyLength = 22; next = 27; break; // OutputHeader } goto case 27; - case 27: // Header: "Set-Cookie" - if ((tempBits & 0x4000000L) != 0) + case 27: // Header: "Retry-After" + if ((tempBits & 0x2000000L) != 0) { - tempBits ^= 0x4000000L; - values = ref _headers._SetCookie; - keyStart = 360; - keyLength = 14; + tempBits ^= 0x2000000L; + values = ref _headers._RetryAfter; + keyStart = 346; + keyLength = 15; next = 28; break; // OutputHeader } goto case 28; - case 28: // Header: "Vary" + case 28: // Header: "Set-Cookie" if ((tempBits & 0x8000000L) != 0) { tempBits ^= 0x8000000L; - values = ref _headers._Vary; - keyStart = 374; - keyLength = 8; + values = ref _headers._SetCookie; + keyStart = 371; + keyLength = 14; next = 29; break; // OutputHeader } goto case 29; - case 29: // Header: "WWW-Authenticate" + case 29: // Header: "Vary" if ((tempBits & 0x10000000L) != 0) { tempBits ^= 0x10000000L; - values = ref _headers._WWWAuthenticate; - keyStart = 382; - keyLength = 20; + values = ref _headers._Vary; + keyStart = 385; + keyLength = 8; next = 30; break; // OutputHeader } goto case 30; - case 30: // Header: "Access-Control-Allow-Credentials" + case 30: // Header: "WWW-Authenticate" if ((tempBits & 0x20000000L) != 0) { tempBits ^= 0x20000000L; - values = ref _headers._AccessControlAllowCredentials; - keyStart = 402; - keyLength = 36; + values = ref _headers._WWWAuthenticate; + keyStart = 393; + keyLength = 20; next = 31; break; // OutputHeader } goto case 31; - case 31: // Header: "Access-Control-Allow-Headers" + case 31: // Header: "Access-Control-Allow-Credentials" if ((tempBits & 0x40000000L) != 0) { tempBits ^= 0x40000000L; - values = ref _headers._AccessControlAllowHeaders; - keyStart = 438; - keyLength = 32; + values = ref _headers._AccessControlAllowCredentials; + keyStart = 413; + keyLength = 36; next = 32; break; // OutputHeader } goto case 32; - case 32: // Header: "Access-Control-Allow-Methods" + case 32: // Header: "Access-Control-Allow-Headers" if ((tempBits & 0x80000000L) != 0) { tempBits ^= 0x80000000L; - values = ref _headers._AccessControlAllowMethods; - keyStart = 470; + values = ref _headers._AccessControlAllowHeaders; + keyStart = 449; keyLength = 32; next = 33; break; // OutputHeader } goto case 33; - case 33: // Header: "Access-Control-Allow-Origin" + case 33: // Header: "Access-Control-Allow-Methods" if ((tempBits & 0x100000000L) != 0) { tempBits ^= 0x100000000L; - values = ref _headers._AccessControlAllowOrigin; - keyStart = 502; - keyLength = 31; + values = ref _headers._AccessControlAllowMethods; + keyStart = 481; + keyLength = 32; next = 34; break; // OutputHeader } goto case 34; - case 34: // Header: "Access-Control-Expose-Headers" + case 34: // Header: "Access-Control-Allow-Origin" if ((tempBits & 0x200000000L) != 0) { tempBits ^= 0x200000000L; - values = ref _headers._AccessControlExposeHeaders; - keyStart = 533; - keyLength = 33; + values = ref _headers._AccessControlAllowOrigin; + keyStart = 513; + keyLength = 31; next = 35; break; // OutputHeader } goto case 35; - case 35: // Header: "Access-Control-Max-Age" + case 35: // Header: "Access-Control-Expose-Headers" if ((tempBits & 0x400000000L) != 0) { tempBits ^= 0x400000000L; - values = ref _headers._AccessControlMaxAge; - keyStart = 566; - keyLength = 26; + values = ref _headers._AccessControlExposeHeaders; + keyStart = 544; + keyLength = 33; next = 36; break; // OutputHeader } + goto case 36; + case 36: // Header: "Access-Control-Max-Age" + if ((tempBits & 0x800000000L) != 0) + { + tempBits ^= 0x800000000L; + values = ref _headers._AccessControlMaxAge; + keyStart = 577; + keyLength = 26; + next = 37; + break; // OutputHeader + } return; default: return; @@ -11421,7 +12151,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if (value != null) { output.Write(headerKey); - output.WriteAsciiNoValidation(value); + output.WriteAscii(value); } } } @@ -11451,6 +12181,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http public StringValues _LastModified; public StringValues _AcceptRanges; public StringValues _Age; + public StringValues _AltSvc; public StringValues _ETag; public StringValues _Location; public StringValues _ProxyAuthenticate; @@ -11522,34 +12253,36 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http case 20: goto HeaderAge; case 21: - goto HeaderETag; + goto HeaderAltSvc; case 22: - goto HeaderLocation; + goto HeaderETag; case 23: - goto HeaderProxyAuthenticate; + goto HeaderLocation; case 24: - goto HeaderRetryAfter; + goto HeaderProxyAuthenticate; case 25: - goto HeaderServer; + goto HeaderRetryAfter; case 26: - goto HeaderSetCookie; + goto HeaderServer; case 27: - goto HeaderVary; + goto HeaderSetCookie; case 28: - goto HeaderWWWAuthenticate; + goto HeaderVary; case 29: - goto HeaderAccessControlAllowCredentials; + goto HeaderWWWAuthenticate; case 30: - goto HeaderAccessControlAllowHeaders; + goto HeaderAccessControlAllowCredentials; case 31: - goto HeaderAccessControlAllowMethods; + goto HeaderAccessControlAllowHeaders; case 32: - goto HeaderAccessControlAllowOrigin; + goto HeaderAccessControlAllowMethods; case 33: - goto HeaderAccessControlExposeHeaders; + goto HeaderAccessControlAllowOrigin; case 34: - goto HeaderAccessControlMaxAge; + goto HeaderAccessControlExposeHeaders; case 35: + goto HeaderAccessControlMaxAge; + case 36: goto HeaderContentLength; default: goto ExtraHeaders; @@ -11559,6 +12292,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if ((_bits & 0x1L) != 0) { _current = new KeyValuePair(HeaderNames.CacheControl, _collection._headers._CacheControl); + _currentKnownType = KnownHeaderType.CacheControl; _next = 1; return true; } @@ -11566,6 +12300,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if ((_bits & 0x2L) != 0) { _current = new KeyValuePair(HeaderNames.Connection, _collection._headers._Connection); + _currentKnownType = KnownHeaderType.Connection; _next = 2; return true; } @@ -11573,6 +12308,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if ((_bits & 0x4L) != 0) { _current = new KeyValuePair(HeaderNames.Date, _collection._headers._Date); + _currentKnownType = KnownHeaderType.Date; _next = 3; return true; } @@ -11580,6 +12316,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if ((_bits & 0x8L) != 0) { _current = new KeyValuePair(HeaderNames.KeepAlive, _collection._headers._KeepAlive); + _currentKnownType = KnownHeaderType.KeepAlive; _next = 4; return true; } @@ -11587,6 +12324,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if ((_bits & 0x10L) != 0) { _current = new KeyValuePair(HeaderNames.Pragma, _collection._headers._Pragma); + _currentKnownType = KnownHeaderType.Pragma; _next = 5; return true; } @@ -11594,6 +12332,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if ((_bits & 0x20L) != 0) { _current = new KeyValuePair(HeaderNames.Trailer, _collection._headers._Trailer); + _currentKnownType = KnownHeaderType.Trailer; _next = 6; return true; } @@ -11601,6 +12340,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if ((_bits & 0x40L) != 0) { _current = new KeyValuePair(HeaderNames.TransferEncoding, _collection._headers._TransferEncoding); + _currentKnownType = KnownHeaderType.TransferEncoding; _next = 7; return true; } @@ -11608,6 +12348,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if ((_bits & 0x80L) != 0) { _current = new KeyValuePair(HeaderNames.Upgrade, _collection._headers._Upgrade); + _currentKnownType = KnownHeaderType.Upgrade; _next = 8; return true; } @@ -11615,6 +12356,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if ((_bits & 0x100L) != 0) { _current = new KeyValuePair(HeaderNames.Via, _collection._headers._Via); + _currentKnownType = KnownHeaderType.Via; _next = 9; return true; } @@ -11622,6 +12364,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if ((_bits & 0x200L) != 0) { _current = new KeyValuePair(HeaderNames.Warning, _collection._headers._Warning); + _currentKnownType = KnownHeaderType.Warning; _next = 10; return true; } @@ -11629,6 +12372,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if ((_bits & 0x400L) != 0) { _current = new KeyValuePair(HeaderNames.Allow, _collection._headers._Allow); + _currentKnownType = KnownHeaderType.Allow; _next = 11; return true; } @@ -11636,6 +12380,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if ((_bits & 0x800L) != 0) { _current = new KeyValuePair(HeaderNames.ContentType, _collection._headers._ContentType); + _currentKnownType = KnownHeaderType.ContentType; _next = 12; return true; } @@ -11643,6 +12388,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if ((_bits & 0x1000L) != 0) { _current = new KeyValuePair(HeaderNames.ContentEncoding, _collection._headers._ContentEncoding); + _currentKnownType = KnownHeaderType.ContentEncoding; _next = 13; return true; } @@ -11650,6 +12396,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if ((_bits & 0x2000L) != 0) { _current = new KeyValuePair(HeaderNames.ContentLanguage, _collection._headers._ContentLanguage); + _currentKnownType = KnownHeaderType.ContentLanguage; _next = 14; return true; } @@ -11657,6 +12404,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if ((_bits & 0x4000L) != 0) { _current = new KeyValuePair(HeaderNames.ContentLocation, _collection._headers._ContentLocation); + _currentKnownType = KnownHeaderType.ContentLocation; _next = 15; return true; } @@ -11664,6 +12412,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if ((_bits & 0x8000L) != 0) { _current = new KeyValuePair(HeaderNames.ContentMD5, _collection._headers._ContentMD5); + _currentKnownType = KnownHeaderType.ContentMD5; _next = 16; return true; } @@ -11671,6 +12420,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if ((_bits & 0x10000L) != 0) { _current = new KeyValuePair(HeaderNames.ContentRange, _collection._headers._ContentRange); + _currentKnownType = KnownHeaderType.ContentRange; _next = 17; return true; } @@ -11678,6 +12428,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if ((_bits & 0x20000L) != 0) { _current = new KeyValuePair(HeaderNames.Expires, _collection._headers._Expires); + _currentKnownType = KnownHeaderType.Expires; _next = 18; return true; } @@ -11685,6 +12436,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if ((_bits & 0x40000L) != 0) { _current = new KeyValuePair(HeaderNames.LastModified, _collection._headers._LastModified); + _currentKnownType = KnownHeaderType.LastModified; _next = 19; return true; } @@ -11692,6 +12444,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if ((_bits & 0x80000L) != 0) { _current = new KeyValuePair(HeaderNames.AcceptRanges, _collection._headers._AcceptRanges); + _currentKnownType = KnownHeaderType.AcceptRanges; _next = 20; return true; } @@ -11699,121 +12452,147 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if ((_bits & 0x100000L) != 0) { _current = new KeyValuePair(HeaderNames.Age, _collection._headers._Age); + _currentKnownType = KnownHeaderType.Age; _next = 21; return true; } - HeaderETag: // case 21 + HeaderAltSvc: // case 21 if ((_bits & 0x200000L) != 0) { - _current = new KeyValuePair(HeaderNames.ETag, _collection._headers._ETag); + _current = new KeyValuePair(HeaderNames.AltSvc, _collection._headers._AltSvc); + _currentKnownType = KnownHeaderType.AltSvc; _next = 22; return true; } - HeaderLocation: // case 22 + HeaderETag: // case 22 if ((_bits & 0x400000L) != 0) { - _current = new KeyValuePair(HeaderNames.Location, _collection._headers._Location); + _current = new KeyValuePair(HeaderNames.ETag, _collection._headers._ETag); + _currentKnownType = KnownHeaderType.ETag; _next = 23; return true; } - HeaderProxyAuthenticate: // case 23 + HeaderLocation: // case 23 if ((_bits & 0x800000L) != 0) { - _current = new KeyValuePair(HeaderNames.ProxyAuthenticate, _collection._headers._ProxyAuthenticate); + _current = new KeyValuePair(HeaderNames.Location, _collection._headers._Location); + _currentKnownType = KnownHeaderType.Location; _next = 24; return true; } - HeaderRetryAfter: // case 24 + HeaderProxyAuthenticate: // case 24 if ((_bits & 0x1000000L) != 0) { - _current = new KeyValuePair(HeaderNames.RetryAfter, _collection._headers._RetryAfter); + _current = new KeyValuePair(HeaderNames.ProxyAuthenticate, _collection._headers._ProxyAuthenticate); + _currentKnownType = KnownHeaderType.ProxyAuthenticate; _next = 25; return true; } - HeaderServer: // case 25 + HeaderRetryAfter: // case 25 if ((_bits & 0x2000000L) != 0) { - _current = new KeyValuePair(HeaderNames.Server, _collection._headers._Server); + _current = new KeyValuePair(HeaderNames.RetryAfter, _collection._headers._RetryAfter); + _currentKnownType = KnownHeaderType.RetryAfter; _next = 26; return true; } - HeaderSetCookie: // case 26 + HeaderServer: // case 26 if ((_bits & 0x4000000L) != 0) { - _current = new KeyValuePair(HeaderNames.SetCookie, _collection._headers._SetCookie); + _current = new KeyValuePair(HeaderNames.Server, _collection._headers._Server); + _currentKnownType = KnownHeaderType.Server; _next = 27; return true; } - HeaderVary: // case 27 + HeaderSetCookie: // case 27 if ((_bits & 0x8000000L) != 0) { - _current = new KeyValuePair(HeaderNames.Vary, _collection._headers._Vary); + _current = new KeyValuePair(HeaderNames.SetCookie, _collection._headers._SetCookie); + _currentKnownType = KnownHeaderType.SetCookie; _next = 28; return true; } - HeaderWWWAuthenticate: // case 28 + HeaderVary: // case 28 if ((_bits & 0x10000000L) != 0) { - _current = new KeyValuePair(HeaderNames.WWWAuthenticate, _collection._headers._WWWAuthenticate); + _current = new KeyValuePair(HeaderNames.Vary, _collection._headers._Vary); + _currentKnownType = KnownHeaderType.Vary; _next = 29; return true; } - HeaderAccessControlAllowCredentials: // case 29 + HeaderWWWAuthenticate: // case 29 if ((_bits & 0x20000000L) != 0) { - _current = new KeyValuePair(HeaderNames.AccessControlAllowCredentials, _collection._headers._AccessControlAllowCredentials); + _current = new KeyValuePair(HeaderNames.WWWAuthenticate, _collection._headers._WWWAuthenticate); + _currentKnownType = KnownHeaderType.WWWAuthenticate; _next = 30; return true; } - HeaderAccessControlAllowHeaders: // case 30 + HeaderAccessControlAllowCredentials: // case 30 if ((_bits & 0x40000000L) != 0) { - _current = new KeyValuePair(HeaderNames.AccessControlAllowHeaders, _collection._headers._AccessControlAllowHeaders); + _current = new KeyValuePair(HeaderNames.AccessControlAllowCredentials, _collection._headers._AccessControlAllowCredentials); + _currentKnownType = KnownHeaderType.AccessControlAllowCredentials; _next = 31; return true; } - HeaderAccessControlAllowMethods: // case 31 + HeaderAccessControlAllowHeaders: // case 31 if ((_bits & 0x80000000L) != 0) { - _current = new KeyValuePair(HeaderNames.AccessControlAllowMethods, _collection._headers._AccessControlAllowMethods); + _current = new KeyValuePair(HeaderNames.AccessControlAllowHeaders, _collection._headers._AccessControlAllowHeaders); + _currentKnownType = KnownHeaderType.AccessControlAllowHeaders; _next = 32; return true; } - HeaderAccessControlAllowOrigin: // case 32 + HeaderAccessControlAllowMethods: // case 32 if ((_bits & 0x100000000L) != 0) { - _current = new KeyValuePair(HeaderNames.AccessControlAllowOrigin, _collection._headers._AccessControlAllowOrigin); + _current = new KeyValuePair(HeaderNames.AccessControlAllowMethods, _collection._headers._AccessControlAllowMethods); + _currentKnownType = KnownHeaderType.AccessControlAllowMethods; _next = 33; return true; } - HeaderAccessControlExposeHeaders: // case 33 + HeaderAccessControlAllowOrigin: // case 33 if ((_bits & 0x200000000L) != 0) { - _current = new KeyValuePair(HeaderNames.AccessControlExposeHeaders, _collection._headers._AccessControlExposeHeaders); + _current = new KeyValuePair(HeaderNames.AccessControlAllowOrigin, _collection._headers._AccessControlAllowOrigin); + _currentKnownType = KnownHeaderType.AccessControlAllowOrigin; _next = 34; return true; } - HeaderAccessControlMaxAge: // case 34 + HeaderAccessControlExposeHeaders: // case 34 if ((_bits & 0x400000000L) != 0) { - _current = new KeyValuePair(HeaderNames.AccessControlMaxAge, _collection._headers._AccessControlMaxAge); + _current = new KeyValuePair(HeaderNames.AccessControlExposeHeaders, _collection._headers._AccessControlExposeHeaders); + _currentKnownType = KnownHeaderType.AccessControlExposeHeaders; _next = 35; return true; } - HeaderContentLength: // case 35 + HeaderAccessControlMaxAge: // case 35 + if ((_bits & 0x800000000L) != 0) + { + _current = new KeyValuePair(HeaderNames.AccessControlMaxAge, _collection._headers._AccessControlMaxAge); + _currentKnownType = KnownHeaderType.AccessControlMaxAge; + _next = 36; + return true; + } + HeaderContentLength: // case 36 if (_collection._contentLength.HasValue) { _current = new KeyValuePair(HeaderNames.ContentLength, HeaderUtilities.FormatNonNegativeInt64(_collection._contentLength.Value)); - _next = 36; + _currentKnownType = KnownHeaderType.ContentLength; + _next = 37; return true; } ExtraHeaders: if (!_hasUnknown || !_unknownEnumerator.MoveNext()) { _current = default(KeyValuePair); + _currentKnownType = default; return false; } _current = _unknownEnumerator.Current; + _currentKnownType = KnownHeaderType.Unknown; return true; } } @@ -12059,6 +12838,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if ((_bits & 0x1L) != 0) { _current = new KeyValuePair(HeaderNames.ETag, _collection._headers._ETag); + _currentKnownType = KnownHeaderType.ETag; _next = 1; return true; } @@ -12067,9 +12847,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if (!_hasUnknown || !_unknownEnumerator.MoveNext()) { _current = default(KeyValuePair); + _currentKnownType = default; return false; } _current = _unknownEnumerator.Current; + _currentKnownType = KnownHeaderType.Unknown; return true; } } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/HttpHeaders.cs b/src/Servers/Kestrel/Core/src/Internal/Http/HttpHeaders.cs index d041705a19..e1175fcde9 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/HttpHeaders.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/HttpHeaders.cs @@ -27,6 +27,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http get { return _contentLength; } set { + if (_isReadOnly) + { + ThrowHeadersReadOnlyException(); + } if (value.HasValue && value.Value < 0) { ThrowInvalidContentLengthException(value.Value); diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/HttpParser.cs b/src/Servers/Kestrel/Core/src/Internal/Http/HttpParser.cs index 2fe5dfdb36..ff905ca079 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/HttpParser.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/HttpParser.cs @@ -4,6 +4,7 @@ using System; using System.Buffers; using System.Diagnostics; +using System.Net.Http; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; @@ -31,6 +32,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http private const byte ByteTab = (byte)'\t'; private const byte ByteQuestionMark = (byte)'?'; private const byte BytePercentage = (byte)'%'; + private const int MinTlsRequestSize = 1; // We need at least 1 byte to check for a proper TLS request line public unsafe bool ParseRequestLine(TRequestHandler handler, in ReadOnlySequence buffer, out SequencePosition consumed, out SequencePosition examined) { @@ -38,7 +40,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http examined = buffer.End; // Prepare the first span - var span = buffer.First.Span; + var span = buffer.FirstSpan; var lineIndex = span.IndexOf(ByteLF); if (lineIndex >= 0) { @@ -211,7 +213,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } // Double CRLF found, so end of headers. - handler.OnHeadersComplete(); + handler.OnHeadersComplete(endStream: false); return true; } else if (readAhead == 1) @@ -239,7 +241,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { TakeSingleHeader(pHeader, length, handler); } - // Read the header sucessfully, skip the reader forward past the header line. + // Read the header successfully, skip the reader forward past the header line. reader.Advance(length); span = span.Slice(length); } @@ -415,9 +417,29 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http return new Span(data, methodLength); } + private unsafe bool IsTlsHandshake(byte* data, int length) + { + const byte SslRecordTypeHandshake = (byte)0x16; + + // Make sure we can check at least for the existence of a TLS handshake - we check the first byte + // See https://serializethoughts.com/2014/07/27/dissecting-tls-client-hello-message/ + + return (length >= MinTlsRequestSize && data[0] == SslRecordTypeHandshake); + } + [StackTraceHidden] private unsafe void RejectRequestLine(byte* requestLine, int length) - => throw GetInvalidRequestException(RequestRejectionReason.InvalidRequestLine, requestLine, length); + { + // Check for incoming TLS handshake over HTTP + if (IsTlsHandshake(requestLine, length)) + { + throw GetInvalidRequestException(RequestRejectionReason.TlsOverHttpError, requestLine, length); + } + else + { + throw GetInvalidRequestException(RequestRejectionReason.InvalidRequestLine, requestLine, length); + } + } [StackTraceHidden] private unsafe void RejectRequestHeader(byte* headerLine, int length) diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.FeatureCollection.cs b/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.FeatureCollection.cs index 88ae92e0c0..8841be09fa 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.FeatureCollection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.FeatureCollection.cs @@ -37,8 +37,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http string IHttpRequestFeature.Protocol { - get => HttpVersion; - set => HttpVersion = value; + get => _httpProtocol ??= HttpVersion; + set => _httpProtocol = value; } string IHttpRequestFeature.Scheme diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.Generated.cs b/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.Generated.cs index 514a1ef7a5..f6833a4748 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.Generated.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.Generated.cs @@ -13,34 +13,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { internal partial class HttpProtocol : IFeatureCollection { - private static readonly Type IHttpRequestFeatureType = typeof(IHttpRequestFeature); - private static readonly Type IHttpResponseFeatureType = typeof(IHttpResponseFeature); - private static readonly Type IHttpResponseBodyFeatureType = typeof(IHttpResponseBodyFeature); - private static readonly Type IRequestBodyPipeFeatureType = typeof(IRequestBodyPipeFeature); - private static readonly Type IHttpRequestIdentifierFeatureType = typeof(IHttpRequestIdentifierFeature); - private static readonly Type IServiceProvidersFeatureType = typeof(IServiceProvidersFeature); - private static readonly Type IHttpRequestLifetimeFeatureType = typeof(IHttpRequestLifetimeFeature); - private static readonly Type IHttpConnectionFeatureType = typeof(IHttpConnectionFeature); - private static readonly Type IRouteValuesFeatureType = typeof(IRouteValuesFeature); - private static readonly Type IEndpointFeatureType = typeof(IEndpointFeature); - private static readonly Type IHttpAuthenticationFeatureType = typeof(IHttpAuthenticationFeature); - private static readonly Type IHttpRequestTrailersFeatureType = typeof(IHttpRequestTrailersFeature); - private static readonly Type IQueryFeatureType = typeof(IQueryFeature); - private static readonly Type IFormFeatureType = typeof(IFormFeature); - private static readonly Type IHttpUpgradeFeatureType = typeof(IHttpUpgradeFeature); - private static readonly Type IHttp2StreamIdFeatureType = typeof(IHttp2StreamIdFeature); - private static readonly Type IHttpResponseTrailersFeatureType = typeof(IHttpResponseTrailersFeature); - private static readonly Type IResponseCookiesFeatureType = typeof(IResponseCookiesFeature); - private static readonly Type IItemsFeatureType = typeof(IItemsFeature); - private static readonly Type ITlsConnectionFeatureType = typeof(ITlsConnectionFeature); - private static readonly Type IHttpWebSocketFeatureType = typeof(IHttpWebSocketFeature); - private static readonly Type ISessionFeatureType = typeof(ISessionFeature); - private static readonly Type IHttpMaxRequestBodySizeFeatureType = typeof(IHttpMaxRequestBodySizeFeature); - private static readonly Type IHttpMinRequestBodyDataRateFeatureType = typeof(IHttpMinRequestBodyDataRateFeature); - private static readonly Type IHttpMinResponseDataRateFeatureType = typeof(IHttpMinResponseDataRateFeature); - private static readonly Type IHttpBodyControlFeatureType = typeof(IHttpBodyControlFeature); - private static readonly Type IHttpResetFeatureType = typeof(IHttpResetFeature); - private object _currentIHttpRequestFeature; private object _currentIHttpResponseFeature; private object _currentIHttpResponseBodyFeature; @@ -157,111 +129,111 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http get { object feature = null; - if (key == IHttpRequestFeatureType) + if (key == typeof(IHttpRequestFeature)) { feature = _currentIHttpRequestFeature; } - else if (key == IHttpResponseFeatureType) + else if (key == typeof(IHttpResponseFeature)) { feature = _currentIHttpResponseFeature; } - else if (key == IHttpResponseBodyFeatureType) + else if (key == typeof(IHttpResponseBodyFeature)) { feature = _currentIHttpResponseBodyFeature; } - else if (key == IRequestBodyPipeFeatureType) + else if (key == typeof(IRequestBodyPipeFeature)) { feature = _currentIRequestBodyPipeFeature; } - else if (key == IHttpRequestIdentifierFeatureType) + else if (key == typeof(IHttpRequestIdentifierFeature)) { feature = _currentIHttpRequestIdentifierFeature; } - else if (key == IServiceProvidersFeatureType) + else if (key == typeof(IServiceProvidersFeature)) { feature = _currentIServiceProvidersFeature; } - else if (key == IHttpRequestLifetimeFeatureType) + else if (key == typeof(IHttpRequestLifetimeFeature)) { feature = _currentIHttpRequestLifetimeFeature; } - else if (key == IHttpConnectionFeatureType) + else if (key == typeof(IHttpConnectionFeature)) { feature = _currentIHttpConnectionFeature; } - else if (key == IRouteValuesFeatureType) + else if (key == typeof(IRouteValuesFeature)) { feature = _currentIRouteValuesFeature; } - else if (key == IEndpointFeatureType) + else if (key == typeof(IEndpointFeature)) { feature = _currentIEndpointFeature; } - else if (key == IHttpAuthenticationFeatureType) + else if (key == typeof(IHttpAuthenticationFeature)) { feature = _currentIHttpAuthenticationFeature; } - else if (key == IHttpRequestTrailersFeatureType) + else if (key == typeof(IHttpRequestTrailersFeature)) { feature = _currentIHttpRequestTrailersFeature; } - else if (key == IQueryFeatureType) + else if (key == typeof(IQueryFeature)) { feature = _currentIQueryFeature; } - else if (key == IFormFeatureType) + else if (key == typeof(IFormFeature)) { feature = _currentIFormFeature; } - else if (key == IHttpUpgradeFeatureType) + else if (key == typeof(IHttpUpgradeFeature)) { feature = _currentIHttpUpgradeFeature; } - else if (key == IHttp2StreamIdFeatureType) + else if (key == typeof(IHttp2StreamIdFeature)) { feature = _currentIHttp2StreamIdFeature; } - else if (key == IHttpResponseTrailersFeatureType) + else if (key == typeof(IHttpResponseTrailersFeature)) { feature = _currentIHttpResponseTrailersFeature; } - else if (key == IResponseCookiesFeatureType) + else if (key == typeof(IResponseCookiesFeature)) { feature = _currentIResponseCookiesFeature; } - else if (key == IItemsFeatureType) + else if (key == typeof(IItemsFeature)) { feature = _currentIItemsFeature; } - else if (key == ITlsConnectionFeatureType) + else if (key == typeof(ITlsConnectionFeature)) { feature = _currentITlsConnectionFeature; } - else if (key == IHttpWebSocketFeatureType) + else if (key == typeof(IHttpWebSocketFeature)) { feature = _currentIHttpWebSocketFeature; } - else if (key == ISessionFeatureType) + else if (key == typeof(ISessionFeature)) { feature = _currentISessionFeature; } - else if (key == IHttpMaxRequestBodySizeFeatureType) + else if (key == typeof(IHttpMaxRequestBodySizeFeature)) { feature = _currentIHttpMaxRequestBodySizeFeature; } - else if (key == IHttpMinRequestBodyDataRateFeatureType) + else if (key == typeof(IHttpMinRequestBodyDataRateFeature)) { feature = _currentIHttpMinRequestBodyDataRateFeature; } - else if (key == IHttpMinResponseDataRateFeatureType) + else if (key == typeof(IHttpMinResponseDataRateFeature)) { feature = _currentIHttpMinResponseDataRateFeature; } - else if (key == IHttpBodyControlFeatureType) + else if (key == typeof(IHttpBodyControlFeature)) { feature = _currentIHttpBodyControlFeature; } - else if (key == IHttpResetFeatureType) + else if (key == typeof(IHttpResetFeature)) { feature = _currentIHttpResetFeature; } @@ -277,111 +249,111 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { _featureRevision++; - if (key == IHttpRequestFeatureType) + if (key == typeof(IHttpRequestFeature)) { _currentIHttpRequestFeature = value; } - else if (key == IHttpResponseFeatureType) + else if (key == typeof(IHttpResponseFeature)) { _currentIHttpResponseFeature = value; } - else if (key == IHttpResponseBodyFeatureType) + else if (key == typeof(IHttpResponseBodyFeature)) { _currentIHttpResponseBodyFeature = value; } - else if (key == IRequestBodyPipeFeatureType) + else if (key == typeof(IRequestBodyPipeFeature)) { _currentIRequestBodyPipeFeature = value; } - else if (key == IHttpRequestIdentifierFeatureType) + else if (key == typeof(IHttpRequestIdentifierFeature)) { _currentIHttpRequestIdentifierFeature = value; } - else if (key == IServiceProvidersFeatureType) + else if (key == typeof(IServiceProvidersFeature)) { _currentIServiceProvidersFeature = value; } - else if (key == IHttpRequestLifetimeFeatureType) + else if (key == typeof(IHttpRequestLifetimeFeature)) { _currentIHttpRequestLifetimeFeature = value; } - else if (key == IHttpConnectionFeatureType) + else if (key == typeof(IHttpConnectionFeature)) { _currentIHttpConnectionFeature = value; } - else if (key == IRouteValuesFeatureType) + else if (key == typeof(IRouteValuesFeature)) { _currentIRouteValuesFeature = value; } - else if (key == IEndpointFeatureType) + else if (key == typeof(IEndpointFeature)) { _currentIEndpointFeature = value; } - else if (key == IHttpAuthenticationFeatureType) + else if (key == typeof(IHttpAuthenticationFeature)) { _currentIHttpAuthenticationFeature = value; } - else if (key == IHttpRequestTrailersFeatureType) + else if (key == typeof(IHttpRequestTrailersFeature)) { _currentIHttpRequestTrailersFeature = value; } - else if (key == IQueryFeatureType) + else if (key == typeof(IQueryFeature)) { _currentIQueryFeature = value; } - else if (key == IFormFeatureType) + else if (key == typeof(IFormFeature)) { _currentIFormFeature = value; } - else if (key == IHttpUpgradeFeatureType) + else if (key == typeof(IHttpUpgradeFeature)) { _currentIHttpUpgradeFeature = value; } - else if (key == IHttp2StreamIdFeatureType) + else if (key == typeof(IHttp2StreamIdFeature)) { _currentIHttp2StreamIdFeature = value; } - else if (key == IHttpResponseTrailersFeatureType) + else if (key == typeof(IHttpResponseTrailersFeature)) { _currentIHttpResponseTrailersFeature = value; } - else if (key == IResponseCookiesFeatureType) + else if (key == typeof(IResponseCookiesFeature)) { _currentIResponseCookiesFeature = value; } - else if (key == IItemsFeatureType) + else if (key == typeof(IItemsFeature)) { _currentIItemsFeature = value; } - else if (key == ITlsConnectionFeatureType) + else if (key == typeof(ITlsConnectionFeature)) { _currentITlsConnectionFeature = value; } - else if (key == IHttpWebSocketFeatureType) + else if (key == typeof(IHttpWebSocketFeature)) { _currentIHttpWebSocketFeature = value; } - else if (key == ISessionFeatureType) + else if (key == typeof(ISessionFeature)) { _currentISessionFeature = value; } - else if (key == IHttpMaxRequestBodySizeFeatureType) + else if (key == typeof(IHttpMaxRequestBodySizeFeature)) { _currentIHttpMaxRequestBodySizeFeature = value; } - else if (key == IHttpMinRequestBodyDataRateFeatureType) + else if (key == typeof(IHttpMinRequestBodyDataRateFeature)) { _currentIHttpMinRequestBodyDataRateFeature = value; } - else if (key == IHttpMinResponseDataRateFeatureType) + else if (key == typeof(IHttpMinResponseDataRateFeature)) { _currentIHttpMinResponseDataRateFeature = value; } - else if (key == IHttpBodyControlFeatureType) + else if (key == typeof(IHttpBodyControlFeature)) { _currentIHttpBodyControlFeature = value; } - else if (key == IHttpResetFeatureType) + else if (key == typeof(IHttpResetFeature)) { _currentIHttpResetFeature = value; } @@ -637,111 +609,111 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { if (_currentIHttpRequestFeature != null) { - yield return new KeyValuePair(IHttpRequestFeatureType, _currentIHttpRequestFeature); + yield return new KeyValuePair(typeof(IHttpRequestFeature), _currentIHttpRequestFeature); } if (_currentIHttpResponseFeature != null) { - yield return new KeyValuePair(IHttpResponseFeatureType, _currentIHttpResponseFeature); + yield return new KeyValuePair(typeof(IHttpResponseFeature), _currentIHttpResponseFeature); } if (_currentIHttpResponseBodyFeature != null) { - yield return new KeyValuePair(IHttpResponseBodyFeatureType, _currentIHttpResponseBodyFeature); + yield return new KeyValuePair(typeof(IHttpResponseBodyFeature), _currentIHttpResponseBodyFeature); } if (_currentIRequestBodyPipeFeature != null) { - yield return new KeyValuePair(IRequestBodyPipeFeatureType, _currentIRequestBodyPipeFeature); + yield return new KeyValuePair(typeof(IRequestBodyPipeFeature), _currentIRequestBodyPipeFeature); } if (_currentIHttpRequestIdentifierFeature != null) { - yield return new KeyValuePair(IHttpRequestIdentifierFeatureType, _currentIHttpRequestIdentifierFeature); + yield return new KeyValuePair(typeof(IHttpRequestIdentifierFeature), _currentIHttpRequestIdentifierFeature); } if (_currentIServiceProvidersFeature != null) { - yield return new KeyValuePair(IServiceProvidersFeatureType, _currentIServiceProvidersFeature); + yield return new KeyValuePair(typeof(IServiceProvidersFeature), _currentIServiceProvidersFeature); } if (_currentIHttpRequestLifetimeFeature != null) { - yield return new KeyValuePair(IHttpRequestLifetimeFeatureType, _currentIHttpRequestLifetimeFeature); + yield return new KeyValuePair(typeof(IHttpRequestLifetimeFeature), _currentIHttpRequestLifetimeFeature); } if (_currentIHttpConnectionFeature != null) { - yield return new KeyValuePair(IHttpConnectionFeatureType, _currentIHttpConnectionFeature); + yield return new KeyValuePair(typeof(IHttpConnectionFeature), _currentIHttpConnectionFeature); } if (_currentIRouteValuesFeature != null) { - yield return new KeyValuePair(IRouteValuesFeatureType, _currentIRouteValuesFeature); + yield return new KeyValuePair(typeof(IRouteValuesFeature), _currentIRouteValuesFeature); } if (_currentIEndpointFeature != null) { - yield return new KeyValuePair(IEndpointFeatureType, _currentIEndpointFeature); + yield return new KeyValuePair(typeof(IEndpointFeature), _currentIEndpointFeature); } if (_currentIHttpAuthenticationFeature != null) { - yield return new KeyValuePair(IHttpAuthenticationFeatureType, _currentIHttpAuthenticationFeature); + yield return new KeyValuePair(typeof(IHttpAuthenticationFeature), _currentIHttpAuthenticationFeature); } if (_currentIHttpRequestTrailersFeature != null) { - yield return new KeyValuePair(IHttpRequestTrailersFeatureType, _currentIHttpRequestTrailersFeature); + yield return new KeyValuePair(typeof(IHttpRequestTrailersFeature), _currentIHttpRequestTrailersFeature); } if (_currentIQueryFeature != null) { - yield return new KeyValuePair(IQueryFeatureType, _currentIQueryFeature); + yield return new KeyValuePair(typeof(IQueryFeature), _currentIQueryFeature); } if (_currentIFormFeature != null) { - yield return new KeyValuePair(IFormFeatureType, _currentIFormFeature); + yield return new KeyValuePair(typeof(IFormFeature), _currentIFormFeature); } if (_currentIHttpUpgradeFeature != null) { - yield return new KeyValuePair(IHttpUpgradeFeatureType, _currentIHttpUpgradeFeature); + yield return new KeyValuePair(typeof(IHttpUpgradeFeature), _currentIHttpUpgradeFeature); } if (_currentIHttp2StreamIdFeature != null) { - yield return new KeyValuePair(IHttp2StreamIdFeatureType, _currentIHttp2StreamIdFeature); + yield return new KeyValuePair(typeof(IHttp2StreamIdFeature), _currentIHttp2StreamIdFeature); } if (_currentIHttpResponseTrailersFeature != null) { - yield return new KeyValuePair(IHttpResponseTrailersFeatureType, _currentIHttpResponseTrailersFeature); + yield return new KeyValuePair(typeof(IHttpResponseTrailersFeature), _currentIHttpResponseTrailersFeature); } if (_currentIResponseCookiesFeature != null) { - yield return new KeyValuePair(IResponseCookiesFeatureType, _currentIResponseCookiesFeature); + yield return new KeyValuePair(typeof(IResponseCookiesFeature), _currentIResponseCookiesFeature); } if (_currentIItemsFeature != null) { - yield return new KeyValuePair(IItemsFeatureType, _currentIItemsFeature); + yield return new KeyValuePair(typeof(IItemsFeature), _currentIItemsFeature); } if (_currentITlsConnectionFeature != null) { - yield return new KeyValuePair(ITlsConnectionFeatureType, _currentITlsConnectionFeature); + yield return new KeyValuePair(typeof(ITlsConnectionFeature), _currentITlsConnectionFeature); } if (_currentIHttpWebSocketFeature != null) { - yield return new KeyValuePair(IHttpWebSocketFeatureType, _currentIHttpWebSocketFeature); + yield return new KeyValuePair(typeof(IHttpWebSocketFeature), _currentIHttpWebSocketFeature); } if (_currentISessionFeature != null) { - yield return new KeyValuePair(ISessionFeatureType, _currentISessionFeature); + yield return new KeyValuePair(typeof(ISessionFeature), _currentISessionFeature); } if (_currentIHttpMaxRequestBodySizeFeature != null) { - yield return new KeyValuePair(IHttpMaxRequestBodySizeFeatureType, _currentIHttpMaxRequestBodySizeFeature); + yield return new KeyValuePair(typeof(IHttpMaxRequestBodySizeFeature), _currentIHttpMaxRequestBodySizeFeature); } if (_currentIHttpMinRequestBodyDataRateFeature != null) { - yield return new KeyValuePair(IHttpMinRequestBodyDataRateFeatureType, _currentIHttpMinRequestBodyDataRateFeature); + yield return new KeyValuePair(typeof(IHttpMinRequestBodyDataRateFeature), _currentIHttpMinRequestBodyDataRateFeature); } if (_currentIHttpMinResponseDataRateFeature != null) { - yield return new KeyValuePair(IHttpMinResponseDataRateFeatureType, _currentIHttpMinResponseDataRateFeature); + yield return new KeyValuePair(typeof(IHttpMinResponseDataRateFeature), _currentIHttpMinResponseDataRateFeature); } if (_currentIHttpBodyControlFeature != null) { - yield return new KeyValuePair(IHttpBodyControlFeatureType, _currentIHttpBodyControlFeature); + yield return new KeyValuePair(typeof(IHttpBodyControlFeature), _currentIHttpBodyControlFeature); } if (_currentIHttpResetFeature != null) { - yield return new KeyValuePair(IHttpResetFeatureType, _currentIHttpResetFeature); + yield return new KeyValuePair(typeof(IHttpResetFeature), _currentIHttpResetFeature); } if (MaybeExtra != null) diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.cs b/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.cs index 0dc3170b08..539c386efa 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.cs @@ -8,6 +8,7 @@ using System.IO; using System.IO.Pipelines; using System.Linq; using System.Net; +using System.Net.Http.Headers; using System.Runtime.CompilerServices; using System.Text; using System.Threading; @@ -37,7 +38,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http private Stack, object>> _onCompleted; private readonly object _abortLock = new object(); - private volatile bool _connectionAborted; + protected volatile bool _connectionAborted; private bool _preventRequestAbortedCancellation; private CancellationTokenSource _abortedCts; private CancellationToken? _manuallySetRequestAbortToken; @@ -57,13 +58,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http private BadHttpRequestException _requestRejectedException; protected HttpVersion _httpVersion; + // This should only be used by the application, not the server. This is settable on HttpRequest but we don't want that to affect + // how Kestrel processes requests/responses. + private string _httpProtocol; private string _requestId; private int _requestHeadersParsed; private long _responseBytesWritten; - private readonly HttpConnectionContext _context; + private HttpConnectionContext _context; private RouteValueDictionary _routeValues; private Endpoint _endpoint; @@ -72,15 +76,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http private Stream _requestStreamInternal; private Stream _responseStreamInternal; - public HttpProtocol(HttpConnectionContext context) + public void Initialize(HttpConnectionContext context) { _context = context; ServerOptions = ServiceContext.ServerOptions; - HttpRequestHeaders = new HttpRequestHeaders( - reuseHeaderValues: !ServerOptions.DisableStringReuse, - useLatin1: ServerOptions.Latin1RequestHeaders); + Reset(); HttpResponseControl = this; } @@ -98,7 +100,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http protected IKestrelTrace Log => ServiceContext.Log; private DateHeaderValueManager DateHeaderValueManager => ServiceContext.DateHeaderValueManager; // Hold direct reference to ServerOptions since this is used very often in the request processing path - protected KestrelServerOptions ServerOptions { get; } + protected KestrelServerOptions ServerOptions { get; set; } protected string ConnectionId => _context.ConnectionId; public string ConnectionIdFeature { get; set; } @@ -142,17 +144,21 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { get { - if (_httpVersion == Http.HttpVersion.Http11) + if (_httpVersion == Http.HttpVersion.Http3) { - return HttpUtilities.Http11Version; - } - if (_httpVersion == Http.HttpVersion.Http10) - { - return HttpUtilities.Http10Version; + return AspNetCore.Http.HttpProtocol.Http3; } if (_httpVersion == Http.HttpVersion.Http2) { - return HttpUtilities.Http2Version; + return AspNetCore.Http.HttpProtocol.Http2; + } + if (_httpVersion == Http.HttpVersion.Http11) + { + return AspNetCore.Http.HttpProtocol.Http11; + } + if (_httpVersion == Http.HttpVersion.Http10) + { + return AspNetCore.Http.HttpProtocol.Http10; } return string.Empty; @@ -163,18 +169,22 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { // GetKnownVersion returns versions which ReferenceEquals interned string // As most common path, check for this only in fast-path and inline - if (ReferenceEquals(value, HttpUtilities.Http11Version)) + if (ReferenceEquals(value, AspNetCore.Http.HttpProtocol.Http3)) + { + _httpVersion = Http.HttpVersion.Http3; + } + else if (ReferenceEquals(value, AspNetCore.Http.HttpProtocol.Http2)) + { + _httpVersion = Http.HttpVersion.Http2; + } + else if (ReferenceEquals(value, AspNetCore.Http.HttpProtocol.Http11)) { _httpVersion = Http.HttpVersion.Http11; } - else if (ReferenceEquals(value, HttpUtilities.Http10Version)) + else if (ReferenceEquals(value, AspNetCore.Http.HttpProtocol.Http10)) { _httpVersion = Http.HttpVersion.Http10; } - else if (ReferenceEquals(value, HttpUtilities.Http2Version)) - { - _httpVersion = Http.HttpVersion.Http2; - } else { HttpVersionSetSlow(value); @@ -185,18 +195,22 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http [MethodImpl(MethodImplOptions.NoInlining)] private void HttpVersionSetSlow(string value) { - if (value == HttpUtilities.Http11Version) + if (AspNetCore.Http.HttpProtocol.IsHttp3(value)) + { + _httpVersion = Http.HttpVersion.Http3; + } + else if (AspNetCore.Http.HttpProtocol.IsHttp2(value)) + { + _httpVersion = Http.HttpVersion.Http2; + } + else if (AspNetCore.Http.HttpProtocol.IsHttp11(value)) { _httpVersion = Http.HttpVersion.Http11; } - else if (value == HttpUtilities.Http10Version) + else if (AspNetCore.Http.HttpProtocol.IsHttp10(value)) { _httpVersion = Http.HttpVersion.Http10; } - else if (value == HttpUtilities.Http2Version) - { - _httpVersion = Http.HttpVersion.Http2; - } else { _httpVersion = Http.HttpVersion.Unknown; @@ -290,7 +304,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http public bool HasResponseCompleted => _requestProcessingStatus == RequestProcessingStatus.ResponseCompleted; - protected HttpRequestHeaders HttpRequestHeaders { get; } + protected HttpRequestHeaders HttpRequestHeaders { get; set; } = new HttpRequestHeaders(); protected HttpResponseHeaders HttpResponseHeaders { get; } = new HttpResponseHeaders(); @@ -338,13 +352,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http RawTarget = null; QueryString = null; _httpVersion = Http.HttpVersion.Unknown; + _httpProtocol = null; _statusCode = StatusCodes.Status200OK; _reasonPhrase = null; var remoteEndPoint = RemoteEndPoint; RemoteIpAddress = remoteEndPoint?.Address; RemotePort = remoteEndPoint?.Port ?? 0; - var localEndPoint = LocalEndPoint; LocalIpAddress = localEndPoint?.Address; LocalPort = localEndPoint?.Port ?? 0; @@ -352,10 +366,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http ConnectionIdFeature = ConnectionId; HttpRequestHeaders.Reset(); + HttpRequestHeaders.UseLatin1 = ServerOptions.Latin1RequestHeaders; + HttpRequestHeaders.ReuseHeaderValues = !ServerOptions.DisableStringReuse; HttpResponseHeaders.Reset(); RequestHeaders = HttpRequestHeaders; ResponseHeaders = HttpResponseHeaders; RequestTrailers.Clear(); + ResponseTrailers?.Reset(); RequestTrailersAvailable = false; _isLeasedMemoryInvalid = true; @@ -491,7 +508,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } } - public void OnHeader(Span name, Span value) + public virtual void OnHeader(ReadOnlySpan name, ReadOnlySpan value) { _requestHeadersParsed++; if (_requestHeadersParsed > ServerOptions.Limits.MaxRequestHeaderCount) @@ -502,7 +519,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http HttpRequestHeaders.Append(name, value); } - public void OnTrailer(Span name, Span value) + public void OnTrailer(ReadOnlySpan name, ReadOnlySpan value) { // Trailers still count towards the limit. _requestHeadersParsed++; @@ -1043,7 +1060,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http private Task WriteSuffix() { - if (_autoChunk || _httpVersion == Http.HttpVersion.Http2) + if (_autoChunk || _httpVersion >= Http.HttpVersion.Http2) { // For the same reason we call CheckLastWrite() in Content-Length responses. PreventRequestAbortedCancellation(); @@ -1159,7 +1176,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http responseHeaders.SetReadOnly(); - if (!hasConnection && _httpVersion != Http.HttpVersion.Http2) + if (!hasConnection && _httpVersion < Http.HttpVersion.Http2) { if (!_keepAlive) { @@ -1171,6 +1188,19 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } } + // TODO allow customization of this. + if (ServerOptions.EnableAltSvc && _httpVersion < Http.HttpVersion.Http3) + { + foreach (var option in ServerOptions.ListenOptions) + { + if ((option.Protocols & HttpProtocols.Http3) == HttpProtocols.Http3) + { + responseHeaders.HeaderAltSvc = $"h3-25=\":{option.IPEndPoint.Port}\"; ma=84600"; + break; + } + } + } + if (ServerOptions.AddServerHeader && !responseHeaders.HasServer) { responseHeaders.SetRawServer(Constants.ServerName, _bytesServer); diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/HttpRequestHeaders.cs b/src/Servers/Kestrel/Core/src/Internal/Http/HttpRequestHeaders.cs index af631f6442..a5e78a4442 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/HttpRequestHeaders.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/HttpRequestHeaders.cs @@ -14,14 +14,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { internal sealed partial class HttpRequestHeaders : HttpHeaders { - private readonly bool _reuseHeaderValues; - private readonly bool _useLatin1; private long _previousBits = 0; + public bool ReuseHeaderValues { get; set; } + public bool UseLatin1 { get; set; } + public HttpRequestHeaders(bool reuseHeaderValues = true, bool useLatin1 = false) { - _reuseHeaderValues = reuseHeaderValues; - _useLatin1 = useLatin1; + ReuseHeaderValues = reuseHeaderValues; + UseLatin1 = useLatin1; } public void OnHeadersComplete() @@ -42,7 +43,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http protected override void ClearFast() { - if (!_reuseHeaderValues) + if (!ReuseHeaderValues) { // If we aren't reusing headers clear them all Clear(_bits); @@ -71,7 +72,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } [MethodImpl(MethodImplOptions.NoInlining)] - private void AppendContentLength(Span value) + private void AppendContentLength(ReadOnlySpan value) { if (_contentLength.HasValue) { @@ -82,7 +83,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http parsed < 0 || consumed != value.Length) { - BadHttpRequestException.Throw(RequestRejectionReason.InvalidContentLength, value.GetRequestHeaderStringNonNullCharacters(_useLatin1)); + BadHttpRequestException.Throw(RequestRejectionReason.InvalidContentLength, value.GetRequestHeaderStringNonNullCharacters(UseLatin1)); } _contentLength = parsed; @@ -103,7 +104,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } [MethodImpl(MethodImplOptions.NoInlining)] - private unsafe void AppendUnknownHeaders(Span name, string valueString) + private unsafe void AppendUnknownHeaders(ReadOnlySpan name, string valueString) { string key = name.GetHeaderName(); Unknown.TryGetValue(key, out var existing); @@ -126,6 +127,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http private readonly long _bits; private int _next; private KeyValuePair _current; + private KnownHeaderType _currentKnownType; private readonly bool _hasUnknown; private Dictionary.Enumerator _unknownEnumerator; @@ -135,6 +137,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http _bits = collection._bits; _next = 0; _current = default; + _currentKnownType = default; _hasUnknown = collection.MaybeUnknown != null; _unknownEnumerator = _hasUnknown ? collection.MaybeUnknown.GetEnumerator() @@ -143,6 +146,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http public KeyValuePair Current => _current; + internal KnownHeaderType CurrentKnownType => _currentKnownType; + object IEnumerator.Current => _current; public void Dispose() diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/HttpRequestStream.cs b/src/Servers/Kestrel/Core/src/Internal/Http/HttpRequestStream.cs index 4d5513293e..a1b30fb940 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/HttpRequestStream.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/HttpRequestStream.cs @@ -90,41 +90,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state) { - var task = ReadAsync(buffer, offset, count, default, state); - if (callback != null) - { - task.ContinueWith(t => callback.Invoke(t)); - } - return task; + return TaskToApm.Begin(ReadAsync(buffer, offset, count), callback, state); } /// public override int EndRead(IAsyncResult asyncResult) { - return ((Task)asyncResult).GetAwaiter().GetResult(); - } - - private Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken, object state) - { - var tcs = new TaskCompletionSource(state); - var task = ReadAsync(buffer, offset, count, cancellationToken); - task.ContinueWith((task2, state2) => - { - var tcs2 = (TaskCompletionSource)state2; - if (task2.IsCanceled) - { - tcs2.SetCanceled(); - } - else if (task2.IsFaulted) - { - tcs2.SetException(task2.Exception); - } - else - { - tcs2.SetResult(task2.Result); - } - }, tcs, cancellationToken); - return tcs.Task; + return TaskToApm.End(asyncResult); } private ValueTask ReadAsyncWrapper(Memory destination, CancellationToken cancellationToken) @@ -139,7 +111,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } } - private async ValueTask ReadAsyncInternal(Memory buffer, CancellationToken cancellationToken) + private async ValueTask ReadAsyncInternal(Memory destination, CancellationToken cancellationToken) { while (true) { @@ -150,19 +122,19 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http throw new OperationCanceledException("The read was canceled"); } - var readableBuffer = result.Buffer; - var readableBufferLength = readableBuffer.Length; + var buffer = result.Buffer; + var length = buffer.Length; - var consumed = readableBuffer.End; + var consumed = buffer.End; try { - if (readableBufferLength != 0) + if (length != 0) { - var actual = (int)Math.Min(readableBufferLength, buffer.Length); + var actual = (int)Math.Min(length, destination.Length); - var slice = actual == readableBufferLength ? readableBuffer : readableBuffer.Slice(0, actual); + var slice = actual == length ? buffer : buffer.Slice(0, actual); consumed = slice.End; - slice.CopyTo(buffer.Span); + slice.CopyTo(destination.Span); return actual; } @@ -193,37 +165,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http throw new ArgumentOutOfRangeException(nameof(bufferSize)); } - return CopyToAsyncInternal(destination, cancellationToken); - } - - private async Task CopyToAsyncInternal(Stream destination, CancellationToken cancellationToken) - { - while (true) - { - var result = await _pipeReader.ReadAsync(cancellationToken); - var readableBuffer = result.Buffer; - var readableBufferLength = readableBuffer.Length; - - try - { - if (readableBufferLength != 0) - { - foreach (var memory in readableBuffer) - { - await destination.WriteAsync(memory, cancellationToken); - } - } - - if (result.IsCompleted) - { - return; - } - } - finally - { - _pipeReader.AdvanceTo(readableBuffer.End); - } - } + return _pipeReader.CopyToAsync(destination, cancellationToken); } } } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/HttpResponseHeaders.cs b/src/Servers/Kestrel/Core/src/Internal/Http/HttpResponseHeaders.cs index 0af675600f..e2ae921259 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/HttpResponseHeaders.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/HttpResponseHeaders.cs @@ -14,6 +14,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { internal sealed partial class HttpResponseHeaders : HttpHeaders { + // This uses C# compiler's ability to refer to static data directly. For more information see https://vcsjones.dev/2019/02/01/csharp-readonly-span-bytes-static private static ReadOnlySpan CrLf => new[] { (byte)'\r', (byte)'\n' }; private static ReadOnlySpan ColonSpace => new[] { (byte)':', (byte)' ' }; @@ -47,9 +48,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if (value != null) { buffer.Write(CrLf); - buffer.WriteAsciiNoValidation(kv.Key); + buffer.WriteAscii(kv.Key); buffer.Write(ColonSpace); - buffer.WriteAsciiNoValidation(value); + buffer.WriteAscii(value); } } } @@ -93,6 +94,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http private readonly long _bits; private int _next; private KeyValuePair _current; + private KnownHeaderType _currentKnownType; private readonly bool _hasUnknown; private Dictionary.Enumerator _unknownEnumerator; @@ -102,6 +104,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http _bits = collection._bits; _next = 0; _current = default; + _currentKnownType = default; _hasUnknown = collection.MaybeUnknown != null; _unknownEnumerator = _hasUnknown ? collection.MaybeUnknown.GetEnumerator() @@ -110,6 +113,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http public KeyValuePair Current => _current; + internal KnownHeaderType CurrentKnownType => _currentKnownType; + object IEnumerator.Current => _current; public void Dispose() diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/HttpResponseStream.cs b/src/Servers/Kestrel/Core/src/Internal/Http/HttpResponseStream.cs index 2bf5017278..86ddb9b157 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/HttpResponseStream.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/HttpResponseStream.cs @@ -3,7 +3,6 @@ using System; using System.IO; -using System.IO.Pipelines; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Http.Features; @@ -87,40 +86,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state) { - var task = WriteAsync(buffer, offset, count, default, state); - if (callback != null) - { - task.ContinueWith(t => callback.Invoke(t)); - } - return task; + return TaskToApm.Begin(WriteAsync(buffer, offset, count), callback, state); } public override void EndWrite(IAsyncResult asyncResult) { - ((Task)asyncResult).GetAwaiter().GetResult(); - } - - private Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken, object state) - { - var tcs = new TaskCompletionSource(state); - var task = WriteAsync(buffer, offset, count, cancellationToken); - task.ContinueWith((task2, state2) => - { - var tcs2 = (TaskCompletionSource)state2; - if (task2.IsCanceled) - { - tcs2.SetCanceled(); - } - else if (task2.IsFaulted) - { - tcs2.SetException(task2.Exception); - } - else - { - tcs2.SetResult(null); - } - }, tcs, cancellationToken); - return tcs.Task; + TaskToApm.End(asyncResult); } public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/HttpResponseTrailers.cs b/src/Servers/Kestrel/Core/src/Internal/Http/HttpResponseTrailers.cs index ec4144cc83..6bae47c690 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/HttpResponseTrailers.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/HttpResponseTrailers.cs @@ -43,6 +43,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http private readonly long _bits; private int _next; private KeyValuePair _current; + private KnownHeaderType _currentKnownType; private readonly bool _hasUnknown; private Dictionary.Enumerator _unknownEnumerator; @@ -52,6 +53,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http _bits = collection._bits; _next = 0; _current = default; + _currentKnownType = default; _hasUnknown = collection.MaybeUnknown != null; _unknownEnumerator = _hasUnknown ? collection.MaybeUnknown.GetEnumerator() @@ -60,6 +62,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http public KeyValuePair Current => _current; + internal KnownHeaderType CurrentKnownType => _currentKnownType; + object IEnumerator.Current => _current; public void Dispose() diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/HttpVersion.cs b/src/Servers/Kestrel/Core/src/Internal/Http/HttpVersion.cs index 832a1c5616..e6e2a0dabf 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/HttpVersion.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/HttpVersion.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http @@ -8,6 +8,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http Unknown = -1, Http10 = 0, Http11 = 1, - Http2 + Http2 = 2, + Http3 = 3 } } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/IHttpParser.cs b/src/Servers/Kestrel/Core/src/Internal/Http/IHttpParser.cs index 18837ccd0a..20688fe291 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/IHttpParser.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/IHttpParser.cs @@ -3,10 +3,11 @@ using System; using System.Buffers; +using System.Net.Http; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { - public interface IHttpParser where TRequestHandler : IHttpHeadersHandler, IHttpRequestLineHandler + internal interface IHttpParser where TRequestHandler : IHttpHeadersHandler, IHttpRequestLineHandler { bool ParseRequestLine(TRequestHandler handler, in ReadOnlySequence buffer, out SequencePosition consumed, out SequencePosition examined); diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/MessageBody.cs b/src/Servers/Kestrel/Core/src/Internal/Http/MessageBody.cs index bae364ca56..e97712f0ae 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/MessageBody.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/MessageBody.cs @@ -6,6 +6,7 @@ using System.IO.Pipelines; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; +using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { @@ -72,6 +73,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http protected virtual Task OnStopAsync() => Task.CompletedTask; + public virtual void Reset() + { + _send100Continue = true; + _consumedBytes = 0; + _stopped = false; + _timingEnabled = false; + _backpressure = false; + _alreadyTimedBytes = 0; + _examinedUnconsumedBytes = 0; + } + protected void TryProduceContinue() { if (_send100Continue) @@ -93,7 +105,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if (!RequestUpgrade) { - Log.RequestBodyStart(_context.ConnectionIdFeature, _context.TraceIdentifier); + // Accessing TraceIdentifier will lazy-allocate a string ID. + // Don't access TraceIdentifer unless logging is enabled. + if (Log.IsEnabled(LogLevel.Debug)) + { + Log.RequestBodyStart(_context.ConnectionIdFeature, _context.TraceIdentifier); + } if (_context.MinRequestBodyDataRate != null) { @@ -116,7 +133,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if (!RequestUpgrade) { - Log.RequestBodyDone(_context.ConnectionIdFeature, _context.TraceIdentifier); + // Accessing TraceIdentifier will lazy-allocate a string ID + // Don't access TraceIdentifer unless logging is enabled. + if (Log.IsEnabled(LogLevel.Debug)) + { + Log.RequestBodyDone(_context.ConnectionIdFeature, _context.TraceIdentifier); + } if (_timingEnabled) { diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/RequestRejectionReason.cs b/src/Servers/Kestrel/Core/src/Internal/Http/RequestRejectionReason.cs index fce21b6210..23dc6c67c6 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/RequestRejectionReason.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/RequestRejectionReason.cs @@ -1,10 +1,11 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { internal enum RequestRejectionReason { + TlsOverHttpError, UnrecognizedHTTPVersion, InvalidRequestLine, InvalidRequestHeader, diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/FlowControl/AwaitableProvider.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/FlowControl/AwaitableProvider.cs new file mode 100644 index 0000000000..c765b22824 --- /dev/null +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/FlowControl/AwaitableProvider.cs @@ -0,0 +1,92 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Diagnostics; +using System.Threading.Tasks.Sources; + +namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.FlowControl +{ + internal abstract class AwaitableProvider + { + public abstract ManualResetValueTaskSource GetAwaitable(); + public abstract void CompleteCurrent(); + public abstract int ActiveCount { get; } + } + + /// + /// Provider returns multiple awaitables. Awaitables are completed FIFO. + /// + internal class MultipleAwaitableProvider : AwaitableProvider + { + private Queue> _awaitableQueue; + private Queue> _awaitableCache; + + public override void CompleteCurrent() + { + var awaitable = _awaitableQueue.Dequeue(); + awaitable.TrySetResult(null); + + // Add completed awaitable to the cache for reuse + _awaitableCache.Enqueue(awaitable); + } + + public override ManualResetValueTaskSource GetAwaitable() + { + if (_awaitableQueue == null) + { + _awaitableQueue = new Queue>(); + _awaitableCache = new Queue>(); + } + + // First attempt to reuse an existing awaitable in the queue + // to save allocating a new instance. + if (_awaitableCache.TryDequeue(out var awaitable)) + { + // Reset previously used awaitable + Debug.Assert(awaitable.GetStatus() == ValueTaskSourceStatus.Succeeded, "Previous awaitable should have been completed."); + awaitable.Reset(); + } + else + { + awaitable = new ManualResetValueTaskSource(); + } + + _awaitableQueue.Enqueue(awaitable); + + return awaitable; + } + + public override int ActiveCount => _awaitableQueue?.Count ?? 0; + } + + /// + /// Provider has a single awaitable. + /// + internal class SingleAwaitableProvider : AwaitableProvider + { + private ManualResetValueTaskSource _awaitable; + + public override void CompleteCurrent() + { + _awaitable.TrySetResult(null); + } + + public override ManualResetValueTaskSource GetAwaitable() + { + if (_awaitable == null) + { + _awaitable = new ManualResetValueTaskSource(); + } + else + { + Debug.Assert(_awaitable.GetStatus() == ValueTaskSourceStatus.Succeeded, "Previous awaitable should have been completed."); + _awaitable.Reset(); + } + + return _awaitable; + } + + public override int ActiveCount => _awaitable != null && _awaitable.GetStatus() != ValueTaskSourceStatus.Succeeded ? 1 : 0; + } +} diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/FlowControl/InputFlowControl.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/FlowControl/InputFlowControl.cs index 7231f882b7..3ee7ca5848 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/FlowControl/InputFlowControl.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/FlowControl/InputFlowControl.cs @@ -26,6 +26,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.FlowControl public bool IsAvailabilityLow => _flow.Available < _minWindowSizeIncrement; + public void Reset() + { + _flow = new FlowControl((uint)_initialWindowSize); + _pendingUpdateSize = 0; + _windowUpdatesDisabled = false; + } + public bool TryAdvance(int bytes) { lock (_flowLock) diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/FlowControl/OutputFlowControl.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/FlowControl/OutputFlowControl.cs index 609398d3d7..198aacb46b 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/FlowControl/OutputFlowControl.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/FlowControl/OutputFlowControl.cs @@ -3,40 +3,43 @@ using System.Collections.Generic; using System.Diagnostics; +using System.Threading.Tasks.Sources; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.FlowControl { internal class OutputFlowControl { private FlowControl _flow; - private Queue _awaitableQueue; + private readonly AwaitableProvider _awaitableProvider; - public OutputFlowControl(uint initialWindowSize) + public OutputFlowControl(AwaitableProvider awaitableProvider, uint initialWindowSize) { _flow = new FlowControl(initialWindowSize); + _awaitableProvider = awaitableProvider; } public int Available => _flow.Available; public bool IsAborted => _flow.IsAborted; - public OutputFlowControlAwaitable AvailabilityAwaitable + public ManualResetValueTaskSource AvailabilityAwaitable { get { Debug.Assert(!_flow.IsAborted, $"({nameof(AvailabilityAwaitable)} accessed after abort."); Debug.Assert(_flow.Available <= 0, $"({nameof(AvailabilityAwaitable)} accessed with {Available} bytes available."); - if (_awaitableQueue == null) - { - _awaitableQueue = new Queue(); - } - - var awaitable = new OutputFlowControlAwaitable(); - _awaitableQueue.Enqueue(awaitable); - return awaitable; + return _awaitableProvider.GetAwaitable(); } } + public void Reset(uint initialWindowSize) + { + // When output flow control is reused the client window size needs to be reset. + // The client might have changed the window size before the stream is reused. + _flow = new FlowControl(initialWindowSize); + Debug.Assert(_awaitableProvider.ActiveCount == 0, "Queue should have been emptied by the previous stream."); + } + public void Advance(int bytes) { _flow.Advance(bytes); @@ -49,9 +52,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.FlowControl { if (_flow.TryUpdateWindow(bytes)) { - while (_flow.Available > 0 && _awaitableQueue?.Count > 0) + while (_flow.Available > 0 && _awaitableProvider.ActiveCount > 0) { - _awaitableQueue.Dequeue().Complete(); + _awaitableProvider.CompleteCurrent(); } return true; @@ -65,9 +68,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.FlowControl // Make sure to set the aborted flag before running any continuations. _flow.Abort(); - while (_awaitableQueue?.Count > 0) + while (_awaitableProvider.ActiveCount > 0) { - _awaitableQueue.Dequeue().Complete(); + _awaitableProvider.CompleteCurrent(); } } } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/FlowControl/OutputFlowControlAwaitable.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/FlowControl/OutputFlowControlAwaitable.cs deleted file mode 100644 index c691a22ed7..0000000000 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/FlowControl/OutputFlowControlAwaitable.cs +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Diagnostics; -using System.Runtime.CompilerServices; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.FlowControl -{ - internal class OutputFlowControlAwaitable : ICriticalNotifyCompletion - { - private static readonly Action _callbackCompleted = () => { }; - - private Action _callback; - - public OutputFlowControlAwaitable GetAwaiter() => this; - public bool IsCompleted => ReferenceEquals(_callback, _callbackCompleted); - - public void GetResult() - { - Debug.Assert(ReferenceEquals(_callback, _callbackCompleted)); - - _callback = null; - } - - public void OnCompleted(Action continuation) - { - if (ReferenceEquals(_callback, _callbackCompleted) || - ReferenceEquals(Interlocked.CompareExchange(ref _callback, continuation, null), _callbackCompleted)) - { - Task.Run(continuation); - } - } - - public void UnsafeOnCompleted(Action continuation) - { - OnCompleted(continuation); - } - - public void Complete() - { - var continuation = Interlocked.Exchange(ref _callback, _callbackCompleted); - - continuation?.Invoke(); - } - } -} diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/FlowControl/StreamInputFlowControl.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/FlowControl/StreamInputFlowControl.cs index afcd4219dc..7e7c8434e8 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/FlowControl/StreamInputFlowControl.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/FlowControl/StreamInputFlowControl.cs @@ -10,11 +10,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.FlowControl private readonly InputFlowControl _connectionLevelFlowControl; private readonly InputFlowControl _streamLevelFlowControl; - private readonly int _streamId; + private int StreamId => _stream.StreamId; + private readonly Http2Stream _stream; private readonly Http2FrameWriter _frameWriter; public StreamInputFlowControl( - int streamId, + Http2Stream stream, Http2FrameWriter frameWriter, InputFlowControl connectionLevelFlowControl, uint initialWindowSize, @@ -22,11 +23,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.FlowControl { _connectionLevelFlowControl = connectionLevelFlowControl; _streamLevelFlowControl = new InputFlowControl(initialWindowSize, minWindowSizeIncrement); - - _streamId = streamId; + _stream = stream; _frameWriter = frameWriter; } + public void Reset() + { + _streamLevelFlowControl.Reset(); + } + public void Advance(int bytes) { var connectionSuccess = _connectionLevelFlowControl.TryAdvance(bytes); @@ -52,7 +57,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.FlowControl if (streamWindowUpdateSize > 0) { // Writing with the FrameWriter should only fail if given a canceled token, so just fire and forget. - _ = _frameWriter.WriteWindowUpdateAsync(_streamId, streamWindowUpdateSize); + _ = _frameWriter.WriteWindowUpdateAsync(StreamId, streamWindowUpdateSize); } UpdateConnectionWindow(bytes); diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/FlowControl/StreamOutputFlowControl.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/FlowControl/StreamOutputFlowControl.cs index 35dccd92fc..792a1ca3a7 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/FlowControl/StreamOutputFlowControl.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/FlowControl/StreamOutputFlowControl.cs @@ -4,6 +4,8 @@ using System; using System.Diagnostics; using System.Runtime.CompilerServices; +using System.Threading.Tasks; +using System.Threading.Tasks.Sources; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.FlowControl { @@ -12,25 +14,37 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.FlowControl private readonly OutputFlowControl _connectionLevelFlowControl; private readonly OutputFlowControl _streamLevelFlowControl; - private OutputFlowControlAwaitable _currentConnectionLevelAwaitable; + private ManualResetValueTaskSource _currentConnectionLevelAwaitable; + private int _currentConnectionLevelAwaitableVersion; public StreamOutputFlowControl(OutputFlowControl connectionLevelFlowControl, uint initialWindowSize) { _connectionLevelFlowControl = connectionLevelFlowControl; - _streamLevelFlowControl = new OutputFlowControl(initialWindowSize); + _streamLevelFlowControl = new OutputFlowControl(new SingleAwaitableProvider(), initialWindowSize); } public int Available => Math.Min(_connectionLevelFlowControl.Available, _streamLevelFlowControl.Available); public bool IsAborted => _connectionLevelFlowControl.IsAborted || _streamLevelFlowControl.IsAborted; + public void Reset(uint initialWindowSize) + { + _streamLevelFlowControl.Reset(initialWindowSize); + if (_currentConnectionLevelAwaitable != null) + { + Debug.Assert(_currentConnectionLevelAwaitable.GetStatus() == ValueTaskSourceStatus.Succeeded, "Should have been completed by the previous stream."); + _currentConnectionLevelAwaitable = null; + _currentConnectionLevelAwaitableVersion = -1; + } + } + public void Advance(int bytes) { _connectionLevelFlowControl.Advance(bytes); _streamLevelFlowControl.Advance(bytes); } - public int AdvanceUpToAndWait(long bytes, out OutputFlowControlAwaitable awaitable) + public int AdvanceUpToAndWait(long bytes, out ValueTask availabilityTask) { var leastAvailableFlow = _connectionLevelFlowControl.Available < _streamLevelFlowControl.Available ? _connectionLevelFlowControl : _streamLevelFlowControl; @@ -42,17 +56,21 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.FlowControl _connectionLevelFlowControl.Advance(actual); _streamLevelFlowControl.Advance(actual); - awaitable = null; + availabilityTask = default; _currentConnectionLevelAwaitable = null; + _currentConnectionLevelAwaitableVersion = -1; if (actual < bytes) { - awaitable = leastAvailableFlow.AvailabilityAwaitable; + var awaitable = leastAvailableFlow.AvailabilityAwaitable; if (leastAvailableFlow == _connectionLevelFlowControl) { _currentConnectionLevelAwaitable = awaitable; + _currentConnectionLevelAwaitableVersion = awaitable.Version; } + + availabilityTask = new ValueTask(awaitable, awaitable.Version); } return actual; @@ -73,7 +91,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.FlowControl // connection-level awaitable so the stream abort is observed immediately. // This could complete an awaitable still sitting in the connection-level awaitable queue, // but this is safe because completing it again will just no-op. - _currentConnectionLevelAwaitable?.Complete(); + if (_currentConnectionLevelAwaitable != null && + _currentConnectionLevelAwaitable.Version == _currentConnectionLevelAwaitableVersion) + { + _currentConnectionLevelAwaitable.SetResult(null); + } } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/HPack/HPackDecoder.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/HPack/HPackDecoder.cs deleted file mode 100644 index 543ce8afc1..0000000000 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/HPack/HPackDecoder.cs +++ /dev/null @@ -1,433 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Buffers; -using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; - -namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack -{ - internal class HPackDecoder - { - private enum State - { - Ready, - HeaderFieldIndex, - HeaderNameIndex, - HeaderNameLength, - HeaderNameLengthContinue, - HeaderName, - HeaderValueLength, - HeaderValueLengthContinue, - HeaderValue, - DynamicTableSizeUpdate - } - - // http://httpwg.org/specs/rfc7541.html#rfc.section.6.1 - // 0 1 2 3 4 5 6 7 - // +---+---+---+---+---+---+---+---+ - // | 1 | Index (7+) | - // +---+---------------------------+ - private const byte IndexedHeaderFieldMask = 0x80; - private const byte IndexedHeaderFieldRepresentation = 0x80; - - // http://httpwg.org/specs/rfc7541.html#rfc.section.6.2.1 - // 0 1 2 3 4 5 6 7 - // +---+---+---+---+---+---+---+---+ - // | 0 | 1 | Index (6+) | - // +---+---+-----------------------+ - private const byte LiteralHeaderFieldWithIncrementalIndexingMask = 0xc0; - private const byte LiteralHeaderFieldWithIncrementalIndexingRepresentation = 0x40; - - // http://httpwg.org/specs/rfc7541.html#rfc.section.6.2.2 - // 0 1 2 3 4 5 6 7 - // +---+---+---+---+---+---+---+---+ - // | 0 | 0 | 0 | 0 | Index (4+) | - // +---+---+-----------------------+ - private const byte LiteralHeaderFieldWithoutIndexingMask = 0xf0; - private const byte LiteralHeaderFieldWithoutIndexingRepresentation = 0x00; - - // http://httpwg.org/specs/rfc7541.html#rfc.section.6.2.3 - // 0 1 2 3 4 5 6 7 - // +---+---+---+---+---+---+---+---+ - // | 0 | 0 | 0 | 1 | Index (4+) | - // +---+---+-----------------------+ - private const byte LiteralHeaderFieldNeverIndexedMask = 0xf0; - private const byte LiteralHeaderFieldNeverIndexedRepresentation = 0x10; - - // http://httpwg.org/specs/rfc7541.html#rfc.section.6.3 - // 0 1 2 3 4 5 6 7 - // +---+---+---+---+---+---+---+---+ - // | 0 | 0 | 1 | Max size (5+) | - // +---+---------------------------+ - private const byte DynamicTableSizeUpdateMask = 0xe0; - private const byte DynamicTableSizeUpdateRepresentation = 0x20; - - // http://httpwg.org/specs/rfc7541.html#rfc.section.5.2 - // 0 1 2 3 4 5 6 7 - // +---+---+---+---+---+---+---+---+ - // | H | String Length (7+) | - // +---+---------------------------+ - private const byte HuffmanMask = 0x80; - - private const int IndexedHeaderFieldPrefix = 7; - private const int LiteralHeaderFieldWithIncrementalIndexingPrefix = 6; - private const int LiteralHeaderFieldWithoutIndexingPrefix = 4; - private const int LiteralHeaderFieldNeverIndexedPrefix = 4; - private const int DynamicTableSizeUpdatePrefix = 5; - private const int StringLengthPrefix = 7; - - private readonly int _maxDynamicTableSize; - private readonly DynamicTable _dynamicTable; - private readonly IntegerDecoder _integerDecoder = new IntegerDecoder(); - private readonly byte[] _stringOctets; - private readonly byte[] _headerNameOctets; - private readonly byte[] _headerValueOctets; - - private State _state = State.Ready; - private byte[] _headerName; - private int _stringIndex; - private int _stringLength; - private int _headerNameLength; - private int _headerValueLength; - private bool _index; - private bool _huffman; - private bool _headersObserved; - - public HPackDecoder(int maxDynamicTableSize, int maxRequestHeaderFieldSize) - : this(maxDynamicTableSize, maxRequestHeaderFieldSize, new DynamicTable(maxDynamicTableSize)) { } - - // For testing. - internal HPackDecoder(int maxDynamicTableSize, int maxRequestHeaderFieldSize, DynamicTable dynamicTable) - { - _maxDynamicTableSize = maxDynamicTableSize; - _dynamicTable = dynamicTable; - - _stringOctets = new byte[maxRequestHeaderFieldSize]; - _headerNameOctets = new byte[maxRequestHeaderFieldSize]; - _headerValueOctets = new byte[maxRequestHeaderFieldSize]; - } - - public void Decode(in ReadOnlySequence data, bool endHeaders, IHttpHeadersHandler handler) - { - foreach (var segment in data) - { - var span = segment.Span; - for (var i = 0; i < span.Length; i++) - { - OnByte(span[i], handler); - } - } - - if (endHeaders) - { - if (_state != State.Ready) - { - throw new HPackDecodingException(CoreStrings.HPackErrorIncompleteHeaderBlock); - } - - _headersObserved = false; - } - } - - private void OnByte(byte b, IHttpHeadersHandler handler) - { - int intResult; - switch (_state) - { - case State.Ready: - if ((b & IndexedHeaderFieldMask) == IndexedHeaderFieldRepresentation) - { - _headersObserved = true; - var val = b & ~IndexedHeaderFieldMask; - - if (_integerDecoder.BeginTryDecode((byte)val, IndexedHeaderFieldPrefix, out intResult)) - { - OnIndexedHeaderField(intResult, handler); - } - else - { - _state = State.HeaderFieldIndex; - } - } - else if ((b & LiteralHeaderFieldWithIncrementalIndexingMask) == LiteralHeaderFieldWithIncrementalIndexingRepresentation) - { - _headersObserved = true; - _index = true; - var val = b & ~LiteralHeaderFieldWithIncrementalIndexingMask; - - if (val == 0) - { - _state = State.HeaderNameLength; - } - else if (_integerDecoder.BeginTryDecode((byte)val, LiteralHeaderFieldWithIncrementalIndexingPrefix, out intResult)) - { - OnIndexedHeaderName(intResult); - } - else - { - _state = State.HeaderNameIndex; - } - } - else if ((b & LiteralHeaderFieldWithoutIndexingMask) == LiteralHeaderFieldWithoutIndexingRepresentation) - { - _headersObserved = true; - _index = false; - var val = b & ~LiteralHeaderFieldWithoutIndexingMask; - - if (val == 0) - { - _state = State.HeaderNameLength; - } - else if (_integerDecoder.BeginTryDecode((byte)val, LiteralHeaderFieldWithoutIndexingPrefix, out intResult)) - { - OnIndexedHeaderName(intResult); - } - else - { - _state = State.HeaderNameIndex; - } - } - else if ((b & LiteralHeaderFieldNeverIndexedMask) == LiteralHeaderFieldNeverIndexedRepresentation) - { - _headersObserved = true; - _index = false; - var val = b & ~LiteralHeaderFieldNeverIndexedMask; - - if (val == 0) - { - _state = State.HeaderNameLength; - } - else if (_integerDecoder.BeginTryDecode((byte)val, LiteralHeaderFieldNeverIndexedPrefix, out intResult)) - { - OnIndexedHeaderName(intResult); - } - else - { - _state = State.HeaderNameIndex; - } - } - else if ((b & DynamicTableSizeUpdateMask) == DynamicTableSizeUpdateRepresentation) - { - // https://tools.ietf.org/html/rfc7541#section-4.2 - // This dynamic table size - // update MUST occur at the beginning of the first header block - // following the change to the dynamic table size. - if (_headersObserved) - { - throw new HPackDecodingException(CoreStrings.HPackErrorDynamicTableSizeUpdateNotAtBeginningOfHeaderBlock); - } - - if (_integerDecoder.BeginTryDecode((byte)(b & ~DynamicTableSizeUpdateMask), DynamicTableSizeUpdatePrefix, out intResult)) - { - SetDynamicHeaderTableSize(intResult); - } - else - { - _state = State.DynamicTableSizeUpdate; - } - } - else - { - // Can't happen - throw new HPackDecodingException($"Byte value {b} does not encode a valid header field representation."); - } - - break; - case State.HeaderFieldIndex: - if (_integerDecoder.TryDecode(b, out intResult)) - { - OnIndexedHeaderField(intResult, handler); - } - - break; - case State.HeaderNameIndex: - if (_integerDecoder.TryDecode(b, out intResult)) - { - OnIndexedHeaderName(intResult); - } - - break; - case State.HeaderNameLength: - _huffman = (b & HuffmanMask) != 0; - - if (_integerDecoder.BeginTryDecode((byte)(b & ~HuffmanMask), StringLengthPrefix, out intResult)) - { - OnStringLength(intResult, nextState: State.HeaderName); - } - else - { - _state = State.HeaderNameLengthContinue; - } - - break; - case State.HeaderNameLengthContinue: - if (_integerDecoder.TryDecode(b, out intResult)) - { - OnStringLength(intResult, nextState: State.HeaderName); - } - - break; - case State.HeaderName: - _stringOctets[_stringIndex++] = b; - - if (_stringIndex == _stringLength) - { - OnString(nextState: State.HeaderValueLength); - } - - break; - case State.HeaderValueLength: - _huffman = (b & HuffmanMask) != 0; - - if (_integerDecoder.BeginTryDecode((byte)(b & ~HuffmanMask), StringLengthPrefix, out intResult)) - { - OnStringLength(intResult, nextState: State.HeaderValue); - if (intResult == 0) - { - ProcessHeaderValue(handler); - } - } - else - { - _state = State.HeaderValueLengthContinue; - } - - break; - case State.HeaderValueLengthContinue: - if (_integerDecoder.TryDecode(b, out intResult)) - { - OnStringLength(intResult, nextState: State.HeaderValue); - if (intResult == 0) - { - ProcessHeaderValue(handler); - } - } - - break; - case State.HeaderValue: - _stringOctets[_stringIndex++] = b; - - if (_stringIndex == _stringLength) - { - ProcessHeaderValue(handler); - } - - break; - case State.DynamicTableSizeUpdate: - if (_integerDecoder.TryDecode(b, out intResult)) - { - SetDynamicHeaderTableSize(intResult); - _state = State.Ready; - } - - break; - default: - // Can't happen - throw new HPackDecodingException("The HPACK decoder reached an invalid state."); - } - } - - private void ProcessHeaderValue(IHttpHeadersHandler handler) - { - OnString(nextState: State.Ready); - - var headerNameSpan = new Span(_headerName, 0, _headerNameLength); - var headerValueSpan = new Span(_headerValueOctets, 0, _headerValueLength); - - handler.OnHeader(headerNameSpan, headerValueSpan); - - if (_index) - { - _dynamicTable.Insert(headerNameSpan, headerValueSpan); - } - } - - private void OnIndexedHeaderField(int index, IHttpHeadersHandler handler) - { - var header = GetHeader(index); - handler.OnHeader(new Span(header.Name), new Span(header.Value)); - _state = State.Ready; - } - - private void OnIndexedHeaderName(int index) - { - var header = GetHeader(index); - _headerName = header.Name; - _headerNameLength = header.Name.Length; - _state = State.HeaderValueLength; - } - - private void OnStringLength(int length, State nextState) - { - if (length > _stringOctets.Length) - { - throw new HPackDecodingException(CoreStrings.FormatHPackStringLengthTooLarge(length, _stringOctets.Length)); - } - - _stringLength = length; - _stringIndex = 0; - _state = nextState; - } - - private void OnString(State nextState) - { - int Decode(byte[] dst) - { - if (_huffman) - { - return Huffman.Decode(new ReadOnlySpan(_stringOctets, 0, _stringLength), dst); - } - else - { - Buffer.BlockCopy(_stringOctets, 0, dst, 0, _stringLength); - return _stringLength; - } - } - - try - { - if (_state == State.HeaderName) - { - _headerName = _headerNameOctets; - _headerNameLength = Decode(_headerNameOctets); - } - else - { - _headerValueLength = Decode(_headerValueOctets); - } - } - catch (HuffmanDecodingException ex) - { - throw new HPackDecodingException(CoreStrings.HPackHuffmanError, ex); - } - - _state = nextState; - } - - private HeaderField GetHeader(int index) - { - try - { - return index <= StaticTable.Instance.Count - ? StaticTable.Instance[index - 1] - : _dynamicTable[index - StaticTable.Instance.Count - 1]; - } - catch (IndexOutOfRangeException ex) - { - throw new HPackDecodingException(CoreStrings.FormatHPackErrorIndexOutOfRange(index), ex); - } - } - - private void SetDynamicHeaderTableSize(int size) - { - if (size > _maxDynamicTableSize) - { - throw new HPackDecodingException( - CoreStrings.FormatHPackErrorDynamicTableSizeUpdateTooLarge(size, _maxDynamicTableSize)); - } - - _dynamicTable.Resize(size); - } - } -} diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/HPack/HPackDecodingException.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/HPack/HPackDecodingException.cs deleted file mode 100644 index d549554ab6..0000000000 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/HPack/HPackDecodingException.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; - -namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack -{ - internal sealed class HPackDecodingException : Exception - { - public HPackDecodingException(string message) - : base(message) - { - } - public HPackDecodingException(string message, Exception innerException) - : base(message, innerException) - { - } - } -} diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/HPack/HPackEncoder.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/HPack/HPackEncoder.cs deleted file mode 100644 index 9268061b28..0000000000 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/HPack/HPackEncoder.cs +++ /dev/null @@ -1,160 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; - -namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack -{ - internal class HPackEncoder - { - private IEnumerator> _enumerator; - - public bool BeginEncode(IEnumerable> headers, Span buffer, out int length) - { - _enumerator = headers.GetEnumerator(); - _enumerator.MoveNext(); - - return Encode(buffer, out length); - } - - public bool BeginEncode(int statusCode, IEnumerable> headers, Span buffer, out int length) - { - _enumerator = headers.GetEnumerator(); - _enumerator.MoveNext(); - - var statusCodeLength = EncodeStatusCode(statusCode, buffer); - var done = Encode(buffer.Slice(statusCodeLength), throwIfNoneEncoded: false, out var headersLength); - length = statusCodeLength + headersLength; - - return done; - } - - public bool Encode(Span buffer, out int length) - { - return Encode(buffer, throwIfNoneEncoded: true, out length); - } - - private bool Encode(Span buffer, bool throwIfNoneEncoded, out int length) - { - length = 0; - - do - { - if (!EncodeHeader(_enumerator.Current.Key, _enumerator.Current.Value, buffer.Slice(length), out var headerLength)) - { - if (length == 0 && throwIfNoneEncoded) - { - throw new HPackEncodingException(CoreStrings.HPackErrorNotEnoughBuffer); - } - return false; - } - - length += headerLength; - } while (_enumerator.MoveNext()); - - return true; - } - - private int EncodeStatusCode(int statusCode, Span buffer) - { - switch (statusCode) - { - case 200: - case 204: - case 206: - case 304: - case 400: - case 404: - case 500: - buffer[0] = (byte)(0x80 | StaticTable.Instance.StatusIndex[statusCode]); - return 1; - default: - // Send as Literal Header Field Without Indexing - Indexed Name - buffer[0] = 0x08; - - var statusBytes = StatusCodes.ToStatusBytes(statusCode); - buffer[1] = (byte)statusBytes.Length; - ((Span)statusBytes).CopyTo(buffer.Slice(2)); - - return 2 + statusBytes.Length; - } - } - - private bool EncodeHeader(string name, string value, Span buffer, out int length) - { - var i = 0; - length = 0; - - if (buffer.Length == 0) - { - return false; - } - - buffer[i++] = 0; - - if (i == buffer.Length) - { - return false; - } - - if (!EncodeString(name, buffer.Slice(i), out var nameLength, lowercase: true)) - { - return false; - } - - i += nameLength; - - if (i >= buffer.Length) - { - return false; - } - - if (!EncodeString(value, buffer.Slice(i), out var valueLength, lowercase: false)) - { - return false; - } - - i += valueLength; - - length = i; - return true; - } - - private bool EncodeString(string s, Span buffer, out int length, bool lowercase) - { - const int toLowerMask = 0x20; - - var i = 0; - length = 0; - - if (buffer.Length == 0) - { - return false; - } - - buffer[0] = 0; - - if (!IntegerEncoder.Encode(s.Length, 7, buffer, out var nameLength)) - { - return false; - } - - i += nameLength; - - // TODO: use huffman encoding - for (var j = 0; j < s.Length; j++) - { - if (i >= buffer.Length) - { - return false; - } - - buffer[i++] = (byte)(s[j] | (lowercase && s[j] >= (byte)'A' && s[j] <= (byte)'Z' ? toLowerMask : 0)); - } - - length = i; - return true; - } - } -} diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/HPack/HeaderField.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/HPack/HeaderField.cs deleted file mode 100644 index d6a07e7b35..0000000000 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/HPack/HeaderField.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; - -namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack -{ - internal readonly struct HeaderField - { - // http://httpwg.org/specs/rfc7541.html#rfc.section.4.1 - public const int RfcOverhead = 32; - - public HeaderField(Span name, Span value) - { - Name = new byte[name.Length]; - name.CopyTo(Name); - - Value = new byte[value.Length]; - value.CopyTo(Value); - } - - public byte[] Name { get; } - - public byte[] Value { get; } - - public int Length => GetLength(Name.Length, Value.Length); - - public static int GetLength(int nameLength, int valueLength) => nameLength + valueLength + 32; - } -} diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/HPack/IntegerDecoder.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/HPack/IntegerDecoder.cs deleted file mode 100644 index 081fd30f6c..0000000000 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/HPack/IntegerDecoder.cs +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack -{ - /// - /// The maximum we will decode is Int32.MaxValue, which is also the maximum request header field size. - /// - internal class IntegerDecoder - { - private int _i; - private int _m; - - /// - /// Callers must ensure higher bits above the prefix are cleared before calling this method. - /// - /// - /// - /// - /// - public bool BeginTryDecode(byte b, int prefixLength, out int result) - { - if (b < ((1 << prefixLength) - 1)) - { - result = b; - return true; - } - - _i = b; - _m = 0; - result = 0; - return false; - } - - public bool TryDecode(byte b, out int result) - { - var m = _m; // Enregister - var i = _i + ((b & 0x7f) << m); // Enregister - - if ((b & 0x80) == 0) - { - // Int32.MaxValue only needs a maximum of 5 bytes to represent and the last byte cannot have any value set larger than 0x7 - if ((m > 21 && b > 0x7) || i < 0) - { - ThrowIntegerTooBigException(); - } - - result = i; - return true; - } - else if (m > 21) - { - // Int32.MaxValue only needs a maximum of 5 bytes to represent - ThrowIntegerTooBigException(); - } - - _m = m + 7; - _i = i; - - result = 0; - return false; - } - - public static void ThrowIntegerTooBigException() - => throw new HPackDecodingException(CoreStrings.HPackErrorIntegerTooBig); - } -} diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/HPack/IntegerEncoder.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/HPack/IntegerEncoder.cs deleted file mode 100644 index 600d032176..0000000000 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/HPack/IntegerEncoder.cs +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Diagnostics; - -namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack -{ - internal static class IntegerEncoder - { - public static bool Encode(int i, int n, Span buffer, out int length) - { - Debug.Assert(i >= 0); - Debug.Assert(n >= 1 && n <= 8); - - var j = 0; - length = 0; - - if (buffer.Length == 0) - { - return false; - } - - if (i < (1 << n) - 1) - { - buffer[j] &= MaskHigh(8 - n); - buffer[j++] |= (byte)i; - } - else - { - buffer[j] &= MaskHigh(8 - n); - buffer[j++] |= (byte)((1 << n) - 1); - - if (j == buffer.Length) - { - return false; - } - - i -= ((1 << n) - 1); - while (i >= 128) - { - var ui = (uint)i; // Use unsigned for optimizations - buffer[j++] = (byte)((ui % 128) + 128); - - if (j >= buffer.Length) - { - return false; - } - - i = (int)(ui / 128); // Jit converts unsigned divide by power-of-2 constant to clean shift - } - buffer[j++] = (byte)i; - } - - length = j; - return true; - } - - private static byte MaskHigh(int n) - { - return (byte)(sbyte.MinValue >> (n - 1)); - } - } -} diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/HPack/StaticTable.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/HPack/StaticTable.cs deleted file mode 100644 index 5c0ece5c9f..0000000000 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/HPack/StaticTable.cs +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Collections.Generic; -using System.Text; -using Microsoft.Net.Http.Headers; - -namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack -{ - internal class StaticTable - { - private static readonly StaticTable _instance = new StaticTable(); - - private readonly Dictionary _statusIndex = new Dictionary - { - [200] = 8, - [204] = 9, - [206] = 10, - [304] = 11, - [400] = 12, - [404] = 13, - [500] = 14, - }; - - private StaticTable() - { - } - - public static StaticTable Instance => _instance; - - public int Count => _staticTable.Length; - - public HeaderField this[int index] => _staticTable[index]; - - public IReadOnlyDictionary StatusIndex => _statusIndex; - - private readonly HeaderField[] _staticTable = new HeaderField[] - { - CreateHeaderField(HeaderNames.Authority, ""), - CreateHeaderField(HeaderNames.Method, "GET"), - CreateHeaderField(HeaderNames.Method, "POST"), - CreateHeaderField(HeaderNames.Path, "/"), - CreateHeaderField(HeaderNames.Path, "/index.html"), - CreateHeaderField(HeaderNames.Scheme, "http"), - CreateHeaderField(HeaderNames.Scheme, "https"), - CreateHeaderField(HeaderNames.Status, "200"), - CreateHeaderField(HeaderNames.Status, "204"), - CreateHeaderField(HeaderNames.Status, "206"), - CreateHeaderField(HeaderNames.Status, "304"), - CreateHeaderField(HeaderNames.Status, "400"), - CreateHeaderField(HeaderNames.Status, "404"), - CreateHeaderField(HeaderNames.Status, "500"), - CreateHeaderField("accept-charset", ""), - CreateHeaderField("accept-encoding", "gzip, deflate"), - CreateHeaderField("accept-language", ""), - CreateHeaderField("accept-ranges", ""), - CreateHeaderField("accept", ""), - CreateHeaderField("access-control-allow-origin", ""), - CreateHeaderField("age", ""), - CreateHeaderField("allow", ""), - CreateHeaderField("authorization", ""), - CreateHeaderField("cache-control", ""), - CreateHeaderField("content-disposition", ""), - CreateHeaderField("content-encoding", ""), - CreateHeaderField("content-language", ""), - CreateHeaderField("content-length", ""), - CreateHeaderField("content-location", ""), - CreateHeaderField("content-range", ""), - CreateHeaderField("content-type", ""), - CreateHeaderField("cookie", ""), - CreateHeaderField("date", ""), - CreateHeaderField("etag", ""), - CreateHeaderField("expect", ""), - CreateHeaderField("expires", ""), - CreateHeaderField("from", ""), - CreateHeaderField("host", ""), - CreateHeaderField("if-match", ""), - CreateHeaderField("if-modified-since", ""), - CreateHeaderField("if-none-match", ""), - CreateHeaderField("if-range", ""), - CreateHeaderField("if-unmodifiedsince", ""), - CreateHeaderField("last-modified", ""), - CreateHeaderField("link", ""), - CreateHeaderField("location", ""), - CreateHeaderField("max-forwards", ""), - CreateHeaderField("proxy-authenticate", ""), - CreateHeaderField("proxy-authorization", ""), - CreateHeaderField("range", ""), - CreateHeaderField("referer", ""), - CreateHeaderField("refresh", ""), - CreateHeaderField("retry-after", ""), - CreateHeaderField("server", ""), - CreateHeaderField("set-cookie", ""), - CreateHeaderField("strict-transport-security", ""), - CreateHeaderField("transfer-encoding", ""), - CreateHeaderField("user-agent", ""), - CreateHeaderField("vary", ""), - CreateHeaderField("via", ""), - CreateHeaderField("www-authenticate", "") - }; - - private static HeaderField CreateHeaderField(string name, string value) - => new HeaderField(Encoding.ASCII.GetBytes(name), Encoding.ASCII.GetBytes(value)); - } -} diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/HPack/StatusCodes.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/HPack/StatusCodes.cs deleted file mode 100644 index e00afa1d28..0000000000 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/HPack/StatusCodes.cs +++ /dev/null @@ -1,158 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Globalization; -using System.Text; - -namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack -{ - internal static class StatusCodes - { - private static readonly byte[] _bytesStatus100 = CreateStatusBytes(Microsoft.AspNetCore.Http.StatusCodes.Status100Continue); - private static readonly byte[] _bytesStatus101 = CreateStatusBytes(Microsoft.AspNetCore.Http.StatusCodes.Status101SwitchingProtocols); - private static readonly byte[] _bytesStatus102 = CreateStatusBytes(Microsoft.AspNetCore.Http.StatusCodes.Status102Processing); - - private static readonly byte[] _bytesStatus200 = CreateStatusBytes(Microsoft.AspNetCore.Http.StatusCodes.Status200OK); - private static readonly byte[] _bytesStatus201 = CreateStatusBytes(Microsoft.AspNetCore.Http.StatusCodes.Status201Created); - private static readonly byte[] _bytesStatus202 = CreateStatusBytes(Microsoft.AspNetCore.Http.StatusCodes.Status202Accepted); - private static readonly byte[] _bytesStatus203 = CreateStatusBytes(Microsoft.AspNetCore.Http.StatusCodes.Status203NonAuthoritative); - private static readonly byte[] _bytesStatus204 = CreateStatusBytes(Microsoft.AspNetCore.Http.StatusCodes.Status204NoContent); - private static readonly byte[] _bytesStatus205 = CreateStatusBytes(Microsoft.AspNetCore.Http.StatusCodes.Status205ResetContent); - private static readonly byte[] _bytesStatus206 = CreateStatusBytes(Microsoft.AspNetCore.Http.StatusCodes.Status206PartialContent); - private static readonly byte[] _bytesStatus207 = CreateStatusBytes(Microsoft.AspNetCore.Http.StatusCodes.Status207MultiStatus); - private static readonly byte[] _bytesStatus208 = CreateStatusBytes(Microsoft.AspNetCore.Http.StatusCodes.Status208AlreadyReported); - private static readonly byte[] _bytesStatus226 = CreateStatusBytes(Microsoft.AspNetCore.Http.StatusCodes.Status226IMUsed); - - private static readonly byte[] _bytesStatus300 = CreateStatusBytes(Microsoft.AspNetCore.Http.StatusCodes.Status300MultipleChoices); - private static readonly byte[] _bytesStatus301 = CreateStatusBytes(Microsoft.AspNetCore.Http.StatusCodes.Status301MovedPermanently); - private static readonly byte[] _bytesStatus302 = CreateStatusBytes(Microsoft.AspNetCore.Http.StatusCodes.Status302Found); - private static readonly byte[] _bytesStatus303 = CreateStatusBytes(Microsoft.AspNetCore.Http.StatusCodes.Status303SeeOther); - private static readonly byte[] _bytesStatus304 = CreateStatusBytes(Microsoft.AspNetCore.Http.StatusCodes.Status304NotModified); - private static readonly byte[] _bytesStatus305 = CreateStatusBytes(Microsoft.AspNetCore.Http.StatusCodes.Status305UseProxy); - private static readonly byte[] _bytesStatus306 = CreateStatusBytes(Microsoft.AspNetCore.Http.StatusCodes.Status306SwitchProxy); - private static readonly byte[] _bytesStatus307 = CreateStatusBytes(Microsoft.AspNetCore.Http.StatusCodes.Status307TemporaryRedirect); - private static readonly byte[] _bytesStatus308 = CreateStatusBytes(Microsoft.AspNetCore.Http.StatusCodes.Status308PermanentRedirect); - - private static readonly byte[] _bytesStatus400 = CreateStatusBytes(Microsoft.AspNetCore.Http.StatusCodes.Status400BadRequest); - private static readonly byte[] _bytesStatus401 = CreateStatusBytes(Microsoft.AspNetCore.Http.StatusCodes.Status401Unauthorized); - private static readonly byte[] _bytesStatus402 = CreateStatusBytes(Microsoft.AspNetCore.Http.StatusCodes.Status402PaymentRequired); - private static readonly byte[] _bytesStatus403 = CreateStatusBytes(Microsoft.AspNetCore.Http.StatusCodes.Status403Forbidden); - private static readonly byte[] _bytesStatus404 = CreateStatusBytes(Microsoft.AspNetCore.Http.StatusCodes.Status404NotFound); - private static readonly byte[] _bytesStatus405 = CreateStatusBytes(Microsoft.AspNetCore.Http.StatusCodes.Status405MethodNotAllowed); - private static readonly byte[] _bytesStatus406 = CreateStatusBytes(Microsoft.AspNetCore.Http.StatusCodes.Status406NotAcceptable); - private static readonly byte[] _bytesStatus407 = CreateStatusBytes(Microsoft.AspNetCore.Http.StatusCodes.Status407ProxyAuthenticationRequired); - private static readonly byte[] _bytesStatus408 = CreateStatusBytes(Microsoft.AspNetCore.Http.StatusCodes.Status408RequestTimeout); - private static readonly byte[] _bytesStatus409 = CreateStatusBytes(Microsoft.AspNetCore.Http.StatusCodes.Status409Conflict); - private static readonly byte[] _bytesStatus410 = CreateStatusBytes(Microsoft.AspNetCore.Http.StatusCodes.Status410Gone); - private static readonly byte[] _bytesStatus411 = CreateStatusBytes(Microsoft.AspNetCore.Http.StatusCodes.Status411LengthRequired); - private static readonly byte[] _bytesStatus412 = CreateStatusBytes(Microsoft.AspNetCore.Http.StatusCodes.Status412PreconditionFailed); - private static readonly byte[] _bytesStatus413 = CreateStatusBytes(Microsoft.AspNetCore.Http.StatusCodes.Status413PayloadTooLarge); - private static readonly byte[] _bytesStatus414 = CreateStatusBytes(Microsoft.AspNetCore.Http.StatusCodes.Status414UriTooLong); - private static readonly byte[] _bytesStatus415 = CreateStatusBytes(Microsoft.AspNetCore.Http.StatusCodes.Status415UnsupportedMediaType); - private static readonly byte[] _bytesStatus416 = CreateStatusBytes(Microsoft.AspNetCore.Http.StatusCodes.Status416RangeNotSatisfiable); - private static readonly byte[] _bytesStatus417 = CreateStatusBytes(Microsoft.AspNetCore.Http.StatusCodes.Status417ExpectationFailed); - private static readonly byte[] _bytesStatus418 = CreateStatusBytes(Microsoft.AspNetCore.Http.StatusCodes.Status418ImATeapot); - private static readonly byte[] _bytesStatus419 = CreateStatusBytes(Microsoft.AspNetCore.Http.StatusCodes.Status419AuthenticationTimeout); - private static readonly byte[] _bytesStatus421 = CreateStatusBytes(Microsoft.AspNetCore.Http.StatusCodes.Status421MisdirectedRequest); - private static readonly byte[] _bytesStatus422 = CreateStatusBytes(Microsoft.AspNetCore.Http.StatusCodes.Status422UnprocessableEntity); - private static readonly byte[] _bytesStatus423 = CreateStatusBytes(Microsoft.AspNetCore.Http.StatusCodes.Status423Locked); - private static readonly byte[] _bytesStatus424 = CreateStatusBytes(Microsoft.AspNetCore.Http.StatusCodes.Status424FailedDependency); - private static readonly byte[] _bytesStatus426 = CreateStatusBytes(Microsoft.AspNetCore.Http.StatusCodes.Status426UpgradeRequired); - private static readonly byte[] _bytesStatus428 = CreateStatusBytes(Microsoft.AspNetCore.Http.StatusCodes.Status428PreconditionRequired); - private static readonly byte[] _bytesStatus429 = CreateStatusBytes(Microsoft.AspNetCore.Http.StatusCodes.Status429TooManyRequests); - private static readonly byte[] _bytesStatus431 = CreateStatusBytes(Microsoft.AspNetCore.Http.StatusCodes.Status431RequestHeaderFieldsTooLarge); - private static readonly byte[] _bytesStatus451 = CreateStatusBytes(Microsoft.AspNetCore.Http.StatusCodes.Status451UnavailableForLegalReasons); - - private static readonly byte[] _bytesStatus500 = CreateStatusBytes(Microsoft.AspNetCore.Http.StatusCodes.Status500InternalServerError); - private static readonly byte[] _bytesStatus501 = CreateStatusBytes(Microsoft.AspNetCore.Http.StatusCodes.Status501NotImplemented); - private static readonly byte[] _bytesStatus502 = CreateStatusBytes(Microsoft.AspNetCore.Http.StatusCodes.Status502BadGateway); - private static readonly byte[] _bytesStatus503 = CreateStatusBytes(Microsoft.AspNetCore.Http.StatusCodes.Status503ServiceUnavailable); - private static readonly byte[] _bytesStatus504 = CreateStatusBytes(Microsoft.AspNetCore.Http.StatusCodes.Status504GatewayTimeout); - private static readonly byte[] _bytesStatus505 = CreateStatusBytes(Microsoft.AspNetCore.Http.StatusCodes.Status505HttpVersionNotsupported); - private static readonly byte[] _bytesStatus506 = CreateStatusBytes(Microsoft.AspNetCore.Http.StatusCodes.Status506VariantAlsoNegotiates); - private static readonly byte[] _bytesStatus507 = CreateStatusBytes(Microsoft.AspNetCore.Http.StatusCodes.Status507InsufficientStorage); - private static readonly byte[] _bytesStatus508 = CreateStatusBytes(Microsoft.AspNetCore.Http.StatusCodes.Status508LoopDetected); - private static readonly byte[] _bytesStatus510 = CreateStatusBytes(Microsoft.AspNetCore.Http.StatusCodes.Status510NotExtended); - private static readonly byte[] _bytesStatus511 = CreateStatusBytes(Microsoft.AspNetCore.Http.StatusCodes.Status511NetworkAuthenticationRequired); - - private static byte[] CreateStatusBytes(int statusCode) - { - return Encoding.ASCII.GetBytes(statusCode.ToString(CultureInfo.InvariantCulture)); - } - - public static byte[] ToStatusBytes(int statusCode) - { - return statusCode switch - { - Microsoft.AspNetCore.Http.StatusCodes.Status100Continue => _bytesStatus100, - Microsoft.AspNetCore.Http.StatusCodes.Status101SwitchingProtocols => _bytesStatus101, - Microsoft.AspNetCore.Http.StatusCodes.Status102Processing => _bytesStatus102, - - Microsoft.AspNetCore.Http.StatusCodes.Status200OK => _bytesStatus200, - Microsoft.AspNetCore.Http.StatusCodes.Status201Created => _bytesStatus201, - Microsoft.AspNetCore.Http.StatusCodes.Status202Accepted => _bytesStatus202, - Microsoft.AspNetCore.Http.StatusCodes.Status203NonAuthoritative => _bytesStatus203, - Microsoft.AspNetCore.Http.StatusCodes.Status204NoContent => _bytesStatus204, - Microsoft.AspNetCore.Http.StatusCodes.Status205ResetContent => _bytesStatus205, - Microsoft.AspNetCore.Http.StatusCodes.Status206PartialContent => _bytesStatus206, - Microsoft.AspNetCore.Http.StatusCodes.Status207MultiStatus => _bytesStatus207, - Microsoft.AspNetCore.Http.StatusCodes.Status208AlreadyReported => _bytesStatus208, - Microsoft.AspNetCore.Http.StatusCodes.Status226IMUsed => _bytesStatus226, - - Microsoft.AspNetCore.Http.StatusCodes.Status300MultipleChoices => _bytesStatus300, - Microsoft.AspNetCore.Http.StatusCodes.Status301MovedPermanently => _bytesStatus301, - Microsoft.AspNetCore.Http.StatusCodes.Status302Found => _bytesStatus302, - Microsoft.AspNetCore.Http.StatusCodes.Status303SeeOther => _bytesStatus303, - Microsoft.AspNetCore.Http.StatusCodes.Status304NotModified => _bytesStatus304, - Microsoft.AspNetCore.Http.StatusCodes.Status305UseProxy => _bytesStatus305, - Microsoft.AspNetCore.Http.StatusCodes.Status306SwitchProxy => _bytesStatus306, - Microsoft.AspNetCore.Http.StatusCodes.Status307TemporaryRedirect => _bytesStatus307, - Microsoft.AspNetCore.Http.StatusCodes.Status308PermanentRedirect => _bytesStatus308, - - Microsoft.AspNetCore.Http.StatusCodes.Status400BadRequest => _bytesStatus400, - Microsoft.AspNetCore.Http.StatusCodes.Status401Unauthorized => _bytesStatus401, - Microsoft.AspNetCore.Http.StatusCodes.Status402PaymentRequired => _bytesStatus402, - Microsoft.AspNetCore.Http.StatusCodes.Status403Forbidden => _bytesStatus403, - Microsoft.AspNetCore.Http.StatusCodes.Status404NotFound => _bytesStatus404, - Microsoft.AspNetCore.Http.StatusCodes.Status405MethodNotAllowed => _bytesStatus405, - Microsoft.AspNetCore.Http.StatusCodes.Status406NotAcceptable => _bytesStatus406, - Microsoft.AspNetCore.Http.StatusCodes.Status407ProxyAuthenticationRequired => _bytesStatus407, - Microsoft.AspNetCore.Http.StatusCodes.Status408RequestTimeout => _bytesStatus408, - Microsoft.AspNetCore.Http.StatusCodes.Status409Conflict => _bytesStatus409, - Microsoft.AspNetCore.Http.StatusCodes.Status410Gone => _bytesStatus410, - Microsoft.AspNetCore.Http.StatusCodes.Status411LengthRequired => _bytesStatus411, - Microsoft.AspNetCore.Http.StatusCodes.Status412PreconditionFailed => _bytesStatus412, - Microsoft.AspNetCore.Http.StatusCodes.Status413PayloadTooLarge => _bytesStatus413, - Microsoft.AspNetCore.Http.StatusCodes.Status414UriTooLong => _bytesStatus414, - Microsoft.AspNetCore.Http.StatusCodes.Status415UnsupportedMediaType => _bytesStatus415, - Microsoft.AspNetCore.Http.StatusCodes.Status416RangeNotSatisfiable => _bytesStatus416, - Microsoft.AspNetCore.Http.StatusCodes.Status417ExpectationFailed => _bytesStatus417, - Microsoft.AspNetCore.Http.StatusCodes.Status418ImATeapot => _bytesStatus418, - Microsoft.AspNetCore.Http.StatusCodes.Status419AuthenticationTimeout => _bytesStatus419, - Microsoft.AspNetCore.Http.StatusCodes.Status421MisdirectedRequest => _bytesStatus421, - Microsoft.AspNetCore.Http.StatusCodes.Status422UnprocessableEntity => _bytesStatus422, - Microsoft.AspNetCore.Http.StatusCodes.Status423Locked => _bytesStatus423, - Microsoft.AspNetCore.Http.StatusCodes.Status424FailedDependency => _bytesStatus424, - Microsoft.AspNetCore.Http.StatusCodes.Status426UpgradeRequired => _bytesStatus426, - Microsoft.AspNetCore.Http.StatusCodes.Status428PreconditionRequired => _bytesStatus428, - Microsoft.AspNetCore.Http.StatusCodes.Status429TooManyRequests => _bytesStatus429, - Microsoft.AspNetCore.Http.StatusCodes.Status431RequestHeaderFieldsTooLarge => _bytesStatus431, - Microsoft.AspNetCore.Http.StatusCodes.Status451UnavailableForLegalReasons => _bytesStatus451, - - Microsoft.AspNetCore.Http.StatusCodes.Status500InternalServerError => _bytesStatus500, - Microsoft.AspNetCore.Http.StatusCodes.Status501NotImplemented => _bytesStatus501, - Microsoft.AspNetCore.Http.StatusCodes.Status502BadGateway => _bytesStatus502, - Microsoft.AspNetCore.Http.StatusCodes.Status503ServiceUnavailable => _bytesStatus503, - Microsoft.AspNetCore.Http.StatusCodes.Status504GatewayTimeout => _bytesStatus504, - Microsoft.AspNetCore.Http.StatusCodes.Status505HttpVersionNotsupported => _bytesStatus505, - Microsoft.AspNetCore.Http.StatusCodes.Status506VariantAlsoNegotiates => _bytesStatus506, - Microsoft.AspNetCore.Http.StatusCodes.Status507InsufficientStorage => _bytesStatus507, - Microsoft.AspNetCore.Http.StatusCodes.Status508LoopDetected => _bytesStatus508, - Microsoft.AspNetCore.Http.StatusCodes.Status510NotExtended => _bytesStatus510, - Microsoft.AspNetCore.Http.StatusCodes.Status511NetworkAuthenticationRequired => _bytesStatus511, - - _ => CreateStatusBytes(statusCode) - }; - } - } -} diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/HPackHeaderWriter.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/HPackHeaderWriter.cs new file mode 100644 index 0000000000..1598a18c7f --- /dev/null +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/HPackHeaderWriter.cs @@ -0,0 +1,157 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Net.Http; +using System.Net.Http.HPack; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; + +namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 +{ + internal static class HPackHeaderWriter + { + /// + /// Begin encoding headers in the first HEADERS frame. + /// + public static bool BeginEncodeHeaders(int statusCode, Http2HeadersEnumerator headersEnumerator, Span buffer, out int length) + { + if (!HPackEncoder.EncodeStatusHeader(statusCode, buffer, out var statusCodeLength)) + { + throw new HPackEncodingException(SR.net_http_hpack_encode_failure); + } + + if (!headersEnumerator.MoveNext()) + { + length = statusCodeLength; + return true; + } + + // We're ok with not throwing if no headers were encoded because we've already encoded the status. + // There is a small chance that the header will encode if there is no other content in the next HEADERS frame. + var done = EncodeHeaders(headersEnumerator, buffer.Slice(statusCodeLength), throwIfNoneEncoded: false, out var headersLength); + length = statusCodeLength + headersLength; + + return done; + } + + /// + /// Begin encoding headers in the first HEADERS frame. + /// + public static bool BeginEncodeHeaders(Http2HeadersEnumerator headersEnumerator, Span buffer, out int length) + { + if (!headersEnumerator.MoveNext()) + { + length = 0; + return true; + } + + return EncodeHeaders(headersEnumerator, buffer, throwIfNoneEncoded: true, out length); + } + + /// + /// Continue encoding headers in the next HEADERS frame. The enumerator should already have a current value. + /// + public static bool ContinueEncodeHeaders(Http2HeadersEnumerator headersEnumerator, Span buffer, out int length) + { + return EncodeHeaders(headersEnumerator, buffer, throwIfNoneEncoded: true, out length); + } + + private static bool EncodeHeaders(Http2HeadersEnumerator headersEnumerator, Span buffer, bool throwIfNoneEncoded, out int length) + { + var currentLength = 0; + do + { + if (!EncodeHeader(headersEnumerator.KnownHeaderType, headersEnumerator.Current.Key, headersEnumerator.Current.Value, buffer.Slice(currentLength), out int headerLength)) + { + // The the header wasn't written and no headers have been written then the header is too large. + // Throw an error to avoid an infinite loop of attempting to write large header. + if (currentLength == 0 && throwIfNoneEncoded) + { + throw new HPackEncodingException(SR.net_http_hpack_encode_failure); + } + + length = currentLength; + return false; + } + + currentLength += headerLength; + } + while (headersEnumerator.MoveNext()); + + length = currentLength; + + return true; + } + + private static bool EncodeHeader(KnownHeaderType knownHeaderType, string name, string value, Span buffer, out int length) + { + var hPackStaticTableId = GetResponseHeaderStaticTableId(knownHeaderType); + + if (hPackStaticTableId == -1) + { + return HPackEncoder.EncodeLiteralHeaderFieldWithoutIndexingNewName(name, value, buffer, out length); + } + else + { + return HPackEncoder.EncodeLiteralHeaderFieldWithoutIndexing(hPackStaticTableId, value, buffer, out length); + } + } + + private static int GetResponseHeaderStaticTableId(KnownHeaderType responseHeaderType) + { + switch (responseHeaderType) + { + case KnownHeaderType.CacheControl: + return H2StaticTable.CacheControl; + case KnownHeaderType.Date: + return H2StaticTable.Date; + case KnownHeaderType.TransferEncoding: + return H2StaticTable.TransferEncoding; + case KnownHeaderType.Via: + return H2StaticTable.Via; + case KnownHeaderType.Allow: + return H2StaticTable.Allow; + case KnownHeaderType.ContentType: + return H2StaticTable.ContentType; + case KnownHeaderType.ContentEncoding: + return H2StaticTable.ContentEncoding; + case KnownHeaderType.ContentLanguage: + return H2StaticTable.ContentLanguage; + case KnownHeaderType.ContentLocation: + return H2StaticTable.ContentLocation; + case KnownHeaderType.ContentRange: + return H2StaticTable.ContentRange; + case KnownHeaderType.Expires: + return H2StaticTable.Expires; + case KnownHeaderType.LastModified: + return H2StaticTable.LastModified; + case KnownHeaderType.AcceptRanges: + return H2StaticTable.AcceptRanges; + case KnownHeaderType.Age: + return H2StaticTable.Age; + case KnownHeaderType.ETag: + return H2StaticTable.ETag; + case KnownHeaderType.Location: + return H2StaticTable.Location; + case KnownHeaderType.ProxyAuthenticate: + return H2StaticTable.ProxyAuthenticate; + case KnownHeaderType.RetryAfter: + return H2StaticTable.RetryAfter; + case KnownHeaderType.Server: + return H2StaticTable.Server; + case KnownHeaderType.SetCookie: + return H2StaticTable.SetCookie; + case KnownHeaderType.Vary: + return H2StaticTable.Vary; + case KnownHeaderType.WWWAuthenticate: + return H2StaticTable.WwwAuthenticate; + case KnownHeaderType.AccessControlAllowOrigin: + return H2StaticTable.AccessControlAllowOrigin; + case KnownHeaderType.ContentLength: + return H2StaticTable.ContentLength; + default: + return -1; + } + } + } +} diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.Generated.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.Generated.cs new file mode 100644 index 0000000000..290fea38f5 --- /dev/null +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.Generated.cs @@ -0,0 +1,23 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 +{ + internal partial class Http2Connection + { + // This uses C# compiler's ability to refer to static data directly. For more information see https://vcsjones.dev/2019/02/01/csharp-readonly-span-bytes-static + + private static ReadOnlySpan ClientPrefaceBytes => new byte[24] { (byte)'P', (byte)'R', (byte)'I', (byte)' ', (byte)'*', (byte)' ', (byte)'H', (byte)'T', (byte)'T', (byte)'P', (byte)'/', (byte)'2', (byte)'.', (byte)'0', (byte)'\r', (byte)'\n', (byte)'\r', (byte)'\n', (byte)'S', (byte)'M', (byte)'\r', (byte)'\n', (byte)'\r', (byte)'\n' }; + private static ReadOnlySpan AuthorityBytes => new byte[10] { (byte)':', (byte)'a', (byte)'u', (byte)'t', (byte)'h', (byte)'o', (byte)'r', (byte)'i', (byte)'t', (byte)'y' }; + private static ReadOnlySpan MethodBytes => new byte[7] { (byte)':', (byte)'m', (byte)'e', (byte)'t', (byte)'h', (byte)'o', (byte)'d' }; + private static ReadOnlySpan PathBytes => new byte[5] { (byte)':', (byte)'p', (byte)'a', (byte)'t', (byte)'h' }; + private static ReadOnlySpan SchemeBytes => new byte[7] { (byte)':', (byte)'s', (byte)'c', (byte)'h', (byte)'e', (byte)'m', (byte)'e' }; + private static ReadOnlySpan StatusBytes => new byte[7] { (byte)':', (byte)'s', (byte)'t', (byte)'a', (byte)'t', (byte)'u', (byte)'s' }; + private static ReadOnlySpan ConnectionBytes => new byte[10] { (byte)'c', (byte)'o', (byte)'n', (byte)'n', (byte)'e', (byte)'c', (byte)'t', (byte)'i', (byte)'o', (byte)'n' }; + private static ReadOnlySpan TeBytes => new byte[2] { (byte)'t', (byte)'e' }; + private static ReadOnlySpan TrailersBytes => new byte[8] { (byte)'t', (byte)'r', (byte)'a', (byte)'i', (byte)'l', (byte)'e', (byte)'r', (byte)'s' }; + private static ReadOnlySpan ConnectBytes => new byte[7] { (byte)'C', (byte)'O', (byte)'N', (byte)'N', (byte)'E', (byte)'C', (byte)'T' }; + } +} diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs index 3ccdccef69..8666bebc64 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs @@ -8,41 +8,30 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.IO.Pipelines; +using System.Net.Http.HPack; using System.Runtime.CompilerServices; using System.Security.Authentication; -using System.Text; using System.Threading; using System.Threading.Tasks; +using System.Net.Http; using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Connections.Features; using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.FlowControl; -using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; using Microsoft.Extensions.Logging; -using Microsoft.Net.Http.Headers; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 { - internal class Http2Connection : IHttp2StreamLifetimeHandler, IHttpHeadersHandler, IRequestProcessor + internal partial class Http2Connection : IHttp2StreamLifetimeHandler, IHttpHeadersHandler, IRequestProcessor { - public static byte[] ClientPreface { get; } = Encoding.ASCII.GetBytes("PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"); + public static ReadOnlySpan ClientPreface => ClientPrefaceBytes; private static readonly PseudoHeaderFields _mandatoryRequestPseudoHeaderFields = PseudoHeaderFields.Method | PseudoHeaderFields.Path | PseudoHeaderFields.Scheme; - private static readonly byte[] _authorityBytes = Encoding.ASCII.GetBytes(HeaderNames.Authority); - private static readonly byte[] _methodBytes = Encoding.ASCII.GetBytes(HeaderNames.Method); - private static readonly byte[] _pathBytes = Encoding.ASCII.GetBytes(HeaderNames.Path); - private static readonly byte[] _schemeBytes = Encoding.ASCII.GetBytes(HeaderNames.Scheme); - private static readonly byte[] _statusBytes = Encoding.ASCII.GetBytes(HeaderNames.Status); - private static readonly byte[] _connectionBytes = Encoding.ASCII.GetBytes("connection"); - private static readonly byte[] _teBytes = Encoding.ASCII.GetBytes("te"); - private static readonly byte[] _trailersBytes = Encoding.ASCII.GetBytes("trailers"); - private static readonly byte[] _connectBytes = Encoding.ASCII.GetBytes("CONNECT"); - private readonly HttpConnectionContext _context; private readonly Http2FrameWriter _frameWriter; private readonly Pipe _input; @@ -50,7 +39,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 private readonly int _minAllocBufferSize; private readonly HPackDecoder _hpackDecoder; private readonly InputFlowControl _inputFlowControl; - private readonly OutputFlowControl _outputFlowControl = new OutputFlowControl(Http2PeerSettings.DefaultInitialWindowSize); + private readonly OutputFlowControl _outputFlowControl = new OutputFlowControl(new MultipleAwaitableProvider(), Http2PeerSettings.DefaultInitialWindowSize); private readonly Http2PeerSettings _serverSettings = new Http2PeerSettings(); private readonly Http2PeerSettings _clientSettings = new Http2PeerSettings(); @@ -66,7 +55,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 private int _highestOpenedStreamId; private bool _gracefulCloseStarted; - private readonly Dictionary _streams = new Dictionary(); private int _clientActiveStreamCount = 0; private int _serverActiveStreamCount = 0; @@ -76,6 +64,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 private int _gracefulCloseInitiator; private int _isClosed; + // Internal for testing + internal readonly Dictionary _streams = new Dictionary(); + internal Http2StreamStack StreamPool; + + internal const int InitialStreamPoolSize = 5; + internal const int MaxStreamPoolSize = 40; + public Http2Connection(HttpConnectionContext context) { var httpLimits = context.ServiceContext.ServerOptions.Limits; @@ -115,6 +110,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 _serverSettings.HeaderTableSize = (uint)http2Limits.HeaderTableSize; _serverSettings.MaxHeaderListSize = (uint)httpLimits.MaxRequestHeadersTotalSize; _serverSettings.InitialWindowSize = (uint)http2Limits.InitialStreamWindowSize; + + // Start pool off at a smaller size if the max number of streams is less than the InitialStreamPoolSize + StreamPool = new Http2StreamStack(Math.Min(InitialStreamPoolSize, http2Limits.MaxStreamsPerConnection)); + _inputTask = ReadInputAsync(); } @@ -204,27 +203,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 while (_isClosed == 0) { var result = await Input.ReadAsync(); - var readableBuffer = result.Buffer; - var consumed = readableBuffer.Start; - var examined = readableBuffer.Start; + var buffer = result.Buffer; - // Call UpdateCompletedStreams() prior to frame processing in order to remove any streams that have exceded their drain timeouts. + // Call UpdateCompletedStreams() prior to frame processing in order to remove any streams that have exceeded their drain timeouts. UpdateCompletedStreams(); try { - if (!readableBuffer.IsEmpty) + while (Http2FrameReader.TryReadFrame(ref buffer, _incomingFrame, _serverSettings.MaxFrameSize, out var framePayload)) { - if (Http2FrameReader.ReadFrame(readableBuffer, _incomingFrame, _serverSettings.MaxFrameSize, out var framePayload)) - { - Log.Http2FrameReceived(ConnectionId, _incomingFrame); - consumed = examined = framePayload.End; - await ProcessFrameAsync(application, framePayload); - } - else - { - examined = readableBuffer.End; - } + Log.Http2FrameReceived(ConnectionId, _incomingFrame); + await ProcessFrameAsync(application, framePayload); } if (result.IsCompleted) @@ -242,7 +231,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 } finally { - Input.AdvanceTo(consumed, examined); + Input.AdvanceTo(buffer.Start, buffer.End); UpdateConnectionState(); } @@ -315,6 +304,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 UpdateCompletedStreams(); } + while (StreamPool.TryPop(out var pooledStream)) + { + pooledStream.Dispose(); + } + // This cancels keep-alive and request header timeouts, but not the response drain timeout. TimeoutControl.CancelTimeout(); TimeoutControl.StartDrainTimeout(Limits.MinResponseDataRate, Limits.MaxResponseBufferSize); @@ -573,25 +567,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 } // Start a new stream - _currentHeadersStream = new Http2Stream(application, new Http2StreamContext - { - ConnectionId = ConnectionId, - StreamId = _incomingFrame.StreamId, - ServiceContext = _context.ServiceContext, - ConnectionFeatures = _context.ConnectionFeatures, - MemoryPool = _context.MemoryPool, - LocalEndPoint = _context.LocalEndPoint, - RemoteEndPoint = _context.RemoteEndPoint, - StreamLifetimeHandler = this, - ClientPeerSettings = _clientSettings, - ServerPeerSettings = _serverSettings, - FrameWriter = _frameWriter, - ConnectionInputFlowControl = _inputFlowControl, - ConnectionOutputFlowControl = _outputFlowControl, - TimeoutControl = TimeoutControl, - }); + _currentHeadersStream = GetStream(application); - _currentHeadersStream.Reset(); _headerFlags = _incomingFrame.HeadersFlags; var headersPayload = payload.Slice(0, _incomingFrame.HeadersPayloadLength); // Minus padding @@ -599,6 +576,52 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 } } + private Http2Stream GetStream(IHttpApplication application) + { + if (StreamPool.TryPop(out var stream)) + { + stream.InitializeWithExistingContext(_incomingFrame.StreamId); + return stream; + } + + return new Http2Stream( + application, + CreateHttp2StreamContext()); + } + + private Http2StreamContext CreateHttp2StreamContext() + { + return new Http2StreamContext + { + ConnectionId = ConnectionId, + StreamId = _incomingFrame.StreamId, + ServiceContext = _context.ServiceContext, + ConnectionFeatures = _context.ConnectionFeatures, + MemoryPool = _context.MemoryPool, + LocalEndPoint = _context.LocalEndPoint, + RemoteEndPoint = _context.RemoteEndPoint, + StreamLifetimeHandler = this, + ClientPeerSettings = _clientSettings, + ServerPeerSettings = _serverSettings, + FrameWriter = _frameWriter, + ConnectionInputFlowControl = _inputFlowControl, + ConnectionOutputFlowControl = _outputFlowControl, + TimeoutControl = TimeoutControl, + }; + } + + private void ReturnStream(Http2Stream stream) + { + // We're conservative about what streams we can reuse. + // If there is a chance the stream is still in use then don't attempt to reuse it. + Debug.Assert(stream.CanReuse); + + if (StreamPool.Count < MaxStreamPoolSize) + { + StreamPool.Push(stream); + } + } + private Task ProcessPriorityFrameAsync() { if (_currentHeadersStream != null) @@ -891,6 +914,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 } catch (Http2StreamErrorException) { + _currentHeadersStream.Dispose(); ResetRequestHeaderParsingState(); throw; } @@ -913,41 +937,62 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 private void StartStream() { - if (!_isMethodConnect && (_parsedPseudoHeaderFields & _mandatoryRequestPseudoHeaderFields) != _mandatoryRequestPseudoHeaderFields) - { - // All HTTP/2 requests MUST include exactly one valid value for the :method, :scheme, and :path pseudo-header - // fields, unless it is a CONNECT request (Section 8.3). An HTTP request that omits mandatory pseudo-header - // fields is malformed (Section 8.1.2.6). - throw new Http2StreamErrorException(_currentHeadersStream.StreamId, CoreStrings.Http2ErrorMissingMandatoryPseudoHeaderFields, Http2ErrorCode.PROTOCOL_ERROR); - } - - if (_clientActiveStreamCount >= _serverSettings.MaxConcurrentStreams) - { - throw new Http2StreamErrorException(_currentHeadersStream.StreamId, CoreStrings.Http2ErrorMaxStreams, Http2ErrorCode.REFUSED_STREAM); - } - - // We don't use the _serverActiveRequestCount here as during shutdown, it and the dictionary - // counts get out of sync during shutdown. The streams still exist in the dictionary until the client responds with a RST or END_STREAM. - // Also, we care about the dictionary size for too much memory consumption. - if (_streams.Count >= _serverSettings.MaxConcurrentStreams * 2) - { - // Server is getting hit hard with connection resets. - // Tell client to calm down. - // TODO consider making when to send ENHANCE_YOUR_CALM configurable? - throw new Http2StreamErrorException(_currentHeadersStream.StreamId, CoreStrings.Http2TellClientToCalmDown, Http2ErrorCode.ENHANCE_YOUR_CALM); - } - // This must be initialized before we offload the request or else we may start processing request body frames without it. - _currentHeadersStream.InputRemaining = _currentHeadersStream.RequestHeaders.ContentLength; - - // This must wait until we've received all of the headers so we can verify the content-length. - if ((_headerFlags & Http2HeadersFrameFlags.END_STREAM) == Http2HeadersFrameFlags.END_STREAM) - { - _currentHeadersStream.OnEndStreamReceived(); - } - + // The stream now exists and must be tracked and drained even if Http2StreamErrorException is thrown before dispatching to the application. _streams[_incomingFrame.StreamId] = _currentHeadersStream; IncrementActiveClientStreamCount(); _serverActiveStreamCount++; + + try + { + // This must be initialized before we offload the request or else we may start processing request body frames without it. + _currentHeadersStream.InputRemaining = _currentHeadersStream.RequestHeaders.ContentLength; + + // This must wait until we've received all of the headers so we can verify the content-length. + // We also must set the proper EndStream state before rejecting the request for any reason. + if ((_headerFlags & Http2HeadersFrameFlags.END_STREAM) == Http2HeadersFrameFlags.END_STREAM) + { + _currentHeadersStream.OnEndStreamReceived(); + } + + if (!_isMethodConnect && (_parsedPseudoHeaderFields & _mandatoryRequestPseudoHeaderFields) != _mandatoryRequestPseudoHeaderFields) + { + // All HTTP/2 requests MUST include exactly one valid value for the :method, :scheme, and :path pseudo-header + // fields, unless it is a CONNECT request (Section 8.3). An HTTP request that omits mandatory pseudo-header + // fields is malformed (Section 8.1.2.6). + throw new Http2StreamErrorException(_currentHeadersStream.StreamId, CoreStrings.Http2ErrorMissingMandatoryPseudoHeaderFields, Http2ErrorCode.PROTOCOL_ERROR); + } + + if (_clientActiveStreamCount > _serverSettings.MaxConcurrentStreams) + { + // The protocol default stream limit is infinite so the client can exceed our limit at the start of the connection. + // Refused streams can be retried, by which time the client must have received our settings frame with our limit information. + throw new Http2StreamErrorException(_currentHeadersStream.StreamId, CoreStrings.Http2ErrorMaxStreams, Http2ErrorCode.REFUSED_STREAM); + } + + // We don't use the _serverActiveRequestCount here as during shutdown, it and the dictionary counts get out of sync. + // The streams still exist in the dictionary until the client responds with a RST or END_STREAM. + // Also, we care about the dictionary size for too much memory consumption. + if (_streams.Count > _serverSettings.MaxConcurrentStreams * 2) + { + // Server is getting hit hard with connection resets. + // Tell client to calm down. + // TODO consider making when to send ENHANCE_YOUR_CALM configurable? + throw new Http2StreamErrorException(_currentHeadersStream.StreamId, CoreStrings.Http2TellClientToCalmDown, Http2ErrorCode.ENHANCE_YOUR_CALM); + } + } + catch (Http2StreamErrorException) + { + MakeSpaceInDrainQueue(); + + // Because this stream isn't being queued, OnRequestProcessingEnded will not be + // automatically called and the stream won't be completed. + // Manually complete stream to ensure pipes are completed. + // Completing the stream will add it to the completed stream queue. + _currentHeadersStream.DecrementActiveClientStreamCount(); + _currentHeadersStream.CompleteStream(errored: true); + throw; + } + // Must not allow app code to block the connection handling loop. ThreadPool.UnsafeQueueUserWorkItem(_currentHeadersStream, preferLocal: false); } @@ -1030,7 +1075,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamClosed(_incomingFrame.Type, _incomingFrame.StreamId), Http2ErrorCode.STREAM_CLOSED); } - _streams.Remove(stream.StreamId); + RemoveStream(stream); } else { @@ -1044,6 +1089,35 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 } } + private void RemoveStream(Http2Stream stream) + { + _streams.Remove(stream.StreamId); + if (stream.CanReuse) + { + ReturnStream(stream); + } + else + { + stream.Dispose(); + } + } + + // Compare to UpdateCompletedStreams, but only removes streams if over the max stream drain limit. + private void MakeSpaceInDrainQueue() + { + var maxStreams = _serverSettings.MaxConcurrentStreams * 2; + // If we're tracking too many streams, discard the oldest. + while (_streams.Count >= maxStreams && _completedStreams.TryDequeue(out var stream)) + { + if (stream.DrainExpirationTicks == default) + { + _serverActiveStreamCount--; + } + + RemoveStream(stream); + } + } + private void UpdateConnectionState() { if (_isClosed != 0) @@ -1090,7 +1164,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 // We can't throw a Http2StreamErrorException here, it interrupts the header decompression state and may corrupt subsequent header frames on other streams. // For now these either need to be connection errors or BadRequests. If we want to downgrade any of them to stream errors later then we need to // rework the flow so that the remaining headers are drained and the decompression state is maintained. - public void OnHeader(Span name, Span value) + public void OnHeader(ReadOnlySpan name, ReadOnlySpan value) { // https://tools.ietf.org/html/rfc7540#section-6.5.2 // "The value is based on the uncompressed size of header fields, including the length of the name and value in octets plus an overhead of 32 octets for each header field."; @@ -1124,10 +1198,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 } } - public void OnHeadersComplete() + public void OnHeadersComplete(bool endStream) => _currentHeadersStream.OnHeadersComplete(); - private void ValidateHeader(Span name, Span value) + private void ValidateHeader(ReadOnlySpan name, ReadOnlySpan value) { // http://httpwg.org/specs/rfc7540.html#rfc.section.8.1.2.1 /* @@ -1184,7 +1258,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 if (headerField == PseudoHeaderFields.Method) { - _isMethodConnect = value.SequenceEqual(_connectBytes); + _isMethodConnect = value.SequenceEqual(ConnectBytes); } _parsedPseudoHeaderFields |= headerField; @@ -1217,7 +1291,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 } } - private bool IsPseudoHeaderField(Span name, out PseudoHeaderFields headerField) + private bool IsPseudoHeaderField(ReadOnlySpan name, out PseudoHeaderFields headerField) { headerField = PseudoHeaderFields.None; @@ -1226,23 +1300,23 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 return false; } - if (name.SequenceEqual(_pathBytes)) + if (name.SequenceEqual(PathBytes)) { headerField = PseudoHeaderFields.Path; } - else if (name.SequenceEqual(_methodBytes)) + else if (name.SequenceEqual(MethodBytes)) { headerField = PseudoHeaderFields.Method; } - else if (name.SequenceEqual(_schemeBytes)) + else if (name.SequenceEqual(SchemeBytes)) { headerField = PseudoHeaderFields.Scheme; } - else if (name.SequenceEqual(_statusBytes)) + else if (name.SequenceEqual(StatusBytes)) { headerField = PseudoHeaderFields.Status; } - else if (name.SequenceEqual(_authorityBytes)) + else if (name.SequenceEqual(AuthorityBytes)) { headerField = PseudoHeaderFields.Authority; } @@ -1254,9 +1328,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 return true; } - private static bool IsConnectionSpecificHeaderField(Span name, Span value) + private static bool IsConnectionSpecificHeaderField(ReadOnlySpan name, ReadOnlySpan value) { - return name.SequenceEqual(_connectionBytes) || (name.SequenceEqual(_teBytes) && !value.SequenceEqual(_trailersBytes)); + return name.SequenceEqual(ConnectionBytes) || (name.SequenceEqual(TeBytes) && !value.SequenceEqual(TrailersBytes)); } private bool TryClose() @@ -1329,6 +1403,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 } } + public void OnStaticIndexedHeader(int index) + { + throw new NotImplementedException(); + } + + public void OnStaticIndexedHeader(int index, ReadOnlySpan value) + { + throw new NotImplementedException(); + } + private class StreamCloseAwaitable : ICriticalNotifyCompletion { private static readonly Action _callbackCompleted = () => { }; diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2FrameWriter.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2FrameWriter.cs index 7555b9f223..fd8d553067 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2FrameWriter.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2FrameWriter.cs @@ -7,13 +7,14 @@ using System.Buffers.Binary; using System.Collections.Generic; using System.Diagnostics; using System.IO.Pipelines; +using System.Net.Http; +using System.Net.Http.HPack; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.FlowControl; -using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.PipeWriterHelpers; @@ -22,11 +23,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 internal class Http2FrameWriter { // Literal Header Field without Indexing - Indexed Name (Index 8 - :status) + // This uses C# compiler's ability to refer to static data directly. For more information see https://vcsjones.dev/2019/02/01/csharp-readonly-span-bytes-static private static ReadOnlySpan ContinueBytes => new byte[] { 0x08, 0x03, (byte)'1', (byte)'0', (byte)'0' }; private readonly object _writeLock = new object(); private readonly Http2Frame _outgoingFrame; - private readonly HPackEncoder _hpackEncoder = new HPackEncoder(); + private readonly Http2HeadersEnumerator _headersEnumerator = new Http2HeadersEnumerator(); private readonly ConcurrentPipeWriter _outputWriter; private readonly ConnectionContext _connectionContext; private readonly Http2Connection _http2Connection; @@ -159,7 +161,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 | Padding (*) ... +---------------------------------------------------------------+ */ - public void WriteResponseHeaders(int streamId, int statusCode, Http2HeadersFrameFlags headerFrameFlags, IHeaderDictionary headers) + public void WriteResponseHeaders(int streamId, int statusCode, Http2HeadersFrameFlags headerFrameFlags, HttpResponseHeaders headers) { lock (_writeLock) { @@ -170,9 +172,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 try { + _headersEnumerator.Initialize(headers); _outgoingFrame.PrepareHeaders(headerFrameFlags, streamId); var buffer = _headerEncodingBuffer.AsSpan(); - var done = _hpackEncoder.BeginEncode(statusCode, EnumerateHeaders(headers), buffer, out var payloadLength); + var done = HPackHeaderWriter.BeginEncodeHeaders(statusCode, _headersEnumerator, buffer, out var payloadLength); FinishWritingHeaders(streamId, payloadLength, done); } catch (HPackEncodingException hex) @@ -195,9 +198,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 try { + _headersEnumerator.Initialize(headers); _outgoingFrame.PrepareHeaders(Http2HeadersFrameFlags.END_STREAM, streamId); var buffer = _headerEncodingBuffer.AsSpan(); - var done = _hpackEncoder.BeginEncode(EnumerateHeaders(headers), buffer, out var payloadLength); + var done = HPackHeaderWriter.BeginEncodeHeaders(_headersEnumerator, buffer, out var payloadLength); FinishWritingHeaders(streamId, payloadLength, done); } catch (HPackEncodingException hex) @@ -226,7 +230,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 { _outgoingFrame.PrepareContinuation(Http2ContinuationFrameFlags.NONE, streamId); - done = _hpackEncoder.Encode(buffer, out payloadLength); + done = HPackHeaderWriter.ContinueEncodeHeaders(_headersEnumerator, buffer, out payloadLength); _outgoingFrame.PayloadLength = payloadLength; if (done) @@ -239,7 +243,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 } } - public ValueTask WriteDataAsync(int streamId, StreamOutputFlowControl flowControl, in ReadOnlySequence data, bool endStream) + public ValueTask WriteDataAsync(int streamId, StreamOutputFlowControl flowControl, in ReadOnlySequence data, bool endStream, bool forceFlush) { // The Length property of a ReadOnlySequence can be expensive, so we cache the value. var dataLength = data.Length; @@ -261,7 +265,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 // This cast is safe since if dataLength would overflow an int, it's guaranteed to be greater than the available flow control window. flowControl.Advance((int)dataLength); WriteDataUnsynchronized(streamId, data, dataLength, endStream); - return TimeFlushUnsynchronizedAsync(); + + if (forceFlush) + { + return TimeFlushUnsynchronizedAsync(); + } + + return default; } } @@ -355,7 +365,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 while (dataLength > 0) { - OutputFlowControlAwaitable availabilityAwaitable; + ValueTask availabilityTask; var writeTask = default(ValueTask); lock (_writeLock) @@ -365,7 +375,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 break; } - var actual = flowControl.AdvanceUpToAndWait(dataLength, out availabilityAwaitable); + var actual = flowControl.AdvanceUpToAndWait(dataLength, out availabilityTask); if (actual > 0) { @@ -395,7 +405,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 } // Avoid timing writes that are already complete. This is likely to happen during the last iteration. - if (availabilityAwaitable == null && writeTask.IsCompleted) + if (availabilityTask.IsCompleted && writeTask.IsCompleted) { continue; } @@ -407,9 +417,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 // This awaitable releases continuations in FIFO order when the window updates. // It should be very rare for a continuation to run without any availability. - if (availabilityAwaitable != null) + if (!availabilityTask.IsCompleted) { - await availabilityAwaitable; + await availabilityTask; } flushResult = await writeTask; @@ -655,16 +665,5 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 flowControl.Abort(); } } - - private static IEnumerable> EnumerateHeaders(IHeaderDictionary headers) - { - foreach (var header in headers) - { - foreach (var value in header.Value) - { - yield return new KeyValuePair(header.Key, value); - } - } - } } } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2HeaderEnumerator.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2HeaderEnumerator.cs new file mode 100644 index 0000000000..421650b9fd --- /dev/null +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2HeaderEnumerator.cs @@ -0,0 +1,169 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections; +using System.Collections.Generic; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; +using Microsoft.Extensions.Primitives; + +namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 +{ + internal sealed class Http2HeadersEnumerator : IEnumerator> + { + private bool _isTrailers; + private HttpResponseHeaders.Enumerator _headersEnumerator; + private HttpResponseTrailers.Enumerator _trailersEnumerator; + private IEnumerator> _genericEnumerator; + private StringValues.Enumerator _stringValuesEnumerator; + + public KnownHeaderType KnownHeaderType { get; private set; } + public KeyValuePair Current { get; private set; } + object IEnumerator.Current => Current; + + public Http2HeadersEnumerator() + { + } + + public void Initialize(HttpResponseHeaders headers) + { + _headersEnumerator = headers.GetEnumerator(); + _trailersEnumerator = default; + _genericEnumerator = null; + _isTrailers = false; + + _stringValuesEnumerator = default; + Current = default; + KnownHeaderType = default; + } + + public void Initialize(HttpResponseTrailers headers) + { + _headersEnumerator = default; + _trailersEnumerator = headers.GetEnumerator(); + _genericEnumerator = null; + _isTrailers = true; + + _stringValuesEnumerator = default; + Current = default; + KnownHeaderType = default; + } + + public void Initialize(IDictionary headers) + { + _headersEnumerator = default; + _trailersEnumerator = default; + _genericEnumerator = headers.GetEnumerator(); + _isTrailers = false; + + _stringValuesEnumerator = default; + Current = default; + KnownHeaderType = default; + } + + public bool MoveNext() + { + if (MoveNextOnStringEnumerator()) + { + return true; + } + + if (!TryGetNextStringEnumerator(out _stringValuesEnumerator)) + { + return false; + } + + return MoveNextOnStringEnumerator(); + } + + private string GetCurrentKey() + { + if (_genericEnumerator != null) + { + return _genericEnumerator.Current.Key; + } + else if (_isTrailers) + { + return _trailersEnumerator.Current.Key; + } + else + { + return _headersEnumerator.Current.Key; + } + } + + private bool MoveNextOnStringEnumerator() + { + var result = _stringValuesEnumerator.MoveNext(); + Current = result ? new KeyValuePair(GetCurrentKey(), _stringValuesEnumerator.Current) : default; + return result; + } + + private bool TryGetNextStringEnumerator(out StringValues.Enumerator enumerator) + { + if (_genericEnumerator != null) + { + if (!_genericEnumerator.MoveNext()) + { + enumerator = default; + return false; + } + else + { + enumerator = _genericEnumerator.Current.Value.GetEnumerator(); + KnownHeaderType = default; + return true; + } + } + else if (_isTrailers) + { + if (!_trailersEnumerator.MoveNext()) + { + enumerator = default; + return false; + } + else + { + enumerator = _trailersEnumerator.Current.Value.GetEnumerator(); + KnownHeaderType = _trailersEnumerator.CurrentKnownType; + return true; + } + } + else + { + if (!_headersEnumerator.MoveNext()) + { + enumerator = default; + return false; + } + else + { + enumerator = _headersEnumerator.Current.Value.GetEnumerator(); + KnownHeaderType = _headersEnumerator.CurrentKnownType; + return true; + } + } + } + + public void Reset() + { + if (_genericEnumerator != null) + { + _genericEnumerator.Reset(); + } + else if (_isTrailers) + { + _trailersEnumerator.Reset(); + } + else + { + _headersEnumerator.Reset(); + } + _stringValuesEnumerator = default; + KnownHeaderType = default; + } + + public void Dispose() + { + } + } +} diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2MessageBody.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2MessageBody.cs index 7dad036475..dd62d69cbb 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2MessageBody.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2MessageBody.cs @@ -15,7 +15,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 private readonly Http2Stream _context; private ReadResult _readResult; - private Http2MessageBody(Http2Stream context) + public Http2MessageBody(Http2Stream context) : base(context) { _context = context; @@ -46,14 +46,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 AddAndCheckConsumedBytes(bytesRead); } - public static MessageBody For(Http2Stream context) + public override void Reset() { - if (context.ReceivedEmptyRequestBody) - { - return ZeroContentLengthClose; - } - - return new Http2MessageBody(context); + base.Reset(); + _readResult = default; } public override void AdvanceTo(SequencePosition consumed) diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2OutputProducer.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2OutputProducer.cs index 18adcc1a82..0f95f70ef7 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2OutputProducer.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2OutputProducer.cs @@ -7,6 +7,7 @@ using System.Diagnostics; using System.IO.Pipelines; using System.Threading; using System.Threading.Tasks; +using System.Threading.Tasks.Sources; using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; @@ -17,9 +18,9 @@ using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 { - internal class Http2OutputProducer : IHttpOutputProducer, IHttpOutputAborter + internal class Http2OutputProducer : IHttpOutputProducer, IHttpOutputAborter, IValueTaskSource, IDisposable { - private readonly int _streamId; + private int StreamId => _stream.StreamId; private readonly Http2FrameWriter _frameWriter; private readonly TimingPipeFlusher _flusher; private readonly IKestrelTrace _log; @@ -30,53 +31,83 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 private readonly MemoryPool _memoryPool; private readonly Http2Stream _stream; private readonly object _dataWriterLock = new object(); - private readonly PipeWriter _pipeWriter; + private readonly Pipe _pipe; + private readonly ConcurrentPipeWriter _pipeWriter; private readonly PipeReader _pipeReader; - private readonly ValueTask _dataWriteProcessingTask; + private readonly ManualResetValueTaskSource _resetAwaitable = new ManualResetValueTaskSource(); + private IMemoryOwner _fakeMemoryOwner; private bool _startedWritingDataFrames; - private bool _completed; + private bool _streamCompleted; private bool _suffixSent; private bool _streamEnded; + private bool _writerComplete; private bool _disposed; - private IMemoryOwner _fakeMemoryOwner; + // Internal for testing + internal ValueTask _dataWriteProcessingTask; - public Http2OutputProducer( - int streamId, - Http2FrameWriter frameWriter, - StreamOutputFlowControl flowControl, - MemoryPool pool, - Http2Stream stream, - IKestrelTrace log) + /// The core logic for the IValueTaskSource implementation. + private ManualResetValueTaskSourceCore _responseCompleteTaskSource = new ManualResetValueTaskSourceCore { RunContinuationsAsynchronously = true }; // mutable struct, do not make this readonly + + // This object is itself usable as a backing source for ValueTask. Since there's only ever one awaiter + // for this object's state transitions at a time, we allow the object to be awaited directly. All functionality + // associated with the implementation is just delegated to the ManualResetValueTaskSourceCore. + private ValueTask GetWaiterTask() => new ValueTask(this, _responseCompleteTaskSource.Version); + ValueTaskSourceStatus IValueTaskSource.GetStatus(short token) => _responseCompleteTaskSource.GetStatus(token); + void IValueTaskSource.OnCompleted(Action continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags) => _responseCompleteTaskSource.OnCompleted(continuation, state, token, flags); + FlushResult IValueTaskSource.GetResult(short token) => _responseCompleteTaskSource.GetResult(token); + + public Http2OutputProducer(Http2Stream stream, Http2StreamContext context, StreamOutputFlowControl flowControl) { - _streamId = streamId; - _frameWriter = frameWriter; - _flowControl = flowControl; - _memoryPool = pool; _stream = stream; - _log = log; + _frameWriter = context.FrameWriter; + _flowControl = flowControl; + _memoryPool = context.MemoryPool; + _log = context.ServiceContext.Log; - var pipe = CreateDataPipe(pool); + _pipe = CreateDataPipe(_memoryPool); - _pipeWriter = new ConcurrentPipeWriter(pipe.Writer, pool, _dataWriterLock); - _pipeReader = pipe.Reader; + _pipeWriter = new ConcurrentPipeWriter(_pipe.Writer, _memoryPool, _dataWriterLock); + _pipeReader = _pipe.Reader; // No need to pass in timeoutControl here, since no minDataRates are passed to the TimingPipeFlusher. // The minimum output data rate is enforced at the connection level by Http2FrameWriter. - _flusher = new TimingPipeFlusher(_pipeWriter, timeoutControl: null, log); + _flusher = new TimingPipeFlusher(_pipeWriter, timeoutControl: null, _log); + _dataWriteProcessingTask = ProcessDataWrites(); } - public void Dispose() + public void StreamReset() + { + // Data background task must still be running. + Debug.Assert(!_dataWriteProcessingTask.IsCompleted); + // Response should have been completed. + Debug.Assert(_responseCompleteTaskSource.GetStatus(_responseCompleteTaskSource.Version) == ValueTaskSourceStatus.Succeeded); + + _streamEnded = false; + _suffixSent = false; + _startedWritingDataFrames = false; + _streamCompleted = false; + _writerComplete = false; + + _pipe.Reset(); + _pipeWriter.Reset(); + _responseCompleteTaskSource.Reset(); + + // Trigger the data process task to resume + _resetAwaitable.SetResult(null); + } + + public void Complete() { lock (_dataWriterLock) { - if (_disposed) + if (_writerComplete) { return; } - _disposed = true; + _writerComplete = true; Stop(); @@ -107,9 +138,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 lock (_dataWriterLock) { - ThrowIfSuffixSentOrDisposed(); + ThrowIfSuffixSentOrCompleted(); - if (_completed) + if (_streamCompleted) { return default; } @@ -133,14 +164,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 { lock (_dataWriterLock) { - ThrowIfSuffixSentOrDisposed(); + ThrowIfSuffixSentOrCompleted(); - if (_completed) + if (_streamCompleted) { return default; } - return _frameWriter.Write100ContinueAsync(_streamId); + return _frameWriter.Write100ContinueAsync(StreamId); } } @@ -150,7 +181,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 { // The HPACK header compressor is stateful, if we compress headers for an aborted stream we must send them. // Optimize for not compressing or sending them. - if (_completed) + if (_streamCompleted) { return; } @@ -175,7 +206,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 http2HeadersFrame = Http2HeadersFrameFlags.NONE; } - _frameWriter.WriteResponseHeaders(_streamId, statusCode, http2HeadersFrame, responseHeaders); + _frameWriter.WriteResponseHeaders(StreamId, statusCode, http2HeadersFrame, responseHeaders); } } @@ -188,11 +219,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 lock (_dataWriterLock) { - ThrowIfSuffixSentOrDisposed(); + ThrowIfSuffixSentOrCompleted(); // This length check is important because we don't want to set _startedWritingDataFrames unless a data // frame will actually be written causing the headers to be flushed. - if (_completed || data.Length == 0) + if (_streamCompleted || data.Length == 0) { return Task.CompletedTask; } @@ -208,16 +239,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 { lock (_dataWriterLock) { - if (_completed) + if (_streamCompleted) { - return _dataWriteProcessingTask; + return GetWaiterTask(); } - _completed = true; + _streamCompleted = true; _suffixSent = true; _pipeWriter.Complete(); - return _dataWriteProcessingTask; + return GetWaiterTask(); } } @@ -228,7 +259,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 // Always send the reset even if the response body is _completed. The request body may not have completed yet. Stop(); - return _frameWriter.WriteRstStreamAsync(_streamId, error); + return _frameWriter.WriteRstStreamAsync(StreamId, error); } } @@ -236,9 +267,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 { lock (_dataWriterLock) { - ThrowIfSuffixSentOrDisposed(); + ThrowIfSuffixSentOrCompleted(); - if (_completed) + if (_streamCompleted) { return; } @@ -253,9 +284,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 { lock (_dataWriterLock) { - ThrowIfSuffixSentOrDisposed(); + ThrowIfSuffixSentOrCompleted(); - if (_completed) + if (_streamCompleted) { return GetFakeMemory(sizeHint).Span; } @@ -268,9 +299,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 { lock (_dataWriterLock) { - ThrowIfSuffixSentOrDisposed(); + ThrowIfSuffixSentOrCompleted(); - if (_completed) + if (_streamCompleted) { return GetFakeMemory(sizeHint); } @@ -283,7 +314,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 { lock (_dataWriterLock) { - if (_completed) + if (_streamCompleted) { return; } @@ -301,11 +332,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 lock (_dataWriterLock) { - ThrowIfSuffixSentOrDisposed(); + ThrowIfSuffixSentOrCompleted(); // This length check is important because we don't want to set _startedWritingDataFrames unless a data // frame will actually be written causing the headers to be flushed. - if (_completed || data.Length == 0) + if (_streamCompleted || data.Length == 0) { return default; } @@ -341,12 +372,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 { lock (_dataWriterLock) { - if (_completed) + if (_streamCompleted) { return; } - _completed = true; + _streamCompleted = true; _pipeReader.CancelPendingRead(); @@ -358,66 +389,82 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 { } - private async ValueTask ProcessDataWrites() + private async ValueTask ProcessDataWrites() { - FlushResult flushResult = default; - try + // ProcessDataWrites runs for the lifetime of the Http2OutputProducer, and is designed to be reused by multiple streams. + // When Http2OutputProducer is no longer used (e.g. a stream is aborted and will no longer be used, or the connection is closed) + // it should be disposed so ProcessDataWrites exits. Not disposing won't cause a memory leak in release builds, but in debug + // builds active tasks are rooted on Task.s_currentActiveTasks. Dispose could be removed in the future when active tasks are + // tracked by a weak reference. See https://github.com/dotnet/runtime/issues/26565 + do { - ReadResult readResult; - - do + FlushResult flushResult = default; + ReadResult readResult = default; + try { - readResult = await _pipeReader.ReadAsync(); - if (readResult.IsCanceled) + do { - // Response body is aborted, break and complete reader. - break; - } - else if (readResult.IsCompleted && _stream.ResponseTrailers?.Count > 0) - { - // Output is ending and there are trailers to write - // Write any remaining content then write trailers - if (readResult.Buffer.Length > 0) + readResult = await _pipeReader.ReadAsync(); + + if (readResult.IsCanceled) { - flushResult = await _frameWriter.WriteDataAsync(_streamId, _flowControl, readResult.Buffer, endStream: false); + // Response body is aborted, break and complete reader. + break; } - - _stream.ResponseTrailers.SetReadOnly(); - _stream.DecrementActiveClientStreamCount(); - flushResult = await _frameWriter.WriteResponseTrailers(_streamId, _stream.ResponseTrailers); - } - else if (readResult.IsCompleted && _streamEnded) - { - if (readResult.Buffer.Length != 0) + else if (readResult.IsCompleted && _stream.ResponseTrailers?.Count > 0) { - ThrowUnexpectedState(); - } + // Output is ending and there are trailers to write + // Write any remaining content then write trailers + if (readResult.Buffer.Length > 0) + { + // Only flush if required (i.e. content length exceeds flow control availability) + // Writing remaining content without flushing allows content and trailers to be sent in the same packet + await _frameWriter.WriteDataAsync(StreamId, _flowControl, readResult.Buffer, endStream: false, forceFlush: false); + } - // Headers have already been written and there is no other content to write - flushResult = await _frameWriter.FlushAsync(outputAborter: null, cancellationToken: default); - } - else - { - var endStream = readResult.IsCompleted; - if (endStream) - { + _stream.ResponseTrailers.SetReadOnly(); _stream.DecrementActiveClientStreamCount(); + flushResult = await _frameWriter.WriteResponseTrailers(StreamId, _stream.ResponseTrailers); } - flushResult = await _frameWriter.WriteDataAsync(_streamId, _flowControl, readResult.Buffer, endStream); - } + else if (readResult.IsCompleted && _streamEnded) + { + if (readResult.Buffer.Length != 0) + { + ThrowUnexpectedState(); + } - _pipeReader.AdvanceTo(readResult.Buffer.End); - } while (!readResult.IsCompleted); - } - catch (Exception ex) - { - _log.LogCritical(ex, nameof(Http2OutputProducer) + "." + nameof(ProcessDataWrites) + " observed an unexpected exception."); - } + // Headers have already been written and there is no other content to write + flushResult = await _frameWriter.FlushAsync(outputAborter: null, cancellationToken: default); + } + else + { + var endStream = readResult.IsCompleted; + if (endStream) + { + _stream.DecrementActiveClientStreamCount(); + } + flushResult = await _frameWriter.WriteDataAsync(StreamId, _flowControl, readResult.Buffer, endStream, forceFlush: true); + } - _pipeReader.Complete(); + _pipeReader.AdvanceTo(readResult.Buffer.End); + } while (!readResult.IsCompleted); + } + catch (Exception ex) + { + _log.LogCritical(ex, nameof(Http2OutputProducer) + "." + nameof(ProcessDataWrites) + " observed an unexpected exception."); + } - return flushResult; + _pipeReader.Complete(); + + // Signal via WriteStreamSuffixAsync to the stream that output has finished. + // Stream state will move to RequestProcessingStatus.ResponseCompleted + _responseCompleteTaskSource.SetResult(flushResult); + + // Wait here for the stream to be reset or disposed. + await new ValueTask(_resetAwaitable, _resetAwaitable.Version); + _resetAwaitable.Reset(); + } while (!_disposed); static void ThrowUnexpectedState() { @@ -436,16 +483,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 } [StackTraceHidden] - private void ThrowIfSuffixSentOrDisposed() + private void ThrowIfSuffixSentOrCompleted() { if (_suffixSent) { ThrowSuffixSent(); } - if (_disposed) + if (_writerComplete) { - ThrowDisposed(); + ThrowWriterComplete(); } } @@ -456,7 +503,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 } [StackTraceHidden] - private static void ThrowDisposed() + private static void ThrowWriterComplete() { throw new InvalidOperationException("Cannot write to response after the request has completed."); } @@ -472,5 +519,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 useSynchronizationContext: false, minimumSegmentSize: pool.GetMinimumSegmentSize() )); + + public void Dispose() + { + if (_disposed) + { + return; + } + _disposed = true; + + // Set awaitable after disposed is true to ensure ProcessDataWrites exits successfully. + _resetAwaitable.SetResult(null); + } } } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Stream.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Stream.cs index 27dd7762ae..f6a19584f2 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Stream.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Stream.cs @@ -17,47 +17,71 @@ using Microsoft.Net.Http.Headers; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 { - internal abstract partial class Http2Stream : HttpProtocol, IThreadPoolWorkItem + internal abstract partial class Http2Stream : HttpProtocol, IThreadPoolWorkItem, IDisposable { - private readonly Http2StreamContext _context; - private readonly Http2OutputProducer _http2Output; - private readonly StreamInputFlowControl _inputFlowControl; - private readonly StreamOutputFlowControl _outputFlowControl; + private Http2StreamContext _context; + private Http2OutputProducer _http2Output; + private StreamInputFlowControl _inputFlowControl; + private StreamOutputFlowControl _outputFlowControl; + private Http2MessageBody _messageBody; private bool _decrementCalled; - public Pipe RequestBodyPipe { get; } + + public Pipe RequestBodyPipe { get; set; } internal long DrainExpirationTicks { get; set; } private StreamCompletionFlags _completionState; private readonly object _completionLock = new object(); - public Http2Stream(Http2StreamContext context) - : base(context) + public void Initialize(Http2StreamContext context) { + base.Initialize(context); + + CanReuse = false; + _decrementCalled = false; + _completionState = StreamCompletionFlags.None; + InputRemaining = null; + RequestBodyStarted = false; + DrainExpirationTicks = 0; + _context = context; - _inputFlowControl = new StreamInputFlowControl( - context.StreamId, - context.FrameWriter, - context.ConnectionInputFlowControl, - context.ServerPeerSettings.InitialWindowSize, - context.ServerPeerSettings.InitialWindowSize / 2); + // First time the stream is used we need to create flow control, producer and pipes. + // When a stream is reused these types will be reset and reused. + if (_inputFlowControl == null) + { + _inputFlowControl = new StreamInputFlowControl( + this, + context.FrameWriter, + context.ConnectionInputFlowControl, + context.ServerPeerSettings.InitialWindowSize, + context.ServerPeerSettings.InitialWindowSize / 2); - _outputFlowControl = new StreamOutputFlowControl( - context.ConnectionOutputFlowControl, - context.ClientPeerSettings.InitialWindowSize); + _outputFlowControl = new StreamOutputFlowControl( + context.ConnectionOutputFlowControl, + context.ClientPeerSettings.InitialWindowSize); - _http2Output = new Http2OutputProducer( - context.StreamId, - context.FrameWriter, - _outputFlowControl, - context.MemoryPool, - this, - context.ServiceContext.Log); + _http2Output = new Http2OutputProducer(this, context, _outputFlowControl); - RequestBodyPipe = CreateRequestBodyPipe(context.ServerPeerSettings.InitialWindowSize); - Output = _http2Output; + RequestBodyPipe = CreateRequestBodyPipe(); + + Output = _http2Output; + } + else + { + _inputFlowControl.Reset(); + _outputFlowControl.Reset(context.ClientPeerSettings.InitialWindowSize); + _http2Output.StreamReset(); + RequestBodyPipe.Reset(); + } + } + + public void InitializeWithExistingContext(int streamId) + { + _context.StreamId = streamId; + + Initialize(_context); } public int StreamId => _context.StreamId; @@ -80,12 +104,22 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 } } + public bool CanReuse { get; private set; } + protected override void OnReset() { + _keepAlive = true; + _connectionAborted = false; + ResetHttp2Features(); } protected override void OnRequestProcessingEnded() + { + CompleteStream(errored: false); + } + + public void CompleteStream(bool errored) { try { @@ -93,19 +127,28 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 // If the app finished without reading the request body tell the client not to finish sending it. if (!EndStreamReceived && !RstStreamReceived) { - Log.RequestBodyNotEntirelyRead(ConnectionIdFeature, TraceIdentifier); + if (!errored) + { + Log.RequestBodyNotEntirelyRead(ConnectionIdFeature, TraceIdentifier); + } var (oldState, newState) = ApplyCompletionFlag(StreamCompletionFlags.Aborted); if (oldState != newState) { Debug.Assert(_decrementCalled); - // Don't block on IO. This never faults. - _ = _http2Output.WriteRstStreamAsync(Http2ErrorCode.NO_ERROR); + + // If there was an error starting the stream then we don't want to write RST_STREAM here. + // The connection will handle writing RST_STREAM with the correct error code. + if (!errored) + { + // Don't block on IO. This never faults. + _ = _http2Output.WriteRstStreamAsync(Http2ErrorCode.NO_ERROR); + } RequestBodyPipe.Writer.Complete(); } } - _http2Output.Dispose(); + _http2Output.Complete(); RequestBodyPipe.Reader.Complete(); @@ -113,7 +156,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 // connection's flow-control window. _inputFlowControl.Abort(); - Reset(); + // We only want to reuse a stream that was not aborted and has completely finished writing. + // This ensures Http2OutputProducer.ProcessDataWrites is in the correct state to be reused. + CanReuse = !_connectionAborted && HasResponseCompleted; } finally { @@ -125,7 +170,23 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 => StringUtilities.ConcatAsHexSuffix(ConnectionId, ':', (uint)StreamId); protected override MessageBody CreateMessageBody() - => Http2MessageBody.For(this); + { + if (ReceivedEmptyRequestBody) + { + return MessageBody.ZeroContentLengthClose; + } + + if (_messageBody != null) + { + _messageBody.Reset(); + } + else + { + _messageBody = new Http2MessageBody(this); + } + + return _messageBody; + } // Compare to Http1Connection.OnStartLine protected override bool TryParseRequest(ReadResult result, out bool endConnection) @@ -156,7 +217,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 // CONNECT - :scheme and :path must be excluded if (Method == HttpMethod.Connect) { - if (!String.IsNullOrEmpty(RequestHeaders[HeaderNames.Scheme]) || !String.IsNullOrEmpty(RequestHeaders[HeaderNames.Path])) + if (!String.IsNullOrEmpty(HttpRequestHeaders.HeaderScheme) || !String.IsNullOrEmpty(HttpRequestHeaders.HeaderPath)) { ResetAndAbort(new ConnectionAbortedException(CoreStrings.Http2ErrorConnectMustNotSendSchemeOrPath), Http2ErrorCode.PROTOCOL_ERROR); return false; @@ -175,16 +236,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 // - That said, we shouldn't allow arbitrary values or use them to populate Request.Scheme, right? // - For now we'll restrict it to http/s and require it match the transport. // - We'll need to find some concrete scenarios to warrant unblocking this. - if (!string.Equals(RequestHeaders[HeaderNames.Scheme], Scheme, StringComparison.OrdinalIgnoreCase)) + if (!string.Equals(HttpRequestHeaders.HeaderScheme, Scheme, StringComparison.OrdinalIgnoreCase)) { ResetAndAbort(new ConnectionAbortedException( - CoreStrings.FormatHttp2StreamErrorSchemeMismatch(RequestHeaders[HeaderNames.Scheme], Scheme)), Http2ErrorCode.PROTOCOL_ERROR); + CoreStrings.FormatHttp2StreamErrorSchemeMismatch(HttpRequestHeaders.HeaderScheme, Scheme)), Http2ErrorCode.PROTOCOL_ERROR); return false; } // :path (and query) - Required // Must start with / except may be * for OPTIONS - var path = RequestHeaders[HeaderNames.Path].ToString(); + var path = HttpRequestHeaders.HeaderPath.ToString(); RawTarget = path; // OPTIONS - https://tools.ietf.org/html/rfc7540#section-8.1.2.3 @@ -221,7 +282,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 private bool TryValidateMethod() { // :method - _methodText = RequestHeaders[HeaderNames.Method].ToString(); + _methodText = HttpRequestHeaders.HeaderMethod.ToString(); Method = HttpUtilities.GetKnownMethod(_methodText); if (Method == HttpMethod.None) @@ -247,7 +308,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 // :authority (optional) // Prefer this over Host - var authority = RequestHeaders[HeaderNames.Authority]; + var authority = HttpRequestHeaders.HeaderAuthority; var host = HttpRequestHeaders.HeaderHost; if (!StringValues.IsNullOrEmpty(authority)) { @@ -300,9 +361,18 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 try { + const int MaxPathBufferStackAllocSize = 256; + // The decoder operates only on raw bytes - var pathBuffer = new byte[pathSegment.Length].AsSpan(); - for (int i = 0; i < pathSegment.Length; i++) + Span pathBuffer = pathSegment.Length <= MaxPathBufferStackAllocSize + // A constant size plus slice generates better code + // https://github.com/dotnet/aspnetcore/pull/19273#discussion_r383159929 + ? stackalloc byte[MaxPathBufferStackAllocSize].Slice(0, pathSegment.Length) + // TODO - Consider pool here for less than 4096 + // https://github.com/dotnet/aspnetcore/pull/19273#discussion_r383604184 + : new byte[pathSegment.Length]; + + for (var i = 0; i < pathSegment.Length; i++) { var ch = pathSegment[i]; // The header parser should already be checking this @@ -504,7 +574,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 _context.StreamLifetimeHandler.DecrementActiveClientStreamCount(); } - private Pipe CreateRequestBodyPipe(uint windowSize) + private Pipe CreateRequestBodyPipe() => new Pipe(new PipeOptions ( pool: _context.MemoryPool, @@ -512,8 +582,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 writerScheduler: PipeScheduler.Inline, // Never pause within the window range. Flow control will prevent more data from being added. // See the assert in OnDataAsync. - pauseWriterThreshold: windowSize + 1, - resumeWriterThreshold: windowSize + 1, + pauseWriterThreshold: _context.ServerPeerSettings.InitialWindowSize + 1, + resumeWriterThreshold: _context.ServerPeerSettings.InitialWindowSize + 1, useSynchronizationContext: false, minimumSegmentSize: _context.MemoryPool.GetMinimumSegmentSize() )); @@ -534,6 +604,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 /// public abstract void Execute(); + public void Dispose() + { + _http2Output.Dispose(); + } + [Flags] private enum StreamCompletionFlags { diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2StreamOfT.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2StreamOfT.cs index e602618976..2ba223f43c 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2StreamOfT.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2StreamOfT.cs @@ -11,8 +11,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 { private readonly IHttpApplication _application; - public Http2Stream(IHttpApplication application, Http2StreamContext context) : base(context) + public Http2Stream(IHttpApplication application, Http2StreamContext context) { + Initialize(context); _application = application; } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2StreamStack.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2StreamStack.cs new file mode 100644 index 0000000000..1c141dd236 --- /dev/null +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2StreamStack.cs @@ -0,0 +1,74 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Runtime.CompilerServices; + +namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 +{ + // See https://github.com/dotnet/runtime/blob/master/src/libraries/System.IO.Pipelines/src/System/IO/Pipelines/BufferSegmentStack.cs + internal struct Http2StreamStack + { + private Http2StreamAsValueType[] _array; + private int _size; + + public Http2StreamStack(int size) + { + _array = new Http2StreamAsValueType[size]; + _size = 0; + } + + public int Count => _size; + + public bool TryPop(out Http2Stream result) + { + int size = _size - 1; + Http2StreamAsValueType[] array = _array; + + if ((uint)size >= (uint)array.Length) + { + result = default; + return false; + } + + _size = size; + result = array[size]; + array[size] = default; + return true; + } + + // Pushes an item to the top of the stack. + public void Push(Http2Stream item) + { + int size = _size; + Http2StreamAsValueType[] array = _array; + + if ((uint)size < (uint)array.Length) + { + array[size] = item; + _size = size + 1; + } + else + { + PushWithResize(item); + } + } + + // Non-inline from Stack.Push to improve its code quality as uncommon path + [MethodImpl(MethodImplOptions.NoInlining)] + private void PushWithResize(Http2Stream item) + { + Array.Resize(ref _array, 2 * _array.Length); + _array[_size] = item; + _size++; + } + + private readonly struct Http2StreamAsValueType + { + private readonly Http2Stream _value; + private Http2StreamAsValueType(Http2Stream value) => _value = value; + public static implicit operator Http2StreamAsValueType(Http2Stream s) => new Http2StreamAsValueType(s); + public static implicit operator Http2Stream(Http2StreamAsValueType s) => s._value; + } + } +} diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/DefaultStreamDirectionFeature.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/DefaultStreamDirectionFeature.cs new file mode 100644 index 0000000000..b96554c171 --- /dev/null +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/DefaultStreamDirectionFeature.cs @@ -0,0 +1,20 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNetCore.Connections.Features; + +namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3 +{ + internal class DefaultStreamDirectionFeature : IStreamDirectionFeature + { + public DefaultStreamDirectionFeature(bool canRead, bool canWrite) + { + CanRead = canRead; + CanWrite = canWrite; + } + + public bool CanRead { get; } + + public bool CanWrite { get; } + } +} diff --git a/src/Servers/Kestrel/perf/PlatformBenchmarks/Startup.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Frames/Http3RawFrame.Data.cs similarity index 55% rename from src/Servers/Kestrel/perf/PlatformBenchmarks/Startup.cs rename to src/Servers/Kestrel/Core/src/Internal/Http3/Frames/Http3RawFrame.Data.cs index fc04173046..b852ed8b5b 100644 --- a/src/Servers/Kestrel/perf/PlatformBenchmarks/Startup.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Frames/Http3RawFrame.Data.cs @@ -1,15 +1,14 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using Microsoft.AspNetCore.Builder; - -namespace PlatformBenchmarks +namespace System.Net.Http { - public class Startup + internal partial class Http3RawFrame { - public void Configure(IApplicationBuilder app) + public void PrepareData() { - + Length = 0; + Type = Http3FrameType.Data; } } } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Frames/Http3RawFrame.GoAway.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Frames/Http3RawFrame.GoAway.cs new file mode 100644 index 0000000000..53bdc4d4bd --- /dev/null +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Frames/Http3RawFrame.GoAway.cs @@ -0,0 +1,14 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace System.Net.Http +{ + internal partial class Http3RawFrame + { + public void PrepareGoAway() + { + Length = 0; + Type = Http3FrameType.GoAway; + } + } +} diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Frames/Http3RawFrame.Headers.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Frames/Http3RawFrame.Headers.cs new file mode 100644 index 0000000000..9913c010bd --- /dev/null +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Frames/Http3RawFrame.Headers.cs @@ -0,0 +1,14 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace System.Net.Http +{ + internal partial class Http3RawFrame + { + public void PrepareHeaders() + { + Length = 0; + Type = Http3FrameType.Headers; + } + } +} diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Frames/Http3RawFrame.Settings.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Frames/Http3RawFrame.Settings.cs new file mode 100644 index 0000000000..a90902470d --- /dev/null +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Frames/Http3RawFrame.Settings.cs @@ -0,0 +1,14 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace System.Net.Http +{ + internal partial class Http3RawFrame + { + public void PrepareSettings() + { + Length = 0; + Type = Http3FrameType.Settings; + } + } +} diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Frames/Http3RawFrame.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Frames/Http3RawFrame.cs new file mode 100644 index 0000000000..f174f4b326 --- /dev/null +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Frames/Http3RawFrame.cs @@ -0,0 +1,17 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace System.Net.Http +{ + internal partial class Http3RawFrame + { + public long Length { get; set; } + + public Http3FrameType Type { get; internal set; } + + public override string ToString() + { + return $"{Type} Length: {Length}"; + } + } +} diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs new file mode 100644 index 0000000000..3aa7d990a3 --- /dev/null +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs @@ -0,0 +1,379 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Net; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Connections; +using Microsoft.AspNetCore.Connections.Features; +using Microsoft.AspNetCore.Hosting.Server; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3.QPack; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3 +{ + internal class Http3Connection : IRequestProcessor, ITimeoutHandler + { + public DynamicTable DynamicTable { get; set; } + + public Http3ControlStream ControlStream { get; set; } + public Http3ControlStream EncoderStream { get; set; } + public Http3ControlStream DecoderStream { get; set; } + + internal readonly Dictionary _streams = new Dictionary(); + + private long _highestOpenedStreamId; // TODO lock to access + private volatile bool _haveSentGoAway; + private object _sync = new object(); + private MultiplexedConnectionContext _multiplexedContext; + private readonly Http3ConnectionContext _context; + private readonly ISystemClock _systemClock; + private readonly TimeoutControl _timeoutControl; + private bool _aborted; + private object _protocolSelectionLock = new object(); + + public Http3Connection(Http3ConnectionContext context) + { + _multiplexedContext = context.ConnectionContext; + _context = context; + DynamicTable = new DynamicTable(0); + _systemClock = context.ServiceContext.SystemClock; + _timeoutControl = new TimeoutControl(this); + _context.TimeoutControl ??= _timeoutControl; + } + + internal long HighestStreamId + { + get + { + return _highestOpenedStreamId; + } + set + { + if (_highestOpenedStreamId < value) + { + _highestOpenedStreamId = value; + } + } + } + + private IKestrelTrace Log => _context.ServiceContext.Log; + + public async Task ProcessRequestsAsync(IHttpApplication httpApplication) + { + try + { + // Ensure TimeoutControl._lastTimestamp is initialized before anything that could set timeouts runs. + _timeoutControl.Initialize(_systemClock.UtcNowTicks); + + var connectionHeartbeatFeature = _context.ConnectionFeatures.Get(); + var connectionLifetimeNotificationFeature = _context.ConnectionFeatures.Get(); + + // These features should never be null in Kestrel itself, if this middleware is ever refactored to run outside of kestrel, + // we'll need to handle these missing. + Debug.Assert(connectionHeartbeatFeature != null, nameof(IConnectionHeartbeatFeature) + " is missing!"); + Debug.Assert(connectionLifetimeNotificationFeature != null, nameof(IConnectionLifetimeNotificationFeature) + " is missing!"); + + // Register the various callbacks once we're going to start processing requests + + // The heart beat for various timeouts + connectionHeartbeatFeature?.OnHeartbeat(state => ((Http3Connection)state).Tick(), this); + + // Register for graceful shutdown of the server + using var shutdownRegistration = connectionLifetimeNotificationFeature?.ConnectionClosedRequested.Register(state => ((Http3Connection)state).StopProcessingNextRequest(), this); + + // Register for connection close + using var closedRegistration = _context.ConnectionContext.ConnectionClosed.Register(state => ((Http3Connection)state).OnConnectionClosed(), this); + + await InnerProcessRequestsAsync(httpApplication); + } + catch (Exception ex) + { + Log.LogCritical(0, ex, $"Unexpected exception in {nameof(Http3Connection)}.{nameof(ProcessRequestsAsync)}."); + } + finally + { + } + } + + // For testing only + internal void Initialize() + { + } + + public void StopProcessingNextRequest() + { + bool previousState; + lock (_protocolSelectionLock) + { + previousState = _aborted; + } + + // TODO figure out how to gracefully close next requests + } + + public void OnConnectionClosed() + { + bool previousState; + lock (_protocolSelectionLock) + { + previousState = _aborted; + } + + // TODO figure out how to gracefully close next requests + } + + public void Abort(ConnectionAbortedException ex) + { + bool previousState; + + lock (_protocolSelectionLock) + { + previousState = _aborted; + _aborted = true; + } + + if (!previousState) + { + InnerAbort(ex); + } + } + + public void Tick() + { + if (_aborted) + { + // It's safe to check for timeouts on a dead connection, + // but try not to in order to avoid extraneous logs. + return; + } + + // It's safe to use UtcNowUnsynchronized since Tick is called by the Heartbeat. + var now = _systemClock.UtcNowUnsynchronized; + _timeoutControl.Tick(now); + } + + public void OnTimeout(TimeoutReason reason) + { + // In the cases that don't log directly here, we expect the setter of the timeout to also be the input + // reader, so when the read is canceled or aborted, the reader should write the appropriate log. + switch (reason) + { + case TimeoutReason.KeepAlive: + StopProcessingNextRequest(); + break; + case TimeoutReason.RequestHeaders: + HandleRequestHeadersTimeout(); + break; + case TimeoutReason.ReadDataRate: + HandleReadDataRateTimeout(); + break; + case TimeoutReason.WriteDataRate: + Log.ResponseMinimumDataRateNotSatisfied(_context.ConnectionId, "" /*TraceIdentifier*/); // TODO trace identifier. + Abort(new ConnectionAbortedException(CoreStrings.ConnectionTimedBecauseResponseMininumDataRateNotSatisfied)); + break; + case TimeoutReason.RequestBodyDrain: + case TimeoutReason.TimeoutFeature: + Abort(new ConnectionAbortedException(CoreStrings.ConnectionTimedOutByServer)); + break; + default: + Debug.Assert(false, "Invalid TimeoutReason"); + break; + } + } + + internal async Task InnerProcessRequestsAsync(IHttpApplication application) + { + // Start other three unidirectional streams here. + var controlTask = CreateControlStream(application); + var encoderTask = CreateEncoderStream(application); + var decoderTask = CreateDecoderStream(application); + + try + { + while (true) + { + var streamContext = await _multiplexedContext.AcceptAsync(); + if (streamContext == null || _haveSentGoAway) + { + break; + } + + var quicStreamFeature = streamContext.Features.Get(); + var streamIdFeature = streamContext.Features.Get(); + + Debug.Assert(quicStreamFeature != null); + + var httpConnectionContext = new Http3StreamContext + { + ConnectionId = streamContext.ConnectionId, + StreamContext = streamContext, + // TODO connection context is null here. Should we set it to anything? + ServiceContext = _context.ServiceContext, + ConnectionFeatures = streamContext.Features, + MemoryPool = _context.MemoryPool, + Transport = streamContext.Transport, + TimeoutControl = _context.TimeoutControl, + LocalEndPoint = streamContext.LocalEndPoint as IPEndPoint, + RemoteEndPoint = streamContext.RemoteEndPoint as IPEndPoint + }; + + if (!quicStreamFeature.CanWrite) + { + // Unidirectional stream + var stream = new Http3ControlStream(application, this, httpConnectionContext); + ThreadPool.UnsafeQueueUserWorkItem(stream, preferLocal: false); + } + else + { + // Keep track of highest stream id seen for GOAWAY + var streamId = streamIdFeature.StreamId; + HighestStreamId = streamId; + + var http3Stream = new Http3Stream(application, this, httpConnectionContext); + var stream = http3Stream; + lock (_streams) + { + _streams[streamId] = http3Stream; + } + ThreadPool.UnsafeQueueUserWorkItem(stream, preferLocal: false); + } + } + } + finally + { + // Abort all streams as connection has shutdown. + lock (_streams) + { + foreach (var stream in _streams.Values) + { + stream.Abort(new ConnectionAbortedException("Connection is shutting down.")); + } + } + + ControlStream?.Abort(new ConnectionAbortedException("Connection is shutting down.")); + EncoderStream?.Abort(new ConnectionAbortedException("Connection is shutting down.")); + DecoderStream?.Abort(new ConnectionAbortedException("Connection is shutting down.")); + + await controlTask; + await encoderTask; + await decoderTask; + } + } + + private async ValueTask CreateControlStream(IHttpApplication application) + { + var stream = await CreateNewUnidirectionalStreamAsync(application); + ControlStream = stream; + await stream.SendStreamIdAsync(id: 0); + await stream.SendSettingsFrameAsync(); + } + + private async ValueTask CreateEncoderStream(IHttpApplication application) + { + var stream = await CreateNewUnidirectionalStreamAsync(application); + EncoderStream = stream; + await stream.SendStreamIdAsync(id: 2); + } + + private async ValueTask CreateDecoderStream(IHttpApplication application) + { + var stream = await CreateNewUnidirectionalStreamAsync(application); + DecoderStream = stream; + await stream.SendStreamIdAsync(id: 3); + } + + private async ValueTask CreateNewUnidirectionalStreamAsync(IHttpApplication application) + { + var features = new FeatureCollection(); + features.Set(new DefaultStreamDirectionFeature(canRead: false, canWrite: true)); + var streamContext = await _multiplexedContext.ConnectAsync(features); + var httpConnectionContext = new Http3StreamContext + { + //ConnectionId = "", TODO getting stream ID from stream that isn't started throws an exception. + StreamContext = streamContext, + Protocols = HttpProtocols.Http3, + ServiceContext = _context.ServiceContext, + ConnectionFeatures = streamContext.Features, + MemoryPool = _context.MemoryPool, + Transport = streamContext.Transport, + TimeoutControl = _context.TimeoutControl, + LocalEndPoint = streamContext.LocalEndPoint as IPEndPoint, + RemoteEndPoint = streamContext.RemoteEndPoint as IPEndPoint + }; + + return new Http3ControlStream(application, this, httpConnectionContext); + } + + public void HandleRequestHeadersTimeout() + { + } + + public void HandleReadDataRateTimeout() + { + } + + public void OnInputOrOutputCompleted() + { + } + + public void Tick(DateTimeOffset now) + { + } + + private void InnerAbort(ConnectionAbortedException ex) + { + lock (_sync) + { + if (ControlStream != null) + { + // TODO need to await this somewhere or allow this to be called elsewhere? + ControlStream.SendGoAway(_highestOpenedStreamId).GetAwaiter().GetResult(); + } + } + + _haveSentGoAway = true; + + // Abort currently active streams + lock (_streams) + { + foreach (var stream in _streams.Values) + { + stream.Abort(new ConnectionAbortedException("The Http3Connection has been aborted"), Http3ErrorCode.UnexpectedFrame); + } + } + + // TODO need to figure out if there is server initiated connection close rather than stream close? + } + + public void ApplyMaxHeaderListSize(long value) + { + // TODO something here to call OnHeader? + } + + internal void ApplyBlockedStream(long value) + { + } + + internal void ApplyMaxTableCapacity(long value) + { + // TODO make sure this works + //_maxDynamicTableSize = value; + } + + internal void RemoveStream(long streamId) + { + lock(_streams) + { + _streams.Remove(streamId); + } + } + } +} diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3ConnectionException.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3ConnectionException.cs new file mode 100644 index 0000000000..c410c34a3f --- /dev/null +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3ConnectionException.cs @@ -0,0 +1,28 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Runtime.Serialization; + +namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal +{ + [Serializable] + internal class Http3ConnectionException : Exception + { + public Http3ConnectionException() + { + } + + public Http3ConnectionException(string message) : base(message) + { + } + + public Http3ConnectionException(string message, Exception innerException) : base(message, innerException) + { + } + + protected Http3ConnectionException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + } +} \ No newline at end of file diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3ControlStream.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3ControlStream.cs new file mode 100644 index 0000000000..a75d8bc69a --- /dev/null +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3ControlStream.cs @@ -0,0 +1,390 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Buffers; +using System.Collections.Generic; +using System.IO.Pipelines; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Connections; +using Microsoft.AspNetCore.Hosting.Server; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3.QPack; + +namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3 +{ + internal abstract class Http3ControlStream : IThreadPoolWorkItem + { + private const int ControlStream = 0; + private const int EncoderStream = 2; + private const int DecoderStream = 3; + + private Http3FrameWriter _frameWriter; + private readonly Http3Connection _http3Connection; + private HttpConnectionContext _context; + private readonly Http3RawFrame _incomingFrame = new Http3RawFrame(); + private volatile int _isClosed; + private int _gracefulCloseInitiator; + + private bool _haveReceivedSettingsFrame; + + public Http3ControlStream(Http3Connection http3Connection, HttpConnectionContext context) + { + var httpLimits = context.ServiceContext.ServerOptions.Limits; + + _http3Connection = http3Connection; + _context = context; + + _frameWriter = new Http3FrameWriter( + context.Transport.Output, + context.ConnectionContext, + context.TimeoutControl, + httpLimits.MinResponseDataRate, + context.ConnectionId, + context.MemoryPool, + context.ServiceContext.Log); + } + + private void OnStreamClosed() + { + Abort(new ConnectionAbortedException("HTTP_CLOSED_CRITICAL_STREAM")); + } + + public PipeReader Input => _context.Transport.Input; + + + public void Abort(ConnectionAbortedException ex) + { + + } + + public void HandleReadDataRateTimeout() + { + //Log.RequestBodyMinimumDataRateNotSatisfied(ConnectionId, null, Limits.MinRequestBodyDataRate.BytesPerSecond); + Abort(new ConnectionAbortedException(CoreStrings.BadRequest_RequestBodyTimeout)); + } + + public void HandleRequestHeadersTimeout() + { + //Log.ConnectionBadRequest(ConnectionId, BadHttpRequestException.GetException(RequestRejectionReason.RequestHeadersTimeout)); + Abort(new ConnectionAbortedException(CoreStrings.BadRequest_RequestHeadersTimeout)); + } + + public void OnInputOrOutputCompleted() + { + TryClose(); + _frameWriter.Abort(new ConnectionAbortedException(CoreStrings.ConnectionAbortedByClient)); + } + + private bool TryClose() + { + if (Interlocked.Exchange(ref _isClosed, 1) == 0) + { + return true; + } + + // TODO make this actually close the Http3Stream by telling quic to close the stream. + return false; + } + + private async ValueTask HandleEncodingTask() + { + var encoder = new EncoderStreamReader(10000); // TODO get value from limits + while (_isClosed == 0) + { + var result = await Input.ReadAsync(); + var readableBuffer = result.Buffer; + if (!readableBuffer.IsEmpty) + { + // This should always read all bytes in the input no matter what. + encoder.Read(readableBuffer); + } + Input.AdvanceTo(readableBuffer.End); + } + } + + private async ValueTask HandleDecodingTask() + { + var decoder = new DecoderStreamReader(); + while (_isClosed == 0) + { + var result = await Input.ReadAsync(); + var readableBuffer = result.Buffer; + var consumed = readableBuffer.Start; + var examined = readableBuffer.Start; + if (!readableBuffer.IsEmpty) + { + decoder.Read(readableBuffer); + } + Input.AdvanceTo(readableBuffer.End); + } + } + + internal async ValueTask SendStreamIdAsync(long id) + { + await _frameWriter.WriteStreamIdAsync(id); + } + + internal async ValueTask SendGoAway(long id) + { + await _frameWriter.WriteGoAway(id); + } + + internal async ValueTask SendSettingsFrameAsync() + { + await _frameWriter.WriteSettingsAsync(null); + } + + private async ValueTask TryReadStreamIdAsync() + { + while (_isClosed == 0) + { + var result = await Input.ReadAsync(); + var readableBuffer = result.Buffer; + var consumed = readableBuffer.Start; + var examined = readableBuffer.End; + + try + { + if (!readableBuffer.IsEmpty) + { + var id = VariableLengthIntegerHelper.GetInteger(readableBuffer, out consumed, out examined); + if (id != -1) + { + return id; + } + } + + if (result.IsCompleted) + { + return -1; + } + } + finally + { + Input.AdvanceTo(consumed, examined); + } + } + + return -1; + } + + public async Task ProcessRequestAsync(IHttpApplication application) + { + var streamType = await TryReadStreamIdAsync(); + + if (streamType == -1) + { + return; + } + + if (streamType == ControlStream) + { + if (_http3Connection.ControlStream != null) + { + throw new Http3ConnectionException("HTTP_STREAM_CREATION_ERROR"); + } + + await HandleControlStream(); + } + else if (streamType == EncoderStream) + { + if (_http3Connection.EncoderStream != null) + { + throw new Http3ConnectionException("HTTP_STREAM_CREATION_ERROR"); + } + await HandleEncodingTask(); + return; + } + else if (streamType == DecoderStream) + { + if (_http3Connection.DecoderStream != null) + { + throw new Http3ConnectionException("HTTP_STREAM_CREATION_ERROR"); + } + await HandleDecodingTask(); + } + else + { + // TODO Close the control stream as it's unexpected. + } + return; + } + + private async Task HandleControlStream() + { + while (_isClosed == 0) + { + var result = await Input.ReadAsync(); + var readableBuffer = result.Buffer; + var consumed = readableBuffer.Start; + var examined = readableBuffer.End; + + try + { + if (!readableBuffer.IsEmpty) + { + // need to kick off httpprotocol process request async here. + while (Http3FrameReader.TryReadFrame(ref readableBuffer, _incomingFrame, 16 * 1024, out var framePayload)) + { + //Log.Http2FrameReceived(ConnectionId, _incomingFrame); + consumed = examined = framePayload.End; + await ProcessHttp3ControlStream(framePayload); + } + } + + if (result.IsCompleted) + { + return; + } + } + catch (Http3StreamErrorException) + { + } + finally + { + Input.AdvanceTo(consumed, examined); + } + } + } + + private ValueTask ProcessHttp3ControlStream(in ReadOnlySequence payload) + { + // Two things: + // settings must be sent as the first frame of each control stream by each peer + // Can't send more than two settings frames. + switch (_incomingFrame.Type) + { + case Http3FrameType.Data: + case Http3FrameType.Headers: + case Http3FrameType.DuplicatePush: + case Http3FrameType.PushPromise: + throw new Http3ConnectionException("HTTP_FRAME_UNEXPECTED"); + case Http3FrameType.Settings: + return ProcessSettingsFrameAsync(payload); + case Http3FrameType.GoAway: + return ProcessGoAwayFrameAsync(payload); + case Http3FrameType.CancelPush: + return ProcessCancelPushFrameAsync(); + case Http3FrameType.MaxPushId: + return ProcessMaxPushIdFrameAsync(); + default: + return ProcessUnknownFrameAsync(); + } + } + + private ValueTask ProcessSettingsFrameAsync(ReadOnlySequence payload) + { + if (_haveReceivedSettingsFrame) + { + throw new Http3ConnectionException("H3_SETTINGS_ERROR"); + } + + _haveReceivedSettingsFrame = true; + using var closedRegistration = _context.ConnectionContext.ConnectionClosed.Register(state => ((Http3ControlStream)state).OnStreamClosed(), this); + + while (true) + { + var id = VariableLengthIntegerHelper.GetInteger(payload, out var consumed, out var examinded); + if (id == -1) + { + break; + } + + payload = payload.Slice(consumed); + + var value = VariableLengthIntegerHelper.GetInteger(payload, out consumed, out examinded); + if (id == -1) + { + break; + } + + payload = payload.Slice(consumed); + ProcessSetting(id, value); + } + + return default; + } + + private void ProcessSetting(long id, long value) + { + // These are client settings, for outbound traffic. + switch (id) + { + case (long)Http3SettingType.QPackMaxTableCapacity: + _http3Connection.ApplyMaxTableCapacity(value); + break; + case (long)Http3SettingType.MaxHeaderListSize: + _http3Connection.ApplyMaxHeaderListSize(value); + break; + case (long)Http3SettingType.QPackBlockedStreams: + _http3Connection.ApplyBlockedStream(value); + break; + default: + // Ignore all unknown settings. + break; + } + } + + private ValueTask ProcessGoAwayFrameAsync(ReadOnlySequence payload) + { + throw new Http3ConnectionException("HTTP_FRAME_UNEXPECTED"); + } + + private ValueTask ProcessCancelPushFrameAsync() + { + if (!_haveReceivedSettingsFrame) + { + throw new Http3ConnectionException("HTTP_FRAME_UNEXPECTED"); + } + + return default; + } + + private ValueTask ProcessMaxPushIdFrameAsync() + { + if (!_haveReceivedSettingsFrame) + { + throw new Http3ConnectionException("HTTP_FRAME_UNEXPECTED"); + } + + return default; + } + + private ValueTask ProcessUnknownFrameAsync() + { + if (!_haveReceivedSettingsFrame) + { + throw new Http3ConnectionException("HTTP_FRAME_UNEXPECTED"); + } + + return default; + } + + public void StopProcessingNextRequest() + => StopProcessingNextRequest(serverInitiated: true); + + public void StopProcessingNextRequest(bool serverInitiated) + { + var initiator = serverInitiated ? GracefulCloseInitiator.Server : GracefulCloseInitiator.Client; + + if (Interlocked.CompareExchange(ref _gracefulCloseInitiator, initiator, GracefulCloseInitiator.None) == GracefulCloseInitiator.None) + { + Input.CancelPendingRead(); + } + } + + /// + /// Used to kick off the request processing loop by derived classes. + /// + public abstract void Execute(); + + private static class GracefulCloseInitiator + { + public const int None = 0; + public const int Server = 1; + public const int Client = 2; + } + } +} diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3ControlStreamOfT.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3ControlStreamOfT.cs new file mode 100644 index 0000000000..880f0f4c1b --- /dev/null +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3ControlStreamOfT.cs @@ -0,0 +1,26 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNetCore.Hosting.Server; +using Microsoft.AspNetCore.Hosting.Server.Abstractions; + +namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3 +{ + internal sealed class Http3ControlStream : Http3ControlStream, IHostContextContainer + { + private readonly IHttpApplication _application; + + public Http3ControlStream(IHttpApplication application, Http3Connection connection, HttpConnectionContext context) : base(connection, context) + { + _application = application; + } + + public override void Execute() + { + _ = ProcessRequestAsync(_application); + } + + // Pooled Host context + TContext IHostContextContainer.HostContext { get; set; } + } +} diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3FrameReader.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3FrameReader.cs new file mode 100644 index 0000000000..98ce27e871 --- /dev/null +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3FrameReader.cs @@ -0,0 +1,60 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Buffers; +using System.Net.Http; + +namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3 +{ + internal class Http3FrameReader + { + /* https://quicwg.org/base-drafts/draft-ietf-quic-http.html#frame-layout + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Type (i) ... + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Length (i) ... + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Frame Payload (*) ... + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + internal static bool TryReadFrame(ref ReadOnlySequence readableBuffer, Http3RawFrame frame, uint maxFrameSize, out ReadOnlySequence framePayload) + { + framePayload = ReadOnlySequence.Empty; + var consumed = readableBuffer.Start; + var examined = readableBuffer.Start; + + var type = VariableLengthIntegerHelper.GetInteger(readableBuffer, out consumed, out examined); + if (type == -1) + { + return false; + } + + var firstLengthBuffer = readableBuffer.Slice(consumed); + + var length = VariableLengthIntegerHelper.GetInteger(firstLengthBuffer, out consumed, out examined); + + // Make sure the whole frame is buffered + if (length == -1) + { + return false; + } + + var startOfFramePayload = readableBuffer.Slice(consumed); + if (startOfFramePayload.Length < length) + { + return false; + } + + frame.Length = length; + frame.Type = (Http3FrameType)type; + + // The remaining payload minus the extra fields + framePayload = startOfFramePayload.Slice(0, length); + readableBuffer = readableBuffer.Slice(framePayload.End); + + return true; + } + } +} diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3FrameWriter.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3FrameWriter.cs new file mode 100644 index 0000000000..bb2e33a4cb --- /dev/null +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3FrameWriter.cs @@ -0,0 +1,343 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO.Pipelines; +using System.Net.Http; +using System.Net.Http.QPack; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Connections; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3.QPack; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.PipeWriterHelpers; + +namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3 +{ + internal class Http3FrameWriter + { + private readonly object _writeLock = new object(); + private readonly QPackEncoder _qpackEncoder = new QPackEncoder(); + + private readonly PipeWriter _outputWriter; + private readonly ConnectionContext _connectionContext; + private readonly ITimeoutControl _timeoutControl; + private readonly MinDataRate _minResponseDataRate; + private readonly MemoryPool _memoryPool; + private readonly IKestrelTrace _log; + private readonly Http3RawFrame _outgoingFrame; + private readonly TimingPipeFlusher _flusher; + + // TODO update max frame size + private uint _maxFrameSize = 10000; //Http3PeerSettings.MinAllowedMaxFrameSize; + private byte[] _headerEncodingBuffer; + + private long _unflushedBytes; + private bool _completed; + private bool _aborted; + + //private int _unflushedBytes; + + public Http3FrameWriter(PipeWriter output, ConnectionContext connectionContext, ITimeoutControl timeoutControl, MinDataRate minResponseDataRate, string connectionId, MemoryPool memoryPool, IKestrelTrace log) + { + _outputWriter = output; + _connectionContext = connectionContext; + _timeoutControl = timeoutControl; + _minResponseDataRate = minResponseDataRate; + _memoryPool = memoryPool; + _log = log; + _outgoingFrame = new Http3RawFrame(); + _flusher = new TimingPipeFlusher(_outputWriter, timeoutControl, log); + _headerEncodingBuffer = new byte[_maxFrameSize]; + } + + public void UpdateMaxFrameSize(uint maxFrameSize) + { + lock (_writeLock) + { + if (_maxFrameSize != maxFrameSize) + { + _maxFrameSize = maxFrameSize; + _headerEncodingBuffer = new byte[_maxFrameSize]; + } + } + } + + // TODO actually write settings here. + internal Task WriteSettingsAsync(IList settings) + { + _outgoingFrame.PrepareSettings(); + var buffer = _outputWriter.GetSpan(2); + + buffer[0] = (byte)_outgoingFrame.Type; + buffer[1] = 0; + + _outputWriter.Advance(2); + + return _outputWriter.FlushAsync().AsTask(); + } + + internal Task WriteStreamIdAsync(long id) + { + var buffer = _outputWriter.GetSpan(8); + _outputWriter.Advance(VariableLengthIntegerHelper.WriteInteger(buffer, id)); + return _outputWriter.FlushAsync().AsTask(); + } + + public ValueTask WriteDataAsync(in ReadOnlySequence data) + { + // The Length property of a ReadOnlySequence can be expensive, so we cache the value. + var dataLength = data.Length; + + lock (_writeLock) + { + if (_completed) + { + return default; + } + + WriteDataUnsynchronized(data, dataLength); + return TimeFlushUnsynchronizedAsync(); + } + } + + private void WriteDataUnsynchronized(in ReadOnlySequence data, long dataLength) + { + Debug.Assert(dataLength == data.Length); + + _outgoingFrame.PrepareData(); + + if (dataLength > _maxFrameSize) + { + SplitAndWriteDataUnsynchronized(in data, dataLength); + return; + } + + _outgoingFrame.Length = (int)dataLength; + + WriteHeaderUnsynchronized(); + + foreach (var buffer in data) + { + _outputWriter.Write(buffer.Span); + } + + return; + + void SplitAndWriteDataUnsynchronized(in ReadOnlySequence data, long dataLength) + { + Debug.Assert(dataLength == data.Length); + + var dataPayloadLength = (int)_maxFrameSize; + + Debug.Assert(dataLength > dataPayloadLength); + + var remainingData = data; + do + { + var currentData = remainingData.Slice(0, dataPayloadLength); + _outgoingFrame.Length = dataPayloadLength; + + WriteHeaderUnsynchronized(); + + foreach (var buffer in currentData) + { + _outputWriter.Write(buffer.Span); + } + + dataLength -= dataPayloadLength; + remainingData = remainingData.Slice(dataPayloadLength); + + } while (dataLength > dataPayloadLength); + + _outgoingFrame.Length = (int)dataLength; + + WriteHeaderUnsynchronized(); + + foreach (var buffer in remainingData) + { + _outputWriter.Write(buffer.Span); + } + } + } + + internal Task WriteGoAway(long id) + { + _outgoingFrame.PrepareGoAway(); + var buffer = _outputWriter.GetSpan(9); + buffer[0] = (byte)_outgoingFrame.Type; + + var length = VariableLengthIntegerHelper.WriteInteger(buffer.Slice(1), id); + + _outgoingFrame.Length = length; + + WriteHeaderUnsynchronized(); + + return _outputWriter.FlushAsync().AsTask(); + } + + private void WriteHeaderUnsynchronized() + { + var headerLength = WriteHeader(_outgoingFrame, _outputWriter); + + // We assume the payload will be written prior to the next flush. + _unflushedBytes += headerLength + _outgoingFrame.Length; + } + + internal static int WriteHeader(Http3RawFrame frame, PipeWriter output) + { + // max size of the header is 16, most likely it will be smaller. + var buffer = output.GetSpan(16); + + var typeLength = VariableLengthIntegerHelper.WriteInteger(buffer, (int)frame.Type); + + buffer = buffer.Slice(typeLength); + + var lengthLength = VariableLengthIntegerHelper.WriteInteger(buffer, (int)frame.Length); + + var totalLength = typeLength + lengthLength; + output.Advance(typeLength + lengthLength); + + return totalLength; + } + + public ValueTask WriteResponseTrailers(HttpResponseTrailers headers) + { + lock (_writeLock) + { + if (_completed) + { + return default; + } + + try + { + _outgoingFrame.PrepareHeaders(); + var buffer = _headerEncodingBuffer.AsSpan(); + var done = _qpackEncoder.BeginEncode(EnumerateHeaders(headers), buffer, out var payloadLength); + FinishWritingHeaders(payloadLength, done); + } + catch (QPackEncodingException) + { + //_log.HPackEncodingError(_connectionId, streamId, hex); + //_http3Stream.Abort(new ConnectionAbortedException(hex.Message, hex)); + } + + return TimeFlushUnsynchronizedAsync(); + } + } + + private ValueTask TimeFlushUnsynchronizedAsync() + { + var bytesWritten = _unflushedBytes; + _unflushedBytes = 0; + + return _flusher.FlushAsync(_minResponseDataRate, bytesWritten); + } + + public ValueTask FlushAsync(IHttpOutputAborter outputAborter, CancellationToken cancellationToken) + { + lock (_writeLock) + { + if (_completed) + { + return default; + } + + var bytesWritten = _unflushedBytes; + _unflushedBytes = 0; + + return _flusher.FlushAsync(_minResponseDataRate, bytesWritten, outputAborter, cancellationToken); + } + } + + internal void WriteResponseHeaders(int statusCode, IHeaderDictionary headers) + { + lock (_writeLock) + { + if (_completed) + { + return; + } + + try + { + _outgoingFrame.PrepareHeaders(); + var buffer = _headerEncodingBuffer.AsSpan(); + var done = _qpackEncoder.BeginEncode(statusCode, EnumerateHeaders(headers), buffer, out var payloadLength); + FinishWritingHeaders(payloadLength, done); + } + catch (QPackEncodingException hex) + { + // TODO figure out how to abort the stream here. + //_http3Stream.Abort(new ConnectionAbortedException(hex.Message, hex)); + throw new InvalidOperationException(hex.Message, hex); // Report the error to the user if this was the first write. + } + } + } + + private void FinishWritingHeaders(int payloadLength, bool done) + { + var buffer = _headerEncodingBuffer.AsSpan(); + _outgoingFrame.Length = payloadLength; + + WriteHeaderUnsynchronized(); + _outputWriter.Write(buffer.Slice(0, payloadLength)); + + while (!done) + { + done = _qpackEncoder.Encode(buffer, out payloadLength); + _outgoingFrame.Length = payloadLength; + + WriteHeaderUnsynchronized(); + _outputWriter.Write(buffer.Slice(0, payloadLength)); + } + } + + public void Complete() + { + lock (_writeLock) + { + if (_completed) + { + return; + } + + _completed = true; + _outputWriter.Complete(); + } + } + + public void Abort(ConnectionAbortedException error) + { + lock (_writeLock) + { + if (_aborted) + { + return; + } + + _aborted = true; + _connectionContext.Abort(error); + + Complete(); + } + } + + private static IEnumerable> EnumerateHeaders(IHeaderDictionary headers) + { + foreach (var header in headers) + { + foreach (var value in header.Value) + { + yield return new KeyValuePair(header.Key, value); + } + } + } + } +} diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3MessageBody.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3MessageBody.cs new file mode 100644 index 0000000000..5af5f8df47 --- /dev/null +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3MessageBody.cs @@ -0,0 +1,126 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO.Pipelines; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Connections; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; + +namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3 +{ + internal sealed class Http3MessageBody : MessageBody + { + private readonly Http3Stream _context; + private ReadResult _readResult; + + private Http3MessageBody(Http3Stream context) + : base(context) + { + _context = context; + } + protected override void OnReadStarting() + { + // Note ContentLength or MaxRequestBodySize may be null + if (_context.RequestHeaders.ContentLength > _context.MaxRequestBodySize) + { + BadHttpRequestException.Throw(RequestRejectionReason.RequestBodyTooLarge); + } + } + + protected override void OnReadStarted() + { + } + + protected override void OnDataRead(long bytesRead) + { + AddAndCheckConsumedBytes(bytesRead); + } + + public static MessageBody For(Http3Stream context) + { + return new Http3MessageBody(context); + } + + public override void AdvanceTo(SequencePosition consumed) + { + AdvanceTo(consumed, consumed); + } + + public override void AdvanceTo(SequencePosition consumed, SequencePosition examined) + { + OnAdvance(_readResult, consumed, examined); + _context.RequestBodyPipe.Reader.AdvanceTo(consumed, examined); + } + + public override bool TryRead(out ReadResult readResult) + { + TryStart(); + + var hasResult = _context.RequestBodyPipe.Reader.TryRead(out readResult); + + if (hasResult) + { + _readResult = readResult; + + CountBytesRead(readResult.Buffer.Length); + + if (readResult.IsCompleted) + { + TryStop(); + } + } + + return hasResult; + } + + public override async ValueTask ReadAsync(CancellationToken cancellationToken = default) + { + TryStart(); + + try + { + var readAwaitable = _context.RequestBodyPipe.Reader.ReadAsync(cancellationToken); + + _readResult = await StartTimingReadAsync(readAwaitable, cancellationToken); + } + catch (ConnectionAbortedException ex) + { + throw new TaskCanceledException("The request was aborted", ex); + } + + StopTimingRead(_readResult.Buffer.Length); + + if (_readResult.IsCompleted) + { + TryStop(); + } + + return _readResult; + } + + public override void Complete(Exception exception) + { + _context.RequestBodyPipe.Reader.Complete(); + _context.ReportApplicationError(exception); + } + + public override void CancelPendingRead() + { + _context.RequestBodyPipe.Reader.CancelPendingRead(); + } + + protected override Task OnStopAsync() + { + if (!_context.HasStartedConsumingRequestBody) + { + return Task.CompletedTask; + } + + _context.RequestBodyPipe.Reader.Complete(); + + return Task.CompletedTask; + } + } +} diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3OutputProducer.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3OutputProducer.cs new file mode 100644 index 0000000000..6c2f142591 --- /dev/null +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3OutputProducer.cs @@ -0,0 +1,404 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Buffers; +using System.IO.Pipelines; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Connections; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.PipeWriterHelpers; +using Microsoft.Extensions.Logging; +using System.Diagnostics; +using Microsoft.AspNetCore.Internal; +using System.Net.Http; + +namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3 +{ + internal class Http3OutputProducer : IHttpOutputProducer, IHttpOutputAborter + { + private readonly Http3FrameWriter _frameWriter; + private readonly TimingPipeFlusher _flusher; + private readonly IKestrelTrace _log; + private readonly MemoryPool _memoryPool; + private readonly Http3Stream _stream; + private readonly PipeWriter _pipeWriter; + private readonly PipeReader _pipeReader; + private readonly object _dataWriterLock = new object(); + private readonly ValueTask _dataWriteProcessingTask; + private bool _startedWritingDataFrames; + private bool _completed; + private bool _disposed; + private bool _suffixSent; + private IMemoryOwner _fakeMemoryOwner; + + public Http3OutputProducer( + Http3FrameWriter frameWriter, + MemoryPool pool, + Http3Stream stream, + IKestrelTrace log) + { + _frameWriter = frameWriter; + _memoryPool = pool; + _stream = stream; + _log = log; + + var pipe = CreateDataPipe(pool); + + _pipeWriter = pipe.Writer; + _pipeReader = pipe.Reader; + + _flusher = new TimingPipeFlusher(_pipeWriter, timeoutControl: null, log); + _dataWriteProcessingTask = ProcessDataWrites(); + } + + public void Dispose() + { + lock (_dataWriterLock) + { + if (_disposed) + { + return; + } + + _disposed = true; + + Stop(); + + if (_fakeMemoryOwner != null) + { + _fakeMemoryOwner.Dispose(); + _fakeMemoryOwner = null; + } + } + } + + void IHttpOutputAborter.Abort(ConnectionAbortedException abortReason) + { + _stream.Abort(abortReason, Http3ErrorCode.InternalError); + } + + public void Advance(int bytes) + { + lock (_dataWriterLock) + { + ThrowIfSuffixSent(); + + if (_completed) + { + return; + } + + _startedWritingDataFrames = true; + + _pipeWriter.Advance(bytes); + } + } + + public void CancelPendingFlush() + { + lock (_dataWriterLock) + { + if (_completed) + { + return; + } + + _pipeWriter.CancelPendingFlush(); + } + } + + public ValueTask FirstWriteAsync(int statusCode, string reasonPhrase, HttpResponseHeaders responseHeaders, bool autoChunk, ReadOnlySpan data, CancellationToken cancellationToken) + { + lock (_dataWriterLock) + { + WriteResponseHeaders(statusCode, reasonPhrase, responseHeaders, autoChunk, appCompleted: false); + + return WriteDataToPipeAsync(data, cancellationToken); + } + } + + public ValueTask FirstWriteChunkedAsync(int statusCode, string reasonPhrase, HttpResponseHeaders responseHeaders, bool autoChunk, ReadOnlySpan data, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + public ValueTask FlushAsync(CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + { + return new ValueTask(Task.FromCanceled(cancellationToken)); + } + + lock (_dataWriterLock) + { + ThrowIfSuffixSent(); + + if (_completed) + { + return default; + } + + if (_startedWritingDataFrames) + { + // If there's already been response data written to the stream, just wait for that. Any header + // should be in front of the data frames in the connection pipe. Trailers could change things. + return _flusher.FlushAsync(this, cancellationToken); + } + else + { + // Flushing the connection pipe ensures headers already in the pipe are flushed even if no data + // frames have been written. + return _frameWriter.FlushAsync(this, cancellationToken); + } + } + } + + public Memory GetMemory(int sizeHint = 0) + { + lock (_dataWriterLock) + { + ThrowIfSuffixSent(); + + if (_completed) + { + return GetFakeMemory(sizeHint); + } + + return _pipeWriter.GetMemory(sizeHint); + } + } + + + public Span GetSpan(int sizeHint = 0) + { + lock (_dataWriterLock) + { + ThrowIfSuffixSent(); + + if (_completed) + { + return GetFakeMemory(sizeHint).Span; + } + + return _pipeWriter.GetSpan(sizeHint); + } + } + + private Memory GetFakeMemory(int sizeHint) + { + if (_fakeMemoryOwner == null) + { + _fakeMemoryOwner = _memoryPool.Rent(sizeHint); + } + + return _fakeMemoryOwner.Memory; + } + + [StackTraceHidden] + private void ThrowIfSuffixSent() + { + if (_suffixSent) + { + ThrowSuffixSent(); + } + } + + [StackTraceHidden] + private static void ThrowSuffixSent() + { + throw new InvalidOperationException("Writing is not allowed after writer was completed."); + } + + public void Reset() + { + } + + public void Stop() + { + lock (_dataWriterLock) + { + if (_completed) + { + return; + } + + _completed = true; + + _pipeWriter.Complete(new OperationCanceledException()); + + } + } + + public ValueTask Write100ContinueAsync() + { + throw new NotImplementedException(); + } + + public ValueTask WriteChunkAsync(ReadOnlySpan data, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + public Task WriteDataAsync(ReadOnlySpan data, CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + + lock (_dataWriterLock) + { + ThrowIfSuffixSent(); + + // This length check is important because we don't want to set _startedWritingDataFrames unless a data + // frame will actually be written causing the headers to be flushed. + if (_completed || data.Length == 0) + { + return Task.CompletedTask; + } + + _startedWritingDataFrames = true; + + _pipeWriter.Write(data); + return _flusher.FlushAsync(this, cancellationToken).GetAsTask(); + } + } + + public ValueTask WriteDataToPipeAsync(ReadOnlySpan data, CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + { + return new ValueTask(Task.FromCanceled(cancellationToken)); + } + + lock (_dataWriterLock) + { + ThrowIfSuffixSent(); + + // This length check is important because we don't want to set _startedWritingDataFrames unless a data + // frame will actually be written causing the headers to be flushed. + if (_completed || data.Length == 0) + { + return default; + } + + _startedWritingDataFrames = true; + + _pipeWriter.Write(data); + return _flusher.FlushAsync(this, cancellationToken); + } + } + + public void WriteResponseHeaders(int statusCode, string reasonPhrase, HttpResponseHeaders responseHeaders, bool autoChunk, bool appCompleted) + { + lock (_dataWriterLock) + { + if (_completed) + { + return; + } + + if (appCompleted && !_startedWritingDataFrames && (_stream.ResponseTrailers == null || _stream.ResponseTrailers.Count == 0)) + { + // TODO figure out something to do here. + } + + _frameWriter.WriteResponseHeaders(statusCode, responseHeaders); + } + } + + public ValueTask WriteStreamSuffixAsync() + { + lock (_dataWriterLock) + { + if (_completed) + { + return _dataWriteProcessingTask; + } + + _completed = true; + _suffixSent = true; + + _pipeWriter.Complete(); + return _dataWriteProcessingTask; + } + } + + private async ValueTask ProcessDataWrites() + { + FlushResult flushResult = default; + try + { + ReadResult readResult; + + do + { + readResult = await _pipeReader.ReadAsync(); + + if (readResult.IsCompleted && _stream.ResponseTrailers?.Count > 0) + { + // Output is ending and there are trailers to write + // Write any remaining content then write trailers + if (readResult.Buffer.Length > 0) + { + flushResult = await _frameWriter.WriteDataAsync(readResult.Buffer); + } + + _stream.ResponseTrailers.SetReadOnly(); + flushResult = await _frameWriter.WriteResponseTrailers(_stream.ResponseTrailers); + } + else if (readResult.IsCompleted) + { + if (readResult.Buffer.Length != 0) + { + ThrowUnexpectedState(); + } + + // Headers have already been written and there is no other content to write + // TODO complete something here. + flushResult = await _frameWriter.FlushAsync(outputAborter: null, cancellationToken: default); + _frameWriter.Complete(); + } + else + { + flushResult = await _frameWriter.WriteDataAsync(readResult.Buffer); + } + + _pipeReader.AdvanceTo(readResult.Buffer.End); + } while (!readResult.IsCompleted); + } + catch (OperationCanceledException) + { + // Writes should not throw for aborted streams/connections. + } + catch (Exception ex) + { + _log.LogCritical(ex, nameof(Http3OutputProducer) + "." + nameof(ProcessDataWrites) + " observed an unexpected exception."); + } + + _pipeReader.Complete(); + + return flushResult; + + static void ThrowUnexpectedState() + { + throw new InvalidOperationException(nameof(Http3OutputProducer) + "." + nameof(ProcessDataWrites) + " observed an unexpected state where the streams output ended with data still remaining in the pipe."); + } + } + + private static Pipe CreateDataPipe(MemoryPool pool) + => new Pipe(new PipeOptions + ( + pool: pool, + readerScheduler: PipeScheduler.Inline, + writerScheduler: PipeScheduler.ThreadPool, + pauseWriterThreshold: 1, + resumeWriterThreshold: 1, + useSynchronizationContext: false, + minimumSegmentSize: pool.GetMinimumSegmentSize() + )); + } +} diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/IHttpHeadersHandler.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3PeerSettings.cs similarity index 56% rename from src/Servers/Kestrel/Core/src/Internal/Http/IHttpHeadersHandler.cs rename to src/Servers/Kestrel/Core/src/Internal/Http3/Http3PeerSettings.cs index 0ed16148b8..4a2961d7ec 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/IHttpHeadersHandler.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3PeerSettings.cs @@ -1,13 +1,12 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; - -namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http +namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3 { - public interface IHttpHeadersHandler + internal class Http3PeerSettings { - void OnHeader(Span name, Span value); - void OnHeadersComplete(); + internal const uint DefaultMaxFrameSize = 16 * 1024; + + public static int MinAllowedMaxFrameSize { get; internal set; } = 16 * 1024; } -} \ No newline at end of file +} diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/HPack/HuffmanDecodingException.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3SettingType.cs similarity index 50% rename from src/Servers/Kestrel/Core/src/Internal/Http2/HPack/HuffmanDecodingException.cs rename to src/Servers/Kestrel/Core/src/Internal/Http3/Http3SettingType.cs index f20769f0b6..325ac0fd91 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/HPack/HuffmanDecodingException.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3SettingType.cs @@ -1,15 +1,15 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; - -namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack +namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3 { - internal sealed class HuffmanDecodingException : Exception + enum Http3SettingType : long { - public HuffmanDecodingException(string message) - : base(message) - { - } + QPackMaxTableCapacity = 0x1, + /// + /// SETTINGS_MAX_HEADER_LIST_SIZE, default is unlimited. + /// + MaxHeaderListSize = 0x6, + QPackBlockedStreams = 0x7 } } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs new file mode 100644 index 0000000000..6518d00afd --- /dev/null +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs @@ -0,0 +1,770 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Buffers; +using System.Diagnostics; +using System.IO.Pipelines; +using System.Net.Http; +using System.Net.Http.QPack; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Connections; +using Microsoft.AspNetCore.Connections.Features; +using Microsoft.AspNetCore.Hosting.Server; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Primitives; +using Microsoft.Net.Http.Headers; + +namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3 +{ + internal abstract class Http3Stream : HttpProtocol, IHttpHeadersHandler, IThreadPoolWorkItem + { + private static ReadOnlySpan AuthorityBytes => new byte[10] { (byte)':', (byte)'a', (byte)'u', (byte)'t', (byte)'h', (byte)'o', (byte)'r', (byte)'i', (byte)'t', (byte)'y' }; + private static ReadOnlySpan MethodBytes => new byte[7] { (byte)':', (byte)'m', (byte)'e', (byte)'t', (byte)'h', (byte)'o', (byte)'d' }; + private static ReadOnlySpan PathBytes => new byte[5] { (byte)':', (byte)'p', (byte)'a', (byte)'t', (byte)'h' }; + private static ReadOnlySpan SchemeBytes => new byte[7] { (byte)':', (byte)'s', (byte)'c', (byte)'h', (byte)'e', (byte)'m', (byte)'e' }; + private static ReadOnlySpan StatusBytes => new byte[7] { (byte)':', (byte)'s', (byte)'t', (byte)'a', (byte)'t', (byte)'u', (byte)'s' }; + private static ReadOnlySpan ConnectionBytes => new byte[10] { (byte)'c', (byte)'o', (byte)'n', (byte)'n', (byte)'e', (byte)'c', (byte)'t', (byte)'i', (byte)'o', (byte)'n' }; + private static ReadOnlySpan TeBytes => new byte[2] { (byte)'t', (byte)'e' }; + private static ReadOnlySpan TrailersBytes => new byte[8] { (byte)'t', (byte)'r', (byte)'a', (byte)'i', (byte)'l', (byte)'e', (byte)'r', (byte)'s' }; + private static ReadOnlySpan ConnectBytes => new byte[7] { (byte)'C', (byte)'O', (byte)'N', (byte)'N', (byte)'E', (byte)'C', (byte)'T' }; + + private Http3FrameWriter _frameWriter; + private Http3OutputProducer _http3Output; + private int _isClosed; + private int _gracefulCloseInitiator; + private readonly Http3StreamContext _context; + private readonly IProtocolErrorCodeFeature _errorCodeFeature; + private readonly IStreamIdFeature _streamIdFeature; + private readonly Http3RawFrame _incomingFrame = new Http3RawFrame(); + protected RequestHeaderParsingState _requestHeaderParsingState; + private PseudoHeaderFields _parsedPseudoHeaderFields; + private bool _isMethodConnect; + + private readonly Http3Connection _http3Connection; + private bool _receivedHeaders; + private TaskCompletionSource _appCompleted; + + public Pipe RequestBodyPipe { get; } + + public Http3Stream(Http3Connection http3Connection, Http3StreamContext context) + { + Initialize(context); + + InputRemaining = null; + + // First, determine how we know if an Http3stream is unidirectional or bidirectional + var httpLimits = context.ServiceContext.ServerOptions.Limits; + var http3Limits = httpLimits.Http3; + _http3Connection = http3Connection; + _context = context; + + _errorCodeFeature = _context.ConnectionFeatures.Get(); + _streamIdFeature = _context.ConnectionFeatures.Get(); + + _frameWriter = new Http3FrameWriter( + context.Transport.Output, + context.StreamContext, + context.TimeoutControl, + httpLimits.MinResponseDataRate, + context.ConnectionId, + context.MemoryPool, + context.ServiceContext.Log); + + // ResponseHeaders aren't set, kind of ugly that we need to reset. + Reset(); + + _http3Output = new Http3OutputProducer( + _frameWriter, + context.MemoryPool, + this, + context.ServiceContext.Log); + RequestBodyPipe = CreateRequestBodyPipe(64 * 1024); // windowSize? + Output = _http3Output; + QPackDecoder = new QPackDecoder(_context.ServiceContext.ServerOptions.Limits.Http3.MaxRequestHeaderFieldSize); + } + + public long? InputRemaining { get; internal set; } + + public QPackDecoder QPackDecoder { get; } + + public PipeReader Input => _context.Transport.Input; + + public ISystemClock SystemClock => _context.ServiceContext.SystemClock; + public KestrelServerLimits Limits => _context.ServiceContext.ServerOptions.Limits; + + public void Abort(ConnectionAbortedException ex) + { + Abort(ex, Http3ErrorCode.InternalError); + } + + public void Abort(ConnectionAbortedException ex, Http3ErrorCode errorCode) + { + _errorCodeFeature.Error = (long)errorCode; + // TODO replace with IKestrelTrace log. + Log.LogWarning(ex, ex.Message); + _frameWriter.Abort(ex); + } + + public void OnHeadersComplete(bool endStream) + { + OnHeadersComplete(); + } + + public void OnStaticIndexedHeader(int index) + { + var knownHeader = H3StaticTable.Instance[index]; + OnHeader(knownHeader.Name, knownHeader.Value); + } + + public void OnStaticIndexedHeader(int index, ReadOnlySpan value) + { + var knownHeader = H3StaticTable.Instance[index]; + OnHeader(knownHeader.Name, value); + } + + public override void OnHeader(ReadOnlySpan name, ReadOnlySpan value) + { + // TODO MaxRequestHeadersTotalSize? + ValidateHeader(name, value); + try + { + if (_requestHeaderParsingState == RequestHeaderParsingState.Trailers) + { + OnTrailer(name, value); + } + else + { + // Throws BadRequest for header count limit breaches. + // Throws InvalidOperation for bad encoding. + base.OnHeader(name, value); + } + } + catch (BadHttpRequestException bre) + { + throw new Http3StreamErrorException(bre.Message, Http3ErrorCode.ProtocolError); + } + catch (InvalidOperationException) + { + throw new Http3StreamErrorException(CoreStrings.BadRequest_MalformedRequestInvalidHeaders, Http3ErrorCode.ProtocolError); + } + } + + private void ValidateHeader(ReadOnlySpan name, ReadOnlySpan value) + { + // http://httpwg.org/specs/rfc7540.html#rfc.section.8.1.2.1 + /* + Intermediaries that process HTTP requests or responses (i.e., any + intermediary not acting as a tunnel) MUST NOT forward a malformed + request or response. Malformed requests or responses that are + detected MUST be treated as a stream error (Section 5.4.2) of type + PROTOCOL_ERROR. + + For malformed requests, a server MAY send an HTTP response prior to + closing or resetting the stream. Clients MUST NOT accept a malformed + response. Note that these requirements are intended to protect + against several types of common attacks against HTTP; they are + deliberately strict because being permissive can expose + implementations to these vulnerabilities.*/ + if (IsPseudoHeaderField(name, out var headerField)) + { + if (_requestHeaderParsingState == RequestHeaderParsingState.Headers) + { + // All pseudo-header fields MUST appear in the header block before regular header fields. + // Any request or response that contains a pseudo-header field that appears in a header + // block after a regular header field MUST be treated as malformed (Section 8.1.2.6). + throw new Http3StreamErrorException(CoreStrings.Http2ErrorPseudoHeaderFieldAfterRegularHeaders, Http3ErrorCode.ProtocolError); + } + + if (_requestHeaderParsingState == RequestHeaderParsingState.Trailers) + { + // Pseudo-header fields MUST NOT appear in trailers. + throw new Http3StreamErrorException(CoreStrings.Http2ErrorTrailersContainPseudoHeaderField, Http3ErrorCode.ProtocolError); + } + + _requestHeaderParsingState = RequestHeaderParsingState.PseudoHeaderFields; + + if (headerField == PseudoHeaderFields.Unknown) + { + // Endpoints MUST treat a request or response that contains undefined or invalid pseudo-header + // fields as malformed (Section 8.1.2.6). + throw new Http3StreamErrorException(CoreStrings.Http2ErrorUnknownPseudoHeaderField, Http3ErrorCode.ProtocolError); + } + + if (headerField == PseudoHeaderFields.Status) + { + // Pseudo-header fields defined for requests MUST NOT appear in responses; pseudo-header fields + // defined for responses MUST NOT appear in requests. + throw new Http3StreamErrorException(CoreStrings.Http2ErrorResponsePseudoHeaderField, Http3ErrorCode.ProtocolError); + } + + if ((_parsedPseudoHeaderFields & headerField) == headerField) + { + // http://httpwg.org/specs/rfc7540.html#rfc.section.8.1.2.3 + // All HTTP/2 requests MUST include exactly one valid value for the :method, :scheme, and :path pseudo-header fields + throw new Http3StreamErrorException(CoreStrings.Http2ErrorDuplicatePseudoHeaderField, Http3ErrorCode.ProtocolError); + } + + if (headerField == PseudoHeaderFields.Method) + { + _isMethodConnect = value.SequenceEqual(ConnectBytes); + } + + _parsedPseudoHeaderFields |= headerField; + } + else if (_requestHeaderParsingState != RequestHeaderParsingState.Trailers) + { + _requestHeaderParsingState = RequestHeaderParsingState.Headers; + } + + if (IsConnectionSpecificHeaderField(name, value)) + { + throw new Http3StreamErrorException(CoreStrings.Http2ErrorConnectionSpecificHeaderField, Http3ErrorCode.ProtocolError); + } + + // http://httpwg.org/specs/rfc7540.html#rfc.section.8.1.2 + // A request or response containing uppercase header field names MUST be treated as malformed (Section 8.1.2.6). + for (var i = 0; i < name.Length; i++) + { + if (name[i] >= 65 && name[i] <= 90) + { + if (_requestHeaderParsingState == RequestHeaderParsingState.Trailers) + { + throw new Http3StreamErrorException(CoreStrings.Http2ErrorTrailerNameUppercase, Http3ErrorCode.ProtocolError); + } + else + { + throw new Http3StreamErrorException(CoreStrings.Http2ErrorHeaderNameUppercase, Http3ErrorCode.ProtocolError); + } + } + } + } + + private bool IsPseudoHeaderField(ReadOnlySpan name, out PseudoHeaderFields headerField) + { + headerField = PseudoHeaderFields.None; + + if (name.IsEmpty || name[0] != (byte)':') + { + return false; + } + + if (name.SequenceEqual(PathBytes)) + { + headerField = PseudoHeaderFields.Path; + } + else if (name.SequenceEqual(MethodBytes)) + { + headerField = PseudoHeaderFields.Method; + } + else if (name.SequenceEqual(SchemeBytes)) + { + headerField = PseudoHeaderFields.Scheme; + } + else if (name.SequenceEqual(StatusBytes)) + { + headerField = PseudoHeaderFields.Status; + } + else if (name.SequenceEqual(AuthorityBytes)) + { + headerField = PseudoHeaderFields.Authority; + } + else + { + headerField = PseudoHeaderFields.Unknown; + } + + return true; + } + + private static bool IsConnectionSpecificHeaderField(ReadOnlySpan name, ReadOnlySpan value) + { + return name.SequenceEqual(ConnectionBytes) || (name.SequenceEqual(TeBytes) && !value.SequenceEqual(TrailersBytes)); + } + + public void HandleReadDataRateTimeout() + { + Log.RequestBodyMinimumDataRateNotSatisfied(ConnectionId, null, Limits.MinRequestBodyDataRate.BytesPerSecond); + Abort(new ConnectionAbortedException(CoreStrings.BadRequest_RequestBodyTimeout), Http3ErrorCode.RequestRejected); + } + + public void HandleRequestHeadersTimeout() + { + Log.ConnectionBadRequest(ConnectionId, BadHttpRequestException.GetException(RequestRejectionReason.RequestHeadersTimeout)); + Abort(new ConnectionAbortedException(CoreStrings.BadRequest_RequestHeadersTimeout), Http3ErrorCode.RequestRejected); + } + + public void OnInputOrOutputCompleted() + { + TryClose(); + Abort(new ConnectionAbortedException(CoreStrings.ConnectionAbortedByClient), Http3ErrorCode.NoError); + } + + protected override void OnRequestProcessingEnded() + { + Debug.Assert(_appCompleted != null); + + _appCompleted.SetResult(new object()); + } + + private bool TryClose() + { + if (Interlocked.Exchange(ref _isClosed, 1) == 0) + { + return true; + } + + // TODO make this actually close the Http3Stream by telling quic to close the stream. + return false; + } + + public async Task ProcessRequestAsync(IHttpApplication application) + { + Exception error = null; + + try + { + while (_isClosed == 0) + { + var result = await Input.ReadAsync(); + var readableBuffer = result.Buffer; + var consumed = readableBuffer.Start; + var examined = readableBuffer.End; + + try + { + if (!readableBuffer.IsEmpty) + { + while (Http3FrameReader.TryReadFrame(ref readableBuffer, _incomingFrame, 16 * 1024, out var framePayload)) + { + consumed = examined = framePayload.End; + await ProcessHttp3Stream(application, framePayload); + } + } + + if (result.IsCompleted) + { + OnEndStreamReceived(); + return; + } + } + + finally + { + Input.AdvanceTo(consumed, examined); + } + } + } + catch (Http3StreamErrorException ex) + { + error = ex; + Abort(new ConnectionAbortedException(ex.Message, ex), ex.ErrorCode); + } + catch (Exception ex) + { + error = ex; + Log.LogWarning(0, ex, "Stream threw an unexpected exception."); + } + finally + { + var streamError = error as ConnectionAbortedException + ?? new ConnectionAbortedException("The stream has completed.", error); + + Input.Complete(); + + await RequestBodyPipe.Writer.CompleteAsync(); + + // Make sure application func is completed before completing writer. + if (_appCompleted != null) + { + await _appCompleted.Task; + } + + try + { + _frameWriter.Complete(); + } + catch + { + Abort(streamError, Http3ErrorCode.ProtocolError); + throw; + } + finally + { + _http3Connection.RemoveStream(_streamIdFeature.StreamId); + } + } + } + + private void OnEndStreamReceived() + { + if (InputRemaining.HasValue) + { + // https://tools.ietf.org/html/rfc7540#section-8.1.2.6 + if (InputRemaining.Value != 0) + { + throw new Http3StreamErrorException(CoreStrings.Http3StreamErrorLessDataThanLength, Http3ErrorCode.ProtocolError); + } + } + + OnTrailersComplete(); + RequestBodyPipe.Writer.Complete(); + } + + private Task ProcessHttp3Stream(IHttpApplication application, in ReadOnlySequence payload) + { + switch (_incomingFrame.Type) + { + case Http3FrameType.Data: + return ProcessDataFrameAsync(payload); + case Http3FrameType.Headers: + return ProcessHeadersFrameAsync(application, payload); + // need to be on control stream + case Http3FrameType.DuplicatePush: + case Http3FrameType.PushPromise: + case Http3FrameType.Settings: + case Http3FrameType.GoAway: + case Http3FrameType.CancelPush: + case Http3FrameType.MaxPushId: + throw new Http3ConnectionException("HTTP_FRAME_UNEXPECTED"); + default: + return ProcessUnknownFrameAsync(); + } + } + + private Task ProcessUnknownFrameAsync() + { + // Unknown frames must be explicitly ignored. + return Task.CompletedTask; + } + + private Task ProcessHeadersFrameAsync(IHttpApplication application, ReadOnlySequence payload) + { + QPackDecoder.Decode(payload, handler: this); + + // start off a request once qpack has decoded + // Make sure to await this task. + if (_receivedHeaders) + { + // trailers + // TODO figure out if there is anything else to do here. + return Task.CompletedTask; + } + + _receivedHeaders = true; + InputRemaining = HttpRequestHeaders.ContentLength; + + _appCompleted = new TaskCompletionSource(); + + ThreadPool.UnsafeQueueUserWorkItem(this, preferLocal: false); + + return Task.CompletedTask; + } + + private Task ProcessDataFrameAsync(in ReadOnlySequence payload) + { + if (InputRemaining.HasValue) + { + // https://tools.ietf.org/html/rfc7540#section-8.1.2.6 + if (payload.Length > InputRemaining.Value) + { + throw new Http3StreamErrorException(CoreStrings.Http3StreamErrorMoreDataThanLength, Http3ErrorCode.ProtocolError); + } + + InputRemaining -= payload.Length; + } + + foreach (var segment in payload) + { + RequestBodyPipe.Writer.Write(segment.Span); + } + + // TODO this can be better. + return RequestBodyPipe.Writer.FlushAsync().AsTask(); + } + + public void StopProcessingNextRequest() + => StopProcessingNextRequest(serverInitiated: true); + + public void StopProcessingNextRequest(bool serverInitiated) + { + var initiator = serverInitiated ? GracefulCloseInitiator.Server : GracefulCloseInitiator.Client; + + if (Interlocked.CompareExchange(ref _gracefulCloseInitiator, initiator, GracefulCloseInitiator.None) == GracefulCloseInitiator.None) + { + Input.CancelPendingRead(); + } + } + + public void Tick(DateTimeOffset now) + { + } + + protected override void OnReset() + { + } + + protected override void ApplicationAbort() + { + var abortReason = new ConnectionAbortedException(CoreStrings.ConnectionAbortedByApplication); + Abort(abortReason, Http3ErrorCode.InternalError); + } + + protected override string CreateRequestId() + { + // TODO include stream id. + return ConnectionId; + } + + protected override MessageBody CreateMessageBody() + => Http3MessageBody.For(this); + + + protected override bool TryParseRequest(ReadResult result, out bool endConnection) + { + endConnection = !TryValidatePseudoHeaders(); + return true; + } + + private bool TryValidatePseudoHeaders() + { + _httpVersion = Http.HttpVersion.Http3; + + if (!TryValidateMethod()) + { + return false; + } + + if (!TryValidateAuthorityAndHost(out var hostText)) + { + return false; + } + + // CONNECT - :scheme and :path must be excluded + if (Method == Http.HttpMethod.Connect) + { + if (!string.IsNullOrEmpty(RequestHeaders[HeaderNames.Scheme]) || !string.IsNullOrEmpty(RequestHeaders[HeaderNames.Path])) + { + Abort(new ConnectionAbortedException(CoreStrings.Http3ErrorConnectMustNotSendSchemeOrPath), Http3ErrorCode.ProtocolError); + return false; + } + + RawTarget = hostText; + + return true; + } + + // :scheme https://tools.ietf.org/html/rfc7540#section-8.1.2.3 + // ":scheme" is not restricted to "http" and "https" schemed URIs. A + // proxy or gateway can translate requests for non - HTTP schemes, + // enabling the use of HTTP to interact with non - HTTP services. + + // - That said, we shouldn't allow arbitrary values or use them to populate Request.Scheme, right? + // - For now we'll restrict it to http/s and require it match the transport. + // - We'll need to find some concrete scenarios to warrant unblocking this. + if (!string.Equals(RequestHeaders[HeaderNames.Scheme], Scheme, StringComparison.OrdinalIgnoreCase)) + { + var str = CoreStrings.FormatHttp3StreamErrorSchemeMismatch(RequestHeaders[HeaderNames.Scheme], Scheme); + Abort(new ConnectionAbortedException(str), Http3ErrorCode.ProtocolError); + return false; + } + + // :path (and query) - Required + // Must start with / except may be * for OPTIONS + var path = RequestHeaders[HeaderNames.Path].ToString(); + RawTarget = path; + + // OPTIONS - https://tools.ietf.org/html/rfc7540#section-8.1.2.3 + // This pseudo-header field MUST NOT be empty for "http" or "https" + // URIs; "http" or "https" URIs that do not contain a path component + // MUST include a value of '/'. The exception to this rule is an + // OPTIONS request for an "http" or "https" URI that does not include + // a path component; these MUST include a ":path" pseudo-header field + // with a value of '*'. + if (Method == Http.HttpMethod.Options && path.Length == 1 && path[0] == '*') + { + // * is stored in RawTarget only since HttpRequest expects Path to be empty or start with a /. + Path = string.Empty; + QueryString = string.Empty; + return true; + } + + // Approximate MaxRequestLineSize by totaling the required pseudo header field lengths. + var requestLineLength = _methodText.Length + Scheme.Length + hostText.Length + path.Length; + if (requestLineLength > ServerOptions.Limits.MaxRequestLineSize) + { + Abort(new ConnectionAbortedException(CoreStrings.BadRequest_RequestLineTooLong), Http3ErrorCode.ProtocolError); + return false; + } + + var queryIndex = path.IndexOf('?'); + QueryString = queryIndex == -1 ? string.Empty : path.Substring(queryIndex); + + var pathSegment = queryIndex == -1 ? path.AsSpan() : path.AsSpan(0, queryIndex); + + return TryValidatePath(pathSegment); + } + + + private bool TryValidateMethod() + { + // :method + _methodText = RequestHeaders[HeaderNames.Method].ToString(); + Method = HttpUtilities.GetKnownMethod(_methodText); + + if (Method == Http.HttpMethod.None) + { + Abort(new ConnectionAbortedException(CoreStrings.FormatHttp3ErrorMethodInvalid(_methodText)), Http3ErrorCode.ProtocolError); + return false; + } + + if (Method == Http.HttpMethod.Custom) + { + if (HttpCharacters.IndexOfInvalidTokenChar(_methodText) >= 0) + { + Abort(new ConnectionAbortedException(CoreStrings.FormatHttp3ErrorMethodInvalid(_methodText)), Http3ErrorCode.ProtocolError); + return false; + } + } + + return true; + } + + private bool TryValidateAuthorityAndHost(out string hostText) + { + // :authority (optional) + // Prefer this over Host + + var authority = RequestHeaders[HeaderNames.Authority]; + var host = HttpRequestHeaders.HeaderHost; + if (!StringValues.IsNullOrEmpty(authority)) + { + // https://tools.ietf.org/html/rfc7540#section-8.1.2.3 + // Clients that generate HTTP/2 requests directly SHOULD use the ":authority" + // pseudo - header field instead of the Host header field. + // An intermediary that converts an HTTP/2 request to HTTP/1.1 MUST + // create a Host header field if one is not present in a request by + // copying the value of the ":authority" pseudo - header field. + + // We take this one step further, we don't want mismatched :authority + // and Host headers, replace Host if :authority is defined. The application + // will operate on the Host header. + HttpRequestHeaders.HeaderHost = authority; + host = authority; + } + + // https://tools.ietf.org/html/rfc7230#section-5.4 + // A server MUST respond with a 400 (Bad Request) status code to any + // HTTP/1.1 request message that lacks a Host header field and to any + // request message that contains more than one Host header field or a + // Host header field with an invalid field-value. + hostText = host.ToString(); + if (host.Count > 1 || !HttpUtilities.IsHostHeaderValid(hostText)) + { + // RST replaces 400 + Abort(new ConnectionAbortedException(CoreStrings.FormatBadRequest_InvalidHostHeader_Detail(hostText)), Http3ErrorCode.ProtocolError); + return false; + } + + return true; + } + + private bool TryValidatePath(ReadOnlySpan pathSegment) + { + // Must start with a leading slash + if (pathSegment.Length == 0 || pathSegment[0] != '/') + { + Abort(new ConnectionAbortedException(CoreStrings.FormatHttp3StreamErrorPathInvalid(RawTarget)), Http3ErrorCode.ProtocolError); + return false; + } + + var pathEncoded = pathSegment.Contains('%'); + + // Compare with Http1Connection.OnOriginFormTarget + + // URIs are always encoded/escaped to ASCII https://tools.ietf.org/html/rfc3986#page-11 + // Multibyte Internationalized Resource Identifiers (IRIs) are first converted to utf8; + // then encoded/escaped to ASCII https://www.ietf.org/rfc/rfc3987.txt "Mapping of IRIs to URIs" + + try + { + const int MaxPathBufferStackAllocSize = 256; + + // The decoder operates only on raw bytes + Span pathBuffer = pathSegment.Length <= MaxPathBufferStackAllocSize + // A constant size plus slice generates better code + // https://github.com/dotnet/aspnetcore/pull/19273#discussion_r383159929 + ? stackalloc byte[MaxPathBufferStackAllocSize].Slice(0, pathSegment.Length) + // TODO - Consider pool here for less than 4096 + // https://github.com/dotnet/aspnetcore/pull/19273#discussion_r383604184 + : new byte[pathSegment.Length]; + + for (var i = 0; i < pathSegment.Length; i++) + { + var ch = pathSegment[i]; + // The header parser should already be checking this + Debug.Assert(32 < ch && ch < 127); + pathBuffer[i] = (byte)ch; + } + + Path = PathNormalizer.DecodePath(pathBuffer, pathEncoded, RawTarget, QueryString.Length); + + return true; + } + catch (InvalidOperationException) + { + Abort(new ConnectionAbortedException(CoreStrings.FormatHttp3StreamErrorPathInvalid(RawTarget)), Http3ErrorCode.ProtocolError); + return false; + } + } + + private Pipe CreateRequestBodyPipe(uint windowSize) + => new Pipe(new PipeOptions + ( + pool: _context.MemoryPool, + readerScheduler: ServiceContext.Scheduler, + writerScheduler: PipeScheduler.Inline, + // Never pause within the window range. Flow control will prevent more data from being added. + // See the assert in OnDataAsync. + pauseWriterThreshold: windowSize + 1, + resumeWriterThreshold: windowSize + 1, + useSynchronizationContext: false, + minimumSegmentSize: _context.MemoryPool.GetMinimumSegmentSize() + )); + + /// + /// Used to kick off the request processing loop by derived classes. + /// + public abstract void Execute(); + + protected enum RequestHeaderParsingState + { + Ready, + PseudoHeaderFields, + Headers, + Trailers + } + + [Flags] + private enum PseudoHeaderFields + { + None = 0x0, + Authority = 0x1, + Method = 0x2, + Path = 0x4, + Scheme = 0x8, + Status = 0x10, + Unknown = 0x40000000 + } + + private static class GracefulCloseInitiator + { + public const int None = 0; + public const int Server = 1; + public const int Client = 2; + } + } +} diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3StreamErrorException.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3StreamErrorException.cs new file mode 100644 index 0000000000..8edccac290 --- /dev/null +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3StreamErrorException.cs @@ -0,0 +1,19 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Net.Http; + +namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3 +{ + class Http3StreamErrorException : Exception + { + public Http3StreamErrorException(string message, Http3ErrorCode errorCode) + : base($"HTTP/3 stream error ({errorCode}): {message}") + { + ErrorCode = errorCode; + } + + public Http3ErrorCode ErrorCode { get; } + } +} diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3StreamOfT.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3StreamOfT.cs new file mode 100644 index 0000000000..b0dc9e4729 --- /dev/null +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3StreamOfT.cs @@ -0,0 +1,33 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNetCore.Hosting.Server; +using Microsoft.AspNetCore.Hosting.Server.Abstractions; + +namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3 +{ + class Http3Stream : Http3Stream, IHostContextContainer + { + private readonly IHttpApplication _application; + + public Http3Stream(IHttpApplication application, Http3Connection connection, Http3StreamContext context) : base(connection, context) + { + _application = application; + } + + public override void Execute() + { + if (_requestHeaderParsingState == Http3Stream.RequestHeaderParsingState.Ready) + { + _ = ProcessRequestAsync(_application); + } + else + { + _ = base.ProcessRequestsAsync(_application); + } + } + + // Pooled Host context + TContext IHostContextContainer.HostContext { get; set; } + } +} diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/QPack/DecoderStreamReader.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/QPack/DecoderStreamReader.cs new file mode 100644 index 0000000000..6b53b79900 --- /dev/null +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/QPack/DecoderStreamReader.cs @@ -0,0 +1,130 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Buffers; +using System.Net.Http.HPack; + +namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3.QPack +{ + internal class DecoderStreamReader + { + private enum State + { + Ready, + HeaderAckowledgement, + StreamCancellation, + InsertCountIncrement + } + + //0 1 2 3 4 5 6 7 + //+---+---+---+---+---+---+---+---+ + //| 1 | Stream ID(7+) | + //+---+---------------------------+ + private const byte HeaderAcknowledgementMask = 0x80; + private const byte HeaderAcknowledgementRepresentation = 0x80; + private const byte HeaderAcknowledgementPrefixMask = 0x7F; + private const int HeaderAcknowledgementPrefix = 7; + + //0 1 2 3 4 5 6 7 + //+---+---+---+---+---+---+---+---+ + //| 0 | 1 | Stream ID(6+) | + //+---+---+-----------------------+ + private const byte StreamCancellationMask = 0xC0; + private const byte StreamCancellationRepresentation = 0x40; + private const byte StreamCancellationPrefixMask = 0x3F; + private const int StreamCancellationPrefix = 6; + + //0 1 2 3 4 5 6 7 + //+---+---+---+---+---+---+---+---+ + //| 0 | 0 | Increment(6+) | + //+---+---+-----------------------+ + private const byte InsertCountIncrementMask = 0xC0; + private const byte InsertCountIncrementRepresentation = 0x00; + private const byte InsertCountIncrementPrefixMask = 0x3F; + private const int InsertCountIncrementPrefix = 6; + + private IntegerDecoder _integerDecoder = new IntegerDecoder(); + private State _state; + + public DecoderStreamReader() + { + } + + public void Read(ReadOnlySequence data) + { + foreach (var segment in data) + { + var span = segment.Span; + for (var i = 0; i < span.Length; i++) + { + OnByte(span[i]); + } + } + } + + private void OnByte(byte b) + { + int intResult; + int prefixInt; + switch (_state) + { + case State.Ready: + if ((b & HeaderAcknowledgementMask) == HeaderAcknowledgementRepresentation) + { + prefixInt = HeaderAcknowledgementPrefixMask & b; + if (_integerDecoder.BeginTryDecode((byte)prefixInt, HeaderAcknowledgementPrefix, out intResult)) + { + OnHeaderAcknowledgement(intResult); + } + else + { + _state = State.HeaderAckowledgement; + } + } + else if ((b & StreamCancellationMask) == StreamCancellationRepresentation) + { + prefixInt = StreamCancellationPrefixMask & b; + if (_integerDecoder.BeginTryDecode((byte)prefixInt, StreamCancellationPrefix, out intResult)) + { + OnStreamCancellation(intResult); + } + else + { + _state = State.StreamCancellation; + } + } + else if ((b & InsertCountIncrementMask) == InsertCountIncrementRepresentation) + { + prefixInt = InsertCountIncrementPrefixMask & b; + if (_integerDecoder.BeginTryDecode((byte)prefixInt, InsertCountIncrementPrefix, out intResult)) + { + OnInsertCountIncrement(intResult); + } + else + { + _state = State.InsertCountIncrement; + } + } + break; + } + } + + private void OnInsertCountIncrement(int intResult) + { + // increment some count. + _state = State.Ready; + } + + private void OnStreamCancellation(int streamId) + { + // Remove stream? + _state = State.Ready; + } + + private void OnHeaderAcknowledgement(int intResult) + { + // Acknowledge header somehow + _state = State.Ready; + } + } +} diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/QPack/DynamicTable.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/QPack/DynamicTable.cs new file mode 100644 index 0000000000..63388ffdfb --- /dev/null +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/QPack/DynamicTable.cs @@ -0,0 +1,47 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Net.Http.QPack; + +namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3.QPack +{ + // The size of the dynamic table is the sum of the size of its entries. + // The size of an entry is the sum of its name's length in bytes (as + // defined in Section 4.1.2), its value's length in bytes, and 32. + + internal class DynamicTable + { + + // The encoder sends a Set Dynamic Table Capacity + // instruction(Section 4.3.1) with a non-zero capacity to begin using + // the dynamic table. + public DynamicTable(int maxSize) + { + } + + public HeaderField this[int index] + { + get + { + return new HeaderField(); + } + } + + // TODO + public void Insert(Span name, Span value) + { + } + + // TODO + public void Resize(int maxSize) + { + } + + // TODO + internal void Duplicate(int index) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/QPack/EncoderStreamReader.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/QPack/EncoderStreamReader.cs new file mode 100644 index 0000000000..54ebad55db --- /dev/null +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/QPack/EncoderStreamReader.cs @@ -0,0 +1,333 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Buffers; +using System.Net.Http.HPack; +using System.Net.Http.QPack; + +namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3.QPack +{ + internal class EncoderStreamReader + { + private enum State + { + Ready, + DynamicTableCapcity, + NameIndex, + NameLength, + Name, + ValueLength, + ValueLengthContinue, + Value, + Duplicate + } + + //0 1 2 3 4 5 6 7 + //+---+---+---+---+---+---+---+---+ + //| 0 | 0 | 1 | Capacity(5+) | + //+---+---+---+-------------------+ + private const byte DynamicTableCapacityMask = 0xE0; + private const byte DynamicTableCapacityRepresentation = 0x20; + private const byte DynamicTableCapacityPrefixMask = 0x1F; + private const int DynamicTableCapacityPrefix = 5; + + + //0 1 2 3 4 5 6 7 + //+---+---+---+---+---+---+---+---+ + //| 1 | S | Name Index(6+) | + //+---+---+-----------------------+ + //| H | Value Length(7+) | + //+---+---------------------------+ + //| Value String(Length bytes) | + //+-------------------------------+ + private const byte InsertWithNameReferenceMask = 0x80; + private const byte InsertWithNameReferenceRepresentation = 0x80; + private const byte InsertWithNameReferencePrefixMask = 0x3F; + private const byte InsertWithNameReferenceStaticMask = 0x40; + private const int InsertWithNameReferencePrefix = 6; + + //0 1 2 3 4 5 6 7 + //+---+---+---+---+---+---+---+---+ + //| 0 | 1 | H | Name Length(5+) | + //+---+---+---+-------------------+ + //| Name String(Length bytes) | + //+---+---------------------------+ + //| H | Value Length(7+) | + //+---+---------------------------+ + //| Value String(Length bytes) | + //+-------------------------------+ + private const byte InsertWithoutNameReferenceMask = 0xC0; + private const byte InsertWithoutNameReferenceRepresentation = 0x40; + private const byte InsertWithoutNameReferencePrefixMask = 0x1F; + private const byte InsertWithoutNameReferenceHuffmanMask = 0x20; + private const int InsertWithoutNameReferencePrefix = 5; + + //0 1 2 3 4 5 6 7 + //+---+---+---+---+---+---+---+---+ + //| 0 | 0 | 0 | Index(5+) | + //+---+---+---+-------------------+ + private const byte DuplicateMask = 0xE0; + private const byte DuplicateRepresentation = 0x00; + private const byte DuplicatePrefixMask = 0x1F; + private const int DuplicatePrefix = 5; + + private const int StringLengthPrefix = 7; + private const byte HuffmanMask = 0x80; + + private bool _s; + private byte[] _stringOctets; + private byte[] _headerNameOctets; + private byte[] _headerValueOctets; + private byte[] _headerName; + private int _headerNameLength; + private int _headerValueLength; + private int _stringLength; + private int _stringIndex; + private DynamicTable _dynamicTable = new DynamicTable(1000); // TODO figure out architecture. + + private readonly IntegerDecoder _integerDecoder = new IntegerDecoder(); + private State _state = State.Ready; + private bool _huffman; + + public EncoderStreamReader(int maxRequestHeaderFieldSize) + { + // TODO how to propagate dynamic table around. + + _stringOctets = new byte[maxRequestHeaderFieldSize]; + _headerNameOctets = new byte[maxRequestHeaderFieldSize]; + _headerValueOctets = new byte[maxRequestHeaderFieldSize]; + } + + public void Read(ReadOnlySequence data) + { + foreach (var segment in data) + { + var span = segment.Span; + for (var i = 0; i < span.Length; i++) + { + OnByte(span[i]); + } + } + } + + private void OnByte(byte b) + { + int intResult; + int prefixInt; + switch (_state) + { + case State.Ready: + if ((b & DynamicTableCapacityMask) == DynamicTableCapacityRepresentation) + { + prefixInt = DynamicTableCapacityPrefixMask & b; + if (_integerDecoder.BeginTryDecode((byte)prefixInt, DynamicTableCapacityPrefix, out intResult)) + { + OnDynamicTableCapacity(intResult); + } + else + { + _state = State.DynamicTableCapcity; + } + } + else if ((b & InsertWithNameReferenceMask) == InsertWithNameReferenceRepresentation) + { + prefixInt = InsertWithNameReferencePrefixMask & b; + _s = (InsertWithNameReferenceStaticMask & b) == InsertWithNameReferenceStaticMask; + + if (_integerDecoder.BeginTryDecode((byte)prefixInt, InsertWithNameReferencePrefix, out intResult)) + { + OnNameIndex(intResult); + } + else + { + _state = State.NameIndex; + } + } + else if ((b & InsertWithoutNameReferenceMask) == InsertWithoutNameReferenceRepresentation) + { + prefixInt = InsertWithoutNameReferencePrefixMask & b; + _huffman = (InsertWithoutNameReferenceHuffmanMask & b) == InsertWithoutNameReferenceHuffmanMask; + + if (_integerDecoder.BeginTryDecode((byte)prefixInt, InsertWithoutNameReferencePrefix, out intResult)) + { + OnStringLength(intResult, State.Name); + } + else + { + _state = State.NameIndex; + } + } + else if ((b & DuplicateMask) == DuplicateRepresentation) + { + prefixInt = DuplicatePrefixMask & b; + if (_integerDecoder.BeginTryDecode((byte)prefixInt, DuplicatePrefix, out intResult)) + { + OnDuplicate(intResult); + } + else + { + _state = State.Duplicate; + } + } + break; + case State.DynamicTableCapcity: + if (_integerDecoder.TryDecode(b, out intResult)) + { + OnDynamicTableCapacity(intResult); + } + break; + case State.NameIndex: + if (_integerDecoder.TryDecode(b, out intResult)) + { + OnNameIndex(intResult); + } + break; + case State.NameLength: + if (_integerDecoder.TryDecode(b, out intResult)) + { + OnStringLength(intResult, nextState: State.Name); + } + break; + case State.Name: + _stringOctets[_stringIndex++] = b; + + if (_stringIndex == _stringLength) + { + OnString(nextState: State.ValueLength); + } + + break; + case State.ValueLength: + _huffman = (b & HuffmanMask) != 0; + + // TODO confirm this. + if (_integerDecoder.BeginTryDecode((byte)(b & ~HuffmanMask), StringLengthPrefix, out intResult)) + { + OnStringLength(intResult, nextState: State.Value); + if (intResult == 0) + { + ProcessValue(); + } + } + else + { + _state = State.ValueLengthContinue; + } + break; + case State.ValueLengthContinue: + if (_integerDecoder.TryDecode(b, out intResult)) + { + OnStringLength(intResult, nextState: State.Value); + if (intResult == 0) + { + ProcessValue(); + } + } + break; + case State.Value: + _stringOctets[_stringIndex++] = b; + if (_stringIndex == _stringLength) + { + ProcessValue(); + } + break; + case State.Duplicate: + if (_integerDecoder.TryDecode(b, out intResult)) + { + OnDuplicate(intResult); + } + break; + } + } + + + private void OnStringLength(int length, State nextState) + { + if (length > _stringOctets.Length) + { + throw new QPackDecodingException(/*CoreStrings.FormatQPackStringLengthTooLarge(length, _stringOctets.Length)*/); + } + + _stringLength = length; + _stringIndex = 0; + _state = nextState; + } + + private void ProcessValue() + { + OnString(nextState: State.Ready); + var headerNameSpan = new Span(_headerName, 0, _headerNameLength); + var headerValueSpan = new Span(_headerValueOctets, 0, _headerValueLength); + _dynamicTable.Insert(headerNameSpan, headerValueSpan); + } + + private void OnString(State nextState) + { + int Decode(byte[] dst) + { + if (_huffman) + { + return Huffman.Decode(new ReadOnlySpan(_stringOctets, 0, _stringLength), ref dst); + } + else + { + Buffer.BlockCopy(_stringOctets, 0, dst, 0, _stringLength); + return _stringLength; + } + } + + try + { + if (_state == State.Name) + { + _headerName = _headerNameOctets; + _headerNameLength = Decode(_headerNameOctets); + } + else + { + _headerValueLength = Decode(_headerValueOctets); + } + } + catch (HuffmanDecodingException ex) + { + throw new QPackDecodingException(""/*CoreStrings.QPackHuffmanError*/, ex); + } + + _state = nextState; + } + + private void OnNameIndex(int index) + { + var header = GetHeader(index); + _headerName = header.Name; + _headerNameLength = header.Name.Length; + _state = State.ValueLength; + } + + private void OnDynamicTableCapacity(int dynamicTableSize) + { + // Call Decoder to update the table size. + _dynamicTable.Resize(dynamicTableSize); + _state = State.Ready; + } + + private void OnDuplicate(int index) + { + _dynamicTable.Duplicate(index); + _state = State.Ready; + } + + private System.Net.Http.QPack.HeaderField GetHeader(int index) + { + try + { + return _s ? H3StaticTable.Instance[index] : _dynamicTable[index]; + } + catch (IndexOutOfRangeException ex) + { + throw new QPackDecodingException( "" /*CoreStrings.FormatQPackErrorIndexOutOfRange(index)*/, ex); + } + } + } +} diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3ConnectionContext.cs b/src/Servers/Kestrel/Core/src/Internal/Http3ConnectionContext.cs new file mode 100644 index 0000000000..e20f8c33a6 --- /dev/null +++ b/src/Servers/Kestrel/Core/src/Internal/Http3ConnectionContext.cs @@ -0,0 +1,23 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Buffers; +using System.Net; +using Microsoft.AspNetCore.Connections; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; + +namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal +{ + internal class Http3ConnectionContext + { + public string ConnectionId { get; set; } + public MultiplexedConnectionContext ConnectionContext { get; set; } + public ServiceContext ServiceContext { get; set; } + public IFeatureCollection ConnectionFeatures { get; set; } + public MemoryPool MemoryPool { get; set; } + public IPEndPoint LocalEndPoint { get; set; } + public IPEndPoint RemoteEndPoint { get; set; } + public ITimeoutControl TimeoutControl { get; set; } + } +} diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3StreamContext.cs b/src/Servers/Kestrel/Core/src/Internal/Http3StreamContext.cs new file mode 100644 index 0000000000..9107b6dc84 --- /dev/null +++ b/src/Servers/Kestrel/Core/src/Internal/Http3StreamContext.cs @@ -0,0 +1,12 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNetCore.Connections; + +namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal +{ + internal class Http3StreamContext : HttpConnectionContext + { + public ConnectionContext StreamContext { get; set; } + } +} diff --git a/src/Servers/Kestrel/Core/src/Internal/HttpConnection.cs b/src/Servers/Kestrel/Core/src/Internal/HttpConnection.cs index de8411d7f8..a00f6002a4 100644 --- a/src/Servers/Kestrel/Core/src/Internal/HttpConnection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/HttpConnection.cs @@ -3,6 +3,7 @@ using System; using System.Diagnostics; +using System.Net; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Connections.Features; @@ -11,6 +12,7 @@ using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Server.Kestrel.Core.Features; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; using Microsoft.Extensions.Logging; @@ -69,10 +71,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal case HttpProtocols.None: // An error was already logged in SelectProtocol(), but we should close the connection. break; + default: // SelectProtocol() only returns Http1, Http2 or None. - throw new NotSupportedException($"{nameof(SelectProtocol)} returned something other than Http1, Http2 or None."); - } + throw new NotSupportedException($"{nameof(SelectProtocol)} returned something other than Http1, Http2, Http3 or None."); + } _requestProcessor = requestProcessor; diff --git a/src/Servers/Kestrel/Core/src/Internal/HttpConnectionContext.cs b/src/Servers/Kestrel/Core/src/Internal/HttpConnectionContext.cs index 562b7bd1a9..69c46ec79c 100644 --- a/src/Servers/Kestrel/Core/src/Internal/HttpConnectionContext.cs +++ b/src/Servers/Kestrel/Core/src/Internal/HttpConnectionContext.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Buffers; -using System.Collections.Generic; using System.IO.Pipelines; using System.Net; using Microsoft.AspNetCore.Connections; diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/ConnectionReference.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/ConnectionReference.cs index b5dc202e01..dd31fde12f 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/ConnectionReference.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/ConnectionReference.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/Heartbeat.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/Heartbeat.cs index 7cba84b138..9cf9e2d62e 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/Heartbeat.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/Heartbeat.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; @@ -18,6 +18,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure private readonly TimeSpan _interval; private Timer _timer; private int _executingOnHeartbeat; + private long _lastHeartbeatTicks; public Heartbeat(IHeartbeatHandler[] callbacks, ISystemClock systemClock, IDebugger debugger, IKestrelTrace trace) { @@ -46,6 +47,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure if (Interlocked.Exchange(ref _executingOnHeartbeat, 1) == 0) { + Volatile.Write(ref _lastHeartbeatTicks, now.Ticks); + try { foreach (var callback in _callbacks) @@ -66,7 +69,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure { if (!_debugger.IsAttached) { - _trace.HeartbeatSlow(_interval, now); + var lastHeartbeatTicks = Volatile.Read(ref _lastHeartbeatTicks); + + _trace.HeartbeatSlow(TimeSpan.FromTicks(now.Ticks - lastHeartbeatTicks), _interval, now); } } } diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/HttpUtilities.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/HttpUtilities.cs index 04fd9711d7..70fa7f00b3 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/HttpUtilities.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/HttpUtilities.cs @@ -2,8 +2,10 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Buffers; using System.Diagnostics; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Text; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; @@ -12,10 +14,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure { internal static partial class HttpUtilities { - public const string Http10Version = "HTTP/1.0"; - public const string Http11Version = "HTTP/1.1"; - public const string Http2Version = "HTTP/2"; - public const string HttpUriScheme = "http://"; public const string HttpsUriScheme = "https://"; @@ -29,6 +27,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure private const ulong _http11VersionLong = 3543824036068086856; // GetAsciiStringAsLong("HTTP/1.1"); const results in better codegen private static readonly UTF8EncodingSealed HeaderValueEncoding = new UTF8EncodingSealed(); + private static readonly SpanAction _getHeaderName = GetHeaderName; + private static readonly SpanAction _getAsciiStringNonNullCharacters = GetAsciiStringNonNullCharacters; [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void SetKnownMethod(ulong mask, ulong knownMethodUlong, HttpMethod knownMethod, int length) @@ -85,66 +85,79 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure } // The same as GetAsciiStringNonNullCharacters but throws BadRequest - public static unsafe string GetHeaderName(this Span span) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe string GetHeaderName(this ReadOnlySpan span) { if (span.IsEmpty) { return string.Empty; } - var asciiString = new string('\0', span.Length); + fixed (byte* source = &MemoryMarshal.GetReference(span)) + { + return string.Create(span.Length, new IntPtr(source), _getHeaderName); + } + } - fixed (char* output = asciiString) - fixed (byte* buffer = span) + private static unsafe void GetHeaderName(Span buffer, IntPtr state) + { + fixed (char* output = &MemoryMarshal.GetReference(buffer)) { // This version if AsciiUtilities returns null if there are any null (0 byte) characters // in the string - if (!StringUtilities.TryGetAsciiString(buffer, output, span.Length)) + if (!StringUtilities.TryGetAsciiString((byte*)state.ToPointer(), output, buffer.Length)) { BadHttpRequestException.Throw(RequestRejectionReason.InvalidCharactersInHeaderName); } } - - return asciiString; } - public static unsafe string GetAsciiStringNonNullCharacters(this Span span) + public static string GetAsciiStringNonNullCharacters(this Span span) + => GetAsciiStringNonNullCharacters((ReadOnlySpan)span); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe string GetAsciiStringNonNullCharacters(this ReadOnlySpan span) { if (span.IsEmpty) { return string.Empty; } - var asciiString = new string('\0', span.Length); + fixed (byte* source = &MemoryMarshal.GetReference(span)) + { + return string.Create(span.Length, new IntPtr(source), _getAsciiStringNonNullCharacters); + } + } - fixed (char* output = asciiString) - fixed (byte* buffer = span) + private static unsafe void GetAsciiStringNonNullCharacters(Span buffer, IntPtr state) + { + fixed (char* output = &MemoryMarshal.GetReference(buffer)) { // StringUtilities.TryGetAsciiString returns null if there are any null (0 byte) characters // in the string - if (!StringUtilities.TryGetAsciiString(buffer, output, span.Length)) + if (!StringUtilities.TryGetAsciiString((byte*)state.ToPointer(), output, buffer.Length)) { throw new InvalidOperationException(); } } - return asciiString; } private static unsafe string GetAsciiOrUTF8StringNonNullCharacters(this Span span) + => GetAsciiOrUTF8StringNonNullCharacters((ReadOnlySpan)span); + + public static unsafe string GetAsciiOrUTF8StringNonNullCharacters(this ReadOnlySpan span) { if (span.IsEmpty) { return string.Empty; } - var resultString = new string('\0', span.Length); - - fixed (char* output = resultString) - fixed (byte* buffer = span) + fixed (byte* source = &MemoryMarshal.GetReference(span)) { - // StringUtilities.TryGetAsciiString returns null if there are any null (0 byte) characters - // in the string - if (!StringUtilities.TryGetAsciiString(buffer, output, span.Length)) + var resultString = string.Create(span.Length, new IntPtr(source), s_getAsciiOrUtf8StringNonNullCharacters); + + // If resultString is marked, perform UTF-8 encoding + if (resultString[0] == '\0') { // null characters are considered invalid if (span.IndexOf((byte)0) != -1) @@ -154,19 +167,35 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure try { - resultString = HeaderValueEncoding.GetString(buffer, span.Length); + resultString = HeaderValueEncoding.GetString(span); } catch (DecoderFallbackException) { throw new InvalidOperationException(); } } - } - return resultString; + return resultString; + } } - private static unsafe string GetLatin1StringNonNullCharacters(this Span span) + private static readonly SpanAction s_getAsciiOrUtf8StringNonNullCharacters = GetAsciiOrUTF8StringNonNullCharacters; + + private static unsafe void GetAsciiOrUTF8StringNonNullCharacters(Span buffer, IntPtr state) + { + fixed (char* output = &MemoryMarshal.GetReference(buffer)) + { + // This version if AsciiUtilities returns null if there are any null (0 byte) characters + // in the string + if (!StringUtilities.TryGetAsciiString((byte*)state.ToPointer(), output, buffer.Length)) + { + // Mark resultString for UTF-8 encoding + output[0] = '\0'; + } + } + } + + private static unsafe string GetLatin1StringNonNullCharacters(this ReadOnlySpan span) { if (span.IsEmpty) { @@ -189,7 +218,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure return resultString; } - public static string GetRequestHeaderStringNonNullCharacters(this Span span, bool useLatin1) => + public static string GetRequestHeaderStringNonNullCharacters(this ReadOnlySpan span, bool useLatin1) => useLatin1 ? GetLatin1StringNonNullCharacters(span) : GetAsciiOrUTF8StringNonNullCharacters(span); public static string GetAsciiStringEscaped(this Span span, int maxChars) @@ -308,7 +337,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure { method = HttpMethod.Head; } - else if(firstChar == 'P' && string.Equals(value, HttpMethods.Post, StringComparison.Ordinal)) + else if (firstChar == 'P' && string.Equals(value, HttpMethods.Post, StringComparison.Ordinal)) { method = HttpMethod.Post; } @@ -319,7 +348,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure { method = HttpMethod.Trace; } - else if(firstChar == 'P' && string.Equals(value, HttpMethods.Patch, StringComparison.Ordinal)) + else if (firstChar == 'P' && string.Equals(value, HttpMethods.Patch, StringComparison.Ordinal)) { method = HttpMethod.Patch; } @@ -449,13 +478,22 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure public static string VersionToString(HttpVersion httpVersion) { - return httpVersion switch + switch (httpVersion) { - HttpVersion.Http10 => Http10Version, - HttpVersion.Http11 => Http11Version, - _ => null, + case HttpVersion.Http10: + return AspNetCore.Http.HttpProtocol.Http10; + case HttpVersion.Http11: + return AspNetCore.Http.HttpProtocol.Http11; + case HttpVersion.Http2: + return AspNetCore.Http.HttpProtocol.Http2; + case HttpVersion.Http3: + return AspNetCore.Http.HttpProtocol.Http3; + default: + Debug.Fail("Unexpected HttpVersion: " + httpVersion); + return null; }; } + public static string MethodToString(HttpMethod method) { var methodIndex = (int)method; diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/IKestrelTrace.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/IKestrelTrace.cs index 1a5815300e..cd441ae3a2 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/IKestrelTrace.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/IKestrelTrace.cs @@ -2,9 +2,9 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Net.Http.HPack; using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2; -using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack; using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure @@ -39,7 +39,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure void NotAllConnectionsAborted(); - void HeartbeatSlow(TimeSpan interval, DateTimeOffset now); + void HeartbeatSlow(TimeSpan heartbeatDuration, TimeSpan interval, DateTimeOffset now); void ApplicationNeverCompleted(string connectionId); diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelConnection.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelConnection.cs index bec07b018d..5365ed7397 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelConnection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelConnection.cs @@ -11,7 +11,7 @@ using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure { - internal class KestrelConnection : IConnectionHeartbeatFeature, IConnectionCompleteFeature, IConnectionLifetimeNotificationFeature, IThreadPoolWorkItem + internal abstract class KestrelConnection : IConnectionHeartbeatFeature, IConnectionCompleteFeature, IConnectionLifetimeNotificationFeature { private List<(Action handler, object state)> _heartbeatHandlers; private readonly object _heartbeatLock = new object(); @@ -21,31 +21,22 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure private readonly CancellationTokenSource _connectionClosingCts = new CancellationTokenSource(); private readonly TaskCompletionSource _completionTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - private readonly long _id; - private readonly ServiceContext _serviceContext; - private readonly ConnectionDelegate _connectionDelegate; + protected readonly long _id; + protected readonly ServiceContext _serviceContext; public KestrelConnection(long id, ServiceContext serviceContext, - ConnectionDelegate connectionDelegate, - ConnectionContext connectionContext, IKestrelTrace logger) { _id = id; _serviceContext = serviceContext; - _connectionDelegate = connectionDelegate; Logger = logger; - TransportConnection = connectionContext; - connectionContext.Features.Set(this); - connectionContext.Features.Set(this); - connectionContext.Features.Set(this); ConnectionClosedRequested = _connectionClosingCts.Token; } - private IKestrelTrace Logger { get; } + protected IKestrelTrace Logger { get; } - public ConnectionContext TransportConnection { get; set; } public CancellationToken ConnectionClosedRequested { get; set; } public Task ExecutionTask => _completionTcs.Task; @@ -65,6 +56,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure } } + public abstract BaseConnectionContext TransportConnection { get; } + public void OnHeartbeat(Action action, object state) { lock (_heartbeatLock) @@ -124,7 +117,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure } catch (Exception ex) { - Logger.LogError(ex, "An error occured running an IConnectionCompleteFeature.OnCompleted callback."); + Logger.LogError(ex, "An error occurred running an IConnectionCompleteFeature.OnCompleted callback."); } } @@ -139,7 +132,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure } catch (Exception ex) { - Logger.LogError(ex, "An error occured running an IConnectionCompleteFeature.OnCompleted callback."); + Logger.LogError(ex, "An error occurred running an IConnectionCompleteFeature.OnCompleted callback."); } while (onCompleted.TryPop(out var entry)) @@ -150,7 +143,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure } catch (Exception ex) { - Logger.LogError(ex, "An error occured running an IConnectionCompleteFeature.OnCompleted callback."); + Logger.LogError(ex, "An error occurred running an IConnectionCompleteFeature.OnCompleted callback."); } } } @@ -175,49 +168,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure _connectionClosingCts.Dispose(); } - void IThreadPoolWorkItem.Execute() - { - _ = ExecuteAsync(); - } - - internal async Task ExecuteAsync() - { - var connectionContext = TransportConnection; - - try - { - Logger.ConnectionStart(connectionContext.ConnectionId); - KestrelEventSource.Log.ConnectionStart(connectionContext); - - using (BeginConnectionScope(connectionContext)) - { - try - { - await _connectionDelegate(connectionContext); - } - catch (Exception ex) - { - Logger.LogError(0, ex, "Unhandled exception while processing {ConnectionId}.", connectionContext.ConnectionId); - } - } - } - finally - { - await FireOnCompletedAsync(); - - Logger.ConnectionStop(connectionContext.ConnectionId); - KestrelEventSource.Log.ConnectionStop(connectionContext); - - // Dispose the transport connection, this needs to happen before removing it from the - // connection manager so that we only signal completion of this connection after the transport - // is properly torn down. - await TransportConnection.DisposeAsync(); - - _serviceContext.ConnectionManager.RemoveConnection(_id); - } - } - - private IDisposable BeginConnectionScope(ConnectionContext connectionContext) + protected IDisposable BeginConnectionScope(BaseConnectionContext connectionContext) { if (Logger.IsEnabled(LogLevel.Critical)) { diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelConnectionOfT.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelConnectionOfT.cs new file mode 100644 index 0000000000..d758bf3f1a --- /dev/null +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelConnectionOfT.cs @@ -0,0 +1,73 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Connections; +using Microsoft.AspNetCore.Connections.Features; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure +{ + internal class KestrelConnection : KestrelConnection, IThreadPoolWorkItem where T : BaseConnectionContext + { + private readonly Func _connectionDelegate; + private readonly T _transportConnection; + + public KestrelConnection(long id, + ServiceContext serviceContext, + Func connectionDelegate, + T connectionContext, + IKestrelTrace logger) + : base(id, serviceContext, logger) + { + _connectionDelegate = connectionDelegate; + _transportConnection = connectionContext; + connectionContext.Features.Set(this); + connectionContext.Features.Set(this); + connectionContext.Features.Set(this); + } + + public override BaseConnectionContext TransportConnection => _transportConnection; + + void IThreadPoolWorkItem.Execute() + { + _ = ExecuteAsync(); + } + + internal async Task ExecuteAsync() + { + var connectionContext = _transportConnection; + + try + { + Logger.ConnectionStart(connectionContext.ConnectionId); + KestrelEventSource.Log.ConnectionStart(connectionContext); + + using (BeginConnectionScope(connectionContext)) + { + try + { + await _connectionDelegate(connectionContext); + } + catch (Exception ex) + { + Logger.LogError(0, ex, "Unhandled exception while processing {ConnectionId}.", connectionContext.ConnectionId); + } + } + } + finally + { + await FireOnCompletedAsync(); + + Logger.ConnectionStop(connectionContext.ConnectionId); + KestrelEventSource.Log.ConnectionStop(connectionContext); + + // Dispose the transport connection, this needs to happen before removing it from the + // connection manager so that we only signal completion of this connection after the transport + // is properly torn down. + await connectionContext.DisposeAsync(); + + _serviceContext.ConnectionManager.RemoveConnection(_id); + } + } + } +} diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelEventSource.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelEventSource.cs index fdabf48247..19afb9af71 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelEventSource.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelEventSource.cs @@ -26,7 +26,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure // - Avoid renaming methods or parameters marked with EventAttribute. EventSource uses these to form the event object. [NonEvent] - public void ConnectionStart(ConnectionContext connection) + public void ConnectionStart(BaseConnectionContext connection) { // avoid allocating strings unless this event source is enabled if (IsEnabled()) @@ -53,7 +53,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure } [NonEvent] - public void ConnectionStop(ConnectionContext connection) + public void ConnectionStop(BaseConnectionContext connection) { if (IsEnabled()) { diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelTrace.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelTrace.cs index d72aa3c707..f89732a704 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelTrace.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelTrace.cs @@ -2,9 +2,9 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Net.Http.HPack; using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2; -using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack; using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure @@ -47,8 +47,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure private static readonly Action _notAllConnectionsAborted = LoggerMessage.Define(LogLevel.Debug, new EventId(21, nameof(NotAllConnectionsAborted)), "Some connections failed to abort during server shutdown."); - private static readonly Action _heartbeatSlow = - LoggerMessage.Define(LogLevel.Warning, new EventId(22, nameof(HeartbeatSlow)), @"Heartbeat took longer than ""{interval}"" at ""{now}"". This could be caused by thread pool starvation."); + private static readonly Action _heartbeatSlow = + LoggerMessage.Define(LogLevel.Warning, new EventId(22, nameof(HeartbeatSlow)), @"As of ""{now}"", the heartbeat has been running for ""{heartbeatDuration}"" which is longer than ""{interval}"". This could be caused by thread pool starvation."); private static readonly Action _applicationNeverCompleted = LoggerMessage.Define(LogLevel.Critical, new EventId(23, nameof(ApplicationNeverCompleted)), @"Connection id ""{ConnectionId}"" application never completed"); @@ -190,9 +190,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure _notAllConnectionsAborted(_logger, null); } - public virtual void HeartbeatSlow(TimeSpan interval, DateTimeOffset now) + public virtual void HeartbeatSlow(TimeSpan heartbeatDuration, TimeSpan interval, DateTimeOffset now) { - _heartbeatSlow(_logger, interval, now, null); + _heartbeatSlow(_logger, heartbeatDuration, interval, now, null); } public virtual void ApplicationNeverCompleted(string connectionId) @@ -272,12 +272,18 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure public void Http2FrameReceived(string connectionId, Http2Frame frame) { - _http2FrameReceived(_logger, connectionId, frame.Type, frame.StreamId, frame.PayloadLength, frame.ShowFlags(), null); + if (_logger.IsEnabled(LogLevel.Trace)) + { + _http2FrameReceived(_logger, connectionId, frame.Type, frame.StreamId, frame.PayloadLength, frame.ShowFlags(), null); + } } public void Http2FrameSending(string connectionId, Http2Frame frame) { - _http2FrameSending(_logger, connectionId, frame.Type, frame.StreamId, frame.PayloadLength, frame.ShowFlags(), null); + if (_logger.IsEnabled(LogLevel.Trace)) + { + _http2FrameSending(_logger, connectionId, frame.Type, frame.StreamId, frame.PayloadLength, frame.ShowFlags(), null); + } } public virtual void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/PipeWriterHelpers/BufferSegment.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/PipeWriterHelpers/BufferSegment.cs index fdd8ac7367..a9bf8d9424 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/PipeWriterHelpers/BufferSegment.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/PipeWriterHelpers/BufferSegment.cs @@ -59,20 +59,16 @@ namespace System.IO.Pipelines AvailableMemory = arrayPoolBuffer; } - public void SetUnownedMemory(Memory memory) - { - AvailableMemory = memory; - } - public void ResetMemory() { if (_memoryOwner is IMemoryOwner owner) { owner.Dispose(); } - else if (_memoryOwner is byte[] array) + else { - ArrayPool.Shared.Return(array); + byte[] poolArray = (byte[])_memoryOwner; + ArrayPool.Shared.Return(poolArray); } // Order of below field clears is significant as it clears in a sequential order diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/PipeWriterHelpers/ConcurrentPipeWriter.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/PipeWriterHelpers/ConcurrentPipeWriter.cs index 3ab051c8c4..5e871e9dad 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/PipeWriterHelpers/ConcurrentPipeWriter.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/PipeWriterHelpers/ConcurrentPipeWriter.cs @@ -3,6 +3,7 @@ using System; using System.Buffers; +using System.Diagnostics; using System.IO.Pipelines; using System.Runtime.CompilerServices; using System.Threading; @@ -59,6 +60,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.PipeW _sync = sync; } + public void Reset() + { + Debug.Assert(_currentFlushTcs == null, "There should not be a pending flush."); + + _aborted = false; + _completeException = null; + } + public override Memory GetMemory(int sizeHint = 0) { if (_currentFlushTcs == null && _head == null) @@ -341,8 +350,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.PipeW } else { - // We can't use the pool so allocate an array - newSegment.SetUnownedMemory(new byte[sizeHint]); + // We can't use the recommended pool so use the ArrayPool + newSegment.SetOwnedMemory(ArrayPool.Shared.Rent(sizeHint)); } _tailMemory = newSegment.AvailableMemory; diff --git a/src/Servers/Kestrel/Core/src/Internal/MultiplexedConnectionDispatcher.cs b/src/Servers/Kestrel/Core/src/Internal/MultiplexedConnectionDispatcher.cs new file mode 100644 index 0000000000..e0fe1edbdc --- /dev/null +++ b/src/Servers/Kestrel/Core/src/Internal/MultiplexedConnectionDispatcher.cs @@ -0,0 +1,77 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Connections; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal +{ + internal class MultiplexedConnectionDispatcher + { + private static long _lastConnectionId = long.MinValue; + + private readonly ServiceContext _serviceContext; + private readonly MultiplexedConnectionDelegate _connectionDelegate; + private readonly TaskCompletionSource _acceptLoopTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + + public MultiplexedConnectionDispatcher(ServiceContext serviceContext, MultiplexedConnectionDelegate connectionDelegate) + { + _serviceContext = serviceContext; + _connectionDelegate = connectionDelegate; + } + + private IKestrelTrace Log => _serviceContext.Log; + + public Task StartAcceptingConnections(IMultiplexedConnectionListener listener) + { + ThreadPool.UnsafeQueueUserWorkItem(StartAcceptingConnectionsCore, listener, preferLocal: false); + return _acceptLoopTcs.Task; + } + + private void StartAcceptingConnectionsCore(IMultiplexedConnectionListener listener) + { + // REVIEW: Multiple accept loops in parallel? + _ = AcceptConnectionsAsync(); + + async Task AcceptConnectionsAsync() + { + try + { + while (true) + { + var connection = await listener.AcceptAsync(); + + if (connection == null) + { + // We're done listening + break; + } + + // Add the connection to the connection manager before we queue it for execution + var id = Interlocked.Increment(ref _lastConnectionId); + var kestrelConnection = new KestrelConnection(id, _serviceContext, c => _connectionDelegate(c), connection, Log); + + _serviceContext.ConnectionManager.AddConnection(id, kestrelConnection); + + Log.ConnectionAccepted(connection.ConnectionId); + + ThreadPool.UnsafeQueueUserWorkItem(kestrelConnection, preferLocal: false); + } + } + catch (Exception ex) + { + // REVIEW: If the accept loop ends should this trigger a server shutdown? It will manifest as a hang + Log.LogCritical(0, ex, "The connection listener failed to accept any new connections."); + } + finally + { + _acceptLoopTcs.TrySetResult(null); + } + } + } + } +} diff --git a/src/Servers/Kestrel/Core/src/KestrelServer.cs b/src/Servers/Kestrel/Core/src/KestrelServer.cs index 6eb5ada99d..fdd2b47319 100644 --- a/src/Servers/Kestrel/Core/src/KestrelServer.cs +++ b/src/Servers/Kestrel/Core/src/KestrelServer.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.IO.Pipelines; using System.Linq; +using System.Net; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; @@ -22,27 +23,46 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core public class KestrelServer : IServer { private readonly List<(IConnectionListener, Task)> _transports = new List<(IConnectionListener, Task)>(); + private readonly List<(IMultiplexedConnectionListener, Task)> _multiplexedTransports = new List<(IMultiplexedConnectionListener, Task)>(); private readonly IServerAddressesFeature _serverAddresses; - private readonly IConnectionListenerFactory _transportFactory; + private readonly List _transportFactories; + private readonly List _multiplexedTransportFactories; private bool _hasStarted; private int _stopping; private readonly TaskCompletionSource _stoppedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - public KestrelServer(IOptions options, IConnectionListenerFactory transportFactory, ILoggerFactory loggerFactory) - : this(transportFactory, CreateServiceContext(options, loggerFactory)) + public KestrelServer(IOptions options, IEnumerable transportFactories, ILoggerFactory loggerFactory) + : this(transportFactories, null, CreateServiceContext(options, loggerFactory)) + { + } + public KestrelServer(IOptions options, IEnumerable transportFactories, IEnumerable multiplexedFactories, ILoggerFactory loggerFactory) + : this(transportFactories, multiplexedFactories, CreateServiceContext(options, loggerFactory)) { } // For testing - internal KestrelServer(IConnectionListenerFactory transportFactory, ServiceContext serviceContext) + internal KestrelServer(IEnumerable transportFactories, ServiceContext serviceContext) + : this(transportFactories, null, serviceContext) { - if (transportFactory == null) + } + + // For testing + internal KestrelServer(IEnumerable transportFactories, IEnumerable multiplexedFactories, ServiceContext serviceContext) + { + if (transportFactories == null) { - throw new ArgumentNullException(nameof(transportFactory)); + throw new ArgumentNullException(nameof(transportFactories)); + } + + _transportFactories = transportFactories.ToList(); + _multiplexedTransportFactories = multiplexedFactories?.ToList(); + + if (_transportFactories.Count == 0 && (_multiplexedTransportFactories == null || _multiplexedTransportFactories.Count == 0)) + { + throw new InvalidOperationException(CoreStrings.TransportNotFound); } - _transportFactory = transportFactory; ServiceContext = serviceContext; Features = new FeatureCollection(); @@ -72,6 +92,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core var heartbeatManager = new HeartbeatManager(connectionManager); var dateHeaderValueManager = new DateHeaderValueManager(); + var heartbeat = new Heartbeat( new IHeartbeatHandler[] { dateHeaderValueManager, heartbeatManager }, new SystemClock(), @@ -123,25 +144,52 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core async Task OnBind(ListenOptions options) { - // Add the HTTP middleware as the terminal connection middleware - options.UseHttpServer(ServiceContext, application, options.Protocols); - - var connectionDelegate = options.Build(); - - // Add the connection limit middleware - if (Options.Limits.MaxConcurrentConnections.HasValue) + // INVESTIGATE: For some reason, MsQuic needs to bind before + // sockets for it to successfully listen. It also seems racy. + if ((options.Protocols & HttpProtocols.Http3) == HttpProtocols.Http3) { - connectionDelegate = new ConnectionLimitMiddleware(connectionDelegate, Options.Limits.MaxConcurrentConnections.Value, Trace).OnConnectionAsync; + if (_multiplexedTransportFactories == null || _multiplexedTransportFactories.Count == 0) + { + throw new InvalidOperationException("Cannot start HTTP/3 server if no MultiplexedTransportFactories are registered."); + } + + options.UseHttp3Server(ServiceContext, application, options.Protocols); + var multiplxedConnectionDelegate = ((IMultiplexedConnectionBuilder)options).Build(); + + var multiplexedConnectionDispatcher = new MultiplexedConnectionDispatcher(ServiceContext, multiplxedConnectionDelegate); + var multiplexedFactory = _multiplexedTransportFactories.Last(); + var multiplexedTransport = await multiplexedFactory.BindAsync(options.EndPoint).ConfigureAwait(false); + + var acceptLoopTask = multiplexedConnectionDispatcher.StartAcceptingConnections(multiplexedTransport); + _multiplexedTransports.Add((multiplexedTransport, acceptLoopTask)); + + options.EndPoint = multiplexedTransport.EndPoint; } - var connectionDispatcher = new ConnectionDispatcher(ServiceContext, connectionDelegate); - var transport = await _transportFactory.BindAsync(options.EndPoint).ConfigureAwait(false); + // Add the HTTP middleware as the terminal connection middleware + if ((options.Protocols & HttpProtocols.Http1) == HttpProtocols.Http1 + || (options.Protocols & HttpProtocols.Http2) == HttpProtocols.Http2 + || options.Protocols == HttpProtocols.None) // TODO a test fails because it doesn't throw an exception in the right place + // when there is no HttpProtocols in KestrelServer, can we remove/change the test? + { + options.UseHttpServer(ServiceContext, application, options.Protocols); + var connectionDelegate = options.Build(); - // Update the endpoint - options.EndPoint = transport.EndPoint; - var acceptLoopTask = connectionDispatcher.StartAcceptingConnections(transport); + // Add the connection limit middleware + if (Options.Limits.MaxConcurrentConnections.HasValue) + { + connectionDelegate = new ConnectionLimitMiddleware(connectionDelegate, Options.Limits.MaxConcurrentConnections.Value, Trace).OnConnectionAsync; + } - _transports.Add((transport, acceptLoopTask)); + var connectionDispatcher = new ConnectionDispatcher(ServiceContext, connectionDelegate); + var factory = _transportFactories.Last(); + var transport = await factory.BindAsync(options.EndPoint).ConfigureAwait(false); + + var acceptLoopTask = connectionDispatcher.StartAcceptingConnections(transport); + + _transports.Add((transport, acceptLoopTask)); + options.EndPoint = transport.EndPoint; + } } await AddressBinder.BindAsync(_serverAddresses, Options, Trace, OnBind).ConfigureAwait(false); @@ -165,13 +213,22 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core try { - var tasks = new Task[_transports.Count]; - for (int i = 0; i < _transports.Count; i++) + var connectionTransportCount = _transports.Count; + var totalTransportCount = _transports.Count + _multiplexedTransports.Count; + var tasks = new Task[totalTransportCount]; + + for (int i = 0; i < connectionTransportCount; i++) { (IConnectionListener listener, Task acceptLoop) = _transports[i]; tasks[i] = Task.WhenAll(listener.UnbindAsync(cancellationToken).AsTask(), acceptLoop); } + for (int i = connectionTransportCount; i < totalTransportCount; i++) + { + (IMultiplexedConnectionListener listener, Task acceptLoop) = _multiplexedTransports[i - connectionTransportCount]; + tasks[i] = Task.WhenAll(listener.UnbindAsync(cancellationToken).AsTask(), acceptLoop); + } + await Task.WhenAll(tasks).ConfigureAwait(false); if (!await ConnectionManager.CloseAllConnectionsAsync(cancellationToken).ConfigureAwait(false)) @@ -184,12 +241,18 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core } } - for (int i = 0; i < _transports.Count; i++) + for (int i = 0; i < connectionTransportCount; i++) { (IConnectionListener listener, Task acceptLoop) = _transports[i]; tasks[i] = listener.DisposeAsync().AsTask(); } + for (int i = connectionTransportCount; i < totalTransportCount; i++) + { + (IMultiplexedConnectionListener listener, Task acceptLoop) = _multiplexedTransports[i - connectionTransportCount]; + tasks[i] = listener.DisposeAsync().AsTask(); + } + await Task.WhenAll(tasks).ConfigureAwait(false); ServiceContext.Heartbeat?.Dispose(); diff --git a/src/Servers/Kestrel/Core/src/KestrelServerLimits.cs b/src/Servers/Kestrel/Core/src/KestrelServerLimits.cs index 10db0fe662..46a916e963 100644 --- a/src/Servers/Kestrel/Core/src/KestrelServerLimits.cs +++ b/src/Servers/Kestrel/Core/src/KestrelServerLimits.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; @@ -258,6 +258,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core /// public Http2Limits Http2 { get; } = new Http2Limits(); + /// + /// Limits only applicable to HTTP/3 connections. + /// + public Http3Limits Http3 { get; } = new Http3Limits(); + /// /// Gets or sets the request body minimum data rate in bytes/second. /// Setting this property to null indicates no minimum data rate should be enforced. diff --git a/src/Servers/Kestrel/Core/src/KestrelServerOptions.cs b/src/Servers/Kestrel/Core/src/KestrelServerOptions.cs index 35a5b0bd49..71def30d73 100644 --- a/src/Servers/Kestrel/Core/src/KestrelServerOptions.cs +++ b/src/Servers/Kestrel/Core/src/KestrelServerOptions.cs @@ -72,6 +72,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core /// public KestrelConfigurationLoader ConfigurationLoader { get; set; } + /// + /// Controls whether to return the AltSvcHeader from on an HTTP/2 or lower response for HTTP/3 + /// + public bool EnableAltSvc { get; set; } = false; + /// /// A default configuration action for all endpoints. Use for Listen, configuration, the default url, and URLs. /// diff --git a/src/Servers/Kestrel/Core/src/ListenOptions.cs b/src/Servers/Kestrel/Core/src/ListenOptions.cs index 14e483c403..16a8e05477 100644 --- a/src/Servers/Kestrel/Core/src/ListenOptions.cs +++ b/src/Servers/Kestrel/Core/src/ListenOptions.cs @@ -15,9 +15,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core /// Describes either an , Unix domain socket path, or a file descriptor for an already open /// socket that Kestrel should bind to or open. /// - public class ListenOptions : IConnectionBuilder + public class ListenOptions : IConnectionBuilder, IMultiplexedConnectionBuilder { internal readonly List> _middleware = new List>(); + internal readonly List> _multiplexedMiddleware = new List>(); internal ListenOptions(IPEndPoint endPoint) { @@ -123,6 +124,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core return this; } + IMultiplexedConnectionBuilder IMultiplexedConnectionBuilder.Use(Func middleware) + { + _multiplexedMiddleware.Add(middleware); + return this; + } + public ConnectionDelegate Build() { ConnectionDelegate app = context => @@ -139,6 +146,22 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core return app; } + MultiplexedConnectionDelegate IMultiplexedConnectionBuilder.Build() + { + MultiplexedConnectionDelegate app = context => + { + return Task.CompletedTask; + }; + + for (int i = _multiplexedMiddleware.Count - 1; i >= 0; i--) + { + var component = _multiplexedMiddleware[i]; + app = component(app); + } + + return app; + } + internal virtual async Task BindAsync(AddressBindContext context) { await AddressBinder.BindEndpointAsync(this, context).ConfigureAwait(false); diff --git a/src/Servers/Kestrel/Core/src/Microsoft.AspNetCore.Server.Kestrel.Core.csproj b/src/Servers/Kestrel/Core/src/Microsoft.AspNetCore.Server.Kestrel.Core.csproj index 6c18f1a078..72a38463da 100644 --- a/src/Servers/Kestrel/Core/src/Microsoft.AspNetCore.Server.Kestrel.Core.csproj +++ b/src/Servers/Kestrel/Core/src/Microsoft.AspNetCore.Server.Kestrel.Core.csproj @@ -9,12 +9,18 @@ true CS1591;$(NoWarn) false + $(DefineConstants);KESTREL + + + + + @@ -33,6 +39,14 @@ + + Microsoft.AspNetCore.Server.SharedStrings + + + + System.Net.Http.SR + + diff --git a/src/Servers/Kestrel/Core/src/Middleware/Http3ConnectionMiddleware.cs b/src/Servers/Kestrel/Core/src/Middleware/Http3ConnectionMiddleware.cs new file mode 100644 index 0000000000..c330a69ca6 --- /dev/null +++ b/src/Servers/Kestrel/Core/src/Middleware/Http3ConnectionMiddleware.cs @@ -0,0 +1,44 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Net; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Connections; +using Microsoft.AspNetCore.Connections.Features; +using Microsoft.AspNetCore.Hosting.Server; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3; + +namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal +{ + internal class Http3ConnectionMiddleware + { + private readonly ServiceContext _serviceContext; + private readonly IHttpApplication _application; + + public Http3ConnectionMiddleware(ServiceContext serviceContext, IHttpApplication application) + { + _serviceContext = serviceContext; + _application = application; + } + + public Task OnConnectionAsync(MultiplexedConnectionContext connectionContext) + { + var memoryPoolFeature = connectionContext.Features.Get(); + + var http3ConnectionContext = new Http3ConnectionContext + { + ConnectionId = connectionContext.ConnectionId, + ConnectionContext = connectionContext, + ServiceContext = _serviceContext, + ConnectionFeatures = connectionContext.Features, + MemoryPool = memoryPoolFeature.MemoryPool, + LocalEndPoint = connectionContext.LocalEndPoint as IPEndPoint, + RemoteEndPoint = connectionContext.RemoteEndPoint as IPEndPoint + }; + + var connection = new Http3Connection(http3ConnectionContext); + + return connection.ProcessRequestsAsync(_application); + } + } +} diff --git a/src/Servers/Kestrel/Core/src/Middleware/HttpConnectionBuilderExtensions.cs b/src/Servers/Kestrel/Core/src/Middleware/HttpConnectionBuilderExtensions.cs index e46a2c2a83..09c1ce476c 100644 --- a/src/Servers/Kestrel/Core/src/Middleware/HttpConnectionBuilderExtensions.cs +++ b/src/Servers/Kestrel/Core/src/Middleware/HttpConnectionBuilderExtensions.cs @@ -1,8 +1,6 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; -using System.Collections.Generic; using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Connections; @@ -18,5 +16,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal return middleware.OnConnectionAsync; }); } + + public static IMultiplexedConnectionBuilder UseHttp3Server(this IMultiplexedConnectionBuilder builder, ServiceContext serviceContext, IHttpApplication application, HttpProtocols protocols) + { + var middleware = new Http3ConnectionMiddleware(serviceContext, application); + return builder.Use(next => + { + return middleware.OnConnectionAsync; + }); + } } } diff --git a/src/Servers/Kestrel/Core/src/Middleware/HttpsConnectionMiddleware.cs b/src/Servers/Kestrel/Core/src/Middleware/HttpsConnectionMiddleware.cs index c3f24b4afd..a568f7a02c 100644 --- a/src/Servers/Kestrel/Core/src/Middleware/HttpsConnectionMiddleware.cs +++ b/src/Servers/Kestrel/Core/src/Middleware/HttpsConnectionMiddleware.cs @@ -77,16 +77,20 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Https.Internal } _options = options; - _logger = loggerFactory?.CreateLogger(); - } - public Task OnConnectionAsync(ConnectionContext context) - { - return Task.Run(() => InnerOnConnectionAsync(context)); + _logger = loggerFactory.CreateLogger(); } - private async Task InnerOnConnectionAsync(ConnectionContext context) + public async Task OnConnectionAsync(ConnectionContext context) { + await Task.Yield(); + bool certificateRequired; + if (context.Features.Get() != null) + { + await _next(context); + return; + } + var feature = new Core.Internal.TlsConnectionFeature(); context.Features.Set(feature); context.Features.Set(feature); @@ -156,7 +160,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Https.Internal var sslStream = sslDuplexPipe.Stream; using (var cancellationTokeSource = new CancellationTokenSource(_options.HandshakeTimeout)) - using (cancellationTokeSource.Token.UnsafeRegister(state => ((ConnectionContext)state).Abort(), context)) { try { @@ -201,17 +204,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Https.Internal _options.OnAuthenticate?.Invoke(context, sslOptions); - await sslStream.AuthenticateAsServerAsync(sslOptions, CancellationToken.None); + await sslStream.AuthenticateAsServerAsync(sslOptions, cancellationTokeSource.Token); } catch (OperationCanceledException) { - _logger?.LogDebug(2, CoreStrings.AuthenticationTimedOut); + _logger.LogDebug(2, CoreStrings.AuthenticationTimedOut); await sslStream.DisposeAsync(); return; } catch (IOException ex) { - _logger?.LogDebug(1, ex, CoreStrings.AuthenticationFailed); + _logger.LogDebug(1, ex, CoreStrings.AuthenticationFailed); await sslStream.DisposeAsync(); return; } @@ -221,11 +224,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Https.Internal !CertificateManager.IsHttpsDevelopmentCertificate(_serverCertificate) || CertificateManager.CheckDeveloperCertificateKey(_serverCertificate)) { - _logger?.LogDebug(1, ex, CoreStrings.AuthenticationFailed); + _logger.LogDebug(1, ex, CoreStrings.AuthenticationFailed); } else { - _logger?.LogError(3, ex, CoreStrings.BadDeveloperCertificateState); + _logger.LogError(3, ex, CoreStrings.BadDeveloperCertificateState); } await sslStream.DisposeAsync(); @@ -288,19 +291,5 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Https.Internal return new X509Certificate2(certificate); } - - private class SslDuplexPipe : DuplexPipeStreamAdapter - { - public SslDuplexPipe(IDuplexPipe transport, StreamPipeReaderOptions readerOptions, StreamPipeWriterOptions writerOptions) - : this(transport, readerOptions, writerOptions, s => new SslStream(s)) - { - - } - - public SslDuplexPipe(IDuplexPipe transport, StreamPipeReaderOptions readerOptions, StreamPipeWriterOptions writerOptions, Func factory) : - base(transport, readerOptions, writerOptions, factory) - { - } - } } } diff --git a/src/Servers/Kestrel/Core/src/Middleware/Internal/LoggingStream.cs b/src/Servers/Kestrel/Core/src/Middleware/Internal/LoggingStream.cs index e3fdec3f81..15758f64f4 100644 --- a/src/Servers/Kestrel/Core/src/Middleware/Internal/LoggingStream.cs +++ b/src/Servers/Kestrel/Core/src/Middleware/Internal/LoggingStream.cs @@ -176,78 +176,22 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal // The below APM methods call the underlying Read/WriteAsync methods which will still be logged. public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state) { - var task = ReadAsync(buffer, offset, count, default(CancellationToken), state); - if (callback != null) - { - task.ContinueWith(t => callback.Invoke(t)); - } - return task; + return TaskToApm.Begin(ReadAsync(buffer, offset, count), callback, state); } public override int EndRead(IAsyncResult asyncResult) { - return ((Task)asyncResult).GetAwaiter().GetResult(); - } - - private Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken, object state) - { - var tcs = new TaskCompletionSource(state); - var task = ReadAsync(buffer, offset, count, cancellationToken); - task.ContinueWith((task2, state2) => - { - var tcs2 = (TaskCompletionSource)state2; - if (task2.IsCanceled) - { - tcs2.SetCanceled(); - } - else if (task2.IsFaulted) - { - tcs2.SetException(task2.Exception); - } - else - { - tcs2.SetResult(task2.Result); - } - }, tcs, cancellationToken); - return tcs.Task; + return TaskToApm.End(asyncResult); } public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state) { - var task = WriteAsync(buffer, offset, count, default(CancellationToken), state); - if (callback != null) - { - task.ContinueWith(t => callback.Invoke(t)); - } - return task; + return TaskToApm.Begin(WriteAsync(buffer, offset, count), callback, state); } public override void EndWrite(IAsyncResult asyncResult) { - ((Task)asyncResult).GetAwaiter().GetResult(); - } - - private Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken, object state) - { - var tcs = new TaskCompletionSource(state); - var task = WriteAsync(buffer, offset, count, cancellationToken); - task.ContinueWith((task2, state2) => - { - var tcs2 = (TaskCompletionSource)state2; - if (task2.IsCanceled) - { - tcs2.SetCanceled(); - } - else if (task2.IsFaulted) - { - tcs2.SetException(task2.Exception); - } - else - { - tcs2.SetResult(null); - } - }, tcs, cancellationToken); - return tcs.Task; + TaskToApm.End(asyncResult); } } } diff --git a/src/Servers/Kestrel/Core/test/AddressBinderTests.cs b/src/Servers/Kestrel/Core/test/AddressBinderTests.cs index b5305f5c1d..d4aaee32b7 100644 --- a/src/Servers/Kestrel/Core/test/AddressBinderTests.cs +++ b/src/Servers/Kestrel/Core/test/AddressBinderTests.cs @@ -77,7 +77,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests } [ConditionalFact] - [MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win10_RS4)] + [OSSkipCondition(OperatingSystems.Windows, SkipReason = "tmp/kestrel-test.sock is not valid for windows. Unix socket path must be absolute.")] public void ParseAddressUnixPipe() { var listenOptions = AddressBinder.ParseAddress("http://unix:/tmp/kestrel-test.sock", out var https); @@ -86,6 +86,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests Assert.False(https); } + [ConditionalFact] + [OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX, SkipReason = "Windows has drive letters and volume separator (c:), testing this url on unix or osx provides completely different output.")] + [MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win10_RS4)] + public void ParseAddressUnixPipeOnWindows() + { + var listenOptions = AddressBinder.ParseAddress(@"http://unix:/c:/foo/bar/pipe.socket", out var https); + Assert.IsType(listenOptions.EndPoint); + Assert.Equal("c:/foo/bar/pipe.socket", listenOptions.SocketPath); + Assert.False(https); + } + [Theory] [InlineData("http://10.10.10.10:5000/", "10.10.10.10", 5000, false)] [InlineData("http://[::1]:5000", "::1", 5000, false)] diff --git a/src/Servers/Kestrel/Core/test/ConnectionDispatcherTests.cs b/src/Servers/Kestrel/Core/test/ConnectionDispatcherTests.cs index 1315c24b4b..33bba9c48e 100644 --- a/src/Servers/Kestrel/Core/test/ConnectionDispatcherTests.cs +++ b/src/Servers/Kestrel/Core/test/ConnectionDispatcherTests.cs @@ -29,7 +29,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests var connection = new Mock { CallBase = true }.Object; connection.ConnectionClosed = new CancellationToken(canceled: true); - var kestrelConnection = new KestrelConnection(0, serviceContext, _ => tcs.Task, connection, serviceContext.Log); + var kestrelConnection = new KestrelConnection(0, serviceContext, _ => tcs.Task, connection, serviceContext.Log); serviceContext.ConnectionManager.AddConnection(0, kestrelConnection); var task = kestrelConnection.ExecuteAsync(); @@ -79,7 +79,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests var connection = new Mock { CallBase = true }.Object; connection.ConnectionClosed = new CancellationToken(canceled: true); - var kestrelConnection = new KestrelConnection(0, serviceContext, _ => Task.CompletedTask, connection, serviceContext.Log); + var kestrelConnection = new KestrelConnection(0, serviceContext, _ => Task.CompletedTask, connection, serviceContext.Log); serviceContext.ConnectionManager.AddConnection(0, kestrelConnection); var completeFeature = kestrelConnection.TransportConnection.Features.Get(); @@ -100,7 +100,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests var logger = ((TestKestrelTrace)serviceContext.Log).Logger; var connection = new Mock { CallBase = true }.Object; connection.ConnectionClosed = new CancellationToken(canceled: true); - var kestrelConnection = new KestrelConnection(0, serviceContext, _ => Task.CompletedTask, connection, serviceContext.Log); + var kestrelConnection = new KestrelConnection(0, serviceContext, _ => Task.CompletedTask, connection, serviceContext.Log); serviceContext.ConnectionManager.AddConnection(0, kestrelConnection); var completeFeature = kestrelConnection.TransportConnection.Features.Get(); @@ -114,7 +114,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests Assert.Equal(stateObject, callbackState); var errors = logger.Messages.Where(e => e.LogLevel >= LogLevel.Error).ToArray(); Assert.Single(errors); - Assert.Equal("An error occured running an IConnectionCompleteFeature.OnCompleted callback.", errors[0].Message); + Assert.Equal("An error occurred running an IConnectionCompleteFeature.OnCompleted callback.", errors[0].Message); } private class ThrowingListener : IConnectionListener diff --git a/src/Servers/Kestrel/Core/test/DynamicTableTests.cs b/src/Servers/Kestrel/Core/test/DynamicTableTests.cs deleted file mode 100644 index a7fb8520c6..0000000000 --- a/src/Servers/Kestrel/Core/test/DynamicTableTests.cs +++ /dev/null @@ -1,174 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Linq; -using System.Text; -using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack; -using Xunit; - -namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests -{ - public class DynamicTableTests - { - private readonly HeaderField _header1 = new HeaderField(Encoding.ASCII.GetBytes("header-1"), Encoding.ASCII.GetBytes("value1")); - private readonly HeaderField _header2 = new HeaderField(Encoding.ASCII.GetBytes("header-02"), Encoding.ASCII.GetBytes("value_2")); - - [Fact] - public void DynamicTableIsInitiallyEmpty() - { - var dynamicTable = new DynamicTable(4096); - Assert.Equal(0, dynamicTable.Count); - Assert.Equal(0, dynamicTable.Size); - Assert.Equal(4096, dynamicTable.MaxSize); - } - - [Fact] - public void CountIsNumberOfEntriesInDynamicTable() - { - var dynamicTable = new DynamicTable(4096); - - dynamicTable.Insert(_header1.Name, _header1.Value); - Assert.Equal(1, dynamicTable.Count); - - dynamicTable.Insert(_header2.Name, _header2.Value); - Assert.Equal(2, dynamicTable.Count); - } - - [Fact] - public void SizeIsCurrentDynamicTableSize() - { - var dynamicTable = new DynamicTable(4096); - Assert.Equal(0, dynamicTable.Size); - - dynamicTable.Insert(_header1.Name, _header1.Value); - Assert.Equal(_header1.Length, dynamicTable.Size); - - dynamicTable.Insert(_header2.Name, _header2.Value); - Assert.Equal(_header1.Length + _header2.Length, dynamicTable.Size); - } - - [Fact] - public void FirstEntryIsMostRecentEntry() - { - var dynamicTable = new DynamicTable(4096); - dynamicTable.Insert(_header1.Name, _header1.Value); - dynamicTable.Insert(_header2.Name, _header2.Value); - - VerifyTableEntries(dynamicTable, _header2, _header1); - } - - [Fact] - public void WrapsAroundBuffer() - { - var header3 = new HeaderField(Encoding.ASCII.GetBytes("header-3"), Encoding.ASCII.GetBytes("value3")); - var header4 = new HeaderField(Encoding.ASCII.GetBytes("header-4"), Encoding.ASCII.GetBytes("value4")); - - // Make the table small enough that the circular buffer kicks in. - var dynamicTable = new DynamicTable(HeaderField.RfcOverhead * 3); - dynamicTable.Insert(header4.Name, header4.Value); - dynamicTable.Insert(header3.Name, header3.Value); - dynamicTable.Insert(_header2.Name, _header2.Value); - dynamicTable.Insert(_header1.Name, _header1.Value); - - VerifyTableEntries(dynamicTable, _header1, _header2); - } - - [Fact] - public void ThrowsIndexOutOfRangeException() - { - var dynamicTable = new DynamicTable(4096); - Assert.Throws(() => dynamicTable[0]); - - dynamicTable.Insert(_header1.Name, _header1.Value); - Assert.Throws(() => dynamicTable[1]); - } - - [Fact] - public void NoOpWhenInsertingEntryLargerThanMaxSize() - { - var dynamicTable = new DynamicTable(_header1.Length - 1); - dynamicTable.Insert(_header1.Name, _header1.Value); - - Assert.Equal(0, dynamicTable.Count); - Assert.Equal(0, dynamicTable.Size); - } - - [Fact] - public void NoOpWhenInsertingEntryLargerThanRemainingSpace() - { - var dynamicTable = new DynamicTable(_header1.Length); - dynamicTable.Insert(_header1.Name, _header1.Value); - - VerifyTableEntries(dynamicTable, _header1); - - dynamicTable.Insert(_header2.Name, _header2.Value); - - Assert.Equal(0, dynamicTable.Count); - Assert.Equal(0, dynamicTable.Size); - } - - [Fact] - public void ResizingEvictsOldestEntries() - { - var dynamicTable = new DynamicTable(4096); - dynamicTable.Insert(_header1.Name, _header1.Value); - dynamicTable.Insert(_header2.Name, _header2.Value); - - VerifyTableEntries(dynamicTable, _header2, _header1); - - dynamicTable.Resize(_header2.Length); - - VerifyTableEntries(dynamicTable, _header2); - } - - [Fact] - public void ResizingToZeroEvictsAllEntries() - { - var dynamicTable = new DynamicTable(4096); - dynamicTable.Insert(_header1.Name, _header1.Value); - dynamicTable.Insert(_header2.Name, _header2.Value); - - dynamicTable.Resize(0); - - Assert.Equal(0, dynamicTable.Count); - Assert.Equal(0, dynamicTable.Size); - } - - [Fact] - public void CanBeResizedToLargerMaxSize() - { - var dynamicTable = new DynamicTable(_header1.Length); - dynamicTable.Insert(_header1.Name, _header1.Value); - dynamicTable.Insert(_header2.Name, _header2.Value); - - // _header2 is larger than _header1, so an attempt at inserting it - // would first clear the table then return without actually inserting it, - // given it is larger than the current max size. - Assert.Equal(0, dynamicTable.Count); - Assert.Equal(0, dynamicTable.Size); - - dynamicTable.Resize(dynamicTable.MaxSize + _header2.Length); - dynamicTable.Insert(_header2.Name, _header2.Value); - - VerifyTableEntries(dynamicTable, _header2); - } - - private void VerifyTableEntries(DynamicTable dynamicTable, params HeaderField[] entries) - { - Assert.Equal(entries.Length, dynamicTable.Count); - Assert.Equal(entries.Sum(e => e.Length), dynamicTable.Size); - - for (var i = 0; i < entries.Length; i++) - { - var headerField = dynamicTable[i]; - - Assert.NotSame(entries[i].Name, headerField.Name); - Assert.Equal(entries[i].Name, headerField.Name); - - Assert.NotSame(entries[i].Value, headerField.Value); - Assert.Equal(entries[i].Value, headerField.Value); - } - } - } -} diff --git a/src/Servers/Kestrel/Core/test/HPackEncoderTests.cs b/src/Servers/Kestrel/Core/test/HPackHeaderWriterTests.cs similarity index 83% rename from src/Servers/Kestrel/Core/test/HPackEncoderTests.cs rename to src/Servers/Kestrel/Core/test/HPackHeaderWriterTests.cs index 57ee0ba9a4..3b290d712b 100644 --- a/src/Servers/Kestrel/Core/test/HPackEncoderTests.cs +++ b/src/Servers/Kestrel/Core/test/HPackHeaderWriterTests.cs @@ -3,12 +3,14 @@ using System; using System.Collections.Generic; -using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack; +using System.Linq; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2; +using Microsoft.Extensions.Primitives; using Xunit; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests { - public class HPackEncoderTests + public class HPackHeaderWriterTests { public static TheoryData[], byte[], int?> SinglePayloadData { @@ -89,16 +91,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [MemberData(nameof(SinglePayloadData))] public void EncodesHeadersInSinglePayloadWhenSpaceAvailable(KeyValuePair[] headers, byte[] expectedPayload, int? statusCode) { - var encoder = new HPackEncoder(); var payload = new byte[1024]; var length = 0; if (statusCode.HasValue) { - Assert.True(encoder.BeginEncode(statusCode.Value, headers, payload, out length)); + Assert.True(HPackHeaderWriter.BeginEncodeHeaders(statusCode.Value, GetHeadersEnumerator(headers), payload, out length)); } else { - Assert.True(encoder.BeginEncode(headers, payload, out length)); + Assert.True(HPackHeaderWriter.BeginEncodeHeaders(GetHeadersEnumerator(headers), payload, out length)); } Assert.Equal(expectedPayload.Length, length); @@ -115,10 +116,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [InlineData(false)] public void EncodesHeadersInMultiplePayloadsWhenSpaceNotAvailable(bool exactSize) { - var encoder = new HPackEncoder(); - var statusCode = 200; - var headers = new [] + var headers = new[] { new KeyValuePair("date", "Mon, 24 Jul 2017 19:22:30 GMT"), new KeyValuePair("content-type", "text/html; charset=utf-8"), @@ -156,33 +155,45 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests Span payload = new byte[1024]; var offset = 0; + var headerEnumerator = GetHeadersEnumerator(headers); // When !exactSize, slices are one byte short of fitting the next header var sliceLength = expectedStatusCodePayload.Length + (exactSize ? 0 : expectedDateHeaderPayload.Length - 1); - Assert.False(encoder.BeginEncode(statusCode, headers, payload.Slice(offset, sliceLength), out var length)); + Assert.False(HPackHeaderWriter.BeginEncodeHeaders(statusCode, headerEnumerator, payload.Slice(offset, sliceLength), out var length)); Assert.Equal(expectedStatusCodePayload.Length, length); Assert.Equal(expectedStatusCodePayload, payload.Slice(0, length).ToArray()); offset += length; sliceLength = expectedDateHeaderPayload.Length + (exactSize ? 0 : expectedContentTypeHeaderPayload.Length - 1); - Assert.False(encoder.Encode(payload.Slice(offset, sliceLength), out length)); + Assert.False(HPackHeaderWriter.ContinueEncodeHeaders(headerEnumerator, payload.Slice(offset, sliceLength), out length)); Assert.Equal(expectedDateHeaderPayload.Length, length); Assert.Equal(expectedDateHeaderPayload, payload.Slice(offset, length).ToArray()); offset += length; sliceLength = expectedContentTypeHeaderPayload.Length + (exactSize ? 0 : expectedServerHeaderPayload.Length - 1); - Assert.False(encoder.Encode(payload.Slice(offset, sliceLength), out length)); + Assert.False(HPackHeaderWriter.ContinueEncodeHeaders(headerEnumerator, payload.Slice(offset, sliceLength), out length)); Assert.Equal(expectedContentTypeHeaderPayload.Length, length); Assert.Equal(expectedContentTypeHeaderPayload, payload.Slice(offset, length).ToArray()); offset += length; sliceLength = expectedServerHeaderPayload.Length; - Assert.True(encoder.Encode(payload.Slice(offset, sliceLength), out length)); + Assert.True(HPackHeaderWriter.ContinueEncodeHeaders(headerEnumerator, payload.Slice(offset, sliceLength), out length)); Assert.Equal(expectedServerHeaderPayload.Length, length); Assert.Equal(expectedServerHeaderPayload, payload.Slice(offset, length).ToArray()); } + + private static Http2HeadersEnumerator GetHeadersEnumerator(IEnumerable> headers) + { + var groupedHeaders = headers + .GroupBy(k => k.Key) + .ToDictionary(g => g.Key, g => new StringValues(g.Select(gg => gg.Value).ToArray())); + + var enumerator = new Http2HeadersEnumerator(); + enumerator.Initialize(groupedHeaders); + return enumerator; + } } } diff --git a/src/Servers/Kestrel/Core/test/HPackIntegerTests.cs b/src/Servers/Kestrel/Core/test/HPackIntegerTests.cs deleted file mode 100644 index 4448463dd4..0000000000 --- a/src/Servers/Kestrel/Core/test/HPackIntegerTests.cs +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Linq; -using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack; -using Xunit; - -namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests -{ - public class HPackIntegerTests - { - [Fact] - public void IntegerEncoderDecoderRoundtrips() - { - var decoder = new IntegerDecoder(); - var range = 1 << 8; - - foreach (var i in Enumerable.Range(0, range).Concat(Enumerable.Range(int.MaxValue - range + 1, range))) - { - for (int n = 1; n <= 8; n++) - { - var integerBytes = new byte[6]; - Assert.True(IntegerEncoder.Encode(i, n, integerBytes, out var length)); - - var decodeResult = decoder.BeginTryDecode(integerBytes[0], n, out var intResult); - - for (int j = 1; j < length; j++) - { - Assert.False(decodeResult); - decodeResult = decoder.TryDecode(integerBytes[j], out intResult); - } - - Assert.True(decodeResult); - Assert.Equal(i, intResult); - } - } - } - - [Theory] - [MemberData(nameof(IntegerCodecSamples))] - public void EncodeSamples(int value, int bits, byte[] expectedResult) - { - Span actualResult = new byte[64]; - bool success = IntegerEncoder.Encode(value, bits, actualResult, out int bytesWritten); - - Assert.True(success); - Assert.Equal(expectedResult.Length, bytesWritten); - Assert.True(actualResult.Slice(0, bytesWritten).SequenceEqual(expectedResult)); - } - - [Theory] - [MemberData(nameof(IntegerCodecSamples))] - public void EncodeSamplesWithShortBuffer(int value, int bits, byte[] expectedResult) - { - Span actualResult = new byte[expectedResult.Length - 1]; - bool success = IntegerEncoder.Encode(value, bits, actualResult, out int bytesWritten); - - Assert.False(success); - } - - [Theory] - [MemberData(nameof(IntegerCodecSamples))] - public void DecodeSamples(int expectedResult, int bits, byte[] encoded) - { - var integerDecoder = new IntegerDecoder(); - - bool finished = integerDecoder.BeginTryDecode(encoded[0], bits, out int actualResult); - - int i = 1; - for (; !finished && i < encoded.Length; ++i) - { - finished = integerDecoder.TryDecode(encoded[i], out actualResult); - } - - Assert.True(finished); - Assert.Equal(encoded.Length, i); - - Assert.Equal(expectedResult, actualResult); - } - - // integer, prefix length, encoded - public static IEnumerable IntegerCodecSamples() - { - yield return new object[] { 10, 5, new byte[] { 0x0A } }; - yield return new object[] { 1337, 5, new byte[] { 0x1F, 0x9A, 0x0A } }; - yield return new object[] { 42, 8, new byte[] { 0x2A } }; - } - } -} diff --git a/src/Servers/Kestrel/Core/test/HeartbeatTests.cs b/src/Servers/Kestrel/Core/test/HeartbeatTests.cs index f0d4485b3e..bb4e896b44 100644 --- a/src/Servers/Kestrel/Core/test/HeartbeatTests.cs +++ b/src/Servers/Kestrel/Core/test/HeartbeatTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; @@ -54,7 +54,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await blockedHeartbeatTask.DefaultTimeout(); heartbeatHandler.Verify(h => h.OnHeartbeat(systemClock.UtcNow), Times.Once()); - kestrelTrace.Verify(t => t.HeartbeatSlow(Heartbeat.Interval, systemClock.UtcNow), Times.Once()); + kestrelTrace.Verify(t => t.HeartbeatSlow(TimeSpan.Zero, Heartbeat.Interval, systemClock.UtcNow), Times.Once()); } [Fact] @@ -91,7 +91,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await blockedHeartbeatTask.DefaultTimeout(); heartbeatHandler.Verify(h => h.OnHeartbeat(systemClock.UtcNow), Times.Once()); - kestrelTrace.Verify(t => t.HeartbeatSlow(Heartbeat.Interval, systemClock.UtcNow), Times.Never()); + kestrelTrace.Verify(t => t.HeartbeatSlow(TimeSpan.Zero, Heartbeat.Interval, systemClock.UtcNow), Times.Never()); } [Fact] diff --git a/src/Servers/Kestrel/Core/test/Http1ConnectionTests.cs b/src/Servers/Kestrel/Core/test/Http1ConnectionTests.cs index f602d09798..0992626a80 100644 --- a/src/Servers/Kestrel/Core/test/Http1ConnectionTests.cs +++ b/src/Servers/Kestrel/Core/test/Http1ConnectionTests.cs @@ -69,7 +69,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests }; _http1Connection = new TestHttp1Connection(_http1ConnectionContext); - _http1Connection.Reset(); } public void Dispose() @@ -751,7 +750,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests _http1Connection.Abort(new ConnectionAbortedException()); // The following line will throw an ODE because the original CTS backing the token has been diposed. - // See https://github.com/aspnet/AspNetCore/pull/4447 for the history behind this test. + // See https://github.com/dotnet/aspnetcore/pull/4447 for the history behind this test. //Assert.True(originalToken.WaitHandle.WaitOne(TestConstants.DefaultTimeout)); Assert.True(_http1Connection.RequestAborted.WaitHandle.WaitOne(TestConstants.DefaultTimeout)); diff --git a/src/Servers/Kestrel/Core/test/Http2HeadersEnumeratorTests.cs b/src/Servers/Kestrel/Core/test/Http2HeadersEnumeratorTests.cs new file mode 100644 index 0000000000..8aed0c4498 --- /dev/null +++ b/src/Servers/Kestrel/Core/test/Http2HeadersEnumeratorTests.cs @@ -0,0 +1,88 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2; +using Microsoft.Extensions.Primitives; +using Xunit; + +namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests +{ + public class Http2HeadersEnumeratorTests + { + [Fact] + public void CanIterateOverResponseHeaders() + { + var responseHeaders = new HttpResponseHeaders + { + ContentLength = 9, + HeaderAcceptRanges = "AcceptRanges!", + HeaderAge = new StringValues(new[] { "1", "2" }), + HeaderDate = "Date!" + }; + responseHeaders.Append("Name1", "Value1"); + responseHeaders.Append("Name2", "Value2-1"); + responseHeaders.Append("Name2", "Value2-2"); + responseHeaders.Append("Name3", "Value3"); + + var e = new Http2HeadersEnumerator(); + e.Initialize(responseHeaders); + + var headers = GetNormalizedHeaders(e); + + Assert.Equal(new[] + { + new KeyValuePair("Date", "Date!"), + new KeyValuePair("Accept-Ranges", "AcceptRanges!"), + new KeyValuePair("Age", "1"), + new KeyValuePair("Age", "2"), + new KeyValuePair("Content-Length", "9"), + new KeyValuePair("Name1", "Value1"), + new KeyValuePair("Name2", "Value2-1"), + new KeyValuePair("Name2", "Value2-2"), + new KeyValuePair("Name3", "Value3"), + }, headers); + } + + [Fact] + public void CanIterateOverResponseTrailers() + { + var responseHeaders = new HttpResponseTrailers + { + ContentLength = 9, + HeaderETag = "ETag!" + }; + responseHeaders.Append("Name1", "Value1"); + responseHeaders.Append("Name2", "Value2-1"); + responseHeaders.Append("Name2", "Value2-2"); + responseHeaders.Append("Name3", "Value3"); + + var e = new Http2HeadersEnumerator(); + e.Initialize(responseHeaders); + + var headers = GetNormalizedHeaders(e); + + Assert.Equal(new[] + { + new KeyValuePair("ETag", "ETag!"), + new KeyValuePair("Name1", "Value1"), + new KeyValuePair("Name2", "Value2-1"), + new KeyValuePair("Name2", "Value2-2"), + new KeyValuePair("Name3", "Value3"), + }, headers); + } + + private KeyValuePair[] GetNormalizedHeaders(Http2HeadersEnumerator enumerator) + { + var headers = new List>(); + while (enumerator.MoveNext()) + { + headers.Add(enumerator.Current); + } + return headers.ToArray(); + } + } +} diff --git a/src/Servers/Kestrel/Core/test/HttpConnectionManagerTests.cs b/src/Servers/Kestrel/Core/test/HttpConnectionManagerTests.cs index 815fe5019a..42867902ff 100644 --- a/src/Servers/Kestrel/Core/test/HttpConnectionManagerTests.cs +++ b/src/Servers/Kestrel/Core/test/HttpConnectionManagerTests.cs @@ -44,7 +44,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests var serviceContext = new TestServiceContext(); var mock = new Mock() { CallBase = true }; mock.Setup(m => m.ConnectionId).Returns(connectionId); - var httpConnection = new KestrelConnection(0, serviceContext, _ => Task.CompletedTask, mock.Object, Mock.Of()); + var httpConnection = new KestrelConnection(0, serviceContext, _ => Task.CompletedTask, mock.Object, Mock.Of()); httpConnectionManager.AddConnection(0, httpConnection); diff --git a/src/Servers/Kestrel/Core/test/HttpParserTests.cs b/src/Servers/Kestrel/Core/test/HttpParserTests.cs index 7ce8587743..1b2646d16b 100644 --- a/src/Servers/Kestrel/Core/test/HttpParserTests.cs +++ b/src/Servers/Kestrel/Core/test/HttpParserTests.cs @@ -1,10 +1,11 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Buffers; using System.Collections.Generic; using System.Linq; +using System.Net.Http; using System.Text; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; @@ -13,6 +14,7 @@ using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.Logging; using Moq; using Xunit; +using HttpMethod = Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpMethod; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests { @@ -394,6 +396,23 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests Assert.Equal(buffer.End, examined); } + [Fact] + public void ParseRequestLineTlsOverHttp() + { + var parser = CreateParser(_nullTrace); + var buffer = ReadOnlySequenceFactory.CreateSegments(new byte[] { 0x16, 0x03, 0x01, 0x02, 0x00, 0x01, 0x00, 0xfc, 0x03, 0x03, 0x03, 0xca, 0xe0, 0xfd, 0x0a }); + + var requestHandler = new RequestHandler(); + + var badHttpRequestException = Assert.Throws(() => + { + parser.ParseRequestLine(requestHandler, buffer, out var consumed, out var examined); + }); + + Assert.Equal(badHttpRequestException.StatusCode, StatusCodes.Status400BadRequest); + Assert.Equal(RequestRejectionReason.TlsOverHttpError, badHttpRequestException.Reason); + } + [Fact] public void ParseHeadersWithGratuitouslySplitBuffers() { @@ -488,12 +507,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests public Dictionary Headers { get; } = new Dictionary(); - public void OnHeader(Span name, Span value) + public void OnHeader(ReadOnlySpan name, ReadOnlySpan value) { Headers[name.GetAsciiStringNonNullCharacters()] = value.GetAsciiStringNonNullCharacters(); } - void IHttpHeadersHandler.OnHeadersComplete() { } + void IHttpHeadersHandler.OnHeadersComplete(bool endStream) { } public void OnStartLine(HttpMethod method, HttpVersion version, Span target, Span path, Span query, Span customMethod, bool pathEncoded) { @@ -504,6 +523,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests Query = query.GetAsciiStringNonNullCharacters(); PathEncoded = pathEncoded; } + + public void OnStaticIndexedHeader(int index) + { + throw new NotImplementedException(); + } + + public void OnStaticIndexedHeader(int index, ReadOnlySpan value) + { + throw new NotImplementedException(); + } } // Doesn't put empty blocks in between every byte diff --git a/src/Servers/Kestrel/Core/test/HttpProtocolFeatureCollectionTests.cs b/src/Servers/Kestrel/Core/test/HttpProtocolFeatureCollectionTests.cs index 030931dae4..f62bad9117 100644 --- a/src/Servers/Kestrel/Core/test/HttpProtocolFeatureCollectionTests.cs +++ b/src/Servers/Kestrel/Core/test/HttpProtocolFeatureCollectionTests.cs @@ -249,8 +249,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests private class TestHttp2Stream : Http2Stream { - public TestHttp2Stream(Http2StreamContext context) : base(context) + public TestHttp2Stream(Http2StreamContext context) { + Initialize(context); } public override void Execute() diff --git a/src/Servers/Kestrel/Core/test/HttpResponseHeadersTests.cs b/src/Servers/Kestrel/Core/test/HttpResponseHeadersTests.cs index ed3c492e3c..1bbe06e99c 100644 --- a/src/Servers/Kestrel/Core/test/HttpResponseHeadersTests.cs +++ b/src/Servers/Kestrel/Core/test/HttpResponseHeadersTests.cs @@ -138,6 +138,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests Assert.Throws(() => ((IDictionary)headers).Add("my-header", new[] { "value" })); } + [Fact] + public void ThrowsWhenSettingContentLengthPropertyAfterReadOnlyIsSet() + { + var headers = new HttpResponseHeaders(); + headers.SetReadOnly(); + + Assert.Throws(() => headers.ContentLength = null); + } + [Fact] public void ThrowsWhenChangingHeaderAfterReadOnlyIsSet() { diff --git a/src/Servers/Kestrel/Core/test/HttpUtilitiesTest.cs b/src/Servers/Kestrel/Core/test/HttpUtilitiesTest.cs index 9b58a1e878..e8778dc390 100644 --- a/src/Servers/Kestrel/Core/test/HttpUtilitiesTest.cs +++ b/src/Servers/Kestrel/Core/test/HttpUtilitiesTest.cs @@ -52,8 +52,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests } [Theory] - [InlineData("HTTP/1.0\r", true, HttpUtilities.Http10Version, (int)HttpVersion.Http10)] - [InlineData("HTTP/1.1\r", true, HttpUtilities.Http11Version, (int)HttpVersion.Http11)] + [InlineData("HTTP/1.0\r", true, "HTTP/1.0", (int)HttpVersion.Http10)] + [InlineData("HTTP/1.1\r", true, "HTTP/1.1", (int)HttpVersion.Http11)] [InlineData("HTTP/3.0\r", false, null, (int)HttpVersion.Unknown)] [InlineData("http/1.0\r", false, null, (int)HttpVersion.Unknown)] [InlineData("http/1.1\r", false, null, (int)HttpVersion.Unknown)] diff --git a/src/Servers/Kestrel/Core/test/IntegerDecoderTests.cs b/src/Servers/Kestrel/Core/test/IntegerDecoderTests.cs deleted file mode 100644 index fdebb56922..0000000000 --- a/src/Servers/Kestrel/Core/test/IntegerDecoderTests.cs +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack; -using Xunit; - -namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests -{ - public class IntegerDecoderTests - { - [Theory] - [MemberData(nameof(IntegerData))] - public void IntegerDecode(int i, int prefixLength, byte[] octets) - { - var decoder = new IntegerDecoder(); - var result = decoder.BeginTryDecode(octets[0], prefixLength, out var intResult); - - if (octets.Length == 1) - { - Assert.True(result); - } - else - { - var j = 1; - - for (; j < octets.Length - 1; j++) - { - Assert.False(decoder.TryDecode(octets[j], out intResult)); - } - - Assert.True(decoder.TryDecode(octets[j], out intResult)); - } - - Assert.Equal(i, intResult); - } - - [Theory] - [MemberData(nameof(IntegerData_OverMax))] - public void IntegerDecode_Throws_IfMaxExceeded(int prefixLength, byte[] octets) - { - var decoder = new IntegerDecoder(); - var result = decoder.BeginTryDecode(octets[0], prefixLength, out var intResult); - - for (var j = 1; j < octets.Length - 1; j++) - { - Assert.False(decoder.TryDecode(octets[j], out intResult)); - } - - Assert.Throws(() => decoder.TryDecode(octets[octets.Length - 1], out intResult)); - } - - public static TheoryData IntegerData - { - get - { - var data = new TheoryData(); - - data.Add(10, 5, new byte[] { 10 }); - data.Add(1337, 5, new byte[] { 0x1f, 0x9a, 0x0a }); - data.Add(42, 8, new byte[] { 42 }); - data.Add(7, 3, new byte[] { 0x7, 0x0 }); - data.Add(int.MaxValue, 1, new byte[] { 0x01, 0xfe, 0xff, 0xff, 0xff, 0x07 }); - data.Add(int.MaxValue, 8, new byte[] { 0xff, 0x80, 0xfe, 0xff, 0xff, 0x07 }); - - return data; - } - } - - public static TheoryData IntegerData_OverMax - { - get - { - var data = new TheoryData(); - - data.Add(1, new byte[] { 0x01, 0xff, 0xff, 0xff, 0xff, 0x07 }); // Int32.MaxValue + 1 - data.Add(1, new byte[] { 0x01, 0xff, 0xff, 0xff, 0xff, 0x08 }); // MSB exceeds maximum - data.Add(1, new byte[] { 0x01, 0xff, 0xff, 0xff, 0xff, 0x80 }); // Undefined since continuation bit set - data.Add(8, new byte[] { 0xff, 0x81, 0xfe, 0xff, 0xff, 0x07 }); // Int32.MaxValue + 1 - data.Add(8, new byte[] { 0xff, 0x81, 0xfe, 0xff, 0xff, 0x08 }); // MSB exceeds maximum - data.Add(8, new byte[] { 0xff, 0x81, 0xfe, 0xff, 0xff, 0x80 }); // Undefined since continuation bit set - - return data; - } - } - } -} diff --git a/src/Servers/Kestrel/Core/test/IntegerEncoderTests.cs b/src/Servers/Kestrel/Core/test/IntegerEncoderTests.cs deleted file mode 100644 index c667cc6cee..0000000000 --- a/src/Servers/Kestrel/Core/test/IntegerEncoderTests.cs +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack; -using Xunit; - -namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests -{ - public class IntegerEncoderTests - { - [Theory] - [MemberData(nameof(IntegerData))] - public void IntegerEncode(int i, int prefixLength, byte[] expectedOctets) - { - var buffer = new byte[expectedOctets.Length]; - - Assert.True(IntegerEncoder.Encode(i, prefixLength, buffer, out var octets)); - Assert.Equal(expectedOctets.Length, octets); - Assert.Equal(expectedOctets, buffer); - } - - public static TheoryData IntegerData - { - get - { - var data = new TheoryData(); - - data.Add(10, 5, new byte[] { 10 }); - data.Add(1337, 5, new byte[] { 0x1f, 0x9a, 0x0a }); - data.Add(42, 8, new byte[] { 42 }); - - return data; - } - } - } -} diff --git a/src/Servers/Kestrel/Core/test/KestrelServerTests.cs b/src/Servers/Kestrel/Core/test/KestrelServerTests.cs index 36add68a21..56454bb7a4 100644 --- a/src/Servers/Kestrel/Core/test/KestrelServerTests.cs +++ b/src/Servers/Kestrel/Core/test/KestrelServerTests.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; using System.Linq; using System.Net; using System.Threading; @@ -203,20 +204,43 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests var mockLoggerFactory = new Mock(); var mockLogger = new Mock(); mockLoggerFactory.Setup(m => m.CreateLogger(It.IsAny())).Returns(mockLogger.Object); - new KestrelServer(Options.Create(null), Mock.Of(), mockLoggerFactory.Object); + new KestrelServer(Options.Create(null), new List() { new MockTransportFactory() }, mockLoggerFactory.Object); mockLoggerFactory.Verify(factory => factory.CreateLogger("Microsoft.AspNetCore.Server.Kestrel")); } [Fact] - public void StartWithNoTransportFactoryThrows() + public void ConstructorWithNullTransportFactoriesThrows() { - var mockLoggerFactory = new Mock(); - var mockLogger = new Mock(); - mockLoggerFactory.Setup(m => m.CreateLogger(It.IsAny())).Returns(mockLogger.Object); var exception = Assert.Throws(() => - new KestrelServer(Options.Create(null), null, mockLoggerFactory.Object)); + new KestrelServer( + Options.Create(null), + null, + new LoggerFactory(new[] { new KestrelTestLoggerProvider() }))); - Assert.Equal("transportFactory", exception.ParamName); + Assert.Equal("transportFactories", exception.ParamName); + } + + [Fact] + public void ConstructorWithNoTransportFactoriesThrows() + { + var exception = Assert.Throws(() => + new KestrelServer( + Options.Create(null), + new List(), + new LoggerFactory(new[] { new KestrelTestLoggerProvider() }))); + + Assert.Equal(CoreStrings.TransportNotFound, exception.Message); + } + + [Fact] + public void StartWithMultipleTransportFactoriesDoesNotThrow() + { + using var server = new KestrelServer( + Options.Create(CreateServerOptions()), + new List() { new ThrowingTransportFactory(), new MockTransportFactory() }, + new LoggerFactory(new[] { new KestrelTestLoggerProvider() })); + + StartDummyApplication(server); } [Fact] @@ -257,7 +281,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests var mockLoggerFactory = new Mock(); var mockLogger = new Mock(); mockLoggerFactory.Setup(m => m.CreateLogger(It.IsAny())).Returns(mockLogger.Object); - var server = new KestrelServer(Options.Create(options), mockTransportFactory.Object, mockLoggerFactory.Object); + var server = new KestrelServer(Options.Create(options), new List() { mockTransportFactory.Object }, mockLoggerFactory.Object); await server.StartAsync(new DummyApplication(), CancellationToken.None); var stopTask1 = server.StopAsync(default); @@ -315,7 +339,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests var mockLoggerFactory = new Mock(); var mockLogger = new Mock(); mockLoggerFactory.Setup(m => m.CreateLogger(It.IsAny())).Returns(mockLogger.Object); - var server = new KestrelServer(Options.Create(options), mockTransportFactory.Object, mockLoggerFactory.Object); + var server = new KestrelServer(Options.Create(options), new List() { mockTransportFactory.Object }, mockLoggerFactory.Object); await server.StartAsync(new DummyApplication(), CancellationToken.None); var stopTask1 = server.StopAsync(default); @@ -370,7 +394,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests var mockLoggerFactory = new Mock(); var mockLogger = new Mock(); mockLoggerFactory.Setup(m => m.CreateLogger(It.IsAny())).Returns(mockLogger.Object); - var server = new KestrelServer(Options.Create(options), mockTransportFactory.Object, mockLoggerFactory.Object); + var server = new KestrelServer(Options.Create(options), new List() { mockTransportFactory.Object }, mockLoggerFactory.Object); await server.StartAsync(new DummyApplication(), default); var stopTask1 = server.StopAsync(default); @@ -416,7 +440,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests DebuggerWrapper.Singleton, testContext.Log); - using (var server = new KestrelServer(new MockTransportFactory(), testContext)) + using (var server = new KestrelServer(new List() { new MockTransportFactory() }, testContext)) { Assert.Null(testContext.DateHeaderValueManager.GetDateHeaderValues()); @@ -433,12 +457,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests private static KestrelServer CreateServer(KestrelServerOptions options, ILogger testLogger) { - return new KestrelServer(Options.Create(options), new MockTransportFactory(), new LoggerFactory(new[] { new KestrelTestLoggerProvider(testLogger) })); + return new KestrelServer(Options.Create(options), new List() { new MockTransportFactory() }, new LoggerFactory(new[] { new KestrelTestLoggerProvider(testLogger) })); } private static KestrelServer CreateServer(KestrelServerOptions options, bool throwOnCriticalErrors = true) { - return new KestrelServer(Options.Create(options), new MockTransportFactory(), new LoggerFactory(new[] { new KestrelTestLoggerProvider(throwOnCriticalErrors) })); + return new KestrelServer(Options.Create(options), new List() { new MockTransportFactory() }, new LoggerFactory(new[] { new KestrelTestLoggerProvider(throwOnCriticalErrors) })); } private static void StartDummyApplication(IServer server) @@ -455,5 +479,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests return new ValueTask(mock.Object); } } + + private class ThrowingTransportFactory : IConnectionListenerFactory + { + public ValueTask BindAsync(EndPoint endpoint, CancellationToken cancellationToken = default) + { + throw new InvalidOperationException(); + } + } } } diff --git a/src/Servers/Kestrel/Core/test/MessageBodyTests.cs b/src/Servers/Kestrel/Core/test/MessageBodyTests.cs index 7461bb8eab..b44caa62d4 100644 --- a/src/Servers/Kestrel/Core/test/MessageBodyTests.cs +++ b/src/Servers/Kestrel/Core/test/MessageBodyTests.cs @@ -810,6 +810,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests using (var input = new TestInput()) { var mockLogger = new Mock(); + mockLogger + .Setup(logger => logger.IsEnabled(Extensions.Logging.LogLevel.Debug)) + .Returns(true); input.Http1Connection.ServiceContext.Log = mockLogger.Object; input.Http1Connection.ConnectionIdFeature = "ConnectionId"; input.Http1Connection.TraceIdentifier = "RequestId"; @@ -841,6 +844,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests mockLogger .Setup(logger => logger.RequestBodyDone("ConnectionId", "RequestId")) .Callback(() => logEvent.SetResult(null)); + mockLogger + .Setup(logger => logger.IsEnabled(Extensions.Logging.LogLevel.Debug)) + .Returns(true); input.Http1Connection.ServiceContext.Log = mockLogger.Object; input.Http1Connection.ConnectionIdFeature = "ConnectionId"; input.Http1Connection.TraceIdentifier = "RequestId"; diff --git a/src/Servers/Kestrel/Core/test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests.csproj b/src/Servers/Kestrel/Core/test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests.csproj index 0497c91342..108edc514d 100644 --- a/src/Servers/Kestrel/Core/test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests.csproj +++ b/src/Servers/Kestrel/Core/test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests.csproj @@ -4,6 +4,7 @@ $(DefaultNetCoreTargetFramework) true Kestrel.Core.Tests + $(DefineConstants);KESTREL @@ -13,8 +14,8 @@ - + diff --git a/src/Servers/Kestrel/Core/test/PipelineExtensionTests.cs b/src/Servers/Kestrel/Core/test/PipelineExtensionTests.cs index ebf57aebeb..d95b5c18db 100644 --- a/src/Servers/Kestrel/Core/test/PipelineExtensionTests.cs +++ b/src/Servers/Kestrel/Core/test/PipelineExtensionTests.cs @@ -87,7 +87,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests { var pipeWriter = _pipe.Writer; var writer = new BufferWriter(pipeWriter); - writer.WriteAsciiNoValidation(input); + writer.WriteAscii(input); writer.Commit(); pipeWriter.FlushAsync().GetAwaiter().GetResult(); pipeWriter.Complete(); @@ -111,13 +111,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [InlineData("𤭢𐐝")] // non-ascii characters stored in 16 bits [InlineData("ñ٢⛄⛵")] - public void WriteAsciiNoValidationWritesOnlyOneBytePerChar(string input) + public void WriteAsciiWritesOnlyOneBytePerChar(string input) { // WriteAscii doesn't validate if characters are in the ASCII range // but it shouldn't produce more than one byte per character var writerBuffer = _pipe.Writer; var writer = new BufferWriter(writerBuffer); - writer.WriteAsciiNoValidation(input); + writer.WriteAscii(input); writer.Commit(); writerBuffer.FlushAsync().GetAwaiter().GetResult(); var reader = _pipe.Reader.ReadAsync().GetAwaiter().GetResult(); @@ -126,14 +126,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests } [Fact] - public void WriteAsciiNoValidation() + public void WriteAscii() { const byte maxAscii = 0x7f; var writerBuffer = _pipe.Writer; var writer = new BufferWriter(writerBuffer); for (var i = 0; i < maxAscii; i++) { - writer.WriteAsciiNoValidation(new string((char)i, 1)); + writer.WriteAscii(new string((char)i, 1)); } writer.Commit(); writerBuffer.FlushAsync().GetAwaiter().GetResult(); @@ -167,7 +167,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests Assert.Equal(gapSize, writer.Span.Length); var bufferLength = writer.Span.Length; - writer.WriteAsciiNoValidation(testString); + writer.WriteAscii(testString); Assert.NotEqual(bufferLength, writer.Span.Length); writer.Commit(); writerBuffer.FlushAsync().GetAwaiter().GetResult(); diff --git a/src/Servers/Kestrel/Core/test/TimeoutControlTests.cs b/src/Servers/Kestrel/Core/test/TimeoutControlTests.cs index 170d7b1479..9477729e3a 100644 --- a/src/Servers/Kestrel/Core/test/TimeoutControlTests.cs +++ b/src/Servers/Kestrel/Core/test/TimeoutControlTests.cs @@ -328,7 +328,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests // Read 0 bytes in 1 second now += TimeSpan.FromSeconds(1); - _timeoutControl.Tick(now);; + _timeoutControl.Tick(now); // Timed out _mockTimeoutHandler.Verify(h => h.OnTimeout(TimeoutReason.ReadDataRate), Times.Once); diff --git a/src/Servers/Kestrel/Core/test/UTF8Decoding.cs b/src/Servers/Kestrel/Core/test/UTF8Decoding.cs index 4c4e377400..35043d8322 100644 --- a/src/Servers/Kestrel/Core/test/UTF8Decoding.cs +++ b/src/Servers/Kestrel/Core/test/UTF8Decoding.cs @@ -17,7 +17,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [InlineData(new byte[] { 0xef, 0xbf, 0xbd })] // 3 bytes: Replacement character, highest UTF-8 character currently encoded in the UTF-8 code page private void FullUTF8RangeSupported(byte[] encodedBytes) { - var s = encodedBytes.AsSpan().GetRequestHeaderStringNonNullCharacters(useLatin1: false); + var s = HttpUtilities.GetRequestHeaderStringNonNullCharacters(encodedBytes.AsSpan(), useLatin1: false); Assert.Equal(1, s.Length); } @@ -35,7 +35,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests var byteRange = Enumerable.Range(1, length).Select(x => (byte)x).ToArray(); Array.Copy(bytes, 0, byteRange, position, bytes.Length); - Assert.Throws(() => byteRange.AsSpan().GetRequestHeaderStringNonNullCharacters(useLatin1: false)); + Assert.Throws(() => HttpUtilities.GetRequestHeaderStringNonNullCharacters(byteRange.AsSpan(), useLatin1: false)); } } } diff --git a/src/Servers/Kestrel/Core/test/VariableIntHelperTests.cs b/src/Servers/Kestrel/Core/test/VariableIntHelperTests.cs new file mode 100644 index 0000000000..579d85a687 --- /dev/null +++ b/src/Servers/Kestrel/Core/test/VariableIntHelperTests.cs @@ -0,0 +1,46 @@ +using System; +using System.Buffers; +using System.Net.Http; +using Xunit; + +namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests +{ + public class VariableIntHelperTests + { + [Theory] + [MemberData(nameof(IntegerData))] + public void CheckDecoding(long expected, byte[] input) + { + var decoded = VariableLengthIntegerHelper.GetInteger(new ReadOnlySequence(input), out _, out _); + Assert.Equal(expected, decoded); + } + + [Theory] + [MemberData(nameof(IntegerData))] + public void CheckEncoding(long input, byte[] expected) + { + var outputBuffer = new Span(new byte[8]); + var encodedLength = VariableLengthIntegerHelper.WriteInteger(outputBuffer, input); + Assert.Equal(expected.Length, encodedLength); + for(var i = 0; i < expected.Length; i++) + { + Assert.Equal(expected[i], outputBuffer[i]); + } + } + + public static TheoryData IntegerData + { + get + { + var data = new TheoryData(); + + data.Add(151288809941952652, new byte[] { 0xc2, 0x19, 0x7c, 0x5e, 0xff, 0x14, 0xe8, 0x8c }); + data.Add(494878333, new byte[] { 0x9d, 0x7f, 0x3e, 0x7d }); + data.Add(15293, new byte[] { 0x7b, 0xbd }); + data.Add(37, new byte[] { 0x25 }); + + return data; + } + } + } +} diff --git a/src/Servers/Kestrel/Kestrel.sln b/src/Servers/Kestrel/Kestrel.sln index 5f7b931878..39711faec1 100644 --- a/src/Servers/Kestrel/Kestrel.sln +++ b/src/Servers/Kestrel/Kestrel.sln @@ -82,6 +82,16 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Hostin EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.WebUtilities", "..\..\Http\WebUtilities\src\Microsoft.AspNetCore.WebUtilities.csproj", "{EE45763C-753D-4228-8E5D-A71F8BDB3D89}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "http2cat", "samples\http2cat\http2cat.csproj", "{3D6821F5-F242-4828-8DDE-89488E85512D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QuicSampleApp", "samples\QuicSampleApp\QuicSampleApp.csproj", "{53A8634C-DFC5-4A5B-8864-9EF1707E3F18}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Server.Kestrel.Transport.Experimental.Quic", "Transport.Quic\src\Microsoft.AspNetCore.Server.Kestrel.Transport.Experimental.Quic.csproj", "{62CFF861-807E-43F6-9403-22AA7F06C9A6}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QuicSampleClient", "samples\QuicSampleClient\QuicSampleClient.csproj", "{F39A942B-85A8-4C1B-A5BC-435555E79F20}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Http3SampleApp", "samples\Http3SampleApp\Http3SampleApp.csproj", "{B3CDC83A-A9C5-45DF-9828-6BC419C24308}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -452,6 +462,66 @@ Global {EE45763C-753D-4228-8E5D-A71F8BDB3D89}.Release|x64.Build.0 = Release|Any CPU {EE45763C-753D-4228-8E5D-A71F8BDB3D89}.Release|x86.ActiveCfg = Release|Any CPU {EE45763C-753D-4228-8E5D-A71F8BDB3D89}.Release|x86.Build.0 = Release|Any CPU + {3D6821F5-F242-4828-8DDE-89488E85512D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3D6821F5-F242-4828-8DDE-89488E85512D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3D6821F5-F242-4828-8DDE-89488E85512D}.Debug|x64.ActiveCfg = Debug|Any CPU + {3D6821F5-F242-4828-8DDE-89488E85512D}.Debug|x64.Build.0 = Debug|Any CPU + {3D6821F5-F242-4828-8DDE-89488E85512D}.Debug|x86.ActiveCfg = Debug|Any CPU + {3D6821F5-F242-4828-8DDE-89488E85512D}.Debug|x86.Build.0 = Debug|Any CPU + {3D6821F5-F242-4828-8DDE-89488E85512D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3D6821F5-F242-4828-8DDE-89488E85512D}.Release|Any CPU.Build.0 = Release|Any CPU + {3D6821F5-F242-4828-8DDE-89488E85512D}.Release|x64.ActiveCfg = Release|Any CPU + {3D6821F5-F242-4828-8DDE-89488E85512D}.Release|x64.Build.0 = Release|Any CPU + {3D6821F5-F242-4828-8DDE-89488E85512D}.Release|x86.ActiveCfg = Release|Any CPU + {3D6821F5-F242-4828-8DDE-89488E85512D}.Release|x86.Build.0 = Release|Any CPU + {53A8634C-DFC5-4A5B-8864-9EF1707E3F18}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {53A8634C-DFC5-4A5B-8864-9EF1707E3F18}.Debug|Any CPU.Build.0 = Debug|Any CPU + {53A8634C-DFC5-4A5B-8864-9EF1707E3F18}.Debug|x64.ActiveCfg = Debug|Any CPU + {53A8634C-DFC5-4A5B-8864-9EF1707E3F18}.Debug|x64.Build.0 = Debug|Any CPU + {53A8634C-DFC5-4A5B-8864-9EF1707E3F18}.Debug|x86.ActiveCfg = Debug|Any CPU + {53A8634C-DFC5-4A5B-8864-9EF1707E3F18}.Debug|x86.Build.0 = Debug|Any CPU + {53A8634C-DFC5-4A5B-8864-9EF1707E3F18}.Release|Any CPU.ActiveCfg = Release|Any CPU + {53A8634C-DFC5-4A5B-8864-9EF1707E3F18}.Release|Any CPU.Build.0 = Release|Any CPU + {53A8634C-DFC5-4A5B-8864-9EF1707E3F18}.Release|x64.ActiveCfg = Release|Any CPU + {53A8634C-DFC5-4A5B-8864-9EF1707E3F18}.Release|x64.Build.0 = Release|Any CPU + {53A8634C-DFC5-4A5B-8864-9EF1707E3F18}.Release|x86.ActiveCfg = Release|Any CPU + {53A8634C-DFC5-4A5B-8864-9EF1707E3F18}.Release|x86.Build.0 = Release|Any CPU + {62CFF861-807E-43F6-9403-22AA7F06C9A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {62CFF861-807E-43F6-9403-22AA7F06C9A6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {62CFF861-807E-43F6-9403-22AA7F06C9A6}.Debug|x64.ActiveCfg = Debug|Any CPU + {62CFF861-807E-43F6-9403-22AA7F06C9A6}.Debug|x64.Build.0 = Debug|Any CPU + {62CFF861-807E-43F6-9403-22AA7F06C9A6}.Debug|x86.ActiveCfg = Debug|Any CPU + {62CFF861-807E-43F6-9403-22AA7F06C9A6}.Debug|x86.Build.0 = Debug|Any CPU + {62CFF861-807E-43F6-9403-22AA7F06C9A6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {62CFF861-807E-43F6-9403-22AA7F06C9A6}.Release|Any CPU.Build.0 = Release|Any CPU + {62CFF861-807E-43F6-9403-22AA7F06C9A6}.Release|x64.ActiveCfg = Release|Any CPU + {62CFF861-807E-43F6-9403-22AA7F06C9A6}.Release|x64.Build.0 = Release|Any CPU + {62CFF861-807E-43F6-9403-22AA7F06C9A6}.Release|x86.ActiveCfg = Release|Any CPU + {62CFF861-807E-43F6-9403-22AA7F06C9A6}.Release|x86.Build.0 = Release|Any CPU + {F39A942B-85A8-4C1B-A5BC-435555E79F20}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F39A942B-85A8-4C1B-A5BC-435555E79F20}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F39A942B-85A8-4C1B-A5BC-435555E79F20}.Debug|x64.ActiveCfg = Debug|Any CPU + {F39A942B-85A8-4C1B-A5BC-435555E79F20}.Debug|x64.Build.0 = Debug|Any CPU + {F39A942B-85A8-4C1B-A5BC-435555E79F20}.Debug|x86.ActiveCfg = Debug|Any CPU + {F39A942B-85A8-4C1B-A5BC-435555E79F20}.Debug|x86.Build.0 = Debug|Any CPU + {F39A942B-85A8-4C1B-A5BC-435555E79F20}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F39A942B-85A8-4C1B-A5BC-435555E79F20}.Release|Any CPU.Build.0 = Release|Any CPU + {F39A942B-85A8-4C1B-A5BC-435555E79F20}.Release|x64.ActiveCfg = Release|Any CPU + {F39A942B-85A8-4C1B-A5BC-435555E79F20}.Release|x64.Build.0 = Release|Any CPU + {F39A942B-85A8-4C1B-A5BC-435555E79F20}.Release|x86.ActiveCfg = Release|Any CPU + {F39A942B-85A8-4C1B-A5BC-435555E79F20}.Release|x86.Build.0 = Release|Any CPU + {B3CDC83A-A9C5-45DF-9828-6BC419C24308}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B3CDC83A-A9C5-45DF-9828-6BC419C24308}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B3CDC83A-A9C5-45DF-9828-6BC419C24308}.Debug|x64.ActiveCfg = Debug|Any CPU + {B3CDC83A-A9C5-45DF-9828-6BC419C24308}.Debug|x64.Build.0 = Debug|Any CPU + {B3CDC83A-A9C5-45DF-9828-6BC419C24308}.Debug|x86.ActiveCfg = Debug|Any CPU + {B3CDC83A-A9C5-45DF-9828-6BC419C24308}.Debug|x86.Build.0 = Debug|Any CPU + {B3CDC83A-A9C5-45DF-9828-6BC419C24308}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B3CDC83A-A9C5-45DF-9828-6BC419C24308}.Release|Any CPU.Build.0 = Release|Any CPU + {B3CDC83A-A9C5-45DF-9828-6BC419C24308}.Release|x64.ActiveCfg = Release|Any CPU + {B3CDC83A-A9C5-45DF-9828-6BC419C24308}.Release|x64.Build.0 = Release|Any CPU + {B3CDC83A-A9C5-45DF-9828-6BC419C24308}.Release|x86.ActiveCfg = Release|Any CPU + {B3CDC83A-A9C5-45DF-9828-6BC419C24308}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -488,6 +558,11 @@ Global {D9872E91-EF1D-4181-82C9-584224ADE368} = {F0A1281A-B512-49D2-8362-21EE32B3674F} {E0AD50A3-2518-4060-8BB9-5649B04B3A6D} = {F0A1281A-B512-49D2-8362-21EE32B3674F} {EE45763C-753D-4228-8E5D-A71F8BDB3D89} = {F0A1281A-B512-49D2-8362-21EE32B3674F} + {3D6821F5-F242-4828-8DDE-89488E85512D} = {F826BA61-60A9-45B6-AF29-FD1A6E313EF0} + {53A8634C-DFC5-4A5B-8864-9EF1707E3F18} = {F826BA61-60A9-45B6-AF29-FD1A6E313EF0} + {62CFF861-807E-43F6-9403-22AA7F06C9A6} = {2B456D08-F72B-4EB8-B663-B6D78FC04BF8} + {F39A942B-85A8-4C1B-A5BC-435555E79F20} = {F826BA61-60A9-45B6-AF29-FD1A6E313EF0} + {B3CDC83A-A9C5-45DF-9828-6BC419C24308} = {F826BA61-60A9-45B6-AF29-FD1A6E313EF0} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {48207B50-7D05-4B10-B585-890FE0F4FCE1} diff --git a/src/Servers/Kestrel/Kestrel/test/GeneratedCodeTests.cs b/src/Servers/Kestrel/Kestrel/test/GeneratedCodeTests.cs index 67969b30f5..da53ab050b 100644 --- a/src/Servers/Kestrel/Kestrel/test/GeneratedCodeTests.cs +++ b/src/Servers/Kestrel/Kestrel/test/GeneratedCodeTests.cs @@ -13,17 +13,20 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests public class GeneratedCodeTests { [ConditionalFact] - [Flaky("https://github.com/aspnet/AspNetCore-Internal/issues/2223", FlakyOn.Helix.All)] public void GeneratedCodeIsUpToDate() { - var httpHeadersGeneratedPath = Path.Combine(AppContext.BaseDirectory,"shared", "GeneratedContent", "HttpHeaders.Generated.cs"); - var httpProtocolGeneratedPath = Path.Combine(AppContext.BaseDirectory,"shared", "GeneratedContent", "HttpProtocol.Generated.cs"); - var httpUtilitiesGeneratedPath = Path.Combine(AppContext.BaseDirectory,"shared", "GeneratedContent", "HttpUtilities.Generated.cs"); - var transportConnectionGeneratedPath = Path.Combine(AppContext.BaseDirectory,"shared", "GeneratedContent", "TransportConnection.Generated.cs"); + var httpHeadersGeneratedPath = Path.Combine(AppContext.BaseDirectory, "shared", "GeneratedContent", "HttpHeaders.Generated.cs"); + var httpProtocolGeneratedPath = Path.Combine(AppContext.BaseDirectory, "shared", "GeneratedContent", "HttpProtocol.Generated.cs"); + var httpUtilitiesGeneratedPath = Path.Combine(AppContext.BaseDirectory, "shared", "GeneratedContent", "HttpUtilities.Generated.cs"); + var http2ConnectionGeneratedPath = Path.Combine(AppContext.BaseDirectory, "shared", "GeneratedContent", "Http2Connection.Generated.cs"); + var transportMultiplexedConnectionGeneratedPath = Path.Combine(AppContext.BaseDirectory, "shared", "GeneratedContent", "TransportMultiplexedConnection.Generated.cs"); + var transportConnectionGeneratedPath = Path.Combine(AppContext.BaseDirectory, "shared", "GeneratedContent", "TransportConnection.Generated.cs"); var testHttpHeadersGeneratedPath = Path.GetTempFileName(); var testHttpProtocolGeneratedPath = Path.GetTempFileName(); var testHttpUtilitiesGeneratedPath = Path.GetTempFileName(); + var testHttp2ConnectionGeneratedPath = Path.GetTempFileName(); + var testTransportMultiplexedConnectionGeneratedPath = Path.GetTempFileName(); var testTransportConnectionGeneratedPath = Path.GetTempFileName(); try @@ -31,26 +34,38 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests var currentHttpHeadersGenerated = File.ReadAllText(httpHeadersGeneratedPath); var currentHttpProtocolGenerated = File.ReadAllText(httpProtocolGeneratedPath); var currentHttpUtilitiesGenerated = File.ReadAllText(httpUtilitiesGeneratedPath); + var currentHttp2ConnectionGenerated = File.ReadAllText(http2ConnectionGeneratedPath); + var currentTransportConnectionBaseGenerated = File.ReadAllText(transportMultiplexedConnectionGeneratedPath); var currentTransportConnectionGenerated = File.ReadAllText(transportConnectionGeneratedPath); - CodeGenerator.Program.Run(testHttpHeadersGeneratedPath, testHttpProtocolGeneratedPath, testHttpUtilitiesGeneratedPath, testTransportConnectionGeneratedPath); + CodeGenerator.Program.Run(testHttpHeadersGeneratedPath, + testHttpProtocolGeneratedPath, + testHttpUtilitiesGeneratedPath, + testHttp2ConnectionGeneratedPath, + testTransportMultiplexedConnectionGeneratedPath, + testTransportConnectionGeneratedPath); var testHttpHeadersGenerated = File.ReadAllText(testHttpHeadersGeneratedPath); var testHttpProtocolGenerated = File.ReadAllText(testHttpProtocolGeneratedPath); var testHttpUtilitiesGenerated = File.ReadAllText(testHttpUtilitiesGeneratedPath); + var testHttp2ConnectionGenerated = File.ReadAllText(testHttp2ConnectionGeneratedPath); + var testTransportMultiplxedConnectionGenerated = File.ReadAllText(testTransportMultiplexedConnectionGeneratedPath); var testTransportConnectionGenerated = File.ReadAllText(testTransportConnectionGeneratedPath); Assert.Equal(currentHttpHeadersGenerated, testHttpHeadersGenerated, ignoreLineEndingDifferences: true); Assert.Equal(currentHttpProtocolGenerated, testHttpProtocolGenerated, ignoreLineEndingDifferences: true); Assert.Equal(currentHttpUtilitiesGenerated, testHttpUtilitiesGenerated, ignoreLineEndingDifferences: true); + Assert.Equal(currentHttp2ConnectionGenerated, testHttp2ConnectionGenerated, ignoreLineEndingDifferences: true); + Assert.Equal(currentTransportConnectionBaseGenerated, testTransportMultiplxedConnectionGenerated, ignoreLineEndingDifferences: true); Assert.Equal(currentTransportConnectionGenerated, testTransportConnectionGenerated, ignoreLineEndingDifferences: true); - } finally { File.Delete(testHttpHeadersGeneratedPath); File.Delete(testHttpProtocolGeneratedPath); File.Delete(testHttpUtilitiesGeneratedPath); + File.Delete(testHttp2ConnectionGeneratedPath); + File.Delete(testTransportMultiplexedConnectionGeneratedPath); File.Delete(testTransportConnectionGeneratedPath); } } diff --git a/src/Servers/Kestrel/Kestrel/test/KestrelConfigurationBuilderTests.cs b/src/Servers/Kestrel/Kestrel/test/KestrelConfigurationBuilderTests.cs index 23fb611551..a8035946ba 100644 --- a/src/Servers/Kestrel/Kestrel/test/KestrelConfigurationBuilderTests.cs +++ b/src/Servers/Kestrel/Kestrel/test/KestrelConfigurationBuilderTests.cs @@ -218,7 +218,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Tests try { var serverOptions = CreateServerOptions(); - var certificate = new X509Certificate2(TestResources.GetCertPath("aspnetdevcert.pfx"), "aspnetdevcert", X509KeyStorageFlags.Exportable); + var certificate = new X509Certificate2(TestResources.GetCertPath("aspnetdevcert.pfx"), "testPassword", X509KeyStorageFlags.Exportable); var bytes = certificate.Export(X509ContentType.Pkcs12, "1234"); var path = GetCertificatePath(); Directory.CreateDirectory(Path.GetDirectoryName(path)); @@ -258,7 +258,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Tests try { var serverOptions = CreateServerOptions(); - var certificate = new X509Certificate2(TestResources.GetCertPath("aspnetdevcert.pfx"), "aspnetdevcert", X509KeyStorageFlags.Exportable); + var certificate = new X509Certificate2(TestResources.GetCertPath("aspnetdevcert.pfx"), "testPassword", X509KeyStorageFlags.Exportable); var bytes = certificate.Export(X509ContentType.Pkcs12, "1234"); var path = GetCertificatePath(); Directory.CreateDirectory(Path.GetDirectoryName(path)); @@ -320,7 +320,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Tests // [InlineData("http2", HttpProtocols.Http2)] // Not supported due to missing ALPN support. https://github.com/dotnet/corefx/issues/33016 [InlineData("http1AndHttp2", HttpProtocols.Http1AndHttp2)] // Gracefully falls back to HTTP/1 [OSSkipCondition(OperatingSystems.Linux)] - [OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win10, WindowsVersions.Win81)] + [MaximumOSVersion(OperatingSystems.Windows, WindowsVersions.Win7)] public void DefaultConfigSectionCanSetProtocols_MacAndWin7(string input, HttpProtocols expected) => DefaultConfigSectionCanSetProtocols(input, expected); @@ -389,7 +389,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Tests // [InlineData("http2", HttpProtocols.Http2)] // Not supported due to missing ALPN support. https://github.com/dotnet/corefx/issues/33016 [InlineData("http1AndHttp2", HttpProtocols.Http1AndHttp2)] // Gracefully falls back to HTTP/1 [OSSkipCondition(OperatingSystems.Linux)] - [OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win10, WindowsVersions.Win81)] + [MaximumOSVersion(OperatingSystems.Windows, WindowsVersions.Win7)] public void EndpointConfigSectionCanSetProtocols_MacAndWin7(string input, HttpProtocols expected) => EndpointConfigSectionCanSetProtocols(input, expected); diff --git a/src/Servers/Kestrel/Kestrel/test/Microsoft.AspNetCore.Server.Kestrel.Tests.csproj b/src/Servers/Kestrel/Kestrel/test/Microsoft.AspNetCore.Server.Kestrel.Tests.csproj index cade793bc2..abf2fb6b3a 100644 --- a/src/Servers/Kestrel/Kestrel/test/Microsoft.AspNetCore.Server.Kestrel.Tests.csproj +++ b/src/Servers/Kestrel/Kestrel/test/Microsoft.AspNetCore.Server.Kestrel.Tests.csproj @@ -12,7 +12,9 @@ + + diff --git a/src/Servers/Kestrel/NuGet.config b/src/Servers/Kestrel/NuGet.config new file mode 100644 index 0000000000..cbdc0002cb --- /dev/null +++ b/src/Servers/Kestrel/NuGet.config @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConstants.cs b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConstants.cs index 0e07f1a69c..1e5b7bb75b 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConstants.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConstants.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Runtime.CompilerServices; @@ -8,8 +8,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal { internal static class LibuvConstants { - public const int ListenBacklog = 128; - public const int EOF = -4095; public static readonly int? ECONNRESET = GetECONNRESET(); public static readonly int? EADDRINUSE = GetEADDRINUSE(); diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Internal/Listener.cs b/src/Servers/Kestrel/Transport.Libuv/src/Internal/Listener.cs index 9dc11a31a4..6279764101 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Internal/Listener.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/Internal/Listener.cs @@ -37,7 +37,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal return Thread.PostAsync(listener => { listener.ListenSocket = listener.CreateListenSocket(); - listener.ListenSocket.Listen(LibuvConstants.ListenBacklog, ConnectionCallback, listener); + listener.ListenSocket.Listen(TransportContext.Options.Backlog, ConnectionCallback, listener); }, this); } diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerPrimary.cs b/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerPrimary.cs index acbc356294..070ee5a73b 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerPrimary.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerPrimary.cs @@ -69,7 +69,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal ListenPipe = new UvPipeHandle(Log); ListenPipe.Init(Thread.Loop, Thread.QueueCloseHandle, false); ListenPipe.Bind(_pipeName); - ListenPipe.Listen(LibuvConstants.ListenBacklog, + ListenPipe.Listen(TransportContext.Options.Backlog, (pipe, status, error, state) => ((ListenerPrimary)state).OnListenPipe(pipe, status, error), this); } @@ -232,7 +232,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal { // This is an unexpected immediate termination of the dispatch pipe most likely caused by an // external process scanning the pipe, so don't we don't log it too severely. - // https://github.com/aspnet/AspNetCore/issues/4741 + // https://github.com/dotnet/aspnetcore/issues/4741 dispatchPipe.Dispose(); _bufHandle.Free(); diff --git a/src/Servers/Kestrel/Transport.Libuv/src/LibuvTransportOptions.cs b/src/Servers/Kestrel/Transport.Libuv/src/LibuvTransportOptions.cs index 06c8aec796..0aa477f3af 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/LibuvTransportOptions.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/LibuvTransportOptions.cs @@ -27,6 +27,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv /// public bool NoDelay { get; set; } = true; + /// + /// The maximum length of the pending connection queue. + /// + /// + /// Defaults to 128. + /// + public int Backlog { get; set; } = 128; + public long? MaxReadBufferSize { get; set; } = 1024 * 1024; public long? MaxWriteBufferSize { get; set; } = 64 * 1024; diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.csproj b/src/Servers/Kestrel/Transport.Libuv/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.csproj index 3fc89b9e9f..9bc5d6b037 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.csproj +++ b/src/Servers/Kestrel/Transport.Libuv/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.csproj @@ -7,13 +7,13 @@ aspnetcore;kestrel true CS1591;$(NoWarn) - true + true - + diff --git a/src/Servers/Kestrel/Transport.Libuv/test/LibuvOutputConsumerTests.cs b/src/Servers/Kestrel/Transport.Libuv/test/LibuvOutputConsumerTests.cs index 03c24d661a..77fa96d008 100644 --- a/src/Servers/Kestrel/Transport.Libuv/test/LibuvOutputConsumerTests.cs +++ b/src/Servers/Kestrel/Transport.Libuv/test/LibuvOutputConsumerTests.cs @@ -606,7 +606,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests Assert.True(task3Success.IsCompleted); Assert.False(task3Success.IsCanceled); - Assert.False(task3Success.IsFaulted);; + Assert.False(task3Success.IsFaulted); } }); } diff --git a/src/Servers/Kestrel/Transport.Libuv/test/LibuvTransportTests.cs b/src/Servers/Kestrel/Transport.Libuv/test/LibuvTransportTests.cs index 13595d51ae..db7ad6033a 100644 --- a/src/Servers/Kestrel/Transport.Libuv/test/LibuvTransportTests.cs +++ b/src/Servers/Kestrel/Transport.Libuv/test/LibuvTransportTests.cs @@ -184,7 +184,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests return context.Response.WriteAsync("Hello World"); }); - listenOptions.UseHttpServer(serviceContext, testApplication, HttpProtocols.Http1); + listenOptions.UseHttpServer(serviceContext, testApplication, Core.HttpProtocols.Http1); var transportContext = new TestLibuvTransportContext { diff --git a/src/Servers/Kestrel/Transport.Quic/README.md b/src/Servers/Kestrel/Transport.Quic/README.md new file mode 100644 index 0000000000..1c0f135cdb --- /dev/null +++ b/src/Servers/Kestrel/Transport.Quic/README.md @@ -0,0 +1,3 @@ +For external contributors, msquic.dll isn't available publicly yet. See https://github.com/aspnet/Announcements/issues/393. + +Credit to Diwakar Mantha and the Kaizala team for the MsQuic interop code. \ No newline at end of file diff --git a/src/Servers/Kestrel/Transport.Quic/src/Internal/FakeTlsConnectionFeature.cs b/src/Servers/Kestrel/Transport.Quic/src/Internal/FakeTlsConnectionFeature.cs new file mode 100644 index 0000000000..6165d46de8 --- /dev/null +++ b/src/Servers/Kestrel/Transport.Quic/src/Internal/FakeTlsConnectionFeature.cs @@ -0,0 +1,25 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Security.Cryptography.X509Certificates; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http.Features; + +namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Experimental.Quic.Internal +{ + internal class FakeTlsConnectionFeature : ITlsConnectionFeature + { + public FakeTlsConnectionFeature() + { + } + + public X509Certificate2 ClientCertificate { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + + public Task GetClientCertificateAsync(CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/Servers/Kestrel/Transport.Quic/src/Internal/IQuicTrace.cs b/src/Servers/Kestrel/Transport.Quic/src/Internal/IQuicTrace.cs new file mode 100644 index 0000000000..d91db88d70 --- /dev/null +++ b/src/Servers/Kestrel/Transport.Quic/src/Internal/IQuicTrace.cs @@ -0,0 +1,20 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Experimental.Quic.Internal +{ + internal interface IQuicTrace : ILogger + { + void NewConnection(string connectionId); + void NewStream(string streamId); + void ConnectionError(string connectionId, Exception ex); + void StreamError(string streamId, Exception ex); + void StreamPause(string streamId); + void StreamResume(string streamId); + void StreamShutdownWrite(string streamId, Exception ex); + void StreamAbort(string streamId, Exception ex); + } +} diff --git a/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicConnectionContext.cs b/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicConnectionContext.cs new file mode 100644 index 0000000000..75bdb99831 --- /dev/null +++ b/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicConnectionContext.cs @@ -0,0 +1,117 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Net.Quic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Connections; +using Microsoft.AspNetCore.Connections.Features; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Experimental.Quic.Internal +{ + internal class QuicConnectionContext : TransportMultiplexedConnection, IProtocolErrorCodeFeature + { + private QuicConnection _connection; + private readonly QuicTransportContext _context; + private readonly IQuicTrace _log; + + private ValueTask _closeTask; + + public long Error { get; set; } + + public QuicConnectionContext(QuicConnection connection, QuicTransportContext context) + { + _log = context.Log; + _context = context; + _connection = connection; + Features.Set(new FakeTlsConnectionFeature()); + Features.Set(this); + + _log.NewConnection(ConnectionId); + } + + public ValueTask StartUnidirectionalStreamAsync() + { + var stream = _connection.OpenUnidirectionalStream(); + return new ValueTask(new QuicStreamContext(stream, this, _context)); + } + + public ValueTask StartBidirectionalStreamAsync() + { + var stream = _connection.OpenBidirectionalStream(); + return new ValueTask(new QuicStreamContext(stream, this, _context)); + } + + public override async ValueTask DisposeAsync() + { + try + { + if (_closeTask != default) + { + _closeTask = _connection.CloseAsync(errorCode: 0); + await _closeTask; + } + else + { + await _closeTask; + } + } + catch (Exception ex) + { + _log.LogWarning(ex, "Failed to gracefully shutdown connection."); + } + + _connection.Dispose(); + } + + public override void Abort() => Abort(new ConnectionAbortedException("The connection was aborted by the application via MultiplexedConnectionContext.Abort().")); + + public override void Abort(ConnectionAbortedException abortReason) + { + _closeTask = _connection.CloseAsync(errorCode: Error); + } + + public override async ValueTask AcceptAsync(CancellationToken cancellationToken = default) + { + try + { + var stream = await _connection.AcceptStreamAsync(cancellationToken); + return new QuicStreamContext(stream, this, _context); + } + catch (QuicException ex) + { + // Accept on graceful close throws an aborted exception rather than returning null. + _log.LogDebug($"Accept loop ended with exception: {ex.Message}"); + } + + return null; + } + + public override ValueTask ConnectAsync(IFeatureCollection features = null, CancellationToken cancellationToken = default) + { + QuicStream quicStream; + + if (features != null) + { + var streamDirectionFeature = features.Get(); + if (streamDirectionFeature.CanRead) + { + quicStream = _connection.OpenBidirectionalStream(); + } + else + { + quicStream = _connection.OpenUnidirectionalStream(); + } + } + else + { + quicStream = _connection.OpenBidirectionalStream(); + } + + return new ValueTask(new QuicStreamContext(quicStream, this, _context)); + } + } +} diff --git a/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicConnectionListener.cs b/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicConnectionListener.cs new file mode 100644 index 0000000000..c6d50f0250 --- /dev/null +++ b/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicConnectionListener.cs @@ -0,0 +1,83 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Quic; +using System.Net.Security; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Connections; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Experimental.Quic.Internal +{ + /// + /// Listens for new Quic Connections. + /// + internal class QuicConnectionListener : IMultiplexedConnectionListener, IAsyncDisposable + { + private readonly IQuicTrace _log; + private bool _disposed; + private readonly QuicTransportContext _context; + private readonly QuicListener _listener; + + public QuicConnectionListener(QuicTransportOptions options, IQuicTrace log, EndPoint endpoint) + { + _log = log; + _context = new QuicTransportContext(_log, options); + EndPoint = endpoint; + + var quicListenerOptions = new QuicListenerOptions(); + var sslConfig = new SslServerAuthenticationOptions(); + sslConfig.ServerCertificate = options.Certificate; + sslConfig.ApplicationProtocols = new List() { new SslApplicationProtocol(options.Alpn) }; + + quicListenerOptions.ServerAuthenticationOptions = sslConfig; + quicListenerOptions.CertificateFilePath = options.CertificateFilePath; + quicListenerOptions.PrivateKeyFilePath = options.PrivateKeyFilePath; + quicListenerOptions.ListenEndPoint = endpoint as IPEndPoint; + + _listener = new QuicListener(QuicImplementationProviders.MsQuic, quicListenerOptions); + _listener.Start(); + } + + public EndPoint EndPoint { get; set; } + + public async ValueTask AcceptAsync(IFeatureCollection features = null, CancellationToken cancellationToken = default) + { + try + { + var quicConnection = await _listener.AcceptConnectionAsync(cancellationToken); + return new QuicConnectionContext(quicConnection, _context); + } + catch (QuicOperationAbortedException ex) + { + _log.LogDebug($"Listener has aborted with exception: {ex.Message}"); + } + return null; + } + + public async ValueTask UnbindAsync(CancellationToken cancellationToken = default) + { + await DisposeAsync(); + } + + public ValueTask DisposeAsync() + { + if (_disposed) + { + return new ValueTask(); + } + + _disposed = true; + + _listener.Close(); + _listener.Dispose(); + + return new ValueTask(); + } + } +} diff --git a/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicStreamContext.cs b/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicStreamContext.cs new file mode 100644 index 0000000000..8a17ae6aa5 --- /dev/null +++ b/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicStreamContext.cs @@ -0,0 +1,333 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Buffers; +using System.IO.Pipelines; +using System.Net.Quic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Connections; +using Microsoft.AspNetCore.Connections.Features; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Experimental.Quic.Internal +{ + internal class QuicStreamContext : TransportConnection, IStreamDirectionFeature, IProtocolErrorCodeFeature, IStreamIdFeature + { + private readonly Task _processingTask; + private readonly QuicStream _stream; + private readonly QuicConnectionContext _connection; + private readonly QuicTransportContext _context; + private readonly CancellationTokenSource _streamClosedTokenSource = new CancellationTokenSource(); + private readonly IQuicTrace _log; + private string _connectionId; + private const int MinAllocBufferSize = 4096; + private volatile Exception _shutdownReason; + private readonly TaskCompletionSource _waitForConnectionClosedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + private readonly object _shutdownLock = new object(); + + public QuicStreamContext(QuicStream stream, QuicConnectionContext connection, QuicTransportContext context) + { + _stream = stream; + _connection = connection; + _context = context; + _log = context.Log; + + ConnectionClosed = _streamClosedTokenSource.Token; + + var maxReadBufferSize = context.Options.MaxReadBufferSize.Value; + var maxWriteBufferSize = context.Options.MaxWriteBufferSize.Value; + + // TODO should we allow these PipeScheduler to be configurable here? + var inputOptions = new PipeOptions(MemoryPool, PipeScheduler.ThreadPool, PipeScheduler.Inline, maxReadBufferSize, maxReadBufferSize / 2, useSynchronizationContext: false); + var outputOptions = new PipeOptions(MemoryPool, PipeScheduler.Inline, PipeScheduler.ThreadPool, maxWriteBufferSize, maxWriteBufferSize / 2, useSynchronizationContext: false); + + var pair = DuplexPipe.CreateConnectionPair(inputOptions, outputOptions); + + Features.Set(this); + Features.Set(this); + Features.Set(this); + + // TODO populate the ITlsConnectionFeature (requires client certs). + Features.Set(new FakeTlsConnectionFeature()); + CanRead = stream.CanRead; + CanWrite = stream.CanWrite; + + Transport = pair.Transport; + Application = pair.Application; + + _processingTask = StartAsync(); + } + + public override MemoryPool MemoryPool { get; } + public PipeWriter Input => Application.Output; + public PipeReader Output => Application.Input; + + public bool CanRead { get; } + public bool CanWrite { get; } + + public long StreamId + { + get + { + return _stream.StreamId; + } + } + + public override string ConnectionId + { + get + { + if (_connectionId == null) + { + _connectionId = $"{_connection.ConnectionId}:{StreamId}"; + } + return _connectionId; + } + set + { + _connectionId = value; + } + } + + public long Error { get; set; } + + private async Task StartAsync() + { + try + { + // Spawn send and receive logic + // Streams may or may not have reading/writing, so only start tasks accordingly + var receiveTask = Task.CompletedTask; + var sendTask = Task.CompletedTask; + + if (_stream.CanRead) + { + receiveTask = DoReceive(); + } + + if (_stream.CanWrite) + { + sendTask = DoSend(); + } + + // Now wait for both to complete + await receiveTask; + await sendTask; + + } + catch (Exception ex) + { + _log.LogError(0, ex, $"Unexpected exception in {nameof(QuicStreamContext)}.{nameof(StartAsync)}."); + } + } + + private async Task DoReceive() + { + Exception error = null; + + try + { + await ProcessReceives(); + } + catch (QuicException ex) + { + // This could be ignored if _shutdownReason is already set. + error = new ConnectionResetException(ex.Message, ex); + } + catch (Exception ex) + { + // This is unexpected. + error = ex; + _log.StreamError(ConnectionId, error); + } + finally + { + // If Shutdown() has already bee called, assume that was the reason ProcessReceives() exited. + Input.Complete(_shutdownReason ?? error); + + FireStreamClosed(); + + await _waitForConnectionClosedTcs.Task; + } + } + + private async Task ProcessReceives() + { + var input = Input; + while (true) + { + var buffer = Input.GetMemory(MinAllocBufferSize); + var bytesReceived = await _stream.ReadAsync(buffer); + + if (bytesReceived == 0) + { + // Read completed. + break; + } + + input.Advance(bytesReceived); + + var flushTask = input.FlushAsync(); + + var paused = !flushTask.IsCompleted; + + if (paused) + { + _log.StreamPause(ConnectionId); + } + + var result = await flushTask; + + if (paused) + { + _log.StreamResume(ConnectionId); + } + + if (result.IsCompleted || result.IsCanceled) + { + // Pipe consumer is shut down, do we stop writing + break; + } + } + } + + private void FireStreamClosed() + { + ThreadPool.UnsafeQueueUserWorkItem(state => + { + state.CancelConnectionClosedToken(); + + state._waitForConnectionClosedTcs.TrySetResult(null); + }, + this, + preferLocal: false); + } + + private void CancelConnectionClosedToken() + { + try + { + _streamClosedTokenSource.Cancel(); + } + catch (Exception ex) + { + _log.LogError(0, ex, $"Unexpected exception in {nameof(QuicStreamContext)}.{nameof(CancelConnectionClosedToken)}."); + } + } + + + private async Task DoSend() + { + Exception shutdownReason = null; + Exception unexpectedError = null; + + try + { + await ProcessSends(); + } + catch (QuicException ex) + { + shutdownReason = new ConnectionResetException(ex.Message, ex); + } + catch (Exception ex) + { + shutdownReason = ex; + unexpectedError = ex; + _log.ConnectionError(ConnectionId, unexpectedError); + } + finally + { + await ShutdownWrite(shutdownReason); + + // Complete the output after disposing the stream + Output.Complete(unexpectedError); + + // Cancel any pending flushes so that the input loop is un-paused + Input.CancelPendingFlush(); + } + } + + private async Task ProcessSends() + { + // Resolve `output` PipeReader via the IDuplexPipe interface prior to loop start for performance. + var output = Output; + while (true) + { + var result = await output.ReadAsync(); + + if (result.IsCanceled) + { + break; + } + + var buffer = result.Buffer; + + var end = buffer.End; + var isCompleted = result.IsCompleted; + if (!buffer.IsEmpty) + { + await _stream.WriteAsync(buffer, endStream: isCompleted); + } + + output.AdvanceTo(end); + + if (isCompleted) + { + // Once the stream pipe is closed, shutdown the stream. + break; + } + } + } + + public override void Abort(ConnectionAbortedException abortReason) + { + // Don't call _stream.Shutdown and _stream.Abort at the same time. + _log.StreamAbort(ConnectionId, abortReason); + + lock (_shutdownLock) + { + _stream.AbortRead(Error); + _stream.AbortWrite(Error); + } + + // Cancel ProcessSends loop after calling shutdown to ensure the correct _shutdownReason gets set. + Output.CancelPendingRead(); + } + + private async ValueTask ShutdownWrite(Exception shutdownReason) + { + try + { + lock (_shutdownLock) + { + _shutdownReason = shutdownReason ?? new ConnectionAbortedException("The Quic transport's send loop completed gracefully."); + + _log.StreamShutdownWrite(ConnectionId, _shutdownReason); + _stream.Shutdown(); + } + + await _stream.ShutdownWriteCompleted(); + } + catch (Exception ex) + { + _log.LogWarning(ex, "Stream failed to gracefully shutdown."); + // Ignore any errors from Shutdown() since we're tearing down the stream anyway. + } + } + + public override async ValueTask DisposeAsync() + { + Transport.Input.Complete(); + Transport.Output.Complete(); + + await _processingTask; + + _stream.Dispose(); + + _streamClosedTokenSource.Dispose(); + } + } +} diff --git a/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicTrace.cs b/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicTrace.cs new file mode 100644 index 0000000000..85f8119490 --- /dev/null +++ b/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicTrace.cs @@ -0,0 +1,81 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Experimental.Quic.Internal +{ + internal class QuicTrace : IQuicTrace + { + private static readonly Action _acceptedConnection = + LoggerMessage.Define(LogLevel.Debug, new EventId(4, nameof(NewConnection)), @"Connection id ""{ConnectionId}"" accepted."); + private static readonly Action _acceptedStream = + LoggerMessage.Define(LogLevel.Debug, new EventId(5, nameof(NewStream)), @"Stream id ""{ConnectionId}"" accepted."); + private static readonly Action _connectionError = + LoggerMessage.Define(LogLevel.Debug, new EventId(6, nameof(ConnectionError)), @"Connection id ""{ConnectionId}"" hit an exception: ""{Reason}""."); + private static readonly Action _streamError = + LoggerMessage.Define(LogLevel.Debug, new EventId(7, nameof(StreamError)), @"Connection id ""{ConnectionId}"" hit an exception: ""{Reason}""."); + private static readonly Action _streamPause = + LoggerMessage.Define(LogLevel.Debug, new EventId(7, nameof(StreamPause)), @"Stream id ""{ConnectionId}"" paused."); + private static readonly Action _streamResume = + LoggerMessage.Define(LogLevel.Debug, new EventId(7, nameof(StreamResume)), @"Stream id ""{ConnectionId}"" resumed."); + private static readonly Action _streamShutdownWrite = + LoggerMessage.Define(LogLevel.Debug, new EventId(7, nameof(StreamShutdownWrite)), @"Stream id ""{ConnectionId}"" shutting down writes, exception: ""{Reason}""."); + private static readonly Action _streamAborted = + LoggerMessage.Define(LogLevel.Debug, new EventId(7, nameof(StreamShutdownWrite)), @"Stream id ""{ConnectionId}"" aborted by application, exception: ""{Reason}""."); + + private ILogger _logger; + + public QuicTrace(ILogger logger) + { + _logger = logger; + } + + public IDisposable BeginScope(TState state) => _logger.BeginScope(state); + + public bool IsEnabled(LogLevel logLevel) => _logger.IsEnabled(logLevel); + + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) + => _logger.Log(logLevel, eventId, state, exception, formatter); + + public void NewConnection(string connectionId) + { + _acceptedConnection(_logger, connectionId, null); + } + + public void NewStream(string streamId) + { + _acceptedStream(_logger, streamId, null); + } + public void ConnectionError(string connectionId, Exception ex) + { + _connectionError(_logger, connectionId, ex.Message, ex); + } + + public void StreamError(string streamId, Exception ex) + { + _streamError(_logger, streamId, ex.Message, ex); + } + + public void StreamPause(string streamId) + { + _streamPause(_logger, streamId, null); + } + + public void StreamResume(string streamId) + { + _streamResume(_logger, streamId, null); + } + + public void StreamShutdownWrite(string streamId, Exception ex) + { + _streamShutdownWrite(_logger, streamId, ex.Message, ex); + } + + public void StreamAbort(string streamId, Exception ex) + { + _streamAborted(_logger, streamId, ex.Message, ex); + } + } +} diff --git a/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicTransportContext.cs b/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicTransportContext.cs new file mode 100644 index 0000000000..0ca29b2d2d --- /dev/null +++ b/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicTransportContext.cs @@ -0,0 +1,19 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.Extensions.Hosting; + +namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Experimental.Quic.Internal +{ + internal class QuicTransportContext + { + public QuicTransportContext(IQuicTrace log, QuicTransportOptions options) + { + Log = log; + Options = options; + } + + public IQuicTrace Log { get; } + public QuicTransportOptions Options { get; } + } +} diff --git a/src/Servers/Kestrel/Transport.Quic/src/Libraries.cs b/src/Servers/Kestrel/Transport.Quic/src/Libraries.cs new file mode 100644 index 0000000000..82340cf941 --- /dev/null +++ b/src/Servers/Kestrel/Transport.Quic/src/Libraries.cs @@ -0,0 +1,12 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +internal static partial class Interop +{ + internal static class Libraries + { + // Compare to https://github.com/dotnet/runtime/blob/63c88901df460c47eaffc6b970c4b5f0aeaf0a88/src/libraries/Common/src/Interop/Linux/Interop.Libraries.cs#L10 + internal const string MsQuic = "msquic"; + } +} diff --git a/src/Servers/Kestrel/Transport.Quic/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Experimental.Quic.csproj b/src/Servers/Kestrel/Transport.Quic/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Experimental.Quic.csproj new file mode 100644 index 0000000000..69b85b7204 --- /dev/null +++ b/src/Servers/Kestrel/Transport.Quic/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Experimental.Quic.csproj @@ -0,0 +1,43 @@ + + + + Quic transport for the ASP.NET Core Kestrel cross-platform web server. + $(DefaultNetCoreTargetFramework) + true + aspnetcore;kestrel + true + CS1591;CS0436;$(NoWarn) + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + System.Net.Quic.SR + + + + + diff --git a/src/Servers/Kestrel/Transport.Quic/src/QuicConnectionFactory.cs b/src/Servers/Kestrel/Transport.Quic/src/QuicConnectionFactory.cs new file mode 100644 index 0000000000..abc73c72c0 --- /dev/null +++ b/src/Servers/Kestrel/Transport.Quic/src/QuicConnectionFactory.cs @@ -0,0 +1,51 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Quic; +using System.Net.Security; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Connections; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.Server.Kestrel.Transport.Experimental.Quic.Internal; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Experimental.Quic +{ + public class QuicConnectionFactory : IMultiplexedConnectionFactory + { + private QuicTransportContext _transportContext; + + public QuicConnectionFactory(IOptions options, ILoggerFactory loggerFactory) + { + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + + var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Server.Kestrel.Transport.Experimental.Quic.Client"); + var trace = new QuicTrace(logger); + + _transportContext = new QuicTransportContext(trace, options.Value); + } + + public async ValueTask ConnectAsync(EndPoint endPoint, IFeatureCollection features = null, CancellationToken cancellationToken = default) + { + if (!(endPoint is IPEndPoint ipEndPoint)) + { + throw new NotSupportedException($"{endPoint} is not supported"); + } + + var sslOptions = new SslClientAuthenticationOptions(); + sslOptions.ApplicationProtocols = new List() { new SslApplicationProtocol(_transportContext.Options.Alpn) }; + var connection = new QuicConnection(QuicImplementationProviders.MsQuic, endPoint as IPEndPoint, sslOptions); + + await connection.ConnectAsync(cancellationToken); + return new QuicConnectionContext(connection, _transportContext); + } + } +} diff --git a/src/Servers/Kestrel/Transport.Quic/src/QuicTransportFactory.cs b/src/Servers/Kestrel/Transport.Quic/src/QuicTransportFactory.cs new file mode 100644 index 0000000000..98b089c892 --- /dev/null +++ b/src/Servers/Kestrel/Transport.Quic/src/QuicTransportFactory.cs @@ -0,0 +1,44 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Net; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Connections; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.Server.Kestrel.Transport.Experimental.Quic.Internal; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Experimental.Quic +{ + public class QuicTransportFactory : IMultiplexedConnectionListenerFactory + { + private QuicTrace _log; + private QuicTransportOptions _options; + + public QuicTransportFactory(ILoggerFactory loggerFactory, IOptions options) + { + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + + if (loggerFactory == null) + { + throw new ArgumentNullException(nameof(loggerFactory)); + } + + var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic"); + _log = new QuicTrace(logger); + _options = options.Value; + } + + public ValueTask BindAsync(EndPoint endpoint, IFeatureCollection features = null, CancellationToken cancellationToken = default) + { + var transport = new QuicConnectionListener(_options, _log, endpoint); + return new ValueTask(transport); + } + } +} diff --git a/src/Servers/Kestrel/Transport.Quic/src/QuicTransportOptions.cs b/src/Servers/Kestrel/Transport.Quic/src/QuicTransportOptions.cs new file mode 100644 index 0000000000..801ef8da8f --- /dev/null +++ b/src/Servers/Kestrel/Transport.Quic/src/QuicTransportOptions.cs @@ -0,0 +1,60 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Buffers; +using System.Security.Cryptography.X509Certificates; + +namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Experimental.Quic +{ + public class QuicTransportOptions + { + /// + /// The maximum number of concurrent bi-directional streams per connection. + /// + public ushort MaxBidirectionalStreamCount { get; set; } = 100; + + /// + /// The maximum number of concurrent inbound uni-directional streams per connection. + /// + public ushort MaxUnidirectionalStreamCount { get; set; } = 10; + + /// + /// The Application Layer Protocol Negotiation string. + /// + public string Alpn { get; set; } + + /// + /// The certificate that MsQuic will use. + /// + public X509Certificate2 Certificate { get; set; } + + /// + /// Optional path to certificate file to configure the security configuration. + /// + public string CertificateFilePath { get; set; } + + /// + /// Optional path to private key file to configure the security configuration. + /// + public string PrivateKeyFilePath { get; set; } + + /// + /// Sets the idle timeout for connections and streams. + /// + public TimeSpan IdleTimeout { get; set; } + + /// + /// The maximum read size. + /// + public long? MaxReadBufferSize { get; set; } = 1024 * 1024; + + /// + /// The maximum write size. + /// + public long? MaxWriteBufferSize { get; set; } = 64 * 1024; + + internal Func> MemoryPoolFactory { get; set; } = System.Buffers.SlabMemoryPoolFactory.Create; + + } +} diff --git a/src/Servers/Kestrel/Transport.Quic/src/WebHostBuilderMsQuicExtensions.cs b/src/Servers/Kestrel/Transport.Quic/src/WebHostBuilderMsQuicExtensions.cs new file mode 100644 index 0000000000..2c5ab8be37 --- /dev/null +++ b/src/Servers/Kestrel/Transport.Quic/src/WebHostBuilderMsQuicExtensions.cs @@ -0,0 +1,29 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNetCore.Connections; +using Microsoft.AspNetCore.Server.Kestrel.Transport.Experimental.Quic; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNetCore.Hosting +{ + public static class WebHostBuilderMsQuicExtensions + { + public static IWebHostBuilder UseQuic(this IWebHostBuilder hostBuilder) + { + return hostBuilder.ConfigureServices(services => + { + services.AddSingleton(); + }); + } + + public static IWebHostBuilder UseQuic(this IWebHostBuilder hostBuilder, Action configureOptions) + { + return hostBuilder.UseQuic().ConfigureServices(services => + { + services.Configure(configureOptions); + }); + } + } +} diff --git a/src/Servers/Kestrel/Transport.Sockets/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.netcoreapp.cs b/src/Servers/Kestrel/Transport.Sockets/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.netcoreapp.cs index c1e32cc39d..020e80124d 100644 --- a/src/Servers/Kestrel/Transport.Sockets/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.netcoreapp.cs +++ b/src/Servers/Kestrel/Transport.Sockets/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.netcoreapp.cs @@ -11,6 +11,13 @@ namespace Microsoft.AspNetCore.Hosting } namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets { + public partial class SocketConnectionFactory : Microsoft.AspNetCore.Connections.IConnectionFactory, System.IAsyncDisposable + { + public SocketConnectionFactory(Microsoft.Extensions.Options.IOptions options, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { } + [System.Diagnostics.DebuggerStepThroughAttribute] + public System.Threading.Tasks.ValueTask ConnectAsync(System.Net.EndPoint endpoint, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + public System.Threading.Tasks.ValueTask DisposeAsync() { throw null; } + } public sealed partial class SocketTransportFactory : Microsoft.AspNetCore.Connections.IConnectionListenerFactory { public SocketTransportFactory(Microsoft.Extensions.Options.IOptions options, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { } @@ -19,9 +26,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets public partial class SocketTransportOptions { public SocketTransportOptions() { } - public int IOQueueCount { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public long? MaxReadBufferSize { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public long? MaxWriteBufferSize { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public bool NoDelay { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public int Backlog { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public int IOQueueCount { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public long? MaxReadBufferSize { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public long? MaxWriteBufferSize { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public bool NoDelay { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public bool WaitForDataBeforeAllocatingBuffer { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } } diff --git a/src/Servers/Kestrel/Transport.Sockets/src/Client/SocketConnectionFactory.cs b/src/Servers/Kestrel/Transport.Sockets/src/Client/SocketConnectionFactory.cs new file mode 100644 index 0000000000..6f95d242a5 --- /dev/null +++ b/src/Servers/Kestrel/Transport.Sockets/src/Client/SocketConnectionFactory.cs @@ -0,0 +1,78 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Buffers; +using System.IO.Pipelines; +using System.Net; +using System.Net.Sockets; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Connections; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets +{ + public class SocketConnectionFactory : IConnectionFactory, IAsyncDisposable + { + private readonly SocketTransportOptions _options; + private readonly MemoryPool _memoryPool; + private readonly SocketsTrace _trace; + + public SocketConnectionFactory(IOptions options, ILoggerFactory loggerFactory) + { + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + + if (loggerFactory == null) + { + throw new ArgumentNullException(nameof(loggerFactory)); + } + + _options = options.Value; + _memoryPool = options.Value.MemoryPoolFactory(); + var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Client"); + _trace = new SocketsTrace(logger); + } + + public async ValueTask ConnectAsync(EndPoint endpoint, CancellationToken cancellationToken = default) + { + var ipEndPoint = endpoint as IPEndPoint; + + if (ipEndPoint is null) + { + throw new NotSupportedException("The SocketConnectionFactory only supports IPEndPoints for now."); + } + + var socket = new Socket(ipEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp) + { + NoDelay = _options.NoDelay + }; + + await socket.ConnectAsync(ipEndPoint); + + var socketConnection = new SocketConnection( + socket, + _memoryPool, + PipeScheduler.ThreadPool, + _trace, + _options.MaxReadBufferSize, + _options.MaxWriteBufferSize, + _options.WaitForDataBeforeAllocatingBuffer); + + socketConnection.Start(); + return socketConnection; + } + + public ValueTask DisposeAsync() + { + _memoryPool.Dispose(); + return default; + } + } +} diff --git a/src/Servers/Kestrel/Transport.Sockets/src/Internal/SocketAwaitableEventArgs.cs b/src/Servers/Kestrel/Transport.Sockets/src/Internal/SocketAwaitableEventArgs.cs index 9c8f2aef9d..0d59cd1c43 100644 --- a/src/Servers/Kestrel/Transport.Sockets/src/Internal/SocketAwaitableEventArgs.cs +++ b/src/Servers/Kestrel/Transport.Sockets/src/Internal/SocketAwaitableEventArgs.cs @@ -11,7 +11,7 @@ using System.Threading.Tasks; namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal { - internal class SocketAwaitableEventArgs : SocketAsyncEventArgs, ICriticalNotifyCompletion + internal sealed class SocketAwaitableEventArgs : SocketAsyncEventArgs, ICriticalNotifyCompletion { private static readonly Action _callbackCompleted = () => { }; @@ -20,6 +20,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal private Action _callback; public SocketAwaitableEventArgs(PipeScheduler ioScheduler) + : base(unsafeSuppressExecutionContextFlow: true) { _ioScheduler = ioScheduler; } diff --git a/src/Servers/Kestrel/Transport.Sockets/src/Internal/SocketConnection.cs b/src/Servers/Kestrel/Transport.Sockets/src/Internal/SocketConnection.cs index 8074739454..4088dab978 100644 --- a/src/Servers/Kestrel/Transport.Sockets/src/Internal/SocketConnection.cs +++ b/src/Servers/Kestrel/Transport.Sockets/src/Internal/SocketConnection.cs @@ -33,13 +33,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal private Task _processingTask; private readonly TaskCompletionSource _waitForConnectionClosedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); private bool _connectionClosed; + private readonly bool _waitForData; internal SocketConnection(Socket socket, MemoryPool memoryPool, PipeScheduler scheduler, ISocketsTrace trace, long? maxReadBufferSize = null, - long? maxWriteBufferSize = null) + long? maxWriteBufferSize = null, + bool waitForData = true) { Debug.Assert(socket != null); Debug.Assert(memoryPool != null); @@ -48,6 +50,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal _socket = socket; MemoryPool = memoryPool; _trace = trace; + _waitForData = waitForData; LocalEndPoint = _socket.LocalEndPoint; RemoteEndPoint = _socket.RemoteEndPoint; @@ -186,8 +189,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal var input = Input; while (true) { - // Wait for data before allocating a buffer. - await _receiver.WaitForDataAsync(); + if (_waitForData) + { + // Wait for data before allocating a buffer. + await _receiver.WaitForDataAsync(); + } // Ensure we have some reasonable amount of buffer space var buffer = input.GetMemory(MinAllocBufferSize); diff --git a/src/Servers/Kestrel/Transport.Sockets/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.csproj b/src/Servers/Kestrel/Transport.Sockets/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.csproj index ecdca4391b..278fed94dd 100644 --- a/src/Servers/Kestrel/Transport.Sockets/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.csproj +++ b/src/Servers/Kestrel/Transport.Sockets/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.csproj @@ -1,4 +1,4 @@ - + Managed socket transport for the ASP.NET Core Kestrel cross-platform web server. @@ -14,7 +14,7 @@ - + diff --git a/src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionListener.cs b/src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionListener.cs index ccdb774674..4c49a90233 100644 --- a/src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionListener.cs +++ b/src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionListener.cs @@ -62,6 +62,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets throw new InvalidOperationException(SocketsStrings.TransportAlreadyBound); } + // Check if EndPoint is a FileHandleEndpoint before attempting to access EndPoint.AddressFamily + // since that will throw an NotImplementedException. + if (EndPoint is FileHandleEndPoint) + { + throw new NotSupportedException(SocketsStrings.FileHandleEndPointNotSupported); + } + Socket listenSocket; // Unix domain sockets are unspecified @@ -86,7 +93,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets EndPoint = listenSocket.LocalEndPoint; - listenSocket.Listen(512); + listenSocket.Listen(_options.Backlog); _listenSocket = listenSocket; } @@ -105,7 +112,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets acceptSocket.NoDelay = _options.NoDelay; } - var connection = new SocketConnection(acceptSocket, _memoryPool, _schedulers[_schedulerIndex], _trace, _options.MaxReadBufferSize, _options.MaxWriteBufferSize); + var connection = new SocketConnection(acceptSocket, _memoryPool, _schedulers[_schedulerIndex], _trace, + _options.MaxReadBufferSize, _options.MaxWriteBufferSize, _options.WaitForDataBeforeAllocatingBuffer); connection.Start(); diff --git a/src/Servers/Kestrel/Transport.Sockets/src/SocketTransportOptions.cs b/src/Servers/Kestrel/Transport.Sockets/src/SocketTransportOptions.cs index 4adb16ebfb..957876ca59 100644 --- a/src/Servers/Kestrel/Transport.Sockets/src/SocketTransportOptions.cs +++ b/src/Servers/Kestrel/Transport.Sockets/src/SocketTransportOptions.cs @@ -16,6 +16,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets /// public int IOQueueCount { get; set; } = Math.Min(Environment.ProcessorCount, 16); + /// + /// Wait until there is data available to allocate a buffer. Setting this to false can increase throughput at the cost of increased memory usage. + /// + /// + /// Defaults to true. + /// + public bool WaitForDataBeforeAllocatingBuffer { get; set; } = true; + /// /// Set to false to enable Nagle's algorithm for all connections. /// @@ -24,6 +32,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets /// public bool NoDelay { get; set; } = true; + /// + /// The maximum length of the pending connection queue. + /// + /// + /// Defaults to 512. + /// + public int Backlog { get; set; } = 512; + public long? MaxReadBufferSize { get; set; } = 1024 * 1024; public long? MaxWriteBufferSize { get; set; } = 64 * 1024; diff --git a/src/Servers/Kestrel/Transport.Sockets/src/SocketsStrings.resx b/src/Servers/Kestrel/Transport.Sockets/src/SocketsStrings.resx index 52b26c66bc..5f1475a1cf 100644 --- a/src/Servers/Kestrel/Transport.Sockets/src/SocketsStrings.resx +++ b/src/Servers/Kestrel/Transport.Sockets/src/SocketsStrings.resx @@ -117,10 +117,13 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + The Socket transport does not support binding to file handles. Consider using the libuv transport instead. + Only ListenType.IPEndPoint is supported by the Socket Transport. https://go.microsoft.com/fwlink/?linkid=874850 Transport is already bound. - \ No newline at end of file + diff --git a/src/Servers/Kestrel/Transport.Sockets/src/WebHostBuilderSocketExtensions.cs b/src/Servers/Kestrel/Transport.Sockets/src/WebHostBuilderSocketExtensions.cs index d073f91aa4..8ee49f198d 100644 --- a/src/Servers/Kestrel/Transport.Sockets/src/WebHostBuilderSocketExtensions.cs +++ b/src/Servers/Kestrel/Transport.Sockets/src/WebHostBuilderSocketExtensions.cs @@ -8,6 +8,9 @@ using Microsoft.Extensions.DependencyInjection; namespace Microsoft.AspNetCore.Hosting { + /// + /// extension methods to configure the Socket transport to be used by Kestrel. + /// public static class WebHostBuilderSocketExtensions { /// diff --git a/src/Servers/Kestrel/perf/Kestrel.Performance/Http1ConnectionBenchmark.cs b/src/Servers/Kestrel/perf/Kestrel.Performance/Http1ConnectionBenchmark.cs index 887aad3939..d54f4a879f 100644 --- a/src/Servers/Kestrel/perf/Kestrel.Performance/Http1ConnectionBenchmark.cs +++ b/src/Servers/Kestrel/perf/Kestrel.Performance/Http1ConnectionBenchmark.cs @@ -4,12 +4,14 @@ using System; using System.Buffers; using System.IO.Pipelines; +using System.Net.Http; using BenchmarkDotNet.Attributes; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; +using HttpMethod = Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpMethod; namespace Microsoft.AspNetCore.Server.Kestrel.Performance { @@ -104,14 +106,24 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance RequestHandler = requestHandler; } - public void OnHeader(Span name, Span value) + public void OnHeader(ReadOnlySpan name, ReadOnlySpan value) => RequestHandler.Connection.OnHeader(name, value); - public void OnHeadersComplete() + public void OnHeadersComplete(bool endStream) => RequestHandler.Connection.OnHeadersComplete(); public void OnStartLine(HttpMethod method, HttpVersion version, Span target, Span path, Span query, Span customMethod, bool pathEncoded) => RequestHandler.Connection.OnStartLine(method, version, target, path, query, customMethod, pathEncoded); + + public void OnStaticIndexedHeader(int index) + { + throw new NotImplementedException(); + } + + public void OnStaticIndexedHeader(int index, ReadOnlySpan value) + { + throw new NotImplementedException(); + } } } } diff --git a/src/Servers/Kestrel/perf/Kestrel.Performance/Http2ConnectionBenchmark.cs b/src/Servers/Kestrel/perf/Kestrel.Performance/Http2ConnectionBenchmark.cs new file mode 100644 index 0000000000..b3e8f15403 --- /dev/null +++ b/src/Servers/Kestrel/perf/Kestrel.Performance/Http2ConnectionBenchmark.cs @@ -0,0 +1,183 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Buffers; +using System.Buffers.Binary; +using System.Diagnostics; +using System.IO; +using System.IO.Pipelines; +using System.Linq; +using System.Threading.Tasks; +using BenchmarkDotNet.Attributes; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.Server.Kestrel.Core; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; +using Microsoft.AspNetCore.Testing; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Primitives; +using Microsoft.Net.Http.Headers; + +namespace Microsoft.AspNetCore.Server.Kestrel.Performance +{ + public class Http2ConnectionBenchmark + { + private MemoryPool _memoryPool; + private HttpRequestHeaders _httpRequestHeaders; + private Http2Connection _connection; + private Http2HeadersEnumerator _requestHeadersEnumerator; + private int _currentStreamId; + private byte[] _headersBuffer; + private DuplexPipe.DuplexPipePair _connectionPair; + private Http2Frame _httpFrame; + private string _responseData; + private int _dataWritten; + + [Params(0, 10, 1024 * 1024)] + public int ResponseDataLength { get; set; } + + [GlobalSetup] + public void GlobalSetup() + { + _memoryPool = SlabMemoryPoolFactory.Create(); + _httpFrame = new Http2Frame(); + _responseData = new string('!', ResponseDataLength); + + var options = new PipeOptions(_memoryPool, readerScheduler: PipeScheduler.Inline, writerScheduler: PipeScheduler.Inline, useSynchronizationContext: false); + + _connectionPair = DuplexPipe.CreateConnectionPair(options, options); + + _httpRequestHeaders = new HttpRequestHeaders(); + _httpRequestHeaders.Append(HeaderNames.Method, new StringValues("GET")); + _httpRequestHeaders.Append(HeaderNames.Path, new StringValues("/")); + _httpRequestHeaders.Append(HeaderNames.Scheme, new StringValues("http")); + _httpRequestHeaders.Append(HeaderNames.Authority, new StringValues("localhost:80")); + + _headersBuffer = new byte[1024 * 16]; + + var serviceContext = new ServiceContext + { + DateHeaderValueManager = new DateHeaderValueManager(), + ServerOptions = new KestrelServerOptions(), + Log = new KestrelTrace(NullLogger.Instance), + SystemClock = new MockSystemClock() + }; + serviceContext.DateHeaderValueManager.OnHeartbeat(default); + + _connection = new Http2Connection(new HttpConnectionContext + { + MemoryPool = _memoryPool, + ConnectionId = "TestConnectionId", + Protocols = HttpProtocols.Http2, + Transport = _connectionPair.Transport, + ServiceContext = serviceContext, + ConnectionFeatures = new FeatureCollection(), + TimeoutControl = new MockTimeoutControl(), + }); + + _requestHeadersEnumerator = new Http2HeadersEnumerator(); + + _currentStreamId = 1; + + _ = _connection.ProcessRequestsAsync(new DummyApplication(c => ResponseDataLength == 0 ? Task.CompletedTask : c.Response.WriteAsync(_responseData), new MockHttpContextFactory())); + + _connectionPair.Application.Output.Write(Http2Connection.ClientPreface); + _connectionPair.Application.Output.WriteSettings(new Http2PeerSettings + { + InitialWindowSize = 2147483647 + }); + _connectionPair.Application.Output.FlushAsync().GetAwaiter().GetResult(); + + // Read past connection setup frames + ReceiveFrameAsync(_connectionPair.Application.Input, _httpFrame).GetAwaiter().GetResult(); + Debug.Assert(_httpFrame.Type == Http2FrameType.SETTINGS); + ReceiveFrameAsync(_connectionPair.Application.Input, _httpFrame).GetAwaiter().GetResult(); + Debug.Assert(_httpFrame.Type == Http2FrameType.WINDOW_UPDATE); + ReceiveFrameAsync(_connectionPair.Application.Input, _httpFrame).GetAwaiter().GetResult(); + Debug.Assert(_httpFrame.Type == Http2FrameType.SETTINGS); + } + + [Benchmark] + public async Task EmptyRequest() + { + _requestHeadersEnumerator.Initialize(_httpRequestHeaders); + _requestHeadersEnumerator.MoveNext(); + _connectionPair.Application.Output.WriteStartStream(streamId: _currentStreamId, _requestHeadersEnumerator, _headersBuffer, endStream: true, frame: _httpFrame); + await _connectionPair.Application.Output.FlushAsync(); + + while (true) + { + await ReceiveFrameAsync(_connectionPair.Application.Input, _httpFrame); + + if (_httpFrame.StreamId != _currentStreamId && _httpFrame.StreamId != 0) + { + throw new Exception($"Unexpected stream ID: {_httpFrame.StreamId}"); + } + + if (_httpFrame.Type == Http2FrameType.DATA) + { + _dataWritten += _httpFrame.DataPayloadLength; + } + + if (_dataWritten > 1024 * 32) + { + _connectionPair.Application.Output.WriteWindowUpdateAsync(streamId: 0, _dataWritten, _httpFrame); + await _connectionPair.Application.Output.FlushAsync(); + + _dataWritten = 0; + } + + if ((_httpFrame.HeadersFlags & Http2HeadersFrameFlags.END_STREAM) == Http2HeadersFrameFlags.END_STREAM) + { + break; + } + } + + _currentStreamId += 2; + } + + internal async ValueTask ReceiveFrameAsync(PipeReader pipeReader, Http2Frame frame, uint maxFrameSize = Http2PeerSettings.DefaultMaxFrameSize) + { + while (true) + { + var result = await pipeReader.ReadAsync(); + var buffer = result.Buffer; + var consumed = buffer.Start; + var examined = buffer.Start; + + try + { + if (Http2FrameReader.TryReadFrame(ref buffer, frame, maxFrameSize, out var framePayload)) + { + consumed = examined = framePayload.End; + return; + } + else + { + examined = buffer.End; + } + + if (result.IsCompleted) + { + throw new IOException("The reader completed without returning a frame."); + } + } + finally + { + pipeReader.AdvanceTo(consumed, examined); + } + } + } + + [GlobalCleanup] + public void Dispose() + { + _connectionPair.Application.Output.Complete(); + _memoryPool?.Dispose(); + } + } +} diff --git a/src/Servers/Kestrel/perf/Kestrel.Performance/Http2FrameWriterBenchmark.cs b/src/Servers/Kestrel/perf/Kestrel.Performance/Http2FrameWriterBenchmark.cs new file mode 100644 index 0000000000..839558d1a3 --- /dev/null +++ b/src/Servers/Kestrel/perf/Kestrel.Performance/Http2FrameWriterBenchmark.cs @@ -0,0 +1,59 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Buffers; +using System.IO.Pipelines; +using BenchmarkDotNet.Attributes; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.FlowControl; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; +using Microsoft.Extensions.Logging.Abstractions; + +namespace Microsoft.AspNetCore.Server.Kestrel.Performance +{ + public class Http2FrameWriterBenchmark + { + private MemoryPool _memoryPool; + private Pipe _pipe; + private Http2FrameWriter _frameWriter; + private HttpResponseHeaders _responseHeaders; + + [GlobalSetup] + public void GlobalSetup() + { + _memoryPool = SlabMemoryPoolFactory.Create(); + + var options = new PipeOptions(_memoryPool, readerScheduler: PipeScheduler.Inline, writerScheduler: PipeScheduler.Inline, useSynchronizationContext: false); + _pipe = new Pipe(options); + + _frameWriter = new Http2FrameWriter( + new NullPipeWriter(), + connectionContext: null, + http2Connection: null, + new OutputFlowControl(new SingleAwaitableProvider(), initialWindowSize: uint.MaxValue), + timeoutControl: null, + minResponseDataRate: null, + "TestConnectionId", + _memoryPool, + new KestrelTrace(NullLogger.Instance)); + + _responseHeaders = new HttpResponseHeaders(); + _responseHeaders.HeaderContentType = "application/json"; + _responseHeaders.HeaderContentLength = "1024"; + } + + [Benchmark] + public void WriteResponseHeaders() + { + _frameWriter.WriteResponseHeaders(0, 200, Http2HeadersFrameFlags.END_HEADERS, _responseHeaders); + } + + [GlobalCleanup] + public void Dispose() + { + _pipe.Writer.Complete(); + _memoryPool?.Dispose(); + } + } +} diff --git a/src/Servers/Kestrel/perf/Kestrel.Performance/HttpParserBenchmark.cs b/src/Servers/Kestrel/perf/Kestrel.Performance/HttpParserBenchmark.cs index c5eb24baf8..3960ebe388 100644 --- a/src/Servers/Kestrel/perf/Kestrel.Performance/HttpParserBenchmark.cs +++ b/src/Servers/Kestrel/perf/Kestrel.Performance/HttpParserBenchmark.cs @@ -3,8 +3,10 @@ using System; using System.Buffers; +using System.Net.Http; using BenchmarkDotNet.Attributes; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; +using HttpMethod = Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpMethod; namespace Microsoft.AspNetCore.Server.Kestrel.Performance { @@ -69,14 +71,24 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance { } - public void OnHeader(Span name, Span value) + public void OnHeader(ReadOnlySpan name, ReadOnlySpan value) { } - public void OnHeadersComplete() + public void OnHeadersComplete(bool endStream) { } + public void OnStaticIndexedHeader(int index) + { + throw new NotImplementedException(); + } + + public void OnStaticIndexedHeader(int index, ReadOnlySpan value) + { + throw new NotImplementedException(); + } + private struct Adapter : IHttpRequestLineHandler, IHttpHeadersHandler { public HttpParserBenchmark RequestHandler; @@ -86,14 +98,24 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance RequestHandler = requestHandler; } - public void OnHeader(Span name, Span value) + public void OnHeader(ReadOnlySpan name, ReadOnlySpan value) => RequestHandler.OnHeader(name, value); - public void OnHeadersComplete() - => RequestHandler.OnHeadersComplete(); + public void OnHeadersComplete(bool endStream) + => RequestHandler.OnHeadersComplete(endStream); public void OnStartLine(HttpMethod method, HttpVersion version, Span target, Span path, Span query, Span customMethod, bool pathEncoded) => RequestHandler.OnStartLine(method, version, target, path, query, customMethod, pathEncoded); + + public void OnStaticIndexedHeader(int index) + { + throw new NotImplementedException(); + } + + public void OnStaticIndexedHeader(int index, ReadOnlySpan value) + { + throw new NotImplementedException(); + } } } } diff --git a/src/Servers/Kestrel/perf/Kestrel.Performance/IntegerDecoderBenchmark.cs b/src/Servers/Kestrel/perf/Kestrel.Performance/IntegerDecoderBenchmark.cs index 6fc22390c0..e41d076ec6 100644 --- a/src/Servers/Kestrel/perf/Kestrel.Performance/IntegerDecoderBenchmark.cs +++ b/src/Servers/Kestrel/perf/Kestrel.Performance/IntegerDecoderBenchmark.cs @@ -1,8 +1,8 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System.Net.Http.HPack; using BenchmarkDotNet.Attributes; -using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack; namespace Microsoft.AspNetCore.Server.Kestrel.Performance { diff --git a/src/Servers/Kestrel/perf/Kestrel.Performance/Microsoft.AspNetCore.Server.Kestrel.Performance.csproj b/src/Servers/Kestrel/perf/Kestrel.Performance/Microsoft.AspNetCore.Server.Kestrel.Performance.csproj index 61d180a73c..ef22ae3002 100644 --- a/src/Servers/Kestrel/perf/Kestrel.Performance/Microsoft.AspNetCore.Server.Kestrel.Performance.csproj +++ b/src/Servers/Kestrel/perf/Kestrel.Performance/Microsoft.AspNetCore.Server.Kestrel.Performance.csproj @@ -10,13 +10,14 @@ + + - @@ -24,11 +25,12 @@ - + + diff --git a/src/Servers/Kestrel/perf/Kestrel.Performance/Mocks/MockDuplexPipe.cs b/src/Servers/Kestrel/perf/Kestrel.Performance/Mocks/MockDuplexPipe.cs new file mode 100644 index 0000000000..86f6dc3112 --- /dev/null +++ b/src/Servers/Kestrel/perf/Kestrel.Performance/Mocks/MockDuplexPipe.cs @@ -0,0 +1,19 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.IO.Pipelines; + +namespace Microsoft.AspNetCore.Server.Kestrel.Performance +{ + internal class MockDuplexPipe : IDuplexPipe + { + public MockDuplexPipe(PipeReader input, PipeWriter output) + { + Input = input; + Output = output; + } + + public PipeReader Input { get; } + public PipeWriter Output { get; } + } +} diff --git a/src/Servers/Kestrel/perf/Kestrel.Performance/Mocks/MockHttpContextFactory.cs b/src/Servers/Kestrel/perf/Kestrel.Performance/Mocks/MockHttpContextFactory.cs new file mode 100644 index 0000000000..e89076aba2 --- /dev/null +++ b/src/Servers/Kestrel/perf/Kestrel.Performance/Mocks/MockHttpContextFactory.cs @@ -0,0 +1,42 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; + +namespace Microsoft.AspNetCore.Server.Kestrel.Performance +{ + public class MockHttpContextFactory : IHttpContextFactory + { + private readonly object _lock = new object(); + private readonly Queue _cache = new Queue(); + + public HttpContext Create(IFeatureCollection featureCollection) + { + DefaultHttpContext httpContext; + + lock (_lock) + { + if (!_cache.TryDequeue(out httpContext)) + { + httpContext = new DefaultHttpContext(); + } + } + + httpContext.Initialize(featureCollection); + return httpContext; + } + + public void Dispose(HttpContext httpContext) + { + lock (_lock) + { + var defaultHttpContext = (DefaultHttpContext)httpContext; + + defaultHttpContext.Uninitialize(); + _cache.Enqueue(defaultHttpContext); + } + } + } +} diff --git a/src/Servers/Kestrel/perf/Kestrel.Performance/Mocks/MockSystemClock.cs b/src/Servers/Kestrel/perf/Kestrel.Performance/Mocks/MockSystemClock.cs new file mode 100644 index 0000000000..960d2cba95 --- /dev/null +++ b/src/Servers/Kestrel/perf/Kestrel.Performance/Mocks/MockSystemClock.cs @@ -0,0 +1,15 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; + +namespace Microsoft.AspNetCore.Server.Kestrel.Performance +{ + internal class MockSystemClock : ISystemClock + { + public DateTimeOffset UtcNow { get; } + public long UtcNowTicks { get; } + public DateTimeOffset UtcNowUnsynchronized { get; } + } +} diff --git a/src/Servers/Kestrel/perf/Kestrel.Performance/Mocks/MockTimeoutControl.cs b/src/Servers/Kestrel/perf/Kestrel.Performance/Mocks/MockTimeoutControl.cs new file mode 100644 index 0000000000..54865db3c3 --- /dev/null +++ b/src/Servers/Kestrel/perf/Kestrel.Performance/Mocks/MockTimeoutControl.cs @@ -0,0 +1,62 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNetCore.Server.Kestrel.Core; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.FlowControl; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; + +namespace Microsoft.AspNetCore.Server.Kestrel.Performance +{ + internal class MockTimeoutControl : ITimeoutControl + { + public TimeoutReason TimerReason { get; } = TimeoutReason.KeepAlive; + + public void BytesRead(long count) + { + } + + public void BytesWrittenToBuffer(MinDataRate minRate, long count) + { + } + + public void CancelTimeout() + { + } + + public void InitializeHttp2(InputFlowControl connectionInputFlowControl) + { + } + + public void ResetTimeout(long ticks, TimeoutReason timeoutReason) + { + } + + public void SetTimeout(long ticks, TimeoutReason timeoutReason) + { + } + + public void StartRequestBody(MinDataRate minRate) + { + } + + public void StartTimingRead() + { + } + + public void StartTimingWrite() + { + } + + public void StopRequestBody() + { + } + + public void StopTimingRead() + { + } + + public void StopTimingWrite() + { + } + } +} diff --git a/src/Servers/Kestrel/perf/Kestrel.Performance/Mocks/MockTrace.cs b/src/Servers/Kestrel/perf/Kestrel.Performance/Mocks/MockTrace.cs index d2514f998f..bd93636c83 100644 --- a/src/Servers/Kestrel/perf/Kestrel.Performance/Mocks/MockTrace.cs +++ b/src/Servers/Kestrel/perf/Kestrel.Performance/Mocks/MockTrace.cs @@ -2,10 +2,10 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Net.Http.HPack; using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2; -using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; using Microsoft.Extensions.Logging; @@ -38,7 +38,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance public void NotAllConnectionsAborted() { } public void NotAllConnectionsClosedGracefully() { } public void RequestProcessingError(string connectionId, Exception ex) { } - public void HeartbeatSlow(TimeSpan interval, DateTimeOffset now) { } + public void HeartbeatSlow(TimeSpan heartbeatDuration, TimeSpan interval, DateTimeOffset now) { } public void ApplicationNeverCompleted(string connectionId) { } public void RequestBodyStart(string connectionId, string traceIdentifier) { } public void RequestBodyDone(string connectionId, string traceIdentifier) { } diff --git a/src/Servers/Kestrel/perf/Kestrel.Performance/Mocks/NullParser.cs b/src/Servers/Kestrel/perf/Kestrel.Performance/Mocks/NullParser.cs index 288588f3b1..53bae7b1b5 100644 --- a/src/Servers/Kestrel/perf/Kestrel.Performance/Mocks/NullParser.cs +++ b/src/Servers/Kestrel/perf/Kestrel.Performance/Mocks/NullParser.cs @@ -3,8 +3,10 @@ using System; using System.Buffers; +using System.Net.Http; using System.Text; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; +using HttpMethod = Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpMethod; namespace Microsoft.AspNetCore.Server.Kestrel.Performance { @@ -26,7 +28,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance handler.OnHeader(new Span(_hostHeaderName), new Span(_hostHeaderValue)); handler.OnHeader(new Span(_acceptHeaderName), new Span(_acceptHeaderValue)); handler.OnHeader(new Span(_connectionHeaderName), new Span(_connectionHeaderValue)); - handler.OnHeadersComplete(); + handler.OnHeadersComplete(endStream: false); return true; } diff --git a/src/Servers/Kestrel/perf/Kestrel.Performance/Mocks/NullPipeWriter.cs b/src/Servers/Kestrel/perf/Kestrel.Performance/Mocks/NullPipeWriter.cs new file mode 100644 index 0000000000..c982ec4902 --- /dev/null +++ b/src/Servers/Kestrel/perf/Kestrel.Performance/Mocks/NullPipeWriter.cs @@ -0,0 +1,43 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO.Pipelines; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.AspNetCore.Server.Kestrel.Performance +{ + internal class NullPipeWriter : PipeWriter + { + // Should be large enough for any content attempting to write to the buffer + private readonly byte[] _buffer = new byte[1024 * 128]; + + public override void Advance(int bytes) + { + } + + public override void CancelPendingFlush() + { + } + + public override void Complete(Exception exception = null) + { + } + + public override ValueTask FlushAsync(CancellationToken cancellationToken = default) + { + return new ValueTask(new FlushResult(false, true)); + } + + public override Memory GetMemory(int sizeHint = 0) + { + return _buffer; + } + + public override Span GetSpan(int sizeHint = 0) + { + return _buffer; + } + } +} diff --git a/src/Servers/Kestrel/perf/PlatformBenchmarks/AsciiString.cs b/src/Servers/Kestrel/perf/PlatformBenchmarks/AsciiString.cs deleted file mode 100644 index 2b16c54e42..0000000000 --- a/src/Servers/Kestrel/perf/PlatformBenchmarks/AsciiString.cs +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Text; - -namespace PlatformBenchmarks -{ - public readonly struct AsciiString : IEquatable - { - private readonly byte[] _data; - - public AsciiString(string s) => _data = Encoding.ASCII.GetBytes(s); - - public int Length => _data.Length; - - public ReadOnlySpan AsSpan() => _data; - - public static implicit operator ReadOnlySpan(AsciiString str) => str._data; - public static implicit operator byte[] (AsciiString str) => str._data; - - public static implicit operator AsciiString(string str) => new AsciiString(str); - - public override string ToString() => Encoding.ASCII.GetString(_data); - public static explicit operator string(AsciiString str) => str.ToString(); - - public bool Equals(AsciiString other) => ReferenceEquals(_data, other._data) || SequenceEqual(_data, other._data); - private bool SequenceEqual(byte[] data1, byte[] data2) => new Span(data1).SequenceEqual(data2); - - public static bool operator ==(AsciiString a, AsciiString b) => a.Equals(b); - public static bool operator !=(AsciiString a, AsciiString b) => !a.Equals(b); - public override bool Equals(object other) => (other is AsciiString) && Equals((AsciiString)other); - - public override int GetHashCode() - { - // Copied from x64 version of string.GetLegacyNonRandomizedHashCode() - // https://github.com/dotnet/coreclr/blob/master/src/mscorlib/src/System/String.Comparison.cs - var data = _data; - int hash1 = 5381; - int hash2 = hash1; - foreach (int b in data) - { - hash1 = ((hash1 << 5) + hash1) ^ b; - } - return hash1 + (hash2 * 1566083941); - } - - } -} diff --git a/src/Servers/Kestrel/perf/PlatformBenchmarks/BenchmarkApplication.HttpConnection.cs b/src/Servers/Kestrel/perf/PlatformBenchmarks/BenchmarkApplication.HttpConnection.cs deleted file mode 100644 index 969272d548..0000000000 --- a/src/Servers/Kestrel/perf/PlatformBenchmarks/BenchmarkApplication.HttpConnection.cs +++ /dev/null @@ -1,200 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Buffers; -using System.IO.Pipelines; -using System.Runtime.CompilerServices; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; - -namespace PlatformBenchmarks -{ - public partial class BenchmarkApplication : IHttpConnection - { - private State _state; - - public PipeReader Reader { get; set; } - public PipeWriter Writer { get; set; } - - private HttpParser Parser { get; } = new HttpParser(); - - public async Task ExecuteAsync() - { - try - { - await ProcessRequestsAsync(); - - Reader.Complete(); - } - catch (Exception ex) - { - Reader.Complete(ex); - } - finally - { - Writer.Complete(); - } - } - - private async Task ProcessRequestsAsync() - { - while (true) - { - var task = Reader.ReadAsync(); - - if (!task.IsCompleted) - { - // No more data in the input - await OnReadCompletedAsync(); - } - - var result = await task; - var buffer = result.Buffer; - while (true) - { - if (!ParseHttpRequest(ref buffer, result.IsCompleted, out var examined)) - { - return; - } - - if (_state == State.Body) - { - await ProcessRequestAsync(); - - _state = State.StartLine; - - if (!buffer.IsEmpty) - { - // More input data to parse - continue; - } - } - - // No more input or incomplete data, Advance the Reader - Reader.AdvanceTo(buffer.Start, examined); - break; - } - } - } - - private bool ParseHttpRequest(ref ReadOnlySequence buffer, bool isCompleted, out SequencePosition examined) - { - examined = buffer.End; - - var consumed = buffer.Start; - var state = _state; - - if (!buffer.IsEmpty) - { - if (state == State.StartLine) - { - if (Parser.ParseRequestLine(new ParsingAdapter(this), buffer, out consumed, out examined)) - { - state = State.Headers; - } - - buffer = buffer.Slice(consumed); - } - - if (state == State.Headers) - { - var reader = new SequenceReader(buffer); - var success = Parser.ParseHeaders(new ParsingAdapter(this), ref reader); - - consumed = reader.Position; - if (success) - { - examined = consumed; - state = State.Body; - } - else - { - examined = buffer.End; - } - - buffer = buffer.Slice(consumed); - } - - if (state != State.Body && isCompleted) - { - ThrowUnexpectedEndOfData(); - } - } - else if (isCompleted) - { - return false; - } - - _state = state; - return true; - } - - public void OnHeader(Span name, Span value) - { - } - - public void OnHeadersComplete() - { - } - - public async ValueTask OnReadCompletedAsync() - { - await Writer.FlushAsync(); - } - - private static void ThrowUnexpectedEndOfData() - { - throw new InvalidOperationException("Unexpected end of data!"); - } - - private enum State - { - StartLine, - Headers, - Body - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static BufferWriter GetWriter(PipeWriter pipeWriter) - => new BufferWriter(new WriterAdapter(pipeWriter)); - - private struct WriterAdapter : IBufferWriter - { - public PipeWriter Writer; - - public WriterAdapter(PipeWriter writer) - => Writer = writer; - - public void Advance(int count) - => Writer.Advance(count); - - public Memory GetMemory(int sizeHint = 0) - => Writer.GetMemory(sizeHint); - - public Span GetSpan(int sizeHint = 0) - => Writer.GetSpan(sizeHint); - } - - private struct ParsingAdapter : IHttpRequestLineHandler, IHttpHeadersHandler - { - public BenchmarkApplication RequestHandler; - - public ParsingAdapter(BenchmarkApplication requestHandler) - => RequestHandler = requestHandler; - - public void OnHeader(Span name, Span value) - => RequestHandler.OnHeader(name, value); - - public void OnStartLine(HttpMethod method, HttpVersion version, Span target, Span path, Span query, Span customMethod, bool pathEncoded) - => RequestHandler.OnStartLine(method, version, target, path, query, customMethod, pathEncoded); - - public void OnHeadersComplete() - => RequestHandler.OnHeadersComplete(); -#if !NETCOREAPP -#error This is a .NET Core 3.0 application and needs to be compiled for $(DefaultNetCoreTargetFramework) -#endif - } - } - -} diff --git a/src/Servers/Kestrel/perf/PlatformBenchmarks/BenchmarkApplication.cs b/src/Servers/Kestrel/perf/PlatformBenchmarks/BenchmarkApplication.cs deleted file mode 100644 index d8e539f51d..0000000000 --- a/src/Servers/Kestrel/perf/PlatformBenchmarks/BenchmarkApplication.cs +++ /dev/null @@ -1,165 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.IO.Pipelines; -using System.Text.Json; -using System.Text.Json.Serialization; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; - -namespace PlatformBenchmarks -{ - public partial class BenchmarkApplication - { - private readonly static AsciiString _applicationName = "Kestrel Platform-Level Application"; - public static AsciiString ApplicationName => _applicationName; - - private readonly static AsciiString _crlf = "\r\n"; - private readonly static AsciiString _eoh = "\r\n\r\n"; // End Of Headers - private readonly static AsciiString _http11OK = "HTTP/1.1 200 OK\r\n"; - private readonly static AsciiString _headerServer = "Server: Custom"; - private readonly static AsciiString _headerContentLength = "Content-Length: "; - private readonly static AsciiString _headerContentLengthZero = "Content-Length: 0\r\n"; - private readonly static AsciiString _headerContentTypeText = "Content-Type: text/plain\r\n"; - private readonly static AsciiString _headerContentTypeJson = "Content-Type: application/json\r\n"; - - private readonly static AsciiString _plainTextBody = "Hello, World!"; - - private static readonly JsonSerializerOptions SerializerOptions = new JsonSerializerOptions(); - - public static class Paths - { - public readonly static AsciiString Plaintext = "/plaintext"; - public readonly static AsciiString Json = "/json"; - } - - private RequestType _requestType; - - public void OnStartLine(HttpMethod method, HttpVersion version, Span target, Span path, Span query, Span customMethod, bool pathEncoded) - { - var requestType = RequestType.NotRecognized; - if (method == HttpMethod.Get) - { - if (Paths.Plaintext.Length <= path.Length && path.StartsWith(Paths.Plaintext)) - { - requestType = RequestType.PlainText; - } - else if (Paths.Json.Length <= path.Length && path.StartsWith(Paths.Json)) - { - requestType = RequestType.Json; - } - } - - _requestType = requestType; - } - - public ValueTask ProcessRequestAsync() - { - if (_requestType == RequestType.PlainText) - { - PlainText(Writer); - } - else if (_requestType == RequestType.Json) - { - Json(Writer); - } - else - { - Default(Writer); - } - - return default; - } - - private static void PlainText(PipeWriter pipeWriter) - { - var writer = GetWriter(pipeWriter); - - // HTTP 1.1 OK - writer.Write(_http11OK); - - // Server headers - writer.Write(_headerServer); - - // Date header - writer.Write(DateHeader.HeaderBytes); - - // Content-Type header - writer.Write(_headerContentTypeText); - - // Content-Length header - writer.Write(_headerContentLength); - writer.WriteNumeric((uint)_plainTextBody.Length); - - // End of headers - writer.Write(_eoh); - - // Body - writer.Write(_plainTextBody); - writer.Commit(); - } - - private static void Json(PipeWriter pipeWriter) - { - var writer = GetWriter(pipeWriter); - - // HTTP 1.1 OK - writer.Write(_http11OK); - - // Server headers - writer.Write(_headerServer); - - // Date header - writer.Write(DateHeader.HeaderBytes); - - // Content-Type header - writer.Write(_headerContentTypeJson); - - // Content-Length header - writer.Write(_headerContentLength); - var jsonPayload = JsonSerializer.SerializeToUtf8Bytes(new JsonMessage { message = "Hello, World!" }, SerializerOptions); - writer.WriteNumeric((uint)jsonPayload.Length); - - // End of headers - writer.Write(_eoh); - - // Body - writer.Write(jsonPayload); - writer.Commit(); - } - - private static void Default(PipeWriter pipeWriter) - { - var writer = GetWriter(pipeWriter); - - // HTTP 1.1 OK - writer.Write(_http11OK); - - // Server headers - writer.Write(_headerServer); - - // Date header - writer.Write(DateHeader.HeaderBytes); - - // Content-Length 0 - writer.Write(_headerContentLengthZero); - - // End of headers - writer.Write(_crlf); - writer.Commit(); - } - - private enum RequestType - { - NotRecognized, - PlainText, - Json - } - - public struct JsonMessage - { - public string message { get; set; } - } - } -} diff --git a/src/Servers/Kestrel/perf/PlatformBenchmarks/BenchmarkConfigurationHelpers.cs b/src/Servers/Kestrel/perf/PlatformBenchmarks/BenchmarkConfigurationHelpers.cs deleted file mode 100644 index 37a45db13b..0000000000 --- a/src/Servers/Kestrel/perf/PlatformBenchmarks/BenchmarkConfigurationHelpers.cs +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Net; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Configuration; - -namespace PlatformBenchmarks -{ - public static class BenchmarkConfigurationHelpers - { - public static IWebHostBuilder UseBenchmarksConfiguration(this IWebHostBuilder builder, IConfiguration configuration) - { - builder.UseConfiguration(configuration); - - // Handle the transport type - var webHost = builder.GetSetting("KestrelTransport"); - - // Handle the thread count - var threadCountRaw = builder.GetSetting("threadCount"); - int? theadCount = null; - - if (!string.IsNullOrEmpty(threadCountRaw) && - Int32.TryParse(threadCountRaw, out var value)) - { - theadCount = value; - } - - if (string.Equals(webHost, "Libuv", StringComparison.OrdinalIgnoreCase)) - { - builder.UseLibuv(options => - { - if (theadCount.HasValue) - { - options.ThreadCount = theadCount.Value; - } - }); - } - else if (string.Equals(webHost, "Sockets", StringComparison.OrdinalIgnoreCase)) - { - builder.UseSockets(options => - { - if (theadCount.HasValue) - { - options.IOQueueCount = theadCount.Value; - } - }); - } - - return builder; - } - - public static IPEndPoint CreateIPEndPoint(this IConfiguration config) - { - var url = config["server.urls"] ?? config["urls"]; - - if (string.IsNullOrEmpty(url)) - { - return new IPEndPoint(IPAddress.Loopback, 8080); - } - - var address = BindingAddress.Parse(url); - - IPAddress ip; - - if (string.Equals(address.Host, "localhost", StringComparison.OrdinalIgnoreCase)) - { - ip = IPAddress.Loopback; - } - else if (!IPAddress.TryParse(address.Host, out ip)) - { - ip = IPAddress.IPv6Any; - } - - return new IPEndPoint(ip, address.Port); - } - } -} diff --git a/src/Servers/Kestrel/perf/PlatformBenchmarks/BufferExtensions.cs b/src/Servers/Kestrel/perf/PlatformBenchmarks/BufferExtensions.cs deleted file mode 100644 index 9551661831..0000000000 --- a/src/Servers/Kestrel/perf/PlatformBenchmarks/BufferExtensions.cs +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Buffers; -using System.Runtime.CompilerServices; - -namespace PlatformBenchmarks -{ - // Same as KestrelHttpServer\src\Kestrel.Core\Internal\Http\PipelineExtensions.cs - // However methods accept T : struct, IBufferWriter rather than PipeWriter. - // This allows a struct wrapper to turn CountingBufferWriter into a non-shared generic, - // while still offering the WriteNumeric extension. - - public static class BufferExtensions - { - private const int _maxULongByteLength = 20; - - [ThreadStatic] - private static byte[] _numericBytesScratch; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static unsafe void WriteNumeric(ref this BufferWriter buffer, uint number) - where T : struct, IBufferWriter - { - const byte AsciiDigitStart = (byte)'0'; - - var span = buffer.Span; - var bytesLeftInBlock = span.Length; - - // Fast path, try copying to the available memory directly - var advanceBy = 0; - fixed (byte* output = span) - { - var start = output; - if (number < 10 && bytesLeftInBlock >= 1) - { - start[0] = (byte)(number + AsciiDigitStart); - advanceBy = 1; - } - else if (number < 100 && bytesLeftInBlock >= 2) - { - var tens = (byte)((number * 205u) >> 11); // div10, valid to 1028 - - start[0] = (byte)(tens + AsciiDigitStart); - start[1] = (byte)(number - (tens * 10) + AsciiDigitStart); - advanceBy = 2; - } - else if (number < 1000 && bytesLeftInBlock >= 3) - { - var digit0 = (byte)((number * 41u) >> 12); // div100, valid to 1098 - var digits01 = (byte)((number * 205u) >> 11); // div10, valid to 1028 - - start[0] = (byte)(digit0 + AsciiDigitStart); - start[1] = (byte)(digits01 - (digit0 * 10) + AsciiDigitStart); - start[2] = (byte)(number - (digits01 * 10) + AsciiDigitStart); - advanceBy = 3; - } - } - - if (advanceBy > 0) - { - buffer.Advance(advanceBy); - } - else - { - WriteNumericMultiWrite(ref buffer, number); - } - } - - [MethodImpl(MethodImplOptions.NoInlining)] - private static void WriteNumericMultiWrite(ref this BufferWriter buffer, uint number) - where T : struct, IBufferWriter - { - const byte AsciiDigitStart = (byte)'0'; - - var value = number; - var position = _maxULongByteLength; - var byteBuffer = NumericBytesScratch; - do - { - // Consider using Math.DivRem() if available - var quotient = value / 10; - byteBuffer[--position] = (byte)(AsciiDigitStart + (value - quotient * 10)); // 0x30 = '0' - value = quotient; - } - while (value != 0); - - var length = _maxULongByteLength - position; - buffer.Write(new ReadOnlySpan(byteBuffer, position, length)); - } - - private static byte[] NumericBytesScratch => _numericBytesScratch ?? CreateNumericBytesScratch(); - - [MethodImpl(MethodImplOptions.NoInlining)] - private static byte[] CreateNumericBytesScratch() - { - var bytes = new byte[_maxULongByteLength]; - _numericBytesScratch = bytes; - return bytes; - } - } -} diff --git a/src/Servers/Kestrel/perf/PlatformBenchmarks/BufferWriter.cs b/src/Servers/Kestrel/perf/PlatformBenchmarks/BufferWriter.cs deleted file mode 100644 index c7d0893969..0000000000 --- a/src/Servers/Kestrel/perf/PlatformBenchmarks/BufferWriter.cs +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Buffers; -using System.Runtime.CompilerServices; - -namespace PlatformBenchmarks -{ - public ref struct BufferWriter where T : IBufferWriter - { - private T _output; - private Span _span; - private int _buffered; - - public BufferWriter(T output) - { - _buffered = 0; - _output = output; - _span = output.GetSpan(); - } - - public Span Span => _span; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Commit() - { - var buffered = _buffered; - if (buffered > 0) - { - _buffered = 0; - _output.Advance(buffered); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Advance(int count) - { - _buffered += count; - _span = _span.Slice(count); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Write(ReadOnlySpan source) - { - if (_span.Length >= source.Length) - { - source.CopyTo(_span); - Advance(source.Length); - } - else - { - WriteMultiBuffer(source); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Ensure(int count = 1) - { - if (_span.Length < count) - { - EnsureMore(count); - } - } - - [MethodImpl(MethodImplOptions.NoInlining)] - private void EnsureMore(int count = 0) - { - if (_buffered > 0) - { - Commit(); - } - - _span = _output.GetSpan(count); - } - - private void WriteMultiBuffer(ReadOnlySpan source) - { - while (source.Length > 0) - { - if (_span.Length == 0) - { - EnsureMore(); - } - - var writable = Math.Min(source.Length, _span.Length); - source.Slice(0, writable).CopyTo(_span); - source = source.Slice(writable); - Advance(writable); - } - } - } -} diff --git a/src/Servers/Kestrel/perf/PlatformBenchmarks/DateHeader.cs b/src/Servers/Kestrel/perf/PlatformBenchmarks/DateHeader.cs deleted file mode 100644 index 47415305b2..0000000000 --- a/src/Servers/Kestrel/perf/PlatformBenchmarks/DateHeader.cs +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Buffers.Text; -using System.Diagnostics; -using System.Text; -using System.Threading; - -namespace PlatformBenchmarks -{ - /// - /// Manages the generation of the date header value. - /// - internal static class DateHeader - { - const int prefixLength = 8; // "\r\nDate: ".Length - const int dateTimeRLength = 29; // Wed, 14 Mar 2018 14:20:00 GMT - const int suffixLength = 2; // crlf - const int suffixIndex = dateTimeRLength + prefixLength; - - private static readonly Timer s_timer = new Timer((s) => { - SetDateValues(DateTimeOffset.UtcNow); - }, null, 1000, 1000); - - private static byte[] s_headerBytesMaster = new byte[prefixLength + dateTimeRLength + suffixLength]; - private static byte[] s_headerBytesScratch = new byte[prefixLength + dateTimeRLength + suffixLength]; - - static DateHeader() - { - var utf8 = Encoding.ASCII.GetBytes("\r\nDate: ").AsSpan(); - utf8.CopyTo(s_headerBytesMaster); - utf8.CopyTo(s_headerBytesScratch); - s_headerBytesMaster[suffixIndex] = (byte)'\r'; - s_headerBytesMaster[suffixIndex + 1] = (byte)'\n'; - s_headerBytesScratch[suffixIndex] = (byte)'\r'; - s_headerBytesScratch[suffixIndex + 1] = (byte)'\n'; - SetDateValues(DateTimeOffset.UtcNow); - SyncDateTimer(); - } - - public static void SyncDateTimer() => s_timer.Change(1000, 1000); - - public static ReadOnlySpan HeaderBytes => s_headerBytesMaster; - - private static void SetDateValues(DateTimeOffset value) - { - lock (s_headerBytesScratch) - { - if (!Utf8Formatter.TryFormat(value, s_headerBytesScratch.AsSpan(prefixLength), out int written, 'R')) - { - throw new Exception("date time format failed"); - } - Debug.Assert(written == dateTimeRLength); - var temp = s_headerBytesMaster; - s_headerBytesMaster = s_headerBytesScratch; - s_headerBytesScratch = temp; - } - } - } -} \ No newline at end of file diff --git a/src/Servers/Kestrel/perf/PlatformBenchmarks/Directory.Build.targets b/src/Servers/Kestrel/perf/PlatformBenchmarks/Directory.Build.targets deleted file mode 100644 index 2e3fb2fa71..0000000000 --- a/src/Servers/Kestrel/perf/PlatformBenchmarks/Directory.Build.targets +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/src/Servers/Kestrel/perf/PlatformBenchmarks/HttpApplication.cs b/src/Servers/Kestrel/perf/PlatformBenchmarks/HttpApplication.cs deleted file mode 100644 index 48c736ab27..0000000000 --- a/src/Servers/Kestrel/perf/PlatformBenchmarks/HttpApplication.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Threading.Tasks; -using Microsoft.AspNetCore.Connections; - -namespace PlatformBenchmarks -{ - public static class HttpApplicationConnectionBuilderExtensions - { - public static IConnectionBuilder UseHttpApplication(this IConnectionBuilder builder) where TConnection : IHttpConnection, new() - { - return builder.Use(next => new HttpApplication().ExecuteAsync); - } - } - - public class HttpApplication where TConnection : IHttpConnection, new() - { - public Task ExecuteAsync(ConnectionContext connection) - { - var httpConnection = new TConnection - { - Reader = connection.Transport.Input, - Writer = connection.Transport.Output - }; - return httpConnection.ExecuteAsync(); - } - } -} diff --git a/src/Servers/Kestrel/perf/PlatformBenchmarks/IHttpConnection.cs b/src/Servers/Kestrel/perf/PlatformBenchmarks/IHttpConnection.cs deleted file mode 100644 index 2d58819ae2..0000000000 --- a/src/Servers/Kestrel/perf/PlatformBenchmarks/IHttpConnection.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.IO.Pipelines; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; - -namespace PlatformBenchmarks -{ - public interface IHttpConnection : IHttpHeadersHandler, IHttpRequestLineHandler - { - PipeReader Reader { get; set; } - PipeWriter Writer { get; set; } - Task ExecuteAsync(); - ValueTask OnReadCompletedAsync(); - } -} diff --git a/src/Servers/Kestrel/perf/PlatformBenchmarks/NuGet.config b/src/Servers/Kestrel/perf/PlatformBenchmarks/NuGet.config deleted file mode 100644 index 298193b812..0000000000 --- a/src/Servers/Kestrel/perf/PlatformBenchmarks/NuGet.config +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/src/Servers/Kestrel/perf/PlatformBenchmarks/PlatformBenchmarks.csproj b/src/Servers/Kestrel/perf/PlatformBenchmarks/PlatformBenchmarks.csproj deleted file mode 100644 index 5ac7ed26d2..0000000000 --- a/src/Servers/Kestrel/perf/PlatformBenchmarks/PlatformBenchmarks.csproj +++ /dev/null @@ -1,27 +0,0 @@ - - - - $(DefaultNetCoreTargetFramework) - $(BenchmarksTargetFramework) - Exe - latest - true - true - - - - - - - - - - - - - - - - - - diff --git a/src/Servers/Kestrel/perf/PlatformBenchmarks/Program.cs b/src/Servers/Kestrel/perf/PlatformBenchmarks/Program.cs deleted file mode 100644 index 784af36c2b..0000000000 --- a/src/Servers/Kestrel/perf/PlatformBenchmarks/Program.cs +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Net; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; - -namespace PlatformBenchmarks -{ - public class Program - { - public static void Main(string[] args) - { - Console.WriteLine(BenchmarkApplication.ApplicationName); - Console.WriteLine(BenchmarkApplication.Paths.Plaintext); - Console.WriteLine(BenchmarkApplication.Paths.Json); - DateHeader.SyncDateTimer(); - - BuildWebHost(args).Run(); - } - - public static IWebHost BuildWebHost(string[] args) - { - var config = new ConfigurationBuilder() - .AddEnvironmentVariables(prefix: "ASPNETCORE_") - .AddCommandLine(args) - .Build(); - - var host = new WebHostBuilder() - .UseBenchmarksConfiguration(config) - .UseKestrel((context, options) => - { - IPEndPoint endPoint = context.Configuration.CreateIPEndPoint(); - - options.Listen(endPoint, builder => - { - builder.UseHttpApplication(); - }); - }) - .UseStartup() - .Build(); - - return host; - } - } -} diff --git a/src/Servers/Kestrel/perf/PlatformBenchmarks/benchmarks.json.json b/src/Servers/Kestrel/perf/PlatformBenchmarks/benchmarks.json.json deleted file mode 100644 index 8313f994bd..0000000000 --- a/src/Servers/Kestrel/perf/PlatformBenchmarks/benchmarks.json.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "Default": { - "Client": "Wrk", - "PresetHeaders": "Json", - - "Source": { - "Repository": "https://github.com/aspnet/AspNetCore.git", - "BranchOrCommit": "master", - "Project": "src/Servers/Kestrel/perf/PlatformBenchmarks/PlatformBenchmarks.csproj" - } - }, - "JsonPlatform": { - "Path": "/json" - } -} \ No newline at end of file diff --git a/src/Servers/Kestrel/perf/PlatformBenchmarks/benchmarks.plaintext.json b/src/Servers/Kestrel/perf/PlatformBenchmarks/benchmarks.plaintext.json deleted file mode 100644 index 6cfe040ea4..0000000000 --- a/src/Servers/Kestrel/perf/PlatformBenchmarks/benchmarks.plaintext.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "Default": { - "Client": "Wrk", - "PresetHeaders": "Plaintext", - "ClientProperties": { - "ScriptName": "pipeline", - "PipelineDepth": 16 - }, - "Source": { - "Repository": "https://github.com/aspnet/AspNetCore.git", - "BranchOrCommit": "master", - "Project": "src/Servers/Kestrel/perf/PlatformBenchmarks/PlatformBenchmarks.csproj" - }, - "Port": 8080 - }, - "PlaintextPlatform": { - "Path": "/plaintext" - }, - "PlaintextNonPipelinedPlatform": { - "Path": "/plaintext", - "ClientProperties": { - "ScriptName": "", - "PipelineDepth": 0 - } - } -} diff --git a/src/Servers/Kestrel/samples/Http2SampleApp/Program.cs b/src/Servers/Kestrel/samples/Http2SampleApp/Program.cs index c6b37eb216..2b26fe09c6 100644 --- a/src/Servers/Kestrel/samples/Http2SampleApp/Program.cs +++ b/src/Servers/Kestrel/samples/Http2SampleApp/Program.cs @@ -28,13 +28,13 @@ namespace Http2SampleApp var basePort = context.Configuration.GetValue("BASE_PORT") ?? 5000; // Http/1.1 endpoint for comparison - options.Listen(IPAddress.Any, basePort, listenOptions => + options.ListenAnyIP(basePort, listenOptions => { listenOptions.Protocols = HttpProtocols.Http1; }); // TLS Http/1.1 or HTTP/2 endpoint negotiated via ALPN - options.Listen(IPAddress.Any, basePort + 1, listenOptions => + options.ListenAnyIP(basePort + 1, listenOptions => { listenOptions.Protocols = HttpProtocols.Http1AndHttp2; listenOptions.UseHttps(); @@ -56,7 +56,7 @@ namespace Http2SampleApp // Prior knowledge, no TLS handshake. WARNING: Not supported by browsers // but useful for the h2spec tests - options.Listen(IPAddress.Any, basePort + 5, listenOptions => + options.ListenAnyIP(basePort + 5, listenOptions => { listenOptions.Protocols = HttpProtocols.Http2; }); diff --git a/src/Servers/Kestrel/samples/Http2SampleApp/scripts/build-docker.sh b/src/Servers/Kestrel/samples/Http2SampleApp/scripts/build-docker.sh old mode 100644 new mode 100755 diff --git a/src/Servers/Kestrel/samples/Http2SampleApp/scripts/run-docker.sh b/src/Servers/Kestrel/samples/Http2SampleApp/scripts/run-docker.sh old mode 100644 new mode 100755 diff --git a/src/Servers/Kestrel/samples/Http3SampleApp/Http3SampleApp.csproj b/src/Servers/Kestrel/samples/Http3SampleApp/Http3SampleApp.csproj new file mode 100644 index 0000000000..04b7e7420c --- /dev/null +++ b/src/Servers/Kestrel/samples/Http3SampleApp/Http3SampleApp.csproj @@ -0,0 +1,20 @@ + + + + $(DefaultNetCoreTargetFramework) + false + + + + + + + + + + + PreserveNewest + PreserveNewest + + + diff --git a/src/Servers/Kestrel/samples/Http3SampleApp/Program.cs b/src/Servers/Kestrel/samples/Http3SampleApp/Program.cs new file mode 100644 index 0000000000..7e37138aaf --- /dev/null +++ b/src/Servers/Kestrel/samples/Http3SampleApp/Program.cs @@ -0,0 +1,54 @@ +using System; +using System.Net; +using System.Security.Cryptography.X509Certificates; +using Microsoft.AspNetCore.Connections; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Server.Kestrel.Core; +using Microsoft.AspNetCore.Server.Kestrel.Https; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace Http3SampleApp +{ + public class Program + { + public static void Main(string[] args) + { + var cert = CertificateLoader.LoadFromStoreCert("localhost", StoreName.My.ToString(), StoreLocation.CurrentUser, false); + + var hostBuilder = new HostBuilder() + .ConfigureLogging((_, factory) => + { + factory.SetMinimumLevel(LogLevel.Trace); + factory.AddConsole(); + }) + .ConfigureWebHost(webHost => + { + webHost.UseKestrel() + .UseQuic(options => + { + options.Certificate = cert; // Shouldn't need this either here. + options.Alpn = "h3-25"; // Shouldn't need to populate this as well. + options.IdleTimeout = TimeSpan.FromHours(1); + }) + .ConfigureKestrel((context, options) => + { + var basePort = 5557; + options.EnableAltSvc = true; + + options.Listen(IPAddress.Any, basePort, listenOptions => + { + listenOptions.UseHttps(httpsOptions => + { + httpsOptions.ServerCertificate = cert; + }); + listenOptions.Protocols = HttpProtocols.Http1AndHttp2AndHttp3; + }); + }) + .UseStartup(); + }); + + hostBuilder.Build().Run(); + } + } +} diff --git a/src/Servers/Kestrel/samples/Http3SampleApp/Startup.cs b/src/Servers/Kestrel/samples/Http3SampleApp/Startup.cs new file mode 100644 index 0000000000..0434605cfc --- /dev/null +++ b/src/Servers/Kestrel/samples/Http3SampleApp/Startup.cs @@ -0,0 +1,30 @@ +using System; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; + +namespace Http3SampleApp +{ + public class Startup + { + // This method gets called by the runtime. Use this method to add services to the container. + // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 + public void ConfigureServices(IServiceCollection services) + { + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app) + { + app.Run(async context => + { + var memory = new Memory(new byte[4096]); + var length = await context.Request.Body.ReadAsync(memory); + context.Response.Headers["test"] = "foo"; + // for testing + await context.Response.WriteAsync("Hello World! " + context.Request.Protocol); + }); + } + } +} diff --git a/src/Servers/Kestrel/samples/Http3SampleApp/appsettings.Development.json b/src/Servers/Kestrel/samples/Http3SampleApp/appsettings.Development.json new file mode 100644 index 0000000000..e203e9407e --- /dev/null +++ b/src/Servers/Kestrel/samples/Http3SampleApp/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + } +} diff --git a/src/Servers/Kestrel/samples/Http3SampleApp/appsettings.json b/src/Servers/Kestrel/samples/Http3SampleApp/appsettings.json new file mode 100644 index 0000000000..d9d9a9bff6 --- /dev/null +++ b/src/Servers/Kestrel/samples/Http3SampleApp/appsettings.json @@ -0,0 +1,10 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "AllowedHosts": "*" +} diff --git a/src/Servers/Kestrel/samples/QuicSampleApp/Program.cs b/src/Servers/Kestrel/samples/QuicSampleApp/Program.cs new file mode 100644 index 0000000000..54a11c63a0 --- /dev/null +++ b/src/Servers/Kestrel/samples/QuicSampleApp/Program.cs @@ -0,0 +1,83 @@ +using System; +using System.Buffers; +using System.Net; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Connections; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Server.Kestrel.Core; +using Microsoft.Extensions.Logging; + +namespace QuicSampleApp +{ + public class Startup + { + public void Configure(IApplicationBuilder app) + { + app.Run((httpContext) => + { + return Task.CompletedTask; + }); + } + + public static void Main(string[] args) + { + var hostBuilder = new WebHostBuilder() + .ConfigureLogging((_, factory) => + { + factory.SetMinimumLevel(LogLevel.Debug); + factory.AddConsole(); + }) + .UseKestrel() + .UseQuic(options => + { + options.Certificate = null; + options.Alpn = "QuicTest"; + options.IdleTimeout = TimeSpan.FromHours(1); + }) + .ConfigureKestrel((context, options) => + { + var basePort = 5555; + + options.Listen(IPAddress.Any, basePort, listenOptions => + { + listenOptions.Protocols = HttpProtocols.Http3; + + async Task EchoServer(MultiplexedConnectionContext connection) + { + // For graceful shutdown + + while (true) + { + var stream = await connection.AcceptAsync(); + while (true) + { + var result = await stream.Transport.Input.ReadAsync(); + + if (result.IsCompleted) + { + break; + } + + await stream.Transport.Output.WriteAsync(result.Buffer.ToArray()); + + stream.Transport.Input.AdvanceTo(result.Buffer.End); + } + } + } + + ((IMultiplexedConnectionBuilder)listenOptions).Use(next => + { + return context => + { + return EchoServer(context); + }; + }); + }); + }) + .UseStartup(); + + hostBuilder.Build().Run(); + } + } +} diff --git a/src/Servers/Kestrel/samples/QuicSampleApp/QuicSampleApp.csproj b/src/Servers/Kestrel/samples/QuicSampleApp/QuicSampleApp.csproj new file mode 100644 index 0000000000..56ee0e01e1 --- /dev/null +++ b/src/Servers/Kestrel/samples/QuicSampleApp/QuicSampleApp.csproj @@ -0,0 +1,28 @@ + + + + $(DefaultNetCoreTargetFramework) + false + true + false + + + + + + + + + + + true + + + + + + true + + + + diff --git a/src/Servers/Kestrel/samples/QuicSampleApp/appsettings.Development.json b/src/Servers/Kestrel/samples/QuicSampleApp/appsettings.Development.json new file mode 100644 index 0000000000..e203e9407e --- /dev/null +++ b/src/Servers/Kestrel/samples/QuicSampleApp/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + } +} diff --git a/src/Servers/Kestrel/samples/QuicSampleApp/appsettings.json b/src/Servers/Kestrel/samples/QuicSampleApp/appsettings.json new file mode 100644 index 0000000000..d9d9a9bff6 --- /dev/null +++ b/src/Servers/Kestrel/samples/QuicSampleApp/appsettings.json @@ -0,0 +1,10 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "AllowedHosts": "*" +} diff --git a/src/Servers/Kestrel/samples/QuicSampleClient/Program.cs b/src/Servers/Kestrel/samples/QuicSampleClient/Program.cs new file mode 100644 index 0000000000..5ced2cc44b --- /dev/null +++ b/src/Servers/Kestrel/samples/QuicSampleClient/Program.cs @@ -0,0 +1,103 @@ +using System; +using System.Buffers; +using System.Net; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Server.Kestrel.Transport.Experimental.Quic; +using Microsoft.Extensions.Hosting; +using Microsoft.AspNetCore.Connections; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.DependencyInjection; + +namespace QuicSampleClient +{ + class Program + { + static async Task Main(string[] args) + { + var host = new HostBuilder() + .ConfigureLogging(loggingBuilder => + { + loggingBuilder.AddConsole(); + loggingBuilder.SetMinimumLevel(LogLevel.Error); + }) + .ConfigureServices(services => + { + services.AddSingleton(); + services.AddSingleton(); + services.AddOptions(); + services.Configure((options) => + { + options.Alpn = "QuicTest"; + options.Certificate = null; + options.IdleTimeout = TimeSpan.FromHours(1); + }); + }) + .Build(); + await host.Services.GetService().RunAsync(); + } + + private class QuicClientService + { + private readonly IMultiplexedConnectionFactory _connectionFactory; + private readonly ILogger _logger; + public QuicClientService(IMultiplexedConnectionFactory connectionFactory, ILogger logger) + { + _connectionFactory = connectionFactory; + _logger = logger; + } + + public async Task RunAsync() + { + Console.WriteLine("Starting"); + var connectionContext = await _connectionFactory.ConnectAsync(new IPEndPoint(IPAddress.Loopback, 5555)); + var streamContext = await connectionContext.ConnectAsync(); + + Console.CancelKeyPress += new ConsoleCancelEventHandler((sender, args) => + { + streamContext.Transport.Input.CancelPendingRead(); + streamContext.Transport.Output.CancelPendingFlush(); + }); + + var input = "asdf"; + while (true) + { + try + { + //var input = Console.ReadLine(); + if (input.Length == 0) + { + continue; + } + var flushResult = await streamContext.Transport.Output.WriteAsync(Encoding.ASCII.GetBytes(input)); + if (flushResult.IsCanceled) + { + break; + } + + var readResult = await streamContext.Transport.Input.ReadAsync(); + if (readResult.IsCanceled || readResult.IsCompleted) + { + break; + } + + if (readResult.Buffer.Length > 0) + { + Console.WriteLine(Encoding.ASCII.GetString(readResult.Buffer.ToArray())); + } + + streamContext.Transport.Input.AdvanceTo(readResult.Buffer.End); + } + catch (Exception ex) + { + Console.WriteLine(ex.Message); + break; + } + } + + await streamContext.Transport.Input.CompleteAsync(); + await streamContext.Transport.Output.CompleteAsync(); + } + } + } +} diff --git a/src/Servers/Kestrel/samples/QuicSampleClient/QuicSampleClient.csproj b/src/Servers/Kestrel/samples/QuicSampleClient/QuicSampleClient.csproj new file mode 100644 index 0000000000..902824d836 --- /dev/null +++ b/src/Servers/Kestrel/samples/QuicSampleClient/QuicSampleClient.csproj @@ -0,0 +1,28 @@ + + + + Exe + $(DefaultNetCoreTargetFramework) + false + + + + + + + + + + + + true + + + + + + true + + + + diff --git a/src/Servers/Kestrel/samples/http2cat/Program.cs b/src/Servers/Kestrel/samples/http2cat/Program.cs new file mode 100644 index 0000000000..f7a8634bda --- /dev/null +++ b/src/Servers/Kestrel/samples/http2cat/Program.cs @@ -0,0 +1,81 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Diagnostics; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http2Cat; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace http2cat +{ + public class Program + { + public static async Task Main(string[] args) + { + using var host = new HostBuilder() + .ConfigureLogging(loggingBuilder => + { + loggingBuilder.AddConsole(); + }) + .UseHttp2Cat("https://localhost:5001", RunTestCase) + .Build(); + + await host.RunAsync(); + } + + internal static async Task RunTestCase(Http2Utilities h2Connection) + { + await h2Connection.InitializeConnectionAsync(); + + h2Connection.Logger.LogInformation("Initialized http2 connection. Starting stream 1."); + + await h2Connection.StartStreamAsync(1, Http2Utilities.BrowserRequestHeaders, endStream: true); + + var headersFrame = await h2Connection.ReceiveFrameAsync(); + + Trace.Assert(headersFrame.Type == Http2FrameType.HEADERS, headersFrame.Type.ToString()); + Trace.Assert((headersFrame.Flags & (byte)Http2HeadersFrameFlags.END_HEADERS) != 0); + Trace.Assert((headersFrame.Flags & (byte)Http2HeadersFrameFlags.END_STREAM) == 0); + + h2Connection.Logger.LogInformation("Received headers in a single frame."); + + var decodedHeaders = h2Connection.DecodeHeaders(headersFrame); + + foreach (var header in decodedHeaders) + { + h2Connection.Logger.LogInformation($"{header.Key}: {header.Value}"); + } + + var dataFrame = await h2Connection.ReceiveFrameAsync(); + + Trace.Assert(dataFrame.Type == Http2FrameType.DATA); + Trace.Assert((dataFrame.Flags & (byte)Http2DataFrameFlags.END_STREAM) == 0); + + h2Connection.Logger.LogInformation("Received data in a single frame."); + + h2Connection.Logger.LogInformation(Encoding.UTF8.GetString(dataFrame.Payload.ToArray())); + + var trailersFrame = await h2Connection.ReceiveFrameAsync(); + + Trace.Assert(trailersFrame.Type == Http2FrameType.HEADERS); + Trace.Assert((trailersFrame.Flags & (byte)Http2DataFrameFlags.END_STREAM) == 1); + + h2Connection.Logger.LogInformation("Received trailers in a single frame."); + + h2Connection.ResetHeaders(); + var decodedTrailers = h2Connection.DecodeHeaders(trailersFrame); + + foreach (var header in decodedTrailers) + { + h2Connection.Logger.LogInformation($"{header.Key}: {header.Value}"); + } + + await h2Connection.StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); + + h2Connection.Logger.LogInformation("Connection stopped."); + } + } +} diff --git a/src/Servers/Kestrel/samples/http2cat/http2cat.csproj b/src/Servers/Kestrel/samples/http2cat/http2cat.csproj new file mode 100644 index 0000000000..5bfcc5fda7 --- /dev/null +++ b/src/Servers/Kestrel/samples/http2cat/http2cat.csproj @@ -0,0 +1,41 @@ + + + + Exe + netcoreapp5.0 + true + + + + + + + + + + + + + + + + + + + + + Microsoft.AspNetCore.Server.SharedStrings + + + + System.Net.Http.SR + + + + + + + + + + diff --git a/src/Servers/Kestrel/shared/KnownHeaders.cs b/src/Servers/Kestrel/shared/KnownHeaders.cs index b2f3292f34..e6389a675e 100644 --- a/src/Servers/Kestrel/shared/KnownHeaders.cs +++ b/src/Servers/Kestrel/shared/KnownHeaders.cs @@ -19,6 +19,10 @@ namespace CodeGenerator { var requestPrimaryHeaders = new[] { + ":authority", + ":method", + ":path", + ":scheme", "Accept", "Connection", "Host", @@ -72,6 +76,10 @@ namespace CodeGenerator }; RequestHeaders = commonHeaders.Concat(new[] { + ":authority", + ":method", + ":path", + ":scheme", "Accept", "Accept-Charset", "Accept-Encoding", @@ -145,6 +153,7 @@ namespace CodeGenerator { "Accept-Ranges", "Age", + "Alt-Svc", "ETag", "Location", "Proxy-Authenticate", @@ -246,7 +255,7 @@ namespace CodeGenerator { public string Name { get; set; } public int Index { get; set; } - public string Identifier => Name.Replace("-", ""); + public string Identifier => ResolveIdentifier(Name); public byte[] Bytes => Encoding.ASCII.GetBytes($"\r\n{Name}: "); public int BytesOffset { get; set; } @@ -263,6 +272,21 @@ namespace CodeGenerator public string SetBit() => $"_bits |= {"0x" + (1L << Index).ToString("x")}L"; public string ClearBit() => $"_bits &= ~{"0x" + (1L << Index).ToString("x")}L"; + private string ResolveIdentifier(string name) + { + var identifer = name.Replace("-", ""); + + // Pseudo headers start with a colon. A colon isn't valid in C# names so + // remove it and pascal case the header name. e.g. :path -> Path, :scheme -> Scheme. + // This identifier will match the names in HeadersNames.cs + if (identifer.StartsWith(':')) + { + identifer = char.ToUpper(identifer[1]) + identifer.Substring(2); + } + + return identifer; + } + private void GetMaskAndComp(string name, int offset, int count, out ulong mask, out ulong comp) { mask = 0; @@ -538,6 +562,9 @@ namespace CodeGenerator var responseTrailers = ResponseTrailers; + var allHeaderNames = RequestHeaders.Concat(ResponseHeaders).Concat(ResponseTrailers) + .Select(h => h.Identifier).Distinct().OrderBy(n => n).ToArray(); + var loops = new[] { new @@ -588,6 +615,11 @@ using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http {{ + internal enum KnownHeaderType + {{ + Unknown,{Each(allHeaderNames, n => @" + " + n + ",")} + }} {Each(loops, loop => $@" internal partial class {loop.ClassName} {{{(loop.Bytes != null ? @@ -944,14 +976,14 @@ $@" private void Clear(long bitsToClear) if (value != null) {{ output.Write(headerKey); - output.WriteAsciiNoValidation(value); + output.WriteAscii(value); }} }} }} }} while (tempBits != 0); }}" : "")}{(loop.ClassName == "HttpRequestHeaders" ? $@" [MethodImpl(MethodImplOptions.AggressiveOptimization)] - public unsafe void Append(Span name, Span value) + public unsafe void Append(ReadOnlySpan name, ReadOnlySpan value) {{ ref byte nameStart = ref MemoryMarshal.GetReference(name); ref StringValues values = ref Unsafe.AsRef(null); @@ -985,7 +1017,7 @@ $@" private void Clear(long bitsToClear) }} // We didn't have a previous matching header value, or have already added a header, so get the string for this value. - var valueStr = value.GetRequestHeaderStringNonNullCharacters(_useLatin1); + var valueStr = value.GetRequestHeaderStringNonNullCharacters(UseLatin1); if ((_bits & flag) == 0) {{ // We didn't already have a header set, so add a new one. @@ -1003,7 +1035,7 @@ $@" private void Clear(long bitsToClear) // The header was not one of the ""known"" headers. // Convert value to string first, because passing two spans causes 8 bytes stack zeroing in // this method with rep stosd, which is slower than necessary. - var valueStr = value.GetRequestHeaderStringNonNullCharacters(_useLatin1); + var valueStr = value.GetRequestHeaderStringNonNullCharacters(UseLatin1); AppendUnknownHeaders(name, valueStr); }} }}" : "")} @@ -1034,6 +1066,7 @@ $@" private void Clear(long bitsToClear) if ({header.TestBit()}) {{ _current = new KeyValuePair(HeaderNames.{header.Identifier}, _collection._headers._{header.Identifier}); + _currentKnownType = KnownHeaderType.{header.Identifier}; _next = {header.Index + 1}; return true; }}")} @@ -1041,6 +1074,7 @@ $@" private void Clear(long bitsToClear) if (_collection._contentLength.HasValue) {{ _current = new KeyValuePair(HeaderNames.ContentLength, HeaderUtilities.FormatNonNegativeInt64(_collection._contentLength.Value)); + _currentKnownType = KnownHeaderType.ContentLength; _next = {loop.Headers.Count()}; return true; }}" : "")} @@ -1048,9 +1082,11 @@ $@" private void Clear(long bitsToClear) if (!_hasUnknown || !_unknownEnumerator.MoveNext()) {{ _current = default(KeyValuePair); + _currentKnownType = default; return false; }} _current = _unknownEnumerator.Current; + _currentKnownType = KnownHeaderType.Unknown; return true; }} }} diff --git a/src/Servers/Kestrel/shared/TransportConnection.Generated.cs b/src/Servers/Kestrel/shared/TransportConnection.Generated.cs index 1b7a216976..a9ba1cd762 100644 --- a/src/Servers/Kestrel/shared/TransportConnection.Generated.cs +++ b/src/Servers/Kestrel/shared/TransportConnection.Generated.cs @@ -12,12 +12,6 @@ namespace Microsoft.AspNetCore.Connections { internal partial class TransportConnection : IFeatureCollection { - private static readonly Type IConnectionIdFeatureType = typeof(IConnectionIdFeature); - private static readonly Type IConnectionTransportFeatureType = typeof(IConnectionTransportFeature); - private static readonly Type IConnectionItemsFeatureType = typeof(IConnectionItemsFeature); - private static readonly Type IMemoryPoolFeatureType = typeof(IMemoryPoolFeature); - private static readonly Type IConnectionLifetimeFeatureType = typeof(IConnectionLifetimeFeature); - private object _currentIConnectionIdFeature; private object _currentIConnectionTransportFeature; private object _currentIConnectionItemsFeature; @@ -90,23 +84,23 @@ namespace Microsoft.AspNetCore.Connections get { object feature = null; - if (key == IConnectionIdFeatureType) + if (key == typeof(IConnectionIdFeature)) { feature = _currentIConnectionIdFeature; } - else if (key == IConnectionTransportFeatureType) + else if (key == typeof(IConnectionTransportFeature)) { feature = _currentIConnectionTransportFeature; } - else if (key == IConnectionItemsFeatureType) + else if (key == typeof(IConnectionItemsFeature)) { feature = _currentIConnectionItemsFeature; } - else if (key == IMemoryPoolFeatureType) + else if (key == typeof(IMemoryPoolFeature)) { feature = _currentIMemoryPoolFeature; } - else if (key == IConnectionLifetimeFeatureType) + else if (key == typeof(IConnectionLifetimeFeature)) { feature = _currentIConnectionLifetimeFeature; } @@ -122,23 +116,23 @@ namespace Microsoft.AspNetCore.Connections { _featureRevision++; - if (key == IConnectionIdFeatureType) + if (key == typeof(IConnectionIdFeature)) { _currentIConnectionIdFeature = value; } - else if (key == IConnectionTransportFeatureType) + else if (key == typeof(IConnectionTransportFeature)) { _currentIConnectionTransportFeature = value; } - else if (key == IConnectionItemsFeatureType) + else if (key == typeof(IConnectionItemsFeature)) { _currentIConnectionItemsFeature = value; } - else if (key == IMemoryPoolFeatureType) + else if (key == typeof(IMemoryPoolFeature)) { _currentIMemoryPoolFeature = value; } - else if (key == IConnectionLifetimeFeatureType) + else if (key == typeof(IConnectionLifetimeFeature)) { _currentIConnectionLifetimeFeature = value; } @@ -213,23 +207,23 @@ namespace Microsoft.AspNetCore.Connections { if (_currentIConnectionIdFeature != null) { - yield return new KeyValuePair(IConnectionIdFeatureType, _currentIConnectionIdFeature); + yield return new KeyValuePair(typeof(IConnectionIdFeature), _currentIConnectionIdFeature); } if (_currentIConnectionTransportFeature != null) { - yield return new KeyValuePair(IConnectionTransportFeatureType, _currentIConnectionTransportFeature); + yield return new KeyValuePair(typeof(IConnectionTransportFeature), _currentIConnectionTransportFeature); } if (_currentIConnectionItemsFeature != null) { - yield return new KeyValuePair(IConnectionItemsFeatureType, _currentIConnectionItemsFeature); + yield return new KeyValuePair(typeof(IConnectionItemsFeature), _currentIConnectionItemsFeature); } if (_currentIMemoryPoolFeature != null) { - yield return new KeyValuePair(IMemoryPoolFeatureType, _currentIMemoryPoolFeature); + yield return new KeyValuePair(typeof(IMemoryPoolFeature), _currentIMemoryPoolFeature); } if (_currentIConnectionLifetimeFeature != null) { - yield return new KeyValuePair(IConnectionLifetimeFeatureType, _currentIConnectionLifetimeFeature); + yield return new KeyValuePair(typeof(IConnectionLifetimeFeature), _currentIConnectionLifetimeFeature); } if (MaybeExtra != null) diff --git a/src/Servers/Kestrel/shared/TransportMultiplexedConnection.FeatureCollection.cs b/src/Servers/Kestrel/shared/TransportMultiplexedConnection.FeatureCollection.cs new file mode 100644 index 0000000000..fae4129d25 --- /dev/null +++ b/src/Servers/Kestrel/shared/TransportMultiplexedConnection.FeatureCollection.cs @@ -0,0 +1,37 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Buffers; +using System.Collections.Generic; +using System.IO.Pipelines; +using System.Threading; +using Microsoft.AspNetCore.Connections.Features; + +namespace Microsoft.AspNetCore.Connections +{ + internal partial class TransportMultiplexedConnection : IConnectionIdFeature, + IConnectionItemsFeature, + IMemoryPoolFeature, + IConnectionLifetimeFeature + { + // NOTE: When feature interfaces are added to or removed from this TransportConnection class implementation, + // then the list of `features` in the generated code project MUST also be updated. + // See also: tools/CodeGenerator/TransportConnectionFeatureCollection.cs + + MemoryPool IMemoryPoolFeature.MemoryPool => MemoryPool; + + IDictionary IConnectionItemsFeature.Items + { + get => Items; + set => Items = value; + } + + CancellationToken IConnectionLifetimeFeature.ConnectionClosed + { + get => ConnectionClosed; + set => ConnectionClosed = value; + } + + void IConnectionLifetimeFeature.Abort() => Abort(new ConnectionAbortedException("The connection was aborted by the application via IConnectionLifetimeFeature.Abort().")); + } +} diff --git a/src/Servers/Kestrel/shared/TransportMultiplexedConnection.Generated.cs b/src/Servers/Kestrel/shared/TransportMultiplexedConnection.Generated.cs new file mode 100644 index 0000000000..e7df1de198 --- /dev/null +++ b/src/Servers/Kestrel/shared/TransportMultiplexedConnection.Generated.cs @@ -0,0 +1,242 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections; +using System.Collections.Generic; + +using Microsoft.AspNetCore.Connections.Features; +using Microsoft.AspNetCore.Http.Features; + +namespace Microsoft.AspNetCore.Connections +{ + internal partial class TransportMultiplexedConnection : IFeatureCollection + { + private object _currentIConnectionIdFeature; + private object _currentIConnectionTransportFeature; + private object _currentIConnectionItemsFeature; + private object _currentIMemoryPoolFeature; + private object _currentIConnectionLifetimeFeature; + + private int _featureRevision; + + private List> MaybeExtra; + + private void FastReset() + { + _currentIConnectionIdFeature = this; + _currentIConnectionTransportFeature = this; + _currentIConnectionItemsFeature = this; + _currentIMemoryPoolFeature = this; + _currentIConnectionLifetimeFeature = this; + + } + + // Internal for testing + internal void ResetFeatureCollection() + { + FastReset(); + MaybeExtra?.Clear(); + _featureRevision++; + } + + private object ExtraFeatureGet(Type key) + { + if (MaybeExtra == null) + { + return null; + } + for (var i = 0; i < MaybeExtra.Count; i++) + { + var kv = MaybeExtra[i]; + if (kv.Key == key) + { + return kv.Value; + } + } + return null; + } + + private void ExtraFeatureSet(Type key, object value) + { + if (MaybeExtra == null) + { + MaybeExtra = new List>(2); + } + + for (var i = 0; i < MaybeExtra.Count; i++) + { + if (MaybeExtra[i].Key == key) + { + MaybeExtra[i] = new KeyValuePair(key, value); + return; + } + } + MaybeExtra.Add(new KeyValuePair(key, value)); + } + + bool IFeatureCollection.IsReadOnly => false; + + int IFeatureCollection.Revision => _featureRevision; + + object IFeatureCollection.this[Type key] + { + get + { + object feature = null; + if (key == typeof(IConnectionIdFeature)) + { + feature = _currentIConnectionIdFeature; + } + else if (key == typeof(IConnectionTransportFeature)) + { + feature = _currentIConnectionTransportFeature; + } + else if (key == typeof(IConnectionItemsFeature)) + { + feature = _currentIConnectionItemsFeature; + } + else if (key == typeof(IMemoryPoolFeature)) + { + feature = _currentIMemoryPoolFeature; + } + else if (key == typeof(IConnectionLifetimeFeature)) + { + feature = _currentIConnectionLifetimeFeature; + } + else if (MaybeExtra != null) + { + feature = ExtraFeatureGet(key); + } + + return feature; + } + + set + { + _featureRevision++; + + if (key == typeof(IConnectionIdFeature)) + { + _currentIConnectionIdFeature = value; + } + else if (key == typeof(IConnectionTransportFeature)) + { + _currentIConnectionTransportFeature = value; + } + else if (key == typeof(IConnectionItemsFeature)) + { + _currentIConnectionItemsFeature = value; + } + else if (key == typeof(IMemoryPoolFeature)) + { + _currentIMemoryPoolFeature = value; + } + else if (key == typeof(IConnectionLifetimeFeature)) + { + _currentIConnectionLifetimeFeature = value; + } + else + { + ExtraFeatureSet(key, value); + } + } + } + + TFeature IFeatureCollection.Get() + { + TFeature feature = default; + if (typeof(TFeature) == typeof(IConnectionIdFeature)) + { + feature = (TFeature)_currentIConnectionIdFeature; + } + else if (typeof(TFeature) == typeof(IConnectionTransportFeature)) + { + feature = (TFeature)_currentIConnectionTransportFeature; + } + else if (typeof(TFeature) == typeof(IConnectionItemsFeature)) + { + feature = (TFeature)_currentIConnectionItemsFeature; + } + else if (typeof(TFeature) == typeof(IMemoryPoolFeature)) + { + feature = (TFeature)_currentIMemoryPoolFeature; + } + else if (typeof(TFeature) == typeof(IConnectionLifetimeFeature)) + { + feature = (TFeature)_currentIConnectionLifetimeFeature; + } + else if (MaybeExtra != null) + { + feature = (TFeature)(ExtraFeatureGet(typeof(TFeature))); + } + + return feature; + } + + void IFeatureCollection.Set(TFeature feature) + { + _featureRevision++; + if (typeof(TFeature) == typeof(IConnectionIdFeature)) + { + _currentIConnectionIdFeature = feature; + } + else if (typeof(TFeature) == typeof(IConnectionTransportFeature)) + { + _currentIConnectionTransportFeature = feature; + } + else if (typeof(TFeature) == typeof(IConnectionItemsFeature)) + { + _currentIConnectionItemsFeature = feature; + } + else if (typeof(TFeature) == typeof(IMemoryPoolFeature)) + { + _currentIMemoryPoolFeature = feature; + } + else if (typeof(TFeature) == typeof(IConnectionLifetimeFeature)) + { + _currentIConnectionLifetimeFeature = feature; + } + else + { + ExtraFeatureSet(typeof(TFeature), feature); + } + } + + private IEnumerable> FastEnumerable() + { + if (_currentIConnectionIdFeature != null) + { + yield return new KeyValuePair(typeof(IConnectionIdFeature), _currentIConnectionIdFeature); + } + if (_currentIConnectionTransportFeature != null) + { + yield return new KeyValuePair(typeof(IConnectionTransportFeature), _currentIConnectionTransportFeature); + } + if (_currentIConnectionItemsFeature != null) + { + yield return new KeyValuePair(typeof(IConnectionItemsFeature), _currentIConnectionItemsFeature); + } + if (_currentIMemoryPoolFeature != null) + { + yield return new KeyValuePair(typeof(IMemoryPoolFeature), _currentIMemoryPoolFeature); + } + if (_currentIConnectionLifetimeFeature != null) + { + yield return new KeyValuePair(typeof(IConnectionLifetimeFeature), _currentIConnectionLifetimeFeature); + } + + if (MaybeExtra != null) + { + foreach (var item in MaybeExtra) + { + yield return item; + } + } + } + + IEnumerator> IEnumerable>.GetEnumerator() => FastEnumerable().GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => FastEnumerable().GetEnumerator(); + } +} diff --git a/src/Servers/Kestrel/shared/TransportMultiplexedConnection.cs b/src/Servers/Kestrel/shared/TransportMultiplexedConnection.cs new file mode 100644 index 0000000000..7dea0569b9 --- /dev/null +++ b/src/Servers/Kestrel/shared/TransportMultiplexedConnection.cs @@ -0,0 +1,75 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Buffers; +using System.Collections.Generic; +using System.IO.Pipelines; +using System.Net; +using System.Threading; +using Microsoft.AspNetCore.Http.Features; + +namespace Microsoft.AspNetCore.Connections +{ + internal abstract partial class TransportMultiplexedConnection : MultiplexedConnectionContext + { + private IDictionary _items; + private string _connectionId; + + public TransportMultiplexedConnection() + { + FastReset(); + } + + public override EndPoint LocalEndPoint { get; set; } + public override EndPoint RemoteEndPoint { get; set; } + + public override string ConnectionId + { + get + { + if (_connectionId == null) + { + _connectionId = CorrelationIdGenerator.GetNextId(); + } + + return _connectionId; + } + set + { + _connectionId = value; + } + } + + public override IFeatureCollection Features => this; + + public virtual MemoryPool MemoryPool { get; } + + public IDuplexPipe Application { get; set; } + + public override IDictionary Items + { + get + { + // Lazily allocate connection metadata + return _items ?? (_items = new ConnectionItems()); + } + set + { + _items = value; + } + } + + public override CancellationToken ConnectionClosed { get; set; } + + // DO NOT remove this override to ConnectionContext.Abort. Doing so would cause + // any TransportConnection that does not override Abort or calls base.Abort + // to stack overflow when IConnectionLifetimeFeature.Abort() is called. + // That said, all derived types should override this method should override + // this implementation of Abort because canceling pending output reads is not + // sufficient to abort the connection if there is backpressure. + public override void Abort(ConnectionAbortedException abortReason) + { + Application.Input.CancelPendingRead(); + } + } +} diff --git a/src/Servers/Kestrel/shared/test/CompositeKestrelTrace.cs b/src/Servers/Kestrel/shared/test/CompositeKestrelTrace.cs index f4b55a18a6..85a7643202 100644 --- a/src/Servers/Kestrel/shared/test/CompositeKestrelTrace.cs +++ b/src/Servers/Kestrel/shared/test/CompositeKestrelTrace.cs @@ -2,10 +2,10 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Net.Http.HPack; using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2; -using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; using Microsoft.Extensions.Logging; @@ -122,10 +122,10 @@ namespace Microsoft.AspNetCore.Testing _trace2.NotAllConnectionsAborted(); } - public void HeartbeatSlow(TimeSpan interval, DateTimeOffset now) + public void HeartbeatSlow(TimeSpan heartbeatDuration, TimeSpan interval, DateTimeOffset now) { - _trace1.HeartbeatSlow(interval, now); - _trace2.HeartbeatSlow(interval, now); + _trace1.HeartbeatSlow(heartbeatDuration, interval, now); + _trace2.HeartbeatSlow(heartbeatDuration, interval, now); } public void ApplicationNeverCompleted(string connectionId) diff --git a/src/Servers/Kestrel/shared/test/PipeWriterHttp2FrameExtensions.cs b/src/Servers/Kestrel/shared/test/PipeWriterHttp2FrameExtensions.cs new file mode 100644 index 0000000000..481278ae42 --- /dev/null +++ b/src/Servers/Kestrel/shared/test/PipeWriterHttp2FrameExtensions.cs @@ -0,0 +1,103 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Buffers; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.IO.Pipelines; +using System.Net.Http.HPack; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2; + +namespace Microsoft.AspNetCore.Testing +{ + internal static class PipeWriterHttp2FrameExtensions + { + public static void WriteSettings(this PipeWriter writer, Http2PeerSettings clientSettings) + { + var frame = new Http2Frame(); + frame.PrepareSettings(Http2SettingsFrameFlags.NONE); + var settings = clientSettings.GetNonProtocolDefaults(); + var payload = new byte[settings.Count * Http2FrameReader.SettingSize]; + frame.PayloadLength = payload.Length; + Http2FrameWriter.WriteSettings(settings, payload); + Http2FrameWriter.WriteHeader(frame, writer); + writer.Write(payload); + } + + public static void WriteStartStream(this PipeWriter writer, int streamId, Http2HeadersEnumerator headers, byte[] headerEncodingBuffer, bool endStream, Http2Frame frame = null) + { + frame ??= new Http2Frame(); + frame.PrepareHeaders(Http2HeadersFrameFlags.NONE, streamId); + + var buffer = headerEncodingBuffer.AsSpan(); + var done = HPackHeaderWriter.BeginEncodeHeaders(headers, buffer, out var length); + frame.PayloadLength = length; + + if (done) + { + frame.HeadersFlags = Http2HeadersFrameFlags.END_HEADERS; + } + + if (endStream) + { + frame.HeadersFlags |= Http2HeadersFrameFlags.END_STREAM; + } + + Http2FrameWriter.WriteHeader(frame, writer); + writer.Write(buffer.Slice(0, length)); + + while (!done) + { + frame.PrepareContinuation(Http2ContinuationFrameFlags.NONE, streamId); + + done = HPackHeaderWriter.ContinueEncodeHeaders(headers, buffer, out length); + frame.PayloadLength = length; + + if (done) + { + frame.ContinuationFlags = Http2ContinuationFrameFlags.END_HEADERS; + } + + Http2FrameWriter.WriteHeader(frame, writer); + writer.Write(buffer.Slice(0, length)); + } + } + + public static void WriteStartStream(this PipeWriter writer, int streamId, Span headerData, bool endStream, Http2Frame frame = null) + { + frame ??= new Http2Frame(); + frame.PrepareHeaders(Http2HeadersFrameFlags.NONE, streamId); + frame.PayloadLength = headerData.Length; + frame.HeadersFlags = Http2HeadersFrameFlags.END_HEADERS; + + if (endStream) + { + frame.HeadersFlags |= Http2HeadersFrameFlags.END_STREAM; + } + + Http2FrameWriter.WriteHeader(frame, writer); + writer.Write(headerData); + } + + public static void WriteData(this PipeWriter writer, int streamId, Memory data, bool endStream, Http2Frame frame = null) + { + frame ??= new Http2Frame(); + frame.PrepareData(streamId); + frame.PayloadLength = data.Length; + frame.DataFlags = endStream ? Http2DataFrameFlags.END_STREAM : Http2DataFrameFlags.NONE; + + Http2FrameWriter.WriteHeader(frame, writer); + writer.Write(data.Span); + } + + public static void WriteWindowUpdateAsync(this PipeWriter writer, int streamId, int sizeIncrement, Http2Frame frame = null) + { + frame ??= new Http2Frame(); + frame.PrepareWindowUpdate(streamId, sizeIncrement); + Http2FrameWriter.WriteHeader(frame, writer); + BinaryPrimitives.WriteUInt32BigEndian(writer.GetSpan(4), (uint)sizeIncrement); + writer.Advance(4); + } + } +} diff --git a/src/Servers/Kestrel/shared/test/TestCertificates/aspnetdevcert.pfx b/src/Servers/Kestrel/shared/test/TestCertificates/aspnetdevcert.pfx index e6eeeaa2e1..888ccb032a 100644 Binary files a/src/Servers/Kestrel/shared/test/TestCertificates/aspnetdevcert.pfx and b/src/Servers/Kestrel/shared/test/TestCertificates/aspnetdevcert.pfx differ diff --git a/src/Servers/Kestrel/shared/test/TestCertificates/make-test-certs.sh b/src/Servers/Kestrel/shared/test/TestCertificates/make-test-certs.sh old mode 100644 new mode 100755 diff --git a/src/Servers/Kestrel/shared/test/TestCertificates/testCert.pfx b/src/Servers/Kestrel/shared/test/TestCertificates/testCert.pfx index 888ccb032a..94befa6e7f 100644 Binary files a/src/Servers/Kestrel/shared/test/TestCertificates/testCert.pfx and b/src/Servers/Kestrel/shared/test/TestCertificates/testCert.pfx differ diff --git a/src/Servers/Kestrel/shared/test/TransportTestHelpers/TestServer.cs b/src/Servers/Kestrel/shared/test/TransportTestHelpers/TestServer.cs index d79a6d9055..7ff959bb1f 100644 --- a/src/Servers/Kestrel/shared/test/TransportTestHelpers/TestServer.cs +++ b/src/Servers/Kestrel/shared/test/TransportTestHelpers/TestServer.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; using System.Linq; using System.Net; using System.Reflection; @@ -89,7 +90,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests c.Configure(context.ServerOptions); } - return new KestrelServer(sp.GetRequiredService(), context); + return new KestrelServer(new List() { sp.GetRequiredService() }, context); }); configureServices(services); }) diff --git a/src/Servers/Kestrel/stress/Program.cs b/src/Servers/Kestrel/stress/Program.cs index 8bddf0cddb..c962d5bfd7 100644 --- a/src/Servers/Kestrel/stress/Program.cs +++ b/src/Servers/Kestrel/stress/Program.cs @@ -237,7 +237,7 @@ public class Program using (HttpResponseMessage m = await ctx.HttpClient.SendAsync(req)) { ValidateResponse(m, httpVersion); - ValidateContent(content, await m.Content.ReadAsStringAsync());; + ValidateContent(content, await m.Content.ReadAsStringAsync()); } }), diff --git a/src/Servers/Kestrel/test/BindTests/AddressRegistrationTests.cs b/src/Servers/Kestrel/test/BindTests/AddressRegistrationTests.cs index 205bdbbe5f..787d3fe7c8 100644 --- a/src/Servers/Kestrel/test/BindTests/AddressRegistrationTests.cs +++ b/src/Servers/Kestrel/test/BindTests/AddressRegistrationTests.cs @@ -32,7 +32,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests [ConditionalFact] [HostNameIsReachable] - [SkipOnHelix("https://github.com/aspnet/AspNetCore/issues/7267")] + [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/7267")] public async Task RegisterAddresses_HostName_Success() { var hostName = Dns.GetHostName(); @@ -100,7 +100,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests [ConditionalTheory] [MemberData(nameof(IPEndPointRegistrationDataDynamicPort))] [IPv6SupportedCondition] - [Flaky("https://github.com/aspnet/AspNetCore-Internal/issues/2074", FlakyOn.AzP.macOS)] + [QuarantinedTest("https://github.com/dotnet/aspnetcore-internal/issues/2074")] public async Task RegisterIPEndPoint_DynamicPort_Success(IPEndPoint endPoint, string testUrl) { await RegisterIPEndPoint_Success(endPoint, testUrl); @@ -109,7 +109,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests [ConditionalTheory] [MemberData(nameof(IPEndPointRegistrationDataPort443))] [IPv6SupportedCondition] - [Flaky("https://github.com/aspnet/AspNetCore-Internal/issues/2711", FlakyOn.AzP.All)] + [QuarantinedTest("https://github.com/dotnet/aspnetcore-internal/issues/2711")] public async Task RegisterIPEndPoint_Port443_Success(IPEndPoint endpoint, string testUrl) { if (!CanBindToEndpoint(endpoint.Address, 443)) @@ -131,7 +131,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests [ConditionalTheory] [MemberData(nameof(AddressRegistrationDataIPv6Port5000Default))] [IPv6SupportedCondition] - [Flaky("https://github.com/aspnet/AspNetCore-Internal/issues/2711", FlakyOn.AzP.All)] + [QuarantinedTest("https://github.com/dotnet/aspnetcore-internal/issues/2711")] public async Task RegisterAddresses_IPv6Port5000Default_Success(string addressInput, string[] testUrls) { if (!CanBindToEndpoint(IPAddress.Loopback, 5000) || !CanBindToEndpoint(IPAddress.IPv6Loopback, 5000)) @@ -145,7 +145,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests [ConditionalTheory] [MemberData(nameof(AddressRegistrationDataIPv6Port80))] [IPv6SupportedCondition] - [Flaky("https://github.com/aspnet/AspNetCore-Internal/issues/2711", FlakyOn.AzP.All)] + [QuarantinedTest("https://github.com/dotnet/aspnetcore-internal/issues/2711")] public async Task RegisterAddresses_IPv6Port80_Success(string addressInput, string[] testUrls) { if (!CanBindToEndpoint(IPAddress.Loopback, 80) || !CanBindToEndpoint(IPAddress.IPv6Loopback, 80)) @@ -157,7 +157,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests } [ConditionalTheory] - [Flaky("https://github.com/aspnet/AspNetCore-Internal/issues/2179", FlakyOn.Helix.All)] + [QuarantinedTest("https://github.com/dotnet/aspnetcore-internal/issues/2179")] [MemberData(nameof(AddressRegistrationDataIPv6ScopeId))] [IPv6SupportedCondition] [IPv6ScopeIdPresentCondition] @@ -338,7 +338,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests [ConditionalFact] [HostNameIsReachable] - [SkipOnHelix("https://github.com/aspnet/AspNetCore/issues/7267")] + [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/7267")] public async Task ListenAnyIP_HostName_Success() { var hostName = Dns.GetHostName(); @@ -596,7 +596,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests } [Fact] - [Flaky("https://github.com/aspnet/AspNetCore-Internal/issues/2178", FlakyOn.All)] public async Task DoesNotOverrideDirectConfigurationWithIServerAddressesFeature_IfPreferHostingUrlsFalse() { var useUrlsAddress = $"http://127.0.0.1:0"; @@ -881,7 +880,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests var exception = Assert.Throws(() => host.Start()); var thisAddressString = $"http://{(addressFamily == AddressFamily.InterNetwork ? "127.0.0.1" : "[::1]")}:{port}"; - var otherAddressString = $"http://{(addressFamily == AddressFamily.InterNetworkV6? "127.0.0.1" : "[::1]")}:{port}"; + var otherAddressString = $"http://{(addressFamily == AddressFamily.InterNetworkV6 ? "127.0.0.1" : "[::1]")}:{port}"; if (exception.Message == CoreStrings.FormatEndpointAlreadyInUse(otherAddressString)) { diff --git a/src/Servers/Kestrel/test/FunctionalTests/Http2/HandshakeTests.cs b/src/Servers/Kestrel/test/FunctionalTests/Http2/HandshakeTests.cs index aaf101cced..7ffd9b2bc0 100644 --- a/src/Servers/Kestrel/test/FunctionalTests/Http2/HandshakeTests.cs +++ b/src/Servers/Kestrel/test/FunctionalTests/Http2/HandshakeTests.cs @@ -58,7 +58,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests.Http2 [ConditionalFact] [OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX)] - [OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win10, WindowsVersions.Win81)] + [MaximumOSVersion(OperatingSystems.Windows, WindowsVersions.Win7)] // Win7 SslStream is missing ALPN support. public void TlsAndHttp2NotSupportedOnWin7() { @@ -80,7 +80,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests.Http2 [ConditionalFact] [OSSkipCondition(OperatingSystems.MacOSX, SkipReason = "Missing SslStream ALPN support: https://github.com/dotnet/corefx/issues/30492")] - [SkipOnHelix("https://github.com/aspnet/AspNetCore/issues/10428", Queues = "Debian.8.Amd64.Open")] // Debian 8 uses OpenSSL 1.0.1 which does not support HTTP/2 + [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/10428", Queues = "Debian.8.Amd64;Debian.8.Amd64.Open")] // Debian 8 uses OpenSSL 1.0.1 which does not support HTTP/2 [MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win81)] public async Task TlsAlpnHandshakeSelectsHttp2From1and2() { @@ -111,7 +111,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests.Http2 [ConditionalFact] [OSSkipCondition(OperatingSystems.MacOSX, SkipReason = "Missing SslStream ALPN support: https://github.com/dotnet/corefx/issues/30492")] - [SkipOnHelix("https://github.com/aspnet/AspNetCore/issues/10428", Queues = "Debian.8.Amd64.Open")] // Debian 8 uses OpenSSL 1.0.1 which does not support HTTP/2 + [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/10428", Queues = "Debian.8.Amd64;Debian.8.Amd64.Open")] // Debian 8 uses OpenSSL 1.0.1 which does not support HTTP/2 [MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win81)] public async Task TlsAlpnHandshakeSelectsHttp2() { diff --git a/src/Servers/Kestrel/test/FunctionalTests/Http2/ShutdownTests.cs b/src/Servers/Kestrel/test/FunctionalTests/Http2/ShutdownTests.cs index af2b768fd1..9ce2aa0563 100644 --- a/src/Servers/Kestrel/test/FunctionalTests/Http2/ShutdownTests.cs +++ b/src/Servers/Kestrel/test/FunctionalTests/Http2/ShutdownTests.cs @@ -23,7 +23,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests.Http2 { [OSSkipCondition(OperatingSystems.MacOSX, SkipReason = "Missing SslStream ALPN support: https://github.com/dotnet/corefx/issues/30492")] [MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win10)] - [SkipOnHelix("https://github.com/aspnet/AspNetCore/issues/10428", Queues = "Debian.8.Amd64.Open")] // Debian 8 uses OpenSSL 1.0.1 which does not support HTTP/2 + [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/10428", Queues = "Debian.8.Amd64;Debian.8.Amd64.Open")] // Debian 8 uses OpenSSL 1.0.1 which does not support HTTP/2 public class ShutdownTests : TestApplicationErrorLoggerLoggedTest { private static X509Certificate2 _x509Certificate2 = TestResources.GetTestCertificate(); @@ -44,8 +44,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests.Http2 [CollectDump] [ConditionalFact] - [SkipOnHelix("https://github.com/aspnet/AspNetCore/issues/9985", Queues = "Fedora.28.Amd64.Open")] - [Flaky("https://github.com/aspnet/AspNetCore/issues/9985", FlakyOn.All)] + [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/9985", Queues = "Fedora.28.Amd64;Fedora.28.Amd64.Open")] public async Task GracefulShutdownWaitsForRequestsToFinish() { var requestStarted = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); @@ -95,13 +94,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests.Http2 await stopTask.DefaultTimeout(); } - Assert.Contains(TestApplicationErrorLogger.Messages, m => m.Message.Contains("Request finished in")); + Assert.Contains(TestApplicationErrorLogger.Messages, m => m.Message.Contains("Request finished ")); Assert.Contains(TestApplicationErrorLogger.Messages, m => m.Message.Contains("is closing.")); Assert.Contains(TestApplicationErrorLogger.Messages, m => m.Message.Contains("is closed. The last processed stream ID was 1.")); } [ConditionalFact] - [Flaky("https://github.com/aspnet/AspNetCore-Internal/issues/2667", FlakyOn.All)] public async Task GracefulTurnsAbortiveIfRequestsDoNotFinish() { var requestStarted = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); @@ -139,7 +137,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests.Http2 await requestStarted.Task.DefaultTimeout(); // Wait for the graceful shutdown log before canceling the token passed to StopAsync and triggering an ungraceful shutdown. - // Otherwise, graceful shutdown might be skipped causing there to be no corresponding log. https://github.com/aspnet/AspNetCore/issues/6556 + // Otherwise, graceful shutdown might be skipped causing there to be no corresponding log. https://github.com/dotnet/aspnetcore/issues/6556 var closingMessageTask = TestApplicationErrorLogger.WaitForMessage(m => m.Message.Contains("is closing.")).DefaultTimeout(); var cts = new CancellationTokenSource(); diff --git a/src/Servers/Kestrel/test/FunctionalTests/MaxRequestBufferSizeTests.cs b/src/Servers/Kestrel/test/FunctionalTests/MaxRequestBufferSizeTests.cs index f1ed973b5b..4236508cb9 100644 --- a/src/Servers/Kestrel/test/FunctionalTests/MaxRequestBufferSizeTests.cs +++ b/src/Servers/Kestrel/test/FunctionalTests/MaxRequestBufferSizeTests.cs @@ -108,7 +108,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests } } [Theory] - [Flaky("https://github.com/aspnet/AspNetCore-Internal/issues/2489", FlakyOn.AzP.All)] + [QuarantinedTest("https://github.com/dotnet/aspnetcore-internal/issues/2489")] [MemberData(nameof(LargeUploadData))] public async Task LargeUpload(long? maxRequestBufferSize, bool connectionAdapter, bool expectPause) { @@ -203,6 +203,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests } [Fact] + [QuarantinedTest] public async Task ServerShutsDownGracefullyWhenMaxRequestBufferSizeExceeded() { // Parameters diff --git a/src/Servers/Kestrel/test/FunctionalTests/RequestTests.cs b/src/Servers/Kestrel/test/FunctionalTests/RequestTests.cs index 6e4a5b8803..500547687a 100644 --- a/src/Servers/Kestrel/test/FunctionalTests/RequestTests.cs +++ b/src/Servers/Kestrel/test/FunctionalTests/RequestTests.cs @@ -132,9 +132,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests [ConditionalFact] [IPv6SupportedCondition] -#if LIBUV - [Flaky("https://github.com/aspnet/AspNetCore-Internal/issues/1977", FlakyOn.Helix.All)] // https://github.com/aspnet/AspNetCore/issues/8109 -#endif public Task RemoteIPv6Address() { return TestRemoteIPAddress("[::1]", "[::1]", "::1"); @@ -503,6 +500,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests [Theory] [MemberData(nameof(ConnectionMiddlewareData))] + [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/20163")] public async Task ConnectionClosedTokenFiresOnClientFIN(ListenOptions listenOptions) { var testContext = new TestServiceContext(LoggerFactory); @@ -538,7 +536,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests } [Theory] - [Flaky("https://github.com/aspnet/AspNetCore-Internal/issues/2181", FlakyOn.Helix.All)] [MemberData(nameof(ConnectionMiddlewareData))] public async Task ConnectionClosedTokenFiresOnServerFIN(ListenOptions listenOptions) { @@ -791,9 +788,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests } [Theory] -#if LIBUV - [Flaky("https://github.com/aspnet/AspNetCore-Internal/issues/1971", FlakyOn.Helix.All)] -#endif [MemberData(nameof(ConnectionMiddlewareData))] public async Task AppCanHandleClientAbortingConnectionMidRequest(ListenOptions listenOptions) { diff --git a/src/Servers/Kestrel/test/FunctionalTests/ResponseTests.cs b/src/Servers/Kestrel/test/FunctionalTests/ResponseTests.cs index d3fa68a1bd..05148cbebd 100644 --- a/src/Servers/Kestrel/test/FunctionalTests/ResponseTests.cs +++ b/src/Servers/Kestrel/test/FunctionalTests/ResponseTests.cs @@ -17,6 +17,7 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Server.Kestrel.Core; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; using Microsoft.AspNetCore.Server.Kestrel.Https; using Microsoft.AspNetCore.Server.Kestrel.Https.Internal; @@ -338,7 +339,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests } [Theory] - [Flaky("https://github.com/aspnet/AspNetCore-Internal/issues/1972", FlakyOn.All)] [MemberData(nameof(ConnectionMiddlewareData))] public async Task AppCanHandleClientAbortingConnectionMidResponse(ListenOptions listenOptions) { @@ -732,7 +732,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests } } - [Fact] + [ConditionalFact] public async Task ConnectionNotClosedWhenClientSatisfiesMinimumDataRateGivenLargeResponseChunks() { var chunkSize = 64 * 128 * 1024; @@ -755,6 +755,176 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests }; testContext.InitializeHeartbeat(); + var dateHeaderValueManager = new DateHeaderValueManager(); + dateHeaderValueManager.OnHeartbeat(DateTimeOffset.MinValue); + testContext.DateHeaderValueManager = dateHeaderValueManager; + + var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)); + + async Task App(HttpContext context) + { + context.RequestAborted.Register(() => + { + requestAborted = true; + }); + + for (var i = 0; i < chunkCount; i++) + { + await context.Response.BodyWriter.WriteAsync(new Memory(chunkData, 0, chunkData.Length), context.RequestAborted); + } + + appFuncCompleted.SetResult(null); + } + + using (var server = new TestServer(App, testContext, listenOptions)) + { + using (var connection = server.CreateConnection()) + { + // Close the connection with the last request so AssertStreamCompleted actually completes. + await connection.Send( + "GET / HTTP/1.1", + "Host:", + "", + ""); + + await connection.Receive( + "HTTP/1.1 200 OK", + $"Date: {dateHeaderValueManager.GetDateHeaderValues().String}"); + + // Make sure consuming a single chunk exceeds the 2 second timeout. + var targetBytesPerSecond = chunkSize / 4; + + // expectedBytes was determined by manual testing. A constant Date header is used, so this shouldn't change unless + // the response header writing logic or response body chunking logic itself changes. + await AssertBytesReceivedAtTargetRate(connection.Stream, expectedBytes: 33_553_537, targetBytesPerSecond); + await appFuncCompleted.Task.DefaultTimeout(); + + connection.ShutdownSend(); + await connection.WaitForConnectionClose(); + } + await server.StopAsync(); + } + + mockKestrelTrace.Verify(t => t.ResponseMinimumDataRateNotSatisfied(It.IsAny(), It.IsAny()), Times.Never()); + mockKestrelTrace.Verify(t => t.ConnectionStop(It.IsAny()), Times.Once()); + Assert.False(requestAborted); + } + + [Fact] + [CollectDump] + [QuarantinedTest] + public async Task ConnectionNotClosedWhenClientSatisfiesMinimumDataRateGivenLargeResponseHeaders() + { + var headerSize = 1024 * 1024; // 1 MB for each header value + var headerCount = 64; // 64 MB of headers per response + var requestCount = 4; // Minimum of 256 MB of total response headers + var headerValue = new string('a', headerSize); + var headerStringValues = new StringValues(Enumerable.Repeat(headerValue, headerCount).ToArray()); + + var requestAborted = false; + var mockKestrelTrace = new Mock(); + + var testContext = new TestServiceContext(LoggerFactory, mockKestrelTrace.Object) + { + ServerOptions = + { + Limits = + { + MinResponseDataRate = new MinDataRate(bytesPerSecond: 240, gracePeriod: TimeSpan.FromSeconds(2)) + } + } + }; + + testContext.InitializeHeartbeat(); + var dateHeaderValueManager = new DateHeaderValueManager(); + dateHeaderValueManager.OnHeartbeat(DateTimeOffset.MinValue); + testContext.DateHeaderValueManager = dateHeaderValueManager; + + var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)); + + async Task App(HttpContext context) + { + context.RequestAborted.Register(() => + { + requestAborted = true; + }); + + context.Response.Headers[$"X-Custom-Header"] = headerStringValues; + context.Response.ContentLength = 0; + + await context.Response.BodyWriter.FlushAsync(); + } + + using (var server = new TestServer(App, testContext, listenOptions)) + { + using (var connection = server.CreateConnection()) + { + for (var i = 0; i < requestCount - 1; i++) + { + await connection.Send( + "GET / HTTP/1.1", + "Host:", + "", + ""); + } + + await connection.Send( + "GET / HTTP/1.1", + "Host:", + "", + ""); + + await connection.Receive( + "HTTP/1.1 200 OK", + $"Date: {dateHeaderValueManager.GetDateHeaderValues().String}"); + + var minResponseSize = headerSize * headerCount; + var minTotalOutputSize = requestCount * minResponseSize; + + // Make sure consuming a single set of response headers exceeds the 2 second timeout. + var targetBytesPerSecond = minResponseSize / 4; + + // expectedBytes was determined by manual testing. A constant Date header is used, so this shouldn't change unless + // the response header writing logic itself changes. + await AssertBytesReceivedAtTargetRate(connection.Stream, expectedBytes: 268_439_596, targetBytesPerSecond); + connection.ShutdownSend(); + await connection.WaitForConnectionClose(); + } + + await server.StopAsync(); + } + + mockKestrelTrace.Verify(t => t.ResponseMinimumDataRateNotSatisfied(It.IsAny(), It.IsAny()), Times.Never()); + mockKestrelTrace.Verify(t => t.ConnectionStop(It.IsAny()), Times.Once()); + Assert.False(requestAborted); + } + + [Fact] + public async Task ClientCanReceiveFullConnectionCloseResponseWithoutErrorAtALowDataRate() + { + var chunkSize = 64 * 128 * 1024; + var chunkCount = 4; + var chunkData = new byte[chunkSize]; + + var requestAborted = false; + var appFuncCompleted = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var mockKestrelTrace = new Mock(); + + var testContext = new TestServiceContext(LoggerFactory, mockKestrelTrace.Object) + { + ServerOptions = + { + Limits = + { + MinResponseDataRate = new MinDataRate(bytesPerSecond: 240, gracePeriod: TimeSpan.FromSeconds(2)) + } + } + }; + + testContext.InitializeHeartbeat(); + var dateHeaderValueManager = new DateHeaderValueManager(); + dateHeaderValueManager.OnHeartbeat(DateTimeOffset.MinValue); + testContext.DateHeaderValueManager = dateHeaderValueManager; var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)); @@ -785,11 +955,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests "", ""); - var minTotalOutputSize = chunkCount * chunkSize; + await connection.Receive( + "HTTP/1.1 200 OK", + "Connection: close", + $"Date: {dateHeaderValueManager.GetDateHeaderValues().String}"); // Make sure consuming a single chunk exceeds the 2 second timeout. var targetBytesPerSecond = chunkSize / 4; - await AssertStreamCompleted(connection.Stream, minTotalOutputSize, targetBytesPerSecond); + + // expectedBytes was determined by manual testing. A constant Date header is used, so this shouldn't change unless + // the response header writing logic or response body chunking logic itself changes. + await AssertStreamCompletedAtTargetRate(connection.Stream, expectedBytes: 33_553_556, targetBytesPerSecond); await appFuncCompleted.Task.DefaultTimeout(); } await server.StopAsync(); @@ -800,87 +976,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests Assert.False(requestAborted); } - private bool ConnectionNotClosedWhenClientSatisfiesMinimumDataRateGivenLargeResponseHeadersRetryPredicate(Exception e) - => e is IOException && e.Message.Contains("Unable to read data from the transport connection: The I/O operation has been aborted because of either a thread exit or an application request"); - - [Fact] - [Flaky("https://github.com/dotnet/corefx/issues/30691", FlakyOn.AzP.Windows)] - [CollectDump] - public async Task ConnectionNotClosedWhenClientSatisfiesMinimumDataRateGivenLargeResponseHeaders() - { - var headerSize = 1024 * 1024; // 1 MB for each header value - var headerCount = 64; // 64 MB of headers per response - var requestCount = 4; // Minimum of 256 MB of total response headers - var headerValue = new string('a', headerSize); - var headerStringValues = new StringValues(Enumerable.Repeat(headerValue, headerCount).ToArray()); - - var requestAborted = false; - var mockKestrelTrace = new Mock(); - - var testContext = new TestServiceContext(LoggerFactory, mockKestrelTrace.Object) - { - ServerOptions = - { - Limits = - { - MinResponseDataRate = new MinDataRate(bytesPerSecond: 240, gracePeriod: TimeSpan.FromSeconds(2)) - } - } - }; - - testContext.InitializeHeartbeat(); - - var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)); - - async Task App(HttpContext context) - { - context.RequestAborted.Register(() => - { - requestAborted = true; - }); - - context.Response.Headers[$"X-Custom-Header"] = headerStringValues; - context.Response.ContentLength = 0; - - await context.Response.BodyWriter.FlushAsync(); - } - - using (var server = new TestServer(App, testContext, listenOptions)) - { - using (var connection = server.CreateConnection()) - { - for (var i = 0; i < requestCount - 1; i++) - { - await connection.Send( - "GET / HTTP/1.1", - "Host:", - "", - ""); - } - - // Close the connection with the last request so AssertStreamCompleted actually completes. - await connection.Send( - "GET / HTTP/1.1", - "Host:", - "Connection: close", - "", - ""); - - var responseSize = headerSize * headerCount; - var minTotalOutputSize = requestCount * responseSize; - - // Make sure consuming a single set of response headers exceeds the 2 second timeout. - var targetBytesPerSecond = responseSize / 4; - await AssertStreamCompleted(connection.Stream, minTotalOutputSize, targetBytesPerSecond); - } - await server.StopAsync(); - } - - mockKestrelTrace.Verify(t => t.ResponseMinimumDataRateNotSatisfied(It.IsAny(), It.IsAny()), Times.Never()); - mockKestrelTrace.Verify(t => t.ConnectionStop(It.IsAny()), Times.Once()); - Assert.False(requestAborted); - } - private async Task AssertStreamAborted(Stream stream, int totalBytes) { var receiveBuffer = new byte[64 * 1024]; @@ -908,7 +1003,30 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests Assert.True(totalReceived < totalBytes, $"{nameof(AssertStreamAborted)} Stream completed successfully."); } - private async Task AssertStreamCompleted(Stream stream, long minimumBytes, int targetBytesPerSecond) + private async Task AssertBytesReceivedAtTargetRate(Stream stream, int expectedBytes, int targetBytesPerSecond) + { + var receiveBuffer = new byte[64 * 1024]; + var totalReceived = 0; + var startTime = DateTimeOffset.UtcNow; + + do + { + var received = await stream.ReadAsync(receiveBuffer, 0, Math.Min(receiveBuffer.Length, expectedBytes - totalReceived)); + + Assert.NotEqual(0, received); + + totalReceived += received; + + var expectedTimeElapsed = TimeSpan.FromSeconds(totalReceived / targetBytesPerSecond); + var timeElapsed = DateTimeOffset.UtcNow - startTime; + if (timeElapsed < expectedTimeElapsed) + { + await Task.Delay(expectedTimeElapsed - timeElapsed); + } + } while (totalReceived < expectedBytes); + } + + private async Task AssertStreamCompletedAtTargetRate(Stream stream, long expectedBytes, int targetBytesPerSecond) { var receiveBuffer = new byte[64 * 1024]; var received = 0; @@ -928,7 +1046,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests } } while (received > 0); - Assert.True(totalReceived >= minimumBytes, $"{nameof(AssertStreamCompleted)} Stream aborted prematurely."); + Assert.Equal(expectedBytes, totalReceived); } public static TheoryData NullHeaderData diff --git a/src/Servers/Kestrel/test/FunctionalTests/UnixDomainSocketsTests.cs b/src/Servers/Kestrel/test/FunctionalTests/UnixDomainSocketsTests.cs index 12fe59d9e8..a6a49d8b3b 100644 --- a/src/Servers/Kestrel/test/FunctionalTests/UnixDomainSocketsTests.cs +++ b/src/Servers/Kestrel/test/FunctionalTests/UnixDomainSocketsTests.cs @@ -12,6 +12,8 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Connections.Features; using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Testing; @@ -96,7 +98,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests var read = 0; while (read < data.Length) { - read += await socket.ReceiveAsync(buffer.AsMemory(read, buffer.Length - read), SocketFlags.None).DefaultTimeout(); + var bytesReceived = await socket.ReceiveAsync(buffer.AsMemory(read, buffer.Length - read), SocketFlags.None).DefaultTimeout(); + read += bytesReceived; + if (bytesReceived <= 0) break; } Assert.Equal(data, buffer); @@ -114,6 +118,73 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests } } +#if LIBUV + [OSSkipCondition(OperatingSystems.Windows, SkipReason = "Libuv does not support unix domain sockets on Windows.")] +#else + [MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win10_RS4)] +#endif + [ConditionalFact] + [CollectDump] + public async Task TestUnixDomainSocketWithUrl() + { + var path = Path.GetTempFileName(); + var url = $"http://unix:/{path}"; + + Delete(path); + + try + { + var hostBuilder = TransportSelector.GetWebHostBuilder() + .UseUrls(url) + .UseKestrel() + .ConfigureServices(AddTestLogging) + .Configure(app => + { + app.Run(async context => + { + await context.Response.WriteAsync("Hello World"); + }); + }); + + using (var host = hostBuilder.Build()) + { + await host.StartAsync().DefaultTimeout(); + + // https://github.com/dotnet/corefx/issues/5999 + // .NET Core HttpClient does not support unix sockets, it's difficult to parse raw response data. below is a little hacky way. + using (var socket = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.Unspecified)) + { + await socket.ConnectAsync(new UnixDomainSocketEndPoint(path)).DefaultTimeout(); + + var httpRequest = Encoding.ASCII.GetBytes("GET / HTTP/1.1\r\nHost:\r\nConnection: close\r\n\r\n"); + await socket.SendAsync(httpRequest, SocketFlags.None).DefaultTimeout(); + + var readBuffer = new byte[512]; + var read = 0; + while (true) + { + var bytesReceived = await socket.ReceiveAsync(readBuffer.AsMemory(read), SocketFlags.None).DefaultTimeout(); + read += bytesReceived; + if (bytesReceived <= 0) break; + } + + var httpResponse = Encoding.ASCII.GetString(readBuffer, 0, read); + int httpStatusStart = httpResponse.IndexOf(' ') + 1; + int httpStatusEnd = httpResponse.IndexOf(' ', httpStatusStart); + + var httpStatus = int.Parse(httpResponse.Substring(httpStatusStart, httpStatusEnd - httpStatusStart)); + Assert.Equal(httpStatus, StatusCodes.Status200OK); + + } + await host.StopAsync().DefaultTimeout(); + } + } + finally + { + Delete(path); + } + } + private static void Delete(string path) { try diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/ChunkedResponseTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/ChunkedResponseTests.cs index 3d109476a5..f279141a7d 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/ChunkedResponseTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/ChunkedResponseTests.cs @@ -78,6 +78,42 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests } } + [Fact] + public async Task IgnoresChangesToHttpProtocol() + { + var testContext = new TestServiceContext(LoggerFactory); + + await using (var server = new TestServer(async httpContext => + { + httpContext.Request.Protocol = "HTTP/2"; // Doesn't support chunking. This change should be ignored. + var response = httpContext.Response; + await response.BodyWriter.WriteAsync(new Memory(Encoding.ASCII.GetBytes("Hello "), 0, 6)); + await response.BodyWriter.WriteAsync(new Memory(Encoding.ASCII.GetBytes("World!"), 0, 6)); + }, testContext)) + { + using (var connection = server.CreateConnection()) + { + await connection.Send( + "GET / HTTP/1.1", + "Host:", + "", + ""); + await connection.Receive( + "HTTP/1.1 200 OK", + $"Date: {testContext.DateHeaderValue}", + "Transfer-Encoding: chunked", + "", + "6", + "Hello ", + "6", + "World!", + "0", + "", + ""); + } + } + } + [Fact] public async Task ResponsesAreChunkedAutomaticallyForHttp11NonKeepAliveRequests() { diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/ConnectionLimitTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/ConnectionLimitTests.cs index 43c92b0121..532629f0dd 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/ConnectionLimitTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/ConnectionLimitTests.cs @@ -142,7 +142,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests } [Fact] - [Flaky("https://github.com/aspnet/KestrelHttpServer/issues/2282", FlakyOn.AzP.macOS)] + [QuarantinedTest("https://github.com/aspnet/KestrelHttpServer/issues/2282")] public async Task ConnectionCountingReturnsToZero() { const int count = 100; diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs index a294d65225..6fd7ce1d8b 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs @@ -6,13 +6,16 @@ using System.Buffers; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Net.Http; +using System.Net.Http.HPack; using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Features; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2; -using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.Logging; using Microsoft.Net.Http.Headers; @@ -23,6 +26,517 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests { public class Http2ConnectionTests : Http2TestBase { + [Fact] + [QuarantinedTest] + public async Task FlowControl_ParallelStreams_FirstInFirstOutOrder() + { + // Increase response buffer size so there is no delay in writing to it. + // We only want to hit flow control back-pressure and not pipe back-pressure. + // This fixes flakyness https://github.com/dotnet/aspnetcore/pull/19949 + _serviceContext.ServerOptions.Limits.MaxResponseBufferSize = 128 * 1024; + + var writeTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + + await InitializeConnectionAsync(async c => + { + // Send headers + await c.Response.Body.FlushAsync(); + + // Send large data (3 larger than window size) + var writeTask = c.Response.Body.WriteAsync(new byte[65538]); + + // Notify test that write has started + writeTcs.SetResult(null); + + // Wait for write to complete + await writeTask; + }); + + await StartStreamAsync(1, _browserRequestHeaders, endStream: true); + // Ensure the stream window size is large enough + await SendWindowUpdateAsync(streamId: 1, 65538); + + await ExpectAsync(Http2FrameType.HEADERS, + withLength: 33, + withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, + withStreamId: 1); + + await writeTcs.Task; + + await ExpectAsync(Http2FrameType.DATA, + withLength: 16384, + withFlags: (byte)Http2DataFrameFlags.NONE, + withStreamId: 1); + await ExpectAsync(Http2FrameType.DATA, + withLength: 16384, + withFlags: (byte)Http2DataFrameFlags.NONE, + withStreamId: 1); + await ExpectAsync(Http2FrameType.DATA, + withLength: 16384, + withFlags: (byte)Http2DataFrameFlags.NONE, + withStreamId: 1); + await ExpectAsync(Http2FrameType.DATA, + withLength: 16383, + withFlags: (byte)Http2DataFrameFlags.NONE, + withStreamId: 1); + + // 3 byte is remaining on stream 1 + + writeTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + + await StartStreamAsync(3, _browserRequestHeaders, endStream: true); + // Ensure the stream window size is large enough + await SendWindowUpdateAsync(streamId: 3, 65538); + + await ExpectAsync(Http2FrameType.HEADERS, + withLength: 33, + withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, + withStreamId: 3); + + await writeTcs.Task; + + await SendWindowUpdateAsync(streamId: 0, 1); + + // FIFO means stream 1 returns data first + await ExpectAsync(Http2FrameType.DATA, + withLength: 1, + withFlags: (byte)Http2DataFrameFlags.NONE, + withStreamId: 1); + + await SendWindowUpdateAsync(streamId: 0, 1); + + // Stream 3 data + await ExpectAsync(Http2FrameType.DATA, + withLength: 1, + withFlags: (byte)Http2DataFrameFlags.NONE, + withStreamId: 3); + + await SendWindowUpdateAsync(streamId: 0, 1); + + // Stream 1 data + await ExpectAsync(Http2FrameType.DATA, + withLength: 1, + withFlags: (byte)Http2DataFrameFlags.NONE, + withStreamId: 1); + + await SendWindowUpdateAsync(streamId: 0, 1); + + // Stream 3 data + await ExpectAsync(Http2FrameType.DATA, + withLength: 1, + withFlags: (byte)Http2DataFrameFlags.NONE, + withStreamId: 3); + + await SendWindowUpdateAsync(streamId: 0, 1); + + // Stream 1 data + await ExpectAsync(Http2FrameType.DATA, + withLength: 1, + withFlags: (byte)Http2DataFrameFlags.NONE, + withStreamId: 1); + + // Stream 1 ends + await ExpectAsync(Http2FrameType.DATA, + withLength: 0, + withFlags: (byte)Http2DataFrameFlags.END_STREAM, + withStreamId: 1); + + await SendWindowUpdateAsync(streamId: 0, 1); + + // Stream 3 data + await ExpectAsync(Http2FrameType.DATA, + withLength: 1, + withFlags: (byte)Http2DataFrameFlags.NONE, + withStreamId: 3); + + await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false); + } + + [Fact] + public async Task FlowControl_OneStream_CorrectlyAwaited() + { + await InitializeConnectionAsync(async c => + { + // Send headers + await c.Response.Body.FlushAsync(); + + // Send large data (1 larger than window size) + await c.Response.Body.WriteAsync(new byte[65540]); + }); + + // Ensure the connection window size is large enough + await SendWindowUpdateAsync(streamId: 0, 65537); + + await StartStreamAsync(1, _browserRequestHeaders, endStream: true); + + await ExpectAsync(Http2FrameType.HEADERS, + withLength: 33, + withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, + withStreamId: 1); + await ExpectAsync(Http2FrameType.DATA, + withLength: 16384, + withFlags: (byte)Http2DataFrameFlags.NONE, + withStreamId: 1); + await ExpectAsync(Http2FrameType.DATA, + withLength: 16384, + withFlags: (byte)Http2DataFrameFlags.NONE, + withStreamId: 1); + await ExpectAsync(Http2FrameType.DATA, + withLength: 16384, + withFlags: (byte)Http2DataFrameFlags.NONE, + withStreamId: 1); + await ExpectAsync(Http2FrameType.DATA, + withLength: 16383, + withFlags: (byte)Http2DataFrameFlags.NONE, + withStreamId: 1); + + // 2 bytes remaining + + await SendWindowUpdateAsync(streamId: 1, 1); + await ExpectAsync(Http2FrameType.DATA, + withLength: 1, + withFlags: (byte)Http2DataFrameFlags.NONE, + withStreamId: 1); + + await SendWindowUpdateAsync(streamId: 1, 1); + await ExpectAsync(Http2FrameType.DATA, + withLength: 1, + withFlags: (byte)Http2DataFrameFlags.NONE, + withStreamId: 1); + + await SendWindowUpdateAsync(streamId: 1, 1); + await ExpectAsync(Http2FrameType.DATA, + withLength: 1, + withFlags: (byte)Http2DataFrameFlags.NONE, + withStreamId: 1); + + await SendWindowUpdateAsync(streamId: 1, 1); + await ExpectAsync(Http2FrameType.DATA, + withLength: 1, + withFlags: (byte)Http2DataFrameFlags.NONE, + withStreamId: 1); + + await SendWindowUpdateAsync(streamId: 1, 1); + await ExpectAsync(Http2FrameType.DATA, + withLength: 1, + withFlags: (byte)Http2DataFrameFlags.NONE, + withStreamId: 1); + + await ExpectAsync(Http2FrameType.DATA, + withLength: 0, + withFlags: (byte)Http2DataFrameFlags.END_STREAM, + withStreamId: 1); + + await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); + } + + [Fact] + public async Task RequestHeaderStringReuse_MultipleStreams_KnownHeaderReused() + { + IEnumerable> requestHeaders = new[] + { + new KeyValuePair(HeaderNames.Method, "GET"), + new KeyValuePair(HeaderNames.Path, "/hello"), + new KeyValuePair(HeaderNames.Scheme, "http"), + new KeyValuePair(HeaderNames.Authority, "localhost:80"), + new KeyValuePair(HeaderNames.ContentType, "application/json") + }; + + await InitializeConnectionAsync(_readHeadersApplication); + + await StartStreamAsync(1, requestHeaders, endStream: true); + + await ExpectAsync(Http2FrameType.HEADERS, + withLength: 37, + withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM), + withStreamId: 1); + + var contentType1 = _receivedHeaders["Content-Type"]; + + // Ping will trigger the stream to be returned to the pool so we can assert it + await SendPingAsync(Http2PingFrameFlags.NONE); + await ExpectAsync(Http2FrameType.PING, + withLength: 8, + withFlags: (byte)Http2PingFrameFlags.ACK, + withStreamId: 0); + + // Stream has been returned to the pool + Assert.Equal(1, _connection.StreamPool.Count); + + await StartStreamAsync(3, requestHeaders, endStream: true); + + await ExpectAsync(Http2FrameType.HEADERS, + withLength: 37, + withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM), + withStreamId: 3); + + var contentType2 = _receivedHeaders["Content-Type"]; + + Assert.Same(contentType1, contentType2); + + await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false); + } + + [Fact] + public async Task StreamPool_SingleStream_ReturnedToPool() + { + var serverTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + + await InitializeConnectionAsync(async context => + { + await serverTcs.Task; + await _echoApplication(context); + }); + + Assert.Equal(0, _connection.StreamPool.Count); + + await StartStreamAsync(1, _browserRequestHeaders, endStream: true); + + var stream = _connection._streams[1]; + serverTcs.SetResult(null); + + await ExpectAsync(Http2FrameType.HEADERS, + withLength: 37, + withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM), + withStreamId: 1); + + // Ping will trigger the stream to be returned to the pool so we can assert it + await SendPingAsync(Http2PingFrameFlags.NONE); + await ExpectAsync(Http2FrameType.PING, + withLength: 8, + withFlags: (byte)Http2PingFrameFlags.ACK, + withStreamId: 0); + + // Stream has been returned to the pool + Assert.Equal(1, _connection.StreamPool.Count); + + await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); + + var output = (Http2OutputProducer)stream.Output; + await output._dataWriteProcessingTask; + } + + [Fact] + public async Task StreamPool_MultipleStreamsConcurrent_StreamsReturnedToPool() + { + await InitializeConnectionAsync(_echoApplication); + + Assert.Equal(0, _connection.StreamPool.Count); + + await StartStreamAsync(1, _browserRequestHeaders, endStream: false); + await StartStreamAsync(3, _browserRequestHeaders, endStream: false); + + await SendDataAsync(1, _helloBytes, endStream: true); + + await ExpectAsync(Http2FrameType.HEADERS, + withLength: 33, + withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, + withStreamId: 1); + await ExpectAsync(Http2FrameType.DATA, + withLength: 5, + withFlags: (byte)Http2DataFrameFlags.NONE, + withStreamId: 1); + await ExpectAsync(Http2FrameType.DATA, + withLength: 0, + withFlags: (byte)Http2DataFrameFlags.END_STREAM, + withStreamId: 1); + + await SendDataAsync(3, _helloBytes, endStream: true); + + await ExpectAsync(Http2FrameType.HEADERS, + withLength: 33, + withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, + withStreamId: 3); + await ExpectAsync(Http2FrameType.DATA, + withLength: 5, + withFlags: (byte)Http2DataFrameFlags.NONE, + withStreamId: 3); + await ExpectAsync(Http2FrameType.DATA, + withLength: 0, + withFlags: (byte)Http2DataFrameFlags.END_STREAM, + withStreamId: 3); + + // Ping will trigger the stream to be returned to the pool so we can assert it + await SendPingAsync(Http2PingFrameFlags.NONE); + await ExpectAsync(Http2FrameType.PING, + withLength: 8, + withFlags: (byte)Http2PingFrameFlags.ACK, + withStreamId: 0); + + // Streams have been returned to the pool + Assert.Equal(2, _connection.StreamPool.Count); + + await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false); + } + + [Fact] + [QuarantinedTest] + public async Task StreamPool_MultipleStreamsInSequence_PooledStreamReused() + { + TaskCompletionSource appDelegateTcs = null; + + await InitializeConnectionAsync(async context => + { + await appDelegateTcs.Task; + }); + + Assert.Equal(0, _connection.StreamPool.Count); + + appDelegateTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + await StartStreamAsync(1, _browserRequestHeaders, endStream: true); + + appDelegateTcs.TrySetResult(null); + + await ExpectAsync(Http2FrameType.HEADERS, + withLength: 37, + withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM), + withStreamId: 1); + + // Ping will trigger the stream to be returned to the pool so we can assert it + await SendPingAsync(Http2PingFrameFlags.NONE); + await ExpectAsync(Http2FrameType.PING, + withLength: 8, + withFlags: (byte)Http2PingFrameFlags.ACK, + withStreamId: 0); + + // Stream has been returned to the pool + Assert.Equal(1, _connection.StreamPool.Count); + + appDelegateTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + await StartStreamAsync(3, _browserRequestHeaders, endStream: true); + + // New stream has been taken from the pool + Assert.Equal(0, _connection.StreamPool.Count); + + appDelegateTcs.TrySetResult(null); + + await ExpectAsync(Http2FrameType.HEADERS, + withLength: 37, + withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM), + withStreamId: 3); + + // Ping will trigger the stream to be returned to the pool so we can assert it + await SendPingAsync(Http2PingFrameFlags.NONE); + await ExpectAsync(Http2FrameType.PING, + withLength: 8, + withFlags: (byte)Http2PingFrameFlags.ACK, + withStreamId: 0); + + // Stream was reused and returned to the pool + Assert.Equal(1, _connection.StreamPool.Count); + + await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false); + } + + [Fact] + [QuarantinedTest] + public async Task StreamPool_StreamIsInvalidState_DontReturnedToPool() + { + var serverTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + + await InitializeConnectionAsync(async context => + { + await serverTcs.Task; + + await context.Response.WriteAsync("Content"); + throw new InvalidOperationException("Put the stream into an invalid state by throwing after writing to response."); + }); + + await StartStreamAsync(1, _browserRequestHeaders, endStream: true); + + var stream = _connection._streams[1]; + serverTcs.SetResult(null); + + await ExpectAsync(Http2FrameType.HEADERS, + withLength: 33, + withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, + withStreamId: 1); + await ExpectAsync(Http2FrameType.DATA, + withLength: 7, + withFlags: (byte)Http2DataFrameFlags.NONE, + withStreamId: 1); + await WaitForStreamErrorAsync(1, Http2ErrorCode.INTERNAL_ERROR, null); + + // Ping will trigger the stream to be returned to the pool so we can assert it + await SendPingAsync(Http2PingFrameFlags.NONE); + await ExpectAsync(Http2FrameType.PING, + withLength: 8, + withFlags: (byte)Http2PingFrameFlags.ACK, + withStreamId: 0); + + // Stream is not returned to the pool + Assert.Equal(0, _connection.StreamPool.Count); + + var output = (Http2OutputProducer)stream.Output; + await output._dataWriteProcessingTask; + + await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); + } + + [Fact] + public async Task StreamPool_EndedStreamErrorsOnStart_NotReturnedToPool() + { + await InitializeConnectionAsync(_echoApplication); + + _connection.ServerSettings.MaxConcurrentStreams = 1; + + await StartStreamAsync(1, _browserRequestHeaders, endStream: false); + + // This stream will error because it exceeds max concurrent streams + await StartStreamAsync(3, _browserRequestHeaders, endStream: true); + await WaitForStreamErrorAsync(3, Http2ErrorCode.REFUSED_STREAM, CoreStrings.Http2ErrorMaxStreams); + + // Ping will trigger the stream to be returned to the pool so we can assert it + await SendPingAsync(Http2PingFrameFlags.NONE); + await ExpectAsync(Http2FrameType.PING, + withLength: 8, + withFlags: (byte)Http2PingFrameFlags.ACK, + withStreamId: 0); + + // Stream not returned to the pool + Assert.Equal(0, _connection.StreamPool.Count); + + await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false); + } + + [Fact] + public async Task StreamPool_UnendedStreamErrorsOnStart_NotReturnedToPool() + { + _serviceContext.ServerOptions.Limits.MinRequestBodyDataRate = null; + + await InitializeConnectionAsync(_echoApplication); + + _connection.ServerSettings.MaxConcurrentStreams = 1; + + await StartStreamAsync(1, _browserRequestHeaders, endStream: false); + + // This stream will error because it exceeds max concurrent streams + await StartStreamAsync(3, _browserRequestHeaders, endStream: false); + await WaitForStreamErrorAsync(3, Http2ErrorCode.REFUSED_STREAM, CoreStrings.Http2ErrorMaxStreams); + + // Ping will trigger the stream to be returned to the pool so we can assert it + await SendPingAsync(Http2PingFrameFlags.NONE); + await ExpectAsync(Http2FrameType.PING, + withLength: 8, + withFlags: (byte)Http2PingFrameFlags.ACK, + withStreamId: 0); + + AdvanceClock(TimeSpan.FromTicks(Constants.RequestBodyDrainTimeout.Ticks + 1)); + + // Ping will trigger the stream to attempt to be returned to the pool + await SendPingAsync(Http2PingFrameFlags.NONE); + await ExpectAsync(Http2FrameType.PING, + withLength: 8, + withFlags: (byte)Http2PingFrameFlags.ACK, + withStreamId: 0); + + // Drain timeout has past but the stream was not returned because it is unfinished + Assert.Equal(0, _connection.StreamPool.Count); + + await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false); + + } + [Fact] public async Task Frame_Received_OverMaxSize_FrameError() { @@ -52,7 +566,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendDataAsync(1, new byte[length], endStream: true); await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); // The client's settings is still defaulted to Http2PeerSettings.MinAllowedMaxFrameSize so the echo response will come back in two separate frames @@ -81,7 +595,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendDataAsync(1, _helloWorldBytes, endStream: true); await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); var dataFrame = await ExpectAsync(Http2FrameType.DATA, @@ -107,7 +621,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendDataAsync(1, _maxData, endStream: true); await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); @@ -150,7 +664,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests } await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); @@ -271,7 +785,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendDataAsync(1, _noData, endStream: true); await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); var dataFrame = await ExpectAsync(Http2FrameType.DATA, @@ -299,7 +813,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendDataAsync(1, _helloBytes, endStream: false); await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); var stream1DataFrame1 = await ExpectAsync(Http2FrameType.DATA, @@ -310,7 +824,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendDataAsync(3, _helloBytes, endStream: false); await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 3); var stream3DataFrame1 = await ExpectAsync(Http2FrameType.DATA, @@ -379,7 +893,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests } await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); @@ -431,7 +945,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests withStreamId: 0); await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 3); @@ -509,7 +1023,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests stream3ReadFinished.TrySetResult(null); await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 3); await ExpectAsync(Http2FrameType.DATA, @@ -524,7 +1038,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests stream1ReadFinished.TrySetResult(null); await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); await ExpectAsync(Http2FrameType.DATA, @@ -551,7 +1065,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendDataWithPaddingAsync(1, _helloWorldBytes, padLength, endStream: true); await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); var dataFrame = await ExpectAsync(Http2FrameType.DATA, @@ -596,7 +1110,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests } await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); @@ -655,7 +1169,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests } await ExpectAsync(Http2FrameType.HEADERS, - withLength: 55, + withLength: 37, withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM), withStreamId: 1); @@ -724,7 +1238,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendDataAsync(1, _maxData, endStream: false); await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); @@ -899,7 +1413,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, _postRequestHeaders, endStream: true); await ExpectAsync(Http2FrameType.HEADERS, - withLength: 55, + withLength: 37, withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM), withStreamId: 1); @@ -919,7 +1433,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [Fact] public async Task Frame_MultipleStreams_CanBeCreatedIfClientCountIsLessThanActualMaxStreamCount() { - _serviceContext.ServerOptions.Limits.Http2.MaxStreamsPerConnection = 1; + _serviceContext.ServerOptions.Limits.Http2.MaxStreamsPerConnection = 1; var firstRequestBlock = new TaskCompletionSource(); var firstRequestReceived = new TaskCompletionSource(); var makeFirstRequestWait = false; @@ -941,7 +1455,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(3, _browserRequestHeaders, endStream: true); await ExpectAsync(Http2FrameType.HEADERS, - withLength: 55, + withLength: 37, withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM), withStreamId: 3); @@ -954,7 +1468,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests public async Task Frame_MultipleStreams_RequestsNotFinished_EnhanceYourCalm() { _serviceContext.ServerOptions.Limits.Http2.MaxStreamsPerConnection = 1; - var tcs = new TaskCompletionSource(); + var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); await InitializeConnectionAsync(async context => { await tcs.Task.DefaultTimeout(); @@ -991,7 +1505,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(3, _browserRequestHeaders, endStream: true); await ExpectAsync(Http2FrameType.HEADERS, - withLength: 55, + withLength: 37, withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM), withStreamId: 3); @@ -1095,7 +1609,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, _browserRequestHeaders, endStream: true); await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); @@ -1116,7 +1630,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(3, _browserRequestHeaders, endStream: true); await ExpectAsync(Http2FrameType.HEADERS, - withLength: 55, + withLength: 37, withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM), withStreamId: 3); @@ -1150,7 +1664,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, _browserRequestHeaders, endStream: true); await ExpectAsync(Http2FrameType.HEADERS, - withLength: 55, + withLength: 37, withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM), withStreamId: 1); @@ -1165,7 +1679,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, _browserRequestHeaders, endStream: true); await ExpectAsync(Http2FrameType.HEADERS, - withLength: 55, + withLength: 37, withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM), withStreamId: 1); @@ -1185,7 +1699,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendHeadersWithPaddingAsync(1, _browserRequestHeaders, padLength, endStream: true); await ExpectAsync(Http2FrameType.HEADERS, - withLength: 55, + withLength: 37, withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM), withStreamId: 1); @@ -1202,7 +1716,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendHeadersWithPriorityAsync(1, _browserRequestHeaders, priority: 42, streamDependency: 0, endStream: true); await ExpectAsync(Http2FrameType.HEADERS, - withLength: 55, + withLength: 37, withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM), withStreamId: 1); @@ -1222,7 +1736,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendHeadersWithPaddingAndPriorityAsync(1, _browserRequestHeaders, padLength, priority: 42, streamDependency: 0, endStream: true); await ExpectAsync(Http2FrameType.HEADERS, - withLength: 55, + withLength: 37, withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM), withStreamId: 1); @@ -1248,7 +1762,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests // The second stream should end first, since the first one is waiting for the request body. await ExpectAsync(Http2FrameType.HEADERS, - withLength: 55, + withLength: 37, withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM), withStreamId: 3); @@ -1260,7 +1774,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM, _requestTrailers); await ExpectAsync(Http2FrameType.HEADERS, - withLength: 55, + withLength: 37, withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM), withStreamId: 1); @@ -1294,7 +1808,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendDataAsync(1, _helloBytes, endStream: true); await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); await ExpectAsync(Http2FrameType.DATA, @@ -1344,14 +1858,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests finishSecondRequest.TrySetResult(null); await ExpectAsync(Http2FrameType.HEADERS, - withLength: 55, + withLength: 37, withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM), withStreamId: 3); finishFirstRequest.TrySetResult(null); await ExpectAsync(Http2FrameType.HEADERS, - withLength: 55, + withLength: 37, withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM), withStreamId: 1); @@ -1377,7 +1891,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests requestBlocker.SetResult(0); await ExpectAsync(Http2FrameType.HEADERS, - withLength: 55, + withLength: 37, withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM), withStreamId: 1); @@ -1420,7 +1934,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, _browserRequestHeaders, endStream: true); await ExpectAsync(Http2FrameType.HEADERS, - withLength: 55, + withLength: 37, withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM), withStreamId: 1); @@ -1463,7 +1977,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(3, _browserRequestHeaders, endStream: true); await ExpectAsync(Http2FrameType.HEADERS, - withLength: 55, + withLength: 37, withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM), withStreamId: 3); @@ -1564,7 +2078,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests ignoreNonGoAwayFrames: false, expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.COMPRESSION_ERROR, - expectedErrorMessage: CoreStrings.HPackErrorIncompleteHeaderBlock); + expectedErrorMessage: SR.net_http_hpack_incomplete_header_block); } [Fact] @@ -1592,7 +2106,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests ignoreNonGoAwayFrames: false, expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.COMPRESSION_ERROR, - expectedErrorMessage: CoreStrings.HPackErrorIntegerTooBig); + expectedErrorMessage: SR.net_http_hpack_bad_integer); } [Theory] @@ -1687,7 +2201,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM, headers); await ExpectAsync(Http2FrameType.HEADERS, - withLength: 55, + withLength: 37, withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM), withStreamId: 1); @@ -1718,14 +2232,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests { await InitializeConnectionAsync(_noopApplication); - await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS, headers); + await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM, headers); await WaitForStreamErrorAsync( expectedStreamId: 1, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.Http2ErrorMissingMandatoryPseudoHeaderFields); // Verify that the stream ID can't be re-used - await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS, _browserRequestHeaders); + await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM, _browserRequestHeaders); await WaitForConnectionErrorAsync( ignoreNonGoAwayFrames: false, expectedLastStreamId: 1, @@ -1832,7 +2346,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM, headers); await ExpectAsync(Http2FrameType.HEADERS, - withLength: 55, + withLength: 37, withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM), withStreamId: 1); @@ -1970,7 +2484,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, _browserRequestHeaders, endStream: true); await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); @@ -1995,7 +2509,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests // The headers, but not the data for stream 3, can be sent prior to any window updates. await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 3); @@ -2079,7 +2593,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(streamId, _browserRequestHeaders, endStream: true); await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: streamId); @@ -2559,7 +3073,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests _connection.ServerSettings.MaxFrameSize = Http2PeerSettings.MaxAllowedMaxFrameSize; // This includes the default response headers such as :status, etc - var defaultResponseHeaderLength = 37; + var defaultResponseHeaderLength = 33; var headerValueLength = Http2PeerSettings.MinAllowedMaxFrameSize; // First byte is always 0 // Second byte is the length of header name which is 1 @@ -2629,7 +3143,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, _browserRequestHeaders, endStream: true); await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); await ExpectAsync(Http2FrameType.DATA, @@ -2778,7 +3292,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendDataAsync(1, _helloBytes, true); await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); await ExpectAsync(Http2FrameType.DATA, @@ -2791,7 +3305,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests withStreamId: 1); await SendDataAsync(3, _helloBytes, true); await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 3); await ExpectAsync(Http2FrameType.DATA, @@ -2864,7 +3378,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, _browserRequestHeaders, endStream: true); await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); @@ -2887,13 +3401,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests // The headers, but not the data for the stream, can still be sent. await StartStreamAsync(3, _browserRequestHeaders, endStream: true); await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 3); await StartStreamAsync(5, _browserRequestHeaders, endStream: true); await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 5); @@ -2957,7 +3471,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(streamId, _browserRequestHeaders, endStream: true); await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: streamId); @@ -3190,7 +3704,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, _browserRequestHeaders, endStream: true); await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); @@ -3247,7 +3761,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendDataAsync(1, _helloWorldBytes, endStream: true); await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); @@ -3286,7 +3800,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendDataAsync(1, _helloWorldBytes, endStream: true); await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); @@ -3338,7 +3852,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, _twoContinuationsRequestHeaders, endStream: true); await ExpectAsync(Http2FrameType.HEADERS, - withLength: 55, + withLength: 37, withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM), withStreamId: 1); @@ -3350,6 +3864,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [Theory] [InlineData(true)] [InlineData(false)] + [QuarantinedTest] public async Task CONTINUATION_Received_WithTrailers_Available(bool sendData) { await InitializeConnectionAsync(_readTrailersApplication); @@ -3364,7 +3879,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests // The second stream should end first, since the first one is waiting for the request body. await ExpectAsync(Http2FrameType.HEADERS, - withLength: 55, + withLength: 37, withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM), withStreamId: 3); @@ -3387,7 +3902,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendContinuationAsync(1, Http2ContinuationFrameFlags.END_HEADERS, trailers); await ExpectAsync(Http2FrameType.HEADERS, - withLength: 55, + withLength: 37, withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM), withStreamId: 1); @@ -3409,8 +3924,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests { await InitializeConnectionAsync(_readHeadersApplication); - await SendHeadersAsync(1, Http2HeadersFrameFlags.NONE, _oneContinuationRequestHeaders); - await SendContinuationAsync(3, Http2ContinuationFrameFlags.END_HEADERS); + var headersEnumerator = GetHeadersEnumerator(_oneContinuationRequestHeaders); + await SendHeadersAsync(1, Http2HeadersFrameFlags.NONE, headersEnumerator); + await SendContinuationAsync(3, Http2ContinuationFrameFlags.END_HEADERS, headersEnumerator); await WaitForConnectionErrorAsync( ignoreNonGoAwayFrames: false, @@ -3431,7 +3947,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests ignoreNonGoAwayFrames: false, expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.COMPRESSION_ERROR, - expectedErrorMessage: CoreStrings.HPackErrorIncompleteHeaderBlock); + expectedErrorMessage: SR.net_http_hpack_incomplete_header_block); } [Theory] @@ -3457,7 +3973,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests { await InitializeConnectionAsync(_noopApplication); - Assert.True(await SendHeadersAsync(1, Http2HeadersFrameFlags.NONE, headers)); + Assert.True(await SendHeadersAsync(1, Http2HeadersFrameFlags.END_STREAM, headers)); await SendEmptyContinuationFrameAsync(1, Http2ContinuationFrameFlags.END_HEADERS); await WaitForStreamErrorAsync( @@ -3466,7 +3982,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests expectedErrorMessage: CoreStrings.Http2ErrorMissingMandatoryPseudoHeaderFields); // Verify that the stream ID can't be re-used - await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS, headers); + await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM, headers); await WaitForConnectionErrorAsync( ignoreNonGoAwayFrames: false, expectedLastStreamId: 1, @@ -3484,7 +4000,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendEmptyContinuationFrameAsync(1, Http2ContinuationFrameFlags.END_HEADERS); await ExpectAsync(Http2FrameType.HEADERS, - withLength: 55, + withLength: 37, withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM), withStreamId: 1); @@ -3499,7 +4015,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, _browserRequestHeaders, endStream: true); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 12361, + withLength: 12343, withFlags: (byte)Http2HeadersFrameFlags.END_STREAM, withStreamId: 1); var continuationFrame1 = await ExpectAsync(Http2FrameType.CONTINUATION, @@ -3658,7 +4174,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, _browserRequestHeaders, endStream: true); await ExpectAsync(Http2FrameType.HEADERS, - withLength: 55, + withLength: 37, withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM), withStreamId: 1); @@ -3708,7 +4224,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendDataAsync(1, _helloBytes, true); await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); await ExpectAsync(Http2FrameType.DATA, @@ -3742,7 +4258,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendDataAsync(1, _helloBytes, true); await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); await ExpectAsync(Http2FrameType.DATA, @@ -3755,7 +4271,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests withStreamId: 1); await SendDataAsync(3, _helloBytes, true); await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 3); await ExpectAsync(Http2FrameType.DATA, @@ -3845,7 +4361,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, headers, endStream: false); await ExpectAsync(Http2FrameType.HEADERS, - withLength: 55, + withLength: 37, withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM), withStreamId: 1); @@ -3964,6 +4480,160 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); } + [Theory] + [InlineData((int)(Http2FrameType.DATA))] + [InlineData((int)(Http2FrameType.WINDOW_UPDATE))] + [InlineData((int)(Http2FrameType.HEADERS))] + [InlineData((int)(Http2FrameType.CONTINUATION))] + public async Task RefusedStream_Post_ResetsAndDrainsRequest(int intFinalFrameType) + { + var finalFrameType = (Http2FrameType)intFinalFrameType; + + CreateConnection(); + + _connection.ServerSettings.MaxConcurrentStreams = 0; // Refuse all streams + + var connectionTask = _connection.ProcessRequestsAsync(new DummyApplication(_noopApplication)); + + async Task CompletePipeOnTaskCompletion() + { + try + { + await connectionTask; + } + finally + { + _pair.Transport.Input.Complete(); + _pair.Transport.Output.Complete(); + } + } + + _connectionTask = CompletePipeOnTaskCompletion(); + + await SendPreambleAsync().ConfigureAwait(false); + await SendSettingsAsync(); + + // Requests can be sent before receiving and acking settings. + + var headers = new[] + { + new KeyValuePair(HeaderNames.Method, "POST"), + new KeyValuePair(HeaderNames.Path, "/"), + new KeyValuePair(HeaderNames.Scheme, "http"), + }; + + await StartStreamAsync(1, headers, endStream: false); + + await ExpectAsync(Http2FrameType.SETTINGS, + withLength: 3 * Http2FrameReader.SettingSize, + withFlags: 0, + withStreamId: 0); + + await ExpectAsync(Http2FrameType.WINDOW_UPDATE, + withLength: 4, + withFlags: 0, + withStreamId: 0); + + await ExpectAsync(Http2FrameType.SETTINGS, + withLength: 0, + withFlags: (byte)Http2SettingsFrameFlags.ACK, + withStreamId: 0); + + await WaitForStreamErrorAsync(1, Http2ErrorCode.REFUSED_STREAM, "HTTP/2 stream ID 1 error (REFUSED_STREAM): A new stream was refused because this connection has reached its stream limit."); + + // These frames should be drained and ignored while in cool-down mode. + switch (finalFrameType) + { + case Http2FrameType.DATA: + await SendDataAsync(1, new byte[100], endStream: true); + break; + case Http2FrameType.WINDOW_UPDATE: + await SendWindowUpdateAsync(1, 1024); + break; + case Http2FrameType.HEADERS: + await SendHeadersAsync(1, Http2HeadersFrameFlags.END_STREAM | Http2HeadersFrameFlags.END_HEADERS, _requestTrailers); + break; + case Http2FrameType.CONTINUATION: + await SendHeadersAsync(1, Http2HeadersFrameFlags.END_STREAM, _requestTrailers); + await SendContinuationAsync(1, Http2ContinuationFrameFlags.END_HEADERS, _requestTrailers); + break; + default: + throw new NotImplementedException(finalFrameType.ToString()); + } + + await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); + } + + [Fact] + public async Task RefusedStream_Post_2xLimitRefused() + { + var requestBlock = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + + CreateConnection(); + + _connection.ServerSettings.MaxConcurrentStreams = 1; + + var connectionTask = _connection.ProcessRequestsAsync(new DummyApplication(_ => requestBlock.Task)); + + async Task CompletePipeOnTaskCompletion() + { + try + { + await connectionTask; + } + finally + { + _pair.Transport.Input.Complete(); + _pair.Transport.Output.Complete(); + } + } + + _connectionTask = CompletePipeOnTaskCompletion(); + + await SendPreambleAsync().ConfigureAwait(false); + await SendSettingsAsync(); + + // Requests can be sent before receiving and acking settings. + + var headers = new[] + { + new KeyValuePair(HeaderNames.Method, "POST"), + new KeyValuePair(HeaderNames.Path, "/"), + new KeyValuePair(HeaderNames.Scheme, "http"), + }; + + // This mimics gRPC, sending headers and data close together before receiving a reset. + await StartStreamAsync(1, headers, endStream: false); + await SendDataAsync(1, new byte[100], endStream: false); + await StartStreamAsync(3, headers, endStream: false); + await SendDataAsync(3, new byte[100], endStream: false); + await StartStreamAsync(5, headers, endStream: false); + await SendDataAsync(5, new byte[100], endStream: false); + await StartStreamAsync(7, headers, endStream: false); + await SendDataAsync(7, new byte[100], endStream: false); + + await ExpectAsync(Http2FrameType.SETTINGS, + withLength: 3 * Http2FrameReader.SettingSize, + withFlags: 0, + withStreamId: 0); + + await ExpectAsync(Http2FrameType.WINDOW_UPDATE, + withLength: 4, + withFlags: 0, + withStreamId: 0); + + await ExpectAsync(Http2FrameType.SETTINGS, + withLength: 0, + withFlags: (byte)Http2SettingsFrameFlags.ACK, + withStreamId: 0); + + await WaitForStreamErrorAsync(3, Http2ErrorCode.REFUSED_STREAM, "HTTP/2 stream ID 3 error (REFUSED_STREAM): A new stream was refused because this connection has reached its stream limit."); + await WaitForStreamErrorAsync(5, Http2ErrorCode.REFUSED_STREAM, "HTTP/2 stream ID 5 error (REFUSED_STREAM): A new stream was refused because this connection has reached its stream limit."); + await WaitForStreamErrorAsync(7, Http2ErrorCode.REFUSED_STREAM, "HTTP/2 stream ID 7 error (REFUSED_STREAM): A new stream was refused because this connection has reached its stream limit."); + requestBlock.SetResult(0); + await StopConnectionAsync(expectedLastStreamId: 7, ignoreNonGoAwayFrames: true); + } + [Theory] [InlineData((int)(Http2FrameType.DATA))] [InlineData((int)(Http2FrameType.HEADERS))] diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2StreamTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2StreamTests.cs index 4b1bced230..a59207ccda 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2StreamTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2StreamTests.cs @@ -6,6 +6,8 @@ using System.Buffers; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Net.Http; +using System.Net.Http.HPack; using System.Runtime.ExceptionServices; using System.Text; using System.Threading; @@ -15,7 +17,6 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Server.Kestrel.Core.Features; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2; -using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack; using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.Logging; using Microsoft.Net.Http.Headers; @@ -78,7 +79,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, headers, endStream: true); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 70, + withLength: 52, withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM), withStreamId: 1); @@ -103,7 +104,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM, headers); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 71, + withLength: 53, withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM), withStreamId: 1); @@ -130,7 +131,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM, headers); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 75, + withLength: 57, withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM), withStreamId: 1); @@ -158,7 +159,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM, headers); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 76, + withLength: 58, withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM), withStreamId: 1); @@ -192,7 +193,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM, headers); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 118, + withLength: 100, withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM), withStreamId: 1); @@ -234,7 +235,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM, headers); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 55, + withLength: 37, withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM), withStreamId: 1); @@ -296,7 +297,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, headers, endStream: true); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 55, + withLength: 37, withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM), withStreamId: 1); @@ -325,7 +326,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, headers, endStream: true); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 55, + withLength: 37, withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM), withStreamId: 1); @@ -354,7 +355,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, headers, endStream: true); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 65, + withLength: 47, withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM), withStreamId: 1); @@ -385,7 +386,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, headers, endStream: true); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 65, + withLength: 47, withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM), withStreamId: 1); @@ -416,7 +417,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, headers, endStream: true); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 65, + withLength: 47, withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM), withStreamId: 1); @@ -447,7 +448,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, headers, endStream: true); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 65, + withLength: 47, withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM), withStreamId: 1); @@ -569,7 +570,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendDataAsync(1, new byte[12], endStream: true); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 55, + withLength: 37, withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM), withStreamId: 1); @@ -610,7 +611,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendDataAsync(1, new byte[12], endStream: true); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 55, + withLength: 37, withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM), withStreamId: 1); @@ -654,7 +655,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendDataAsync(1, new byte[8], endStream: true); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 55, + withLength: 37, withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM), withStreamId: 1); @@ -697,7 +698,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendDataAsync(1, new byte[8], endStream: true); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 55, + withLength: 37, withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM), withStreamId: 1); @@ -750,7 +751,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendDataAsync(1, new byte[8], endStream: true); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 55, + withLength: 37, withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM), withStreamId: 1); @@ -982,7 +983,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendDataAsync(1, new byte[12], endStream: true); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 55, + withLength: 37, withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM), withStreamId: 1); @@ -996,7 +997,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests Assert.Equal("0", _decodedHeaders[HeaderNames.ContentLength]); } - [Fact] // TODO https://github.com/aspnet/AspNetCore/issues/7034 + [Fact] // TODO https://github.com/dotnet/aspnetcore/issues/7034 public async Task ContentLength_Response_FirstWriteMoreBytesWritten_Throws_Sends500() { var headers = new[] @@ -1014,7 +1015,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, headers, endStream: true); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 56, + withLength: 38, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); await ExpectAsync(Http2FrameType.RST_STREAM, @@ -1053,7 +1054,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, headers, endStream: true); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 56, + withLength: 38, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); await ExpectAsync(Http2FrameType.DATA, @@ -1091,7 +1092,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, headers, endStream: true); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 55, + withLength: 37, withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM), withStreamId: 1); @@ -1124,7 +1125,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, headers, endStream: true); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); await ExpectAsync(Http2FrameType.DATA, @@ -1159,7 +1160,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, headers, endStream: true); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 55, + withLength: 37, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); await ExpectAsync(Http2FrameType.DATA, @@ -1197,7 +1198,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, headers, endStream: true); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); await ExpectAsync(Http2FrameType.DATA, @@ -1235,7 +1236,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, headers, endStream: true); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); await ExpectAsync(Http2FrameType.DATA, @@ -1275,7 +1276,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, headers, endStream: true); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); @@ -1322,7 +1323,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, headers, endStream: true); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); @@ -1360,7 +1361,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, headers, endStream: true); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 55, + withLength: 37, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); await ExpectAsync(Http2FrameType.DATA, @@ -1396,7 +1397,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, headers, endStream: true); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); @@ -1440,7 +1441,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, headers, endStream: true); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 56, + withLength: 38, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); @@ -1474,7 +1475,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, headers, endStream: true); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); @@ -1507,7 +1508,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, headers, endStream: true); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 56, + withLength: 38, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); await ExpectAsync(Http2FrameType.DATA, @@ -1551,7 +1552,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendDataAsync(1, new byte[12], endStream: true); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 55, + withLength: 37, withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM), withStreamId: 1); @@ -1590,7 +1591,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, headers, endStream: false); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 59, + withLength: 41, withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM), withStreamId: 1); @@ -1633,7 +1634,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendDataAsync(1, new byte[12], endStream: true); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 55, + withLength: 37, withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM), withStreamId: 1); @@ -1673,7 +1674,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendDataAsync(1, new byte[6], endStream: false); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 59, + withLength: 41, withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM), withStreamId: 1); @@ -1732,7 +1733,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendDataAsync(1, new byte[6], endStream: false); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 59, + withLength: 41, withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM), withStreamId: 1); @@ -1787,7 +1788,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendDataAsync(1, new byte[12], endStream: true); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 55, + withLength: 37, withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM), withStreamId: 1); @@ -1813,7 +1814,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, _browserRequestHeaders, endStream: true); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 55, + withLength: 37, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); @@ -1851,7 +1852,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, _browserRequestHeaders, endStream: true); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 55, + withLength: 37, withFlags: (byte)(Http2HeadersFrameFlags.END_STREAM | Http2HeadersFrameFlags.END_HEADERS), withStreamId: 1); @@ -1865,6 +1866,58 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests Assert.Equal("0", _decodedHeaders[HeaderNames.ContentLength]); } + [Fact] + public async Task ResponseTrailers_WorksAcrossMultipleStreams_Cleared() + { + await InitializeConnectionAsync(context => + { + Assert.True(context.Response.SupportsTrailers(), "SupportsTrailers"); + + var trailers = context.Features.Get().Trailers; + Assert.False(trailers.IsReadOnly); + + context.Response.AppendTrailer("CustomName", "Custom Value"); + return Task.CompletedTask; + }); + + await StartStreamAsync(1, _browserRequestHeaders, endStream: true); + + var headersFrame1 = await ExpectAsync(Http2FrameType.HEADERS, + withLength: 37, + withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS), + withStreamId: 1); + var trailersFrame1 = await ExpectAsync(Http2FrameType.HEADERS, + withLength: 25, + withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM), + withStreamId: 1); + + await StartStreamAsync(3, _browserRequestHeaders, endStream: true); + + var headersFrame2 = await ExpectAsync(Http2FrameType.HEADERS, + withLength: 37, + withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS), + withStreamId: 3); + + var trailersFrame2 = await ExpectAsync(Http2FrameType.HEADERS, + withLength: 25, + withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM), + withStreamId: 3); + + await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false); + + _hpackDecoder.Decode(trailersFrame1.PayloadSequence, endHeaders: true, handler: this); + + Assert.Single(_decodedHeaders); + Assert.Equal("Custom Value", _decodedHeaders["CustomName"]); + + _decodedHeaders.Clear(); + + _hpackDecoder.Decode(trailersFrame2.PayloadSequence, endHeaders: true, handler: this); + + Assert.Single(_decodedHeaders); + Assert.Equal("Custom Value", _decodedHeaders["CustomName"]); + } + [Fact] public async Task ResponseTrailers_WithData_Sent() { @@ -1877,7 +1930,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, _browserRequestHeaders, endStream: true); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); @@ -1927,7 +1980,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, _browserRequestHeaders, endStream: true); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); @@ -1986,7 +2039,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, _browserRequestHeaders, endStream: true); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); @@ -2021,7 +2074,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, _browserRequestHeaders, endStream: true); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); @@ -2041,7 +2094,128 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await _connectionTask; var message = Assert.Single(TestApplicationErrorLogger.Messages, m => m.Exception is HPackEncodingException); - Assert.Contains(CoreStrings.HPackErrorNotEnoughBuffer, message.Exception.Message); + Assert.Contains(SR.net_http_hpack_encode_failure, message.Exception.Message); + } + + [Fact] + public async Task ResponseTrailers_WithLargeUnflushedData_DataExceedsFlowControlAvailableAndNotSentWithTrailers() + { + const int windowSize = (int)Http2PeerSettings.DefaultMaxFrameSize; + _clientSettings.InitialWindowSize = windowSize; + + var headers = new[] + { + new KeyValuePair(HeaderNames.Method, "GET"), + new KeyValuePair(HeaderNames.Path, "/"), + new KeyValuePair(HeaderNames.Scheme, "http"), + }; + await InitializeConnectionAsync(async context => + { + await context.Response.StartAsync(); + + // Body exceeds flow control available and requires the client to allow more + // data via updating the window + context.Response.BodyWriter.GetMemory(windowSize + 1); + context.Response.BodyWriter.Advance(windowSize + 1); + + context.Response.AppendTrailer("CustomName", "Custom Value"); + }).DefaultTimeout(); + + await StartStreamAsync(1, headers, endStream: true).DefaultTimeout(); + + var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, + withLength: 33, + withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, + withStreamId: 1).DefaultTimeout(); + + await ExpectAsync(Http2FrameType.DATA, + withLength: 16384, + withFlags: (byte)Http2DataFrameFlags.NONE, + withStreamId: 1).DefaultTimeout(); + + var dataTask = ExpectAsync(Http2FrameType.DATA, + withLength: 1, + withFlags: (byte)Http2DataFrameFlags.NONE, + withStreamId: 1).DefaultTimeout(); + + // Reading final frame of data requires window update + // Verify this data task is waiting on window update + Assert.False(dataTask.IsCompletedSuccessfully); + + await SendWindowUpdateAsync(1, 1); + + await dataTask; + + var trailersFrame = await ExpectAsync(Http2FrameType.HEADERS, + withLength: 25, + withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM), + withStreamId: 1).DefaultTimeout(); + + await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false).DefaultTimeout(); + + _hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this); + + Assert.Equal(2, _decodedHeaders.Count); + Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase); + Assert.Equal("200", _decodedHeaders[HeaderNames.Status]); + + _decodedHeaders.Clear(); + _hpackDecoder.Decode(trailersFrame.PayloadSequence, endHeaders: true, handler: this); + + Assert.Single(_decodedHeaders); + Assert.Equal("Custom Value", _decodedHeaders["CustomName"]); + } + + [Fact] + public async Task ResponseTrailers_WithUnflushedData_DataSentWithTrailers() + { + var headers = new[] + { + new KeyValuePair(HeaderNames.Method, "GET"), + new KeyValuePair(HeaderNames.Path, "/"), + new KeyValuePair(HeaderNames.Scheme, "http"), + }; + await InitializeConnectionAsync(async context => + { + await context.Response.StartAsync(); + + var s = context.Response.BodyWriter.GetMemory(1); + s.Span[0] = byte.MaxValue; + context.Response.BodyWriter.Advance(1); + + context.Response.AppendTrailer("CustomName", "Custom Value"); + }); + + await StartStreamAsync(1, headers, endStream: true); + + var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, + withLength: 33, + withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, + withStreamId: 1); + + await ExpectAsync(Http2FrameType.DATA, + withLength: 1, + withFlags: (byte)Http2DataFrameFlags.NONE, + withStreamId: 1); + + var trailersFrame = await ExpectAsync(Http2FrameType.HEADERS, + withLength: 25, + withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM), + withStreamId: 1); + + await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); + + _hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this); + + Assert.Equal(2, _decodedHeaders.Count); + Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase); + Assert.Equal("200", _decodedHeaders[HeaderNames.Status]); + + _decodedHeaders.Clear(); + _hpackDecoder.Decode(trailersFrame.PayloadSequence, endHeaders: true, handler: this); + + Assert.Single(_decodedHeaders); + Assert.Equal("Custom Value", _decodedHeaders["CustomName"]); } [Fact] @@ -2061,7 +2235,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, headers, endStream: true); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 55, + withLength: 37, withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM), withStreamId: 1); @@ -2095,7 +2269,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, headers, endStream: true); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); await ExpectAsync(Http2FrameType.DATA, @@ -2358,7 +2532,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, _browserRequestHeaders, endStream: true); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); await ExpectAsync(Http2FrameType.DATA, @@ -2449,7 +2623,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, _browserRequestHeaders, endStream: true); await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); @@ -2493,18 +2667,18 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, _browserRequestHeaders, endStream: true); var message = await appFinished.Task.DefaultTimeout(); - Assert.Equal(CoreStrings.HPackErrorNotEnoughBuffer, message); + Assert.Equal(SR.net_http_hpack_encode_failure, message); // Just the StatusCode gets written before aborting in the continuation frame await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)Http2HeadersFrameFlags.NONE, withStreamId: 1); _pair.Application.Output.Complete(); await WaitForConnectionErrorAsync(ignoreNonGoAwayFrames: false, expectedLastStreamId: int.MaxValue, Http2ErrorCode.INTERNAL_ERROR, - CoreStrings.HPackErrorNotEnoughBuffer); + SR.net_http_hpack_encode_failure); } [Fact] @@ -2526,7 +2700,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, headers, endStream: true); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 55, + withLength: 37, withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM), withStreamId: 1); @@ -2569,7 +2743,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, headers, endStream: true); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); @@ -2615,7 +2789,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, headers, endStream: true); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); var dataFrame = await ExpectAsync(Http2FrameType.DATA, @@ -2661,7 +2835,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, headers, endStream: true); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); var dataFrame = await ExpectAsync(Http2FrameType.DATA, @@ -2710,7 +2884,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, headers, endStream: true); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); var dataFrame = await ExpectAsync(Http2FrameType.DATA, @@ -2763,7 +2937,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, headers, endStream: true); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); var dataFrame = await ExpectAsync(Http2FrameType.DATA, @@ -2813,7 +2987,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, headers, endStream: true); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); var dataFrame = await ExpectAsync(Http2FrameType.DATA, @@ -2863,7 +3037,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, headers, endStream: true); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); var dataFrame = await ExpectAsync(Http2FrameType.DATA, @@ -2906,7 +3080,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, headers, endStream: true); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); var dataFrame = await ExpectAsync(Http2FrameType.DATA, @@ -2952,7 +3126,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, headers, endStream: true); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); var dataFrame = await ExpectAsync(Http2FrameType.DATA, @@ -2994,7 +3168,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, headers, endStream: true); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 55, + withLength: 37, withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM), withStreamId: 1); @@ -3039,7 +3213,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, headers, endStream: true); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); await ExpectAsync(Http2FrameType.DATA, @@ -3105,7 +3279,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, headers, endStream: true); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 56, + withLength: 38, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); await ExpectAsync(Http2FrameType.DATA, @@ -3151,7 +3325,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, headers, endStream: true); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 56, + withLength: 38, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); await ExpectAsync(Http2FrameType.DATA, @@ -3187,7 +3361,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, headers, endStream: true); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 56, + withLength: 38, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); await ExpectAsync(Http2FrameType.DATA, @@ -3239,7 +3413,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, headers, endStream: true); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 56, + withLength: 38, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); await ExpectAsync(Http2FrameType.DATA, @@ -3291,7 +3465,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, headers, endStream: true); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 55, + withLength: 37, withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM), withStreamId: 1); @@ -3324,7 +3498,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests // Don't receive content length because we called WriteAsync which caused an invalid response var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 55, + withLength: 37, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS | (byte)Http2HeadersFrameFlags.END_STREAM, withStreamId: 1); @@ -3357,7 +3531,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, headers, endStream: true); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 55, + withLength: 37, withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM), withStreamId: 1); @@ -3409,7 +3583,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, headers, endStream: true); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 55, + withLength: 37, withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM), withStreamId: 1); @@ -3465,7 +3639,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, headers, endStream: true); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 55, + withLength: 37, withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS), withStreamId: 1); var trailersFrame = await ExpectAsync(Http2FrameType.HEADERS, @@ -3531,7 +3705,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, headers, endStream: true); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 55, + withLength: 37, withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM), withStreamId: 1); @@ -3587,7 +3761,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, headers, endStream: true); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS), withStreamId: 1); var bodyFrame = await ExpectAsync(Http2FrameType.DATA, @@ -3652,7 +3826,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, headers, endStream: true); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 55, + withLength: 37, withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM), withStreamId: 1); @@ -3711,7 +3885,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, headers, endStream: true); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS), withStreamId: 1); var bodyFrame = await ExpectAsync(Http2FrameType.DATA, @@ -3767,7 +3941,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, headers, endStream: true); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 55, + withLength: 37, withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM), withStreamId: 1); @@ -3829,7 +4003,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, headers, endStream: true); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS), withStreamId: 1); var bodyFrame = await ExpectAsync(Http2FrameType.DATA, @@ -3903,7 +4077,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, headers, endStream: true); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS), withStreamId: 1); var bodyFrame = await ExpectAsync(Http2FrameType.DATA, @@ -3979,7 +4153,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, headers, endStream: true); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 56, + withLength: 38, withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS), withStreamId: 1); var bodyFrame = await ExpectAsync(Http2FrameType.DATA, @@ -4050,7 +4224,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, headers, endStream: true); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 56, + withLength: 38, withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS), withStreamId: 1); var bodyFrame = await ExpectAsync(Http2FrameType.DATA, @@ -4122,7 +4296,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, headers, endStream: true); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS), withStreamId: 1); var bodyFrame = await ExpectAsync(Http2FrameType.DATA, @@ -4206,7 +4380,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, headers, endStream: false); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS), withStreamId: 1); var bodyFrame = await ExpectAsync(Http2FrameType.DATA, @@ -4287,7 +4461,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, headers, endStream: true); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS), withStreamId: 1); var bodyFrame = await ExpectAsync(Http2FrameType.DATA, @@ -4374,7 +4548,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, headers, endStream: false); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS), withStreamId: 1); var bodyFrame = await ExpectAsync(Http2FrameType.DATA, @@ -4409,30 +4583,32 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests Assert.Equal("Custom Value", _decodedHeaders["CustomName"]); } + // :method = GET + // :path = / + // :scheme = http + // X-Test = £ + private static readonly byte[] LatinHeaderData = new byte[] + { + 0, 7, 58, 109, 101, 116, 104, 111, 100, 3, 71, 69, 84, 0, 5, 58, 112, 97, 116, + 104, 1, 47, 0, 7, 58, 115, 99, 104, 101, 109, 101, 4, 104, 116, 116, 112, 0, + 6, 120, 45, 116, 101, 115, 116, 1, 163 + }; + [Fact] public async Task HEADERS_Received_Latin1_AcceptedWhenLatin1OptionIsConfigured() { _serviceContext.ServerOptions.Latin1RequestHeaders = true; - var headers = new[] - { - new KeyValuePair(HeaderNames.Method, "GET"), - new KeyValuePair(HeaderNames.Path, "/"), - new KeyValuePair(HeaderNames.Scheme, "http"), - // The HPackEncoder will encode £ as 0xA3 aka Latin1 encoding. - new KeyValuePair("X-Test", "£"), - }; - await InitializeConnectionAsync(context => { Assert.Equal("£", context.Request.Headers["X-Test"]); return Task.CompletedTask; }); - await StartStreamAsync(1, headers, endStream: true); + await StartStreamAsync(1, LatinHeaderData, endStream: true); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, - withLength: 55, + withLength: 37, withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM), withStreamId: 1); @@ -4449,18 +4625,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [Fact] public async Task HEADERS_Received_Latin1_RejectedWhenLatin1OptionIsNotConfigured() { - var headers = new[] - { - new KeyValuePair(HeaderNames.Method, "GET"), - new KeyValuePair(HeaderNames.Path, "/"), - new KeyValuePair(HeaderNames.Scheme, "http"), - // The HPackEncoder will encode £ as 0xA3 aka Latin1 encoding. - new KeyValuePair("X-Test", "£"), - }; - await InitializeConnectionAsync(_noopApplication); - await StartStreamAsync(1, headers, endStream: true); + await StartStreamAsync(1, LatinHeaderData, endStream: true); await WaitForConnectionErrorAsync( ignoreNonGoAwayFrames: true, diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs index d3d7e0e7ae..f80e5ad386 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs @@ -6,9 +6,12 @@ using System.Buffers; using System.Buffers.Binary; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Drawing; using System.IO; using System.IO.Pipelines; using System.Linq; +using System.Net.Http; +using System.Net.Http.HPack; using System.Reflection; using System.Text; using System.Threading; @@ -21,10 +24,10 @@ using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.FlowControl; -using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Primitives; using Microsoft.Net.Http.Headers; using Moq; using Xunit; @@ -117,7 +120,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests private readonly MemoryPool _memoryPool = SlabMemoryPoolFactory.Create(); internal readonly Http2PeerSettings _clientSettings = new Http2PeerSettings(); - internal readonly HPackEncoder _hpackEncoder = new HPackEncoder(); internal readonly HPackDecoder _hpackDecoder; private readonly byte[] _headerEncodingBuffer = new byte[Http2PeerSettings.MinAllowedMaxFrameSize]; @@ -400,12 +402,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests base.Dispose(); } - void IHttpHeadersHandler.OnHeader(Span name, Span value) + void IHttpHeadersHandler.OnHeader(ReadOnlySpan name, ReadOnlySpan value) { _decodedHeaders[name.GetAsciiStringNonNullCharacters()] = value.GetRequestHeaderStringNonNullCharacters(useLatin1: _serviceContext.ServerOptions.Latin1RequestHeaders); } - void IHttpHeadersHandler.OnHeadersComplete() { } + void IHttpHeadersHandler.OnHeadersComplete(bool endStream) { } protected void CreateConnection() { @@ -499,42 +501,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); _runningStreams[streamId] = tcs; - var frame = new Http2Frame(); - frame.PrepareHeaders(Http2HeadersFrameFlags.NONE, streamId); + writableBuffer.WriteStartStream(streamId, GetHeadersEnumerator(headers), _headerEncodingBuffer, endStream); + return FlushAsync(writableBuffer); + } - var buffer = _headerEncodingBuffer.AsSpan(); - var done = _hpackEncoder.BeginEncode(headers, buffer, out var length); - frame.PayloadLength = length; - - if (done) - { - frame.HeadersFlags = Http2HeadersFrameFlags.END_HEADERS; - } - - if (endStream) - { - frame.HeadersFlags |= Http2HeadersFrameFlags.END_STREAM; - } - - Http2FrameWriter.WriteHeader(frame, writableBuffer); - writableBuffer.Write(buffer.Slice(0, length)); - - while (!done) - { - frame.PrepareContinuation(Http2ContinuationFrameFlags.NONE, streamId); - - done = _hpackEncoder.Encode(buffer, out length); - frame.PayloadLength = length; - - if (done) - { - frame.ContinuationFlags = Http2ContinuationFrameFlags.END_HEADERS; - } - - Http2FrameWriter.WriteHeader(frame, writableBuffer); - writableBuffer.Write(buffer.Slice(0, length)); - } + protected Task StartStreamAsync(int streamId, Span headerData, bool endStream) + { + var writableBuffer = _pair.Application.Output; + var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + _runningStreams[streamId] = tcs; + writableBuffer.WriteStartStream(streamId, headerData, endStream); return FlushAsync(writableBuffer); } @@ -564,7 +541,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests extendedHeader[0] = padLength; var payload = buffer.Slice(extendedHeaderLength, buffer.Length - padLength - extendedHeaderLength); - _hpackEncoder.BeginEncode(headers, payload, out var length); + HPackHeaderWriter.BeginEncodeHeaders(GetHeadersEnumerator(headers), payload, out var length); var padding = buffer.Slice(extendedHeaderLength + length, padLength); padding.Fill(0); @@ -607,7 +584,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests extendedHeader[4] = priority; var payload = buffer.Slice(extendedHeaderLength); - _hpackEncoder.BeginEncode(headers, payload, out var length); + HPackHeaderWriter.BeginEncodeHeaders(GetHeadersEnumerator(headers), payload, out var length); frame.PayloadLength = extendedHeaderLength + length; @@ -654,7 +631,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests extendedHeader[5] = priority; var payload = buffer.Slice(extendedHeaderLength, buffer.Length - padLength - extendedHeaderLength); - _hpackEncoder.BeginEncode(headers, payload, out var length); + HPackHeaderWriter.BeginEncodeHeaders(GetHeadersEnumerator(headers), payload, out var length); var padding = buffer.Slice(extendedHeaderLength + length, padLength); padding.Fill(0); @@ -687,19 +664,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await writableBuffer.FlushAsync().AsTask().DefaultTimeout(); } - protected Task SendPreambleAsync() => SendAsync(new ArraySegment(Http2Connection.ClientPreface)); + protected Task SendPreambleAsync() => SendAsync(Http2Connection.ClientPreface); protected async Task SendSettingsAsync() { - var writableBuffer = _pair.Application.Output; - var frame = new Http2Frame(); - frame.PrepareSettings(Http2SettingsFrameFlags.NONE); - var settings = _clientSettings.GetNonProtocolDefaults(); - var payload = new byte[settings.Count * Http2FrameReader.SettingSize]; - frame.PayloadLength = payload.Length; - Http2FrameWriter.WriteSettings(settings, payload); - Http2FrameWriter.WriteHeader(frame, writableBuffer); - await SendAsync(payload); + _pair.Application.Output.WriteSettings(_clientSettings); + await FlushAsync(_pair.Application.Output); } protected async Task SendSettingsAckWithInvalidLengthAsync(int length) @@ -768,14 +738,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests return FlushAsync(writableBuffer); } - internal async Task SendHeadersAsync(int streamId, Http2HeadersFrameFlags flags, IEnumerable> headers) + internal async Task SendHeadersAsync(int streamId, Http2HeadersFrameFlags flags, Http2HeadersEnumerator headersEnumerator) { var outputWriter = _pair.Application.Output; var frame = new Http2Frame(); frame.PrepareHeaders(flags, streamId); var buffer = _headerEncodingBuffer.AsMemory(); - var done = _hpackEncoder.BeginEncode(headers, buffer.Span, out var length); + var done = HPackHeaderWriter.BeginEncodeHeaders(headersEnumerator, buffer.Span, out var length); frame.PayloadLength = length; Http2FrameWriter.WriteHeader(frame, outputWriter); @@ -784,6 +754,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests return done; } + internal Task SendHeadersAsync(int streamId, Http2HeadersFrameFlags flags, IEnumerable> headers) + { + return SendHeadersAsync(streamId, flags, GetHeadersEnumerator(headers)); + } + internal async Task SendHeadersAsync(int streamId, Http2HeadersFrameFlags flags, byte[] headerBlock) { var outputWriter = _pair.Application.Output; @@ -833,14 +808,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendAsync(payload); } - internal async Task SendContinuationAsync(int streamId, Http2ContinuationFrameFlags flags) + internal async Task SendContinuationAsync(int streamId, Http2ContinuationFrameFlags flags, Http2HeadersEnumerator headersEnumerator) { var outputWriter = _pair.Application.Output; var frame = new Http2Frame(); frame.PrepareContinuation(flags, streamId); var buffer = _headerEncodingBuffer.AsMemory(); - var done = _hpackEncoder.Encode(buffer.Span, out var length); + var done = HPackHeaderWriter.ContinueEncodeHeaders(headersEnumerator, buffer.Span, out var length); frame.PayloadLength = length; Http2FrameWriter.WriteHeader(frame, outputWriter); @@ -868,7 +843,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests frame.PrepareContinuation(flags, streamId); var buffer = _headerEncodingBuffer.AsMemory(); - var done = _hpackEncoder.BeginEncode(headers, buffer.Span, out var length); + var done = HPackHeaderWriter.BeginEncodeHeaders(GetHeadersEnumerator(headers), buffer.Span, out var length); frame.PayloadLength = length; Http2FrameWriter.WriteHeader(frame, outputWriter); @@ -877,6 +852,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests return done; } + internal Http2HeadersEnumerator GetHeadersEnumerator(IEnumerable> headers) + { + var dictionary = headers + .GroupBy(g => g.Key) + .ToDictionary(g => g.Key, g => new StringValues(g.Select(values => values.Value).ToArray())); + + var headersEnumerator = new Http2HeadersEnumerator(); + headersEnumerator.Initialize(dictionary); + return headersEnumerator; + } + internal Task SendEmptyContinuationFrameAsync(int streamId, Http2ContinuationFrameFlags flags) { var outputWriter = _pair.Application.Output; @@ -910,14 +896,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests protected Task SendDataAsync(int streamId, Memory data, bool endStream) { var outputWriter = _pair.Application.Output; - var frame = new Http2Frame(); - - frame.PrepareData(streamId); - frame.PayloadLength = data.Length; - frame.DataFlags = endStream ? Http2DataFrameFlags.END_STREAM : Http2DataFrameFlags.NONE; - - Http2FrameWriter.WriteHeader(frame, outputWriter); - return SendAsync(data.Span); + outputWriter.WriteData(streamId, data, endStream); + return FlushAsync(outputWriter); } protected async Task SendDataWithPaddingAsync(int streamId, Memory data, byte padLength, bool endStream) @@ -1072,12 +1052,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests protected Task SendWindowUpdateAsync(int streamId, int sizeIncrement) { var outputWriter = _pair.Application.Output; - var frame = new Http2Frame(); - frame.PrepareWindowUpdate(streamId, sizeIncrement); - Http2FrameWriter.WriteHeader(frame, outputWriter); - var buffer = outputWriter.GetSpan(4); - BinaryPrimitives.WriteUInt32BigEndian(buffer, (uint)sizeIncrement); - outputWriter.Advance(4); + outputWriter.WriteWindowUpdateAsync(streamId, sizeIncrement); return FlushAsync(outputWriter); } @@ -1112,12 +1087,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests var buffer = result.Buffer; var consumed = buffer.Start; var examined = buffer.Start; + var copyBuffer = buffer; try { Assert.True(buffer.Length > 0); - if (Http2FrameReader.ReadFrame(buffer, frame, maxFrameSize, out var framePayload)) + if (Http2FrameReader.TryReadFrame(ref buffer, frame, maxFrameSize, out var framePayload)) { consumed = examined = framePayload.End; frame.Payload = framePayload.ToArray(); @@ -1135,7 +1111,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests } finally { - _bytesReceived += buffer.Slice(buffer.Start, consumed).Length; + _bytesReceived += copyBuffer.Slice(copyBuffer.Start, consumed).Length; _pair.Application.Input.AdvanceTo(consumed, examined); } } @@ -1283,6 +1259,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests return bufferSize ?? 0; } + public void OnStaticIndexedHeader(int index) + { + throw new NotImplementedException(); + } + + public void OnStaticIndexedHeader(int index, ReadOnlySpan value) + { + throw new NotImplementedException(); + } + internal class Http2FrameWithPayload : Http2Frame { public Http2FrameWithPayload() : base() diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TimeoutTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TimeoutTests.cs index 23e8374e6f..02413c5805 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TimeoutTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TimeoutTests.cs @@ -101,7 +101,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests _mockTimeoutControl.Verify(c => c.SetTimeout(It.IsAny(), TimeoutReason.RequestHeaders), Times.Once); await ExpectAsync(Http2FrameType.HEADERS, - withLength: 55, + withLength: 37, withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM), withStreamId: 1); @@ -263,7 +263,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests } [Fact] - [Flaky("https://github.com/aspnet/AspNetCore-Internal/issues/1323", FlakyOn.All)] + [QuarantinedTest("https://github.com/dotnet/aspnetcore-internal/issues/1323")] public async Task DATA_Sent_TooSlowlyDueToSocketBackPressureOnSmallWrite_AbortsConnectionAfterGracePeriod() { var mockSystemClock = _serviceContext.MockSystemClock; @@ -283,7 +283,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendDataAsync(1, _helloWorldBytes, endStream: true); await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); @@ -316,6 +316,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests } [Fact] + [QuarantinedTest] public async Task DATA_Sent_TooSlowlyDueToSocketBackPressureOnLargeWrite_AbortsConnectionAfterRateTimeout() { var mockSystemClock = _serviceContext.MockSystemClock; @@ -335,7 +336,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendDataAsync(1, _maxData, endStream: true); await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); @@ -389,7 +390,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendDataAsync(1, _helloWorldBytes, endStream: true); await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); await ExpectAsync(Http2FrameType.DATA, @@ -443,7 +444,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendDataAsync(1, _maxData, endStream: true); await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); await ExpectAsync(Http2FrameType.DATA, @@ -479,7 +480,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests _mockConnectionContext.VerifyNoOtherCalls(); } - [Fact(Skip = "https://github.com/aspnet/AspNetCore-Internal/issues/2197")] + [Fact(Skip = "https://github.com/dotnet/aspnetcore-internal/issues/2197")] public async Task DATA_Sent_TooSlowlyDueToOutputFlowControlOnMultipleStreams_AbortsConnectionAfterAdditiveRateTimeout() { var mockSystemClock = _serviceContext.MockSystemClock; @@ -499,7 +500,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendDataAsync(1, _maxData, endStream: true); await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); await ExpectAsync(Http2FrameType.DATA, @@ -511,7 +512,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendDataAsync(3, _maxData, endStream: true); await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 3); await ExpectAsync(Http2FrameType.DATA, @@ -565,7 +566,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendDataAsync(1, _helloWorldBytes, endStream: false); await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); @@ -614,7 +615,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendDataAsync(1, _maxData, endStream: false); await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); @@ -667,7 +668,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendDataAsync(1, _maxData, endStream: false); await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); @@ -680,7 +681,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendDataAsync(3, _maxData, endStream: false); await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 3); await ExpectAsync(Http2FrameType.DATA, @@ -736,7 +737,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendDataAsync(1, _maxData, endStream: true); await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); @@ -754,7 +755,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendDataAsync(3, _maxData, endStream: false); await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 3); await ExpectAsync(Http2FrameType.DATA, @@ -811,7 +812,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendDataAsync(1, _helloWorldBytes, endStream: false); await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); @@ -883,7 +884,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendDataAsync(3, _helloWorldBytes, endStream: false); await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 3); await ExpectAsync(Http2FrameType.DATA, @@ -900,7 +901,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests backpressureTcs.SetResult(null); await ExpectAsync(Http2FrameType.HEADERS, - withLength: 55, + withLength: 37, withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM), withStreamId: 1); diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/TlsTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/TlsTests.cs index f52e83eb62..950a304ecb 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/TlsTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/TlsTests.cs @@ -29,7 +29,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.Http2 private static X509Certificate2 _x509Certificate2 = TestResources.GetTestCertificate(); [ConditionalFact] - [SkipOnHelix("https://github.com/aspnet/AspNetCore/issues/7000")] + [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/7000")] public async Task TlsHandshakeRejectsTlsLessThan12() { using (var server = new TestServer(context => @@ -102,7 +102,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.Http2 try { - if (Http2FrameReader.ReadFrame(buffer, frame, 16_384, out var framePayload)) + if (Http2FrameReader.TryReadFrame(ref buffer, frame, 16_384, out var framePayload)) { consumed = examined = framePayload.End; return frame; diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3StreamTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3StreamTests.cs new file mode 100644 index 0000000000..a0445ab86f --- /dev/null +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3StreamTests.cs @@ -0,0 +1,607 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.Testing; +using Microsoft.Net.Http.Headers; +using Xunit; + +namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests +{ + public class Http3StreamTests : Http3TestBase + { + [Fact] + public async Task HelloWorldTest() + { + var headers = new[] + { + new KeyValuePair(HeaderNames.Method, "Custom"), + new KeyValuePair(HeaderNames.Path, "/"), + new KeyValuePair(HeaderNames.Scheme, "http"), + new KeyValuePair(HeaderNames.Authority, "localhost:80"), + }; + + var requestStream = await InitializeConnectionAndStreamsAsync(_echoApplication); + + var doneWithHeaders = await requestStream.SendHeadersAsync(headers); + await requestStream.SendDataAsync(Encoding.ASCII.GetBytes("Hello world"), endStream: true); + + var responseHeaders = await requestStream.ExpectHeadersAsync(); + var responseData = await requestStream.ExpectDataAsync(); + Assert.Equal("Hello world", Encoding.ASCII.GetString(responseData.ToArray())); + } + + [Fact] + public async Task EmptyMethod_Reset() + { + var headers = new[] + { + new KeyValuePair(HeaderNames.Method, ""), + new KeyValuePair(HeaderNames.Path, "/"), + new KeyValuePair(HeaderNames.Scheme, "http"), + new KeyValuePair(HeaderNames.Authority, "localhost:80"), + }; + + var requestStream = await InitializeConnectionAndStreamsAsync(_echoApplication); + var doneWithHeaders = await requestStream.SendHeadersAsync(headers); + await requestStream.WaitForStreamErrorAsync(Http3ErrorCode.ProtocolError, CoreStrings.FormatHttp3ErrorMethodInvalid("")); + } + + [Fact] + public async Task InvalidCustomMethod_Reset() + { + var headers = new[] + { + new KeyValuePair(HeaderNames.Method, "Hello,World"), + new KeyValuePair(HeaderNames.Path, "/"), + new KeyValuePair(HeaderNames.Scheme, "http"), + new KeyValuePair(HeaderNames.Authority, "localhost:80"), + }; + + var requestStream = await InitializeConnectionAndStreamsAsync(_echoApplication); + var doneWithHeaders = await requestStream.SendHeadersAsync(headers); + await requestStream.WaitForStreamErrorAsync(Http3ErrorCode.ProtocolError, CoreStrings.FormatHttp3ErrorMethodInvalid("Hello,World")); + } + + [Fact] + public async Task CustomMethod_Accepted() + { + var headers = new[] + { + new KeyValuePair(HeaderNames.Method, "Custom"), + new KeyValuePair(HeaderNames.Path, "/"), + new KeyValuePair(HeaderNames.Scheme, "http"), + new KeyValuePair(HeaderNames.Authority, "localhost:80"), + }; + + var requestStream = await InitializeConnectionAndStreamsAsync(_echoMethod); + var doneWithHeaders = await requestStream.SendHeadersAsync(headers, endStream: true); + + var responseHeaders = await requestStream.ExpectHeadersAsync(); + + Assert.Equal(4, responseHeaders.Count); + Assert.Contains("date", responseHeaders.Keys, StringComparer.OrdinalIgnoreCase); + Assert.Equal("200", responseHeaders[HeaderNames.Status]); + Assert.Equal("Custom", responseHeaders["Method"]); + Assert.Equal("0", responseHeaders["content-length"]); + } + + [Fact] + public async Task RequestHeadersMaxRequestHeaderFieldSize_EndsStream() + { + var headers = new[] + { + new KeyValuePair(HeaderNames.Method, "Custom"), + new KeyValuePair(HeaderNames.Path, "/"), + new KeyValuePair(HeaderNames.Scheme, "http"), + new KeyValuePair(HeaderNames.Authority, "localhost:80"), + new KeyValuePair("test", new string('a', 10000)) + }; + + var requestStream = await InitializeConnectionAndStreamsAsync(_echoApplication); + + var doneWithHeaders = await requestStream.SendHeadersAsync(headers); + await requestStream.SendDataAsync(Encoding.ASCII.GetBytes("Hello world")); + + // TODO figure out how to test errors for request streams that would be set on the Quic Stream. + await requestStream.ExpectReceiveEndOfStream(); + } + + [Fact] + public async Task ConnectMethod_Accepted() + { + var requestStream = await InitializeConnectionAndStreamsAsync(_echoMethod); + + // :path and :scheme are not allowed, :authority is optional + var headers = new[] { new KeyValuePair(HeaderNames.Method, "CONNECT") }; + + var doneWithHeaders = await requestStream.SendHeadersAsync(headers, endStream: true); + + var responseHeaders = await requestStream.ExpectHeadersAsync(); + + Assert.Equal(4, responseHeaders.Count); + Assert.Contains("date", responseHeaders.Keys, StringComparer.OrdinalIgnoreCase); + Assert.Equal("200", responseHeaders[HeaderNames.Status]); + Assert.Equal("CONNECT", responseHeaders["Method"]); + Assert.Equal("0", responseHeaders["content-length"]); + } + + [Fact] + public async Task OptionsStar_LeftOutOfPath() + { + var requestStream = await InitializeConnectionAndStreamsAsync(_echoPath); + var headers = new[] { new KeyValuePair(HeaderNames.Method, "OPTIONS"), + new KeyValuePair(HeaderNames.Scheme, "http"), + new KeyValuePair(HeaderNames.Path, "*")}; + + var doneWithHeaders = await requestStream.SendHeadersAsync(headers, endStream: true); + + var responseHeaders = await requestStream.ExpectHeadersAsync(); + + Assert.Equal(5, responseHeaders.Count); + Assert.Contains("date", responseHeaders.Keys, StringComparer.OrdinalIgnoreCase); + Assert.Equal("200", responseHeaders[HeaderNames.Status]); + Assert.Equal("", responseHeaders["path"]); + Assert.Equal("*", responseHeaders["rawtarget"]); + Assert.Equal("0", responseHeaders["content-length"]); + } + + [Fact] + public async Task OptionsSlash_Accepted() + { + var requestStream = await InitializeConnectionAndStreamsAsync(_echoPath); + + var headers = new[] { new KeyValuePair(HeaderNames.Method, "OPTIONS"), + new KeyValuePair(HeaderNames.Scheme, "http"), + new KeyValuePair(HeaderNames.Path, "/")}; + + var doneWithHeaders = await requestStream.SendHeadersAsync(headers, endStream: true); + + var responseHeaders = await requestStream.ExpectHeadersAsync(); + + Assert.Equal(5, responseHeaders.Count); + Assert.Contains("date", responseHeaders.Keys, StringComparer.OrdinalIgnoreCase); + Assert.Equal("200", responseHeaders[HeaderNames.Status]); + Assert.Equal("/", responseHeaders["path"]); + Assert.Equal("/", responseHeaders["rawtarget"]); + Assert.Equal("0", responseHeaders["content-length"]); + } + + [Fact] + public async Task PathAndQuery_Separated() + { + var requestStream = await InitializeConnectionAndStreamsAsync(context => + { + context.Response.Headers["path"] = context.Request.Path.Value; + context.Response.Headers["query"] = context.Request.QueryString.Value; + context.Response.Headers["rawtarget"] = context.Features.Get().RawTarget; + return Task.CompletedTask; + }); + + // :path and :scheme are not allowed, :authority is optional + var headers = new[] { new KeyValuePair(HeaderNames.Method, "GET"), + new KeyValuePair(HeaderNames.Scheme, "http"), + new KeyValuePair(HeaderNames.Path, "/a/path?a&que%35ry")}; + + var doneWithHeaders = await requestStream.SendHeadersAsync(headers, endStream: true); + + var responseHeaders = await requestStream.ExpectHeadersAsync(); + + Assert.Equal(6, responseHeaders.Count); + Assert.Contains("date", responseHeaders.Keys, StringComparer.OrdinalIgnoreCase); + Assert.Equal("200", responseHeaders[HeaderNames.Status]); + Assert.Equal("/a/path", responseHeaders["path"]); + Assert.Equal("?a&que%35ry", responseHeaders["query"]); + Assert.Equal("/a/path?a&que%35ry", responseHeaders["rawtarget"]); + Assert.Equal("0", responseHeaders["content-length"]); + } + + [Theory] + [InlineData("/", "/")] + [InlineData("/a%5E", "/a^")] + [InlineData("/a%E2%82%AC", "/a€")] + [InlineData("/a%2Fb", "/a%2Fb")] // Forward slash, not decoded + [InlineData("/a%b", "/a%b")] // Incomplete encoding, not decoded + [InlineData("/a/b/c/../d", "/a/b/d")] // Navigation processed + [InlineData("/a/b/c/../../../../d", "/d")] // Navigation escape prevented + [InlineData("/a/b/c/.%2E/d", "/a/b/d")] // Decode before navigation processing + public async Task Path_DecodedAndNormalized(string input, string expected) + { + var requestStream = await InitializeConnectionAndStreamsAsync(context => + { + Assert.Equal(expected, context.Request.Path.Value); + Assert.Equal(input, context.Features.Get().RawTarget); + return Task.CompletedTask; + }); + + // :path and :scheme are not allowed, :authority is optional + var headers = new[] { new KeyValuePair(HeaderNames.Method, "GET"), + new KeyValuePair(HeaderNames.Scheme, "http"), + new KeyValuePair(HeaderNames.Path, input)}; + + var doneWithHeaders = await requestStream.SendHeadersAsync(headers, endStream: true); + + var responseHeaders = await requestStream.ExpectHeadersAsync(); + + Assert.Equal(3, responseHeaders.Count); + Assert.Contains("date", responseHeaders.Keys, StringComparer.OrdinalIgnoreCase); + Assert.Equal("200", responseHeaders[HeaderNames.Status]); + Assert.Equal("0", responseHeaders["content-length"]); + } + + [Theory] + [InlineData(":path", "/")] + [InlineData(":scheme", "http")] + public async Task ConnectMethod_WithSchemeOrPath_Reset(string headerName, string value) + { + var requestStream = await InitializeConnectionAndStreamsAsync(_noopApplication); + + // :path and :scheme are not allowed, :authority is optional + var headers = new[] { new KeyValuePair(HeaderNames.Method, "CONNECT"), + new KeyValuePair(headerName, value) }; + + var doneWithHeaders = await requestStream.SendHeadersAsync(headers, endStream: true); + + await requestStream.WaitForStreamErrorAsync(Http3ErrorCode.ProtocolError, CoreStrings.Http3ErrorConnectMustNotSendSchemeOrPath); + } + + [Fact] + public async Task SchemeMismatch_Reset() + { + var requestStream = await InitializeConnectionAndStreamsAsync(_noopApplication); + + // :path and :scheme are not allowed, :authority is optional + var headers = new[] { new KeyValuePair(HeaderNames.Method, "GET"), + new KeyValuePair(HeaderNames.Path, "/"), + new KeyValuePair(HeaderNames.Scheme, "https") }; // Not the expected "http" + + var doneWithHeaders = await requestStream.SendHeadersAsync(headers, endStream: true); + + await requestStream.WaitForStreamErrorAsync(Http3ErrorCode.ProtocolError, CoreStrings.FormatHttp3StreamErrorSchemeMismatch("https", "http")); + } + + [Fact] + [QuarantinedTest] + public async Task MissingAuthority_200Status() + { + var headers = new[] + { + new KeyValuePair(HeaderNames.Method, "GET"), + new KeyValuePair(HeaderNames.Path, "/"), + new KeyValuePair(HeaderNames.Scheme, "http"), + }; + await InitializeConnectionAsync(_noopApplication); + + var requestStream = await InitializeConnectionAndStreamsAsync(_noopApplication); + + var doneWithHeaders = await requestStream.SendHeadersAsync(headers, endStream: true); + + var responseHeaders = await requestStream.ExpectHeadersAsync(); + + Assert.Equal(3, responseHeaders.Count); + Assert.Contains("date", responseHeaders.Keys, StringComparer.OrdinalIgnoreCase); + Assert.Equal("200", responseHeaders[HeaderNames.Status]); + Assert.Equal("0", responseHeaders["content-length"]); + } + + [Fact] + public async Task EmptyAuthority_200Status() + { + var headers = new[] + { + new KeyValuePair(HeaderNames.Method, "GET"), + new KeyValuePair(HeaderNames.Path, "/"), + new KeyValuePair(HeaderNames.Scheme, "http"), + new KeyValuePair(HeaderNames.Authority, ""), + }; + var requestStream = await InitializeConnectionAndStreamsAsync(_noopApplication); + + var doneWithHeaders = await requestStream.SendHeadersAsync(headers, endStream: true); + + var responseHeaders = await requestStream.ExpectHeadersAsync(); + + Assert.Equal(3, responseHeaders.Count); + Assert.Contains("date", responseHeaders.Keys, StringComparer.OrdinalIgnoreCase); + Assert.Equal("200", responseHeaders[HeaderNames.Status]); + Assert.Equal("0", responseHeaders["content-length"]); + } + + [Fact] + public async Task MissingAuthorityFallsBackToHost_200Status() + { + var headers = new[] + { + new KeyValuePair(HeaderNames.Method, "GET"), + new KeyValuePair(HeaderNames.Path, "/"), + new KeyValuePair(HeaderNames.Scheme, "http"), + new KeyValuePair("Host", "abc"), + }; + + var requestStream = await InitializeConnectionAndStreamsAsync(_echoHost); + var doneWithHeaders = await requestStream.SendHeadersAsync(headers, endStream: true); + + var responseHeaders = await requestStream.ExpectHeadersAsync(); + + Assert.Equal(4, responseHeaders.Count); + Assert.Contains("date", responseHeaders.Keys, StringComparer.OrdinalIgnoreCase); + Assert.Equal("200", responseHeaders[HeaderNames.Status]); + Assert.Equal("0", responseHeaders[HeaderNames.ContentLength]); + Assert.Equal("abc", responseHeaders[HeaderNames.Host]); + } + + [Fact] + public async Task EmptyAuthorityIgnoredOverHost_200Status() + { + var headers = new[] + { + new KeyValuePair(HeaderNames.Method, "GET"), + new KeyValuePair(HeaderNames.Path, "/"), + new KeyValuePair(HeaderNames.Scheme, "http"), + new KeyValuePair(HeaderNames.Authority, ""), + new KeyValuePair("Host", "abc"), + }; + + var requestStream = await InitializeConnectionAndStreamsAsync(_echoHost); + var doneWithHeaders = await requestStream.SendHeadersAsync(headers, endStream: true); + + var responseHeaders = await requestStream.ExpectHeadersAsync(); + + Assert.Equal(4, responseHeaders.Count); + Assert.Contains("date", responseHeaders.Keys, StringComparer.OrdinalIgnoreCase); + Assert.Equal("200", responseHeaders[HeaderNames.Status]); + Assert.Equal("0", responseHeaders[HeaderNames.ContentLength]); + Assert.Equal("abc", responseHeaders[HeaderNames.Host]); + } + + [Fact] + public async Task AuthorityOverridesHost_200Status() + { + var headers = new[] + { + new KeyValuePair(HeaderNames.Method, "GET"), + new KeyValuePair(HeaderNames.Path, "/"), + new KeyValuePair(HeaderNames.Scheme, "http"), + new KeyValuePair(HeaderNames.Authority, "def"), + new KeyValuePair("Host", "abc"), + }; + + var requestStream = await InitializeConnectionAndStreamsAsync(_echoHost); + var doneWithHeaders = await requestStream.SendHeadersAsync(headers, endStream: true); + + var responseHeaders = await requestStream.ExpectHeadersAsync(); + + Assert.Equal(4, responseHeaders.Count); + Assert.Contains("date", responseHeaders.Keys, StringComparer.OrdinalIgnoreCase); + Assert.Equal("200", responseHeaders[HeaderNames.Status]); + Assert.Equal("0", responseHeaders[HeaderNames.ContentLength]); + Assert.Equal("def", responseHeaders[HeaderNames.Host]); + } + + [Fact] + public async Task AuthorityOverridesInvalidHost_200Status() + { + var headers = new[] + { + new KeyValuePair(HeaderNames.Method, "GET"), + new KeyValuePair(HeaderNames.Path, "/"), + new KeyValuePair(HeaderNames.Scheme, "http"), + new KeyValuePair(HeaderNames.Authority, "def"), + new KeyValuePair("Host", "a=bc"), + }; + + var requestStream = await InitializeConnectionAndStreamsAsync(_echoHost); + var doneWithHeaders = await requestStream.SendHeadersAsync(headers, endStream: true); + + var responseHeaders = await requestStream.ExpectHeadersAsync(); + + Assert.Equal(4, responseHeaders.Count); + Assert.Contains("date", responseHeaders.Keys, StringComparer.OrdinalIgnoreCase); + Assert.Equal("200", responseHeaders[HeaderNames.Status]); + Assert.Equal("0", responseHeaders[HeaderNames.ContentLength]); + Assert.Equal("def", responseHeaders[HeaderNames.Host]); + } + + [Fact] + public async Task InvalidAuthority_Reset() + { + var headers = new[] + { + new KeyValuePair(HeaderNames.Method, "GET"), + new KeyValuePair(HeaderNames.Path, "/"), + new KeyValuePair(HeaderNames.Scheme, "http"), + new KeyValuePair(HeaderNames.Authority, "local=host:80"), + }; + + var requestStream = await InitializeConnectionAndStreamsAsync(_noopApplication); + var doneWithHeaders = await requestStream.SendHeadersAsync(headers, endStream: true); + + await requestStream.WaitForStreamErrorAsync(Http3ErrorCode.ProtocolError, + CoreStrings.FormatBadRequest_InvalidHostHeader_Detail("local=host:80")); + } + + [Fact] + public async Task InvalidAuthorityWithValidHost_Reset() + { + var headers = new[] + { + new KeyValuePair(HeaderNames.Method, "GET"), + new KeyValuePair(HeaderNames.Path, "/"), + new KeyValuePair(HeaderNames.Scheme, "http"), + new KeyValuePair(HeaderNames.Authority, "d=ef"), + new KeyValuePair("Host", "abc"), + }; + + var requestStream = await InitializeConnectionAndStreamsAsync(_noopApplication); + var doneWithHeaders = await requestStream.SendHeadersAsync(headers, endStream: true); + + await requestStream.WaitForStreamErrorAsync(Http3ErrorCode.ProtocolError, + CoreStrings.FormatBadRequest_InvalidHostHeader_Detail("d=ef")); + } + + [Fact] + public async Task TwoHosts_StreamReset() + { + var headers = new[] + { + new KeyValuePair(HeaderNames.Method, "GET"), + new KeyValuePair(HeaderNames.Path, "/"), + new KeyValuePair(HeaderNames.Scheme, "http"), + new KeyValuePair("Host", "host1"), + new KeyValuePair("Host", "host2"), + }; + + var requestStream = await InitializeConnectionAndStreamsAsync(_noopApplication); + var doneWithHeaders = await requestStream.SendHeadersAsync(headers, endStream: true); + + await requestStream.WaitForStreamErrorAsync(Http3ErrorCode.ProtocolError, + CoreStrings.FormatBadRequest_InvalidHostHeader_Detail("host1,host2")); + } + + [Fact] + public async Task MaxRequestLineSize_Reset() + { + // Default 8kb limit + // This test has to work around the HPack parser limit for incoming field sizes over 4kb. That's going to be a problem for people with long urls. + // https://github.com/aspnet/KestrelHttpServer/issues/2872 + var headers = new[] + { + new KeyValuePair(HeaderNames.Method, "GET" + new string('a', 1024 * 3)), + new KeyValuePair(HeaderNames.Path, "/Hello/How/Are/You/" + new string('a', 1024 * 3)), + new KeyValuePair(HeaderNames.Scheme, "http"), + new KeyValuePair(HeaderNames.Authority, "localhost" + new string('a', 1024 * 3) + ":80"), + }; + var requestStream = await InitializeConnectionAndStreamsAsync(_noopApplication); + var doneWithHeaders = await requestStream.SendHeadersAsync(headers, endStream: true); + + await requestStream.WaitForStreamErrorAsync(Http3ErrorCode.ProtocolError, + CoreStrings.BadRequest_RequestLineTooLong); + } + + [Fact] + public async Task ContentLength_Received_SingleDataFrame_Verified() + { + var headers = new[] + { + new KeyValuePair(HeaderNames.Method, "POST"), + new KeyValuePair(HeaderNames.Path, "/"), + new KeyValuePair(HeaderNames.Scheme, "http"), + new KeyValuePair(HeaderNames.ContentLength, "12"), + }; + + var requestStream = await InitializeConnectionAndStreamsAsync(async context => + { + var buffer = new byte[100]; + var read = await context.Request.Body.ReadAsync(buffer, 0, buffer.Length); + Assert.Equal(12, read); + read = await context.Request.Body.ReadAsync(buffer, 0, buffer.Length); + Assert.Equal(0, read); + }); + + var doneWithHeaders = await requestStream.SendHeadersAsync(headers, endStream: false); + await requestStream.SendDataAsync(new byte[12], endStream: true); + + var responseHeaders = await requestStream.ExpectHeadersAsync(); + + Assert.Equal(3, responseHeaders.Count); + Assert.Contains("date", responseHeaders.Keys, StringComparer.OrdinalIgnoreCase); + Assert.Equal("200", responseHeaders[HeaderNames.Status]); + Assert.Equal("0", responseHeaders[HeaderNames.ContentLength]); + } + + [Fact] + public async Task ContentLength_Received_MultipleDataFrame_Verified() + { + var headers = new[] + { + new KeyValuePair(HeaderNames.Method, "POST"), + new KeyValuePair(HeaderNames.Path, "/"), + new KeyValuePair(HeaderNames.Scheme, "http"), + new KeyValuePair(HeaderNames.ContentLength, "12"), + }; + + var requestStream = await InitializeConnectionAndStreamsAsync(async context => + { + var buffer = new byte[100]; + var read = await context.Request.Body.ReadAsync(buffer, 0, buffer.Length); + var total = read; + while (read > 0) + { + read = await context.Request.Body.ReadAsync(buffer, total, buffer.Length - total); + total += read; + } + Assert.Equal(12, total); + }); + + var doneWithHeaders = await requestStream.SendHeadersAsync(headers, endStream: false); + + await requestStream.SendDataAsync(new byte[1], endStream: false); + await requestStream.SendDataAsync(new byte[3], endStream: false); + await requestStream.SendDataAsync(new byte[8], endStream: true); + + var responseHeaders = await requestStream.ExpectHeadersAsync(); + + Assert.Equal(3, responseHeaders.Count); + Assert.Contains("date", responseHeaders.Keys, StringComparer.OrdinalIgnoreCase); + Assert.Equal("200", responseHeaders[HeaderNames.Status]); + Assert.Equal("0", responseHeaders[HeaderNames.ContentLength]); + } + + [Fact] + public async Task ContentLength_Received_MultipleDataFrame_ReadViaPipe_Verified() + { + var headers = new[] + { + new KeyValuePair(HeaderNames.Method, "POST"), + new KeyValuePair(HeaderNames.Path, "/"), + new KeyValuePair(HeaderNames.Scheme, "http"), + new KeyValuePair(HeaderNames.ContentLength, "12"), + }; + var requestStream = await InitializeConnectionAndStreamsAsync(async context => + { + var readResult = await context.Request.BodyReader.ReadAsync(); + while (!readResult.IsCompleted) + { + context.Request.BodyReader.AdvanceTo(readResult.Buffer.Start, readResult.Buffer.End); + readResult = await context.Request.BodyReader.ReadAsync(); + } + + Assert.Equal(12, readResult.Buffer.Length); + context.Request.BodyReader.AdvanceTo(readResult.Buffer.End); + }); + + var doneWithHeaders = await requestStream.SendHeadersAsync(headers, endStream: false); + + await requestStream.SendDataAsync(new byte[1], endStream: false); + await requestStream.SendDataAsync(new byte[3], endStream: false); + await requestStream.SendDataAsync(new byte[8], endStream: true); + + var responseHeaders = await requestStream.ExpectHeadersAsync(); + + Assert.Equal(3, responseHeaders.Count); + Assert.Contains("date", responseHeaders.Keys, StringComparer.OrdinalIgnoreCase); + Assert.Equal("200", responseHeaders[HeaderNames.Status]); + Assert.Equal("0", responseHeaders[HeaderNames.ContentLength]); + } + + [Fact(Skip = "Http3OutputProducer.Complete is called before input recognizes there is an error. Why is this different than HTTP/2?")] + public async Task ContentLength_Received_NoDataFrames_Reset() + { + var headers = new[] + { + new KeyValuePair(HeaderNames.Method, "POST"), + new KeyValuePair(HeaderNames.Path, "/"), + new KeyValuePair(HeaderNames.Scheme, "http"), + new KeyValuePair(HeaderNames.ContentLength, "12"), + }; + + var requestStream = await InitializeConnectionAndStreamsAsync(_noopApplication); + + await requestStream.SendHeadersAsync(headers, endStream: true); + + await requestStream.WaitForStreamErrorAsync(Http3ErrorCode.ProtocolError, CoreStrings.Http3StreamErrorLessDataThanLength); + } + } +} diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TestBase.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TestBase.cs new file mode 100644 index 0000000000..f767fc0871 --- /dev/null +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TestBase.cs @@ -0,0 +1,527 @@ +using System; +using System.Buffers; +using System.Collections.Generic; +using System.IO; +using System.IO.Pipelines; +using System.Net.Http; +using System.Net.Http.QPack; +using System.Reflection; +using System.Threading; +using System.Threading.Channels; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Connections; +using Microsoft.AspNetCore.Connections.Features; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; +using Microsoft.AspNetCore.Testing; +using Microsoft.Extensions.Logging; +using Microsoft.Net.Http.Headers; +using Moq; +using Xunit; +using Xunit.Abstractions; +using static System.IO.Pipelines.DuplexPipe; +using static Microsoft.AspNetCore.Server.Kestrel.Core.Tests.Http2TestBase; + +namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests +{ + public class Http3TestBase : TestApplicationErrorLoggerLoggedTest, IDisposable + { + internal TestServiceContext _serviceContext; + internal Http3Connection _connection; + internal readonly TimeoutControl _timeoutControl; + internal readonly Mock _mockKestrelTrace = new Mock(); + internal readonly Mock _mockTimeoutHandler = new Mock(); + internal readonly Mock _mockTimeoutControl; + internal readonly MemoryPool _memoryPool = SlabMemoryPoolFactory.Create(); + protected Task _connectionTask; + private TestMultiplexedConnectionContext _multiplexedContext; + private readonly CancellationTokenSource _connectionClosingCts = new CancellationTokenSource(); + + protected readonly RequestDelegate _noopApplication; + protected readonly RequestDelegate _echoApplication; + protected readonly RequestDelegate _echoMethod; + protected readonly RequestDelegate _echoPath; + protected readonly RequestDelegate _echoHost; + + public Http3TestBase() + { + _timeoutControl = new TimeoutControl(_mockTimeoutHandler.Object); + _mockTimeoutControl = new Mock(_timeoutControl) { CallBase = true }; + _timeoutControl.Debugger = Mock.Of(); + + _noopApplication = context => Task.CompletedTask; + + _echoApplication = async context => + { + var buffer = new byte[Http3PeerSettings.MinAllowedMaxFrameSize]; + var received = 0; + + while ((received = await context.Request.Body.ReadAsync(buffer, 0, buffer.Length)) > 0) + { + await context.Response.Body.WriteAsync(buffer, 0, received); + } + }; + + _echoMethod = context => + { + context.Response.Headers["Method"] = context.Request.Method; + + return Task.CompletedTask; + }; + + _echoPath = context => + { + context.Response.Headers["path"] = context.Request.Path.ToString(); + context.Response.Headers["rawtarget"] = context.Features.Get().RawTarget; + + return Task.CompletedTask; + }; + + _echoHost = context => + { + context.Response.Headers[HeaderNames.Host] = context.Request.Headers[HeaderNames.Host]; + + return Task.CompletedTask; + }; + } + + public override void Initialize(TestContext context, MethodInfo methodInfo, object[] testMethodArguments, ITestOutputHelper testOutputHelper) + { + base.Initialize(context, methodInfo, testMethodArguments, testOutputHelper); + + _serviceContext = new TestServiceContext(LoggerFactory, _mockKestrelTrace.Object) + { + Scheduler = PipeScheduler.Inline, + }; + } + + protected async Task InitializeConnectionAsync(RequestDelegate application) + { + if (_connection == null) + { + CreateConnection(); + } + + // Skip all heartbeat and lifetime notification feature registrations. + _connectionTask = _connection.InnerProcessRequestsAsync(new DummyApplication(application)); + + await Task.CompletedTask; + } + + internal async ValueTask InitializeConnectionAndStreamsAsync(RequestDelegate application) + { + await InitializeConnectionAsync(application); + + var controlStream1 = await CreateControlStream(0); + var controlStream2 = await CreateControlStream(2); + var controlStream3 = await CreateControlStream(3); + + return await CreateRequestStream(); + } + + protected void CreateConnection() + { + var limits = _serviceContext.ServerOptions.Limits; + + var features = new FeatureCollection(); + + _multiplexedContext = new TestMultiplexedConnectionContext(this); + + var httpConnectionContext = new Http3ConnectionContext + { + ConnectionContext = _multiplexedContext, + ConnectionFeatures = features, + ServiceContext = _serviceContext, + MemoryPool = _memoryPool, + TimeoutControl = _mockTimeoutControl.Object + }; + + _connection = new Http3Connection(httpConnectionContext); + _mockTimeoutHandler.Setup(h => h.OnTimeout(It.IsAny())) + .Callback(r => _connection.OnTimeout(r)); + } + + private static PipeOptions GetInputPipeOptions(ServiceContext serviceContext, MemoryPool memoryPool, PipeScheduler writerScheduler) => new PipeOptions + ( + pool: memoryPool, + readerScheduler: serviceContext.Scheduler, + writerScheduler: writerScheduler, + pauseWriterThreshold: serviceContext.ServerOptions.Limits.MaxRequestBufferSize ?? 0, + resumeWriterThreshold: serviceContext.ServerOptions.Limits.MaxRequestBufferSize ?? 0, + useSynchronizationContext: false, + minimumSegmentSize: memoryPool.GetMinimumSegmentSize() + ); + + private static PipeOptions GetOutputPipeOptions(ServiceContext serviceContext, MemoryPool memoryPool, PipeScheduler readerScheduler) => new PipeOptions + ( + pool: memoryPool, + readerScheduler: readerScheduler, + writerScheduler: serviceContext.Scheduler, + pauseWriterThreshold: GetOutputResponseBufferSize(serviceContext), + resumeWriterThreshold: GetOutputResponseBufferSize(serviceContext), + useSynchronizationContext: false, + minimumSegmentSize: memoryPool.GetMinimumSegmentSize() + ); + + private static long GetOutputResponseBufferSize(ServiceContext serviceContext) + { + var bufferSize = serviceContext.ServerOptions.Limits.MaxResponseBufferSize; + if (bufferSize == 0) + { + // 0 = no buffering so we need to configure the pipe so the writer waits on the reader directly + return 1; + } + + // null means that we have no back pressure + return bufferSize ?? 0; + } + + internal async ValueTask CreateControlStream(int id) + { + var stream = new Http3ControlStream(this); + _multiplexedContext.AcceptQueue.Writer.TryWrite(stream.StreamContext); + await stream.WriteStreamIdAsync(id); + return stream; + } + + internal ValueTask CreateRequestStream() + { + var stream = new Http3RequestStream(this, _connection); + _multiplexedContext.AcceptQueue.Writer.TryWrite(stream.StreamContext); + return new ValueTask(stream); + } + + public ValueTask StartBidirectionalStreamAsync() + { + var stream = new Http3RequestStream(this, _connection); + // TODO put these somewhere to be read. + return new ValueTask(stream.StreamContext); + } + + internal class Http3StreamBase + { + protected DuplexPipe.DuplexPipePair _pair; + protected Http3TestBase _testBase; + protected Http3Connection _connection; + + protected Task SendAsync(ReadOnlySpan span) + { + var writableBuffer = _pair.Application.Output; + writableBuffer.Write(span); + return FlushAsync(writableBuffer); + } + + protected static async Task FlushAsync(PipeWriter writableBuffer) + { + await writableBuffer.FlushAsync().AsTask().DefaultTimeout(); + } + } + + internal class Http3RequestStream : Http3StreamBase, IHttpHeadersHandler, IProtocolErrorCodeFeature + { + internal ConnectionContext StreamContext { get; } + + public bool CanRead => true; + public bool CanWrite => true; + + public long StreamId => 0; + + public long Error { get; set; } + + private readonly byte[] _headerEncodingBuffer = new byte[Http3PeerSettings.MinAllowedMaxFrameSize]; + private QPackEncoder _qpackEncoder = new QPackEncoder(); + private QPackDecoder _qpackDecoder = new QPackDecoder(8192); + private long _bytesReceived; + protected readonly Dictionary _decodedHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); + + public Http3RequestStream(Http3TestBase testBase, Http3Connection connection) + { + _testBase = testBase; + _connection = connection; + var inputPipeOptions = GetInputPipeOptions(_testBase._serviceContext, _testBase._memoryPool, PipeScheduler.ThreadPool); + var outputPipeOptions = GetOutputPipeOptions(_testBase._serviceContext, _testBase._memoryPool, PipeScheduler.ThreadPool); + + _pair = DuplexPipe.CreateConnectionPair(inputPipeOptions, outputPipeOptions); + + StreamContext = new TestStreamContext(canRead: true, canWrite: true, _pair, this); + } + + public async Task SendHeadersAsync(IEnumerable> headers, bool endStream = false) + { + var outputWriter = _pair.Application.Output; + var frame = new Http3RawFrame(); + frame.PrepareHeaders(); + var buffer = _headerEncodingBuffer.AsMemory(); + var done = _qpackEncoder.BeginEncode(headers, buffer.Span, out var length); + frame.Length = length; + // TODO may want to modify behavior of input frames to mock different client behavior (client can send anything). + Http3FrameWriter.WriteHeader(frame, outputWriter); + await SendAsync(buffer.Span.Slice(0, length)); + + if (endStream) + { + await _pair.Application.Output.CompleteAsync(); + } + + return done; + } + + internal async Task SendDataAsync(Memory data, bool endStream = false) + { + var outputWriter = _pair.Application.Output; + var frame = new Http3RawFrame(); + frame.PrepareData(); + frame.Length = data.Length; + Http3FrameWriter.WriteHeader(frame, outputWriter); + await SendAsync(data.Span); + + if (endStream) + { + await _pair.Application.Output.CompleteAsync(); + } + } + + internal async Task> ExpectHeadersAsync() + { + var http3WithPayload = await ReceiveFrameAsync(); + _qpackDecoder.Decode(http3WithPayload.PayloadSequence, this); + return _decodedHeaders; + } + + internal async Task> ExpectDataAsync() + { + var http3WithPayload = await ReceiveFrameAsync(); + return http3WithPayload.Payload; + } + + internal async Task ReceiveFrameAsync(uint maxFrameSize = Http3PeerSettings.DefaultMaxFrameSize) + { + var frame = new Http3FrameWithPayload(); + + while (true) + { + var result = await _pair.Application.Input.ReadAsync().AsTask().DefaultTimeout(); + var buffer = result.Buffer; + var consumed = buffer.Start; + var examined = buffer.Start; + var copyBuffer = buffer; + + try + { + Assert.True(buffer.Length > 0); + + if (Http3FrameReader.TryReadFrame(ref buffer, frame, maxFrameSize, out var framePayload)) + { + consumed = examined = framePayload.End; + frame.Payload = framePayload.ToArray(); + return frame; + } + else + { + examined = buffer.End; + } + + if (result.IsCompleted) + { + throw new IOException("The reader completed without returning a frame."); + } + } + finally + { + _bytesReceived += copyBuffer.Slice(copyBuffer.Start, consumed).Length; + _pair.Application.Input.AdvanceTo(consumed, examined); + } + } + } + + internal async Task ExpectReceiveEndOfStream() + { + var result = await _pair.Application.Input.ReadAsync().AsTask().DefaultTimeout(); + Assert.True(result.IsCompleted); + } + + public void OnHeader(ReadOnlySpan name, ReadOnlySpan value) + { + _decodedHeaders[name.GetAsciiStringNonNullCharacters()] = value.GetAsciiOrUTF8StringNonNullCharacters(); + } + + public void OnHeadersComplete(bool endHeaders) + { + } + + public void OnStaticIndexedHeader(int index) + { + var knownHeader = H3StaticTable.Instance[index]; + _decodedHeaders[((Span)knownHeader.Name).GetAsciiStringNonNullCharacters()] = HttpUtilities.GetAsciiOrUTF8StringNonNullCharacters(knownHeader.Value); + } + + public void OnStaticIndexedHeader(int index, ReadOnlySpan value) + { + _decodedHeaders[((Span)H3StaticTable.Instance[index].Name).GetAsciiStringNonNullCharacters()] = value.GetAsciiOrUTF8StringNonNullCharacters(); + } + + internal async Task WaitForStreamErrorAsync(Http3ErrorCode protocolError, string expectedErrorMessage) + { + var readResult = await _pair.Application.Input.ReadAsync(); + _testBase.Logger.LogTrace("Input is completed"); + + Assert.True(readResult.IsCompleted); + Assert.Equal((long)protocolError, Error); + + if (expectedErrorMessage != null) + { + Assert.Contains(_testBase.TestApplicationErrorLogger.Messages, m => m.Exception?.Message.Contains(expectedErrorMessage) ?? false); + } + } + } + + internal class Http3FrameWithPayload : Http3RawFrame + { + public Http3FrameWithPayload() : base() + { + } + + // This does not contain extended headers + public Memory Payload { get; set; } + + public ReadOnlySequence PayloadSequence => new ReadOnlySequence(Payload); + } + + + internal class Http3ControlStream : Http3StreamBase, IProtocolErrorCodeFeature + { + internal ConnectionContext StreamContext { get; } + + public bool CanRead => true; + public bool CanWrite => false; + + public long StreamId => 0; + + public long Error { get; set; } + + public Http3ControlStream(Http3TestBase testBase) + { + _testBase = testBase; + var inputPipeOptions = GetInputPipeOptions(_testBase._serviceContext, _testBase._memoryPool, PipeScheduler.ThreadPool); + var outputPipeOptions = GetOutputPipeOptions(_testBase._serviceContext, _testBase._memoryPool, PipeScheduler.ThreadPool); + _pair = DuplexPipe.CreateConnectionPair(inputPipeOptions, outputPipeOptions); + StreamContext = new TestStreamContext(canRead: false, canWrite: true, _pair, this); + } + + public async Task WriteStreamIdAsync(int id) + { + var writableBuffer = _pair.Application.Output; + + void WriteSpan(PipeWriter pw) + { + var buffer = pw.GetSpan(sizeHint: 8); + var lengthWritten = VariableLengthIntegerHelper.WriteInteger(buffer, id); + pw.Advance(lengthWritten); + } + + WriteSpan(writableBuffer); + + await FlushAsync(writableBuffer); + } + } + + private class TestMultiplexedConnectionContext : MultiplexedConnectionContext + { + public readonly Channel AcceptQueue = Channel.CreateUnbounded(new UnboundedChannelOptions + { + SingleReader = true, + SingleWriter = true + }); + + private readonly Http3TestBase _testBase; + + public TestMultiplexedConnectionContext(Http3TestBase testBase) + { + _testBase = testBase; + } + + public override string ConnectionId { get; set; } + + public override IFeatureCollection Features { get; } + + public override IDictionary Items { get; set; } + + public override void Abort() + { + } + + public override void Abort(ConnectionAbortedException abortReason) + { + } + + public override async ValueTask AcceptAsync(CancellationToken cancellationToken = default) + { + while (await AcceptQueue.Reader.WaitToReadAsync()) + { + while (AcceptQueue.Reader.TryRead(out var connection)) + { + return connection; + } + } + + return null; + } + + public override ValueTask ConnectAsync(IFeatureCollection features = null, CancellationToken cancellationToken = default) + { + var stream = new Http3ControlStream(_testBase); + // TODO put these somewhere to be read. + return new ValueTask(stream.StreamContext); + } + } + + private class TestStreamContext : ConnectionContext, IStreamDirectionFeature, IStreamIdFeature + { + private DuplexPipePair _pair; + public TestStreamContext(bool canRead, bool canWrite, DuplexPipePair pair, IProtocolErrorCodeFeature feature) + { + _pair = pair; + Features = new FeatureCollection(); + Features.Set(this); + Features.Set(this); + Features.Set(feature); + + CanRead = canRead; + CanWrite = canWrite; + } + + public override string ConnectionId { get; set; } + + public long StreamId { get; } + + public override IFeatureCollection Features { get; } + + public override IDictionary Items { get; set; } + + public override IDuplexPipe Transport + { + get + { + return _pair.Transport; + } + set + { + throw new NotImplementedException(); + } + } + + public bool CanRead { get; } + + public bool CanWrite { get; } + + public override void Abort(ConnectionAbortedException abortReason) + { + _pair.Application.Output.Complete(abortReason); + } + } + } +} diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpsConnectionMiddlewareTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpsConnectionMiddlewareTests.cs index 45e21934be..43e557734e 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpsConnectionMiddlewareTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpsConnectionMiddlewareTests.cs @@ -383,14 +383,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests using (var connection = server.CreateConnection()) { var stream = OpenSslStreamWithCert(connection.Stream); - var ex = await Assert.ThrowsAsync( + var ex = await Assert.ThrowsAnyAsync( async () => await stream.AuthenticateAsClientAsync("localhost", new X509CertificateCollection(), SslProtocols.Tls, false)); } } } [Theory] - [Flaky("https://github.com/aspnet/AspNetCore-Internal/issues/1976", FlakyOn.All)] [InlineData(ClientCertificateMode.AllowCertificate)] [InlineData(ClientCertificateMode.RequireCertificate)] public async Task ClientCertificateValidationGetsCalledWithNotNullParameters(ClientCertificateMode mode) @@ -425,7 +424,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests } [ConditionalTheory] - [Flaky("https://github.com/aspnet/AspNetCore-Internal/issues/1950", FlakyOn.Helix.All)] [InlineData(ClientCertificateMode.AllowCertificate)] [InlineData(ClientCertificateMode.RequireCertificate)] public async Task ValidationFailureRejectsConnection(ClientCertificateMode mode) @@ -594,7 +592,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests [InlineData(HttpProtocols.Http2)] [InlineData(HttpProtocols.Http1AndHttp2)] [OSSkipCondition(OperatingSystems.MacOSX, SkipReason = "Missing SslStream ALPN support: https://github.com/dotnet/corefx/issues/30492")] - [SkipOnHelix("https://github.com/aspnet/AspNetCore/issues/10428", Queues = "Debian.8.Amd64.Open")] // Debian 8 uses OpenSSL 1.0.1 which does not support HTTP/2 + [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/10428", Queues = "Debian.8.Amd64;Debian.8.Amd64.Open")] // Debian 8 uses OpenSSL 1.0.1 which does not support HTTP/2 [MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win81)] public async Task ListenOptionsProtolsCanBeSetAfterUseHttps(HttpProtocols httpProtocols) { diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpsTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpsTests.cs index 90a8b1dbde..163d6a632f 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpsTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpsTests.cs @@ -143,6 +143,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests } [Fact] + [QuarantinedTest] public async Task ClientHandshakeFailureLoggedAsDebug() { var loggerProvider = new HandshakeErrorLoggerProvider(); @@ -218,6 +219,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests [Fact] [Repeat(20)] + [QuarantinedTest] public async Task DoesNotThrowObjectDisposedExceptionFromWriteAsyncAfterConnectionIsAborted() { var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); @@ -371,7 +373,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests using (var sslStream = new SslStream(connection.Stream, true, (sender, certificate, chain, errors) => true)) { // SslProtocols.Tls is TLS 1.0 which isn't supported by Kestrel by default. - await Assert.ThrowsAsync(() => + await Assert.ThrowsAnyAsync(() => sslStream.AuthenticateAsClientAsync("127.0.0.1", clientCertificates: null, enabledSslProtocols: SslProtocols.Tls, checkCertificateRevocation: false)); @@ -393,14 +395,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests new TestServiceContext(LoggerFactory), listenOptions => { - listenOptions.UseHttps(TestResources.GetTestCertificate()); + listenOptions.UseHttps(TestResources.GetTestCertificate("aspnetdevcert.pfx", "testPassword")); })) { using (var connection = server.CreateConnection()) using (var sslStream = new SslStream(connection.Stream, true, (sender, certificate, chain, errors) => true)) { // SslProtocols.Tls is TLS 1.0 which isn't supported by Kestrel by default. - await Assert.ThrowsAsync(() => + await Assert.ThrowsAnyAsync(() => sslStream.AuthenticateAsClientAsync("127.0.0.1", clientCertificates: null, enabledSslProtocols: SslProtocols.Tls, checkCertificateRevocation: false)); diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/InMemory.FunctionalTests.csproj b/src/Servers/Kestrel/test/InMemory.FunctionalTests/InMemory.FunctionalTests.csproj index bab7248341..cad32b08bd 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/InMemory.FunctionalTests.csproj +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/InMemory.FunctionalTests.csproj @@ -14,18 +14,17 @@ - + - + - - + \ No newline at end of file diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/LoggingConnectionMiddlewareTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/LoggingConnectionMiddlewareTests.cs index 753e05814a..31539e50b3 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/LoggingConnectionMiddlewareTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/LoggingConnectionMiddlewareTests.cs @@ -14,7 +14,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests public class LoggingConnectionMiddlewareTests : LoggedTest { [Fact] - [Flaky("https://github.com/aspnet/AspNetCore-Internal/issues/2276", FlakyOn.Helix.All)] public async Task LoggingConnectionMiddlewareCanBeAddedBeforeAndAfterHttps() { await using (var server = new TestServer(context => diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestTests.cs index fef0608c1c..dafbd6eca7 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestTests.cs @@ -790,7 +790,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests } [Fact] - [Flaky("https://github.com/aspnet/AspNetCore-Internal/issues/2176", FlakyOn.All)] + [QuarantinedTest] public async Task ContentLengthReadAsyncSingleBytesAtATime() { var testContext = new TestServiceContext(LoggerFactory); @@ -1684,6 +1684,61 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests } } + [Fact] + public async Task ReuseRequestHeaderStrings() + { + var testContext = new TestServiceContext(LoggerFactory); + string customHeaderValue = null; + string contentTypeHeaderValue = null; + + await using (var server = new TestServer(context => + { + customHeaderValue = context.Request.Headers["X-CustomHeader"]; + contentTypeHeaderValue = context.Request.ContentType; + return Task.CompletedTask; + }, testContext)) + { + using (var connection = server.CreateConnection()) + { + // First request + await connection.Send( + "GET / HTTP/1.1", + "Host:", + "Content-Type: application/test", + "X-CustomHeader: customvalue", + "", + ""); + await connection.Receive( + "HTTP/1.1 200 OK", + $"Date: {testContext.DateHeaderValue}", + "Content-Length: 0", + "", + ""); + + var initialCustomHeaderValue = customHeaderValue; + var initialContentTypeValue = contentTypeHeaderValue; + + // Second request + await connection.Send( + "GET / HTTP/1.1", + "Host:", + "Content-Type: application/test", + "X-CustomHeader: customvalue", + "", + ""); + await connection.Receive( + "HTTP/1.1 200 OK", + $"Date: {testContext.DateHeaderValue}", + "Content-Length: 0", + "", + ""); + + Assert.NotSame(initialCustomHeaderValue, customHeaderValue); + Assert.Same(initialContentTypeValue, contentTypeHeaderValue); + } + } + } + [Fact] public async Task Latin1HeaderValueAcceptedWhenLatin1OptionIsConfigured() { diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/ResponseTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/ResponseTests.cs index 935a722029..0dca63fe5f 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/ResponseTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/ResponseTests.cs @@ -2541,7 +2541,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests } [Fact] - [Flaky("https://github.com/aspnet/AspNetCore/issues/12401", FlakyOn.All)] + [QuarantinedTest] public async Task AppAbortViaIConnectionLifetimeFeatureIsLogged() { var testContext = new TestServiceContext(LoggerFactory); diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/TestServer.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/TestServer.cs index 1c76309881..0c8e40921d 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/TestServer.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/TestServer.cs @@ -3,11 +3,13 @@ using System; using System.Buffers; +using System.Collections.Generic; using System.Diagnostics; using System.Net; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Http; @@ -80,7 +82,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.TestTrans { context.ServerOptions.ApplicationServices = sp; configureKestrel(context.ServerOptions); - return new KestrelServer(_transportFactory, context); + return new KestrelServer(new List() { _transportFactory }, context); }); }); diff --git a/src/Servers/Kestrel/test/Interop.FunctionalTests/ChromeTests.cs b/src/Servers/Kestrel/test/Interop.FunctionalTests/ChromeTests.cs index de82af3a98..80cb4e890e 100644 --- a/src/Servers/Kestrel/test/Interop.FunctionalTests/ChromeTests.cs +++ b/src/Servers/Kestrel/test/Interop.FunctionalTests/ChromeTests.cs @@ -62,7 +62,7 @@ namespace Interop.FunctionalTests }; } - [ConditionalTheory(Skip="Disabling while debugging. https://github.com/aspnet/AspNetCore-Internal/issues/1363")] + [ConditionalTheory(Skip="Disabling while debugging. https://github.com/dotnet/aspnetcore-internal/issues/1363")] [OSSkipCondition(OperatingSystems.MacOSX, SkipReason = "Missing SslStream ALPN support: https://github.com/dotnet/corefx/issues/30492")] [MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win81, SkipReason = "Missing Windows ALPN support: https://en.wikipedia.org/wiki/Application-Layer_Protocol_Negotiation#Support")] [InlineData("", "Interop HTTP/2 GET")] @@ -83,7 +83,7 @@ namespace Interop.FunctionalTests .ConfigureServices(AddTestLogging) .Configure(app => app.Run(async context => { - if (string.Equals(context.Request.Query["TestMethod"], "POST", StringComparison.OrdinalIgnoreCase)) + if (HttpMethods.IsPost(context.Request.Query["TestMethod"])) { await context.Response.WriteAsync(_postHtml); } diff --git a/src/Servers/Kestrel/test/Interop.FunctionalTests/H2SpecTests.cs b/src/Servers/Kestrel/test/Interop.FunctionalTests/H2SpecTests.cs index 2c5f39382d..eb7a95a228 100644 --- a/src/Servers/Kestrel/test/Interop.FunctionalTests/H2SpecTests.cs +++ b/src/Servers/Kestrel/test/Interop.FunctionalTests/H2SpecTests.cs @@ -21,7 +21,6 @@ namespace Interop.FunctionalTests { [ConditionalTheory] [MemberData(nameof(H2SpecTestCases))] - [Flaky("https://github.com/aspnet/AspNetCore-Internal/issues/2225", FlakyOn.Helix.All)] public async Task RunIndividualTestCase(H2SpecTestCase testCase) { var hostBuilder = new WebHostBuilder() @@ -74,7 +73,7 @@ namespace Interop.FunctionalTests Skip = skip, }); - // https://github.com/aspnet/AspNetCore/issues/11301 We should use Skip but it's broken at the moment. + // https://github.com/dotnet/aspnetcore/issues/11301 We should use Skip but it's broken at the moment. if (supportsAlpn) { dataset.Add(new H2SpecTestCase diff --git a/src/Servers/Kestrel/test/Interop.FunctionalTests/HttpClientHttp2InteropTests.cs b/src/Servers/Kestrel/test/Interop.FunctionalTests/HttpClientHttp2InteropTests.cs index 234c127379..18faacc44d 100644 --- a/src/Servers/Kestrel/test/Interop.FunctionalTests/HttpClientHttp2InteropTests.cs +++ b/src/Servers/Kestrel/test/Interop.FunctionalTests/HttpClientHttp2InteropTests.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Net; using System.Net.Http; using System.Text; @@ -41,7 +42,6 @@ namespace Interop.FunctionalTests new[] { "http" } }; - // https://github.com/aspnet/AspNetCore/issues/11301 We should use Skip but it's broken at the moment. if (Utilities.CurrentPlatformSupportsAlpn()) { list.Add(new[] { "https" }); @@ -51,7 +51,7 @@ namespace Interop.FunctionalTests } } - [ConditionalTheory] + [Theory] [MemberData(nameof(SupportedSchemes))] public async Task HelloWorld(string scheme) { @@ -72,7 +72,7 @@ namespace Interop.FunctionalTests await host.StopAsync().DefaultTimeout(); } - [ConditionalTheory] + [Theory] [MemberData(nameof(SupportedSchemes))] public async Task Echo(string scheme) { @@ -102,7 +102,7 @@ namespace Interop.FunctionalTests } // Concurrency testing - [ConditionalTheory] + [Theory] [MemberData(nameof(SupportedSchemes))] public async Task MultiplexGet(string scheme) { @@ -150,7 +150,7 @@ namespace Interop.FunctionalTests } // Concurrency testing - [ConditionalTheory] + [Theory] [MemberData(nameof(SupportedSchemes))] public async Task MultiplexEcho(string scheme) { @@ -201,7 +201,7 @@ namespace Interop.FunctionalTests private class BulkContent : HttpContent { private static readonly byte[] Content; - private static readonly int Repititions = 200; + private static readonly int Repetitions = 200; static BulkContent() { @@ -214,7 +214,7 @@ namespace Interop.FunctionalTests protected override async Task SerializeToStreamAsync(Stream stream, TransportContext context) { - for (var i = 0; i < Repititions; i++) + for (var i = 0; i < Repetitions; i++) { using (var timer = new CancellationTokenSource(TimeSpan.FromSeconds(30))) { @@ -244,7 +244,7 @@ namespace Interop.FunctionalTests while (read > 0) { totalRead += read; - Assert.True(totalRead <= Repititions * Content.Length, "Too Long"); + Assert.True(totalRead <= Repetitions * Content.Length, "Too Long"); for (var offset = 0; offset < read; offset++) { @@ -256,11 +256,11 @@ namespace Interop.FunctionalTests read = await stream.ReadAsync(buffer, 0, buffer.Length, timer.Token).DefaultTimeout(); } - Assert.True(totalRead == Repititions * Content.Length, "Too Short"); + Assert.True(totalRead == Repetitions * Content.Length, "Too Short"); } } - [ConditionalTheory] + [Theory] [MemberData(nameof(SupportedSchemes))] public async Task BidirectionalStreaming(string scheme) { @@ -318,7 +318,7 @@ namespace Interop.FunctionalTests await host.StopAsync().DefaultTimeout(); } - [ConditionalTheory(Skip = "https://github.com/dotnet/corefx/issues/39404")] + [Theory] [MemberData(nameof(SupportedSchemes))] public async Task BidirectionalStreamingMoreClientData(string scheme) { @@ -388,7 +388,7 @@ namespace Interop.FunctionalTests await ReadStreamHelloWorld(stream); Assert.Equal(0, await stream.ReadAsync(new byte[10], 0, 10).DefaultTimeout()); - stream.Dispose(); // https://github.com/dotnet/corefx/issues/39404 can be worked around by commenting out this Dispose + stream.Dispose(); // Send one more message after the server has finished. await streamingContent.SendAsync("Hello World").DefaultTimeout(); @@ -400,7 +400,7 @@ namespace Interop.FunctionalTests await host.StopAsync().DefaultTimeout(); } - [ConditionalTheory(Skip = "https://github.com/dotnet/corefx/issues/39404")] + [Theory] [MemberData(nameof(SupportedSchemes))] public async Task ReverseEcho(string scheme) { @@ -412,16 +412,12 @@ namespace Interop.FunctionalTests webHostBuilder.ConfigureServices(AddTestLogging) .Configure(app => app.Run(async context => { - // Prime it? - // var readTask = context.Request.BodyReader.ReadAsync(); context.Response.ContentType = "text/plain"; await context.Response.WriteAsync("Hello World"); await context.Response.CompleteAsync().DefaultTimeout(); try { - // var readResult = await readTask; - // context.Request.BodyReader.AdvanceTo(readResult.Buffer.Start, readResult.Buffer.End); using var streamReader = new StreamReader(context.Request.Body); var read = await streamReader.ReadToEndAsync().DefaultTimeout(); clientEcho.SetResult(read); @@ -437,7 +433,8 @@ namespace Interop.FunctionalTests var url = host.MakeUrl(scheme); using var client = CreateClient(); - // client.DefaultRequestHeaders.ExpectContinue = true; + // The client doesn't flush the headers for requests with a body unless a continue is expected. + client.DefaultRequestHeaders.ExpectContinue = true; var streamingContent = new StreamingContent(); var request = CreateRequestMessage(HttpMethod.Post, url, streamingContent); @@ -446,15 +443,8 @@ namespace Interop.FunctionalTests Assert.Equal(HttpVersion.Version20, response.Version); // Read Hello World and echo it back to the server. - /* https://github.com/dotnet/corefx/issues/39404 var read = await response.Content.ReadAsStringAsync().DefaultTimeout(); Assert.Equal("Hello World", read); - */ - var stream = await response.Content.ReadAsStreamAsync().DefaultTimeout(); - await ReadStreamHelloWorld(stream).DefaultTimeout(); - - Assert.Equal(0, await stream.ReadAsync(new byte[10], 0, 10).DefaultTimeout()); - stream.Dispose(); // https://github.com/dotnet/corefx/issues/39404 can be worked around by commenting out this Dispose await streamingContent.SendAsync("Hello World").DefaultTimeout(); streamingContent.Complete(); @@ -473,6 +463,8 @@ namespace Interop.FunctionalTests { } + public Task SendStarted => _sendStarted.Task; + public async Task SendAsync(string text) { await _sendStarted.Task; @@ -514,6 +506,1098 @@ namespace Interop.FunctionalTests length = 0; return false; } + + internal void Abort() + { + if (_sendComplete == null) + { + throw new InvalidOperationException("Sending hasn't started yet."); + } + _sendComplete.TrySetException(new Exception("Abort")); + } + } + + [Theory] + [MemberData(nameof(SupportedSchemes))] + public async Task ResponseTrailersWithoutData(string scheme) + { + var hostBuilder = new HostBuilder() + .ConfigureWebHost(webHostBuilder => + { + ConfigureKestrel(webHostBuilder, scheme); + webHostBuilder.ConfigureServices(AddTestLogging) + .Configure(app => app.Run(context => + { + context.Response.DeclareTrailer("TestTrailer"); + context.Response.AppendTrailer("TestTrailer", "TestValue"); + return Task.CompletedTask; + })); + }); + using var host = await hostBuilder.StartAsync().DefaultTimeout(); + + var url = host.MakeUrl(scheme); + using var client = CreateClient(); + var response = await client.GetAsync(url).DefaultTimeout(); + Assert.Equal(HttpVersion.Version20, response.Version); + Assert.Equal("TestTrailer", response.Headers.Trailer.Single()); + // The response is buffered, we must already have the trailers. + Assert.Equal("TestValue", response.TrailingHeaders.GetValues("TestTrailer").Single()); + Assert.Equal("", await response.Content.ReadAsStringAsync()); + await host.StopAsync().DefaultTimeout(); + } + + [Theory] + [MemberData(nameof(SupportedSchemes))] + public async Task ResponseTrailersWithData(string scheme) + { + var headersReceived = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var hostBuilder = new HostBuilder() + .ConfigureWebHost(webHostBuilder => + { + ConfigureKestrel(webHostBuilder, scheme); + webHostBuilder.ConfigureServices(AddTestLogging) + .Configure(app => app.Run(async context => + { + context.Response.DeclareTrailer("TestTrailer"); + await context.Response.WriteAsync("Hello "); + await headersReceived.Task.DefaultTimeout(); + await context.Response.WriteAsync("World"); + context.Response.AppendTrailer("TestTrailer", "TestValue"); + })); + }); + using var host = await hostBuilder.StartAsync().DefaultTimeout(); + + var url = host.MakeUrl(scheme); + using var client = CreateClient(); + var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead).DefaultTimeout(); + Assert.Equal(HttpVersion.Version20, response.Version); + Assert.Equal("TestTrailer", response.Headers.Trailer.Single()); + // The server has not sent trailers yet. + Assert.False(response.TrailingHeaders.TryGetValues("TestTrailer", out var none)); + headersReceived.SetResult(0); + var responseBody = await response.Content.ReadAsStringAsync().DefaultTimeout(); + Assert.Equal("Hello World", responseBody); + // The response is buffered, we must already have the trailers. + Assert.Equal("TestValue", response.TrailingHeaders.GetValues("TestTrailer").Single()); + await host.StopAsync().DefaultTimeout(); + } + + [Theory] + [MemberData(nameof(SupportedSchemes))] + public async Task ServerReset_BeforeResponse_ClientThrows(string scheme) + { + var hostBuilder = new HostBuilder() + .ConfigureWebHost(webHostBuilder => + { + ConfigureKestrel(webHostBuilder, scheme); + webHostBuilder.ConfigureServices(AddTestLogging) + .Configure(app => app.Run(context => + { + context.Features.Get().Reset(8); // Cancel + return Task.CompletedTask; + })); + }); + using var host = await hostBuilder.StartAsync().DefaultTimeout(); + + var url = host.MakeUrl(scheme); + using var client = CreateClient(); + var exception = await Assert.ThrowsAsync(() => client.GetAsync(url)).DefaultTimeout(); + Assert.Equal("The HTTP/2 server reset the stream. HTTP/2 error code 'CANCEL' (0x8).", exception?.InnerException?.InnerException.Message); + await host.StopAsync().DefaultTimeout(); + } + + [Theory] + [MemberData(nameof(SupportedSchemes))] + public async Task ServerReset_AfterHeaders_ClientBodyThrows(string scheme) + { + var receivedHeaders = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var hostBuilder = new HostBuilder() + .ConfigureWebHost(webHostBuilder => + { + ConfigureKestrel(webHostBuilder, scheme); + webHostBuilder.ConfigureServices(AddTestLogging) + .Configure(app => app.Run(async context => + { + await context.Response.BodyWriter.FlushAsync(); + await receivedHeaders.Task.DefaultTimeout(); + context.Features.Get().Reset(8); // Cancel + })); + }); + using var host = await hostBuilder.StartAsync().DefaultTimeout(); + + var url = host.MakeUrl(scheme); + using var client = CreateClient(); + var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead).DefaultTimeout(); + response.EnsureSuccessStatusCode(); + receivedHeaders.SetResult(0); + var exception = await Assert.ThrowsAsync(() => response.Content.ReadAsStringAsync()).DefaultTimeout(); + Assert.Equal("The HTTP/2 server reset the stream. HTTP/2 error code 'CANCEL' (0x8).", exception?.InnerException?.InnerException.Message); + await host.StopAsync().DefaultTimeout(); + } + + [Theory] + [MemberData(nameof(SupportedSchemes))] + public async Task ServerReset_AfterEndStream_NoError(string scheme) + { + var hostBuilder = new HostBuilder() + .ConfigureWebHost(webHostBuilder => + { + ConfigureKestrel(webHostBuilder, scheme); + webHostBuilder.ConfigureServices(AddTestLogging) + .Configure(app => app.Run(async context => + { + await context.Response.WriteAsync("Hello World"); + await context.Response.CompleteAsync(); + context.Features.Get().Reset(8); // Cancel + })); + }); + using var host = await hostBuilder.StartAsync().DefaultTimeout(); + + var url = host.MakeUrl(scheme); + using var client = CreateClient(); + var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead).DefaultTimeout(); + response.EnsureSuccessStatusCode(); + var body = await response.Content.ReadAsStringAsync().DefaultTimeout(); + Assert.Equal("Hello World", body); + await host.StopAsync().DefaultTimeout(); + } + + [Theory] + [MemberData(nameof(SupportedSchemes))] + public async Task ServerReset_AfterTrailers_NoError(string scheme) + { + var hostBuilder = new HostBuilder() + .ConfigureWebHost(webHostBuilder => + { + ConfigureKestrel(webHostBuilder, scheme); + webHostBuilder.ConfigureServices(AddTestLogging) + .Configure(app => app.Run(async context => + { + context.Response.DeclareTrailer("TestTrailer"); + await context.Response.WriteAsync("Hello World"); + context.Response.AppendTrailer("TestTrailer", "TestValue"); + await context.Response.CompleteAsync(); + context.Features.Get().Reset(8); // Cancel + })); + }); + using var host = await hostBuilder.StartAsync().DefaultTimeout(); + + var url = host.MakeUrl(scheme); + using var client = CreateClient(); + var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead).DefaultTimeout(); + Assert.Equal(HttpVersion.Version20, response.Version); + Assert.Equal("TestTrailer", response.Headers.Trailer.Single()); + var responseBody = await response.Content.ReadAsStringAsync().DefaultTimeout(); + Assert.Equal("Hello World", responseBody); + // The response is buffered, we must already have the trailers. + Assert.Equal("TestValue", response.TrailingHeaders.GetValues("TestTrailer").Single()); + await host.StopAsync().DefaultTimeout(); + } + + [Theory] + [MemberData(nameof(SupportedSchemes))] + public async Task ServerReset_BeforeRequestBody_ClientBodyThrows(string scheme) + { + var clientEcho = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var serverReset = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var headersReceived = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var hostBuilder = new HostBuilder() + .ConfigureWebHost(webHostBuilder => + { + ConfigureKestrel(webHostBuilder, scheme); + webHostBuilder.ConfigureServices(AddTestLogging) + .Configure(app => app.Run(async context => + { + context.Response.ContentType = "text/plain"; + await context.Response.BodyWriter.FlushAsync(); + await headersReceived.Task.DefaultTimeout(); + context.Features.Get().Reset(8); // Cancel + serverReset.SetResult(0); + + try + { + using var streamReader = new StreamReader(context.Request.Body); + var read = await streamReader.ReadToEndAsync().DefaultTimeout(); + clientEcho.SetResult(read); + } + catch (Exception ex) + { + clientEcho.SetException(ex); + } + })); + }); + using var host = await hostBuilder.StartAsync().DefaultTimeout(); + + var url = host.MakeUrl(scheme); + + using var client = CreateClient(); + // The client doesn't flush the headers for requests with a body unless a continue is expected. + client.DefaultRequestHeaders.ExpectContinue = true; + + var streamingContent = new StreamingContent(); + var request = CreateRequestMessage(HttpMethod.Post, url, streamingContent); + using var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead).DefaultTimeout(); + headersReceived.SetResult(0); + + Assert.Equal(HttpVersion.Version20, response.Version); + + await serverReset.Task.DefaultTimeout(); + var responseEx = await Assert.ThrowsAsync(() => response.Content.ReadAsStringAsync().DefaultTimeout()); + Assert.Contains("The HTTP/2 server reset the stream. HTTP/2 error code 'CANCEL' (0x8)", responseEx.ToString()); + await Assert.ThrowsAsync(() => streamingContent.SendAsync("Hello World").DefaultTimeout()); + await Assert.ThrowsAnyAsync(() => clientEcho.Task.DefaultTimeout()); + + await host.StopAsync().DefaultTimeout(); + } + + [Theory] + [MemberData(nameof(SupportedSchemes))] + public async Task ServerReset_BeforeRequestBodyEnd_ClientBodyThrows(string scheme) + { + var clientEcho = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var serverReset = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var responseHeadersReceived = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var hostBuilder = new HostBuilder() + .ConfigureWebHost(webHostBuilder => + { + ConfigureKestrel(webHostBuilder, scheme); + webHostBuilder.ConfigureServices(AddTestLogging) + .Configure(app => app.Run(async context => + { + var count = await context.Request.Body.ReadAsync(new byte[11], 0, 11); + Assert.Equal(11, count); + + context.Response.ContentType = "text/plain"; + await context.Response.BodyWriter.FlushAsync(); + await responseHeadersReceived.Task.DefaultTimeout(); + context.Features.Get().Reset(8); // Cancel + serverReset.SetResult(0); + + try + { + using var streamReader = new StreamReader(context.Request.Body); + var read = await streamReader.ReadToEndAsync().DefaultTimeout(); + clientEcho.SetResult(read); + } + catch (Exception ex) + { + clientEcho.SetException(ex); + } + })); + }); + using var host = await hostBuilder.StartAsync().DefaultTimeout(); + + var url = host.MakeUrl(scheme); + + using var client = CreateClient(); + + var streamingContent = new StreamingContent(); + var request = CreateRequestMessage(HttpMethod.Post, url, streamingContent); + var requestTask = client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead).DefaultTimeout(); + await streamingContent.SendAsync("Hello World").DefaultTimeout(); + using var response = await requestTask; + responseHeadersReceived.SetResult(0); + + Assert.Equal(HttpVersion.Version20, response.Version); + + await serverReset.Task.DefaultTimeout(); + var responseEx = await Assert.ThrowsAsync(() => response.Content.ReadAsStringAsync().DefaultTimeout()); + Assert.Contains("The HTTP/2 server reset the stream. HTTP/2 error code 'CANCEL' (0x8)", responseEx.ToString()); + await Assert.ThrowsAsync(() => streamingContent.SendAsync("Hello World").DefaultTimeout()); + await Assert.ThrowsAnyAsync(() => clientEcho.Task.DefaultTimeout()); + + await host.StopAsync().DefaultTimeout(); + } + + [Theory] + [MemberData(nameof(SupportedSchemes))] + public async Task ClientReset_BeforeRequestData_ReadThrows(string scheme) + { + var requestReceived = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var serverResult = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var hostBuilder = new HostBuilder() + .ConfigureWebHost(webHostBuilder => + { + ConfigureKestrel(webHostBuilder, scheme); + webHostBuilder.ConfigureServices(AddTestLogging) + .Configure(app => app.Run(async context => + { + try + { + var readTask = context.Request.Body.ReadAsync(new byte[11], 0, 11); + requestReceived.SetResult(0); + var ex = await Assert.ThrowsAsync(() => readTask).DefaultTimeout(); + serverResult.SetResult(0); + } + catch (Exception ex) + { + serverResult.SetException(ex); + } + })); + }); + using var host = await hostBuilder.StartAsync().DefaultTimeout(); + + var url = host.MakeUrl(scheme); + + using var client = CreateClient(); + // The client doesn't flush the headers for requests with a body unless a continue is expected. + client.DefaultRequestHeaders.ExpectContinue = true; + + var streamingContent = new StreamingContent(); + var request = CreateRequestMessage(HttpMethod.Post, url, streamingContent); + var requestTask = client.SendAsync(request); + await requestReceived.Task.DefaultTimeout(); + await streamingContent.SendStarted.DefaultTimeout(); + streamingContent.Abort(); + await serverResult.Task.DefaultTimeout(); + await Assert.ThrowsAnyAsync(() => requestTask).DefaultTimeout(); + + await host.StopAsync().DefaultTimeout(); + } + + [Theory] + [MemberData(nameof(SupportedSchemes))] + public async Task ClientReset_BeforeRequestDataEnd_ReadThrows(string scheme) + { + var requestReceived = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var serverResult = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var hostBuilder = new HostBuilder() + .ConfigureWebHost(webHostBuilder => + { + ConfigureKestrel(webHostBuilder, scheme); + webHostBuilder.ConfigureServices(AddTestLogging) + .Configure(app => app.Run(async context => + { + try + { + await ReadStreamHelloWorld(context.Request.Body); + var readTask = context.Request.Body.ReadAsync(new byte[11], 0, 11); + requestReceived.SetResult(0); + var ex = await Assert.ThrowsAsync(() => readTask).DefaultTimeout(); + serverResult.SetResult(0); + } + catch (Exception ex) + { + serverResult.SetException(ex); + } + })); + }); + using var host = await hostBuilder.StartAsync().DefaultTimeout(); + + var url = host.MakeUrl(scheme); + + using var client = CreateClient(); + + var streamingContent = new StreamingContent(); + var request = CreateRequestMessage(HttpMethod.Post, url, streamingContent); + var requestTask = client.SendAsync(request); + await streamingContent.SendAsync("Hello World").DefaultTimeout(); + await requestReceived.Task.DefaultTimeout(); + streamingContent.Abort(); + await serverResult.Task.DefaultTimeout(); + await Assert.ThrowsAnyAsync(() => requestTask).DefaultTimeout(); + + await host.StopAsync().DefaultTimeout(); + } + + [Theory] + [MemberData(nameof(SupportedSchemes))] + public async Task ClientReset_BeforeResponse_ResponseSuppressed(string scheme) + { + var requestReceived = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var serverResult = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var hostBuilder = new HostBuilder() + .ConfigureWebHost(webHostBuilder => + { + ConfigureKestrel(webHostBuilder, scheme); + webHostBuilder.ConfigureServices(AddTestLogging) + .Configure(app => app.Run(async context => + { + try + { + context.RequestAborted.Register(() => serverResult.SetResult(0)); + requestReceived.SetResult(0); + await serverResult.Task.DefaultTimeout(); + await context.Response.WriteAsync("Hello World").DefaultTimeout(); + } + catch (Exception ex) + { + serverResult.SetException(ex); + } + })); + }); + using var host = await hostBuilder.StartAsync().DefaultTimeout(); + + var url = host.MakeUrl(scheme); + + using var client = CreateClient(); + + var requestCancellation = new CancellationTokenSource(); + var requestTask = client.GetAsync(url, requestCancellation.Token); + await requestReceived.Task.DefaultTimeout(); + requestCancellation.Cancel(); + await serverResult.Task.DefaultTimeout(); + await Assert.ThrowsAsync(() => requestTask).DefaultTimeout(); + + await host.StopAsync().DefaultTimeout(); + } + + [Theory] + [MemberData(nameof(SupportedSchemes))] + public async Task ClientReset_BeforeEndStream_WritesSuppressed(string scheme) + { + var serverResult = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var hostBuilder = new HostBuilder() + .ConfigureWebHost(webHostBuilder => + { + ConfigureKestrel(webHostBuilder, scheme); + webHostBuilder.ConfigureServices(AddTestLogging) + .Configure(app => app.Run(async context => + { + try + { + context.RequestAborted.Register(() => serverResult.SetResult(0)); + await context.Response.WriteAsync("Hello World").DefaultTimeout(); + await serverResult.Task.DefaultTimeout(); + await context.Response.WriteAsync("Hello World").DefaultTimeout(); + } + catch (Exception ex) + { + serverResult.SetException(ex); + } + })); + }); + using var host = await hostBuilder.StartAsync().DefaultTimeout(); + + var url = host.MakeUrl(scheme); + + using var client = CreateClient(); + + var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead).DefaultTimeout(); + var responseStream = await response.Content.ReadAsStreamAsync().DefaultTimeout(); + await ReadStreamHelloWorld(responseStream); + responseStream.Dispose(); // Sends reset + await serverResult.Task.DefaultTimeout(); + + await host.StopAsync().DefaultTimeout(); + } + + [Theory] + [MemberData(nameof(SupportedSchemes))] + public async Task ClientReset_BeforeTrailers_TrailersSuppressed(string scheme) + { + var serverResult = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var hostBuilder = new HostBuilder() + .ConfigureWebHost(webHostBuilder => + { + ConfigureKestrel(webHostBuilder, scheme); + webHostBuilder.ConfigureServices(AddTestLogging) + .Configure(app => app.Run(async context => + { + try + { + context.RequestAborted.Register(() => serverResult.SetResult(0)); + await context.Response.WriteAsync("Hello World").DefaultTimeout(); + await serverResult.Task.DefaultTimeout(); + context.Response.AppendTrailer("foo", "bar"); + await context.Response.CompleteAsync(); + } + catch (Exception ex) + { + serverResult.SetException(ex); + } + })); + }); + using var host = await hostBuilder.StartAsync().DefaultTimeout(); + + var url = host.MakeUrl(scheme); + + using var client = CreateClient(); + + var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead).DefaultTimeout(); + var responseStream = await response.Content.ReadAsStreamAsync().DefaultTimeout(); + await ReadStreamHelloWorld(responseStream); + responseStream.Dispose(); // Sends reset + await serverResult.Task.DefaultTimeout(); + + await host.StopAsync().DefaultTimeout(); + } + + [Theory] + [QuarantinedTest("https://github.com/dotnet/runtime/issues/860")] + [MemberData(nameof(SupportedSchemes))] + public async Task RequestHeaders_MultipleFrames_Accepted(string scheme) + { + var oneKbString = new string('a', 1024); + var serverResult = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var hostBuilder = new HostBuilder() + .ConfigureWebHost(webHostBuilder => + { + ConfigureKestrel(webHostBuilder, scheme); + webHostBuilder.ConfigureServices(AddTestLogging) + .Configure(app => app.Run(context => + { + try + { + for (var i = 0; i < 20; i++) + { + Assert.Equal(oneKbString + i, context.Request.Headers["header" + i]); + } + serverResult.SetResult(0); + } + catch (Exception ex) + { + serverResult.SetException(ex); + } + return Task.CompletedTask; + })); + }); + using var host = await hostBuilder.StartAsync().DefaultTimeout(); + + var url = host.MakeUrl(scheme); + + using var client = CreateClient(); + + var request = CreateRequestMessage(HttpMethod.Get, url, content: null); + // The default frame size limit is 16kb, and the total header size limit is 32kb. + for (var i = 0; i < 20; i++) + { + request.Headers.Add("header" + i, oneKbString + i); + } + request.Headers.Host = "localhost"; // The default Host header has a random port value which can cause the length to vary. + var requestTask = client.SendAsync(request); + var response = await requestTask.DefaultTimeout(); + await serverResult.Task.DefaultTimeout(); + response.EnsureSuccessStatusCode(); + + Assert.Single(TestSink.Writes.Where(context => context.Message.Contains("received HEADERS frame for stream ID 1 with length 16384 and flags END_STREAM"))); + Assert.Single(TestSink.Writes.Where(context => context.Message.Contains("received CONTINUATION frame for stream ID 1 with length 4390 and flags END_HEADERS"))); + + await host.StopAsync().DefaultTimeout(); + } + + [Theory] + [MemberData(nameof(SupportedSchemes))] + public async Task ResponseHeaders_MultipleFrames_Accepted(string scheme) + { + var oneKbString = new string('a', 1024); + var serverResult = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var hostBuilder = new HostBuilder() + .ConfigureWebHost(webHostBuilder => + { + ConfigureKestrel(webHostBuilder, scheme); + webHostBuilder.ConfigureServices(AddTestLogging) + .Configure(app => app.Run(context => + { + try + { + // The default frame size limit is 16kb, and the total header size limit is 64kb. + for (var i = 0; i < 59; i++) + { + context.Response.Headers.Append("header" + i, oneKbString + i); + } + serverResult.SetResult(0); + } + catch (Exception ex) + { + serverResult.SetException(ex); + } + return Task.CompletedTask; + })); + }); + using var host = await hostBuilder.StartAsync().DefaultTimeout(); + + var url = host.MakeUrl(scheme); + + using var client = CreateClient(); + var response = await client.GetAsync(url).DefaultTimeout(); + await serverResult.Task.DefaultTimeout(); + response.EnsureSuccessStatusCode(); + for (var i = 0; i < 59; i++) + { + Assert.Equal(oneKbString + i, response.Headers.GetValues("header" + i).Single()); + } + + Assert.Single(TestSink.Writes.Where(context => context.Message.Contains("sending HEADERS frame for stream ID 1 with length 15612 and flags END_STREAM"))); + Assert.Equal(2, TestSink.Writes.Where(context => context.Message.Contains("sending CONTINUATION frame for stream ID 1 with length 15585 and flags NONE")).Count()); + Assert.Single(TestSink.Writes.Where(context => context.Message.Contains("sending CONTINUATION frame for stream ID 1 with length 14546 and flags END_HEADERS"))); + + await host.StopAsync().DefaultTimeout(); + } + + [Theory] + // Expect this to change when the client implements dynamic request header compression. + // Will the client send the first headers before receiving our settings frame? + // We'll probably need to ensure the settings changes are ack'd before enforcing them. + [MemberData(nameof(SupportedSchemes))] + public async Task Settings_HeaderTableSize_CanBeReduced_Server(string scheme) + { + var oneKbString = new string('a', 1024); + var serverResult = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var hostBuilder = new HostBuilder() + .ConfigureWebHost(webHostBuilder => + { + ConfigureKestrel(webHostBuilder, scheme); + webHostBuilder.ConfigureKestrel(options => + { + // Must be larger than 0, should disable header compression + options.Limits.Http2.HeaderTableSize = 1; + }); + webHostBuilder.ConfigureServices(AddTestLogging) + .Configure(app => app.Run(context => + { + try + { + for (var i = 0; i < 14; i++) + { + Assert.Equal(oneKbString + i, context.Request.Headers["header" + i]); + } + serverResult.SetResult(0); + } + catch (Exception ex) + { + serverResult.SetException(ex); + } + return Task.CompletedTask; + })); + }); + using var host = await hostBuilder.StartAsync().DefaultTimeout(); + + var url = host.MakeUrl(scheme); + + using var client = CreateClient(); + + var request = CreateRequestMessage(HttpMethod.Get, url, content: null); + // The default frame size limit is 16kb, and the total header size limit is 32kb. + for (var i = 0; i < 14; i++) + { + request.Headers.Add("header" + i, oneKbString + i); + } + request.Headers.Host = "localhost"; // The default Host has a random port value that causes the length to very. + var requestTask = client.SendAsync(request); + var response = await requestTask.DefaultTimeout(); + await serverResult.Task.DefaultTimeout(); + response.EnsureSuccessStatusCode(); + + Assert.Single(TestSink.Writes.Where(context + => context.Message.Contains("received HEADERS frame for stream ID 1 with length 14540 and flags END_STREAM, END_HEADERS"))); + + await host.StopAsync().DefaultTimeout(); + } + + // Settings_HeaderTableSize_CanBeReduced_Client - The client uses the default 4k HPACK dynamic table size and it cannot be changed. + // Nor does Kestrel yet support sending dynamic table updates, so there's nothing to test here. https://github.com/dotnet/aspnetcore/issues/4715 + + [Theory] + [QuarantinedTest] + [MemberData(nameof(SupportedSchemes))] + public async Task Settings_MaxConcurrentStreamsGet_Server(string scheme) + { + var sync = new SemaphoreSlim(5); + var requestCount = 0; + var requestBlock = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var hostBuilder = new HostBuilder() + .ConfigureWebHost(webHostBuilder => + { + ConfigureKestrel(webHostBuilder, scheme); + webHostBuilder.ConfigureKestrel(options => + { + options.Limits.Http2.MaxStreamsPerConnection = 5; + }); + webHostBuilder.ConfigureServices(AddTestLogging) + .Configure(app => app.Run(async context => + { + // The stream limit should mean we never hit the semaphore limit. + Assert.True(sync.Wait(0)); + var count = Interlocked.Increment(ref requestCount); + + if (count == 5) + { + requestBlock.TrySetResult(0); + } + else + { + await requestBlock.Task.DefaultTimeout(); + } + + sync.Release(); + })); + }); + using var host = await hostBuilder.StartAsync().DefaultTimeout(); + + var url = host.MakeUrl(scheme); + + using var client = CreateClient(); + + var tasks = new List>(10); + for (var i = 0; i < 10; i++) + { + var requestTask = client.GetAsync(url).DefaultTimeout(); + tasks.Add(requestTask); + } + + var responses = await Task.WhenAll(tasks.ToList()).DefaultTimeout(); + foreach (var response in responses) + { + response.EnsureSuccessStatusCode(); + } + + // SKIP: https://github.com/dotnet/aspnetcore/issues/17842 + // The client initially issues all 10 requests before receiving the settings, has 5 refused (after receiving the settings), + // waits for the first 5 to finish, retries the refused 5, and in the end each request completes successfully despite the logged errors. + // Assert.Empty(TestSink.Writes.Where(context => context.Message.Contains("HTTP/2 stream error"))); + + await host.StopAsync().DefaultTimeout(); + } + + [Theory] + [QuarantinedTest] + [MemberData(nameof(SupportedSchemes))] + public async Task Settings_MaxConcurrentStreamsPost_Server(string scheme) + { + var sync = new SemaphoreSlim(5); + var requestCount = 0; + var requestBlock = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var hostBuilder = new HostBuilder() + .ConfigureWebHost(webHostBuilder => + { + ConfigureKestrel(webHostBuilder, scheme); + webHostBuilder.ConfigureKestrel(options => + { + options.Limits.Http2.MaxStreamsPerConnection = 5; + }); + webHostBuilder.ConfigureServices(AddTestLogging) + .Configure(app => app.Run(async context => + { + // The stream limit should mean we never hit the semaphore limit. + Assert.True(sync.Wait(0)); + var count = Interlocked.Increment(ref requestCount); + + if (count == 5) + { + requestBlock.TrySetResult(0); + } + else + { + await requestBlock.Task.DefaultTimeout(); + } + + sync.Release(); + })); + }); + using var host = await hostBuilder.StartAsync().DefaultTimeout(); + + var url = host.MakeUrl(scheme); + + using var client = CreateClient(); + + var tasks = new List>(10); + for (var i = 0; i < 10; i++) + { + var requestTask = client.PostAsync(url, new StringContent("Hello World")).DefaultTimeout(); + tasks.Add(requestTask); + } + + var responses = await Task.WhenAll(tasks.ToList()).DefaultTimeout(); + foreach (var response in responses) + { + response.EnsureSuccessStatusCode(); + } + + // SKIP: https://github.com/dotnet/aspnetcore/issues/17842 + // The client initially issues all 10 requests before receiving the settings, has 5 refused (after receiving the settings), + // waits for the first 5 to finish, retries the refused 5, and in the end each request completes successfully despite the logged errors. + // Assert.Empty(TestSink.Writes.Where(context => context.Message.Contains("HTTP/2 stream error"))); + + await host.StopAsync().DefaultTimeout(); + } + + // Settings_MaxConcurrentStreams_Client - Neither client or server support Push, nothing to test in this direction. + + [Theory] + [MemberData(nameof(SupportedSchemes))] + public async Task Settings_MaxFrameSize_Larger_Server(string scheme) + { + var hostBuilder = new HostBuilder() + .ConfigureWebHost(webHostBuilder => + { + ConfigureKestrel(webHostBuilder, scheme); + webHostBuilder.ConfigureKestrel(options => options.Limits.Http2.MaxFrameSize = 1024 * 20); // The default is 16kb + webHostBuilder.ConfigureServices(AddTestLogging) + .Configure(app => app.Run(context => context.Response.WriteAsync("Hello World"))); + }); + using var host = await hostBuilder.StartAsync().DefaultTimeout(); + + var url = host.MakeUrl(scheme); + using var client = CreateClient(); + // Send an initial request to ensure the settings get synced before the real test. + var responseBody = await client.GetStringAsync(url).DefaultTimeout(); + Assert.Equal("Hello World", responseBody); + + var response = await client.PostAsync(url, new ByteArrayContent(new byte[1024 * 18])).DefaultTimeout(); + response.EnsureSuccessStatusCode(); + Assert.Equal("Hello World", await response.Content.ReadAsStringAsync()); + + // SKIP: The client does not take advantage of a larger allowed frame size. + // https://github.com/dotnet/runtime/blob/48a78bfa13e9c710851690621fc2c0fe1637802c/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs#L483-L488 + // Assert.Single(TestSink.Writes.Where(context => context.Message.Contains("received DATA frame for stream ID 1 with length 18432 and flags NONE"))); + + await host.StopAsync().DefaultTimeout(); + } + + // Settings_MaxFrameSize_Larger_Client - Not configurable + + [Theory] + [QuarantinedTest("https://github.com/dotnet/runtime/issues/860")] + [MemberData(nameof(SupportedSchemes))] + public async Task Settings_MaxHeaderListSize_Server(string scheme) + { + var oneKbString = new string('a', 1024); + var hostBuilder = new HostBuilder() + .ConfigureWebHost(webHostBuilder => + { + ConfigureKestrel(webHostBuilder, scheme); + webHostBuilder.ConfigureServices(AddTestLogging) + .Configure(app => app.Run(context => throw new NotImplementedException())); + }); + using var host = await hostBuilder.StartAsync().DefaultTimeout(); + + var url = host.MakeUrl(scheme); + + using var client = CreateClient(); + // There's no point in waiting for the settings to sync, the client doesn't check the header list size setting. + // https://github.com/dotnet/runtime/blob/48a78bfa13e9c710851690621fc2c0fe1637802c/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs#L467-L494 + + var request = CreateRequestMessage(HttpMethod.Get, url, content: null); + // The default size limit is 32kb. + for (var i = 0; i < 33; i++) + { + request.Headers.Add("header" + i, oneKbString + i); + } + // Kestrel closes the connection rather than sending the recommended 431 response. https://github.com/dotnet/aspnetcore/issues/17861 + await Assert.ThrowsAsync(() => client.SendAsync(request)).DefaultTimeout(); + + await host.StopAsync().DefaultTimeout(); + } + + [Theory] + [MemberData(nameof(SupportedSchemes))] + public async Task Settings_MaxHeaderListSize_Client(string scheme) + { + var oneKbString = new string('a', 1024); + var hostBuilder = new HostBuilder() + .ConfigureWebHost(webHostBuilder => + { + ConfigureKestrel(webHostBuilder, scheme); + webHostBuilder.ConfigureServices(AddTestLogging) + .Configure(app => app.Run(context => + { + // The total header size limit is 64kb. + for (var i = 0; i < 65; i++) + { + context.Response.Headers.Append("header" + i, oneKbString + i); + } + return Task.CompletedTask; + })); + }); + using var host = await hostBuilder.StartAsync().DefaultTimeout(); + + var url = host.MakeUrl(scheme); + + using var client = CreateClient(); + var ex = await Assert.ThrowsAsync(() => client.GetAsync(url)).DefaultTimeout(); + Assert.Equal("The HTTP response headers length exceeded the set limit of 65536 bytes.", ex.InnerException?.InnerException?.Message); + + await host.StopAsync().DefaultTimeout(); + } + + // Settings_InitialWindowSize_Lower_Server - Kestrel does not support lowering the InitialStreamWindowSize below the spec default 64kb. + // Settings_InitialWindowSize_Lower_Client - Not configurable. + + [Theory] + [MemberData(nameof(SupportedSchemes))] + public async Task Settings_InitialWindowSize_Server(string scheme) + { + var requestFinished = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var hostBuilder = new HostBuilder() + .ConfigureWebHost(webHostBuilder => + { + ConfigureKestrel(webHostBuilder, scheme); + webHostBuilder.ConfigureServices(AddTestLogging) + .Configure(app => app.Run(async context => + { + await requestFinished.Task.DefaultTimeout(); + + await context.Response.WriteAsync("Hello World"); + })); + }); + using var host = await hostBuilder.StartAsync().DefaultTimeout(); + + var url = host.MakeUrl(scheme); + using var client = CreateClient(); + + var streamingContent = new StreamingContent(); + var requestTask = client.PostAsync(url, streamingContent); + + // The spec default window is 64kb-1 and Kestrel's default is 96kb. + // We should be able to send the entire request body without getting blocked by flow control. + var oneKbString = new string('a', 1024); + for (var i = 0; i < 96; i++) + { + await streamingContent.SendAsync(oneKbString).DefaultTimeout(); + } + streamingContent.Complete(); + requestFinished.SetResult(0); + + var response = await requestTask.DefaultTimeout(); + response.EnsureSuccessStatusCode(); + Assert.Equal("Hello World", await response.Content.ReadAsStringAsync()); + + await host.StopAsync().DefaultTimeout(); + } + + [Theory] + [MemberData(nameof(SupportedSchemes))] + public async Task Settings_InitialWindowSize_Client(string scheme) + { + var responseFinished = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var hostBuilder = new HostBuilder() + .ConfigureWebHost(webHostBuilder => + { + ConfigureKestrel(webHostBuilder, scheme); + webHostBuilder.ConfigureServices(AddTestLogging) + .Configure(app => app.Run(async context => + { + // The spec default window is 64kb - 1. + // We should be able to send the entire response body without getting blocked by flow control. + var oneKbString = new string('a', 1024); + for (var i = 0; i < 63; i++) + { + await context.Response.WriteAsync(oneKbString).DefaultTimeout(); + } + await context.Response.WriteAsync(new string('a', 1023)).DefaultTimeout(); + await context.Response.CompleteAsync().DefaultTimeout(); + responseFinished.SetResult(0); + })); + }); + using var host = await hostBuilder.StartAsync().DefaultTimeout(); + + var url = host.MakeUrl(scheme); + using var client = CreateClient(); + + var requestTask = client.GetStreamAsync(url); + await responseFinished.Task.DefaultTimeout(); + var response = await requestTask.DefaultTimeout(); + + await host.StopAsync().DefaultTimeout(); + } + + [Theory] + [MemberData(nameof(SupportedSchemes))] + public async Task ConnectionWindowSize_Server(string scheme) + { + var requestFinished = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var hostBuilder = new HostBuilder() + .ConfigureWebHost(webHostBuilder => + { + ConfigureKestrel(webHostBuilder, scheme); + webHostBuilder.ConfigureServices(AddTestLogging) + .Configure(app => app.Run(async context => + { + await requestFinished.Task.DefaultTimeout(); + var buffer = new byte[1024]; + var read = 0; + do + { + read = await context.Request.Body.ReadAsync(buffer, 0, buffer.Length).DefaultTimeout(); + } while (read > 0); + + await context.Response.WriteAsync("Hello World"); + })); + }); + using var host = await hostBuilder.StartAsync().DefaultTimeout(); + + var url = host.MakeUrl(scheme); + using var client = CreateClient(); + + var streamingContent0 = new StreamingContent(); + var streamingContent1 = new StreamingContent(); + var requestTask0 = client.PostAsync(url, streamingContent0); + var requestTask1 = client.PostAsync(url, streamingContent1); + + // The spec default connection window is 64kb-1 and Kestrel's default is 128kb. + // We should be able to send two 64kb requests without getting blocked by flow control. + var oneKbString = new string('a', 1024); + for (var i = 0; i < 64; i++) + { + await streamingContent0.SendAsync(oneKbString).DefaultTimeout(); + await streamingContent1.SendAsync(oneKbString).DefaultTimeout(); + } + streamingContent0.Complete(); + streamingContent1.Complete(); + requestFinished.SetResult(0); + + var response0 = await requestTask0.DefaultTimeout(); + var response1 = await requestTask0.DefaultTimeout(); + response0.EnsureSuccessStatusCode(); + response1.EnsureSuccessStatusCode(); + Assert.Equal("Hello World", await response0.Content.ReadAsStringAsync()); + Assert.Equal("Hello World", await response1.Content.ReadAsStringAsync()); + + await host.StopAsync().DefaultTimeout(); + } + + // ConnectionWindowSize_Client - impractical + // The spec default connection window is 64kb - 1 but the client default is 64Mb (not configurable). + // The client restricts streams to 64kb - 1 so we would need to issue 64 * 1024 requests to stress the connection window limit. + + [Theory] + [MemberData(nameof(SupportedSchemes))] + public async Task UnicodeRequestHost(string scheme) + { + var hostBuilder = new HostBuilder() + .ConfigureWebHost(webHostBuilder => + { + ConfigureKestrel(webHostBuilder, scheme); + webHostBuilder.ConfigureServices(AddTestLogging) + .Configure(app => app.Run(context => + { + Assert.Equal("點.看", context.Request.Host.Host); + return context.Response.WriteAsync(context.Request.Host.Host); + })); + }); + using var host = await hostBuilder.StartAsync().DefaultTimeout(); + + var url = host.MakeUrl(scheme); + using var client = CreateClient(); + var request = CreateRequestMessage(HttpMethod.Get, url, content: null); + request.Headers.Host = "xn--md7a.xn--c1y"; // Punycode + var response = await client.SendAsync(request).DefaultTimeout(); + response.EnsureSuccessStatusCode(); + Assert.Equal("點.看", await response.Content.ReadAsStringAsync()); + await host.StopAsync().DefaultTimeout(); + } + + [Theory] + [MemberData(nameof(SupportedSchemes))] + public async Task UrlEncoding(string scheme) + { + var hostBuilder = new HostBuilder() + .ConfigureWebHost(webHostBuilder => + { + ConfigureKestrel(webHostBuilder, scheme); + webHostBuilder.ConfigureServices(AddTestLogging) + .Configure(app => app.Run(context => context.Response.WriteAsync(context.Request.Path.Value))); + }); + using var host = await hostBuilder.StartAsync().DefaultTimeout(); + + var url = host.MakeUrl(scheme); + using var client = CreateClient(); + // Skipped controls, '?' and '#'. + var response = await client.GetAsync(url + " !\"$%&'()*++,-./0123456789:;<>=@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`{|}~點看").DefaultTimeout(); + response.EnsureSuccessStatusCode(); + Assert.Equal("/ !\"$%&'()*++,-./0123456789:;<>=@ABCDEFGHIJKLMNOPQRSTUVWXYZ[/]^_`{|}~點看", await response.Content.ReadAsStringAsync()); + await host.StopAsync().DefaultTimeout(); } private static HttpClient CreateClient() @@ -552,8 +1636,16 @@ namespace Interop.FunctionalTests private static async Task ReadStreamHelloWorld(Stream stream) { var responseBuffer = new byte["Hello World".Length]; - var read = await stream.ReadAsync(responseBuffer, 0, responseBuffer.Length).DefaultTimeout(); - Assert.Equal(responseBuffer.Length, read); + var totalRead = 0; + do + { + var read = await stream.ReadAsync(responseBuffer, totalRead, responseBuffer.Length - totalRead).DefaultTimeout(); + totalRead += read; + if (read == 0) + { + throw new InvalidOperationException("Unexpected end of stream"); + } + } while (totalRead < responseBuffer.Length); Assert.Equal("Hello World", Encoding.UTF8.GetString(responseBuffer)); } } diff --git a/src/Servers/Kestrel/test/Interop.FunctionalTests/Interop.FunctionalTests.csproj b/src/Servers/Kestrel/test/Interop.FunctionalTests/Interop.FunctionalTests.csproj index 3a1b3e7670..9fff0431d9 100644 --- a/src/Servers/Kestrel/test/Interop.FunctionalTests/Interop.FunctionalTests.csproj +++ b/src/Servers/Kestrel/test/Interop.FunctionalTests/Interop.FunctionalTests.csproj @@ -6,8 +6,7 @@ Interop.FunctionalTests false - - false + false diff --git a/src/Servers/Kestrel/test/Interop.FunctionalTests/Utilities.cs b/src/Servers/Kestrel/test/Interop.FunctionalTests/Utilities.cs index a8223118a0..db41d1e1b4 100644 --- a/src/Servers/Kestrel/test/Interop.FunctionalTests/Utilities.cs +++ b/src/Servers/Kestrel/test/Interop.FunctionalTests/Utilities.cs @@ -14,7 +14,7 @@ namespace Interop.FunctionalTests // "Missing SslStream ALPN support: https://github.com/dotnet/corefx/issues/30492" && new OSSkipConditionAttribute(OperatingSystems.MacOSX).IsMet // Debian 8 uses OpenSSL 1.0.1 which does not support ALPN - && new SkipOnHelixAttribute("https://github.com/aspnet/AspNetCore/issues/10428") { Queues = "Debian.8.Amd64.Open" }.IsMet; + && new SkipOnHelixAttribute("https://github.com/dotnet/aspnetcore/issues/10428") { Queues = "Debian.8.Amd64;Debian.8.Amd64.Open" }.IsMet; } } } diff --git a/src/Servers/Kestrel/test/Libuv.BindTests/Libuv.BindTests.csproj b/src/Servers/Kestrel/test/Libuv.BindTests/Libuv.BindTests.csproj index 22cabcb14a..39871045f6 100644 --- a/src/Servers/Kestrel/test/Libuv.BindTests/Libuv.BindTests.csproj +++ b/src/Servers/Kestrel/test/Libuv.BindTests/Libuv.BindTests.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/Servers/Kestrel/test/Sockets.BindTests/SocketTransportFactoryTests.cs b/src/Servers/Kestrel/test/Sockets.BindTests/SocketTransportFactoryTests.cs new file mode 100644 index 0000000000..2ff92f497f --- /dev/null +++ b/src/Servers/Kestrel/test/Sockets.BindTests/SocketTransportFactoryTests.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Connections; +using Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Moq; +using Xunit; + +namespace Sockets.BindTests +{ + public class SocketTransportFactoryTests + { + [Fact] + public async Task ThrowsNotSupportedExceptionWhenBindingToFileHandleEndPoint() + { + var socketTransportFactory = new SocketTransportFactory(Options.Create(new SocketTransportOptions()), Mock.Of()); + await Assert.ThrowsAsync(async () => await socketTransportFactory.BindAsync(new FileHandleEndPoint(0, FileHandleType.Auto))); + } + } +} + diff --git a/src/Servers/Kestrel/test/SystemdActivation/docker-entrypoint.sh b/src/Servers/Kestrel/test/SystemdActivation/docker-entrypoint.sh old mode 100644 new mode 100755 diff --git a/src/Servers/Kestrel/test/SystemdActivation/docker.sh b/src/Servers/Kestrel/test/SystemdActivation/docker.sh old mode 100644 new mode 100755 diff --git a/src/Servers/Kestrel/tools/CodeGenerator/CodeGenerator.csproj b/src/Servers/Kestrel/tools/CodeGenerator/CodeGenerator.csproj index d0431dbb29..b79bf13aa7 100644 --- a/src/Servers/Kestrel/tools/CodeGenerator/CodeGenerator.csproj +++ b/src/Servers/Kestrel/tools/CodeGenerator/CodeGenerator.csproj @@ -1,4 +1,4 @@ - + $(DefaultNetCoreTargetFramework) @@ -18,7 +18,7 @@ $(MSBuildThisFileDirectory)..\..\ - Core/src/Internal/Http/HttpHeaders.Generated.cs Core/src/Internal/Http/HttpProtocol.Generated.cs Core/src/Internal/Infrastructure/HttpUtilities.Generated.cs shared/TransportConnection.Generated.cs + Core/src/Internal/Http/HttpHeaders.Generated.cs Core/src/Internal/Http/HttpProtocol.Generated.cs Core/src/Internal/Infrastructure/HttpUtilities.Generated.cs Core/src/Internal/Http2/Http2Connection.Generated.cs shared/TransportMultiplexedConnection.Generated.cs shared/TransportConnection.Generated.cs diff --git a/src/Servers/Kestrel/tools/CodeGenerator/FeatureCollectionGenerator.cs b/src/Servers/Kestrel/tools/CodeGenerator/FeatureCollectionGenerator.cs index c9a0ea251d..0e91c137d4 100644 --- a/src/Servers/Kestrel/tools/CodeGenerator/FeatureCollectionGenerator.cs +++ b/src/Servers/Kestrel/tools/CodeGenerator/FeatureCollectionGenerator.cs @@ -31,8 +31,6 @@ namespace {namespaceName} {{ internal partial class {className} : IFeatureCollection {{{Each(features, feature => $@" - private static readonly Type {feature.Name}Type = typeof({feature.Name});")} -{Each(features, feature => $@" private object _current{feature.Name};")} private int _featureRevision; @@ -98,7 +96,7 @@ namespace {namespaceName} get {{ object feature = null;{Each(features, feature => $@" - {(feature.Index != 0 ? "else " : "")}if (key == {feature.Name}Type) + {(feature.Index != 0 ? "else " : "")}if (key == typeof({feature.Name})) {{ feature = _current{feature.Name}; }}")} @@ -114,7 +112,7 @@ namespace {namespaceName} {{ _featureRevision++; {Each(features, feature => $@" - {(feature.Index != 0 ? "else " : "")}if (key == {feature.Name}Type) + {(feature.Index != 0 ? "else " : "")}if (key == typeof({feature.Name})) {{ _current{feature.Name} = value; }}")} @@ -162,7 +160,7 @@ namespace {namespaceName} {{{Each(features, feature => $@" if (_current{feature.Name} != null) {{ - yield return new KeyValuePair({feature.Name}Type, _current{feature.Name}); + yield return new KeyValuePair(typeof({feature.Name}), _current{feature.Name}); }}")} if (MaybeExtra != null) diff --git a/src/Servers/Kestrel/tools/CodeGenerator/Http2Connection.cs b/src/Servers/Kestrel/tools/CodeGenerator/Http2Connection.cs new file mode 100644 index 0000000000..6cfabb9962 --- /dev/null +++ b/src/Servers/Kestrel/tools/CodeGenerator/Http2Connection.cs @@ -0,0 +1,33 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using Microsoft.Net.Http.Headers; + +namespace CodeGenerator +{ + public static class Http2Connection + { + public static string GenerateFile() + { + return ReadOnlySpanStaticDataGenerator.GenerateFile( + namespaceName: "Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2", + className: "Http2Connection", + allProperties: GetStrings()); + } + + private static IEnumerable<(string Name, string Value)> GetStrings() + { + yield return ("ClientPreface", "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"); + yield return ("Authority", HeaderNames.Authority); + yield return ("Method", HeaderNames.Method); + yield return ("Path", HeaderNames.Path); + yield return ("Scheme", HeaderNames.Scheme); + yield return ("Status", HeaderNames.Status); + yield return ("Connection", "connection"); + yield return ("Te", "te"); + yield return ("Trailers", "trailers"); + yield return ("Connect", "CONNECT"); + } + } +} diff --git a/src/Servers/Kestrel/tools/CodeGenerator/Program.cs b/src/Servers/Kestrel/tools/CodeGenerator/Program.cs index eb687fccac..48b1fa605f 100644 --- a/src/Servers/Kestrel/tools/CodeGenerator/Program.cs +++ b/src/Servers/Kestrel/tools/CodeGenerator/Program.cs @@ -26,12 +26,22 @@ namespace CodeGenerator return 1; } else if (args.Length < 4) + { + Console.Error.WriteLine("Missing path to Http2Connection.Generated.cs"); + return 1; + } + else if (args.Length < 5) + { + Console.Error.WriteLine("Missing path to TransportMultiplexedConnection.Generated.cs"); + return 1; + } + else if (args.Length < 6) { Console.Error.WriteLine("Missing path to TransportConnection.Generated.cs"); return 1; } - Run(args[0], args[1], args[2], args[3]); + Run(args[0], args[1], args[2], args[3], args[4], args[5]); return 0; } @@ -40,35 +50,37 @@ namespace CodeGenerator string knownHeadersPath, string httpProtocolFeatureCollectionPath, string httpUtilitiesPath, + string http2ConnectionPath, + string transportMultiplexedConnectionFeatureCollectionPath, string transportConnectionFeatureCollectionPath) { var knownHeadersContent = KnownHeaders.GeneratedFile(); var httpProtocolFeatureCollectionContent = HttpProtocolFeatureCollection.GenerateFile(); var httpUtilitiesContent = HttpUtilities.HttpUtilities.GeneratedFile(); + var transportMultiplexedConnectionFeatureCollectionContent = TransportMultiplexedConnectionFeatureCollection.GenerateFile(); var transportConnectionFeatureCollectionContent = TransportConnectionFeatureCollection.GenerateFile(); + var http2ConnectionContent = Http2Connection.GenerateFile(); - var existingKnownHeaders = File.Exists(knownHeadersPath) ? File.ReadAllText(knownHeadersPath) : ""; - if (!string.Equals(knownHeadersContent, existingKnownHeaders)) + UpdateFile(knownHeadersPath, knownHeadersContent); + UpdateFile(httpProtocolFeatureCollectionPath, httpProtocolFeatureCollectionContent); + UpdateFile(httpUtilitiesPath, httpUtilitiesContent); + UpdateFile(http2ConnectionPath, http2ConnectionContent); + UpdateFile(transportMultiplexedConnectionFeatureCollectionPath, transportMultiplexedConnectionFeatureCollectionContent); + UpdateFile(transportConnectionFeatureCollectionPath, transportConnectionFeatureCollectionContent); + } + + public static void UpdateFile(string path, string content) + { + var existingContent = File.Exists(path) ? File.ReadAllText(path) : ""; + if (!string.Equals(content, existingContent)) { - File.WriteAllText(knownHeadersPath, knownHeadersContent); + File.WriteAllText(path, content); } - var existingHttpProtocolFeatureCollection = File.Exists(httpProtocolFeatureCollectionPath) ? File.ReadAllText(httpProtocolFeatureCollectionPath) : ""; - if (!string.Equals(httpProtocolFeatureCollectionContent, existingHttpProtocolFeatureCollection)) + var existingHttp2Connection = File.Exists(path) ? File.ReadAllText(path) : ""; + if (!string.Equals(content, existingHttp2Connection)) { - File.WriteAllText(httpProtocolFeatureCollectionPath, httpProtocolFeatureCollectionContent); - } - - var existingHttpUtilities = File.Exists(httpUtilitiesPath) ? File.ReadAllText(httpUtilitiesPath) : ""; - if (!string.Equals(httpUtilitiesContent, existingHttpUtilities)) - { - File.WriteAllText(httpUtilitiesPath, httpUtilitiesContent); - } - - var existingTransportConnectionFeatureCollection = File.Exists(transportConnectionFeatureCollectionPath) ? File.ReadAllText(transportConnectionFeatureCollectionPath) : ""; - if (!string.Equals(transportConnectionFeatureCollectionContent, existingTransportConnectionFeatureCollection)) - { - File.WriteAllText(transportConnectionFeatureCollectionPath, transportConnectionFeatureCollectionContent); + File.WriteAllText(path, content); } } } diff --git a/src/Servers/Kestrel/tools/CodeGenerator/ReadOnlySpanStaticDataGenerator.cs b/src/Servers/Kestrel/tools/CodeGenerator/ReadOnlySpanStaticDataGenerator.cs new file mode 100644 index 0000000000..1689292eb5 --- /dev/null +++ b/src/Servers/Kestrel/tools/CodeGenerator/ReadOnlySpanStaticDataGenerator.cs @@ -0,0 +1,78 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace CodeGenerator +{ + public static class ReadOnlySpanStaticDataGenerator + { + public static string GenerateFile(string namespaceName, string className, IEnumerable<(string Name, string Value)> allProperties) + { + var properties = allProperties.Select((p, index) => new Property + { + Data = p, + Index = index + }); + + return $@"// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; + +namespace {namespaceName} +{{ + internal partial class {className} + {{ + // This uses C# compiler's ability to refer to static data directly. For more information see https://vcsjones.dev/2019/02/01/csharp-readonly-span-bytes-static + {Each(properties, p => $@" + private static ReadOnlySpan {p.Data.Name}Bytes => new byte[{p.Data.Value.Length}] {{ {GetDataAsBytes(p.Data.Value)} }};")} + }} +}} +"; + } + + private static string Each(IEnumerable values, Func formatter) + { + return values.Any() ? values.Select(formatter).Aggregate((a, b) => a + b) : ""; + } + + private static string GetDataAsBytes(string value) + { + var stringBuilder = new StringBuilder(); + + for (var i = 0; i < value.Length; ++i) + { + var c = value[i]; + if (c == '\n') + { + stringBuilder.Append("(byte)'\\n'"); + } + else if (c == '\r') + { + stringBuilder.Append("(byte)'\\r'"); + } + else + { + stringBuilder.AppendFormat("(byte)'{0}'", c); + } + + if (i < value.Length - 1) + { + stringBuilder.Append(", "); + } + } + + return stringBuilder.ToString(); + } + + private class Property + { + public (string Name, string Value) Data; + public int Index; + } + } +} diff --git a/src/Servers/Kestrel/tools/CodeGenerator/TransportMultiplexedConnectionFeatureCollection.cs b/src/Servers/Kestrel/tools/CodeGenerator/TransportMultiplexedConnectionFeatureCollection.cs new file mode 100644 index 0000000000..8c0ad9d3ff --- /dev/null +++ b/src/Servers/Kestrel/tools/CodeGenerator/TransportMultiplexedConnectionFeatureCollection.cs @@ -0,0 +1,34 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace CodeGenerator +{ + public class TransportMultiplexedConnectionFeatureCollection + { + public static string GenerateFile() + { + // NOTE: This list MUST always match the set of feature interfaces implemented by TransportConnectionBase. + // See also: shared/TransportConnectionBase.FeatureCollection.cs + var features = new[] + { + "IConnectionIdFeature", + "IConnectionTransportFeature", + "IConnectionItemsFeature", + "IMemoryPoolFeature", + "IConnectionLifetimeFeature" + }; + + var usings = $@" +using Microsoft.AspNetCore.Connections.Features; +using Microsoft.AspNetCore.Http.Features;"; + + return FeatureCollectionGenerator.GenerateFile( + namespaceName: "Microsoft.AspNetCore.Connections", + className: "TransportMultiplexedConnection", + allFeatures: features, + implementedFeatures: features, + extraUsings: usings, + fallbackFeatures: null); + } + } +} diff --git a/src/Servers/test/FunctionalTests/HelloWorldTest.cs b/src/Servers/test/FunctionalTests/HelloWorldTest.cs index e047288908..cf20b9d617 100644 --- a/src/Servers/test/FunctionalTests/HelloWorldTest.cs +++ b/src/Servers/test/FunctionalTests/HelloWorldTest.cs @@ -21,7 +21,7 @@ namespace ServerComparison.FunctionalTests public static TestMatrix TestVariants => TestMatrix.ForServers(ServerType.IISExpress, ServerType.Kestrel, ServerType.Nginx, ServerType.HttpSys) - .WithTfms(Tfm.NetCoreApp31) + .WithTfms(Tfm.NetCoreApp50) .WithApplicationTypes(ApplicationType.Portable) .WithAllHostingModels() .WithAllArchitectures(); diff --git a/src/Servers/test/FunctionalTests/NtlmAuthenticationTest.cs b/src/Servers/test/FunctionalTests/NtlmAuthenticationTest.cs index 330586eafa..75ef46207a 100644 --- a/src/Servers/test/FunctionalTests/NtlmAuthenticationTest.cs +++ b/src/Servers/test/FunctionalTests/NtlmAuthenticationTest.cs @@ -22,7 +22,7 @@ namespace ServerComparison.FunctionalTests public static TestMatrix TestVariants => TestMatrix.ForServers(ServerType.IISExpress, ServerType.HttpSys, ServerType.Kestrel) - .WithTfms(Tfm.NetCoreApp31) + .WithTfms(Tfm.NetCoreApp50) .WithAllHostingModels(); [ConditionalTheory] diff --git a/src/Servers/test/FunctionalTests/ResponseCompressionTests.cs b/src/Servers/test/FunctionalTests/ResponseCompressionTests.cs index 4f4af0b3e0..e441343521 100644 --- a/src/Servers/test/FunctionalTests/ResponseCompressionTests.cs +++ b/src/Servers/test/FunctionalTests/ResponseCompressionTests.cs @@ -33,7 +33,7 @@ namespace ServerComparison.FunctionalTests public static TestMatrix NoCompressionTestVariants => TestMatrix.ForServers(ServerType.IISExpress, ServerType.Kestrel, ServerType.Nginx, ServerType.HttpSys) - .WithTfms(Tfm.NetCoreApp31) + .WithTfms(Tfm.NetCoreApp50) .WithAllHostingModels(); [ConditionalTheory] @@ -45,7 +45,7 @@ namespace ServerComparison.FunctionalTests public static TestMatrix HostCompressionTestVariants => TestMatrix.ForServers(ServerType.IISExpress, ServerType.Nginx) - .WithTfms(Tfm.NetCoreApp31) + .WithTfms(Tfm.NetCoreApp50) .WithAllHostingModels(); [ConditionalTheory] @@ -57,7 +57,7 @@ namespace ServerComparison.FunctionalTests public static TestMatrix AppCompressionTestVariants => TestMatrix.ForServers(ServerType.IISExpress, ServerType.Kestrel, ServerType.HttpSys) // No pass-through compression for nginx - .WithTfms(Tfm.NetCoreApp31) + .WithTfms(Tfm.NetCoreApp50) .WithAllHostingModels(); [ConditionalTheory] @@ -69,7 +69,7 @@ namespace ServerComparison.FunctionalTests public static TestMatrix HostAndAppCompressionTestVariants => TestMatrix.ForServers(ServerType.IISExpress, ServerType.Kestrel, ServerType.Nginx, ServerType.HttpSys) - .WithTfms(Tfm.NetCoreApp31) + .WithTfms(Tfm.NetCoreApp50) .WithAllHostingModels(); [ConditionalTheory] diff --git a/src/Servers/test/FunctionalTests/ResponseTests.cs b/src/Servers/test/FunctionalTests/ResponseTests.cs index 169132f30b..c3738f9d16 100644 --- a/src/Servers/test/FunctionalTests/ResponseTests.cs +++ b/src/Servers/test/FunctionalTests/ResponseTests.cs @@ -25,8 +25,8 @@ namespace ServerComparison.FunctionalTests } public static TestMatrix TestVariants - => TestMatrix.ForServers(/* ServerType.IISExpress, https://github.com/aspnet/AspNetCore/issues/6168, */ ServerType.Kestrel, ServerType.Nginx, ServerType.HttpSys) - .WithTfms(Tfm.NetCoreApp31) + => TestMatrix.ForServers(/* ServerType.IISExpress, https://github.com/dotnet/aspnetcore/issues/6168, */ ServerType.Kestrel, ServerType.Nginx, ServerType.HttpSys) + .WithTfms(Tfm.NetCoreApp50) .WithAllHostingModels(); [ConditionalTheory] @@ -52,7 +52,7 @@ namespace ServerComparison.FunctionalTests public static TestMatrix SelfhostTestVariants => TestMatrix.ForServers(ServerType.Kestrel, ServerType.HttpSys) - .WithTfms(Tfm.NetCoreApp31); + .WithTfms(Tfm.NetCoreApp50); // Connection Close tests do not work through reverse proxies [ConditionalTheory] diff --git a/src/Servers/test/FunctionalTests/ServerComparison.FunctionalTests.csproj b/src/Servers/test/FunctionalTests/ServerComparison.FunctionalTests.csproj index 4dd2e88fd4..b3b461eafe 100644 --- a/src/Servers/test/FunctionalTests/ServerComparison.FunctionalTests.csproj +++ b/src/Servers/test/FunctionalTests/ServerComparison.FunctionalTests.csproj @@ -18,7 +18,6 @@ - diff --git a/src/Servers/testassets/ServerComparison.TestSites/StartupNtlmAuthentication.cs b/src/Servers/testassets/ServerComparison.TestSites/StartupNtlmAuthentication.cs index c2adffb4db..9fc54386b1 100644 --- a/src/Servers/testassets/ServerComparison.TestSites/StartupNtlmAuthentication.cs +++ b/src/Servers/testassets/ServerComparison.TestSites/StartupNtlmAuthentication.cs @@ -16,7 +16,7 @@ namespace ServerComparison.TestSites public void ConfigureServices(IServiceCollection services) { services.AddHttpContextAccessor(); - // https://github.com/aspnet/AspNetCore/issues/11462 + // https://github.com/dotnet/aspnetcore/issues/11462 // services.AddSingleton(); // This will deffer to the server implementations when available. @@ -45,7 +45,7 @@ namespace ServerComparison.TestSites }); app.UseAuthentication(); - app.Use((context, next) => + app.Use((context, next) => { if (context.Request.Path.Equals("/Anonymous")) { diff --git a/src/Shared/ActivatorUtilities/ActivatorUtilities.cs b/src/Shared/ActivatorUtilities/ActivatorUtilities.cs new file mode 100644 index 0000000000..4d05ebf589 --- /dev/null +++ b/src/Shared/ActivatorUtilities/ActivatorUtilities.cs @@ -0,0 +1,432 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Runtime.ExceptionServices; + +#if ActivatorUtilities_In_DependencyInjection +using Microsoft.Extensions.Internal; + +namespace Microsoft.Extensions.DependencyInjection +#else +namespace Microsoft.Extensions.Internal +#endif +{ + /// + /// Helper code for the various activator services. + /// + +#if ActivatorUtilities_In_DependencyInjection + public +#else + // Do not take a dependency on this class unless you are explicitly trying to avoid taking a + // dependency on Microsoft.AspNetCore.DependencyInjection.Abstractions. + internal +#endif + static class ActivatorUtilities + { + private static readonly MethodInfo GetServiceInfo = + GetMethodInfo>((sp, t, r, c) => GetService(sp, t, r, c)); + + /// + /// Instantiate a type with constructor arguments provided directly and/or from an . + /// + /// The service provider used to resolve dependencies + /// The type to activate + /// Constructor arguments not provided by the . + /// An activated object of type instanceType + public static object CreateInstance(IServiceProvider provider, Type instanceType, params object[] parameters) + { + int bestLength = -1; + var seenPreferred = false; + + ConstructorMatcher bestMatcher = default; + + if (!instanceType.GetTypeInfo().IsAbstract) + { + foreach (var constructor in instanceType + .GetTypeInfo() + .DeclaredConstructors) + { + if (!constructor.IsStatic && constructor.IsPublic) + { + var matcher = new ConstructorMatcher(constructor); + var isPreferred = constructor.IsDefined(typeof(ActivatorUtilitiesConstructorAttribute), false); + var length = matcher.Match(parameters); + + if (isPreferred) + { + if (seenPreferred) + { + ThrowMultipleCtorsMarkedWithAttributeException(); + } + + if (length == -1) + { + ThrowMarkedCtorDoesNotTakeAllProvidedArguments(); + } + } + + if (isPreferred || bestLength < length) + { + bestLength = length; + bestMatcher = matcher; + } + + seenPreferred |= isPreferred; + } + } + } + + if (bestLength == -1) + { + var message = $"A suitable constructor for type '{instanceType}' could not be located. Ensure the type is concrete and services are registered for all parameters of a public constructor."; + throw new InvalidOperationException(message); + } + + return bestMatcher.CreateInstance(provider); + } + + /// + /// Create a delegate that will instantiate a type with constructor arguments provided directly + /// and/or from an . + /// + /// The type to activate + /// + /// The types of objects, in order, that will be passed to the returned function as its second parameter + /// + /// + /// A factory that will instantiate instanceType using an + /// and an argument array containing objects matching the types defined in argumentTypes + /// + public static ObjectFactory CreateFactory(Type instanceType, Type[] argumentTypes) + { + FindApplicableConstructor(instanceType, argumentTypes, out ConstructorInfo constructor, out int?[] parameterMap); + + var provider = Expression.Parameter(typeof(IServiceProvider), "provider"); + var argumentArray = Expression.Parameter(typeof(object[]), "argumentArray"); + var factoryExpressionBody = BuildFactoryExpression(constructor, parameterMap, provider, argumentArray); + + var factoryLamda = Expression.Lambda>( + factoryExpressionBody, provider, argumentArray); + + var result = factoryLamda.Compile(); + return result.Invoke; + } + + /// + /// Instantiate a type with constructor arguments provided directly and/or from an . + /// + /// The type to activate + /// The service provider used to resolve dependencies + /// Constructor arguments not provided by the . + /// An activated object of type T + public static T CreateInstance(IServiceProvider provider, params object[] parameters) + { + return (T)CreateInstance(provider, typeof(T), parameters); + } + + + /// + /// Retrieve an instance of the given type from the service provider. If one is not found then instantiate it directly. + /// + /// The type of the service + /// The service provider used to resolve dependencies + /// The resolved service or created instance + public static T GetServiceOrCreateInstance(IServiceProvider provider) + { + return (T)GetServiceOrCreateInstance(provider, typeof(T)); + } + + /// + /// Retrieve an instance of the given type from the service provider. If one is not found then instantiate it directly. + /// + /// The service provider + /// The type of the service + /// The resolved service or created instance + public static object GetServiceOrCreateInstance(IServiceProvider provider, Type type) + { + return provider.GetService(type) ?? CreateInstance(provider, type); + } + + private static MethodInfo GetMethodInfo(Expression expr) + { + var mc = (MethodCallExpression)expr.Body; + return mc.Method; + } + + private static object GetService(IServiceProvider sp, Type type, Type requiredBy, bool isDefaultParameterRequired) + { + var service = sp.GetService(type); + if (service == null && !isDefaultParameterRequired) + { + var message = $"Unable to resolve service for type '{type}' while attempting to activate '{requiredBy}'."; + throw new InvalidOperationException(message); + } + return service; + } + + private static Expression BuildFactoryExpression( + ConstructorInfo constructor, + int?[] parameterMap, + Expression serviceProvider, + Expression factoryArgumentArray) + { + var constructorParameters = constructor.GetParameters(); + var constructorArguments = new Expression[constructorParameters.Length]; + + for (var i = 0; i < constructorParameters.Length; i++) + { + var constructorParameter = constructorParameters[i]; + var parameterType = constructorParameter.ParameterType; + var hasDefaultValue = ParameterDefaultValue.TryGetDefaultValue(constructorParameter, out var defaultValue); + + if (parameterMap[i] != null) + { + constructorArguments[i] = Expression.ArrayAccess(factoryArgumentArray, Expression.Constant(parameterMap[i])); + } + else + { + var parameterTypeExpression = new Expression[] { serviceProvider, + Expression.Constant(parameterType, typeof(Type)), + Expression.Constant(constructor.DeclaringType, typeof(Type)), + Expression.Constant(hasDefaultValue) }; + constructorArguments[i] = Expression.Call(GetServiceInfo, parameterTypeExpression); + } + + // Support optional constructor arguments by passing in the default value + // when the argument would otherwise be null. + if (hasDefaultValue) + { + var defaultValueExpression = Expression.Constant(defaultValue); + constructorArguments[i] = Expression.Coalesce(constructorArguments[i], defaultValueExpression); + } + + constructorArguments[i] = Expression.Convert(constructorArguments[i], parameterType); + } + + return Expression.New(constructor, constructorArguments); + } + + private static void FindApplicableConstructor( + Type instanceType, + Type[] argumentTypes, + out ConstructorInfo matchingConstructor, + out int?[] parameterMap) + { + matchingConstructor = null; + parameterMap = null; + + if (!TryFindPreferredConstructor(instanceType, argumentTypes, ref matchingConstructor, ref parameterMap) && + !TryFindMatchingConstructor(instanceType, argumentTypes, ref matchingConstructor, ref parameterMap)) + { + var message = $"A suitable constructor for type '{instanceType}' could not be located. Ensure the type is concrete and services are registered for all parameters of a public constructor."; + throw new InvalidOperationException(message); + } + } + + // Tries to find constructor based on provided argument types + private static bool TryFindMatchingConstructor( + Type instanceType, + Type[] argumentTypes, + ref ConstructorInfo matchingConstructor, + ref int?[] parameterMap) + { + foreach (var constructor in instanceType.GetTypeInfo().DeclaredConstructors) + { + if (constructor.IsStatic || !constructor.IsPublic) + { + continue; + } + + if (TryCreateParameterMap(constructor.GetParameters(), argumentTypes, out int?[] tempParameterMap)) + { + if (matchingConstructor != null) + { + throw new InvalidOperationException($"Multiple constructors accepting all given argument types have been found in type '{instanceType}'. There should only be one applicable constructor."); + } + + matchingConstructor = constructor; + parameterMap = tempParameterMap; + } + } + + return matchingConstructor != null; + } + + // Tries to find constructor marked with ActivatorUtilitiesConstructorAttribute + private static bool TryFindPreferredConstructor( + Type instanceType, + Type[] argumentTypes, + ref ConstructorInfo matchingConstructor, + ref int?[] parameterMap) + { + var seenPreferred = false; + foreach (var constructor in instanceType.GetTypeInfo().DeclaredConstructors) + { + if (constructor.IsStatic || !constructor.IsPublic) + { + continue; + } + + if (constructor.IsDefined(typeof(ActivatorUtilitiesConstructorAttribute), false)) + { + if (seenPreferred) + { + ThrowMultipleCtorsMarkedWithAttributeException(); + } + + if (!TryCreateParameterMap(constructor.GetParameters(), argumentTypes, out int?[] tempParameterMap)) + { + ThrowMarkedCtorDoesNotTakeAllProvidedArguments(); + } + + matchingConstructor = constructor; + parameterMap = tempParameterMap; + seenPreferred = true; + } + } + + return matchingConstructor != null; + } + + // Creates an injective parameterMap from givenParameterTypes to assignable constructorParameters. + // Returns true if each given parameter type is assignable to a unique; otherwise, false. + private static bool TryCreateParameterMap(ParameterInfo[] constructorParameters, Type[] argumentTypes, out int?[] parameterMap) + { + parameterMap = new int?[constructorParameters.Length]; + + for (var i = 0; i < argumentTypes.Length; i++) + { + var foundMatch = false; + var givenParameter = argumentTypes[i].GetTypeInfo(); + + for (var j = 0; j < constructorParameters.Length; j++) + { + if (parameterMap[j] != null) + { + // This ctor parameter has already been matched + continue; + } + + if (constructorParameters[j].ParameterType.GetTypeInfo().IsAssignableFrom(givenParameter)) + { + foundMatch = true; + parameterMap[j] = i; + break; + } + } + + if (!foundMatch) + { + return false; + } + } + + return true; + } + + private struct ConstructorMatcher + { + private readonly ConstructorInfo _constructor; + private readonly ParameterInfo[] _parameters; + private readonly object[] _parameterValues; + + public ConstructorMatcher(ConstructorInfo constructor) + { + _constructor = constructor; + _parameters = _constructor.GetParameters(); + _parameterValues = new object[_parameters.Length]; + } + + public int Match(object[] givenParameters) + { + var applyIndexStart = 0; + var applyExactLength = 0; + for (var givenIndex = 0; givenIndex != givenParameters.Length; givenIndex++) + { + var givenType = givenParameters[givenIndex]?.GetType().GetTypeInfo(); + var givenMatched = false; + + for (var applyIndex = applyIndexStart; givenMatched == false && applyIndex != _parameters.Length; ++applyIndex) + { + if (_parameterValues[applyIndex] == null && + _parameters[applyIndex].ParameterType.GetTypeInfo().IsAssignableFrom(givenType)) + { + givenMatched = true; + _parameterValues[applyIndex] = givenParameters[givenIndex]; + if (applyIndexStart == applyIndex) + { + applyIndexStart++; + if (applyIndex == givenIndex) + { + applyExactLength = applyIndex; + } + } + } + } + + if (givenMatched == false) + { + return -1; + } + } + return applyExactLength; + } + + public object CreateInstance(IServiceProvider provider) + { + for (var index = 0; index != _parameters.Length; index++) + { + if (_parameterValues[index] == null) + { + var value = provider.GetService(_parameters[index].ParameterType); + if (value == null) + { + if (!ParameterDefaultValue.TryGetDefaultValue(_parameters[index], out var defaultValue)) + { + throw new InvalidOperationException($"Unable to resolve service for type '{_parameters[index].ParameterType}' while attempting to activate '{_constructor.DeclaringType}'."); + } + else + { + _parameterValues[index] = defaultValue; + } + } + else + { + _parameterValues[index] = value; + } + } + } + +#if NETCOREAPP + return _constructor.Invoke(BindingFlags.DoNotWrapExceptions, binder: null, parameters: _parameterValues, culture: null); +#else + try + { + return _constructor.Invoke(_parameterValues); + } + catch (TargetInvocationException ex) when (ex.InnerException != null) + { + ExceptionDispatchInfo.Capture(ex.InnerException).Throw(); + // The above line will always throw, but the compiler requires we throw explicitly. + throw; + } +#endif + } + } + + private static void ThrowMultipleCtorsMarkedWithAttributeException() + { + throw new InvalidOperationException($"Multiple constructors were marked with {nameof(ActivatorUtilitiesConstructorAttribute)}."); + } + + private static void ThrowMarkedCtorDoesNotTakeAllProvidedArguments() + { + throw new InvalidOperationException($"Constructor marked with {nameof(ActivatorUtilitiesConstructorAttribute)} does not accept all given argument types."); + } + } +} diff --git a/src/Shared/ActivatorUtilities/ActivatorUtilitiesConstructorAttribute.cs b/src/Shared/ActivatorUtilities/ActivatorUtilitiesConstructorAttribute.cs new file mode 100644 index 0000000000..67ffa13f6f --- /dev/null +++ b/src/Shared/ActivatorUtilities/ActivatorUtilitiesConstructorAttribute.cs @@ -0,0 +1,26 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; + +#if ActivatorUtilities_In_DependencyInjection +namespace Microsoft.Extensions.DependencyInjection +#else +namespace Microsoft.Extensions.Internal +#endif +{ + /// + /// Marks the constructor to be used when activating type using . + /// + +#if ActivatorUtilities_In_DependencyInjection + public +#else + // Do not take a dependency on this class unless you are explicitly trying to avoid taking a + // dependency on Microsoft.AspNetCore.DependencyInjection.Abstractions. + internal +#endif + class ActivatorUtilitiesConstructorAttribute: Attribute + { + } +} diff --git a/src/Shared/ActivatorUtilities/ObjectFactory.cs b/src/Shared/ActivatorUtilities/ObjectFactory.cs new file mode 100644 index 0000000000..517247811e --- /dev/null +++ b/src/Shared/ActivatorUtilities/ObjectFactory.cs @@ -0,0 +1,25 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; + +#if ActivatorUtilities_In_DependencyInjection +namespace Microsoft.Extensions.DependencyInjection +#else +namespace Microsoft.Extensions.Internal +#endif +{ + + /// + /// The result of . + /// + /// The to get service arguments from. + /// Additional constructor arguments. + /// The instantiated type. +#if ActivatorUtilities_In_DependencyInjection + public +#else + internal +#endif + delegate object ObjectFactory(IServiceProvider serviceProvider, object[] arguments); +} \ No newline at end of file diff --git a/src/Shared/BenchmarkRunner/AspNetCoreBenchmarkAttribute.cs b/src/Shared/BenchmarkRunner/AspNetCoreBenchmarkAttribute.cs new file mode 100644 index 0000000000..d16493a738 --- /dev/null +++ b/src/Shared/BenchmarkRunner/AspNetCoreBenchmarkAttribute.cs @@ -0,0 +1,73 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using BenchmarkDotNet.Configs; + +namespace BenchmarkDotNet.Attributes +{ + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Assembly)] + internal class AspNetCoreBenchmarkAttribute : Attribute, IConfigSource + { + public AspNetCoreBenchmarkAttribute() + : this(typeof(DefaultCoreConfig)) + { + } + + public AspNetCoreBenchmarkAttribute(Type configType) + : this(configType, typeof(DefaultCoreValidationConfig)) + { + } + + public AspNetCoreBenchmarkAttribute(Type configType, Type validationConfigType) + { + ConfigTypes = new Dictionary() + { + { NamedConfiguration.Default, typeof(DefaultCoreConfig) }, + { NamedConfiguration.Validation, typeof(DefaultCoreValidationConfig) }, + { NamedConfiguration.Profile, typeof(DefaultCoreProfileConfig) }, + { NamedConfiguration.Debug, typeof(DefaultCoreDebugConfig) }, + { NamedConfiguration.PerfLab, typeof(DefaultCorePerfLabConfig) }, + }; + + if (configType != null) + { + ConfigTypes[NamedConfiguration.Default] = configType; + } + + if (validationConfigType != null) + { + ConfigTypes[NamedConfiguration.Validation] = validationConfigType; + } + } + + public IConfig Config + { + get + { + if (!ConfigTypes.TryGetValue(ConfigName ?? NamedConfiguration.Default, out var configType)) + { + var message = $"Could not find a configuration matching {ConfigName}. " + + $"Known configurations: {string.Join(", ", ConfigTypes.Keys)}"; + throw new InvalidOperationException(message); + } + + return (IConfig)Activator.CreateInstance(configType, Array.Empty()); + } + } + + public Dictionary ConfigTypes { get; } + + public static string ConfigName { get; set; } = NamedConfiguration.Default; + + public static class NamedConfiguration + { + public static readonly string Default = "default"; + public static readonly string Validation = "validation"; + public static readonly string Profile = "profile"; + public static readonly string Debug = "debug"; + public static readonly string PerfLab = "perflab"; + } + } +} diff --git a/src/Shared/BenchmarkRunner/DefaultCoreConfig.cs b/src/Shared/BenchmarkRunner/DefaultCoreConfig.cs new file mode 100644 index 0000000000..e5b0c9b43b --- /dev/null +++ b/src/Shared/BenchmarkRunner/DefaultCoreConfig.cs @@ -0,0 +1,46 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using BenchmarkDotNet.Columns; +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Diagnosers; +using BenchmarkDotNet.Engines; +using BenchmarkDotNet.Exporters; +using BenchmarkDotNet.Jobs; +using BenchmarkDotNet.Loggers; +using BenchmarkDotNet.Toolchains.CsProj; +using BenchmarkDotNet.Toolchains.DotNetCli; +using BenchmarkDotNet.Validators; + +namespace BenchmarkDotNet.Attributes +{ + internal class DefaultCoreConfig : ManualConfig + { + public DefaultCoreConfig() + { + Add(ConsoleLogger.Default); + Add(MarkdownExporter.GitHub); + + Add(MemoryDiagnoser.Default); + Add(StatisticColumn.OperationsPerSecond); + Add(DefaultColumnProviders.Instance); + + Add(JitOptimizationsValidator.FailOnError); + + Add(Job.Default +#if NETCOREAPP2_1 + .With(CsProjCoreToolchain.From(NetCoreAppSettings.NetCoreApp21)) +#elif NETCOREAPP3_0 + .With(CsProjCoreToolchain.From(new NetCoreAppSettings("netcoreapp3.0", null, ".NET Core 3.0"))) +#elif NETCOREAPP3_1 + .With(CsProjCoreToolchain.From(new NetCoreAppSettings("netcoreapp3.1", null, ".NET Core 3.1"))) +#elif NETCOREAPP5_0 + .With(CsProjCoreToolchain.From(new NetCoreAppSettings("netcoreapp5.0", null, ".NET Core 5.0"))) +#else +#error Target frameworks need to be updated. +#endif + .With(new GcMode { Server = true }) + .With(RunStrategy.Throughput)); + } + } +} diff --git a/src/Shared/BenchmarkRunner/DefaultCoreDebugConfig.cs b/src/Shared/BenchmarkRunner/DefaultCoreDebugConfig.cs new file mode 100644 index 0000000000..f51bed2db9 --- /dev/null +++ b/src/Shared/BenchmarkRunner/DefaultCoreDebugConfig.cs @@ -0,0 +1,23 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Engines; +using BenchmarkDotNet.Jobs; +using BenchmarkDotNet.Loggers; +using BenchmarkDotNet.Validators; + +namespace BenchmarkDotNet.Attributes +{ + internal class DefaultCoreDebugConfig : ManualConfig + { + public DefaultCoreDebugConfig() + { + Add(ConsoleLogger.Default); + Add(JitOptimizationsValidator.DontFailOnError); + + Add(Job.InProcess + .With(RunStrategy.Throughput)); + } + } +} diff --git a/src/Shared/BenchmarkRunner/DefaultCorePerfLabConfig.cs b/src/Shared/BenchmarkRunner/DefaultCorePerfLabConfig.cs new file mode 100644 index 0000000000..5c6ba7ac3b --- /dev/null +++ b/src/Shared/BenchmarkRunner/DefaultCorePerfLabConfig.cs @@ -0,0 +1,39 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using BenchmarkDotNet.Columns; +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Diagnosers; +using BenchmarkDotNet.Engines; +using BenchmarkDotNet.Exporters; +using BenchmarkDotNet.Exporters.Csv; +using BenchmarkDotNet.Jobs; +using BenchmarkDotNet.Loggers; +using BenchmarkDotNet.Validators; + +namespace BenchmarkDotNet.Attributes +{ + internal class DefaultCorePerfLabConfig : ManualConfig + { + public DefaultCorePerfLabConfig() + { + Add(ConsoleLogger.Default); + + Add(MemoryDiagnoser.Default); + Add(StatisticColumn.OperationsPerSecond); + Add(new ParamsSummaryColumn()); + Add(DefaultColumnProviders.Statistics, DefaultColumnProviders.Metrics, DefaultColumnProviders.Descriptor); + + Add(JitOptimizationsValidator.FailOnError); + + Add(Job.InProcess + .With(RunStrategy.Throughput)); + + Add(MarkdownExporter.GitHub); + + Add(new CsvExporter( + CsvSeparator.Comma, + new Reports.SummaryStyle(printUnitsInHeader: true, printUnitsInContent: false, timeUnit: Horology.TimeUnit.Microsecond, sizeUnit: SizeUnit.KB))); + } + } +} diff --git a/src/Shared/BenchmarkRunner/DefaultCoreProfileConfig.cs b/src/Shared/BenchmarkRunner/DefaultCoreProfileConfig.cs new file mode 100644 index 0000000000..1b59cb89c5 --- /dev/null +++ b/src/Shared/BenchmarkRunner/DefaultCoreProfileConfig.cs @@ -0,0 +1,32 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using BenchmarkDotNet.Columns; +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Diagnosers; +using BenchmarkDotNet.Engines; +using BenchmarkDotNet.Exporters; +using BenchmarkDotNet.Jobs; +using BenchmarkDotNet.Loggers; +using BenchmarkDotNet.Validators; + +namespace BenchmarkDotNet.Attributes +{ + internal class DefaultCoreProfileConfig : ManualConfig + { + public DefaultCoreProfileConfig() + { + Add(ConsoleLogger.Default); + Add(MarkdownExporter.GitHub); + + Add(MemoryDiagnoser.Default); + Add(StatisticColumn.OperationsPerSecond); + Add(DefaultColumnProviders.Instance); + + Add(JitOptimizationsValidator.FailOnError); + + Add(Job.InProcess + .With(RunStrategy.Throughput)); + } + } +} diff --git a/src/Shared/BenchmarkRunner/DefaultCoreValidationConfig.cs b/src/Shared/BenchmarkRunner/DefaultCoreValidationConfig.cs new file mode 100644 index 0000000000..3faf5ac1cb --- /dev/null +++ b/src/Shared/BenchmarkRunner/DefaultCoreValidationConfig.cs @@ -0,0 +1,20 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Jobs; +using BenchmarkDotNet.Loggers; +using BenchmarkDotNet.Toolchains.InProcess.NoEmit; + +namespace BenchmarkDotNet.Attributes +{ + internal class DefaultCoreValidationConfig : ManualConfig + { + public DefaultCoreValidationConfig() + { + Add(ConsoleLogger.Default); + + Add(Job.Dry.With(InProcessNoEmitToolchain.Instance)); + } + } +} diff --git a/src/Shared/BenchmarkRunner/ParameterizedJobConfigAttribute.cs b/src/Shared/BenchmarkRunner/ParameterizedJobConfigAttribute.cs new file mode 100644 index 0000000000..9e0f947dc7 --- /dev/null +++ b/src/Shared/BenchmarkRunner/ParameterizedJobConfigAttribute.cs @@ -0,0 +1,15 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; + +namespace BenchmarkDotNet.Attributes +{ + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Assembly)] + internal class ParameterizedJobConfigAttribute: AspNetCoreBenchmarkAttribute + { + public ParameterizedJobConfigAttribute(Type configType) : base(configType) + { + } + } +} diff --git a/src/Shared/BenchmarkRunner/ParamsDisplayInfoColumn.cs b/src/Shared/BenchmarkRunner/ParamsDisplayInfoColumn.cs new file mode 100644 index 0000000000..2267de01a6 --- /dev/null +++ b/src/Shared/BenchmarkRunner/ParamsDisplayInfoColumn.cs @@ -0,0 +1,26 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using BenchmarkDotNet.Columns; +using BenchmarkDotNet.Reports; +using BenchmarkDotNet.Running; + +namespace BenchmarkDotNet.Attributes +{ + public class ParamsSummaryColumn : IColumn + { + public string Id => nameof(ParamsSummaryColumn); + public string ColumnName { get; } = "Params"; + public bool IsDefault(Summary summary, BenchmarkCase benchmark) => false; + public string GetValue(Summary summary, BenchmarkCase benchmark) => benchmark.Parameters.DisplayInfo; + public bool IsAvailable(Summary summary) => true; + public bool AlwaysShow => true; + public ColumnCategory Category => ColumnCategory.Params; + public int PriorityInCategory => 0; + public override string ToString() => ColumnName; + public bool IsNumeric => false; + public UnitType UnitType => UnitType.Dimensionless; + public string GetValue(Summary summary, BenchmarkCase benchmark, SummaryStyle style) => GetValue(summary, benchmark); + public string Legend => $"Summary of all parameter values"; + } +} diff --git a/src/Shared/BenchmarkRunner/Program.cs b/src/Shared/BenchmarkRunner/Program.cs new file mode 100644 index 0000000000..87a01cf6c2 --- /dev/null +++ b/src/Shared/BenchmarkRunner/Program.cs @@ -0,0 +1,106 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Running; + +namespace Microsoft.AspNetCore.BenchmarkDotNet.Runner +{ + partial class Program + { + private static TextWriter _standardOutput; + private static StringBuilder _standardOutputText; + + static partial void BeforeMain(string[] args); + + private static int Main(string[] args) + { + BeforeMain(args); + + AssignConfiguration(ref args); + var summaries = BenchmarkSwitcher.FromAssembly(typeof(Program).GetTypeInfo().Assembly) + .Run(args, ManualConfig.CreateEmpty()); + + foreach (var summary in summaries) + { + if (summary.HasCriticalValidationErrors) + { + return Fail(summary, nameof(summary.HasCriticalValidationErrors)); + } + + foreach (var report in summary.Reports) + { + if (!report.BuildResult.IsGenerateSuccess) + { + return Fail(report, nameof(report.BuildResult.IsGenerateSuccess)); + } + + if (!report.BuildResult.IsBuildSuccess) + { + return Fail(report, nameof(report.BuildResult.IsBuildSuccess)); + } + + if (!report.AllMeasurements.Any()) + { + return Fail(report, nameof(report.AllMeasurements)); + } + } + } + + return 0; + } + + private static int Fail(object o, string message) + { + _standardOutput?.WriteLine(_standardOutputText.ToString()); + + Console.Error.WriteLine("'{0}' failed, reason: '{1}'", o, message); + return 1; + } + + private static void AssignConfiguration(ref string[] args) + { + var argsList = args.ToList(); + if (argsList.Remove("--validate") || argsList.Remove("--validate-fast")) + { + // Compat: support the old style of passing a config that is used by our build system. + SuppressConsole(); + AspNetCoreBenchmarkAttribute.ConfigName = AspNetCoreBenchmarkAttribute.NamedConfiguration.Validation; + args = argsList.ToArray(); + return; + } + + var index = argsList.IndexOf("--config"); + if (index >= 0 && index < argsList.Count -1) + { + AspNetCoreBenchmarkAttribute.ConfigName = argsList[index + 1]; + argsList.RemoveAt(index + 1); + argsList.RemoveAt(index); + args = argsList.ToArray(); + return; + } + + if (Debugger.IsAttached) + { + Console.WriteLine("Using the debug config since you are debugging. I hope that's OK!"); + Console.WriteLine("Specify a configuration with --config to override"); + AspNetCoreBenchmarkAttribute.ConfigName = AspNetCoreBenchmarkAttribute.NamedConfiguration.Debug; + return; + } + } + + private static void SuppressConsole() + { + _standardOutput = Console.Out; + _standardOutputText = new StringBuilder(); + Console.SetOut(new StringWriter(_standardOutputText)); + } + } +} diff --git a/src/Shared/Buffers.MemoryPool/DiagnosticMemoryPool.cs b/src/Shared/Buffers.MemoryPool/DiagnosticMemoryPool.cs index 4cbea8a866..fe3e2eb51d 100644 --- a/src/Shared/Buffers.MemoryPool/DiagnosticMemoryPool.cs +++ b/src/Shared/Buffers.MemoryPool/DiagnosticMemoryPool.cs @@ -119,7 +119,7 @@ namespace System.Buffers MemoryPoolThrowHelper.ThrowInvalidOperationException_DisposingPoolWithActiveBlocks(_totalBlocks - _blocks.Count, _totalBlocks, _blocks.ToArray()); } - if (_blockAccessExceptions.Any()) + if (_blockAccessExceptions.Count > 0) { throw CreateAccessExceptions(); } @@ -136,7 +136,7 @@ namespace System.Buffers private void SetAllBlocksReturned() { - if (_blockAccessExceptions.Any()) + if (_blockAccessExceptions.Count > 0) { _allBlocksReturned.SetException(CreateAccessExceptions()); } diff --git a/src/Shared/Buffers.MemoryPool/MemoryPoolSlab.cs b/src/Shared/Buffers.MemoryPool/MemoryPoolSlab.cs index 99dfe082e5..a9baa560f5 100644 --- a/src/Shared/Buffers.MemoryPool/MemoryPoolSlab.cs +++ b/src/Shared/Buffers.MemoryPool/MemoryPoolSlab.cs @@ -58,7 +58,7 @@ namespace System.Buffers _isDisposed = true; Array = null; - NativePointer = IntPtr.Zero;; + NativePointer = IntPtr.Zero; if (_gcHandle.IsAllocated) { diff --git a/src/Shared/CertificateGeneration/CertificateManager.cs b/src/Shared/CertificateGeneration/CertificateManager.cs index ef2a45cdeb..01962e83d4 100644 --- a/src/Shared/CertificateGeneration/CertificateManager.cs +++ b/src/Shared/CertificateGeneration/CertificateManager.cs @@ -807,7 +807,7 @@ namespace Microsoft.AspNetCore.Certificates.Generation result.ResultCode = EnsureCertificateResult.Succeeded; X509Certificate2 certificate = null; - if (certificates.Count() > 0) + if (certificates.Any()) { result.Diagnostics.Debug("Found valid certificates present on the machine."); result.Diagnostics.Debug(result.Diagnostics.DescribeCertificates(certificates)); diff --git a/src/Shared/ChunkingCookieManager/ChunkingCookieManager.cs b/src/Shared/ChunkingCookieManager/ChunkingCookieManager.cs index 4f1700bb77..098e3d6690 100644 --- a/src/Shared/ChunkingCookieManager/ChunkingCookieManager.cs +++ b/src/Shared/ChunkingCookieManager/ChunkingCookieManager.cs @@ -288,7 +288,7 @@ namespace Microsoft.AspNetCore.Internal SameSite = options.SameSite, Secure = options.Secure, IsEssential = options.IsEssential, - Expires = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc), + Expires = DateTimeOffset.UnixEpoch, HttpOnly = options.HttpOnly, }); @@ -305,7 +305,7 @@ namespace Microsoft.AspNetCore.Internal SameSite = options.SameSite, Secure = options.Secure, IsEssential = options.IsEssential, - Expires = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc), + Expires = DateTimeOffset.UnixEpoch, HttpOnly = options.HttpOnly, }); } diff --git a/src/Shared/CommandLineUtils/CommandLine/AnsiConsole.cs b/src/Shared/CommandLineUtils/CommandLine/AnsiConsole.cs new file mode 100644 index 0000000000..379235f274 --- /dev/null +++ b/src/Shared/CommandLineUtils/CommandLine/AnsiConsole.cs @@ -0,0 +1,143 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; + +namespace Microsoft.Extensions.CommandLineUtils +{ + internal class AnsiConsole + { + private AnsiConsole(TextWriter writer, bool useConsoleColor) + { + Writer = writer; + + _useConsoleColor = useConsoleColor; + if (_useConsoleColor) + { + OriginalForegroundColor = Console.ForegroundColor; + } + } + + private int _boldRecursion; + private bool _useConsoleColor; + + public static AnsiConsole GetOutput(bool useConsoleColor) + { + return new AnsiConsole(Console.Out, useConsoleColor); + } + + public static AnsiConsole GetError(bool useConsoleColor) + { + return new AnsiConsole(Console.Error, useConsoleColor); + } + + public TextWriter Writer { get; } + + public ConsoleColor OriginalForegroundColor { get; } + + private void SetColor(ConsoleColor color) + { + Console.ForegroundColor = (ConsoleColor)(((int)Console.ForegroundColor & 0x08) | ((int)color & 0x07)); + } + + private void SetBold(bool bold) + { + _boldRecursion += bold ? 1 : -1; + if (_boldRecursion > 1 || (_boldRecursion == 1 && !bold)) + { + return; + } + + Console.ForegroundColor = (ConsoleColor)((int)Console.ForegroundColor ^ 0x08); + } + + public void WriteLine(string message) + { + if (!_useConsoleColor) + { + Writer.WriteLine(message); + return; + } + + var escapeScan = 0; + for (; ;) + { + var escapeIndex = message.IndexOf("\x1b[", escapeScan); + if (escapeIndex == -1) + { + var text = message.Substring(escapeScan); + Writer.Write(text); + break; + } + else + { + var startIndex = escapeIndex + 2; + var endIndex = startIndex; + while (endIndex != message.Length && + message[endIndex] >= 0x20 && + message[endIndex] <= 0x3f) + { + endIndex += 1; + } + + var text = message.Substring(escapeScan, escapeIndex - escapeScan); + Writer.Write(text); + if (endIndex == message.Length) + { + break; + } + + switch (message[endIndex]) + { + case 'm': + int value; + if (int.TryParse(message.Substring(startIndex, endIndex - startIndex), out value)) + { + switch (value) + { + case 1: + SetBold(true); + break; + case 22: + SetBold(false); + break; + case 30: + SetColor(ConsoleColor.Black); + break; + case 31: + SetColor(ConsoleColor.Red); + break; + case 32: + SetColor(ConsoleColor.Green); + break; + case 33: + SetColor(ConsoleColor.Yellow); + break; + case 34: + SetColor(ConsoleColor.Blue); + break; + case 35: + SetColor(ConsoleColor.Magenta); + break; + case 36: + SetColor(ConsoleColor.Cyan); + break; + case 37: + SetColor(ConsoleColor.Gray); + break; + case 39: + SetColor(OriginalForegroundColor); + break; + } + } + break; + } + + escapeScan = endIndex + 1; + } + } + Writer.WriteLine(); + } + } +} diff --git a/src/Shared/CommandLineUtils/CommandLine/CommandArgument.cs b/src/Shared/CommandLineUtils/CommandLine/CommandArgument.cs new file mode 100644 index 0000000000..4eac95982c --- /dev/null +++ b/src/Shared/CommandLineUtils/CommandLine/CommandArgument.cs @@ -0,0 +1,29 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.Extensions.CommandLineUtils +{ + internal class CommandArgument + { + public CommandArgument() + { + Values = new List(); + } + + public string Name { get; set; } + public bool ShowInHelpText { get; set; } = true; + public string Description { get; set; } + public List Values { get; private set; } + public bool MultipleValues { get; set; } + public string Value + { + get + { + return Values.FirstOrDefault(); + } + } + } +} diff --git a/src/Shared/CommandLineUtils/CommandLine/CommandLineApplication.cs b/src/Shared/CommandLineUtils/CommandLine/CommandLineApplication.cs new file mode 100644 index 0000000000..ce608f65bc --- /dev/null +++ b/src/Shared/CommandLineUtils/CommandLine/CommandLineApplication.cs @@ -0,0 +1,644 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.Extensions.CommandLineUtils +{ + internal class CommandLineApplication + { + // Indicates whether the parser should throw an exception when it runs into an unexpected argument. If this is + // set to true (the default), the parser will throw on the first unexpected argument. Otherwise, all unexpected + // arguments (including the first) are added to RemainingArguments. + private readonly bool _throwOnUnexpectedArg; + + // Indicates whether the parser should check remaining arguments for command or option matches after + // encountering an unexpected argument. Ignored if _throwOnUnexpectedArg is true (the default). If + // _throwOnUnexpectedArg and this are both false, the first unexpected argument and all remaining arguments are + // added to RemainingArguments. If _throwOnUnexpectedArg is false and this is true, only unexpected arguments + // are added to RemainingArguments -- allowing a mix of expected and unexpected arguments, commands and + // options. + private readonly bool _continueAfterUnexpectedArg; + + private readonly bool _treatUnmatchedOptionsAsArguments; + + public CommandLineApplication(bool throwOnUnexpectedArg = true, bool continueAfterUnexpectedArg = false, bool treatUnmatchedOptionsAsArguments = false) + { + _throwOnUnexpectedArg = throwOnUnexpectedArg; + _continueAfterUnexpectedArg = continueAfterUnexpectedArg; + _treatUnmatchedOptionsAsArguments = treatUnmatchedOptionsAsArguments; + Options = new List(); + Arguments = new List(); + Commands = new List(); + RemainingArguments = new List(); + Invoke = () => 0; + } + + public CommandLineApplication Parent { get; set; } + public string Name { get; set; } + public string FullName { get; set; } + public string Syntax { get; set; } + public string Description { get; set; } + public bool ShowInHelpText { get; set; } = true; + public string ExtendedHelpText { get; set; } + public readonly List Options; + public CommandOption OptionHelp { get; private set; } + public CommandOption OptionVersion { get; private set; } + public readonly List Arguments; + public readonly List RemainingArguments; + public bool IsShowingInformation { get; protected set; } // Is showing help or version? + public Func Invoke { get; set; } + public Func LongVersionGetter { get; set; } + public Func ShortVersionGetter { get; set; } + public readonly List Commands; + public bool AllowArgumentSeparator { get; set; } + public TextWriter Out { get; set; } = Console.Out; + public TextWriter Error { get; set; } = Console.Error; + + public IEnumerable GetOptions() + { + var expr = Options.AsEnumerable(); + var rootNode = this; + while (rootNode.Parent != null) + { + rootNode = rootNode.Parent; + expr = expr.Concat(rootNode.Options.Where(o => o.Inherited)); + } + + return expr; + } + + public CommandLineApplication Command(string name, Action configuration, + bool throwOnUnexpectedArg = true) + { + var command = new CommandLineApplication(throwOnUnexpectedArg) { Name = name, Parent = this }; + Commands.Add(command); + configuration(command); + return command; + } + + public CommandOption Option(string template, string description, CommandOptionType optionType) + => Option(template, description, optionType, _ => { }, inherited: false); + + public CommandOption Option(string template, string description, CommandOptionType optionType, bool inherited) + => Option(template, description, optionType, _ => { }, inherited); + + public CommandOption Option(string template, string description, CommandOptionType optionType, Action configuration) + => Option(template, description, optionType, configuration, inherited: false); + + public CommandOption Option(string template, string description, CommandOptionType optionType, Action configuration, bool inherited) + { + var option = new CommandOption(template, optionType) + { + Description = description, + Inherited = inherited + }; + Options.Add(option); + configuration(option); + return option; + } + + public CommandArgument Argument(string name, string description, bool multipleValues = false) + { + return Argument(name, description, _ => { }, multipleValues); + } + + public CommandArgument Argument(string name, string description, Action configuration, bool multipleValues = false) + { + var lastArg = Arguments.LastOrDefault(); + if (lastArg != null && lastArg.MultipleValues) + { + var message = string.Format("The last argument '{0}' accepts multiple values. No more argument can be added.", + lastArg.Name); + throw new InvalidOperationException(message); + } + + var argument = new CommandArgument { Name = name, Description = description, MultipleValues = multipleValues }; + Arguments.Add(argument); + configuration(argument); + return argument; + } + + public void OnExecute(Func invoke) + { + Invoke = invoke; + } + + public void OnExecute(Func> invoke) + { + Invoke = () => invoke().Result; + } + public int Execute(params string[] args) + { + CommandLineApplication command = this; + CommandOption option = null; + IEnumerator arguments = null; + var argumentsAssigned = false; + + for (var index = 0; index < args.Length; index++) + { + var arg = args[index]; + var processed = false; + if (!processed && option == null) + { + string[] longOption = null; + string[] shortOption = null; + + if (arg.StartsWith("--")) + { + longOption = arg.Substring(2).Split(new[] { ':', '=' }, 2); + } + else if (arg.StartsWith("-")) + { + shortOption = arg.Substring(1).Split(new[] { ':', '=' }, 2); + } + + if (longOption != null) + { + processed = true; + var longOptionName = longOption[0]; + option = command.GetOptions().SingleOrDefault(opt => string.Equals(opt.LongName, longOptionName, StringComparison.Ordinal)); + + if (option == null && _treatUnmatchedOptionsAsArguments) + { + if (arguments == null) + { + arguments = new CommandArgumentEnumerator(command.Arguments.GetEnumerator()); + } + if (arguments.MoveNext()) + { + processed = true; + arguments.Current.Values.Add(arg); + argumentsAssigned = true; + continue; + } + //else + //{ + // argumentsAssigned = false; + //} + } + + if (option == null) + { + var ignoreContinueAfterUnexpectedArg = false; + if (string.IsNullOrEmpty(longOptionName) && + !command._throwOnUnexpectedArg && + AllowArgumentSeparator) + { + // Skip over the '--' argument separator then consume all remaining arguments. All + // remaining arguments are unconditionally stored for further use. + index++; + ignoreContinueAfterUnexpectedArg = true; + } + + if (HandleUnexpectedArg( + command, + args, + index, + argTypeName: "option", + ignoreContinueAfterUnexpectedArg)) + { + continue; + } + + break; + } + + // If we find a help/version option, show information and stop parsing + if (command.OptionHelp == option) + { + command.ShowHelp(); + return 0; + } + else if (command.OptionVersion == option) + { + command.ShowVersion(); + return 0; + } + + if (longOption.Length == 2) + { + if (!option.TryParse(longOption[1])) + { + command.ShowHint(); + throw new CommandParsingException(command, $"Unexpected value '{longOption[1]}' for option '{option.LongName}'"); + } + option = null; + } + else if (option.OptionType == CommandOptionType.NoValue) + { + // No value is needed for this option + option.TryParse(null); + option = null; + } + } + + if (shortOption != null) + { + processed = true; + option = command.GetOptions().SingleOrDefault(opt => string.Equals(opt.ShortName, shortOption[0], StringComparison.Ordinal)); + + if (option == null && _treatUnmatchedOptionsAsArguments) + { + if (arguments == null) + { + arguments = new CommandArgumentEnumerator(command.Arguments.GetEnumerator()); + } + if (arguments.MoveNext()) + { + processed = true; + arguments.Current.Values.Add(arg); + argumentsAssigned = true; + continue; + } + //else + //{ + // argumentsAssigned = false; + //} + } + + // If not a short option, try symbol option + if (option == null) + { + option = command.GetOptions().SingleOrDefault(opt => string.Equals(opt.SymbolName, shortOption[0], StringComparison.Ordinal)); + } + + if (option == null) + { + if (HandleUnexpectedArg(command, args, index, argTypeName: "option")) + { + continue; + } + + break; + } + + // If we find a help/version option, show information and stop parsing + if (command.OptionHelp == option) + { + command.ShowHelp(); + return 0; + } + else if (command.OptionVersion == option) + { + command.ShowVersion(); + return 0; + } + + if (shortOption.Length == 2) + { + if (!option.TryParse(shortOption[1])) + { + command.ShowHint(); + throw new CommandParsingException(command, $"Unexpected value '{shortOption[1]}' for option '{option.LongName}'"); + } + option = null; + } + else if (option.OptionType == CommandOptionType.NoValue) + { + // No value is needed for this option + option.TryParse(null); + option = null; + } + } + } + + if (!processed && option != null) + { + processed = true; + if (!option.TryParse(arg)) + { + command.ShowHint(); + throw new CommandParsingException(command, $"Unexpected value '{arg}' for option '{option.LongName}'"); + } + option = null; + } + + if (!processed && !argumentsAssigned) + { + var currentCommand = command; + foreach (var subcommand in command.Commands) + { + if (string.Equals(subcommand.Name, arg, StringComparison.OrdinalIgnoreCase)) + { + processed = true; + command = subcommand; + break; + } + } + + // If we detect a subcommand + if (command != currentCommand) + { + processed = true; + } + } + + if (!processed) + { + if (arguments == null) + { + arguments = new CommandArgumentEnumerator(command.Arguments.GetEnumerator()); + } + if (arguments.MoveNext()) + { + processed = true; + arguments.Current.Values.Add(arg); + } + } + + if (!processed) + { + if (HandleUnexpectedArg(command, args, index, argTypeName: "command or argument")) + { + continue; + } + + break; + } + } + + if (option != null) + { + command.ShowHint(); + throw new CommandParsingException(command, $"Missing value for option '{option.LongName}'"); + } + + return command.Invoke(); + } + + // Helper method that adds a help option + public CommandOption HelpOption(string template) + { + // Help option is special because we stop parsing once we see it + // So we store it separately for further use + OptionHelp = Option(template, "Show help information", CommandOptionType.NoValue); + + return OptionHelp; + } + + public CommandOption VersionOption(string template, + string shortFormVersion, + string longFormVersion = null) + { + if (longFormVersion == null) + { + return VersionOption(template, () => shortFormVersion); + } + else + { + return VersionOption(template, () => shortFormVersion, () => longFormVersion); + } + } + + // Helper method that adds a version option + public CommandOption VersionOption(string template, + Func shortFormVersionGetter, + Func longFormVersionGetter = null) + { + // Version option is special because we stop parsing once we see it + // So we store it separately for further use + OptionVersion = Option(template, "Show version information", CommandOptionType.NoValue); + ShortVersionGetter = shortFormVersionGetter; + LongVersionGetter = longFormVersionGetter ?? shortFormVersionGetter; + + return OptionVersion; + } + + // Show short hint that reminds users to use help option + public void ShowHint() + { + if (OptionHelp != null) + { + Out.WriteLine(string.Format("Specify --{0} for a list of available options and commands.", OptionHelp.LongName)); + } + } + + // Show full help + public void ShowHelp(string commandName = null) + { + for (var cmd = this; cmd != null; cmd = cmd.Parent) + { + cmd.IsShowingInformation = true; + } + + Out.WriteLine(GetHelpText(commandName)); + } + + public virtual string GetHelpText(string commandName = null) + { + var headerBuilder = new StringBuilder("Usage:"); + for (var cmd = this; cmd != null; cmd = cmd.Parent) + { + headerBuilder.Insert(6, string.Format(" {0}", cmd.Name)); + } + + CommandLineApplication target; + + if (commandName == null || string.Equals(Name, commandName, StringComparison.OrdinalIgnoreCase)) + { + target = this; + } + else + { + target = Commands.SingleOrDefault(cmd => string.Equals(cmd.Name, commandName, StringComparison.OrdinalIgnoreCase)); + + if (target != null) + { + headerBuilder.AppendFormat(" {0}", commandName); + } + else + { + // The command name is invalid so don't try to show help for something that doesn't exist + target = this; + } + + } + + var optionsBuilder = new StringBuilder(); + var commandsBuilder = new StringBuilder(); + var argumentsBuilder = new StringBuilder(); + + var arguments = target.Arguments.Where(a => a.ShowInHelpText).ToList(); + if (arguments.Any()) + { + headerBuilder.Append(" [arguments]"); + + argumentsBuilder.AppendLine(); + argumentsBuilder.AppendLine("Arguments:"); + var maxArgLen = arguments.Max(a => a.Name.Length); + var outputFormat = string.Format(" {{0, -{0}}}{{1}}", maxArgLen + 2); + foreach (var arg in arguments) + { + argumentsBuilder.AppendFormat(outputFormat, arg.Name, arg.Description); + argumentsBuilder.AppendLine(); + } + } + + var options = target.GetOptions().Where(o => o.ShowInHelpText).ToList(); + if (options.Any()) + { + headerBuilder.Append(" [options]"); + + optionsBuilder.AppendLine(); + optionsBuilder.AppendLine("Options:"); + var maxOptLen = options.Max(o => o.Template.Length); + var outputFormat = string.Format(" {{0, -{0}}}{{1}}", maxOptLen + 2); + foreach (var opt in options) + { + optionsBuilder.AppendFormat(outputFormat, opt.Template, opt.Description); + optionsBuilder.AppendLine(); + } + } + + var commands = target.Commands.Where(c => c.ShowInHelpText).ToList(); + if (commands.Any()) + { + headerBuilder.Append(" [command]"); + + commandsBuilder.AppendLine(); + commandsBuilder.AppendLine("Commands:"); + var maxCmdLen = commands.Max(c => c.Name.Length); + var outputFormat = string.Format(" {{0, -{0}}}{{1}}", maxCmdLen + 2); + foreach (var cmd in commands.OrderBy(c => c.Name)) + { + commandsBuilder.AppendFormat(outputFormat, cmd.Name, cmd.Description); + commandsBuilder.AppendLine(); + } + + if (OptionHelp != null) + { + commandsBuilder.AppendLine(); + commandsBuilder.AppendFormat($"Use \"{target.Name} [command] --{OptionHelp.LongName}\" for more information about a command."); + commandsBuilder.AppendLine(); + } + } + + if (target.AllowArgumentSeparator) + { + headerBuilder.Append(" [[--] ...]"); + } + + headerBuilder.AppendLine(); + + var nameAndVersion = new StringBuilder(); + nameAndVersion.AppendLine(GetFullNameAndVersion()); + nameAndVersion.AppendLine(); + + return nameAndVersion.ToString() + + headerBuilder.ToString() + + argumentsBuilder.ToString() + + optionsBuilder.ToString() + + commandsBuilder.ToString() + + target.ExtendedHelpText; + } + + public void ShowVersion() + { + for (var cmd = this; cmd != null; cmd = cmd.Parent) + { + cmd.IsShowingInformation = true; + } + + Out.WriteLine(FullName); + Out.WriteLine(LongVersionGetter()); + } + + public string GetFullNameAndVersion() + { + return ShortVersionGetter == null ? FullName : string.Format("{0} {1}", FullName, ShortVersionGetter()); + } + + public void ShowRootCommandFullNameAndVersion() + { + var rootCmd = this; + while (rootCmd.Parent != null) + { + rootCmd = rootCmd.Parent; + } + + Out.WriteLine(rootCmd.GetFullNameAndVersion()); + Out.WriteLine(); + } + + private bool HandleUnexpectedArg( + CommandLineApplication command, + string[] args, + int index, + string argTypeName, + bool ignoreContinueAfterUnexpectedArg = false) + { + if (command._throwOnUnexpectedArg) + { + command.ShowHint(); + throw new CommandParsingException(command, $"Unrecognized {argTypeName} '{args[index]}'"); + } + else if (_continueAfterUnexpectedArg && !ignoreContinueAfterUnexpectedArg) + { + // Store argument for further use. + command.RemainingArguments.Add(args[index]); + return true; + } + else + { + // Store all remaining arguments for later use. + command.RemainingArguments.AddRange(new ArraySegment(args, index, args.Length - index)); + return false; + } + } + + private class CommandArgumentEnumerator : IEnumerator + { + private readonly IEnumerator _enumerator; + + public CommandArgumentEnumerator(IEnumerator enumerator) + { + _enumerator = enumerator; + } + + public CommandArgument Current + { + get + { + return _enumerator.Current; + } + } + + object IEnumerator.Current + { + get + { + return Current; + } + } + + public void Dispose() + { + _enumerator.Dispose(); + } + + public bool MoveNext() + { + if (Current == null || !Current.MultipleValues) + { + return _enumerator.MoveNext(); + } + + // If current argument allows multiple values, we don't move forward and + // all later values will be added to current CommandArgument.Values + return true; + } + + public void Reset() + { + _enumerator.Reset(); + } + } + } +} diff --git a/src/Shared/CommandLineUtils/CommandLine/CommandOption.cs b/src/Shared/CommandLineUtils/CommandLine/CommandOption.cs new file mode 100644 index 0000000000..4e663773cc --- /dev/null +++ b/src/Shared/CommandLineUtils/CommandLine/CommandOption.cs @@ -0,0 +1,108 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.Extensions.CommandLineUtils +{ + internal class CommandOption + { + public CommandOption(string template, CommandOptionType optionType) + { + Template = template; + OptionType = optionType; + Values = new List(); + + foreach (var part in Template.Split(new[] { ' ', '|' }, StringSplitOptions.RemoveEmptyEntries)) + { + if (part.StartsWith("--")) + { + LongName = part.Substring(2); + } + else if (part.StartsWith("-")) + { + var optName = part.Substring(1); + + // If there is only one char and it is not an English letter, it is a symbol option (e.g. "-?") + if (optName.Length == 1 && !IsEnglishLetter(optName[0])) + { + SymbolName = optName; + } + else + { + ShortName = optName; + } + } + else if (part.StartsWith("<") && part.EndsWith(">")) + { + ValueName = part.Substring(1, part.Length - 2); + } + else + { + throw new ArgumentException($"Invalid template pattern '{template}'", nameof(template)); + } + } + + if (string.IsNullOrEmpty(LongName) && string.IsNullOrEmpty(ShortName) && string.IsNullOrEmpty(SymbolName)) + { + throw new ArgumentException($"Invalid template pattern '{template}'", nameof(template)); + } + } + + public string Template { get; set; } + public string ShortName { get; set; } + public string LongName { get; set; } + public string SymbolName { get; set; } + public string ValueName { get; set; } + public string Description { get; set; } + public List Values { get; private set; } + public CommandOptionType OptionType { get; private set; } + public bool ShowInHelpText { get; set; } = true; + public bool Inherited { get; set; } + + public bool TryParse(string value) + { + switch (OptionType) + { + case CommandOptionType.MultipleValue: + Values.Add(value); + break; + case CommandOptionType.SingleValue: + if (Values.Any()) + { + return false; + } + Values.Add(value); + break; + case CommandOptionType.NoValue: + if (value != null) + { + return false; + } + // Add a value to indicate that this option was specified + Values.Add("on"); + break; + default: + break; + } + return true; + } + + public bool HasValue() + { + return Values.Any(); + } + + public string Value() + { + return HasValue() ? Values[0] : null; + } + + private bool IsEnglishLetter(char c) + { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); + } + } +} diff --git a/src/Shared/CommandLineUtils/CommandLine/CommandOptionType.cs b/src/Shared/CommandLineUtils/CommandLine/CommandOptionType.cs new file mode 100644 index 0000000000..76fdf38f5e --- /dev/null +++ b/src/Shared/CommandLineUtils/CommandLine/CommandOptionType.cs @@ -0,0 +1,13 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + + +namespace Microsoft.Extensions.CommandLineUtils +{ + internal enum CommandOptionType + { + MultipleValue, + SingleValue, + NoValue + } +} diff --git a/src/Shared/CommandLineUtils/CommandLine/CommandParsingException.cs b/src/Shared/CommandLineUtils/CommandLine/CommandParsingException.cs new file mode 100644 index 0000000000..2be62b87fa --- /dev/null +++ b/src/Shared/CommandLineUtils/CommandLine/CommandParsingException.cs @@ -0,0 +1,18 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.Extensions.CommandLineUtils +{ + internal class CommandParsingException : Exception + { + public CommandParsingException(CommandLineApplication command, string message) + : base(message) + { + Command = command; + } + + public CommandLineApplication Command { get; } + } +} diff --git a/src/Shared/CommandLineUtils/Utilities/ArgumentEscaper.cs b/src/Shared/CommandLineUtils/Utilities/ArgumentEscaper.cs new file mode 100644 index 0000000000..92543e7f23 --- /dev/null +++ b/src/Shared/CommandLineUtils/Utilities/ArgumentEscaper.cs @@ -0,0 +1,109 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Microsoft.Extensions.CommandLineUtils +{ + /// + /// A utility for escaping arguments for new processes. + /// + internal static class ArgumentEscaper + { + /// + /// Undo the processing which took place to create string[] args in Main, so that the next process will + /// receive the same string[] args. + /// + /// + /// See https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/ + /// + /// The arguments to concatenate. + /// The escaped arguments, concatenated. + public static string EscapeAndConcatenate(IEnumerable args) + => string.Join(" ", args.Select(EscapeSingleArg)); + + private static string EscapeSingleArg(string arg) + { + var sb = new StringBuilder(); + + var needsQuotes = ShouldSurroundWithQuotes(arg); + var isQuoted = needsQuotes || IsSurroundedWithQuotes(arg); + + if (needsQuotes) + { + sb.Append('"'); + } + + for (int i = 0; i < arg.Length; ++i) + { + var backslashes = 0; + + // Consume all backslashes + while (i < arg.Length && arg[i] == '\\') + { + backslashes++; + i++; + } + + if (i == arg.Length && isQuoted) + { + // Escape any backslashes at the end of the arg when the argument is also quoted. + // This ensures the outside quote is interpreted as an argument delimiter + sb.Append('\\', 2 * backslashes); + } + else if (i == arg.Length) + { + // At then end of the arg, which isn't quoted, + // just add the backslashes, no need to escape + sb.Append('\\', backslashes); + } + else if (arg[i] == '"') + { + // Escape any preceding backslashes and the quote + sb.Append('\\', (2 * backslashes) + 1); + sb.Append('"'); + } + else + { + // Output any consumed backslashes and the character + sb.Append('\\', backslashes); + sb.Append(arg[i]); + } + } + + if (needsQuotes) + { + sb.Append('"'); + } + + return sb.ToString(); + } + + private static bool ShouldSurroundWithQuotes(string argument) + { + // Don't quote already quoted strings + if (IsSurroundedWithQuotes(argument)) + { + return false; + } + + // Only quote if whitespace exists in the string + return ContainsWhitespace(argument); + } + + private static bool IsSurroundedWithQuotes(string argument) + { + if (argument.Length <= 1) + { + return false; + } + + return argument[0] == '"' && argument[argument.Length - 1] == '"'; + } + + private static bool ContainsWhitespace(string argument) + => argument.IndexOfAny(new[] { ' ', '\t', '\n' }) >= 0; + } +} diff --git a/src/Shared/CommandLineUtils/Utilities/DotNetMuxer.cs b/src/Shared/CommandLineUtils/Utilities/DotNetMuxer.cs new file mode 100644 index 0000000000..52c98b5eb2 --- /dev/null +++ b/src/Shared/CommandLineUtils/Utilities/DotNetMuxer.cs @@ -0,0 +1,58 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +// System.AppContext.GetData is not available in these frameworks +#if !NET451 && !NET452 && !NET46 && !NET461 + +using System; +using System.Diagnostics; +using System.IO; +using System.Runtime.InteropServices; + +namespace Microsoft.Extensions.CommandLineUtils +{ + /// + /// Utilities for finding the "dotnet.exe" file from the currently running .NET Core application + /// + internal static class DotNetMuxer + { + private const string MuxerName = "dotnet"; + + static DotNetMuxer() + { + MuxerPath = TryFindMuxerPath(); + } + + /// + /// The full filepath to the .NET Core muxer. + /// + public static string MuxerPath { get; } + + /// + /// Finds the full filepath to the .NET Core muxer, + /// or returns a string containing the default name of the .NET Core muxer ('dotnet'). + /// + /// The path or a string named 'dotnet'. + public static string MuxerPathOrDefault() + => MuxerPath ?? MuxerName; + + private static string TryFindMuxerPath() + { + var fileName = MuxerName; + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + fileName += ".exe"; + } + + var mainModule = Process.GetCurrentProcess().MainModule; + if (!string.IsNullOrEmpty(mainModule?.FileName) + && Path.GetFileName(mainModule.FileName).Equals(fileName, StringComparison.OrdinalIgnoreCase)) + { + return mainModule.FileName; + } + + return null; + } + } +} +#endif diff --git a/src/Shared/Diagnostics/BaseView.cs b/src/Shared/Diagnostics/BaseView.cs index 64b40242e0..cb24122c0a 100644 --- a/src/Shared/Diagnostics/BaseView.cs +++ b/src/Shared/Diagnostics/BaseView.cs @@ -102,11 +102,11 @@ namespace Microsoft.AspNetCore.DiagnosticsViewPage.Views private string AttributeEnding { get; set; } - protected void BeginWriteAttribute(string name, string begining, int startPosition, string ending, int endPosition, int thingy) + protected void BeginWriteAttribute(string name, string beginning, int startPosition, string ending, int endPosition, int thingy) { Debug.Assert(string.IsNullOrEmpty(AttributeEnding)); - Output.Write(begining); + Output.Write(beginning); AttributeEnding = ending; } diff --git a/src/Shared/E2ETesting/BrowserFixture.cs b/src/Shared/E2ETesting/BrowserFixture.cs index 7808176318..8b3239c5df 100644 --- a/src/Shared/E2ETesting/BrowserFixture.cs +++ b/src/Shared/E2ETesting/BrowserFixture.cs @@ -118,7 +118,7 @@ namespace Microsoft.AspNetCore.E2ETesting // To prevent this we let the client attempt several times to connect to the server, increasing // the max allowed timeout for a command on each attempt linearly. // This can also be caused if many tests are running concurrently, we might want to manage - // chrome and chromedriver instances more aggresively if we have to. + // chrome and chromedriver instances more aggressively if we have to. // Additionally, if we think the selenium server has become irresponsive, we could spin up // replace the current selenium server instance and let a new instance take over for the // remaining tests. diff --git a/src/Shared/E2ETesting/E2ETesting.props b/src/Shared/E2ETesting/E2ETesting.props index e31cbd93ac..0e6552e7ec 100644 --- a/src/Shared/E2ETesting/E2ETesting.props +++ b/src/Shared/E2ETesting/E2ETesting.props @@ -4,7 +4,10 @@ $(DefaultItemExcludes);node_modules\** $([MSBuild]::NormalizeDirectory('$(ArtifactsTestResultsDir)','$(MSBuildProjectName)')) $([MSBuild]::EnsureTrailingSlash('$(RepoRoot)'))artifacts\tmp\selenium\ - true + true + + + $([MSBuild]::NormalizePath($(MSBuildThisFileDirectory)selenium-config.json)) true @@ -39,12 +42,12 @@ <_Parameter1>Microsoft.AspNetCore.E2ETesting.CI - <_Parameter2>$(ContinuousIntegrationBuild) + <_Parameter2>$(ContinuousIntegrationBuild) - + <_Parameter1>Microsoft.AspNetCore.E2ETesting.ScreenshotsPath - <_Parameter2>$(SeleniumScreenShotsFolderPath) + <_Parameter2>$(SeleniumScreenShotsFolderPath) diff --git a/src/Shared/E2ETesting/E2ETesting.targets b/src/Shared/E2ETesting/E2ETesting.targets index 500d910a30..f3e73276b0 100644 --- a/src/Shared/E2ETesting/E2ETesting.targets +++ b/src/Shared/E2ETesting/E2ETesting.targets @@ -38,7 +38,7 @@ - + @@ -51,7 +51,7 @@ <_PackageJsonLinesContent>@(_PackageJsonLines) - <_PackageJsonSeleniumPackage>"selenium-standalone": "^6.15.4" + <_PackageJsonSeleniumPackage>"selenium-standalone": "^6.17.0" Microsoft.AspNetCore.Testing.Selenium.Supported <_Parameter2>$(_SeleniumE2ETestsSupportedAttributeValue) + + + <_Parameter1>Microsoft.AspNetCore.Testing.SeleniumConfigPath + <_Parameter2>$(SeleniumConfigPath) + diff --git a/src/Shared/E2ETesting/SeleniumStandaloneServer.cs b/src/Shared/E2ETesting/SeleniumStandaloneServer.cs index d62b3ffb00..fd0878aec2 100644 --- a/src/Shared/E2ETesting/SeleniumStandaloneServer.cs +++ b/src/Shared/E2ETesting/SeleniumStandaloneServer.cs @@ -86,10 +86,20 @@ namespace Microsoft.AspNetCore.E2ETesting var port = FindAvailablePort(); var uri = new UriBuilder("http", "localhost", port, "/wd/hub").Uri; + var seleniumConfigPath = typeof(SeleniumStandaloneServer).Assembly + .GetCustomAttributes() + .FirstOrDefault(k => k.Key == "Microsoft.AspNetCore.Testing.SeleniumConfigPath") + ?.Value; + + if (seleniumConfigPath == null) + { + throw new InvalidOperationException("Selenium config path not configured. Does this project import the E2ETesting.targets?"); + } + var psi = new ProcessStartInfo { FileName = "npm", - Arguments = $"run selenium-standalone start -- -- -port {port}", + Arguments = $"run selenium-standalone start -- --config \"{seleniumConfigPath}\" -- -port {port}", RedirectStandardOutput = true, RedirectStandardError = true, }; @@ -103,6 +113,13 @@ namespace Microsoft.AspNetCore.E2ETesting // It's important that we get the folder value before we start the process to prevent // untracked processes when the tracking folder is not correctly configure. var trackingFolder = GetProcessTrackingFolder(); + if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("helix"))) + { + // Just create a random tracking folder on helix + trackingFolder = Path.Combine(Directory.GetCurrentDirectory(), Path.GetRandomFileName()); + Directory.CreateDirectory(trackingFolder); + } + if (!Directory.Exists(trackingFolder)) { throw new InvalidOperationException($"Invalid tracking folder. Set the 'SeleniumProcessTrackingFolder' MSBuild property to a valid folder."); @@ -191,7 +208,7 @@ Captured output lines: private static Process StartSentinelProcess(Process process, string sentinelFile, int timeout) { - // This sentinel process will start and will kill any roge selenium server that want' torn down + // This sentinel process will start and will kill any rouge selenium server that want' torn down // via normal means. var psi = new ProcessStartInfo { diff --git a/src/Shared/E2ETesting/selenium-config.json b/src/Shared/E2ETesting/selenium-config.json new file mode 100644 index 0000000000..95c078e2c7 --- /dev/null +++ b/src/Shared/E2ETesting/selenium-config.json @@ -0,0 +1,6 @@ +{ + "drivers": { + "chrome": {} + }, + "ignoreExtraDrivers": true +} \ No newline at end of file diff --git a/src/Shared/ErrorPage/GeneratePage.ps1 b/src/Shared/ErrorPage/GeneratePage.ps1 index 8bc0f2c07c..94e6746169 100644 --- a/src/Shared/ErrorPage/GeneratePage.ps1 +++ b/src/Shared/ErrorPage/GeneratePage.ps1 @@ -2,7 +2,7 @@ param( [Parameter(Mandatory = $true)][string]$ToolingRepoPath ) -$ToolPath = Join-Path $ToolingRepoPath "artifacts\bin\RazorPageGenerator\Debug\netcoreapp3.1\dotnet-razorpagegenerator.exe" +$ToolPath = Join-Path $ToolingRepoPath "artifacts\bin\RazorPageGenerator\Debug\netcoreapp5.0\dotnet-razorpagegenerator.exe" if (!(Test-Path $ToolPath)) { throw "Unable to find razor page generator tool at $ToolPath" diff --git a/src/Shared/ErrorPage/README.md b/src/Shared/ErrorPage/README.md index 1f74a4e33a..cc0dbc377c 100644 --- a/src/Shared/ErrorPage/README.md +++ b/src/Shared/ErrorPage/README.md @@ -1,13 +1,13 @@ # Error Page -This folder is shared among multiple projects. The `ErrorPage.Designer.cs` and `ErrorPageModel.cs` files are used to render this view. The `ErrorPage.Designer.cs` file is generated by rendering `src/Views/ErrorPage.cshtml` using the [RazorPageGenerator](https://github.com/aspnet/AspNetCore-Tooling/tree/master/src/Razor/src/RazorPageGenerator) tool. +This folder is shared among multiple projects. The `ErrorPage.Designer.cs` and `ErrorPageModel.cs` files are used to render this view. The `ErrorPage.Designer.cs` file is generated by rendering `src/Views/ErrorPage.cshtml` using the [RazorPageGenerator](https://github.com/dotnet/aspnetcore-tooling/tree/master/src/Razor/src/RazorPageGenerator) tool. ## Making changes to ErrorPage.cshtml -1. Clone aspnet/AspNetCore-Tooling +1. Clone dotnet/aspnetcore-tooling 1. Run `./build.cmd` in **that repo** 1. Edit the file -1. Run the `GeneratePage` script, passing in the path to the `aspnet/AspNetCore-Tooling` repo root. +1. Run the `GeneratePage` script, passing in the path to the `dotnet/aspnetcore-tooling` repo root. ``` .\GeneratePage -ToolingRepoPath C:\Code\aspnet\AspNetCore-Tooling diff --git a/src/Shared/ErrorPage/Views/ErrorPage.css b/src/Shared/ErrorPage/Views/ErrorPage.css index 4d3287c12d..1b16b5792c 100644 --- a/src/Shared/ErrorPage/Views/ErrorPage.css +++ b/src/Shared/ErrorPage/Views/ErrorPage.css @@ -98,6 +98,12 @@ body .location { background-color: #fbfbfb; } +#stackpage .frame .source .highlight { + border-left: 3px solid red; + margin-left: -3px; + font-weight: bold; +} + #stackpage .frame .source .highlight li span { color: #FF0000; } diff --git a/src/Shared/HashCodeCombiner/HashCodeCombiner.cs b/src/Shared/HashCodeCombiner/HashCodeCombiner.cs new file mode 100644 index 0000000000..4df8b46b05 --- /dev/null +++ b/src/Shared/HashCodeCombiner/HashCodeCombiner.cs @@ -0,0 +1,84 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections; +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +namespace Microsoft.Extensions.Internal +{ + internal struct HashCodeCombiner + { + private long _combinedHash64; + + public int CombinedHash + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get { return _combinedHash64.GetHashCode(); } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private HashCodeCombiner(long seed) + { + _combinedHash64 = seed; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Add(IEnumerable e) + { + if (e == null) + { + Add(0); + } + else + { + var count = 0; + foreach (object o in e) + { + Add(o); + count++; + } + Add(count); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator int(HashCodeCombiner self) + { + return self.CombinedHash; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Add(int i) + { + _combinedHash64 = ((_combinedHash64 << 5) + _combinedHash64) ^ i; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Add(string s) + { + var hashCode = (s != null) ? s.GetHashCode() : 0; + Add(hashCode); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Add(object o) + { + var hashCode = (o != null) ? o.GetHashCode() : 0; + Add(hashCode); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Add(TValue value, IEqualityComparer comparer) + { + var hashCode = value != null ? comparer.GetHashCode(value) : 0; + Add(hashCode); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static HashCodeCombiner Start() + { + return new HashCodeCombiner(0x1505L); + } + } +} diff --git a/src/Shared/HostFactoryResolver/HostFactoryResolver.cs b/src/Shared/HostFactoryResolver/HostFactoryResolver.cs new file mode 100644 index 0000000000..cb9f811237 --- /dev/null +++ b/src/Shared/HostFactoryResolver/HostFactoryResolver.cs @@ -0,0 +1,112 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Reflection; + +namespace Microsoft.Extensions.Hosting +{ + internal class HostFactoryResolver + { + public static readonly string BuildWebHost = nameof(BuildWebHost); + public static readonly string CreateWebHostBuilder = nameof(CreateWebHostBuilder); + public static readonly string CreateHostBuilder = nameof(CreateHostBuilder); + + public static Func ResolveWebHostFactory(Assembly assembly) + { + return ResolveFactory(assembly, BuildWebHost); + } + + public static Func ResolveWebHostBuilderFactory(Assembly assembly) + { + return ResolveFactory(assembly, CreateWebHostBuilder); + } + + public static Func ResolveHostBuilderFactory(Assembly assembly) + { + return ResolveFactory(assembly, CreateHostBuilder); + } + + private static Func ResolveFactory(Assembly assembly, string name) + { + var programType = assembly?.EntryPoint?.DeclaringType; + if (programType == null) + { + return null; + } + + var factory = programType.GetTypeInfo().GetDeclaredMethod(name); + if (!IsFactory(factory)) + { + return null; + } + + return args => (T)factory.Invoke(null, new object[] { args }); + } + + // TReturn Factory(string[] args); + private static bool IsFactory(MethodInfo factory) + { + return factory != null + && typeof(TReturn).IsAssignableFrom(factory.ReturnType) + && factory.GetParameters().Length == 1 + && typeof(string[]).Equals(factory.GetParameters()[0].ParameterType); + } + + // Used by EF tooling without any Hosting references. Looses some return type safety checks. + public static Func ResolveServiceProviderFactory(Assembly assembly) + { + // Prefer the older patterns by default for back compat. + var webHostFactory = ResolveWebHostFactory(assembly); + if (webHostFactory != null) + { + return args => + { + var webHost = webHostFactory(args); + return GetServiceProvider(webHost); + }; + } + + var webHostBuilderFactory = ResolveWebHostBuilderFactory(assembly); + if (webHostBuilderFactory != null) + { + return args => + { + var webHostBuilder = webHostBuilderFactory(args); + var webHost = Build(webHostBuilder); + return GetServiceProvider(webHost); + }; + } + + var hostBuilderFactory = ResolveHostBuilderFactory(assembly); + if (hostBuilderFactory != null) + { + return args => + { + var hostBuilder = hostBuilderFactory(args); + var host = Build(hostBuilder); + return GetServiceProvider(host); + }; + } + + return null; + } + + private static object Build(object builder) + { + var buildMethod = builder.GetType().GetMethod("Build"); + return buildMethod?.Invoke(builder, Array.Empty()); + } + + private static IServiceProvider GetServiceProvider(object host) + { + if (host == null) + { + return null; + } + var hostType = host.GetType(); + var servicesProperty = hostType.GetTypeInfo().GetDeclaredProperty("Services"); + return (IServiceProvider)servicesProperty.GetValue(host); + } + } +} diff --git a/src/Shared/Http2cat/HPackHeaderWriter.cs b/src/Shared/Http2cat/HPackHeaderWriter.cs new file mode 100644 index 0000000000..27772caa72 --- /dev/null +++ b/src/Shared/Http2cat/HPackHeaderWriter.cs @@ -0,0 +1,91 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Net.Http.HPack; + +namespace Microsoft.AspNetCore.Http2Cat +{ + internal static class HPackHeaderWriter + { + /// + /// Begin encoding headers in the first HEADERS frame. + /// + public static bool BeginEncodeHeaders(int statusCode, IEnumerator> headersEnumerator, Span buffer, out int length) + { + if (!HPackEncoder.EncodeStatusHeader(statusCode, buffer, out var statusCodeLength)) + { + throw new HPackEncodingException(SR.net_http_hpack_encode_failure); + } + + if (!headersEnumerator.MoveNext()) + { + length = statusCodeLength; + return true; + } + + // We're ok with not throwing if no headers were encoded because we've already encoded the status. + // There is a small chance that the header will encode if there is no other content in the next HEADERS frame. + var done = EncodeHeaders(headersEnumerator, buffer.Slice(statusCodeLength), throwIfNoneEncoded: false, out var headersLength); + length = statusCodeLength + headersLength; + + return done; + } + + /// + /// Begin encoding headers in the first HEADERS frame. + /// + public static bool BeginEncodeHeaders(IEnumerator> headersEnumerator, Span buffer, out int length) + { + if (!headersEnumerator.MoveNext()) + { + length = 0; + return true; + } + + return EncodeHeaders(headersEnumerator, buffer, throwIfNoneEncoded: true, out length); + } + + /// + /// Continue encoding headers in the next HEADERS frame. The enumerator should already have a current value. + /// + public static bool ContinueEncodeHeaders(IEnumerator> headersEnumerator, Span buffer, out int length) + { + return EncodeHeaders(headersEnumerator, buffer, throwIfNoneEncoded: true, out length); + } + + private static bool EncodeHeaders(IEnumerator> headersEnumerator, Span buffer, bool throwIfNoneEncoded, out int length) + { + var currentLength = 0; + do + { + if (!EncodeHeader(headersEnumerator.Current.Key, headersEnumerator.Current.Value, buffer.Slice(currentLength), out int headerLength)) + { + // The the header wasn't written and no headers have been written then the header is too large. + // Throw an error to avoid an infinite loop of attempting to write large header. + if (currentLength == 0 && throwIfNoneEncoded) + { + throw new HPackEncodingException(SR.net_http_hpack_encode_failure); + } + + length = currentLength; + return false; + } + + currentLength += headerLength; + } + while (headersEnumerator.MoveNext()); + + length = currentLength; + + return true; + } + + private static bool EncodeHeader(string name, string value, Span buffer, out int length) + { + return HPackEncoder.EncodeLiteralHeaderFieldWithoutIndexingNewName(name, value, buffer, out length); + } + } +} diff --git a/src/Shared/Http2cat/Http2CatHostedService.cs b/src/Shared/Http2cat/Http2CatHostedService.cs new file mode 100644 index 0000000000..55e496fa10 --- /dev/null +++ b/src/Shared/Http2cat/Http2CatHostedService.cs @@ -0,0 +1,129 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.IO.Pipelines; +using System.Linq; +using System.Net; +using System.Net.Security; +using System.Security.Authentication; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Connections; +using Microsoft.AspNetCore.Connections.Features; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; +using Microsoft.AspNetCore.Server.Kestrel.Https.Internal; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace Microsoft.AspNetCore.Http2Cat +{ + internal class Http2CatHostedService : IHostedService + { + private readonly IConnectionFactory _connectionFactory; + private readonly ILogger _logger; + private readonly CancellationTokenSource _stopTokenSource = new CancellationTokenSource(); + private Task _backgroundTask; + + public Http2CatHostedService(IConnectionFactory connectionFactory, ILogger logger, + IOptions options, IHostApplicationLifetime hostApplicationLifetime) + { + _connectionFactory = connectionFactory; + _logger = logger; + HostApplicationLifetime = hostApplicationLifetime; + Options = options.Value; + } + + public IHostApplicationLifetime HostApplicationLifetime { get; } + private Http2CatOptions Options { get; } + + public Task StartAsync(CancellationToken cancellationToken) + { + _backgroundTask = RunAsync(); + return Task.CompletedTask; + } + + public Task StopAsync(CancellationToken cancellationToken) + { + _stopTokenSource.Cancel(); + return _backgroundTask; + } + + private async Task RunAsync() + { + try + { + var address = BindingAddress.Parse(Options.Url); + + if (!IPAddress.TryParse(address.Host, out var ip)) + { + ip = Dns.GetHostEntry(address.Host).AddressList.First(); + } + + var endpoint = new IPEndPoint(ip, address.Port); + + _logger.LogInformation($"Connecting to '{endpoint}'."); + + await using var context = await _connectionFactory.ConnectAsync(endpoint); + + _logger.LogInformation($"Connected to '{endpoint}'."); + + var originalTransport = context.Transport; + IAsyncDisposable sslState = null; + if (address.Scheme.Equals("https", StringComparison.OrdinalIgnoreCase)) + { + _logger.LogInformation("Starting TLS handshake."); + + var memoryPool = context.Features.Get()?.MemoryPool; + var inputPipeOptions = new StreamPipeReaderOptions(memoryPool, memoryPool.GetMinimumSegmentSize(), memoryPool.GetMinimumAllocSize(), leaveOpen: true); + var outputPipeOptions = new StreamPipeWriterOptions(pool: memoryPool, leaveOpen: true); + + var sslDuplexPipe = new SslDuplexPipe(context.Transport, inputPipeOptions, outputPipeOptions); + var sslStream = sslDuplexPipe.Stream; + sslState = sslDuplexPipe; + + context.Transport = sslDuplexPipe; + + await sslStream.AuthenticateAsClientAsync(new SslClientAuthenticationOptions + { + TargetHost = address.Host, + RemoteCertificateValidationCallback = (_, __, ___, ____) => true, + ApplicationProtocols = new List { SslApplicationProtocol.Http2 }, + EnabledSslProtocols = SslProtocols.Tls12, + }, CancellationToken.None); + + _logger.LogInformation($"TLS handshake completed successfully."); + } + + var http2Utilities = new Http2Utilities(context, _logger, _stopTokenSource.Token); + + try + { + await Options.Scenaro(http2Utilities); + } + catch (Exception ex) + { + _logger.LogError(ex, "App error"); + throw; + } + finally + { + // Unwind Https for shutdown. This must happen before the context goes out of scope or else DisposeAsync will never complete + context.Transport = originalTransport; + + if (sslState != null) + { + await sslState.DisposeAsync(); + } + } + } + finally + { + HostApplicationLifetime.StopApplication(); + } + } + } +} diff --git a/src/Shared/Http2cat/Http2CatIHostBuilderExtensions.cs b/src/Shared/Http2cat/Http2CatIHostBuilderExtensions.cs new file mode 100644 index 0000000000..9a37aafffa --- /dev/null +++ b/src/Shared/Http2cat/Http2CatIHostBuilderExtensions.cs @@ -0,0 +1,26 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http2Cat; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.Extensions.Hosting +{ + internal static class Http2CatIHostBuilderExtensions + { + public static IHostBuilder UseHttp2Cat(this IHostBuilder hostBuilder, string address, Func scenario) + { + hostBuilder.ConfigureServices(services => + { + services.UseHttp2Cat(options => + { + options.Url = address; + options.Scenaro = scenario; + }); + }); + return hostBuilder; + } + } +} diff --git a/src/Shared/Http2cat/Http2CatIServiceCollectionExtensions.cs b/src/Shared/Http2cat/Http2CatIServiceCollectionExtensions.cs new file mode 100644 index 0000000000..c4c2c5ca4e --- /dev/null +++ b/src/Shared/Http2cat/Http2CatIServiceCollectionExtensions.cs @@ -0,0 +1,21 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNetCore.Connections; +using Microsoft.AspNetCore.Http2Cat; +using Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets; + +namespace Microsoft.Extensions.DependencyInjection +{ + internal static class Http2CatIServiceCollectionExtensions + { + public static IServiceCollection UseHttp2Cat(this IServiceCollection services, Action configureOptions) + { + services.AddSingleton(); + services.AddHostedService(); + services.Configure(configureOptions); + return services; + } + } +} diff --git a/src/Shared/Http2cat/Http2CatOptions.cs b/src/Shared/Http2cat/Http2CatOptions.cs new file mode 100644 index 0000000000..81e2b082f2 --- /dev/null +++ b/src/Shared/Http2cat/Http2CatOptions.cs @@ -0,0 +1,14 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Threading.Tasks; + +namespace Microsoft.AspNetCore.Http2Cat +{ + internal class Http2CatOptions + { + public string Url { get; set; } + public Func Scenaro { get; set; } + } +} diff --git a/src/Shared/Http2cat/Http2CatReadMe.md b/src/Shared/Http2cat/Http2CatReadMe.md new file mode 100644 index 0000000000..1bd01c17ec --- /dev/null +++ b/src/Shared/Http2cat/Http2CatReadMe.md @@ -0,0 +1,7 @@ +## Http2Cat + +Http2Cat is a low level Http2 testing framework designed to excersize a server with frame level control. This can be useful for unit testing and compat testing. + +The framework is distributed as internal sources since it shares the basic building blocks from Kestrel's Http2 implementation (frames, enum flags, frame reading and writing, etc.). InternalsVisibleTo should not be used, any needed components should be moved to one of the shared code directories. + +This Http2Cat folder contains non-production code used in the test client. The shared production code is kept in separate folders. diff --git a/src/Shared/Http2cat/Http2Utilities.cs b/src/Shared/Http2cat/Http2Utilities.cs new file mode 100644 index 0000000000..13a6ba4fc5 --- /dev/null +++ b/src/Shared/Http2cat/Http2Utilities.cs @@ -0,0 +1,1092 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Buffers; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.IO; +using System.IO.Pipelines; +using System.Linq; +using System.Net.Http; +using System.Net.Http.HPack; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Connections; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; +using Microsoft.Extensions.Logging; +using Microsoft.Net.Http.Headers; +using IHttpHeadersHandler = System.Net.Http.IHttpHeadersHandler; + +namespace Microsoft.AspNetCore.Http2Cat +{ + internal class Http2Utilities : IHttpHeadersHandler + { + public static ReadOnlySpan ClientPreface => new byte[24] { (byte)'P', (byte)'R', (byte)'I', (byte)' ', (byte)'*', (byte)' ', (byte)'H', (byte)'T', (byte)'T', (byte)'P', (byte)'/', (byte)'2', (byte)'.', (byte)'0', (byte)'\r', (byte)'\n', (byte)'\r', (byte)'\n', (byte)'S', (byte)'M', (byte)'\r', (byte)'\n', (byte)'\r', (byte)'\n' }; + public const int MaxRequestHeaderFieldSize = 16 * 1024; + public static readonly string FourKHeaderValue = new string('a', 4096); + private static readonly Encoding HeaderValueEncoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true); + + public static readonly IEnumerable> BrowserRequestHeaders = new[] + { + new KeyValuePair(HeaderNames.Method, "GET"), + new KeyValuePair(HeaderNames.Path, "/"), + new KeyValuePair(HeaderNames.Scheme, "https"), + new KeyValuePair(HeaderNames.Authority, "localhost:443"), + new KeyValuePair("user-agent", "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:54.0) Gecko/20100101 Firefox/54.0"), + new KeyValuePair("accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"), + new KeyValuePair("accept-language", "en-US,en;q=0.5"), + new KeyValuePair("accept-encoding", "gzip, deflate, br"), + new KeyValuePair("upgrade-insecure-requests", "1"), + }; + + public static readonly IEnumerable> BrowserRequestHeadersHttp = new[] + { + new KeyValuePair(HeaderNames.Method, "GET"), + new KeyValuePair(HeaderNames.Path, "/"), + new KeyValuePair(HeaderNames.Scheme, "http"), + new KeyValuePair(HeaderNames.Authority, "localhost:80"), + new KeyValuePair("user-agent", "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:54.0) Gecko/20100101 Firefox/54.0"), + new KeyValuePair("accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"), + new KeyValuePair("accept-language", "en-US,en;q=0.5"), + new KeyValuePair("accept-encoding", "gzip, deflate, br"), + new KeyValuePair("upgrade-insecure-requests", "1"), + }; + + public static readonly IEnumerable> PostRequestHeaders = new[] + { + new KeyValuePair(HeaderNames.Method, "POST"), + new KeyValuePair(HeaderNames.Path, "/"), + new KeyValuePair(HeaderNames.Scheme, "https"), + new KeyValuePair(HeaderNames.Authority, "localhost:80"), + }; + + public static readonly IEnumerable> ExpectContinueRequestHeaders = new[] + { + new KeyValuePair(HeaderNames.Method, "POST"), + new KeyValuePair(HeaderNames.Path, "/"), + new KeyValuePair(HeaderNames.Authority, "127.0.0.1"), + new KeyValuePair(HeaderNames.Scheme, "https"), + new KeyValuePair("expect", "100-continue"), + }; + + public static readonly IEnumerable> RequestTrailers = new[] + { + new KeyValuePair("trailer-one", "1"), + new KeyValuePair("trailer-two", "2"), + }; + + public static readonly IEnumerable> OneContinuationRequestHeaders = new[] + { + new KeyValuePair(HeaderNames.Method, "GET"), + new KeyValuePair(HeaderNames.Path, "/"), + new KeyValuePair(HeaderNames.Scheme, "https"), + new KeyValuePair(HeaderNames.Authority, "localhost:80"), + new KeyValuePair("a", FourKHeaderValue), + new KeyValuePair("b", FourKHeaderValue), + new KeyValuePair("c", FourKHeaderValue), + new KeyValuePair("d", FourKHeaderValue) + }; + + public static readonly IEnumerable> TwoContinuationsRequestHeaders = new[] + { + new KeyValuePair(HeaderNames.Method, "GET"), + new KeyValuePair(HeaderNames.Path, "/"), + new KeyValuePair(HeaderNames.Scheme, "https"), + new KeyValuePair(HeaderNames.Authority, "localhost:80"), + new KeyValuePair("a", FourKHeaderValue), + new KeyValuePair("b", FourKHeaderValue), + new KeyValuePair("c", FourKHeaderValue), + new KeyValuePair("d", FourKHeaderValue), + new KeyValuePair("e", FourKHeaderValue), + new KeyValuePair("f", FourKHeaderValue), + new KeyValuePair("g", FourKHeaderValue), + }; + + public static IEnumerable> ReadRateRequestHeaders(int expectedBytes) => new[] + { + new KeyValuePair(HeaderNames.Method, "POST"), + new KeyValuePair(HeaderNames.Path, "/" + expectedBytes), + new KeyValuePair(HeaderNames.Scheme, "https"), + new KeyValuePair(HeaderNames.Authority, "localhost:80"), + }; + + public static readonly byte[] _helloBytes = Encoding.ASCII.GetBytes("hello"); + public static readonly byte[] _worldBytes = Encoding.ASCII.GetBytes("world"); + public static readonly byte[] _helloWorldBytes = Encoding.ASCII.GetBytes("hello, world"); + public static readonly byte[] _noData = new byte[0]; + public static readonly byte[] _maxData = Encoding.ASCII.GetBytes(new string('a', Http2PeerSettings.MinAllowedMaxFrameSize)); + + internal readonly Http2PeerSettings _clientSettings = new Http2PeerSettings(); + internal readonly HPackDecoder _hpackDecoder; + private readonly byte[] _headerEncodingBuffer = new byte[Http2PeerSettings.MinAllowedMaxFrameSize]; + + public readonly Dictionary _decodedHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); + + internal DuplexPipe.DuplexPipePair _pair; + public long _bytesReceived; + + public Http2Utilities(ConnectionContext clientConnectionContext, ILogger logger, CancellationToken stopToken) + { + _hpackDecoder = new HPackDecoder((int)_clientSettings.HeaderTableSize, MaxRequestHeaderFieldSize); + _pair = new DuplexPipe.DuplexPipePair(transport: null, application: clientConnectionContext.Transport); + Logger = logger; + StopToken = stopToken; + } + + public ILogger Logger { get; } + public CancellationToken StopToken { get; } + + void IHttpHeadersHandler.OnHeader(ReadOnlySpan name, ReadOnlySpan value) + { + _decodedHeaders[GetAsciiStringNonNullCharacters(name)] = GetAsciiOrUTF8StringNonNullCharacters(value); + } + + public unsafe string GetAsciiStringNonNullCharacters(ReadOnlySpan span) + { + if (span.IsEmpty) + { + return string.Empty; + } + + var asciiString = new string('\0', span.Length); + + fixed (char* output = asciiString) + fixed (byte* buffer = span) + { + // This version if AsciiUtilities returns null if there are any null (0 byte) characters + // in the string + if (!StringUtilities.TryGetAsciiString(buffer, output, span.Length)) + { + throw new InvalidOperationException(); + } + } + return asciiString; + } + + public unsafe string GetAsciiOrUTF8StringNonNullCharacters(ReadOnlySpan span) + { + if (span.IsEmpty) + { + return string.Empty; + } + + var resultString = new string('\0', span.Length); + + fixed (char* output = resultString) + fixed (byte* buffer = span) + { + // This version if AsciiUtilities returns null if there are any null (0 byte) characters + // in the string + if (!StringUtilities.TryGetAsciiString(buffer, output, span.Length)) + { + // null characters are considered invalid + if (span.IndexOf((byte)0) != -1) + { + throw new InvalidOperationException(); + } + + try + { + resultString = HeaderValueEncoding.GetString(buffer, span.Length); + } + catch (DecoderFallbackException) + { + throw new InvalidOperationException(); + } + } + } + return resultString; + } + + void IHttpHeadersHandler.OnHeadersComplete(bool endStream) { } + + public async Task InitializeConnectionAsync(int expectedSettingsCount = 3) + { + await SendPreambleAsync().ConfigureAwait(false); + await SendSettingsAsync(); + + await ExpectAsync(Http2FrameType.SETTINGS, + withLength: expectedSettingsCount * Http2FrameReader.SettingSize, + withFlags: 0, + withStreamId: 0); + + await ExpectAsync(Http2FrameType.WINDOW_UPDATE, + withLength: 4, + withFlags: 0, + withStreamId: 0); + + await ExpectAsync(Http2FrameType.SETTINGS, + withLength: 0, + withFlags: (byte)Http2SettingsFrameFlags.ACK, + withStreamId: 0); + } + + public Task StartStreamAsync(int streamId, IEnumerable> headers, bool endStream) + { + var writableBuffer = _pair.Application.Output; + var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + + var frame = new Http2Frame(); + frame.PrepareHeaders(Http2HeadersFrameFlags.NONE, streamId); + + var buffer = _headerEncodingBuffer.AsSpan(); + var headersEnumerator = GetHeadersEnumerator(headers); + var done = HPackHeaderWriter.BeginEncodeHeaders(headersEnumerator, buffer, out var length); + frame.PayloadLength = length; + + if (done) + { + frame.HeadersFlags = Http2HeadersFrameFlags.END_HEADERS; + } + + if (endStream) + { + frame.HeadersFlags |= Http2HeadersFrameFlags.END_STREAM; + } + + WriteHeader(frame, writableBuffer); + writableBuffer.Write(buffer.Slice(0, length)); + + while (!done) + { + frame.PrepareContinuation(Http2ContinuationFrameFlags.NONE, streamId); + + done = HPackHeaderWriter.ContinueEncodeHeaders(headersEnumerator, buffer, out length); + frame.PayloadLength = length; + + if (done) + { + frame.ContinuationFlags = Http2ContinuationFrameFlags.END_HEADERS; + } + + WriteHeader(frame, writableBuffer); + writableBuffer.Write(buffer.Slice(0, length)); + } + + return FlushAsync(writableBuffer); + } + + private static IEnumerator> GetHeadersEnumerator(IEnumerable> headers) + { + var headersEnumerator = headers.GetEnumerator(); + return headersEnumerator; + } + + internal Dictionary DecodeHeaders(Http2FrameWithPayload frame, bool endHeaders = false) + { + Assert.Equal(Http2FrameType.HEADERS, frame.Type); + _hpackDecoder.Decode(frame.PayloadSequence, endHeaders, handler: this); + return _decodedHeaders; + } + + internal void ResetHeaders() + { + _decodedHeaders.Clear(); + } + + /* https://tools.ietf.org/html/rfc7540#section-4.1 + +-----------------------------------------------+ + | Length (24) | + +---------------+---------------+---------------+ + | Type (8) | Flags (8) | + +-+-------------+---------------+-------------------------------+ + |R| Stream Identifier (31) | + +=+=============================================================+ + | Frame Payload (0...) ... + +---------------------------------------------------------------+ + */ + internal static void WriteHeader(Http2Frame frame, PipeWriter output) + { + var buffer = output.GetSpan(Http2FrameReader.HeaderLength); + + Bitshifter.WriteUInt24BigEndian(buffer, (uint)frame.PayloadLength); + buffer = buffer.Slice(3); + + buffer[0] = (byte)frame.Type; + buffer[1] = frame.Flags; + buffer = buffer.Slice(2); + + Bitshifter.WriteUInt31BigEndian(buffer, (uint)frame.StreamId, preserveHighestBit: false); + + output.Advance(Http2FrameReader.HeaderLength); + } + + /* https://tools.ietf.org/html/rfc7540#section-6.2 + +---------------+ + |Pad Length? (8)| + +-+-------------+-----------------------------------------------+ + | Header Block Fragment (*) ... + +---------------------------------------------------------------+ + | Padding (*) ... + +---------------------------------------------------------------+ + */ + public Task SendHeadersWithPaddingAsync(int streamId, IEnumerable> headers, byte padLength, bool endStream) + { + var writableBuffer = _pair.Application.Output; + var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + + var frame = new Http2Frame(); + + frame.PrepareHeaders(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.PADDED, streamId); + frame.HeadersPadLength = padLength; + + var extendedHeaderLength = 1; // Padding length field + var buffer = _headerEncodingBuffer.AsSpan(); + var extendedHeader = buffer.Slice(0, extendedHeaderLength); + extendedHeader[0] = padLength; + var payload = buffer.Slice(extendedHeaderLength, buffer.Length - padLength - extendedHeaderLength); + + HPackHeaderWriter.BeginEncodeHeaders(GetHeadersEnumerator(headers), payload, out var length); + var padding = buffer.Slice(extendedHeaderLength + length, padLength); + padding.Fill(0); + + frame.PayloadLength = extendedHeaderLength + length + padLength; + + if (endStream) + { + frame.HeadersFlags |= Http2HeadersFrameFlags.END_STREAM; + } + + WriteHeader(frame, writableBuffer); + writableBuffer.Write(buffer.Slice(0, frame.PayloadLength)); + return FlushAsync(writableBuffer); + } + + /* https://tools.ietf.org/html/rfc7540#section-6.2 + +-+-------------+-----------------------------------------------+ + |E| Stream Dependency? (31) | + +-+-------------+-----------------------------------------------+ + | Weight? (8) | + +-+-------------+-----------------------------------------------+ + | Header Block Fragment (*) ... + +---------------------------------------------------------------+ + */ + public Task SendHeadersWithPriorityAsync(int streamId, IEnumerable> headers, byte priority, int streamDependency, bool endStream) + { + var writableBuffer = _pair.Application.Output; + var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + + var frame = new Http2Frame(); + frame.PrepareHeaders(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.PRIORITY, streamId); + frame.HeadersPriorityWeight = priority; + frame.HeadersStreamDependency = streamDependency; + + var extendedHeaderLength = 5; // stream dependency + weight + var buffer = _headerEncodingBuffer.AsSpan(); + var extendedHeader = buffer.Slice(0, extendedHeaderLength); + Bitshifter.WriteUInt31BigEndian(extendedHeader, (uint)streamDependency); + extendedHeader[4] = priority; + var payload = buffer.Slice(extendedHeaderLength); + + HPackHeaderWriter.BeginEncodeHeaders(GetHeadersEnumerator(headers), payload, out var length); + + frame.PayloadLength = extendedHeaderLength + length; + + if (endStream) + { + frame.HeadersFlags |= Http2HeadersFrameFlags.END_STREAM; + } + + WriteHeader(frame, writableBuffer); + writableBuffer.Write(buffer.Slice(0, frame.PayloadLength)); + return FlushAsync(writableBuffer); + } + + /* https://tools.ietf.org/html/rfc7540#section-6.2 + +---------------+ + |Pad Length? (8)| + +-+-------------+-----------------------------------------------+ + |E| Stream Dependency? (31) | + +-+-------------+-----------------------------------------------+ + | Weight? (8) | + +-+-------------+-----------------------------------------------+ + | Header Block Fragment (*) ... + +---------------------------------------------------------------+ + | Padding (*) ... + +---------------------------------------------------------------+ + */ + public Task SendHeadersWithPaddingAndPriorityAsync(int streamId, IEnumerable> headers, byte padLength, byte priority, int streamDependency, bool endStream) + { + var writableBuffer = _pair.Application.Output; + var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + + var frame = new Http2Frame(); + frame.PrepareHeaders(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.PADDED | Http2HeadersFrameFlags.PRIORITY, streamId); + frame.HeadersPadLength = padLength; + frame.HeadersPriorityWeight = priority; + frame.HeadersStreamDependency = streamDependency; + + var extendedHeaderLength = 6; // pad length + stream dependency + weight + var buffer = _headerEncodingBuffer.AsSpan(); + var extendedHeader = buffer.Slice(0, extendedHeaderLength); + extendedHeader[0] = padLength; + Bitshifter.WriteUInt31BigEndian(extendedHeader.Slice(1), (uint)streamDependency); + extendedHeader[5] = priority; + var payload = buffer.Slice(extendedHeaderLength, buffer.Length - padLength - extendedHeaderLength); + + HPackHeaderWriter.BeginEncodeHeaders(GetHeadersEnumerator(headers), payload, out var length); + var padding = buffer.Slice(extendedHeaderLength + length, padLength); + padding.Fill(0); + + frame.PayloadLength = extendedHeaderLength + length + padLength; + + if (endStream) + { + frame.HeadersFlags |= Http2HeadersFrameFlags.END_STREAM; + } + + WriteHeader(frame, writableBuffer); + writableBuffer.Write(buffer.Slice(0, frame.PayloadLength)); + return FlushAsync(writableBuffer); + } + + public Task SendAsync(ReadOnlySpan span) + { + var writableBuffer = _pair.Application.Output; + writableBuffer.Write(span); + return FlushAsync(writableBuffer); + } + + public static async Task FlushAsync(PipeWriter writableBuffer) + { + await writableBuffer.FlushAsync().AsTask().DefaultTimeout(); + } + + public Task SendPreambleAsync() => SendAsync(ClientPreface); + + public async Task SendSettingsAsync() + { + var writableBuffer = _pair.Application.Output; + var frame = new Http2Frame(); + frame.PrepareSettings(Http2SettingsFrameFlags.NONE); + var settings = _clientSettings.GetNonProtocolDefaults(); + var payload = new byte[settings.Count * Http2FrameReader.SettingSize]; + frame.PayloadLength = payload.Length; + WriteSettings(settings, payload); + WriteHeader(frame, writableBuffer); + await SendAsync(payload); + } + + internal static void WriteSettings(IList settings, Span destination) + { + foreach (var setting in settings) + { + BinaryPrimitives.WriteUInt16BigEndian(destination, (ushort)setting.Parameter); + BinaryPrimitives.WriteUInt32BigEndian(destination.Slice(2), setting.Value); + destination = destination.Slice(Http2FrameReader.SettingSize); + } + } + + public async Task SendSettingsAckWithInvalidLengthAsync(int length) + { + var writableBuffer = _pair.Application.Output; + var frame = new Http2Frame(); + frame.PrepareSettings(Http2SettingsFrameFlags.ACK); + frame.PayloadLength = length; + WriteHeader(frame, writableBuffer); + await SendAsync(new byte[length]); + } + + public async Task SendSettingsWithInvalidStreamIdAsync(int streamId) + { + var writableBuffer = _pair.Application.Output; + var frame = new Http2Frame(); + frame.PrepareSettings(Http2SettingsFrameFlags.NONE); + frame.StreamId = streamId; + var settings = _clientSettings.GetNonProtocolDefaults(); + var payload = new byte[settings.Count * Http2FrameReader.SettingSize]; + frame.PayloadLength = payload.Length; + WriteSettings(settings, payload); + WriteHeader(frame, writableBuffer); + await SendAsync(payload); + } + + public async Task SendSettingsWithInvalidLengthAsync(int length) + { + var writableBuffer = _pair.Application.Output; + var frame = new Http2Frame(); + frame.PrepareSettings(Http2SettingsFrameFlags.NONE); + + frame.PayloadLength = length; + var payload = new byte[length]; + WriteHeader(frame, writableBuffer); + await SendAsync(payload); + } + + internal async Task SendSettingsWithInvalidParameterValueAsync(Http2SettingsParameter parameter, uint value) + { + var writableBuffer = _pair.Application.Output; + var frame = new Http2Frame(); + frame.PrepareSettings(Http2SettingsFrameFlags.NONE); + frame.PayloadLength = 6; + var payload = new byte[Http2FrameReader.SettingSize]; + payload[0] = (byte)((ushort)parameter >> 8); + payload[1] = (byte)(ushort)parameter; + payload[2] = (byte)(value >> 24); + payload[3] = (byte)(value >> 16); + payload[4] = (byte)(value >> 8); + payload[5] = (byte)value; + + WriteHeader(frame, writableBuffer); + await SendAsync(payload); + } + + public Task SendPushPromiseFrameAsync() + { + var writableBuffer = _pair.Application.Output; + var frame = new Http2Frame(); + frame.PayloadLength = 0; + frame.Type = Http2FrameType.PUSH_PROMISE; + frame.StreamId = 1; + + WriteHeader(frame, writableBuffer); + return FlushAsync(writableBuffer); + } + + internal async Task SendHeadersAsync(int streamId, Http2HeadersFrameFlags flags, IEnumerable> headers) + { + var outputWriter = _pair.Application.Output; + var frame = new Http2Frame(); + + frame.PrepareHeaders(flags, streamId); + var buffer = _headerEncodingBuffer.AsMemory(); + var done = HPackHeaderWriter.BeginEncodeHeaders(GetHeadersEnumerator(headers), buffer.Span, out var length); + frame.PayloadLength = length; + + WriteHeader(frame, outputWriter); + await SendAsync(buffer.Span.Slice(0, length)); + + return done; + } + + internal async Task SendHeadersAsync(int streamId, Http2HeadersFrameFlags flags, byte[] headerBlock) + { + var outputWriter = _pair.Application.Output; + var frame = new Http2Frame(); + + frame.PrepareHeaders(flags, streamId); + frame.PayloadLength = headerBlock.Length; + + WriteHeader(frame, outputWriter); + await SendAsync(headerBlock); + } + + public async Task SendInvalidHeadersFrameAsync(int streamId, int payloadLength, byte padLength) + { + Assert.True(padLength >= payloadLength, $"{nameof(padLength)} must be greater than or equal to {nameof(payloadLength)} to create an invalid frame."); + + var outputWriter = _pair.Application.Output; + var frame = new Http2Frame(); + + frame.PrepareHeaders(Http2HeadersFrameFlags.PADDED, streamId); + frame.PayloadLength = payloadLength; + var payload = new byte[payloadLength]; + if (payloadLength > 0) + { + payload[0] = padLength; + } + + WriteHeader(frame, outputWriter); + await SendAsync(payload); + } + + public async Task SendIncompleteHeadersFrameAsync(int streamId) + { + var outputWriter = _pair.Application.Output; + var frame = new Http2Frame(); + + frame.PrepareHeaders(Http2HeadersFrameFlags.END_HEADERS, streamId); + frame.PayloadLength = 3; + var payload = new byte[3]; + // Set up an incomplete Literal Header Field w/ Incremental Indexing frame, + // with an incomplete new name + payload[0] = 0; + payload[1] = 2; + payload[2] = (byte)'a'; + + WriteHeader(frame, outputWriter); + await SendAsync(payload); + } + + internal async Task SendContinuationAsync(int streamId, Http2ContinuationFrameFlags flags, IEnumerator> headersEnumerator) + { + var outputWriter = _pair.Application.Output; + var frame = new Http2Frame(); + + frame.PrepareContinuation(flags, streamId); + var buffer = _headerEncodingBuffer.AsMemory(); + var done = HPackHeaderWriter.ContinueEncodeHeaders(headersEnumerator, buffer.Span, out var length); + frame.PayloadLength = length; + + WriteHeader(frame, outputWriter); + await SendAsync(buffer.Span.Slice(0, length)); + + return done; + } + + internal async Task SendContinuationAsync(int streamId, Http2ContinuationFrameFlags flags, byte[] payload) + { + var outputWriter = _pair.Application.Output; + var frame = new Http2Frame(); + + frame.PrepareContinuation(flags, streamId); + frame.PayloadLength = payload.Length; + + WriteHeader(frame, outputWriter); + await SendAsync(payload); + } + + internal async Task SendContinuationAsync(int streamId, Http2ContinuationFrameFlags flags, IEnumerable> headers) + { + var outputWriter = _pair.Application.Output; + var frame = new Http2Frame(); + + frame.PrepareContinuation(flags, streamId); + var buffer = _headerEncodingBuffer.AsMemory(); + var done = HPackHeaderWriter.BeginEncodeHeaders(GetHeadersEnumerator(headers), buffer.Span, out var length); + frame.PayloadLength = length; + + WriteHeader(frame, outputWriter); + await SendAsync(buffer.Span.Slice(0, length)); + + return done; + } + + internal Task SendEmptyContinuationFrameAsync(int streamId, Http2ContinuationFrameFlags flags) + { + var outputWriter = _pair.Application.Output; + var frame = new Http2Frame(); + + frame.PrepareContinuation(flags, streamId); + frame.PayloadLength = 0; + + WriteHeader(frame, outputWriter); + return FlushAsync(outputWriter); + } + + public async Task SendIncompleteContinuationFrameAsync(int streamId) + { + var outputWriter = _pair.Application.Output; + var frame = new Http2Frame(); + + frame.PrepareContinuation(Http2ContinuationFrameFlags.END_HEADERS, streamId); + frame.PayloadLength = 3; + var payload = new byte[3]; + // Set up an incomplete Literal Header Field w/ Incremental Indexing frame, + // with an incomplete new name + payload[0] = 0; + payload[1] = 2; + payload[2] = (byte)'a'; + + WriteHeader(frame, outputWriter); + await SendAsync(payload); + } + + public Task SendDataAsync(int streamId, Memory data, bool endStream) + { + var outputWriter = _pair.Application.Output; + var frame = new Http2Frame(); + + frame.PrepareData(streamId); + frame.PayloadLength = data.Length; + frame.DataFlags = endStream ? Http2DataFrameFlags.END_STREAM : Http2DataFrameFlags.NONE; + + WriteHeader(frame, outputWriter); + return SendAsync(data.Span); + } + + public async Task SendDataWithPaddingAsync(int streamId, Memory data, byte padLength, bool endStream) + { + var outputWriter = _pair.Application.Output; + var frame = new Http2Frame(); + + frame.PrepareData(streamId, padLength); + frame.PayloadLength = data.Length + 1 + padLength; + + if (endStream) + { + frame.DataFlags |= Http2DataFrameFlags.END_STREAM; + } + + WriteHeader(frame, outputWriter); + outputWriter.GetSpan(1)[0] = padLength; + outputWriter.Advance(1); + await SendAsync(data.Span); + await SendAsync(new byte[padLength]); + } + + public Task SendInvalidDataFrameAsync(int streamId, int frameLength, byte padLength) + { + Assert.True(padLength >= frameLength, $"{nameof(padLength)} must be greater than or equal to {nameof(frameLength)} to create an invalid frame."); + + var outputWriter = _pair.Application.Output; + var frame = new Http2Frame(); + + frame.PrepareData(streamId); + frame.DataFlags = Http2DataFrameFlags.PADDED; + frame.PayloadLength = frameLength; + var payload = new byte[frameLength]; + if (frameLength > 0) + { + payload[0] = padLength; + } + + WriteHeader(frame, outputWriter); + return SendAsync(payload); + } + + internal Task SendPingAsync(Http2PingFrameFlags flags) + { + var outputWriter = _pair.Application.Output; + var pingFrame = new Http2Frame(); + pingFrame.PreparePing(flags); + WriteHeader(pingFrame, outputWriter); + return SendAsync(new byte[8]); // Empty payload + } + + public Task SendPingWithInvalidLengthAsync(int length) + { + var outputWriter = _pair.Application.Output; + var pingFrame = new Http2Frame(); + pingFrame.PreparePing(Http2PingFrameFlags.NONE); + pingFrame.PayloadLength = length; + WriteHeader(pingFrame, outputWriter); + return SendAsync(new byte[length]); + } + + public Task SendPingWithInvalidStreamIdAsync(int streamId) + { + Assert.NotEqual(0, streamId); + + var outputWriter = _pair.Application.Output; + var pingFrame = new Http2Frame(); + pingFrame.PreparePing(Http2PingFrameFlags.NONE); + pingFrame.StreamId = streamId; + WriteHeader(pingFrame, outputWriter); + return SendAsync(new byte[pingFrame.PayloadLength]); + } + + /* https://tools.ietf.org/html/rfc7540#section-6.3 + +-+-------------------------------------------------------------+ + |E| Stream Dependency (31) | + +-+-------------+-----------------------------------------------+ + | Weight (8) | + +-+-------------+ + */ + public Task SendPriorityAsync(int streamId, int streamDependency = 0) + { + var outputWriter = _pair.Application.Output; + var priorityFrame = new Http2Frame(); + priorityFrame.PreparePriority(streamId, streamDependency: streamDependency, exclusive: false, weight: 0); + + var payload = new byte[priorityFrame.PayloadLength].AsSpan(); + Bitshifter.WriteUInt31BigEndian(payload, (uint)streamDependency); + payload[4] = 0; // Weight + + WriteHeader(priorityFrame, outputWriter); + return SendAsync(payload); + } + + public Task SendInvalidPriorityFrameAsync(int streamId, int length) + { + var outputWriter = _pair.Application.Output; + var priorityFrame = new Http2Frame(); + priorityFrame.PreparePriority(streamId, streamDependency: 0, exclusive: false, weight: 0); + priorityFrame.PayloadLength = length; + + WriteHeader(priorityFrame, outputWriter); + return SendAsync(new byte[length]); + } + + /* https://tools.ietf.org/html/rfc7540#section-6.4 + +---------------------------------------------------------------+ + | Error Code (32) | + +---------------------------------------------------------------+ + */ + public Task SendRstStreamAsync(int streamId) + { + var outputWriter = _pair.Application.Output; + var rstStreamFrame = new Http2Frame(); + rstStreamFrame.PrepareRstStream(streamId, Http2ErrorCode.CANCEL); + var payload = new byte[rstStreamFrame.PayloadLength]; + BinaryPrimitives.WriteUInt32BigEndian(payload, (uint)Http2ErrorCode.CANCEL); + + WriteHeader(rstStreamFrame, outputWriter); + return SendAsync(payload); + } + + public Task SendInvalidRstStreamFrameAsync(int streamId, int length) + { + var outputWriter = _pair.Application.Output; + var frame = new Http2Frame(); + frame.PrepareRstStream(streamId, Http2ErrorCode.CANCEL); + frame.PayloadLength = length; + WriteHeader(frame, outputWriter); + return SendAsync(new byte[length]); + } + + public Task SendGoAwayAsync() + { + var outputWriter = _pair.Application.Output; + var frame = new Http2Frame(); + frame.PrepareGoAway(0, Http2ErrorCode.NO_ERROR); + WriteHeader(frame, outputWriter); + return SendAsync(new byte[frame.PayloadLength]); + } + + public Task SendInvalidGoAwayFrameAsync() + { + var outputWriter = _pair.Application.Output; + var frame = new Http2Frame(); + frame.PrepareGoAway(0, Http2ErrorCode.NO_ERROR); + frame.StreamId = 1; + WriteHeader(frame, outputWriter); + return SendAsync(new byte[frame.PayloadLength]); + } + + public Task SendWindowUpdateAsync(int streamId, int sizeIncrement) + { + var outputWriter = _pair.Application.Output; + var frame = new Http2Frame(); + frame.PrepareWindowUpdate(streamId, sizeIncrement); + WriteHeader(frame, outputWriter); + var buffer = outputWriter.GetSpan(4); + BinaryPrimitives.WriteUInt32BigEndian(buffer, (uint)sizeIncrement); + outputWriter.Advance(4); + return FlushAsync(outputWriter); + } + + public Task SendInvalidWindowUpdateAsync(int streamId, int sizeIncrement, int length) + { + var outputWriter = _pair.Application.Output; + var frame = new Http2Frame(); + frame.PrepareWindowUpdate(streamId, sizeIncrement); + frame.PayloadLength = length; + WriteHeader(frame, outputWriter); + return SendAsync(new byte[length]); + } + + public Task SendUnknownFrameTypeAsync(int streamId, int frameType) + { + var outputWriter = _pair.Application.Output; + var frame = new Http2Frame(); + frame.StreamId = streamId; + frame.Type = (Http2FrameType)frameType; + frame.PayloadLength = 0; + WriteHeader(frame, outputWriter); + return FlushAsync(outputWriter); + } + + internal async Task ReceiveFrameAsync(uint maxFrameSize = Http2PeerSettings.DefaultMaxFrameSize) + { + var frame = new Http2FrameWithPayload(); + + while (true) + { + var result = await _pair.Application.Input.ReadAsync().AsTask().DefaultTimeout(); + var buffer = result.Buffer; + var consumed = buffer.Start; + var examined = buffer.Start; + + try + { + Assert.True(buffer.Length > 0); + + if (Http2FrameReader.TryReadFrame(ref buffer, frame, maxFrameSize, out var framePayload)) + { + consumed = examined = framePayload.End; + frame.Payload = framePayload.ToArray(); + return frame; + } + else + { + examined = buffer.End; + } + + if (result.IsCompleted) + { + throw new IOException("The reader completed without returning a frame."); + } + } + finally + { + _bytesReceived += buffer.Slice(buffer.Start, consumed).Length; + _pair.Application.Input.AdvanceTo(consumed, examined); + } + } + } + + internal async Task ExpectAsync(Http2FrameType type, int withLength, byte withFlags, int withStreamId) + { + var frame = await ReceiveFrameAsync((uint)withLength); + + Assert.Equal(type, frame.Type); + Assert.Equal(withLength, frame.PayloadLength); + Assert.Equal(withFlags, frame.Flags); + Assert.Equal(withStreamId, frame.StreamId); + + return frame; + } + + public async Task StopConnectionAsync(int expectedLastStreamId, bool ignoreNonGoAwayFrames) + { + await SendGoAwayAsync(); + await WaitForConnectionStopAsync(expectedLastStreamId, ignoreNonGoAwayFrames); + + _pair.Application.Output.Complete(); + } + + public Task WaitForConnectionStopAsync(int expectedLastStreamId, bool ignoreNonGoAwayFrames) + { + return WaitForConnectionErrorAsync(ignoreNonGoAwayFrames, expectedLastStreamId, Http2ErrorCode.NO_ERROR); + } + + internal Task ReceiveHeadersAsync(int expectedStreamId, Action> verifyHeaders = null) + => ReceiveHeadersAsync(expectedStreamId, endStream: false, verifyHeaders); + + internal async Task ReceiveHeadersAsync(int expectedStreamId, bool endStream = false, Action> verifyHeaders = null) + { + var headersFrame = await ReceiveFrameAsync(); + Assert.Equal(Http2FrameType.HEADERS, headersFrame.Type); + Assert.Equal(expectedStreamId, headersFrame.StreamId); + Assert.True((headersFrame.Flags & (byte)Http2HeadersFrameFlags.END_HEADERS) != 0); + Assert.Equal(endStream, (headersFrame.Flags & (byte)Http2HeadersFrameFlags.END_STREAM) != 0); + Logger.LogInformation("Received headers in a single frame."); + + ResetHeaders(); + DecodeHeaders(headersFrame); + verifyHeaders?.Invoke(_decodedHeaders); + } + + internal static void VerifyDataFrame(Http2Frame frame, int expectedStreamId, bool endOfStream, int length) + { + Assert.Equal(Http2FrameType.DATA, frame.Type); + Assert.Equal(expectedStreamId, frame.StreamId); + Assert.Equal(endOfStream ? Http2DataFrameFlags.END_STREAM : Http2DataFrameFlags.NONE, frame.DataFlags); + Assert.Equal(length, frame.PayloadLength); + } + + internal void VerifyGoAway(Http2Frame frame, int expectedLastStreamId, Http2ErrorCode expectedErrorCode) + { + Assert.Equal(Http2FrameType.GOAWAY, frame.Type); + Assert.Equal(8, frame.PayloadLength); + Assert.Equal(0, frame.Flags); + Assert.Equal(0, frame.StreamId); + Assert.Equal(expectedLastStreamId, frame.GoAwayLastStreamId); + Assert.Equal(expectedErrorCode, frame.GoAwayErrorCode); + } + + internal static void VerifyResetFrame(Http2Frame frame, int expectedStreamId, Http2ErrorCode expectedErrorCode) + { + Assert.Equal(Http2FrameType.RST_STREAM, frame.Type); + Assert.Equal(expectedStreamId, frame.StreamId); + Assert.Equal(expectedErrorCode, frame.RstStreamErrorCode); + Assert.Equal(4, frame.PayloadLength); + Assert.Equal(0, frame.Flags); + } + + internal async Task WaitForConnectionErrorAsync(bool ignoreNonGoAwayFrames, int expectedLastStreamId, Http2ErrorCode expectedErrorCode) + where TException : Exception + { + await WaitForConnectionErrorAsyncDoNotCloseTransport(ignoreNonGoAwayFrames, expectedLastStreamId, expectedErrorCode); + _pair.Application.Output.Complete(); + } + + internal async Task WaitForConnectionErrorAsyncDoNotCloseTransport(bool ignoreNonGoAwayFrames, int expectedLastStreamId, Http2ErrorCode expectedErrorCode) + where TException : Exception + { + var frame = await ReceiveFrameAsync(); + + if (ignoreNonGoAwayFrames) + { + while (frame.Type != Http2FrameType.GOAWAY) + { + frame = await ReceiveFrameAsync(); + } + } + + VerifyGoAway(frame, expectedLastStreamId, expectedErrorCode); + } + + internal async Task WaitForStreamErrorAsync(int expectedStreamId, Http2ErrorCode expectedErrorCode) + { + var frame = await ReceiveFrameAsync(); + + Assert.Equal(Http2FrameType.RST_STREAM, frame.Type); + Assert.Equal(4, frame.PayloadLength); + Assert.Equal(0, frame.Flags); + Assert.Equal(expectedStreamId, frame.StreamId); + Assert.Equal(expectedErrorCode, frame.RstStreamErrorCode); + } + + public void OnStaticIndexedHeader(int index) + { + throw new NotImplementedException(); + } + + public void OnStaticIndexedHeader(int index, ReadOnlySpan value) + { + throw new NotImplementedException(); + } + + internal class Http2FrameWithPayload : Http2Frame + { + public Http2FrameWithPayload() : base() + { + } + + // This does not contain extended headers + public Memory Payload { get; set; } + + public ReadOnlySequence PayloadSequence => new ReadOnlySequence(Payload); + } + + private static class Assert + { + public static void True(bool condition, string message = "") + { + if (!condition) + { + throw new Exception($"Assert.True failed: '{message}'"); + } + } + + public static void Equal(T expected, T actual) + { + if (!expected.Equals(actual)) + { + throw new Exception($"Assert.Equal('{expected}', '{actual}') failed"); + } + } + + public static void Equal(string expected, string actual, bool ignoreCase = false) + { + if (!expected.Equals(actual, ignoreCase ? StringComparison.InvariantCultureIgnoreCase : StringComparison.InvariantCulture)) + { + throw new Exception($"Assert.Equal('{expected}', '{actual}') failed"); + } + } + + public static void NotEqual(T value1, T value2) + { + if (value1.Equals(value2)) + { + throw new Exception($"Assert.NotEqual('{value1}', '{value2}') failed"); + } + } + + public static void Contains(IEnumerable collection, T value) + { + if (!collection.Contains(value)) + { + throw new Exception($"Assert.Contains(collection, '{value}') failed"); + } + } + } + } +} diff --git a/src/Shared/Http2cat/TaskTimeoutExtensions.cs b/src/Shared/Http2cat/TaskTimeoutExtensions.cs new file mode 100644 index 0000000000..a293c1c66b --- /dev/null +++ b/src/Shared/Http2cat/TaskTimeoutExtensions.cs @@ -0,0 +1,85 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace System.Threading.Tasks +{ + internal static class TaskTimeoutExtensions + { + public static TimeSpan DefaultTimeoutTimeSpan { get; } = TimeSpan.FromSeconds(5); + + public static Task DefaultTimeout(this ValueTask task) + { + return task.AsTask().TimeoutAfter(DefaultTimeoutTimeSpan); + } + + public static Task DefaultTimeout(this ValueTask task) + { + return task.AsTask().TimeoutAfter(DefaultTimeoutTimeSpan); + } + + public static Task DefaultTimeout(this Task task) + { + return task.TimeoutAfter(DefaultTimeoutTimeSpan); + } + + public static Task DefaultTimeout(this Task task) + { + return task.TimeoutAfter(DefaultTimeoutTimeSpan); + } + + private static async Task TimeoutAfter(this Task task, TimeSpan timeout, + [CallerFilePath] string filePath = null, + [CallerLineNumber] int lineNumber = default) + { + // Don't create a timer if the task is already completed + // or the debugger is attached + if (task.IsCompleted || Debugger.IsAttached) + { + return await task; + } + + var cts = new CancellationTokenSource(); + if (task == await Task.WhenAny(task, Task.Delay(timeout, cts.Token))) + { + cts.Cancel(); + return await task; + } + else + { + throw new TimeoutException(CreateMessage(timeout, filePath, lineNumber)); + } + } + + private static async Task TimeoutAfter(this Task task, TimeSpan timeout, + [CallerFilePath] string filePath = null, + [CallerLineNumber] int lineNumber = default) + { + // Don't create a timer if the task is already completed + // or the debugger is attached + if (task.IsCompleted || Debugger.IsAttached) + { + await task; + return; + } + + var cts = new CancellationTokenSource(); + if (task == await Task.WhenAny(task, Task.Delay(timeout, cts.Token))) + { + cts.Cancel(); + await task; + } + else + { + throw new TimeoutException(CreateMessage(timeout, filePath, lineNumber)); + } + } + + private static string CreateMessage(TimeSpan timeout, string filePath, int lineNumber) + => string.IsNullOrEmpty(filePath) + ? $"The operation timed out after reaching the limit of {timeout.TotalMilliseconds}ms." + : $"The operation at {filePath}:{lineNumber} timed out after reaching the limit of {timeout.TotalMilliseconds}ms."; + } +} diff --git a/src/Shared/HttpSys/Constants.cs b/src/Shared/HttpSys/Constants.cs index 6f861c239f..4d0576c477 100644 --- a/src/Shared/HttpSys/Constants.cs +++ b/src/Shared/HttpSys/Constants.cs @@ -15,8 +15,8 @@ namespace Microsoft.AspNetCore.HttpSys.Internal internal const string SchemeDelimiter = "://"; internal const string DefaultServerAddress = "http://localhost:5000"; - internal static Version V1_0 = new Version(1, 0); - internal static Version V1_1 = new Version(1, 1); - internal static Version V2 = new Version(2, 0); + internal static readonly Version V1_0 = new Version(1, 0); + internal static readonly Version V1_1 = new Version(1, 1); + internal static readonly Version V2 = new Version(2, 0); } } diff --git a/src/Shared/HttpSys/NativeInterop/HttpApiTypes.cs b/src/Shared/HttpSys/NativeInterop/HttpApiTypes.cs index aa5d884061..2e69d7bc40 100644 --- a/src/Shared/HttpSys/NativeInterop/HttpApiTypes.cs +++ b/src/Shared/HttpSys/NativeInterop/HttpApiTypes.cs @@ -51,6 +51,16 @@ namespace Microsoft.AspNetCore.HttpSys.Internal HttpResponseInfoTypeQosProperty, } + internal enum HTTP_REQUEST_PROPERTY + { + HttpRequestPropertyIsb, + HttpRequestPropertyTcpInfoV0, + HttpRequestPropertyQuicStats, + HttpRequestPropertyTcpInfoV1, + HttpRequestPropertySni, + HttpRequestPropertyStreamError, + } + internal enum HTTP_TIMEOUT_TYPE { EntityBody, @@ -61,6 +71,11 @@ namespace Microsoft.AspNetCore.HttpSys.Internal MinSendRate, } + internal struct HTTP_REQUEST_PROPERTY_STREAM_ERROR + { + internal uint ErrorCode; + } + internal const int MaxTimeout = 6; [StructLayout(LayoutKind.Sequential)] @@ -88,6 +103,9 @@ namespace Microsoft.AspNetCore.HttpSys.Internal [FieldOffset(8)] internal FromFileHandle fromFile; + + [FieldOffset(8)] + internal Trailers trailers; } [StructLayout(LayoutKind.Sequential)] @@ -106,6 +124,13 @@ namespace Microsoft.AspNetCore.HttpSys.Internal internal IntPtr fileHandle; } + [StructLayout(LayoutKind.Sequential)] + internal struct Trailers + { + internal ushort trailerCount; + internal IntPtr pTrailers; + } + [StructLayout(LayoutKind.Sequential)] internal struct HTTPAPI_VERSION { @@ -362,10 +387,12 @@ namespace Microsoft.AspNetCore.HttpSys.Internal internal enum HTTP_DATA_CHUNK_TYPE : int { - HttpDataChunkFromMemory = 0, - HttpDataChunkFromFileHandle = 1, - HttpDataChunkFromFragmentCache = 2, - HttpDataChunkMaximum = 3, + HttpDataChunkFromMemory, + HttpDataChunkFromFileHandle, + HttpDataChunkFromFragmentCache, + HttpDataChunkFromFragmentCacheEx, + HttpDataChunkTrailers, + HttpDataChunkMaximum, } [StructLayout(LayoutKind.Sequential)] diff --git a/src/Shared/HttpSys/RequestProcessing/HeaderCollection.cs b/src/Shared/HttpSys/RequestProcessing/HeaderCollection.cs index 504c434667..d1ed182f91 100644 --- a/src/Shared/HttpSys/RequestProcessing/HeaderCollection.cs +++ b/src/Shared/HttpSys/RequestProcessing/HeaderCollection.cs @@ -12,12 +12,42 @@ namespace Microsoft.AspNetCore.HttpSys.Internal { internal class HeaderCollection : IHeaderDictionary { + // https://tools.ietf.org/html/rfc7230#section-4.1.2 + internal static readonly HashSet DisallowedTrailers = new HashSet(StringComparer.OrdinalIgnoreCase) + { + // Message framing headers. + HeaderNames.TransferEncoding, HeaderNames.ContentLength, + + // Routing headers. + HeaderNames.Host, + + // Request modifiers: controls and conditionals. + // rfc7231#section-5.1: Controls. + HeaderNames.CacheControl, HeaderNames.Expect, HeaderNames.MaxForwards, HeaderNames.Pragma, HeaderNames.Range, HeaderNames.TE, + + // rfc7231#section-5.2: Conditionals. + HeaderNames.IfMatch, HeaderNames.IfNoneMatch, HeaderNames.IfModifiedSince, HeaderNames.IfUnmodifiedSince, HeaderNames.IfRange, + + // Authentication headers. + HeaderNames.WWWAuthenticate, HeaderNames.Authorization, HeaderNames.ProxyAuthenticate, HeaderNames.ProxyAuthorization, HeaderNames.SetCookie, HeaderNames.Cookie, + + // Response control data. + // rfc7231#section-7.1: Control Data. + HeaderNames.Age, HeaderNames.Expires, HeaderNames.Date, HeaderNames.Location, HeaderNames.RetryAfter, HeaderNames.Vary, HeaderNames.Warning, + + // Content-Encoding, Content-Type, Content-Range, and Trailer itself. + HeaderNames.ContentEncoding, HeaderNames.ContentType, HeaderNames.ContentRange, HeaderNames.Trailer + }; + + // Should this instance check for prohibited trailers? + private readonly bool _checkTrailers; private long? _contentLength; private StringValues _contentLengthText; - public HeaderCollection() + public HeaderCollection(bool checkTrailers = false) : this(new Dictionary(4, StringComparer.OrdinalIgnoreCase)) { + _checkTrailers = checkTrailers; } public HeaderCollection(IDictionary store) @@ -39,6 +69,7 @@ namespace Microsoft.AspNetCore.HttpSys.Internal } set { + ValidateRestrictedTrailers(key); ThrowIfReadOnly(); if (StringValues.IsNullOrEmpty(value)) { @@ -58,6 +89,7 @@ namespace Microsoft.AspNetCore.HttpSys.Internal get { return Store[key]; } set { + ValidateRestrictedTrailers(key); ThrowIfReadOnly(); ValidateHeaderCharacters(key); ValidateHeaderCharacters(value); @@ -105,6 +137,7 @@ namespace Microsoft.AspNetCore.HttpSys.Internal } set { + ValidateRestrictedTrailers(HeaderNames.ContentLength); ThrowIfReadOnly(); if (value.HasValue) @@ -128,6 +161,7 @@ namespace Microsoft.AspNetCore.HttpSys.Internal public void Add(KeyValuePair item) { + ValidateRestrictedTrailers(item.Key); ThrowIfReadOnly(); ValidateHeaderCharacters(item.Key); ValidateHeaderCharacters(item.Value); @@ -136,6 +170,7 @@ namespace Microsoft.AspNetCore.HttpSys.Internal public void Add(string key, StringValues value) { + ValidateRestrictedTrailers(key); ThrowIfReadOnly(); ValidateHeaderCharacters(key); ValidateHeaderCharacters(value); @@ -144,6 +179,7 @@ namespace Microsoft.AspNetCore.HttpSys.Internal public void Append(string key, string value) { + ValidateRestrictedTrailers(key); ThrowIfReadOnly(); ValidateHeaderCharacters(key); ValidateHeaderCharacters(value); @@ -214,6 +250,11 @@ namespace Microsoft.AspNetCore.HttpSys.Internal { if (IsReadOnly) { + if (_checkTrailers) + { + throw new InvalidOperationException("The response trailers cannot be modified because the response has already completed. " + + "If this is a Content-Length response then you need to call HttpResponse.DeclareTrailer before starting the body."); + } throw new InvalidOperationException("The response headers cannot be modified because the response has already started."); } } @@ -239,5 +280,13 @@ namespace Microsoft.AspNetCore.HttpSys.Internal } } } + + private void ValidateRestrictedTrailers(string key) + { + if (_checkTrailers && DisallowedTrailers.Contains(key)) + { + throw new InvalidOperationException($"The '{key}' header is not allowed in HTTP trailers."); + } + } } -} \ No newline at end of file +} diff --git a/src/Shared/HttpSys/RequestProcessing/HeaderEncoding.cs b/src/Shared/HttpSys/RequestProcessing/HeaderEncoding.cs index 991571904b..a5294c5eb7 100644 --- a/src/Shared/HttpSys/RequestProcessing/HeaderEncoding.cs +++ b/src/Shared/HttpSys/RequestProcessing/HeaderEncoding.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.Text; namespace Microsoft.AspNetCore.HttpSys.Internal @@ -14,16 +15,7 @@ namespace Microsoft.AspNetCore.HttpSys.Internal internal static unsafe string GetString(byte* pBytes, int byteCount) { - // net451: return new string(pBytes, 0, byteCount, Encoding); - - var charCount = Encoding.GetCharCount(pBytes, byteCount); - var chars = new char[charCount]; - fixed (char* pChars = chars) - { - var count = Encoding.GetChars(pBytes, byteCount, pChars, charCount); - System.Diagnostics.Debug.Assert(count == charCount); - } - return new string(chars); + return Encoding.GetString(new ReadOnlySpan(pBytes, byteCount)); } internal static byte[] GetBytes(string myString) diff --git a/src/Shared/HttpSys/RequestProcessing/NativeRequestContext.cs b/src/Shared/HttpSys/RequestProcessing/NativeRequestContext.cs index 4108d901e2..790b858b3b 100644 --- a/src/Shared/HttpSys/RequestProcessing/NativeRequestContext.cs +++ b/src/Shared/HttpSys/RequestProcessing/NativeRequestContext.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Buffers; using System.Collections.Generic; using System.Collections.Immutable; using System.Collections.ObjectModel; @@ -17,24 +18,45 @@ namespace Microsoft.AspNetCore.HttpSys.Internal internal unsafe class NativeRequestContext : IDisposable { private const int AlignmentPadding = 8; + private const int DefaultBufferSize = 4096 - AlignmentPadding; private IntPtr _originalBufferAddress; private HttpApiTypes.HTTP_REQUEST* _nativeRequest; - private byte[] _backingBuffer; + private IMemoryOwner _backingBuffer; + private MemoryHandle _memoryHandle; private int _bufferAlignment; private SafeNativeOverlapped _nativeOverlapped; private bool _permanentlyPinned; + private bool _disposed; // To be used by HttpSys - internal NativeRequestContext(SafeNativeOverlapped nativeOverlapped, - int bufferAlignment, - HttpApiTypes.HTTP_REQUEST* nativeRequest, - byte[] backingBuffer, - ulong requestId) + internal NativeRequestContext(SafeNativeOverlapped nativeOverlapped, MemoryPool memoryPool, uint? bufferSize, ulong requestId) { _nativeOverlapped = nativeOverlapped; - _bufferAlignment = bufferAlignment; - _nativeRequest = nativeRequest; - _backingBuffer = backingBuffer; + + // TODO: + // Apparently the HttpReceiveHttpRequest memory alignment requirements for non - ARM processors + // are different than for ARM processors. We have seen 4 - byte - aligned buffers allocated on + // virtual x64/x86 machines which were accepted by HttpReceiveHttpRequest without errors. In + // these cases the buffer alignment may cause reading values at invalid offset. Setting buffer + // alignment to 0 for now. + // + // _bufferAlignment = (int)(requestAddress.ToInt64() & 0x07); + _bufferAlignment = 0; + + var newSize = (int)(bufferSize ?? DefaultBufferSize) + AlignmentPadding; + if (newSize <= memoryPool.MaxBufferSize) + { + _backingBuffer = memoryPool.Rent(newSize); + } + else + { + // No size limit + _backingBuffer = MemoryPool.Shared.Rent(newSize); + } + _backingBuffer.Memory.Span.Fill(0);// Zero the buffer + _memoryHandle = _backingBuffer.Memory.Pin(); + _nativeRequest = (HttpApiTypes.HTTP_REQUEST*)((long)_memoryHandle.Pointer + _bufferAlignment); + RequestId = requestId; } @@ -94,15 +116,17 @@ namespace Microsoft.AspNetCore.HttpSys.Internal internal uint Size { - get { return (uint)_backingBuffer.Length - AlignmentPadding; } + get { return (uint)_backingBuffer.Memory.Length - AlignmentPadding; } } // ReleasePins() should be called exactly once. It must be called before Dispose() is called, which means it must be called // before an object (Request) which closes the RequestContext on demand is returned to the application. internal void ReleasePins() { - Debug.Assert(_nativeRequest != null || _backingBuffer == null, "RequestContextBase::ReleasePins()|ReleasePins() called twice."); + Debug.Assert(_nativeRequest != null, "RequestContextBase::ReleasePins()|ReleasePins() called twice."); _originalBufferAddress = (IntPtr)_nativeRequest; + _memoryHandle.Dispose(); + _memoryHandle = default; _nativeRequest = null; _nativeOverlapped?.Dispose(); _nativeOverlapped = null; @@ -110,8 +134,14 @@ namespace Microsoft.AspNetCore.HttpSys.Internal public virtual void Dispose() { - Debug.Assert(_nativeRequest == null, "RequestContextBase::Dispose()|Dispose() called before ReleasePins()."); - _nativeOverlapped?.Dispose(); + if (!_disposed) + { + _disposed = true; + Debug.Assert(_nativeRequest == null, "RequestContextBase::Dispose()|Dispose() called before ReleasePins()."); + _nativeOverlapped?.Dispose(); + _memoryHandle.Dispose(); + _backingBuffer.Dispose(); + } } // These methods require the HTTP_REQUEST to still be pinned in its original location. @@ -272,7 +302,7 @@ namespace Microsoft.AspNetCore.HttpSys.Internal } else { - fixed (byte* pMemoryBlob = _backingBuffer) + fixed (byte* pMemoryBlob = _backingBuffer.Memory.Span) { var request = (HttpApiTypes.HTTP_REQUEST*)(pMemoryBlob + _bufferAlignment); long fixup = pMemoryBlob - (byte*)_originalBufferAddress; @@ -306,7 +336,7 @@ namespace Microsoft.AspNetCore.HttpSys.Internal else { // Return value. - fixed (byte* pMemoryBlob = _backingBuffer) + fixed (byte* pMemoryBlob = _backingBuffer.Memory.Span) { var request = (HttpApiTypes.HTTP_REQUEST*)(pMemoryBlob + _bufferAlignment); long fixup = pMemoryBlob - (byte*)_originalBufferAddress; @@ -366,7 +396,7 @@ namespace Microsoft.AspNetCore.HttpSys.Internal } else { - fixed (byte* pMemoryBlob = _backingBuffer) + fixed (byte* pMemoryBlob = _backingBuffer.Memory.Span) { var request = (HttpApiTypes.HTTP_REQUEST*)(pMemoryBlob + _bufferAlignment); return GetEndPointHelper(localEndpoint, request, pMemoryBlob); @@ -426,7 +456,7 @@ namespace Microsoft.AspNetCore.HttpSys.Internal } else { - fixed (byte* pMemoryBlob = _backingBuffer) + fixed (byte* pMemoryBlob = _backingBuffer.Memory.Span) { var request = (HttpApiTypes.HTTP_REQUEST*)(pMemoryBlob + _bufferAlignment); long fixup = pMemoryBlob - (byte*)_originalBufferAddress; @@ -490,7 +520,7 @@ namespace Microsoft.AspNetCore.HttpSys.Internal } else { - fixed (byte* pMemoryBlob = _backingBuffer) + fixed (byte* pMemoryBlob = _backingBuffer.Memory.Span) { var request = (HttpApiTypes.HTTP_REQUEST_V2*)(pMemoryBlob + _bufferAlignment); return GetRequestInfo(_originalBufferAddress, request); @@ -514,7 +544,7 @@ namespace Microsoft.AspNetCore.HttpSys.Internal var offset = (long)requestInfo.pInfo - (long)baseAddress; info.Add( (int)requestInfo.InfoType, - new ReadOnlyMemory(_backingBuffer, (int)offset, (int)requestInfo.InfoLength)); + _backingBuffer.Memory.Slice((int)offset, (int)requestInfo.InfoLength)); } return new ReadOnlyDictionary>(info); @@ -528,7 +558,7 @@ namespace Microsoft.AspNetCore.HttpSys.Internal } else { - fixed (byte* pMemoryBlob = _backingBuffer) + fixed (byte* pMemoryBlob = _backingBuffer.Memory.Span) { var request = (HttpApiTypes.HTTP_REQUEST_V2*)(pMemoryBlob + _bufferAlignment); return GetClientCertificate(_originalBufferAddress, request); diff --git a/src/Shared/NonCapturingTimer/NonCapturingTimer.cs b/src/Shared/NonCapturingTimer/NonCapturingTimer.cs new file mode 100644 index 0000000000..6f54b2db47 --- /dev/null +++ b/src/Shared/NonCapturingTimer/NonCapturingTimer.cs @@ -0,0 +1,43 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Threading; + +namespace Microsoft.Extensions.Internal +{ + // A convenience API for interacting with System.Threading.Timer in a way + // that doesn't capture the ExecutionContext. We should be using this (or equivalent) + // everywhere we use timers to avoid rooting any values stored in asynclocals. + internal static class NonCapturingTimer + { + public static Timer Create(TimerCallback callback, object state, TimeSpan dueTime, TimeSpan period) + { + if (callback == null) + { + throw new ArgumentNullException(nameof(callback)); + } + + // Don't capture the current ExecutionContext and its AsyncLocals onto the timer + bool restoreFlow = false; + try + { + if (!ExecutionContext.IsFlowSuppressed()) + { + ExecutionContext.SuppressFlow(); + restoreFlow = true; + } + + return new Timer(callback, state, dueTime, period); + } + finally + { + // Restore the current ExecutionContext + if (restoreFlow) + { + ExecutionContext.RestoreFlow(); + } + } + } + } +} diff --git a/src/Shared/ParameterDefaultValue/ParameterDefaultValue.cs b/src/Shared/ParameterDefaultValue/ParameterDefaultValue.cs new file mode 100644 index 0000000000..dc635bb789 --- /dev/null +++ b/src/Shared/ParameterDefaultValue/ParameterDefaultValue.cs @@ -0,0 +1,62 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Reflection; + +namespace Microsoft.Extensions.Internal +{ + internal class ParameterDefaultValue + { + private static readonly Type _nullable = typeof(Nullable<>); + + public static bool TryGetDefaultValue(ParameterInfo parameter, out object defaultValue) + { + bool hasDefaultValue; + var tryToGetDefaultValue = true; + defaultValue = null; + + try + { + hasDefaultValue = parameter.HasDefaultValue; + } + catch (FormatException) when (parameter.ParameterType == typeof(DateTime)) + { + // Workaround for https://github.com/dotnet/corefx/issues/12338 + // If HasDefaultValue throws FormatException for DateTime + // we expect it to have default value + hasDefaultValue = true; + tryToGetDefaultValue = false; + } + + if (hasDefaultValue) + { + if (tryToGetDefaultValue) + { + defaultValue = parameter.DefaultValue; + } + + // Workaround for https://github.com/dotnet/corefx/issues/11797 + if (defaultValue == null && parameter.ParameterType.IsValueType) + { + defaultValue = Activator.CreateInstance(parameter.ParameterType); + } + + // Handle nullable enums + if (defaultValue != null && + parameter.ParameterType.IsGenericType && + parameter.ParameterType.GetGenericTypeDefinition() == _nullable + ) + { + var underlyingType = Nullable.GetUnderlyingType(parameter.ParameterType); + if (underlyingType != null && underlyingType.IsEnum) + { + defaultValue = Enum.ToObject(underlyingType, defaultValue); + } + } + } + + return hasDefaultValue; + } + } +} diff --git a/src/ProjectTemplates/test/Helpers/ProcessEx.cs b/src/Shared/Process/ProcessEx.cs similarity index 85% rename from src/ProjectTemplates/test/Helpers/ProcessEx.cs rename to src/Shared/Process/ProcessEx.cs index 517c23e7a8..c1743a2f0a 100644 --- a/src/ProjectTemplates/test/Helpers/ProcessEx.cs +++ b/src/Shared/Process/ProcessEx.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; +using System.IO; using System.Linq; using System.Reflection; using System.Runtime.InteropServices; @@ -14,7 +15,7 @@ using System.Threading.Tasks; using Microsoft.Extensions.Internal; using Xunit.Abstractions; -namespace Templates.Test.Helpers +namespace Microsoft.AspNetCore.Internal { internal class ProcessEx : IDisposable { @@ -100,6 +101,11 @@ namespace Templates.Test.Helpers startInfo.EnvironmentVariables["NUGET_PACKAGES"] = NUGET_PACKAGES; + if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("helix"))) + { + startInfo.EnvironmentVariables["NUGET_FALLBACK_PACKAGES"] = Environment.GetEnvironmentVariable("NUGET_FALLBACK_PACKAGES"); + } + output.WriteLine($"==> {startInfo.FileName} {startInfo.Arguments} [{startInfo.WorkingDirectory}]"); var proc = Process.Start(startInfo); @@ -164,7 +170,7 @@ namespace Templates.Test.Helpers { if (!_process.HasExited) { - throw new InvalidOperationException("Process has not finished running."); + throw new InvalidOperationException($"Process {_process.ProcessName} with pid: {_process.Id} has not finished running."); } return $"Process exited with code {_process.ExitCode}\nStdErr: {Error}\nStdOut: {Output}"; @@ -174,22 +180,27 @@ namespace Templates.Test.Helpers { if(!timeSpan.HasValue) { - timeSpan = TimeSpan.FromSeconds(480); + timeSpan = TimeSpan.FromSeconds(600); } - Exited.Wait(timeSpan.Value); - - if (assertSuccess && _process.ExitCode != 0) + var exited = Exited.Wait(timeSpan.Value); + if (!exited) + { + _output.WriteLine($"The process didn't exit within the allotted time ({timeSpan.Value.TotalSeconds} seconds)."); + _process.Dispose(); + } + else if (assertSuccess && _process.ExitCode != 0) { throw new Exception($"Process exited with code {_process.ExitCode}\nStdErr: {Error}\nStdOut: {Output}"); } } - private static string GetNugetPackagesRestorePath() => - typeof(ProcessEx).Assembly + private static string GetNugetPackagesRestorePath() => (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("NUGET_RESTORE"))) + ? typeof(ProcessEx).Assembly .GetCustomAttributes() .First(attribute => attribute.Key == "TestPackageRestorePath") - .Value; + .Value + : Environment.GetEnvironmentVariable("NUGET_RESTORE"); public void Dispose() { diff --git a/src/Shared/RazorViews/BaseView.cs b/src/Shared/RazorViews/BaseView.cs index 8f6f4c3245..97a089267f 100644 --- a/src/Shared/RazorViews/BaseView.cs +++ b/src/Shared/RazorViews/BaseView.cs @@ -64,7 +64,7 @@ namespace Microsoft.Extensions.RazorViews /// The stream to write to public async Task ExecuteAsync(Stream stream) { - // We technically don't need this intermediate buffer if this method accepts a memory stream. + // We technically don't need this intermediate buffer if this method accepts a memory stream. var buffer = new MemoryStream(); Output = new StreamWriter(buffer, UTF8NoBOM, 4096, leaveOpen: true); await ExecuteAsync(); @@ -149,11 +149,11 @@ namespace Microsoft.Extensions.RazorViews private string AttributeEnding { get; set; } - protected void BeginWriteAttribute(string name, string begining, int startPosition, string ending, int endPosition, int thingy) + protected void BeginWriteAttribute(string name, string beginning, int startPosition, string ending, int endPosition, int thingy) { Debug.Assert(string.IsNullOrEmpty(AttributeEnding)); - Output.Write(begining); + Output.Write(beginning); AttributeEnding = ending; } diff --git a/src/Shared/ServerInfrastructure/BufferExtensions.cs b/src/Shared/ServerInfrastructure/BufferExtensions.cs new file mode 100644 index 0000000000..34cbeb5357 --- /dev/null +++ b/src/Shared/ServerInfrastructure/BufferExtensions.cs @@ -0,0 +1,173 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Diagnostics; +using System.IO.Pipelines; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; + +namespace System.Buffers +{ + internal static class BufferExtensions + { + private const int _maxULongByteLength = 20; + + [ThreadStatic] + private static byte[] _numericBytesScratch; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlySpan ToSpan(in this ReadOnlySequence buffer) + { + if (buffer.IsSingleSegment) + { + return buffer.FirstSpan; + } + return buffer.ToArray(); + } + + public static ArraySegment GetArray(this Memory buffer) + { + return ((ReadOnlyMemory)buffer).GetArray(); + } + + public static ArraySegment GetArray(this ReadOnlyMemory memory) + { + if (!MemoryMarshal.TryGetArray(memory, out var result)) + { + throw new InvalidOperationException("Buffer backed by array was expected"); + } + return result; + } + + internal static void WriteAscii(ref this BufferWriter buffer, string data) + { + if (string.IsNullOrEmpty(data)) + { + return; + } + + var dest = buffer.Span; + var sourceLength = data.Length; + // Fast path, try encoding to the available memory directly + if (sourceLength <= dest.Length) + { + Encoding.ASCII.GetBytes(data, dest); + buffer.Advance(sourceLength); + } + else + { + WriteAsciiMultiWrite(ref buffer, data); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static unsafe void WriteNumeric(ref this BufferWriter buffer, ulong number) + { + const byte AsciiDigitStart = (byte)'0'; + + var span = buffer.Span; + var bytesLeftInBlock = span.Length; + + // Fast path, try copying to the available memory directly + var simpleWrite = true; + fixed (byte* output = span) + { + var start = output; + if (number < 10 && bytesLeftInBlock >= 1) + { + *(start) = (byte)(((uint)number) + AsciiDigitStart); + buffer.Advance(1); + } + else if (number < 100 && bytesLeftInBlock >= 2) + { + var val = (uint)number; + var tens = (byte)((val * 205u) >> 11); // div10, valid to 1028 + + *(start) = (byte)(tens + AsciiDigitStart); + *(start + 1) = (byte)(val - (tens * 10) + AsciiDigitStart); + buffer.Advance(2); + } + else if (number < 1000 && bytesLeftInBlock >= 3) + { + var val = (uint)number; + var digit0 = (byte)((val * 41u) >> 12); // div100, valid to 1098 + var digits01 = (byte)((val * 205u) >> 11); // div10, valid to 1028 + + *(start) = (byte)(digit0 + AsciiDigitStart); + *(start + 1) = (byte)(digits01 - (digit0 * 10) + AsciiDigitStart); + *(start + 2) = (byte)(val - (digits01 * 10) + AsciiDigitStart); + buffer.Advance(3); + } + else + { + simpleWrite = false; + } + } + + if (!simpleWrite) + { + WriteNumericMultiWrite(ref buffer, number); + } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void WriteNumericMultiWrite(ref this BufferWriter buffer, ulong number) + { + const byte AsciiDigitStart = (byte)'0'; + + var value = number; + var position = _maxULongByteLength; + var byteBuffer = NumericBytesScratch; + do + { + // Consider using Math.DivRem() if available + var quotient = value / 10; + byteBuffer[--position] = (byte)(AsciiDigitStart + (value - quotient * 10)); // 0x30 = '0' + value = quotient; + } + while (value != 0); + + var length = _maxULongByteLength - position; + buffer.Write(new ReadOnlySpan(byteBuffer, position, length)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void WriteAsciiMultiWrite(ref this BufferWriter buffer, string data) + { + var dataLength = data.Length; + var offset = 0; + var bytes = buffer.Span; + do + { + var writable = Math.Min(dataLength - offset, bytes.Length); + // Zero length spans are possible, though unlikely. + // ASCII.GetBytes and .Advance will both handle them so we won't special case for them. + Encoding.ASCII.GetBytes(data.AsSpan(offset, writable), bytes); + buffer.Advance(writable); + + offset += writable; + if (offset >= dataLength) + { + Debug.Assert(offset == dataLength); + // Encoded everything + break; + } + + // Get new span, more to encode. + buffer.Ensure(); + bytes = buffer.Span; + } while (true); + } + + private static byte[] NumericBytesScratch => _numericBytesScratch ?? CreateNumericBytesScratch(); + + [MethodImpl(MethodImplOptions.NoInlining)] + private static byte[] CreateNumericBytesScratch() + { + var bytes = new byte[_maxULongByteLength]; + _numericBytesScratch = bytes; + return bytes; + } + } +} diff --git a/src/Servers/Kestrel/Core/src/Internal/BufferWriter.cs b/src/Shared/ServerInfrastructure/BufferWriter.cs similarity index 100% rename from src/Servers/Kestrel/Core/src/Internal/BufferWriter.cs rename to src/Shared/ServerInfrastructure/BufferWriter.cs diff --git a/src/Servers/Kestrel/shared/DuplexPipe.cs b/src/Shared/ServerInfrastructure/DuplexPipe.cs similarity index 96% rename from src/Servers/Kestrel/shared/DuplexPipe.cs rename to src/Shared/ServerInfrastructure/DuplexPipe.cs index 1c5896fc0d..cee167b20b 100644 --- a/src/Servers/Kestrel/shared/DuplexPipe.cs +++ b/src/Shared/ServerInfrastructure/DuplexPipe.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft. All rights reserved. +// Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. namespace System.IO.Pipelines diff --git a/src/Servers/Kestrel/Core/src/Middleware/Internal/DuplexPipeStream.cs b/src/Shared/ServerInfrastructure/DuplexPipeStream.cs similarity index 71% rename from src/Servers/Kestrel/Core/src/Middleware/Internal/DuplexPipeStream.cs rename to src/Shared/ServerInfrastructure/DuplexPipeStream.cs index b81a5e8869..7a2dfdf362 100644 --- a/src/Servers/Kestrel/Core/src/Middleware/Internal/DuplexPipeStream.cs +++ b/src/Shared/ServerInfrastructure/DuplexPipeStream.cs @@ -152,78 +152,22 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state) { - var task = ReadAsync(buffer, offset, count, default, state); - if (callback != null) - { - task.ContinueWith(t => callback.Invoke(t)); - } - return task; + return TaskToApm.Begin(ReadAsync(buffer, offset, count), callback, state); } public override int EndRead(IAsyncResult asyncResult) { - return ((Task)asyncResult).GetAwaiter().GetResult(); - } - - private Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken, object state) - { - var tcs = new TaskCompletionSource(state); - var task = ReadAsync(buffer, offset, count, cancellationToken); - task.ContinueWith((task2, state2) => - { - var tcs2 = (TaskCompletionSource)state2; - if (task2.IsCanceled) - { - tcs2.SetCanceled(); - } - else if (task2.IsFaulted) - { - tcs2.SetException(task2.Exception); - } - else - { - tcs2.SetResult(task2.Result); - } - }, tcs, cancellationToken); - return tcs.Task; + return TaskToApm.End(asyncResult); } public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state) { - var task = WriteAsync(buffer, offset, count, default, state); - if (callback != null) - { - task.ContinueWith(t => callback.Invoke(t)); - } - return task; + return TaskToApm.Begin(WriteAsync(buffer, offset, count), callback, state); } public override void EndWrite(IAsyncResult asyncResult) { - ((Task)asyncResult).GetAwaiter().GetResult(); - } - - private Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken, object state) - { - var tcs = new TaskCompletionSource(state); - var task = WriteAsync(buffer, offset, count, cancellationToken); - task.ContinueWith((task2, state2) => - { - var tcs2 = (TaskCompletionSource)state2; - if (task2.IsCanceled) - { - tcs2.SetCanceled(); - } - else if (task2.IsFaulted) - { - tcs2.SetException(task2.Exception); - } - else - { - tcs2.SetResult(null); - } - }, tcs, cancellationToken); - return tcs.Task; + TaskToApm.End(asyncResult); } } } diff --git a/src/Servers/Kestrel/Core/src/Middleware/Internal/DuplexPipeStreamAdapter.cs b/src/Shared/ServerInfrastructure/DuplexPipeStreamAdapter.cs similarity index 100% rename from src/Servers/Kestrel/Core/src/Middleware/Internal/DuplexPipeStreamAdapter.cs rename to src/Shared/ServerInfrastructure/DuplexPipeStreamAdapter.cs diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Bitshifter.cs b/src/Shared/ServerInfrastructure/Http2/Bitshifter.cs similarity index 100% rename from src/Servers/Kestrel/Core/src/Internal/Http2/Bitshifter.cs rename to src/Shared/ServerInfrastructure/Http2/Bitshifter.cs diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2ConnectionErrorException.cs b/src/Shared/ServerInfrastructure/Http2/Http2ConnectionErrorException.cs similarity index 100% rename from src/Servers/Kestrel/Core/src/Internal/Http2/Http2ConnectionErrorException.cs rename to src/Shared/ServerInfrastructure/Http2/Http2ConnectionErrorException.cs diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2ContinuationFrameFlags.cs b/src/Shared/ServerInfrastructure/Http2/Http2ContinuationFrameFlags.cs similarity index 100% rename from src/Servers/Kestrel/Core/src/Internal/Http2/Http2ContinuationFrameFlags.cs rename to src/Shared/ServerInfrastructure/Http2/Http2ContinuationFrameFlags.cs diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2DataFrameFlags.cs b/src/Shared/ServerInfrastructure/Http2/Http2DataFrameFlags.cs similarity index 100% rename from src/Servers/Kestrel/Core/src/Internal/Http2/Http2DataFrameFlags.cs rename to src/Shared/ServerInfrastructure/Http2/Http2DataFrameFlags.cs diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2ErrorCode.cs b/src/Shared/ServerInfrastructure/Http2/Http2ErrorCode.cs similarity index 100% rename from src/Servers/Kestrel/Core/src/Internal/Http2/Http2ErrorCode.cs rename to src/Shared/ServerInfrastructure/Http2/Http2ErrorCode.cs diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Frame.Continuation.cs b/src/Shared/ServerInfrastructure/Http2/Http2Frame.Continuation.cs similarity index 100% rename from src/Servers/Kestrel/Core/src/Internal/Http2/Http2Frame.Continuation.cs rename to src/Shared/ServerInfrastructure/Http2/Http2Frame.Continuation.cs diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Frame.Data.cs b/src/Shared/ServerInfrastructure/Http2/Http2Frame.Data.cs similarity index 100% rename from src/Servers/Kestrel/Core/src/Internal/Http2/Http2Frame.Data.cs rename to src/Shared/ServerInfrastructure/Http2/Http2Frame.Data.cs diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Frame.GoAway.cs b/src/Shared/ServerInfrastructure/Http2/Http2Frame.GoAway.cs similarity index 100% rename from src/Servers/Kestrel/Core/src/Internal/Http2/Http2Frame.GoAway.cs rename to src/Shared/ServerInfrastructure/Http2/Http2Frame.GoAway.cs diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Frame.Headers.cs b/src/Shared/ServerInfrastructure/Http2/Http2Frame.Headers.cs similarity index 100% rename from src/Servers/Kestrel/Core/src/Internal/Http2/Http2Frame.Headers.cs rename to src/Shared/ServerInfrastructure/Http2/Http2Frame.Headers.cs diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Frame.Ping.cs b/src/Shared/ServerInfrastructure/Http2/Http2Frame.Ping.cs similarity index 100% rename from src/Servers/Kestrel/Core/src/Internal/Http2/Http2Frame.Ping.cs rename to src/Shared/ServerInfrastructure/Http2/Http2Frame.Ping.cs diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Frame.Priority.cs b/src/Shared/ServerInfrastructure/Http2/Http2Frame.Priority.cs similarity index 100% rename from src/Servers/Kestrel/Core/src/Internal/Http2/Http2Frame.Priority.cs rename to src/Shared/ServerInfrastructure/Http2/Http2Frame.Priority.cs diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Frame.RstStream.cs b/src/Shared/ServerInfrastructure/Http2/Http2Frame.RstStream.cs similarity index 100% rename from src/Servers/Kestrel/Core/src/Internal/Http2/Http2Frame.RstStream.cs rename to src/Shared/ServerInfrastructure/Http2/Http2Frame.RstStream.cs diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Frame.Settings.cs b/src/Shared/ServerInfrastructure/Http2/Http2Frame.Settings.cs similarity index 100% rename from src/Servers/Kestrel/Core/src/Internal/Http2/Http2Frame.Settings.cs rename to src/Shared/ServerInfrastructure/Http2/Http2Frame.Settings.cs diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Frame.WindowUpdate.cs b/src/Shared/ServerInfrastructure/Http2/Http2Frame.WindowUpdate.cs similarity index 100% rename from src/Servers/Kestrel/Core/src/Internal/Http2/Http2Frame.WindowUpdate.cs rename to src/Shared/ServerInfrastructure/Http2/Http2Frame.WindowUpdate.cs diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Frame.cs b/src/Shared/ServerInfrastructure/Http2/Http2Frame.cs similarity index 100% rename from src/Servers/Kestrel/Core/src/Internal/Http2/Http2Frame.cs rename to src/Shared/ServerInfrastructure/Http2/Http2Frame.cs diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2FrameReader.cs b/src/Shared/ServerInfrastructure/Http2/Http2FrameReader.cs similarity index 92% rename from src/Servers/Kestrel/Core/src/Internal/Http2/Http2FrameReader.cs rename to src/Shared/ServerInfrastructure/Http2/Http2FrameReader.cs index 8437ad334a..5762b4a671 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2FrameReader.cs +++ b/src/Shared/ServerInfrastructure/Http2/Http2FrameReader.cs @@ -6,7 +6,6 @@ using System.Buffers; using System.Buffers.Binary; using System.Collections.Generic; using System.Diagnostics; -using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 { @@ -31,27 +30,27 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 public const int SettingSize = 6; // 2 bytes for the id, 4 bytes for the value. - public static bool ReadFrame(in ReadOnlySequence readableBuffer, Http2Frame frame, uint maxFrameSize, out ReadOnlySequence framePayload) + public static bool TryReadFrame(ref ReadOnlySequence buffer, Http2Frame frame, uint maxFrameSize, out ReadOnlySequence framePayload) { framePayload = ReadOnlySequence.Empty; - if (readableBuffer.Length < HeaderLength) + if (buffer.Length < HeaderLength) { return false; } - var headerSlice = readableBuffer.Slice(0, HeaderLength); + var headerSlice = buffer.Slice(0, HeaderLength); var header = headerSlice.ToSpan(); var payloadLength = (int)Bitshifter.ReadUInt24BigEndian(header); if (payloadLength > maxFrameSize) { - throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorFrameOverLimit(payloadLength, maxFrameSize), Http2ErrorCode.FRAME_SIZE_ERROR); + throw new Http2ConnectionErrorException(SharedStrings.FormatHttp2ErrorFrameOverLimit(payloadLength, maxFrameSize), Http2ErrorCode.FRAME_SIZE_ERROR); } // Make sure the whole frame is buffered var frameLength = HeaderLength + payloadLength; - if (readableBuffer.Length < frameLength) + if (buffer.Length < frameLength) { return false; } @@ -61,10 +60,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 frame.Flags = header[FlagsOffset]; frame.StreamId = (int)Bitshifter.ReadUInt31BigEndian(header.Slice(StreamIdOffset)); - var extendedHeaderLength = ReadExtendedFields(frame, readableBuffer); + var extendedHeaderLength = ReadExtendedFields(frame, buffer); // The remaining payload minus the extra fields - framePayload = readableBuffer.Slice(HeaderLength + extendedHeaderLength, payloadLength - extendedHeaderLength); + framePayload = buffer.Slice(HeaderLength + extendedHeaderLength, payloadLength - extendedHeaderLength); + buffer = buffer.Slice(framePayload.End); return true; } @@ -77,7 +77,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 if (extendedHeaderLength > frame.PayloadLength) { throw new Http2ConnectionErrorException( - CoreStrings.FormatHttp2ErrorUnexpectedFrameLength(frame.Type, expectedLength: extendedHeaderLength), Http2ErrorCode.FRAME_SIZE_ERROR); + SharedStrings.FormatHttp2ErrorUnexpectedFrameLength(frame.Type, expectedLength: extendedHeaderLength), Http2ErrorCode.FRAME_SIZE_ERROR); } var extendedHeaders = readableBuffer.Slice(HeaderLength, extendedHeaderLength).ToSpan(); diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2FrameType.cs b/src/Shared/ServerInfrastructure/Http2/Http2FrameType.cs similarity index 100% rename from src/Servers/Kestrel/Core/src/Internal/Http2/Http2FrameType.cs rename to src/Shared/ServerInfrastructure/Http2/Http2FrameType.cs diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2HeadersFrameFlags.cs b/src/Shared/ServerInfrastructure/Http2/Http2HeadersFrameFlags.cs similarity index 100% rename from src/Servers/Kestrel/Core/src/Internal/Http2/Http2HeadersFrameFlags.cs rename to src/Shared/ServerInfrastructure/Http2/Http2HeadersFrameFlags.cs diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2PeerSetting.cs b/src/Shared/ServerInfrastructure/Http2/Http2PeerSetting.cs similarity index 100% rename from src/Servers/Kestrel/Core/src/Internal/Http2/Http2PeerSetting.cs rename to src/Shared/ServerInfrastructure/Http2/Http2PeerSetting.cs diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2PeerSettings.cs b/src/Shared/ServerInfrastructure/Http2/Http2PeerSettings.cs similarity index 100% rename from src/Servers/Kestrel/Core/src/Internal/Http2/Http2PeerSettings.cs rename to src/Shared/ServerInfrastructure/Http2/Http2PeerSettings.cs diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2PingFrameFlags.cs b/src/Shared/ServerInfrastructure/Http2/Http2PingFrameFlags.cs similarity index 100% rename from src/Servers/Kestrel/Core/src/Internal/Http2/Http2PingFrameFlags.cs rename to src/Shared/ServerInfrastructure/Http2/Http2PingFrameFlags.cs diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2SettingsFrameFlags.cs b/src/Shared/ServerInfrastructure/Http2/Http2SettingsFrameFlags.cs similarity index 100% rename from src/Servers/Kestrel/Core/src/Internal/Http2/Http2SettingsFrameFlags.cs rename to src/Shared/ServerInfrastructure/Http2/Http2SettingsFrameFlags.cs diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2SettingsParameter.cs b/src/Shared/ServerInfrastructure/Http2/Http2SettingsParameter.cs similarity index 100% rename from src/Servers/Kestrel/Core/src/Internal/Http2/Http2SettingsParameter.cs rename to src/Shared/ServerInfrastructure/Http2/Http2SettingsParameter.cs diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2SettingsParameterOutOfRangeException.cs b/src/Shared/ServerInfrastructure/Http2/Http2SettingsParameterOutOfRangeException.cs similarity index 100% rename from src/Servers/Kestrel/Core/src/Internal/Http2/Http2SettingsParameterOutOfRangeException.cs rename to src/Shared/ServerInfrastructure/Http2/Http2SettingsParameterOutOfRangeException.cs diff --git a/src/Shared/ServerInfrastructure/ManualResetValueTaskSource.cs b/src/Shared/ServerInfrastructure/ManualResetValueTaskSource.cs new file mode 100644 index 0000000000..97284208e6 --- /dev/null +++ b/src/Shared/ServerInfrastructure/ManualResetValueTaskSource.cs @@ -0,0 +1,35 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Threading.Tasks.Sources; + +namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal +{ + internal sealed class ManualResetValueTaskSource : IValueTaskSource, IValueTaskSource + { + private ManualResetValueTaskSourceCore _core; // mutable struct; do not make this readonly + + public bool RunContinuationsAsynchronously { get => _core.RunContinuationsAsynchronously; set => _core.RunContinuationsAsynchronously = value; } + public short Version => _core.Version; + public void Reset() => _core.Reset(); + public void SetResult(T result) => _core.SetResult(result); + public void SetException(Exception error) => _core.SetException(error); + + public T GetResult(short token) => _core.GetResult(token); + void IValueTaskSource.GetResult(short token) => _core.GetResult(token); + public ValueTaskSourceStatus GetStatus(short token) => _core.GetStatus(token); + public void OnCompleted(Action continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags) => _core.OnCompleted(continuation, state, token, flags); + + public ValueTaskSourceStatus GetStatus() => _core.GetStatus(_core.Version); + + public void TrySetResult(T result) + { + if (_core.GetStatus(_core.Version) == ValueTaskSourceStatus.Pending) + { + _core.SetResult(result); + } + } + } +} diff --git a/src/Servers/Kestrel/Core/src/Internal/MemoryPoolExtensions.cs b/src/Shared/ServerInfrastructure/MemoryPoolExtensions.cs similarity index 100% rename from src/Servers/Kestrel/Core/src/Internal/MemoryPoolExtensions.cs rename to src/Shared/ServerInfrastructure/MemoryPoolExtensions.cs diff --git a/src/Shared/ServerInfrastructure/ReadMe.md b/src/Shared/ServerInfrastructure/ReadMe.md new file mode 100644 index 0000000000..a23cac13d7 --- /dev/null +++ b/src/Shared/ServerInfrastructure/ReadMe.md @@ -0,0 +1 @@ +This folder contains shared product code that is also used with the Http2Cat test framework. diff --git a/src/Shared/ServerInfrastructure/SharedStrings.resx b/src/Shared/ServerInfrastructure/SharedStrings.resx new file mode 100644 index 0000000000..e6fe3d4ebe --- /dev/null +++ b/src/Shared/ServerInfrastructure/SharedStrings.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The client sent a {frameType} frame with length different than {expectedLength}. + + + The received frame size of {size} exceeds the limit {limit}. + + \ No newline at end of file diff --git a/src/Shared/ServerInfrastructure/SslDuplexPipe.cs b/src/Shared/ServerInfrastructure/SslDuplexPipe.cs new file mode 100644 index 0000000000..d53ced0031 --- /dev/null +++ b/src/Shared/ServerInfrastructure/SslDuplexPipe.cs @@ -0,0 +1,24 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; +using System.IO.Pipelines; +using System.Net.Security; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; + +namespace Microsoft.AspNetCore.Server.Kestrel.Https.Internal +{ + internal class SslDuplexPipe : DuplexPipeStreamAdapter + { + public SslDuplexPipe(IDuplexPipe transport, StreamPipeReaderOptions readerOptions, StreamPipeWriterOptions writerOptions) + : this(transport, readerOptions, writerOptions, s => new SslStream(s)) + { + } + + public SslDuplexPipe(IDuplexPipe transport, StreamPipeReaderOptions readerOptions, StreamPipeWriterOptions writerOptions, Func factory) : + base(transport, readerOptions, writerOptions, factory) + { + } + } +} diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/StringUtilities.cs b/src/Shared/ServerInfrastructure/StringUtilities.cs similarity index 62% rename from src/Servers/Kestrel/Core/src/Internal/Infrastructure/StringUtilities.cs rename to src/Shared/ServerInfrastructure/StringUtilities.cs index 268d77a0e4..5468ae3c9a 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/StringUtilities.cs +++ b/src/Shared/ServerInfrastructure/StringUtilities.cs @@ -2,16 +2,18 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Buffers; using System.Diagnostics; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.X86; using System.Text; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure { - internal class StringUtilities + internal static class StringUtilities { [MethodImpl(MethodImplOptions.AggressiveOptimization)] public static unsafe bool TryGetAsciiString(byte* input, char* output, int count) @@ -19,88 +21,83 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure Debug.Assert(input != null); Debug.Assert(output != null); - // Calculate end position var end = input + count; - // Start as valid - var isValid = true; - do + Debug.Assert((long)end >= Vector256.Count); + + // PERF: so the JIT can reuse the zero from a register + Vector128 zero = Vector128.Zero; + + if (Sse2.IsSupported) { - // If Vector not-accelerated or remaining less than vector size - if (!Vector.IsHardwareAccelerated || input > end - Vector.Count) + if (Avx2.IsSupported && input <= end - Vector256.Count) { - if (IntPtr.Size == 8) // Use Intrinsic switch for branch elimination + Vector256 avxZero = Vector256.Zero; + + do { - // 64-bit: Loop longs by default - while (input <= end - sizeof(long)) + var vector = Avx.LoadVector256(input).AsSByte(); + if (!CheckBytesInAsciiRange(vector, avxZero)) { - isValid &= CheckBytesInAsciiRange(((long*)input)[0]); - - output[0] = (char)input[0]; - output[1] = (char)input[1]; - output[2] = (char)input[2]; - output[3] = (char)input[3]; - output[4] = (char)input[4]; - output[5] = (char)input[5]; - output[6] = (char)input[6]; - output[7] = (char)input[7]; - - input += sizeof(long); - output += sizeof(long); + return false; } - if (input <= end - sizeof(int)) - { - isValid &= CheckBytesInAsciiRange(((int*)input)[0]); - output[0] = (char)input[0]; - output[1] = (char)input[1]; - output[2] = (char)input[2]; - output[3] = (char)input[3]; + var tmp0 = Avx2.UnpackLow(vector, avxZero); + var tmp1 = Avx2.UnpackHigh(vector, avxZero); - input += sizeof(int); - output += sizeof(int); - } - } - else + // Bring into the right order + var out0 = Avx2.Permute2x128(tmp0, tmp1, 0x20); + var out1 = Avx2.Permute2x128(tmp0, tmp1, 0x31); + + Avx.Store((ushort*)output, out0.AsUInt16()); + Avx.Store((ushort*)output + Vector256.Count, out1.AsUInt16()); + + input += Vector256.Count; + output += Vector256.Count; + } while (input <= end - Vector256.Count); + + if (input == end) { - // 32-bit: Loop ints by default - while (input <= end - sizeof(int)) - { - isValid &= CheckBytesInAsciiRange(((int*)input)[0]); - - output[0] = (char)input[0]; - output[1] = (char)input[1]; - output[2] = (char)input[2]; - output[3] = (char)input[3]; - - input += sizeof(int); - output += sizeof(int); - } + return true; } - if (input <= end - sizeof(short)) - { - isValid &= CheckBytesInAsciiRange(((short*)input)[0]); - - output[0] = (char)input[0]; - output[1] = (char)input[1]; - - input += sizeof(short); - output += sizeof(short); - } - if (input < end) - { - isValid &= CheckBytesInAsciiRange(((sbyte*)input)[0]); - output[0] = (char)input[0]; - } - - return isValid; } - // do/while as entry condition already checked - do + if (input <= end - Vector128.Count) + { + do + { + var vector = Sse2.LoadVector128(input).AsSByte(); + if (!CheckBytesInAsciiRange(vector, zero)) + { + return false; + } + + var c0 = Sse2.UnpackLow(vector, zero).AsUInt16(); + var c1 = Sse2.UnpackHigh(vector, zero).AsUInt16(); + + Sse2.Store((ushort*)output, c0); + Sse2.Store((ushort*)output + Vector128.Count, c1); + + input += Vector128.Count; + output += Vector128.Count; + } while (input <= end - Vector128.Count); + + if (input == end) + { + return true; + } + } + } + else if (Vector.IsHardwareAccelerated) + { + while (input <= end - Vector.Count) { var vector = Unsafe.AsRef>(input); - isValid &= CheckBytesInAsciiRange(vector); + if (!CheckBytesInAsciiRange(vector)) + { + return false; + } + Vector.Widen( vector, out Unsafe.AsRef>(output), @@ -108,13 +105,104 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure input += Vector.Count; output += Vector.Count; - } while (input <= end - Vector.Count); + } - // Vector path done, loop back to do non-Vector - // If is a exact multiple of vector size, bail now - } while (input < end); + if (input == end) + { + return true; + } + } - return isValid; + if (Environment.Is64BitProcess) // Use Intrinsic switch for branch elimination + { + // 64-bit: Loop longs by default + while (input <= end - sizeof(long)) + { + var value = *(long*)input; + if (!CheckBytesInAsciiRange(value)) + { + return false; + } + + // BMI2 could be used, but this variant is faster on both Intel and AMD. + if (Sse2.X64.IsSupported) + { + Vector128 vecNarrow = Sse2.X64.ConvertScalarToVector128Int64(value).AsSByte(); + Vector128 vecWide = Sse2.UnpackLow(vecNarrow, zero).AsUInt64(); + Sse2.Store((ulong*)output, vecWide); + } + else + { + output[0] = (char)input[0]; + output[1] = (char)input[1]; + output[2] = (char)input[2]; + output[3] = (char)input[3]; + output[4] = (char)input[4]; + output[5] = (char)input[5]; + output[6] = (char)input[6]; + output[7] = (char)input[7]; + } + + input += sizeof(long); + output += sizeof(long); + } + + if (input <= end - sizeof(int)) + { + var value = *(int*)input; + if (!CheckBytesInAsciiRange(value)) + { + return false; + } + + WidenFourAsciiBytesToUtf16AndWriteToBuffer(output, input, value, zero); + + input += sizeof(int); + output += sizeof(int); + } + } + else + { + // 32-bit: Loop ints by default + while (input <= end - sizeof(int)) + { + var value = *(int*)input; + if (!CheckBytesInAsciiRange(value)) + { + return false; + } + + WidenFourAsciiBytesToUtf16AndWriteToBuffer(output, input, value, zero); + + input += sizeof(int); + output += sizeof(int); + } + } + + if (input <= end - sizeof(short)) + { + if (!CheckBytesInAsciiRange(((short*)input)[0])) + { + return false; + } + + output[0] = (char)input[0]; + output[1] = (char)input[1]; + + input += sizeof(short); + output += sizeof(short); + } + + if (input < end) + { + if (!CheckBytesInAsciiRange(((sbyte*)input)[0])) + { + return false; + } + output[0] = (char)input[0]; + } + + return true; } [MethodImpl(MethodImplOptions.AggressiveOptimization)] @@ -223,7 +311,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure } [MethodImpl(MethodImplOptions.AggressiveOptimization)] - public unsafe static bool BytesOrdinalEqualsStringAndAscii(string previousValue, Span newValue) + public unsafe static bool BytesOrdinalEqualsStringAndAscii(string previousValue, ReadOnlySpan newValue) { // previousValue is a previously materialized string which *must* have already passed validation. Debug.Assert(IsValidHeaderString(previousValue)); @@ -374,6 +462,25 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure return false; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static unsafe void WidenFourAsciiBytesToUtf16AndWriteToBuffer(char* output, byte* input, int value, Vector128 zero) + { + // BMI2 could be used, but this variant is faster on both Intel and AMD. + if (Sse2.X64.IsSupported) + { + Vector128 vecNarrow = Sse2.ConvertScalarToVector128Int32(value).AsSByte(); + Vector128 vecWide = Sse2.UnpackLow(vecNarrow, zero).AsUInt64(); + Unsafe.WriteUnaligned(output, Sse2.X64.ConvertToUInt64(vecWide)); + } + else + { + output[0] = (char)input[0]; + output[1] = (char)input[1]; + output[2] = (char)input[2]; + output[3] = (char)input[3]; + } + } + /// /// Given a DWORD which represents a buffer of 4 bytes, widens the buffer into 4 WORDs and /// compares them to the WORD buffer with machine endianness. @@ -386,11 +493,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure return false; } - if (Bmi2.X64.IsSupported) + // BMI2 could be used, but this variant is faster on both Intel and AMD. + if (Sse2.X64.IsSupported) { - // BMI2 will work regardless of the processor's endianness. + Vector128 vecNarrow = Sse2.ConvertScalarToVector128UInt32(value).AsByte(); + Vector128 vecWide = Sse2.UnpackLow(vecNarrow, Vector128.Zero).AsUInt64(); return Unsafe.ReadUnaligned(ref Unsafe.As(ref charStart)) == - Bmi2.X64.ParallelBitDeposit(value, 0x00FF00FF_00FF00FFul); + Sse2.X64.ConvertToUInt64(vecWide); } else { @@ -423,11 +532,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure return false; } - if (Bmi2.IsSupported) + // BMI2 could be used, but this variant is faster on both Intel and AMD. + if (Sse2.IsSupported) { - // BMI2 will work regardless of the processor's endianness. + Vector128 vecNarrow = Sse2.ConvertScalarToVector128UInt32(value).AsByte(); + Vector128 vecWide = Sse2.UnpackLow(vecNarrow, Vector128.Zero).AsUInt32(); return Unsafe.ReadUnaligned(ref Unsafe.As(ref charStart)) == - Bmi2.ParallelBitDeposit(value, 0x00FF00FFu); + Sse2.ConvertToUInt32(vecWide); } else { @@ -472,12 +583,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true).GetByteCount(value); return !value.Contains('\0'); } - catch (DecoderFallbackException) { + catch (DecoderFallbackException) + { return false; } } - - private static readonly char[] s_encode16Chars = "0123456789ABCDEF".ToCharArray(); + private static readonly SpanAction s_populateSpanWithHexSuffix = PopulateSpanWithHexSuffix; /// /// A faster version of String.Concat(, , .ToString("X8")) @@ -494,28 +605,86 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure length += str.Length; } - return string.Create(length, (str, separator, number), (buffer, tuple) => + return string.Create(length, (str, separator, number), s_populateSpanWithHexSuffix); + } + + private static void PopulateSpanWithHexSuffix(Span buffer, (string str, char separator, uint number) tuple) + { + var (tupleStr, tupleSeparator, tupleNumber) = tuple; + + var i = 0; + if (tupleStr != null) { - var (tupleStr, tupleSeparator, tupleNumber) = tuple; - char[] encode16Chars = s_encode16Chars; + tupleStr.AsSpan().CopyTo(buffer); + i = tupleStr.Length; + } - var i = 0; - if (tupleStr != null) + buffer[i] = tupleSeparator; + i++; + + if (Ssse3.IsSupported) + { + // These must be explicity typed as ReadOnlySpan + // They then become a non-allocating mappings to the data section of the assembly. + // This uses C# compiler's ability to refer to static data directly. For more information see https://vcsjones.dev/2019/02/01/csharp-readonly-span-bytes-static + ReadOnlySpan shuffleMaskData = new byte[16] { - tupleStr.AsSpan().CopyTo(buffer); - i = tupleStr.Length; - } + 0xF, 0xF, 3, 0xF, + 0xF, 0xF, 2, 0xF, + 0xF, 0xF, 1, 0xF, + 0xF, 0xF, 0, 0xF + }; - buffer[i + 8] = encode16Chars[tupleNumber & 0xF]; - buffer[i + 7] = encode16Chars[(tupleNumber >> 4) & 0xF]; - buffer[i + 6] = encode16Chars[(tupleNumber >> 8) & 0xF]; - buffer[i + 5] = encode16Chars[(tupleNumber >> 12) & 0xF]; - buffer[i + 4] = encode16Chars[(tupleNumber >> 16) & 0xF]; - buffer[i + 3] = encode16Chars[(tupleNumber >> 20) & 0xF]; - buffer[i + 2] = encode16Chars[(tupleNumber >> 24) & 0xF]; - buffer[i + 1] = encode16Chars[(tupleNumber >> 28) & 0xF]; - buffer[i] = tupleSeparator; - }); + ReadOnlySpan asciiUpperCaseData = new byte[16] + { + (byte)'0', (byte)'1', (byte)'2', (byte)'3', + (byte)'4', (byte)'5', (byte)'6', (byte)'7', + (byte)'8', (byte)'9', (byte)'A', (byte)'B', + (byte)'C', (byte)'D', (byte)'E', (byte)'F' + }; + + // Load from data section memory into Vector128 registers + var shuffleMask = Unsafe.ReadUnaligned>(ref MemoryMarshal.GetReference(shuffleMaskData)); + var asciiUpperCase = Unsafe.ReadUnaligned>(ref MemoryMarshal.GetReference(asciiUpperCaseData)); + + var lowNibbles = Ssse3.Shuffle(Vector128.CreateScalarUnsafe(tupleNumber).AsByte(), shuffleMask); + var highNibbles = Sse2.ShiftRightLogical(Sse2.ShiftRightLogical128BitLane(lowNibbles, 2).AsInt32(), 4).AsByte(); + var indices = Sse2.And(Sse2.Or(lowNibbles, highNibbles), Vector128.Create((byte)0xF)); + // Lookup the hex values at the positions of the indices + var hex = Ssse3.Shuffle(asciiUpperCase, indices); + // The high bytes (0x00) of the chars have also been converted to ascii hex '0', so clear them out. + hex = Sse2.And(hex, Vector128.Create((ushort)0xFF).AsByte()); + + // This generates much more efficient asm than fixing the buffer and using + // Sse2.Store((byte*)(p + i), chars.AsByte()); + Unsafe.WriteUnaligned( + ref Unsafe.As( + ref Unsafe.Add(ref MemoryMarshal.GetReference(buffer), i)), + hex); + } + else + { + var number = (int)tupleNumber; + // Slice the buffer so we can use constant offsets in a backwards order + // and the highest index [7] will eliminate the bounds checks for all the lower indicies. + buffer = buffer.Slice(i); + + // This must be explicity typed as ReadOnlySpan + // This then becomes a non-allocating mapping to the data section of the assembly. + // If it is a var, Span or byte[], it allocates the byte array per call. + ReadOnlySpan hexEncodeMap = new byte[] { (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', (byte)'6', (byte)'7', (byte)'8', (byte)'9', (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F' }; + // Note: this only works with byte due to endian ambiguity for other types, + // hence the later (char) casts + + buffer[7] = (char)hexEncodeMap[number & 0xF]; + buffer[6] = (char)hexEncodeMap[(number >> 4) & 0xF]; + buffer[5] = (char)hexEncodeMap[(number >> 8) & 0xF]; + buffer[4] = (char)hexEncodeMap[(number >> 12) & 0xF]; + buffer[3] = (char)hexEncodeMap[(number >> 16) & 0xF]; + buffer[2] = (char)hexEncodeMap[(number >> 20) & 0xF]; + buffer[1] = (char)hexEncodeMap[(number >> 24) & 0xF]; + buffer[0] = (char)hexEncodeMap[(number >> 28) & 0xF]; + } } [MethodImpl(MethodImplOptions.AggressiveInlining)] // Needs a push @@ -525,6 +694,24 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure return Vector.GreaterThanAll(check, Vector.Zero); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool CheckBytesInAsciiRange(Vector256 check, Vector256 zero) + { + Debug.Assert(Avx2.IsSupported); + + var mask = Avx2.CompareGreaterThan(check, zero); + return (uint)Avx2.MoveMask(mask) == 0xFFFF_FFFF; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool CheckBytesInAsciiRange(Vector128 check, Vector128 zero) + { + Debug.Assert(Sse2.IsSupported); + + var mask = Sse2.CompareGreaterThan(check, zero); + return Sse2.MoveMask(mask) == 0xFFFF; + } + // Validate: bytes != 0 && bytes <= 127 // Subtract 1 from all bytes to move 0 to high bits // bitwise or with self to catch all > 127 bytes @@ -537,12 +724,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure return (((check - 0x0101010101010101L) | check) & HighBits) == 0; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static bool CheckBytesInAsciiRange(int check) { const int HighBits = unchecked((int)0x80808080); return (((check - 0x01010101) | check) & HighBits) == 0; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static bool CheckBytesInAsciiRange(short check) { const short HighBits = unchecked((short)0x8080); diff --git a/src/Shared/Shared.sln b/src/Shared/Shared.sln new file mode 100644 index 0000000000..b627de76e6 --- /dev/null +++ b/src/Shared/Shared.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 16 +VisualStudioVersion = 16.0.0.0 +MinimumVisualStudioVersion = 16.0.0.0 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Shared.Tests", "test\Shared.Tests\Microsoft.AspNetCore.Shared.Tests.csproj", "{06CD38EF-7733-4284-B3E4-825B6B63E1DD}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {06CD38EF-7733-4284-B3E4-825B6B63E1DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {06CD38EF-7733-4284-B3E4-825B6B63E1DD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {06CD38EF-7733-4284-B3E4-825B6B63E1DD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {06CD38EF-7733-4284-B3E4-825B6B63E1DD}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {9B7E5B1E-6E6D-4185-9088-2C7C779C6AB2} + EndGlobalSection +EndGlobal diff --git a/src/Shared/StackTrace/ExceptionDetails/ExceptionDetailsProvider.cs b/src/Shared/StackTrace/ExceptionDetails/ExceptionDetailsProvider.cs index 2d1dd20710..340fd68743 100644 --- a/src/Shared/StackTrace/ExceptionDetails/ExceptionDetailsProvider.cs +++ b/src/Shared/StackTrace/ExceptionDetails/ExceptionDetailsProvider.cs @@ -7,17 +7,20 @@ using System.IO; using System.Linq; using System.Reflection; using Microsoft.Extensions.FileProviders; +using Microsoft.Extensions.Logging; namespace Microsoft.Extensions.StackTrace.Sources { internal class ExceptionDetailsProvider { private readonly IFileProvider _fileProvider; + private readonly ILogger _logger; private readonly int _sourceCodeLineCount; - public ExceptionDetailsProvider(IFileProvider fileProvider, int sourceCodeLineCount) + public ExceptionDetailsProvider(IFileProvider fileProvider, ILogger logger, int sourceCodeLineCount) { _fileProvider = fileProvider; + _logger = logger; _sourceCodeLineCount = sourceCodeLineCount; } @@ -30,15 +33,27 @@ namespace Microsoft.Extensions.StackTrace.Sources yield return new ExceptionDetails { Error = ex, - StackFrames = StackTraceHelper.GetFrames(ex) - .Select(frame => GetStackFrameSourceCodeInfo( - frame.MethodDisplayInfo.ToString(), - frame.FilePath, - frame.LineNumber)) + StackFrames = GetStackFrames(ex), }; } } + private IEnumerable GetStackFrames(Exception original) + { + var stackFrames = StackTraceHelper.GetFrames(original, out var exception) + .Select(frame => GetStackFrameSourceCodeInfo( + frame.MethodDisplayInfo.ToString(), + frame.FilePath, + frame.LineNumber)); + + if (exception != null) + { + _logger?.FailedToReadStackTraceInfo(exception); + } + + return stackFrames; + } + private static IEnumerable FlattenAndReverseExceptionTree(Exception ex) { // ReflectionTypeLoadException is special because the details are in diff --git a/src/Shared/StackTrace/ExceptionDetails/LoggerExtensions.cs b/src/Shared/StackTrace/ExceptionDetails/LoggerExtensions.cs new file mode 100644 index 0000000000..17ef3a944c --- /dev/null +++ b/src/Shared/StackTrace/ExceptionDetails/LoggerExtensions.cs @@ -0,0 +1,26 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.Extensions.Logging; + +namespace Microsoft.Extensions.StackTrace.Sources +{ + internal static class LoggerExtensions + { + private static readonly Action _failedToReadStackFrameInfo; + + static LoggerExtensions() + { + _failedToReadStackFrameInfo = LoggerMessage.Define( + logLevel: LogLevel.Debug, + eventId: new EventId(0, "FailedToReadStackTraceInfo"), + formatString: "Failed to read stack trace information for exception."); + } + + public static void FailedToReadStackTraceInfo(this ILogger logger, Exception exception) + { + _failedToReadStackFrameInfo(logger, exception); + } + } +} diff --git a/src/Shared/StackTrace/StackFrame/MethodDisplayInfo.cs b/src/Shared/StackTrace/StackFrame/MethodDisplayInfo.cs index b1c0ccc188..8079f96072 100644 --- a/src/Shared/StackTrace/StackFrame/MethodDisplayInfo.cs +++ b/src/Shared/StackTrace/StackFrame/MethodDisplayInfo.cs @@ -33,7 +33,7 @@ namespace Microsoft.Extensions.StackTrace.Sources builder.Append(GenericArguments); builder.Append("("); - builder.Append(string.Join(", ", Parameters.Select(p => p.ToString()))); + builder.AppendJoin(", ", Parameters.Select(p => p.ToString())); builder.Append(")"); if (!string.IsNullOrEmpty(SubMethod)) diff --git a/src/Shared/StackTrace/StackFrame/PortablePdbReader.cs b/src/Shared/StackTrace/StackFrame/PortablePdbReader.cs deleted file mode 100644 index ff6a4947f8..0000000000 --- a/src/Shared/StackTrace/StackFrame/PortablePdbReader.cs +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Reflection; -using System.Reflection.Metadata; -using System.Reflection.Metadata.Ecma335; -using System.Reflection.PortableExecutable; - -namespace Microsoft.Extensions.StackTrace.Sources -{ - internal class PortablePdbReader : IDisposable - { - private readonly Dictionary _cache = - new Dictionary(StringComparer.Ordinal); - - public void PopulateStackFrame(StackFrameInfo frameInfo, MethodBase method, int IlOffset) - { - if (method.Module.Assembly.IsDynamic) - { - return; - } - - var metadataReader = GetMetadataReader(method.Module.Assembly.Location); - - if (metadataReader == null) - { - return; - } - - var methodToken = MetadataTokens.Handle(method.MetadataToken); - - Debug.Assert(methodToken.Kind == HandleKind.MethodDefinition); - - var handle = ((MethodDefinitionHandle)methodToken).ToDebugInformationHandle(); - - if (!handle.IsNil) - { - var methodDebugInfo = metadataReader.GetMethodDebugInformation(handle); - var sequencePoints = methodDebugInfo.GetSequencePoints(); - SequencePoint? bestPointSoFar = null; - - foreach (var point in sequencePoints) - { - if (point.Offset > IlOffset) - { - break; - } - - if (point.StartLine != SequencePoint.HiddenLine) - { - bestPointSoFar = point; - } - } - - if (bestPointSoFar.HasValue) - { - frameInfo.LineNumber = bestPointSoFar.Value.StartLine; - frameInfo.FilePath = metadataReader.GetString(metadataReader.GetDocument(bestPointSoFar.Value.Document).Name); - } - } - } - - private MetadataReader GetMetadataReader(string assemblyPath) - { - MetadataReaderProvider provider = null; - if (!_cache.TryGetValue(assemblyPath, out provider)) - { - var pdbPath = GetPdbPath(assemblyPath); - - if (!string.IsNullOrEmpty(pdbPath) && File.Exists(pdbPath) && IsPortable(pdbPath)) - { - var pdbStream = File.OpenRead(pdbPath); - provider = MetadataReaderProvider.FromPortablePdbStream(pdbStream); - } - - _cache[assemblyPath] = provider; - } - - return provider?.GetMetadataReader(); - } - - private static string GetPdbPath(string assemblyPath) - { - if (string.IsNullOrEmpty(assemblyPath)) - { - return null; - } - - if (File.Exists(assemblyPath)) - { - var peStream = File.OpenRead(assemblyPath); - - using (var peReader = new PEReader(peStream)) - { - foreach (var entry in peReader.ReadDebugDirectory()) - { - if (entry.Type == DebugDirectoryEntryType.CodeView) - { - var codeViewData = peReader.ReadCodeViewDebugDirectoryData(entry); - var peDirectory = Path.GetDirectoryName(assemblyPath); - return Path.Combine(peDirectory, Path.GetFileName(codeViewData.Path)); - } - } - } - } - - return null; - } - - private static bool IsPortable(string pdbPath) - { - using (var pdbStream = File.OpenRead(pdbPath)) - { - return pdbStream.ReadByte() == 'B' && - pdbStream.ReadByte() == 'S' && - pdbStream.ReadByte() == 'J' && - pdbStream.ReadByte() == 'B'; - } - } - - public void Dispose() - { - foreach (var entry in _cache) - { - entry.Value?.Dispose(); - } - - _cache.Clear(); - } - } -} diff --git a/src/Shared/StackTrace/StackFrame/StackTraceHelper.cs b/src/Shared/StackTrace/StackFrame/StackTraceHelper.cs index 1074097aa9..48f72438eb 100644 --- a/src/Shared/StackTrace/StackFrame/StackTraceHelper.cs +++ b/src/Shared/StackTrace/StackFrame/StackTraceHelper.cs @@ -15,57 +15,58 @@ namespace Microsoft.Extensions.StackTrace.Sources { internal class StackTraceHelper { - public static IList GetFrames(Exception exception) + public static IList GetFrames(Exception exception, out AggregateException error) { var frames = new List(); if (exception == null) { + error = default; return frames; } - using (var portablePdbReader = new PortablePdbReader()) + var needFileInfo = true; + var stackTrace = new System.Diagnostics.StackTrace(exception, needFileInfo); + var stackFrames = stackTrace.GetFrames(); + + if (stackFrames == null) { - var needFileInfo = true; - var stackTrace = new System.Diagnostics.StackTrace(exception, needFileInfo); - var stackFrames = stackTrace.GetFrames(); - - if (stackFrames == null) - { - return frames; - } - - for (var i = 0; i < stackFrames.Length; i++) - { - var frame = stackFrames[i]; - var method = frame.GetMethod(); - - // Always show last stackFrame - if (!ShowInStackTrace(method) && i < stackFrames.Length - 1) - { - continue; - } - - var stackFrame = new StackFrameInfo - { - StackFrame = frame, - FilePath = frame.GetFileName(), - LineNumber = frame.GetFileLineNumber(), - MethodDisplayInfo = GetMethodDisplayString(frame.GetMethod()), - }; - - if (string.IsNullOrEmpty(stackFrame.FilePath)) - { - // .NET Framework and older versions of mono don't support portable PDBs - // so we read it manually to get file name and line information - portablePdbReader.PopulateStackFrame(stackFrame, method, frame.GetILOffset()); - } - - frames.Add(stackFrame); - } - + error = default; return frames; } + + List exceptions = null; + + for (var i = 0; i < stackFrames.Length; i++) + { + var frame = stackFrames[i]; + var method = frame.GetMethod(); + + // Always show last stackFrame + if (!ShowInStackTrace(method) && i < stackFrames.Length - 1) + { + continue; + } + + var stackFrame = new StackFrameInfo + { + StackFrame = frame, + FilePath = frame.GetFileName(), + LineNumber = frame.GetFileLineNumber(), + MethodDisplayInfo = GetMethodDisplayString(frame.GetMethod()), + }; + + frames.Add(stackFrame); + } + + if (exceptions != null) + { + error = new AggregateException(exceptions); + return frames; + } + + error = default; + return frames; } internal static MethodDisplayInfo GetMethodDisplayString(MethodBase method) diff --git a/src/Shared/TaskToApm.cs b/src/Shared/TaskToApm.cs new file mode 100644 index 0000000000..3a05eb6b42 --- /dev/null +++ b/src/Shared/TaskToApm.cs @@ -0,0 +1,121 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// Helper methods for using Tasks to implement the APM pattern. +// +// Example usage, wrapping a Task-returning FooAsync method with Begin/EndFoo methods: +// +// public IAsyncResult BeginFoo(..., AsyncCallback callback, object state) => +// TaskToApm.Begin(FooAsync(...), callback, state); +// +// public int EndFoo(IAsyncResult asyncResult) => +// TaskToApm.End(asyncResult); + +#nullable enable +using System.Diagnostics; + +namespace System.Threading.Tasks +{ + /// + /// Provides support for efficiently using Tasks to implement the APM (Begin/End) pattern. + /// + internal static class TaskToApm + { + /// + /// Marshals the Task as an IAsyncResult, using the supplied callback and state + /// to implement the APM pattern. + /// + /// The Task to be marshaled. + /// The callback to be invoked upon completion. + /// The state to be stored in the IAsyncResult. + /// An IAsyncResult to represent the task's asynchronous operation. + public static IAsyncResult Begin(Task task, AsyncCallback? callback, object? state) => + new TaskAsyncResult(task, state, callback); + + /// Processes an IAsyncResult returned by Begin. + /// The IAsyncResult to unwrap. + public static void End(IAsyncResult asyncResult) + { + if (asyncResult is TaskAsyncResult twar) + { + twar._task.GetAwaiter().GetResult(); + return; + } + + throw new ArgumentNullException(); + } + + /// Processes an IAsyncResult returned by Begin. + /// The IAsyncResult to unwrap. + public static TResult End(IAsyncResult asyncResult) + { + if (asyncResult is TaskAsyncResult twar && twar._task is Task task) + { + return task.GetAwaiter().GetResult(); + } + + throw new ArgumentNullException(); + } + + /// Provides a simple IAsyncResult that wraps a Task. + /// + /// We could use the Task as the IAsyncResult if the Task's AsyncState is the same as the object state, + /// but that's very rare, in particular in a situation where someone cares about allocation, and always + /// using TaskAsyncResult simplifies things and enables additional optimizations. + /// + internal sealed class TaskAsyncResult : IAsyncResult + { + /// The wrapped Task. + internal readonly Task _task; + /// Callback to invoke when the wrapped task completes. + private readonly AsyncCallback? _callback; + + /// Initializes the IAsyncResult with the Task to wrap and the associated object state. + /// The Task to wrap. + /// The new AsyncState value. + /// Callback to invoke when the wrapped task completes. + internal TaskAsyncResult(Task task, object? state, AsyncCallback? callback) + { + Debug.Assert(task != null); + _task = task; + AsyncState = state; + + if (task.IsCompleted) + { + // Synchronous completion. Invoke the callback. No need to store it. + CompletedSynchronously = true; + callback?.Invoke(this); + } + else if (callback != null) + { + // Asynchronous completion, and we have a callback; schedule it. We use OnCompleted rather than ContinueWith in + // order to avoid running synchronously if the task has already completed by the time we get here but still run + // synchronously as part of the task's completion if the task completes after (the more common case). + _callback = callback; + _task.ConfigureAwait(continueOnCapturedContext: false) + .GetAwaiter() + .OnCompleted(InvokeCallback); // allocates a delegate, but avoids a closure + } + } + + /// Invokes the callback. + private void InvokeCallback() + { + Debug.Assert(!CompletedSynchronously); + Debug.Assert(_callback != null); + _callback.Invoke(this); + } + + /// Gets a user-defined object that qualifies or contains information about an asynchronous operation. + public object? AsyncState { get; } + /// Gets a value that indicates whether the asynchronous operation completed synchronously. + /// This is set lazily based on whether the has completed by the time this object is created. + public bool CompletedSynchronously { get; } + /// Gets a value that indicates whether the asynchronous operation has completed. + public bool IsCompleted => _task.IsCompleted; + /// Gets a that is used to wait for an asynchronous operation to complete. + public WaitHandle AsyncWaitHandle => ((IAsyncResult)_task).AsyncWaitHandle; + } + } +} \ No newline at end of file diff --git a/src/Shared/TypeNameHelper/TypeNameHelper.cs b/src/Shared/TypeNameHelper/TypeNameHelper.cs new file mode 100644 index 0000000000..d08b9b0439 --- /dev/null +++ b/src/Shared/TypeNameHelper/TypeNameHelper.cs @@ -0,0 +1,179 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Text; +using System.Collections.Generic; + +namespace Microsoft.Extensions.Internal +{ + internal static class TypeNameHelper + { + private const char DefaultNestedTypeDelimiter = '+'; + + private static readonly Dictionary _builtInTypeNames = new Dictionary + { + { typeof(void), "void" }, + { typeof(bool), "bool" }, + { typeof(byte), "byte" }, + { typeof(char), "char" }, + { typeof(decimal), "decimal" }, + { typeof(double), "double" }, + { typeof(float), "float" }, + { typeof(int), "int" }, + { typeof(long), "long" }, + { typeof(object), "object" }, + { typeof(sbyte), "sbyte" }, + { typeof(short), "short" }, + { typeof(string), "string" }, + { typeof(uint), "uint" }, + { typeof(ulong), "ulong" }, + { typeof(ushort), "ushort" } + }; + + public static string GetTypeDisplayName(object item, bool fullName = true) + { + return item == null ? null : GetTypeDisplayName(item.GetType(), fullName); + } + + /// + /// Pretty print a type name. + /// + /// The . + /// true to print a fully qualified name. + /// true to include generic parameter names. + /// true to include generic parameters. + /// Character to use as a delimiter in nested type names + /// The pretty printed type name. + public static string GetTypeDisplayName(Type type, bool fullName = true, bool includeGenericParameterNames = false, bool includeGenericParameters = true, char nestedTypeDelimiter = DefaultNestedTypeDelimiter) + { + var builder = new StringBuilder(); + ProcessType(builder, type, new DisplayNameOptions(fullName, includeGenericParameterNames, includeGenericParameters, nestedTypeDelimiter)); + return builder.ToString(); + } + + private static void ProcessType(StringBuilder builder, Type type, in DisplayNameOptions options) + { + if (type.IsGenericType) + { + var genericArguments = type.GetGenericArguments(); + ProcessGenericType(builder, type, genericArguments, genericArguments.Length, options); + } + else if (type.IsArray) + { + ProcessArrayType(builder, type, options); + } + else if (_builtInTypeNames.TryGetValue(type, out var builtInName)) + { + builder.Append(builtInName); + } + else if (type.IsGenericParameter) + { + if (options.IncludeGenericParameterNames) + { + builder.Append(type.Name); + } + } + else + { + var name = options.FullName ? type.FullName : type.Name; + builder.Append(name); + + if (options.NestedTypeDelimiter != DefaultNestedTypeDelimiter) + { + builder.Replace(DefaultNestedTypeDelimiter, options.NestedTypeDelimiter, builder.Length - name.Length, name.Length); + } + } + } + + private static void ProcessArrayType(StringBuilder builder, Type type, in DisplayNameOptions options) + { + var innerType = type; + while (innerType.IsArray) + { + innerType = innerType.GetElementType(); + } + + ProcessType(builder, innerType, options); + + while (type.IsArray) + { + builder.Append('['); + builder.Append(',', type.GetArrayRank() - 1); + builder.Append(']'); + type = type.GetElementType(); + } + } + + private static void ProcessGenericType(StringBuilder builder, Type type, Type[] genericArguments, int length, in DisplayNameOptions options) + { + var offset = 0; + if (type.IsNested) + { + offset = type.DeclaringType.GetGenericArguments().Length; + } + + if (options.FullName) + { + if (type.IsNested) + { + ProcessGenericType(builder, type.DeclaringType, genericArguments, offset, options); + builder.Append(options.NestedTypeDelimiter); + } + else if (!string.IsNullOrEmpty(type.Namespace)) + { + builder.Append(type.Namespace); + builder.Append('.'); + } + } + + var genericPartIndex = type.Name.IndexOf('`'); + if (genericPartIndex <= 0) + { + builder.Append(type.Name); + return; + } + + builder.Append(type.Name, 0, genericPartIndex); + + if (options.IncludeGenericParameters) + { + builder.Append('<'); + for (var i = offset; i < length; i++) + { + ProcessType(builder, genericArguments[i], options); + if (i + 1 == length) + { + continue; + } + + builder.Append(','); + if (options.IncludeGenericParameterNames || !genericArguments[i + 1].IsGenericParameter) + { + builder.Append(' '); + } + } + builder.Append('>'); + } + } + + private readonly struct DisplayNameOptions + { + public DisplayNameOptions(bool fullName, bool includeGenericParameterNames, bool includeGenericParameters, char nestedTypeDelimiter) + { + FullName = fullName; + IncludeGenericParameters = includeGenericParameters; + IncludeGenericParameterNames = includeGenericParameterNames; + NestedTypeDelimiter = nestedTypeDelimiter; + } + + public bool FullName { get; } + + public bool IncludeGenericParameters { get; } + + public bool IncludeGenericParameterNames { get; } + + public char NestedTypeDelimiter { get; } + } + } +} diff --git a/src/Shared/UrlDecoder/UrlDecoder.cs b/src/Shared/UrlDecoder/UrlDecoder.cs index 5666f5b34f..a5e4fbf045 100644 --- a/src/Shared/UrlDecoder/UrlDecoder.cs +++ b/src/Shared/UrlDecoder/UrlDecoder.cs @@ -20,7 +20,7 @@ namespace Microsoft.AspNetCore.Internal if (destination.Length < source.Length) { throw new ArgumentException( - "Lenghth of the destination byte span is less then the source.", + "Length of the destination byte span is less then the source.", nameof(destination)); } @@ -294,7 +294,7 @@ namespace Microsoft.AspNetCore.Internal /// Read the next char and convert it into hexadecimal value. /// /// The index will be moved to the next - /// byte no matter no matter whether the operation successes. + /// byte no matter whether the operation successes. /// /// The index of the byte in the buffer to read /// The byte span from which the hex to be read diff --git a/src/Shared/ValueStopwatch/ValueStopwatch.cs b/src/Shared/ValueStopwatch/ValueStopwatch.cs new file mode 100644 index 0000000000..f99a084aeb --- /dev/null +++ b/src/Shared/ValueStopwatch/ValueStopwatch.cs @@ -0,0 +1,39 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Diagnostics; + +namespace Microsoft.Extensions.Internal +{ + internal struct ValueStopwatch + { + private static readonly double TimestampToTicks = TimeSpan.TicksPerSecond / (double)Stopwatch.Frequency; + + private long _startTimestamp; + + public bool IsActive => _startTimestamp != 0; + + private ValueStopwatch(long startTimestamp) + { + _startTimestamp = startTimestamp; + } + + public static ValueStopwatch StartNew() => new ValueStopwatch(Stopwatch.GetTimestamp()); + + public TimeSpan GetElapsedTime() + { + // Start timestamp can't be zero in an initialized ValueStopwatch. It would have to be literally the first thing executed when the machine boots to be 0. + // So it being 0 is a clear indication of default(ValueStopwatch) + if (!IsActive) + { + throw new InvalidOperationException("An uninitialized, or 'default', ValueStopwatch cannot be used to get elapsed time."); + } + + var end = Stopwatch.GetTimestamp(); + var timestampDelta = end - _startTimestamp; + var ticks = (long)(TimestampToTicks * timestampDelta); + return new TimeSpan(ticks); + } + } +} diff --git a/src/Shared/runtime/CopyToAspNetCore.cmd b/src/Shared/runtime/CopyToAspNetCore.cmd new file mode 100644 index 0000000000..65a341b3cc --- /dev/null +++ b/src/Shared/runtime/CopyToAspNetCore.cmd @@ -0,0 +1,15 @@ +@ECHO OFF +SETLOCAL + +if not [%1] == [] (set remote_repo=%1) else (set remote_repo=%ASPNETCORE_REPO%) + +IF [%remote_repo%] == [] ( + echo The 'ASPNETCORE_REPO' environment variable or command line parameter is not set, aborting. + exit /b 1 +) + +echo ASPNETCORE_REPO: %remote_repo% + +REM https://superuser.com/questions/280425/getting-robocopy-to-return-a-proper-exit-code +(robocopy . %remote_repo%\src\Shared\runtime /MIR) ^& IF %ERRORLEVEL% LSS 8 SET ERRORLEVEL = 0 +(robocopy .\..\..\..\..\..\tests\Tests\System\Net\aspnetcore\ %remote_repo%\src\Shared\test\Shared.Tests\runtime /MIR) ^& IF %ERRORLEVEL% LSS 8 SET ERRORLEVEL = 0 diff --git a/src/Shared/runtime/CopyToAspNetCore.sh b/src/Shared/runtime/CopyToAspNetCore.sh new file mode 100755 index 0000000000..28d1cb9083 --- /dev/null +++ b/src/Shared/runtime/CopyToAspNetCore.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +if [[ -n "$1" ]]; then + remote_repo="$1" +else + remote_repo="$ASPNETCORE_REPO" +fi + +if [[ -z "$remote_repo" ]]; then + echo The 'ASPNETCORE_REPO' environment variable or command line parameter is not set, aborting. + exit 1 +fi + +cd "$(dirname "$0")" || exit 1 + +echo "ASPNETCORE_REPO: $remote_repo" + +rsync -av --delete ./ "$remote_repo"/src/Shared/runtime +rsync -av --delete ./../../../../../tests/Tests/System/Net/aspnetcore/ "$remote_repo"/src/Shared/test/Shared.Tests/runtime diff --git a/src/Shared/runtime/CopyToRuntime.cmd b/src/Shared/runtime/CopyToRuntime.cmd new file mode 100644 index 0000000000..f1cb32df85 --- /dev/null +++ b/src/Shared/runtime/CopyToRuntime.cmd @@ -0,0 +1,15 @@ +@ECHO OFF +SETLOCAL + +if not [%1] == [] (set remote_repo=%1) else (set remote_repo=%RUNTIME_REPO%) + +IF [%remote_repo%] == [] ( + echo The 'RUNTIME_REPO' environment variable or command line parameter is not set, aborting. + exit /b 1 +) + +echo RUNTIME_REPO: %remote_repo% + +REM https://superuser.com/questions/280425/getting-robocopy-to-return-a-proper-exit-code +(robocopy . %remote_repo%\src\libraries\Common\src\System\Net\Http\aspnetcore /MIR) ^& IF %ERRORLEVEL% LSS 8 SET ERRORLEVEL = 0 +(robocopy .\..\test\Shared.Tests\runtime %remote_repo%\src\libraries\Common\tests\Tests\System\Net\aspnetcore /MIR) ^& IF %ERRORLEVEL% LSS 8 SET ERRORLEVEL = 0 diff --git a/src/Shared/runtime/CopyToRuntime.sh b/src/Shared/runtime/CopyToRuntime.sh new file mode 100755 index 0000000000..d0f44f411e --- /dev/null +++ b/src/Shared/runtime/CopyToRuntime.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +if [[ -n "$1" ]]; then + remote_repo="$1" +else + remote_repo="$RUNTIME_REPO" +fi + +if [[ -z "$remote_repo" ]]; then + echo The 'RUNTIME_REPO' environment variable or command line parameter is not set, aborting. + exit 1 +fi + +cd "$(dirname "$0")" || exit 1 + +echo "RUNTIME_REPO: $remote_repo" + +rsync -av --delete ./ "$remote_repo"/src/libraries/Common/src/System/Net/Http/aspnetcore +rsync -av --delete ./../test/Shared.Tests/runtime/ "$remote_repo"/src/libraries/Common/tests/Tests/System/Net/aspnetcore diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/HPack/DynamicTable.cs b/src/Shared/runtime/Http2/Hpack/DynamicTable.cs similarity index 66% rename from src/Servers/Kestrel/Core/src/Internal/Http2/HPack/DynamicTable.cs rename to src/Shared/runtime/Http2/Hpack/DynamicTable.cs index 7183589021..5a8fdf170f 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/HPack/DynamicTable.cs +++ b/src/Shared/runtime/Http2/Hpack/DynamicTable.cs @@ -1,12 +1,9 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. -using System; - -namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack +namespace System.Net.Http.HPack { - // The dynamic table is defined as a queue where items are inserted at the front and removed from the back. - // It's implemented as a circular buffer that appends to the end and trims from the front. Thus index are reversed. internal class DynamicTable { private HeaderField[] _buffer; @@ -37,19 +34,21 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack throw new IndexOutOfRangeException(); } - var modIndex = _insertIndex - index - 1; - if (modIndex < 0) + index = _insertIndex - index - 1; + + if (index < 0) { - modIndex += _buffer.Length; + // _buffer is circular; wrap the index back around. + index += _buffer.Length; } - return _buffer[modIndex]; + return _buffer[index]; } } - public void Insert(Span name, Span value) + public void Insert(ReadOnlySpan name, ReadOnlySpan value) { - var entryLength = HeaderField.GetLength(name.Length, value.Length); + int entryLength = HeaderField.GetLength(name.Length, value.Length); EnsureAvailable(entryLength); if (entryLength > _maxSize) @@ -74,12 +73,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack { var newBuffer = new HeaderField[maxSize / HeaderField.RfcOverhead]; - for (var i = 0; i < Count; i++) - { - newBuffer[i] = _buffer[i]; - } + int headCount = Math.Min(_buffer.Length - _removeIndex, _count); + int tailCount = _count - headCount; + + Array.Copy(_buffer, _removeIndex, newBuffer, 0, headCount); + Array.Copy(_buffer, 0, newBuffer, headCount, tailCount); _buffer = newBuffer; + _removeIndex = 0; + _insertIndex = _count; _maxSize = maxSize; } else @@ -93,7 +95,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack { while (_count > 0 && _maxSize - _size < available) { - _size -= _buffer[_removeIndex].Length; + ref HeaderField field = ref _buffer[_removeIndex]; + _size -= field.Length; + field = default; + _count--; _removeIndex = (_removeIndex + 1) % _buffer.Length; } diff --git a/src/Shared/runtime/Http2/Hpack/H2StaticTable.cs b/src/Shared/runtime/Http2/Hpack/H2StaticTable.cs new file mode 100644 index 0000000000..7f3b775582 --- /dev/null +++ b/src/Shared/runtime/Http2/Hpack/H2StaticTable.cs @@ -0,0 +1,160 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Text; + +namespace System.Net.Http.HPack +{ + internal static class H2StaticTable + { + // Index of status code into s_staticDecoderTable + private static readonly Dictionary s_statusIndex = new Dictionary + { + [200] = 8, + [204] = 9, + [206] = 10, + [304] = 11, + [400] = 12, + [404] = 13, + [500] = 14, + }; + + public static int Count => s_staticDecoderTable.Length; + + public static HeaderField Get(int index) => s_staticDecoderTable[index]; + + public static IReadOnlyDictionary StatusIndex => s_statusIndex; + + private static readonly HeaderField[] s_staticDecoderTable = new HeaderField[] + { + CreateHeaderField(":authority", ""), + CreateHeaderField(":method", "GET"), + CreateHeaderField(":method", "POST"), + CreateHeaderField(":path", "/"), + CreateHeaderField(":path", "/index.html"), + CreateHeaderField(":scheme", "http"), + CreateHeaderField(":scheme", "https"), + CreateHeaderField(":status", "200"), + CreateHeaderField(":status", "204"), + CreateHeaderField(":status", "206"), + CreateHeaderField(":status", "304"), + CreateHeaderField(":status", "400"), + CreateHeaderField(":status", "404"), + CreateHeaderField(":status", "500"), + CreateHeaderField("accept-charset", ""), + CreateHeaderField("accept-encoding", "gzip, deflate"), + CreateHeaderField("accept-language", ""), + CreateHeaderField("accept-ranges", ""), + CreateHeaderField("accept", ""), + CreateHeaderField("access-control-allow-origin", ""), + CreateHeaderField("age", ""), + CreateHeaderField("allow", ""), + CreateHeaderField("authorization", ""), + CreateHeaderField("cache-control", ""), + CreateHeaderField("content-disposition", ""), + CreateHeaderField("content-encoding", ""), + CreateHeaderField("content-language", ""), + CreateHeaderField("content-length", ""), + CreateHeaderField("content-location", ""), + CreateHeaderField("content-range", ""), + CreateHeaderField("content-type", ""), + CreateHeaderField("cookie", ""), + CreateHeaderField("date", ""), + CreateHeaderField("etag", ""), + CreateHeaderField("expect", ""), + CreateHeaderField("expires", ""), + CreateHeaderField("from", ""), + CreateHeaderField("host", ""), + CreateHeaderField("if-match", ""), + CreateHeaderField("if-modified-since", ""), + CreateHeaderField("if-none-match", ""), + CreateHeaderField("if-range", ""), + CreateHeaderField("if-unmodified-since", ""), + CreateHeaderField("last-modified", ""), + CreateHeaderField("link", ""), + CreateHeaderField("location", ""), + CreateHeaderField("max-forwards", ""), + CreateHeaderField("proxy-authenticate", ""), + CreateHeaderField("proxy-authorization", ""), + CreateHeaderField("range", ""), + CreateHeaderField("referer", ""), + CreateHeaderField("refresh", ""), + CreateHeaderField("retry-after", ""), + CreateHeaderField("server", ""), + CreateHeaderField("set-cookie", ""), + CreateHeaderField("strict-transport-security", ""), + CreateHeaderField("transfer-encoding", ""), + CreateHeaderField("user-agent", ""), + CreateHeaderField("vary", ""), + CreateHeaderField("via", ""), + CreateHeaderField("www-authenticate", "") + }; + + // TODO: The HeaderField constructor will allocate and copy again. We should avoid this. + // Tackle as part of header table allocation strategy in general (see note in HeaderField constructor). + + private static HeaderField CreateHeaderField(string name, string value) => + new HeaderField( + Encoding.ASCII.GetBytes(name), + value.Length != 0 ? Encoding.ASCII.GetBytes(value) : Array.Empty()); + + // Values for encoding. + // Unused values are omitted. + public const int Authority = 1; + public const int MethodGet = 2; + public const int MethodPost = 3; + public const int PathSlash = 4; + public const int SchemeHttp = 6; + public const int SchemeHttps = 7; + public const int Status200 = 8; + public const int AcceptCharset = 15; + public const int AcceptEncoding = 16; + public const int AcceptLanguage = 17; + public const int AcceptRanges = 18; + public const int Accept = 19; + public const int AccessControlAllowOrigin = 20; + public const int Age = 21; + public const int Allow = 22; + public const int Authorization = 23; + public const int CacheControl = 24; + public const int ContentDisposition = 25; + public const int ContentEncoding = 26; + public const int ContentLanguage = 27; + public const int ContentLength = 28; + public const int ContentLocation = 29; + public const int ContentRange = 30; + public const int ContentType = 31; + public const int Cookie = 32; + public const int Date = 33; + public const int ETag = 34; + public const int Expect = 35; + public const int Expires = 36; + public const int From = 37; + public const int Host = 38; + public const int IfMatch = 39; + public const int IfModifiedSince = 40; + public const int IfNoneMatch = 41; + public const int IfRange = 42; + public const int IfUnmodifiedSince = 43; + public const int LastModified = 44; + public const int Link = 45; + public const int Location = 46; + public const int MaxForwards = 47; + public const int ProxyAuthenticate = 48; + public const int ProxyAuthorization = 49; + public const int Range = 50; + public const int Referer = 51; + public const int Refresh = 52; + public const int RetryAfter = 53; + public const int Server = 54; + public const int SetCookie = 55; + public const int StrictTransportSecurity = 56; + public const int TransferEncoding = 57; + public const int UserAgent = 58; + public const int Vary = 59; + public const int Via = 60; + public const int WwwAuthenticate = 61; + } +} diff --git a/src/Shared/runtime/Http2/Hpack/HPackDecoder.cs b/src/Shared/runtime/Http2/Hpack/HPackDecoder.cs new file mode 100644 index 0000000000..3fe3c86243 --- /dev/null +++ b/src/Shared/runtime/Http2/Hpack/HPackDecoder.cs @@ -0,0 +1,495 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable +using System.Buffers; +using System.Diagnostics; +#if KESTREL +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; +#endif + +namespace System.Net.Http.HPack +{ + internal class HPackDecoder + { + private enum State : byte + { + Ready, + HeaderFieldIndex, + HeaderNameIndex, + HeaderNameLength, + HeaderNameLengthContinue, + HeaderName, + HeaderValueLength, + HeaderValueLengthContinue, + HeaderValue, + DynamicTableSizeUpdate + } + + public const int DefaultHeaderTableSize = 4096; + public const int DefaultStringOctetsSize = 4096; + public const int DefaultMaxHeadersLength = 64 * 1024; + + // http://httpwg.org/specs/rfc7541.html#rfc.section.6.1 + // 0 1 2 3 4 5 6 7 + // +---+---+---+---+---+---+---+---+ + // | 1 | Index (7+) | + // +---+---------------------------+ + private const byte IndexedHeaderFieldMask = 0x80; + private const byte IndexedHeaderFieldRepresentation = 0x80; + + // http://httpwg.org/specs/rfc7541.html#rfc.section.6.2.1 + // 0 1 2 3 4 5 6 7 + // +---+---+---+---+---+---+---+---+ + // | 0 | 1 | Index (6+) | + // +---+---+-----------------------+ + private const byte LiteralHeaderFieldWithIncrementalIndexingMask = 0xc0; + private const byte LiteralHeaderFieldWithIncrementalIndexingRepresentation = 0x40; + + // http://httpwg.org/specs/rfc7541.html#rfc.section.6.2.2 + // 0 1 2 3 4 5 6 7 + // +---+---+---+---+---+---+---+---+ + // | 0 | 0 | 0 | 0 | Index (4+) | + // +---+---+-----------------------+ + private const byte LiteralHeaderFieldWithoutIndexingMask = 0xf0; + private const byte LiteralHeaderFieldWithoutIndexingRepresentation = 0x00; + + // http://httpwg.org/specs/rfc7541.html#rfc.section.6.2.3 + // 0 1 2 3 4 5 6 7 + // +---+---+---+---+---+---+---+---+ + // | 0 | 0 | 0 | 1 | Index (4+) | + // +---+---+-----------------------+ + private const byte LiteralHeaderFieldNeverIndexedMask = 0xf0; + private const byte LiteralHeaderFieldNeverIndexedRepresentation = 0x10; + + // http://httpwg.org/specs/rfc7541.html#rfc.section.6.3 + // 0 1 2 3 4 5 6 7 + // +---+---+---+---+---+---+---+---+ + // | 0 | 0 | 1 | Max size (5+) | + // +---+---------------------------+ + private const byte DynamicTableSizeUpdateMask = 0xe0; + private const byte DynamicTableSizeUpdateRepresentation = 0x20; + + // http://httpwg.org/specs/rfc7541.html#rfc.section.5.2 + // 0 1 2 3 4 5 6 7 + // +---+---+---+---+---+---+---+---+ + // | H | String Length (7+) | + // +---+---------------------------+ + private const byte HuffmanMask = 0x80; + + private const int IndexedHeaderFieldPrefix = 7; + private const int LiteralHeaderFieldWithIncrementalIndexingPrefix = 6; + private const int LiteralHeaderFieldWithoutIndexingPrefix = 4; + private const int LiteralHeaderFieldNeverIndexedPrefix = 4; + private const int DynamicTableSizeUpdatePrefix = 5; + private const int StringLengthPrefix = 7; + + private readonly int _maxDynamicTableSize; + private readonly int _maxHeadersLength; + private readonly DynamicTable _dynamicTable; + private readonly IntegerDecoder _integerDecoder = new IntegerDecoder(); + private byte[] _stringOctets; + private byte[] _headerNameOctets; + private byte[] _headerValueOctets; + + private State _state = State.Ready; + private byte[]? _headerName; + private int _stringIndex; + private int _stringLength; + private int _headerNameLength; + private int _headerValueLength; + private bool _index; + private bool _huffman; + private bool _headersObserved; + + public HPackDecoder(int maxDynamicTableSize = DefaultHeaderTableSize, int maxHeadersLength = DefaultMaxHeadersLength) + : this(maxDynamicTableSize, maxHeadersLength, new DynamicTable(maxDynamicTableSize)) + { + } + + // For testing. + internal HPackDecoder(int maxDynamicTableSize, int maxHeadersLength, DynamicTable dynamicTable) + { + _maxDynamicTableSize = maxDynamicTableSize; + _maxHeadersLength = maxHeadersLength; + _dynamicTable = dynamicTable; + + _stringOctets = new byte[DefaultStringOctetsSize]; + _headerNameOctets = new byte[DefaultStringOctetsSize]; + _headerValueOctets = new byte[DefaultStringOctetsSize]; + } + + public void Decode(in ReadOnlySequence data, bool endHeaders, IHttpHeadersHandler handler) + { + foreach (ReadOnlyMemory segment in data) + { + DecodeInternal(segment.Span, endHeaders, handler); + } + + CheckIncompleteHeaderBlock(endHeaders); + } + + public void Decode(ReadOnlySpan data, bool endHeaders, IHttpHeadersHandler? handler) + { + DecodeInternal(data, endHeaders, handler); + CheckIncompleteHeaderBlock(endHeaders); + } + + private void DecodeInternal(ReadOnlySpan data, bool endHeaders, IHttpHeadersHandler? handler) + { + int intResult; + + for (int i = 0; i < data.Length; i++) + { + byte b = data[i]; + switch (_state) + { + case State.Ready: + // TODO: Instead of masking and comparing each prefix value, + // consider doing a 16-way switch on the first four bits (which is the max prefix size). + // Look at this once we have more concrete perf data. + if ((b & IndexedHeaderFieldMask) == IndexedHeaderFieldRepresentation) + { + _headersObserved = true; + + int val = b & ~IndexedHeaderFieldMask; + + if (_integerDecoder.BeginTryDecode((byte)val, IndexedHeaderFieldPrefix, out intResult)) + { + OnIndexedHeaderField(intResult, handler); + } + else + { + _state = State.HeaderFieldIndex; + } + } + else if ((b & LiteralHeaderFieldWithIncrementalIndexingMask) == LiteralHeaderFieldWithIncrementalIndexingRepresentation) + { + _headersObserved = true; + + _index = true; + int val = b & ~LiteralHeaderFieldWithIncrementalIndexingMask; + + if (val == 0) + { + _state = State.HeaderNameLength; + } + else if (_integerDecoder.BeginTryDecode((byte)val, LiteralHeaderFieldWithIncrementalIndexingPrefix, out intResult)) + { + OnIndexedHeaderName(intResult); + } + else + { + _state = State.HeaderNameIndex; + } + } + else if ((b & LiteralHeaderFieldWithoutIndexingMask) == LiteralHeaderFieldWithoutIndexingRepresentation) + { + _headersObserved = true; + + _index = false; + int val = b & ~LiteralHeaderFieldWithoutIndexingMask; + + if (val == 0) + { + _state = State.HeaderNameLength; + } + else if (_integerDecoder.BeginTryDecode((byte)val, LiteralHeaderFieldWithoutIndexingPrefix, out intResult)) + { + OnIndexedHeaderName(intResult); + } + else + { + _state = State.HeaderNameIndex; + } + } + else if ((b & LiteralHeaderFieldNeverIndexedMask) == LiteralHeaderFieldNeverIndexedRepresentation) + { + _headersObserved = true; + + _index = false; + int val = b & ~LiteralHeaderFieldNeverIndexedMask; + + if (val == 0) + { + _state = State.HeaderNameLength; + } + else if (_integerDecoder.BeginTryDecode((byte)val, LiteralHeaderFieldNeverIndexedPrefix, out intResult)) + { + OnIndexedHeaderName(intResult); + } + else + { + _state = State.HeaderNameIndex; + } + } + else if ((b & DynamicTableSizeUpdateMask) == DynamicTableSizeUpdateRepresentation) + { + // https://tools.ietf.org/html/rfc7541#section-4.2 + // This dynamic table size + // update MUST occur at the beginning of the first header block + // following the change to the dynamic table size. + if (_headersObserved) + { + throw new HPackDecodingException(SR.net_http_hpack_late_dynamic_table_size_update); + } + + if (_integerDecoder.BeginTryDecode((byte)(b & ~DynamicTableSizeUpdateMask), DynamicTableSizeUpdatePrefix, out intResult)) + { + SetDynamicHeaderTableSize(intResult); + } + else + { + _state = State.DynamicTableSizeUpdate; + } + } + else + { + // Can't happen + Debug.Fail("Unreachable code"); + throw new InvalidOperationException("Unreachable code."); + } + + break; + case State.HeaderFieldIndex: + if (_integerDecoder.TryDecode(b, out intResult)) + { + OnIndexedHeaderField(intResult, handler); + } + + break; + case State.HeaderNameIndex: + if (_integerDecoder.TryDecode(b, out intResult)) + { + OnIndexedHeaderName(intResult); + } + + break; + case State.HeaderNameLength: + _huffman = (b & HuffmanMask) != 0; + + if (_integerDecoder.BeginTryDecode((byte)(b & ~HuffmanMask), StringLengthPrefix, out intResult)) + { + if (intResult == 0) + { + throw new HPackDecodingException(SR.Format(SR.net_http_invalid_header_name, "")); + } + + OnStringLength(intResult, nextState: State.HeaderName); + } + else + { + _state = State.HeaderNameLengthContinue; + } + + break; + case State.HeaderNameLengthContinue: + if (_integerDecoder.TryDecode(b, out intResult)) + { + // IntegerDecoder disallows overlong encodings, where an integer is encoded with more bytes than is strictly required. + // 0 should always be represented by a single byte, so we shouldn't need to check for it in the continuation case. + Debug.Assert(intResult != 0, "A header name length of 0 should never be encoded with a continuation byte."); + + OnStringLength(intResult, nextState: State.HeaderName); + } + + break; + case State.HeaderName: + _stringOctets[_stringIndex++] = b; + + if (_stringIndex == _stringLength) + { + OnString(nextState: State.HeaderValueLength); + } + + break; + case State.HeaderValueLength: + _huffman = (b & HuffmanMask) != 0; + + if (_integerDecoder.BeginTryDecode((byte)(b & ~HuffmanMask), StringLengthPrefix, out intResult)) + { + OnStringLength(intResult, nextState: State.HeaderValue); + + if (intResult == 0) + { + ProcessHeaderValue(handler); + } + } + else + { + _state = State.HeaderValueLengthContinue; + } + + break; + case State.HeaderValueLengthContinue: + if (_integerDecoder.TryDecode(b, out intResult)) + { + // IntegerDecoder disallows overlong encodings where an integer is encoded with more bytes than is strictly required. + // 0 should always be represented by a single byte, so we shouldn't need to check for it in the continuation case. + Debug.Assert(intResult != 0, "A header value length of 0 should never be encoded with a continuation byte."); + + OnStringLength(intResult, nextState: State.HeaderValue); + } + + break; + case State.HeaderValue: + _stringOctets[_stringIndex++] = b; + + if (_stringIndex == _stringLength) + { + ProcessHeaderValue(handler); + } + + break; + case State.DynamicTableSizeUpdate: + if (_integerDecoder.TryDecode(b, out intResult)) + { + SetDynamicHeaderTableSize(intResult); + _state = State.Ready; + } + + break; + default: + // Can't happen + Debug.Fail("HPACK decoder reach an invalid state"); + throw new NotImplementedException(_state.ToString()); + } + } + } + + private void CheckIncompleteHeaderBlock(bool endHeaders) + { + if (endHeaders) + { + if (_state != State.Ready) + { + throw new HPackDecodingException(SR.net_http_hpack_incomplete_header_block); + } + + _headersObserved = false; + } + } + + private void ProcessHeaderValue(IHttpHeadersHandler? handler) + { + OnString(nextState: State.Ready); + + var headerNameSpan = new Span(_headerName, 0, _headerNameLength); + var headerValueSpan = new Span(_headerValueOctets, 0, _headerValueLength); + + handler?.OnHeader(headerNameSpan, headerValueSpan); + + if (_index) + { + _dynamicTable.Insert(headerNameSpan, headerValueSpan); + } + } + + public void CompleteDecode() + { + if (_state != State.Ready) + { + // Incomplete header block + throw new HPackDecodingException(SR.net_http_hpack_unexpected_end); + } + } + + private void OnIndexedHeaderField(int index, IHttpHeadersHandler? handler) + { + HeaderField header = GetHeader(index); + handler?.OnHeader(header.Name, header.Value); + _state = State.Ready; + } + + private void OnIndexedHeaderName(int index) + { + HeaderField header = GetHeader(index); + _headerName = header.Name; + _headerNameLength = header.Name.Length; + _state = State.HeaderValueLength; + } + + private void OnStringLength(int length, State nextState) + { + if (length > _stringOctets.Length) + { + if (length > _maxHeadersLength) + { + throw new HPackDecodingException(SR.Format(SR.net_http_headers_exceeded_length, _maxHeadersLength)); + } + + _stringOctets = new byte[Math.Max(length, _stringOctets.Length * 2)]; + } + + _stringLength = length; + _stringIndex = 0; + _state = nextState; + } + + private void OnString(State nextState) + { + int Decode(ref byte[] dst) + { + if (_huffman) + { + return Huffman.Decode(new ReadOnlySpan(_stringOctets, 0, _stringLength), ref dst); + } + else + { + if (dst.Length < _stringLength) + { + dst = new byte[Math.Max(_stringLength, dst.Length * 2)]; + } + + Buffer.BlockCopy(_stringOctets, 0, dst, 0, _stringLength); + return _stringLength; + } + } + + try + { + if (_state == State.HeaderName) + { + _headerNameLength = Decode(ref _headerNameOctets); + _headerName = _headerNameOctets; + } + else + { + _headerValueLength = Decode(ref _headerValueOctets); + } + } + catch (HuffmanDecodingException ex) + { + throw new HPackDecodingException(SR.net_http_hpack_huffman_decode_failed, ex); + } + + _state = nextState; + } + + private HeaderField GetHeader(int index) + { + try + { + return index <= H2StaticTable.Count + ? H2StaticTable.Get(index - 1) + : _dynamicTable[index - H2StaticTable.Count - 1]; + } + catch (IndexOutOfRangeException) + { + // Header index out of range. + throw new HPackDecodingException(SR.Format(SR.net_http_hpack_invalid_index, index)); + } + } + + private void SetDynamicHeaderTableSize(int size) + { + if (size > _maxDynamicTableSize) + { + throw new HPackDecodingException(SR.Format(SR.net_http_hpack_large_table_size_update, size, _maxDynamicTableSize)); + } + + _dynamicTable.Resize(size); + } + } +} diff --git a/src/Shared/runtime/Http2/Hpack/HPackDecodingException.cs b/src/Shared/runtime/Http2/Hpack/HPackDecodingException.cs new file mode 100644 index 0000000000..b30eba72b9 --- /dev/null +++ b/src/Shared/runtime/Http2/Hpack/HPackDecodingException.cs @@ -0,0 +1,29 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Runtime.Serialization; + +namespace System.Net.Http.HPack +{ + // TODO: Should this be public? + [Serializable] + internal class HPackDecodingException : Exception + { + public HPackDecodingException() + { + } + + public HPackDecodingException(string message) : base(message) + { + } + + public HPackDecodingException(string message, Exception innerException) : base(message, innerException) + { + } + + public HPackDecodingException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + } +} diff --git a/src/Shared/runtime/Http2/Hpack/HPackEncoder.cs b/src/Shared/runtime/Http2/Hpack/HPackEncoder.cs new file mode 100644 index 0000000000..d09f184134 --- /dev/null +++ b/src/Shared/runtime/Http2/Hpack/HPackEncoder.cs @@ -0,0 +1,480 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable +using System.Collections.Generic; +using System.Diagnostics; + +namespace System.Net.Http.HPack +{ + internal static class HPackEncoder + { + // Things we should add: + // * Huffman encoding + // + // Things we should consider adding: + // * Dynamic table encoding: + // This would make the encoder stateful, which complicates things significantly. + // Additionally, it's not clear exactly what strings we would add to the dynamic table + // without some additional guidance from the user about this. + // So for now, don't do dynamic encoding. + + /// Encodes an "Indexed Header Field". + public static bool EncodeIndexedHeaderField(int index, Span destination, out int bytesWritten) + { + // From https://tools.ietf.org/html/rfc7541#section-6.1 + // ---------------------------------------------------- + // 0 1 2 3 4 5 6 7 + // +---+---+---+---+---+---+---+---+ + // | 1 | Index (7+) | + // +---+---------------------------+ + + if (destination.Length != 0) + { + destination[0] = 0x80; + return IntegerEncoder.Encode(index, 7, destination, out bytesWritten); + } + + bytesWritten = 0; + return false; + } + + /// Encodes the status code of a response to the :status field. + public static bool EncodeStatusHeader(int statusCode, Span destination, out int bytesWritten) + { + // Bytes written depend on whether the status code value maps directly to an index + switch (statusCode) + { + case 200: + case 204: + case 206: + case 304: + case 400: + case 404: + case 500: + // Status codes which exist in the HTTP/2 StaticTable. + return EncodeIndexedHeaderField(H2StaticTable.StatusIndex[statusCode], destination, out bytesWritten); + default: + // If the status code doesn't have a static index then we need to include the full value. + // Write a status index and then the number bytes as a string literal. + if (!EncodeLiteralHeaderFieldWithoutIndexing(H2StaticTable.Status200, destination, out var nameLength)) + { + bytesWritten = 0; + return false; + } + + var statusBytes = StatusCodes.ToStatusBytes(statusCode); + + if (!EncodeStringLiteral(statusBytes, destination.Slice(nameLength), out var valueLength)) + { + bytesWritten = 0; + return false; + } + + bytesWritten = nameLength + valueLength; + return true; + } + } + + /// Encodes a "Literal Header Field without Indexing". + public static bool EncodeLiteralHeaderFieldWithoutIndexing(int index, string value, Span destination, out int bytesWritten) + { + // From https://tools.ietf.org/html/rfc7541#section-6.2.2 + // ------------------------------------------------------ + // 0 1 2 3 4 5 6 7 + // +---+---+---+---+---+---+---+---+ + // | 0 | 0 | 0 | 0 | Index (4+) | + // +---+---+-----------------------+ + // | H | Value Length (7+) | + // +---+---------------------------+ + // | Value String (Length octets) | + // +-------------------------------+ + + if ((uint)destination.Length >= 2) + { + destination[0] = 0; + if (IntegerEncoder.Encode(index, 4, destination, out int indexLength)) + { + Debug.Assert(indexLength >= 1); + if (EncodeStringLiteral(value, destination.Slice(indexLength), out int nameLength)) + { + bytesWritten = indexLength + nameLength; + return true; + } + } + } + + bytesWritten = 0; + return false; + } + + /// + /// Encodes a "Literal Header Field without Indexing", but only the index portion; + /// a subsequent call to EncodeStringLiteral must be used to encode the associated value. + /// + public static bool EncodeLiteralHeaderFieldWithoutIndexing(int index, Span destination, out int bytesWritten) + { + // From https://tools.ietf.org/html/rfc7541#section-6.2.2 + // ------------------------------------------------------ + // 0 1 2 3 4 5 6 7 + // +---+---+---+---+---+---+---+---+ + // | 0 | 0 | 0 | 0 | Index (4+) | + // +---+---+-----------------------+ + // + // ... expected after this: + // + // | H | Value Length (7+) | + // +---+---------------------------+ + // | Value String (Length octets) | + // +-------------------------------+ + + if ((uint)destination.Length != 0) + { + destination[0] = 0; + if (IntegerEncoder.Encode(index, 4, destination, out int indexLength)) + { + Debug.Assert(indexLength >= 1); + bytesWritten = indexLength; + return true; + } + } + + bytesWritten = 0; + return false; + } + + /// Encodes a "Literal Header Field without Indexing - New Name". + public static bool EncodeLiteralHeaderFieldWithoutIndexingNewName(string name, string value, Span destination, out int bytesWritten) + { + // From https://tools.ietf.org/html/rfc7541#section-6.2.2 + // ------------------------------------------------------ + // 0 1 2 3 4 5 6 7 + // +---+---+---+---+---+---+---+---+ + // | 0 | 0 | 0 | 0 | 0 | + // +---+---+-----------------------+ + // | H | Name Length (7+) | + // +---+---------------------------+ + // | Name String (Length octets) | + // +---+---------------------------+ + // | H | Value Length (7+) | + // +---+---------------------------+ + // | Value String (Length octets) | + // +-------------------------------+ + + if ((uint)destination.Length >= 3) + { + destination[0] = 0; + if (EncodeLiteralHeaderName(name, destination.Slice(1), out int nameLength) && + EncodeStringLiteral(value, destination.Slice(1 + nameLength), out int valueLength)) + { + bytesWritten = 1 + nameLength + valueLength; + return true; + } + } + + bytesWritten = 0; + return false; + } + + /// Encodes a "Literal Header Field without Indexing - New Name". + public static bool EncodeLiteralHeaderFieldWithoutIndexingNewName(string name, ReadOnlySpan values, string separator, Span destination, out int bytesWritten) + { + // From https://tools.ietf.org/html/rfc7541#section-6.2.2 + // ------------------------------------------------------ + // 0 1 2 3 4 5 6 7 + // +---+---+---+---+---+---+---+---+ + // | 0 | 0 | 0 | 0 | 0 | + // +---+---+-----------------------+ + // | H | Name Length (7+) | + // +---+---------------------------+ + // | Name String (Length octets) | + // +---+---------------------------+ + // | H | Value Length (7+) | + // +---+---------------------------+ + // | Value String (Length octets) | + // +-------------------------------+ + + if ((uint)destination.Length >= 3) + { + destination[0] = 0; + if (EncodeLiteralHeaderName(name, destination.Slice(1), out int nameLength) && + EncodeStringLiterals(values, separator, destination.Slice(1 + nameLength), out int valueLength)) + { + bytesWritten = 1 + nameLength + valueLength; + return true; + } + } + + bytesWritten = 0; + return false; + } + + /// + /// Encodes a "Literal Header Field without Indexing - New Name", but only the name portion; + /// a subsequent call to EncodeStringLiteral must be used to encode the associated value. + /// + public static bool EncodeLiteralHeaderFieldWithoutIndexingNewName(string name, Span destination, out int bytesWritten) + { + // From https://tools.ietf.org/html/rfc7541#section-6.2.2 + // ------------------------------------------------------ + // 0 1 2 3 4 5 6 7 + // +---+---+---+---+---+---+---+---+ + // | 0 | 0 | 0 | 0 | 0 | + // +---+---+-----------------------+ + // | H | Name Length (7+) | + // +---+---------------------------+ + // | Name String (Length octets) | + // +---+---------------------------+ + // + // ... expected after this: + // + // | H | Value Length (7+) | + // +---+---------------------------+ + // | Value String (Length octets) | + // +-------------------------------+ + + if ((uint)destination.Length >= 2) + { + destination[0] = 0; + if (EncodeLiteralHeaderName(name, destination.Slice(1), out int nameLength)) + { + bytesWritten = 1 + nameLength; + return true; + } + } + + bytesWritten = 0; + return false; + } + + private static bool EncodeLiteralHeaderName(string value, Span destination, out int bytesWritten) + { + // From https://tools.ietf.org/html/rfc7541#section-5.2 + // ------------------------------------------------------ + // 0 1 2 3 4 5 6 7 + // +---+---+---+---+---+---+---+---+ + // | H | String Length (7+) | + // +---+---------------------------+ + // | String Data (Length octets) | + // +-------------------------------+ + + if (destination.Length != 0) + { + destination[0] = 0; // TODO: Use Huffman encoding + if (IntegerEncoder.Encode(value.Length, 7, destination, out int integerLength)) + { + Debug.Assert(integerLength >= 1); + + destination = destination.Slice(integerLength); + if (value.Length <= destination.Length) + { + for (int i = 0; i < value.Length; i++) + { + char c = value[i]; + destination[i] = (byte)((uint)(c - 'A') <= ('Z' - 'A') ? c | 0x20 : c); + } + + bytesWritten = integerLength + value.Length; + return true; + } + } + } + + bytesWritten = 0; + return false; + } + + private static bool EncodeStringLiteralValue(string value, Span destination, out int bytesWritten) + { + if (value.Length <= destination.Length) + { + for (int i = 0; i < value.Length; i++) + { + char c = value[i]; + if ((c & 0xFF80) != 0) + { + throw new HttpRequestException(SR.net_http_request_invalid_char_encoding); + } + + destination[i] = (byte)c; + } + + bytesWritten = value.Length; + return true; + } + + bytesWritten = 0; + return false; + } + + public static bool EncodeStringLiteral(ReadOnlySpan value, Span destination, out int bytesWritten) + { + // From https://tools.ietf.org/html/rfc7541#section-5.2 + // ------------------------------------------------------ + // 0 1 2 3 4 5 6 7 + // +---+---+---+---+---+---+---+---+ + // | H | String Length (7+) | + // +---+---------------------------+ + // | String Data (Length octets) | + // +-------------------------------+ + + if (destination.Length != 0) + { + destination[0] = 0; // TODO: Use Huffman encoding + if (IntegerEncoder.Encode(value.Length, 7, destination, out int integerLength)) + { + Debug.Assert(integerLength >= 1); + + destination = destination.Slice(integerLength); + if (value.Length <= destination.Length) + { + // Note: No validation. Bytes should have already been validated. + value.CopyTo(destination); + + bytesWritten = integerLength + value.Length; + return true; + } + } + } + + bytesWritten = 0; + return false; + } + + public static bool EncodeStringLiteral(string value, Span destination, out int bytesWritten) + { + // From https://tools.ietf.org/html/rfc7541#section-5.2 + // ------------------------------------------------------ + // 0 1 2 3 4 5 6 7 + // +---+---+---+---+---+---+---+---+ + // | H | String Length (7+) | + // +---+---------------------------+ + // | String Data (Length octets) | + // +-------------------------------+ + + if (destination.Length != 0) + { + destination[0] = 0; // TODO: Use Huffman encoding + if (IntegerEncoder.Encode(value.Length, 7, destination, out int integerLength)) + { + Debug.Assert(integerLength >= 1); + + if (EncodeStringLiteralValue(value, destination.Slice(integerLength), out int valueLength)) + { + bytesWritten = integerLength + valueLength; + return true; + } + } + } + + bytesWritten = 0; + return false; + } + + public static bool EncodeStringLiterals(ReadOnlySpan values, string? separator, Span destination, out int bytesWritten) + { + bytesWritten = 0; + + if (values.Length == 0) + { + return EncodeStringLiteral("", destination, out bytesWritten); + } + else if (values.Length == 1) + { + return EncodeStringLiteral(values[0], destination, out bytesWritten); + } + + if (destination.Length != 0) + { + int valueLength = 0; + + // Calculate length of all parts and separators. + foreach (string part in values) + { + valueLength = checked((int)(valueLength + part.Length)); + } + + Debug.Assert(separator != null); + valueLength = checked((int)(valueLength + (values.Length - 1) * separator.Length)); + + destination[0] = 0; + if (IntegerEncoder.Encode(valueLength, 7, destination, out int integerLength)) + { + Debug.Assert(integerLength >= 1); + + int encodedLength = 0; + for (int j = 0; j < values.Length; j++) + { + if (j != 0 && !EncodeStringLiteralValue(separator, destination.Slice(integerLength), out encodedLength)) + { + return false; + } + + integerLength += encodedLength; + + if (!EncodeStringLiteralValue(values[j], destination.Slice(integerLength), out encodedLength)) + { + return false; + } + + integerLength += encodedLength; + } + + bytesWritten = integerLength; + return true; + } + } + + return false; + } + + /// + /// Encodes a "Literal Header Field without Indexing" to a new array, but only the index portion; + /// a subsequent call to EncodeStringLiteral must be used to encode the associated value. + /// + public static byte[] EncodeLiteralHeaderFieldWithoutIndexingToAllocatedArray(int index) + { + Span span = stackalloc byte[256]; + bool success = EncodeLiteralHeaderFieldWithoutIndexing(index, span, out int length); + Debug.Assert(success, $"Stack-allocated space was too small for index '{index}'."); + return span.Slice(0, length).ToArray(); + } + + /// + /// Encodes a "Literal Header Field without Indexing - New Name" to a new array, but only the name portion; + /// a subsequent call to EncodeStringLiteral must be used to encode the associated value. + /// + public static byte[] EncodeLiteralHeaderFieldWithoutIndexingNewNameToAllocatedArray(string name) + { + Span span = stackalloc byte[256]; + bool success = EncodeLiteralHeaderFieldWithoutIndexingNewName(name, span, out int length); + Debug.Assert(success, $"Stack-allocated space was too small for \"{name}\"."); + return span.Slice(0, length).ToArray(); + } + + /// Encodes a "Literal Header Field without Indexing" to a new array. + public static byte[] EncodeLiteralHeaderFieldWithoutIndexingToAllocatedArray(int index, string value) + { + Span span = +#if DEBUG + stackalloc byte[4]; // to validate growth algorithm +#else + stackalloc byte[512]; +#endif + while (true) + { + if (EncodeLiteralHeaderFieldWithoutIndexing(index, value, span, out int length)) + { + return span.Slice(0, length).ToArray(); + } + + // This is a rare path, only used once per HTTP/2 connection and only + // for very long host names. Just allocate rather than complicate + // the code with ArrayPool usage. In practice we should never hit this, + // as hostnames should be <= 255 characters. + span = new byte[span.Length * 2]; + } + } + } +} diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/HPack/HPackEncodingException.cs b/src/Shared/runtime/Http2/Hpack/HPackEncodingException.cs similarity index 52% rename from src/Servers/Kestrel/Core/src/Internal/Http2/HPack/HPackEncodingException.cs rename to src/Shared/runtime/Http2/Hpack/HPackEncodingException.cs index 6911754476..792c871837 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/HPack/HPackEncodingException.cs +++ b/src/Shared/runtime/Http2/Hpack/HPackEncodingException.cs @@ -1,16 +1,20 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. -using System; - -namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack +namespace System.Net.Http.HPack { internal sealed class HPackEncodingException : Exception { + public HPackEncodingException() + { + } + public HPackEncodingException(string message) : base(message) { } + public HPackEncodingException(string message, Exception innerException) : base(message, innerException) { diff --git a/src/Shared/runtime/Http2/Hpack/HeaderField.cs b/src/Shared/runtime/Http2/Hpack/HeaderField.cs new file mode 100644 index 0000000000..2384c71983 --- /dev/null +++ b/src/Shared/runtime/Http2/Hpack/HeaderField.cs @@ -0,0 +1,48 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; +using System.Text; + +namespace System.Net.Http.HPack +{ + internal readonly struct HeaderField + { + // http://httpwg.org/specs/rfc7541.html#rfc.section.4.1 + public const int RfcOverhead = 32; + + public HeaderField(ReadOnlySpan name, ReadOnlySpan value) + { + Debug.Assert(name.Length > 0); + + // TODO: We're allocating here on every new table entry. + // That means a poorly-behaved server could cause us to allocate repeatedly. + // We should revisit our allocation strategy here so we don't need to allocate per entry + // and we have a cap to how much allocation can happen per dynamic table + // (without limiting the number of table entries a server can provide within the table size limit). + Name = name.ToArray(); + Value = value.ToArray(); + } + + public byte[] Name { get; } + + public byte[] Value { get; } + + public int Length => GetLength(Name.Length, Value.Length); + + public static int GetLength(int nameLength, int valueLength) => nameLength + valueLength + RfcOverhead; + + public override string ToString() + { + if (Name != null) + { + return Encoding.ASCII.GetString(Name) + ": " + Encoding.ASCII.GetString(Value); + } + else + { + return ""; + } + } + } +} diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/HPack/Huffman.cs b/src/Shared/runtime/Http2/Hpack/Huffman.cs similarity index 92% rename from src/Servers/Kestrel/Core/src/Internal/Http2/HPack/Huffman.cs rename to src/Shared/runtime/Http2/Hpack/Huffman.cs index fed5481d30..c534e0c173 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/HPack/Huffman.cs +++ b/src/Shared/runtime/Http2/Hpack/Huffman.cs @@ -1,9 +1,10 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. -using System; +using System.Diagnostics; -namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack +namespace System.Net.Http.HPack { internal class Huffman { @@ -303,25 +304,30 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack /// Decodes a Huffman encoded string from a byte array. /// /// The source byte array containing the encoded data. - /// The destination byte array to store the decoded data. + /// The destination byte array to store the decoded data. This may grow if its size is insufficient. /// The number of decoded symbols. - public static int Decode(ReadOnlySpan src, Span dst) + public static int Decode(ReadOnlySpan src, ref byte[] dstArray) { - var i = 0; - var j = 0; - var lastDecodedBits = 0; + Span dst = dstArray; + Debug.Assert(dst != null && dst.Length > 0); + + int i = 0; + int j = 0; + int lastDecodedBits = 0; while (i < src.Length) { // Note that if lastDecodeBits is 3 or more, then we will only get 5 bits (or less) // from src[i]. Thus we need to read 5 bytes here to ensure that we always have // at least 30 bits available for decoding. - var next = (uint)(src[i] << 24 + lastDecodedBits); + // TODO https://github.com/dotnet/runtime/issues/1506: + // Rework this as part of Huffman perf improvements + uint next = (uint)(src[i] << 24 + lastDecodedBits); next |= (i + 1 < src.Length ? (uint)(src[i + 1] << 16 + lastDecodedBits) : 0); next |= (i + 2 < src.Length ? (uint)(src[i + 2] << 8 + lastDecodedBits) : 0); next |= (i + 3 < src.Length ? (uint)(src[i + 3] << lastDecodedBits) : 0); next |= (i + 4 < src.Length ? (uint)(src[i + 4] >> (8 - lastDecodedBits)) : 0); - var ones = (uint)(int.MinValue >> (8 - lastDecodedBits - 1)); + uint ones = (uint)(int.MinValue >> (8 - lastDecodedBits - 1)); if (i == src.Length - 1 && lastDecodedBits > 0 && (next & ones) == ones) { // The remaining 7 or less bits are all 1, which is padding. @@ -334,24 +340,25 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack // The longest possible symbol size is 30 bits. If we're at the last 4 bytes // of the input, we need to make sure we pass the correct number of valid bits // left, otherwise the trailing 0s in next may form a valid symbol. - var validBits = Math.Min(30, (8 - lastDecodedBits) + (src.Length - i - 1) * 8); - var ch = DecodeValue(next, validBits, out var decodedBits); + int validBits = Math.Min(30, (8 - lastDecodedBits) + (src.Length - i - 1) * 8); + int ch = DecodeValue(next, validBits, out int decodedBits); if (ch == -1) { // No valid symbol could be decoded with the bits in next - throw new HuffmanDecodingException(CoreStrings.HPackHuffmanErrorIncomplete); + throw new HuffmanDecodingException(SR.net_http_hpack_huffman_decode_failed); } else if (ch == 256) { // A Huffman-encoded string literal containing the EOS symbol MUST be treated as a decoding error. // http://httpwg.org/specs/rfc7541.html#rfc.section.5.2 - throw new HuffmanDecodingException(CoreStrings.HPackHuffmanErrorEOS); + throw new HuffmanDecodingException(SR.net_http_hpack_huffman_decode_failed); } if (j == dst.Length) { - throw new HuffmanDecodingException(CoreStrings.HPackHuffmanErrorDestinationTooSmall); + Array.Resize(ref dstArray, dst.Length * 2); + dst = dstArray; } dst[j++] = (byte)ch; @@ -398,11 +405,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack // symbol in the list of values associated with bit length b in the decoding table by indexing it // with codeMax - v. - var codeMax = 0; + int codeMax = 0; - for (var i = 0; i < _decodingTable.Length && _decodingTable[i].codeLength <= validBits; i++) + for (int i = 0; i < _decodingTable.Length && _decodingTable[i].codeLength <= validBits; i++) { - var (codeLength, codes) = _decodingTable[i]; + (int codeLength, int[] codes) = _decodingTable[i]; if (i > 0) { @@ -411,8 +418,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack codeMax += codes.Length; - var mask = int.MinValue >> (codeLength - 1); - var masked = (data & mask) >> (32 - codeLength); + int mask = int.MinValue >> (codeLength - 1); + long masked = (data & mask) >> (32 - codeLength); if (masked < codeMax) { diff --git a/src/Shared/runtime/Http2/Hpack/HuffmanDecodingException.cs b/src/Shared/runtime/Http2/Hpack/HuffmanDecodingException.cs new file mode 100644 index 0000000000..2442f02da0 --- /dev/null +++ b/src/Shared/runtime/Http2/Hpack/HuffmanDecodingException.cs @@ -0,0 +1,37 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Runtime.Serialization; + +namespace System.Net.Http.HPack +{ + // TODO: Should this be public? + [Serializable] + internal class HuffmanDecodingException : Exception, ISerializable + { + public HuffmanDecodingException() + { + } + + public HuffmanDecodingException(string message) + : base(message) + { + } + + protected HuffmanDecodingException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + + void ISerializable.GetObjectData(SerializationInfo serializationInfo, StreamingContext streamingContext) + { + base.GetObjectData(serializationInfo, streamingContext); + } + + public override void GetObjectData(SerializationInfo serializationInfo, StreamingContext streamingContext) + { + base.GetObjectData(serializationInfo, streamingContext); + } + } +} diff --git a/src/Shared/runtime/Http2/Hpack/IntegerDecoder.cs b/src/Shared/runtime/Http2/Hpack/IntegerDecoder.cs new file mode 100644 index 0000000000..0841d69bf2 --- /dev/null +++ b/src/Shared/runtime/Http2/Hpack/IntegerDecoder.cs @@ -0,0 +1,103 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; +using System.Numerics; + +namespace System.Net.Http.HPack +{ + internal class IntegerDecoder + { + private int _i; + private int _m; + + /// + /// Decodes the first byte of the integer. + /// + /// + /// The first byte of the variable-length encoded integer. + /// + /// + /// The number of lower bits in this prefix byte that the + /// integer has been encoded into. Must be between 1 and 8. + /// Upper bits must be zero. + /// + /// + /// If decoded successfully, contains the decoded integer. + /// + /// + /// If the integer has been fully decoded, true. + /// Otherwise, false -- must be called on subsequent bytes. + /// + /// + /// The term "prefix" can be confusing. From the HPACK spec: + /// An integer is represented in two parts: a prefix that fills the current octet and an + /// optional list of octets that are used if the integer value does not fit within the prefix. + /// + public bool BeginTryDecode(byte b, int prefixLength, out int result) + { + Debug.Assert(prefixLength >= 1 && prefixLength <= 8); + Debug.Assert((b & ~((1 << prefixLength) - 1)) == 0, "bits other than prefix data must be set to 0."); + + if (b < ((1 << prefixLength) - 1)) + { + result = b; + return true; + } + + _i = b; + _m = 0; + result = 0; + return false; + } + + /// + /// Decodes subsequent bytes of an integer. + /// + /// The next byte. + /// + /// If decoded successfully, contains the decoded integer. + /// + /// If the integer has been fully decoded, true. Otherwise, false -- must be called on subsequent bytes. + public bool TryDecode(byte b, out int result) + { + // Check if shifting b by _m would result in > 31 bits. + // No masking is required: if the 8th bit is set, it indicates there is a + // bit set in a future byte, so it is fine to check that here as if it were + // bit 0 on the next byte. + // This is a simplified form of: + // int additionalBitsRequired = 32 - BitOperations.LeadingZeroCount((uint)b); + // if (_m + additionalBitsRequired > 31) + if (BitOperations.LeadingZeroCount((uint)b) <= _m) + { + throw new HPackDecodingException(SR.net_http_hpack_bad_integer); + } + + _i = _i + ((b & 0x7f) << _m); + + // If the addition overflowed, the result will be negative. + if (_i < 0) + { + throw new HPackDecodingException(SR.net_http_hpack_bad_integer); + } + + _m = _m + 7; + + if ((b & 128) == 0) + { + if (b == 0 && _m / 7 > 1) + { + // Do not accept overlong encodings. + throw new HPackDecodingException(SR.net_http_hpack_bad_integer); + } + + result = _i; + return true; + } + + result = 0; + return false; + } + } +} diff --git a/src/Shared/runtime/Http2/Hpack/IntegerEncoder.cs b/src/Shared/runtime/Http2/Hpack/IntegerEncoder.cs new file mode 100644 index 0000000000..227fbf0a44 --- /dev/null +++ b/src/Shared/runtime/Http2/Hpack/IntegerEncoder.cs @@ -0,0 +1,78 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; + +namespace System.Net.Http.HPack +{ + internal static class IntegerEncoder + { + /// + /// The maximum bytes required to encode a 32-bit int, regardless of prefix length. + /// + public const int MaxInt32EncodedLength = 6; + + /// + /// Encodes an integer into one or more bytes. + /// + /// The value to encode. Must not be negative. + /// The length of the prefix, in bits, to encode within. Must be between 1 and 8. + /// The destination span to encode to. + /// The number of bytes used to encode . + /// If had enough storage to encode , true. Otherwise, false. + public static bool Encode(int value, int numBits, Span destination, out int bytesWritten) + { + Debug.Assert(value >= 0); + Debug.Assert(numBits >= 1 && numBits <= 8); + + if (destination.Length == 0) + { + bytesWritten = 0; + return false; + } + + destination[0] &= MaskHigh(8 - numBits); + + if (value < (1 << numBits) - 1) + { + destination[0] |= (byte)value; + + bytesWritten = 1; + return true; + } + else + { + destination[0] |= (byte)((1 << numBits) - 1); + + if (1 == destination.Length) + { + bytesWritten = 0; + return false; + } + + value = value - ((1 << numBits) - 1); + int i = 1; + + while (value >= 128) + { + destination[i++] = (byte)(value % 128 + 128); + + if (i >= destination.Length) + { + bytesWritten = 0; + return false; + } + + value = value / 128; + } + destination[i++] = (byte)value; + + bytesWritten = i; + return true; + } + } + + private static byte MaskHigh(int n) => (byte)(sbyte.MinValue >> (n - 1)); + } +} diff --git a/src/Shared/runtime/Http2/Hpack/StatusCodes.cs b/src/Shared/runtime/Http2/Hpack/StatusCodes.cs new file mode 100644 index 0000000000..b701fa79f4 --- /dev/null +++ b/src/Shared/runtime/Http2/Hpack/StatusCodes.cs @@ -0,0 +1,220 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Globalization; +using System.Text; + +namespace System.Net.Http.HPack +{ + internal static class StatusCodes + { + // This uses C# compiler's ability to refer to static data directly. For more information see https://vcsjones.dev/2019/02/01/csharp-readonly-span-bytes-static + + private static ReadOnlySpan BytesStatus100 => new byte[] { (byte)'1', (byte)'0', (byte)'0' }; + private static ReadOnlySpan BytesStatus101 => new byte[] { (byte)'1', (byte)'0', (byte)'1' }; + private static ReadOnlySpan BytesStatus102 => new byte[] { (byte)'1', (byte)'0', (byte)'2' }; + + private static ReadOnlySpan BytesStatus200 => new byte[] { (byte)'2', (byte)'0', (byte)'0' }; + private static ReadOnlySpan BytesStatus201 => new byte[] { (byte)'2', (byte)'0', (byte)'1' }; + private static ReadOnlySpan BytesStatus202 => new byte[] { (byte)'2', (byte)'0', (byte)'2' }; + private static ReadOnlySpan BytesStatus203 => new byte[] { (byte)'2', (byte)'0', (byte)'3' }; + private static ReadOnlySpan BytesStatus204 => new byte[] { (byte)'2', (byte)'0', (byte)'4' }; + private static ReadOnlySpan BytesStatus205 => new byte[] { (byte)'2', (byte)'0', (byte)'5' }; + private static ReadOnlySpan BytesStatus206 => new byte[] { (byte)'2', (byte)'0', (byte)'6' }; + private static ReadOnlySpan BytesStatus207 => new byte[] { (byte)'2', (byte)'0', (byte)'7' }; + private static ReadOnlySpan BytesStatus208 => new byte[] { (byte)'2', (byte)'0', (byte)'8' }; + private static ReadOnlySpan BytesStatus226 => new byte[] { (byte)'2', (byte)'2', (byte)'6' }; + + private static ReadOnlySpan BytesStatus300 => new byte[] { (byte)'3', (byte)'0', (byte)'0' }; + private static ReadOnlySpan BytesStatus301 => new byte[] { (byte)'3', (byte)'0', (byte)'1' }; + private static ReadOnlySpan BytesStatus302 => new byte[] { (byte)'3', (byte)'0', (byte)'2' }; + private static ReadOnlySpan BytesStatus303 => new byte[] { (byte)'3', (byte)'0', (byte)'3' }; + private static ReadOnlySpan BytesStatus304 => new byte[] { (byte)'3', (byte)'0', (byte)'4' }; + private static ReadOnlySpan BytesStatus305 => new byte[] { (byte)'3', (byte)'0', (byte)'5' }; + private static ReadOnlySpan BytesStatus306 => new byte[] { (byte)'3', (byte)'0', (byte)'6' }; + private static ReadOnlySpan BytesStatus307 => new byte[] { (byte)'3', (byte)'0', (byte)'7' }; + private static ReadOnlySpan BytesStatus308 => new byte[] { (byte)'3', (byte)'0', (byte)'8' }; + + private static ReadOnlySpan BytesStatus400 => new byte[] { (byte)'4', (byte)'0', (byte)'0' }; + private static ReadOnlySpan BytesStatus401 => new byte[] { (byte)'4', (byte)'0', (byte)'1' }; + private static ReadOnlySpan BytesStatus402 => new byte[] { (byte)'4', (byte)'0', (byte)'2' }; + private static ReadOnlySpan BytesStatus403 => new byte[] { (byte)'4', (byte)'0', (byte)'3' }; + private static ReadOnlySpan BytesStatus404 => new byte[] { (byte)'4', (byte)'0', (byte)'4' }; + private static ReadOnlySpan BytesStatus405 => new byte[] { (byte)'4', (byte)'0', (byte)'5' }; + private static ReadOnlySpan BytesStatus406 => new byte[] { (byte)'4', (byte)'0', (byte)'6' }; + private static ReadOnlySpan BytesStatus407 => new byte[] { (byte)'4', (byte)'0', (byte)'7' }; + private static ReadOnlySpan BytesStatus408 => new byte[] { (byte)'4', (byte)'0', (byte)'8' }; + private static ReadOnlySpan BytesStatus409 => new byte[] { (byte)'4', (byte)'0', (byte)'9' }; + private static ReadOnlySpan BytesStatus410 => new byte[] { (byte)'4', (byte)'1', (byte)'0' }; + private static ReadOnlySpan BytesStatus411 => new byte[] { (byte)'4', (byte)'1', (byte)'1' }; + private static ReadOnlySpan BytesStatus412 => new byte[] { (byte)'4', (byte)'1', (byte)'2' }; + private static ReadOnlySpan BytesStatus413 => new byte[] { (byte)'4', (byte)'1', (byte)'3' }; + private static ReadOnlySpan BytesStatus414 => new byte[] { (byte)'4', (byte)'1', (byte)'4' }; + private static ReadOnlySpan BytesStatus415 => new byte[] { (byte)'4', (byte)'1', (byte)'5' }; + private static ReadOnlySpan BytesStatus416 => new byte[] { (byte)'4', (byte)'1', (byte)'6' }; + private static ReadOnlySpan BytesStatus417 => new byte[] { (byte)'4', (byte)'1', (byte)'7' }; + private static ReadOnlySpan BytesStatus418 => new byte[] { (byte)'4', (byte)'1', (byte)'8' }; + private static ReadOnlySpan BytesStatus419 => new byte[] { (byte)'4', (byte)'1', (byte)'9' }; + private static ReadOnlySpan BytesStatus421 => new byte[] { (byte)'4', (byte)'2', (byte)'1' }; + private static ReadOnlySpan BytesStatus422 => new byte[] { (byte)'4', (byte)'2', (byte)'2' }; + private static ReadOnlySpan BytesStatus423 => new byte[] { (byte)'4', (byte)'2', (byte)'3' }; + private static ReadOnlySpan BytesStatus424 => new byte[] { (byte)'4', (byte)'2', (byte)'4' }; + private static ReadOnlySpan BytesStatus426 => new byte[] { (byte)'4', (byte)'2', (byte)'6' }; + private static ReadOnlySpan BytesStatus428 => new byte[] { (byte)'4', (byte)'2', (byte)'8' }; + private static ReadOnlySpan BytesStatus429 => new byte[] { (byte)'4', (byte)'2', (byte)'9' }; + private static ReadOnlySpan BytesStatus431 => new byte[] { (byte)'4', (byte)'3', (byte)'1' }; + private static ReadOnlySpan BytesStatus451 => new byte[] { (byte)'4', (byte)'5', (byte)'1' }; + + private static ReadOnlySpan BytesStatus500 => new byte[] { (byte)'5', (byte)'0', (byte)'0' }; + private static ReadOnlySpan BytesStatus501 => new byte[] { (byte)'5', (byte)'0', (byte)'1' }; + private static ReadOnlySpan BytesStatus502 => new byte[] { (byte)'5', (byte)'0', (byte)'2' }; + private static ReadOnlySpan BytesStatus503 => new byte[] { (byte)'5', (byte)'0', (byte)'3' }; + private static ReadOnlySpan BytesStatus504 => new byte[] { (byte)'5', (byte)'0', (byte)'4' }; + private static ReadOnlySpan BytesStatus505 => new byte[] { (byte)'5', (byte)'0', (byte)'5' }; + private static ReadOnlySpan BytesStatus506 => new byte[] { (byte)'5', (byte)'0', (byte)'6' }; + private static ReadOnlySpan BytesStatus507 => new byte[] { (byte)'5', (byte)'0', (byte)'7' }; + private static ReadOnlySpan BytesStatus508 => new byte[] { (byte)'5', (byte)'0', (byte)'8' }; + private static ReadOnlySpan BytesStatus510 => new byte[] { (byte)'5', (byte)'1', (byte)'0' }; + private static ReadOnlySpan BytesStatus511 => new byte[] { (byte)'5', (byte)'1', (byte)'1' }; + + public static ReadOnlySpan ToStatusBytes(int statusCode) + { + switch (statusCode) + { + case (int)HttpStatusCode.Continue: + return BytesStatus100; + case (int)HttpStatusCode.SwitchingProtocols: + return BytesStatus101; + case (int)HttpStatusCode.Processing: + return BytesStatus102; + + case (int)HttpStatusCode.OK: + return BytesStatus200; + case (int)HttpStatusCode.Created: + return BytesStatus201; + case (int)HttpStatusCode.Accepted: + return BytesStatus202; + case (int)HttpStatusCode.NonAuthoritativeInformation: + return BytesStatus203; + case (int)HttpStatusCode.NoContent: + return BytesStatus204; + case (int)HttpStatusCode.ResetContent: + return BytesStatus205; + case (int)HttpStatusCode.PartialContent: + return BytesStatus206; + case (int)HttpStatusCode.MultiStatus: + return BytesStatus207; + case (int)HttpStatusCode.AlreadyReported: + return BytesStatus208; + case (int)HttpStatusCode.IMUsed: + return BytesStatus226; + + case (int)HttpStatusCode.MultipleChoices: + return BytesStatus300; + case (int)HttpStatusCode.MovedPermanently: + return BytesStatus301; + case (int)HttpStatusCode.Found: + return BytesStatus302; + case (int)HttpStatusCode.SeeOther: + return BytesStatus303; + case (int)HttpStatusCode.NotModified: + return BytesStatus304; + case (int)HttpStatusCode.UseProxy: + return BytesStatus305; + case (int)HttpStatusCode.Unused: + return BytesStatus306; + case (int)HttpStatusCode.TemporaryRedirect: + return BytesStatus307; + case (int)HttpStatusCode.PermanentRedirect: + return BytesStatus308; + + case (int)HttpStatusCode.BadRequest: + return BytesStatus400; + case (int)HttpStatusCode.Unauthorized: + return BytesStatus401; + case (int)HttpStatusCode.PaymentRequired: + return BytesStatus402; + case (int)HttpStatusCode.Forbidden: + return BytesStatus403; + case (int)HttpStatusCode.NotFound: + return BytesStatus404; + case (int)HttpStatusCode.MethodNotAllowed: + return BytesStatus405; + case (int)HttpStatusCode.NotAcceptable: + return BytesStatus406; + case (int)HttpStatusCode.ProxyAuthenticationRequired: + return BytesStatus407; + case (int)HttpStatusCode.RequestTimeout: + return BytesStatus408; + case (int)HttpStatusCode.Conflict: + return BytesStatus409; + case (int)HttpStatusCode.Gone: + return BytesStatus410; + case (int)HttpStatusCode.LengthRequired: + return BytesStatus411; + case (int)HttpStatusCode.PreconditionFailed: + return BytesStatus412; + case (int)HttpStatusCode.RequestEntityTooLarge: + return BytesStatus413; + case (int)HttpStatusCode.RequestUriTooLong: + return BytesStatus414; + case (int)HttpStatusCode.UnsupportedMediaType: + return BytesStatus415; + case (int)HttpStatusCode.RequestedRangeNotSatisfiable: + return BytesStatus416; + case (int)HttpStatusCode.ExpectationFailed: + return BytesStatus417; + case (int)418: + return BytesStatus418; + case (int)419: + return BytesStatus419; + case (int)HttpStatusCode.MisdirectedRequest: + return BytesStatus421; + case (int)HttpStatusCode.UnprocessableEntity: + return BytesStatus422; + case (int)HttpStatusCode.Locked: + return BytesStatus423; + case (int)HttpStatusCode.FailedDependency: + return BytesStatus424; + case (int)HttpStatusCode.UpgradeRequired: + return BytesStatus426; + case (int)HttpStatusCode.PreconditionRequired: + return BytesStatus428; + case (int)HttpStatusCode.TooManyRequests: + return BytesStatus429; + case (int)HttpStatusCode.RequestHeaderFieldsTooLarge: + return BytesStatus431; + case (int)HttpStatusCode.UnavailableForLegalReasons: + return BytesStatus451; + + case (int)HttpStatusCode.InternalServerError: + return BytesStatus500; + case (int)HttpStatusCode.NotImplemented: + return BytesStatus501; + case (int)HttpStatusCode.BadGateway: + return BytesStatus502; + case (int)HttpStatusCode.ServiceUnavailable: + return BytesStatus503; + case (int)HttpStatusCode.GatewayTimeout: + return BytesStatus504; + case (int)HttpStatusCode.HttpVersionNotSupported: + return BytesStatus505; + case (int)HttpStatusCode.VariantAlsoNegotiates: + return BytesStatus506; + case (int)HttpStatusCode.InsufficientStorage: + return BytesStatus507; + case (int)HttpStatusCode.LoopDetected: + return BytesStatus508; + case (int)HttpStatusCode.NotExtended: + return BytesStatus510; + case (int)HttpStatusCode.NetworkAuthenticationRequired: + return BytesStatus511; + + default: + return Encoding.ASCII.GetBytes(statusCode.ToString(CultureInfo.InvariantCulture)); + + } + } + } +} diff --git a/src/Shared/runtime/Http3/Frames/Http3ErrorCode.cs b/src/Shared/runtime/Http3/Frames/Http3ErrorCode.cs new file mode 100644 index 0000000000..785eab75bd --- /dev/null +++ b/src/Shared/runtime/Http3/Frames/Http3ErrorCode.cs @@ -0,0 +1,92 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Net.Http +{ + internal enum Http3ErrorCode : long + { + /// + /// H3_NO_ERROR (0x100): + /// No error. This is used when the connection or stream needs to be closed, but there is no error to signal. + /// + NoError = 0x100, + /// + /// H3_GENERAL_PROTOCOL_ERROR (0x101): + /// Peer violated protocol requirements in a way which doesn’t match a more specific error code, + /// or endpoint declines to use the more specific error code. + /// + ProtocolError = 0x101, + /// + /// H3_INTERNAL_ERROR (0x102): + /// An internal error has occurred in the HTTP stack. + /// + InternalError = 0x102, + /// + /// H3_STREAM_CREATION_ERROR (0x103): + /// The endpoint detected that its peer created a stream that it will not accept. + /// + StreamCreationError = 0x103, + /// + /// H3_CLOSED_CRITICAL_STREAM (0x104): + /// A stream required by the connection was closed or reset. + /// + ClosedCriticalStream = 0x104, + /// + /// H3_FRAME_UNEXPECTED (0x105): + /// A frame was received which was not permitted in the current state. + /// + UnexpectedFrame = 0x105, + /// + /// H3_FRAME_ERROR (0x106): + /// A frame that fails to satisfy layout requirements or with an invalid size was received. + /// + FrameError = 0x106, + /// + /// H3_EXCESSIVE_LOAD (0x107): + /// The endpoint detected that its peer is exhibiting a behavior that might be generating excessive load. + /// + ExcessiveLoad = 0x107, + /// + /// H3_ID_ERROR (0x109): + /// A Stream ID, Push ID, or Placeholder ID was used incorrectly, such as exceeding a limit, reducing a limit, or being reused. + /// + IdError = 0x108, + /// + /// H3_SETTINGS_ERROR (0x10A): + /// An endpoint detected an error in the payload of a SETTINGS frame: a duplicate setting was detected, + /// a client-only setting was sent by a server, or a server-only setting by a client. + /// + SettingsError = 0x109, + /// + /// H3_MISSING_SETTINGS (0x10B): + /// No SETTINGS frame was received at the beginning of the control stream. + /// + MissingSettings = 0x10a, + /// + /// H3_REQUEST_REJECTED (0x10C): + /// A server rejected a request without performing any application processing. + /// + RequestRejected = 0x10b, + /// + /// H3_REQUEST_CANCELLED (0x10D): + /// The request or its response (including pushed response) is cancelled. + /// + RequestCancelled = 0x10c, + /// + /// H3_REQUEST_INCOMPLETE (0x10E): + /// The client’s stream terminated without containing a fully-formed request. + /// + RequestIncomplete = 0x10d, + /// + /// H3_CONNECT_ERROR (0x110): + /// The connection established in response to a CONNECT request was reset or abnormally closed. + /// + ConnectError = 0x10f, + /// + /// H3_VERSION_FALLBACK (0x111): + /// The requested operation cannot be served over HTTP/3. The peer should retry over HTTP/1.1. + /// + VersionFallback = 0x110, + } +} diff --git a/src/Shared/runtime/Http3/Frames/Http3Frame.cs b/src/Shared/runtime/Http3/Frames/Http3Frame.cs new file mode 100644 index 0000000000..f7ed1b96a5 --- /dev/null +++ b/src/Shared/runtime/Http3/Frames/Http3Frame.cs @@ -0,0 +1,62 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; + +namespace System.Net.Http +{ + internal static partial class Http3Frame + { + public const int MaximumEncodedFrameEnvelopeLength = 1 + VariableLengthIntegerHelper.MaximumEncodedLength; // Frame type + payload length. + + /// + /// Reads two variable-length integers. + /// + public static bool TryReadIntegerPair(ReadOnlySpan buffer, out long a, out long b, out int bytesRead) + { + if (VariableLengthIntegerHelper.TryRead(buffer, out a, out int aLength)) + { + buffer = buffer.Slice(aLength); + if (VariableLengthIntegerHelper.TryRead(buffer, out b, out int bLength)) + { + bytesRead = aLength + bLength; + return true; + } + } + + b = 0; + bytesRead = 0; + return false; + } + + // 0 1 2 3 + // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | Type (i) ... + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | Length (i) ... + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | Frame Payload (*) ... + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + public static bool TryWriteFrameEnvelope(Http3FrameType frameType, long payloadLength, Span buffer, out int bytesWritten) + { + Debug.Assert(VariableLengthIntegerHelper.GetByteCount((long)frameType) == 1, $"{nameof(TryWriteFrameEnvelope)} assumes {nameof(frameType)} will fit within a single byte varint."); + + if (buffer.Length != 0) + { + buffer[0] = (byte)frameType; + buffer = buffer.Slice(1); + + if (VariableLengthIntegerHelper.TryWrite(buffer, payloadLength, out int payloadLengthEncodedLength)) + { + bytesWritten = payloadLengthEncodedLength + 1; + return true; + } + } + + bytesWritten = 0; + return false; + } + } +} diff --git a/src/Shared/runtime/Http3/Frames/Http3FrameType.cs b/src/Shared/runtime/Http3/Frames/Http3FrameType.cs new file mode 100644 index 0000000000..252d6b76b6 --- /dev/null +++ b/src/Shared/runtime/Http3/Frames/Http3FrameType.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Net.Http +{ + internal enum Http3FrameType : long + { + Data = 0x0, + Headers = 0x1, + CancelPush = 0x3, + Settings = 0x4, + PushPromise = 0x5, + GoAway = 0x7, + MaxPushId = 0xD, + DuplicatePush = 0xE + } +} diff --git a/src/Shared/runtime/Http3/Helpers/VariableLengthIntegerHelper.cs b/src/Shared/runtime/Http3/Helpers/VariableLengthIntegerHelper.cs new file mode 100644 index 0000000000..010d2372fa --- /dev/null +++ b/src/Shared/runtime/Http3/Helpers/VariableLengthIntegerHelper.cs @@ -0,0 +1,209 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Buffers; +using System.Buffers.Binary; +using System.Diagnostics; + +namespace System.Net.Http +{ + /// + /// Variable length integer encoding and decoding methods. Based on https://tools.ietf.org/html/draft-ietf-quic-transport-24#section-16. + /// A variable-length integer can use 1, 2, 4, or 8 bytes. + /// + internal static class VariableLengthIntegerHelper + { + public const int MaximumEncodedLength = 8; + + // The high 4 bits indicate the length of the integer. + // 00 = length 1 + // 01 = length 2 + // 10 = length 4 + // 11 = length 8 + private const byte LengthMask = 0xC0; + private const byte InitialOneByteLengthMask = 0x00; + private const byte InitialTwoByteLengthMask = 0x40; + private const byte InitialFourByteLengthMask = 0x80; + private const byte InitialEightByteLengthMask = 0xC0; + + // Bits to subtract to remove the length. + private const uint TwoByteLengthMask = 0x4000; + private const uint FourByteLengthMask = 0x80000000; + private const ulong EightByteLengthMask = 0xC000000000000000; + + public const uint OneByteLimit = (1U << 6) - 1; + private const uint TwoByteLimit = (1U << 16) - 1; + private const uint FourByteLimit = (1U << 30) - 1; + private const long EightByteLimit = (1L << 62) - 1; + + public static bool TryRead(ReadOnlySpan buffer, out long value, out int bytesRead) + { + if (buffer.Length != 0) + { + byte firstByte = buffer[0]; + + switch (firstByte & LengthMask) + { + case InitialOneByteLengthMask: + value = firstByte; + bytesRead = 1; + return true; + case InitialTwoByteLengthMask: + if (BinaryPrimitives.TryReadUInt16BigEndian(buffer, out ushort serializedShort)) + { + value = serializedShort - TwoByteLengthMask; + bytesRead = 2; + return true; + } + break; + case InitialFourByteLengthMask: + if (BinaryPrimitives.TryReadUInt32BigEndian(buffer, out uint serializedInt)) + { + value = serializedInt - FourByteLengthMask; + bytesRead = 4; + return true; + } + break; + default: // InitialEightByteLengthMask + Debug.Assert((firstByte & LengthMask) == InitialEightByteLengthMask); + if (BinaryPrimitives.TryReadUInt64BigEndian(buffer, out ulong serializedLong)) + { + value = (long)(serializedLong - EightByteLengthMask); + Debug.Assert(value >= 0 && value <= EightByteLimit, "Serialized values are within [0, 2^62)."); + + bytesRead = 8; + return true; + } + break; + } + } + + value = 0; + bytesRead = 0; + return false; + } + + public static bool TryRead(ref SequenceReader reader, out long value) + { + // Hot path: we probably have the entire integer in one unbroken span. + if (TryRead(reader.UnreadSpan, out value, out int bytesRead)) + { + reader.Advance(bytesRead); + return true; + } + + // Cold path: copy to a temporary buffer before calling span-based read. + return TryReadSlow(ref reader, out value); + + static bool TryReadSlow(ref SequenceReader reader, out long value) + { + ReadOnlySpan span = reader.CurrentSpan; + + if (reader.TryPeek(out byte firstByte)) + { + int length = + (firstByte & LengthMask) switch + { + InitialOneByteLengthMask => 1, + InitialTwoByteLengthMask => 2, + InitialFourByteLengthMask => 4, + _ => 8 // LengthEightByte + }; + + Span temp = (stackalloc byte[8])[..length]; + if (reader.TryCopyTo(temp)) + { + bool result = TryRead(temp, out value, out int bytesRead); + Debug.Assert(result == true); + Debug.Assert(bytesRead == length); + + reader.Advance(bytesRead); + return true; + } + } + + value = 0; + return false; + } + } + + public static long GetInteger(in ReadOnlySequence buffer, out SequencePosition consumed, out SequencePosition examined) + { + var reader = new SequenceReader(buffer); + if (TryRead(ref reader, out long value)) + { + consumed = examined = buffer.GetPosition(reader.Consumed); + return value; + } + else + { + consumed = default; + examined = buffer.End; + return -1; + } + } + + public static bool TryWrite(Span buffer, long longToEncode, out int bytesWritten) + { + Debug.Assert(longToEncode >= 0); + Debug.Assert(longToEncode <= EightByteLimit); + + if (longToEncode < OneByteLimit) + { + if (buffer.Length != 0) + { + buffer[0] = (byte)longToEncode; + bytesWritten = 1; + return true; + } + } + else if (longToEncode < TwoByteLimit) + { + if (BinaryPrimitives.TryWriteUInt16BigEndian(buffer, (ushort)((uint)longToEncode | TwoByteLengthMask))) + { + bytesWritten = 2; + return true; + } + } + else if (longToEncode < FourByteLimit) + { + if (BinaryPrimitives.TryWriteUInt32BigEndian(buffer, (uint)longToEncode | FourByteLengthMask)) + { + bytesWritten = 4; + return true; + } + } + else // EightByteLimit + { + if (BinaryPrimitives.TryWriteUInt64BigEndian(buffer, (ulong)longToEncode | EightByteLengthMask)) + { + bytesWritten = 8; + return true; + } + } + + bytesWritten = 0; + return false; + } + + public static int WriteInteger(Span buffer, long longToEncode) + { + bool res = TryWrite(buffer, longToEncode, out int bytesWritten); + Debug.Assert(res == true); + return bytesWritten; + } + + public static int GetByteCount(long value) + { + Debug.Assert(value >= 0); + Debug.Assert(value <= EightByteLimit); + + return + value < OneByteLimit ? 1 : + value < TwoByteLimit ? 2 : + value < FourByteLimit ? 4 : + 8; // EightByteLimit + } + } +} diff --git a/src/Shared/runtime/Http3/Http3SettingType.cs b/src/Shared/runtime/Http3/Http3SettingType.cs new file mode 100644 index 0000000000..760446fa5c --- /dev/null +++ b/src/Shared/runtime/Http3/Http3SettingType.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Net.Http +{ + internal enum Http3SettingType : long + { + /// + /// SETTINGS_QPACK_MAX_TABLE_CAPACITY + /// The maximum dynamic table size. The default is 0. + /// https://tools.ietf.org/html/draft-ietf-quic-qpack-11#section-5 + /// + QPackMaxTableCapacity = 0x1, + + /// + /// SETTINGS_MAX_HEADER_LIST_SIZE + /// The maximum size of headers. The default is unlimited. + /// https://tools.ietf.org/html/draft-ietf-quic-http-24#section-7.2.4.1 + /// + MaxHeaderListSize = 0x6, + + /// + /// SETTINGS_QPACK_BLOCKED_STREAMS + /// The maximum number of request streams that can be blocked waiting for QPack instructions. The default is 0. + /// https://tools.ietf.org/html/draft-ietf-quic-qpack-11#section-5 + /// + QPackBlockedStreams = 0x7 + } +} diff --git a/src/Shared/runtime/Http3/Http3StreamType.cs b/src/Shared/runtime/Http3/Http3StreamType.cs new file mode 100644 index 0000000000..c3402f2550 --- /dev/null +++ b/src/Shared/runtime/Http3/Http3StreamType.cs @@ -0,0 +1,32 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Net.Http +{ + /// + /// Unidirectional stream types. + /// + /// + /// Bidirectional streams are always a request stream. + /// + internal enum Http3StreamType : long + { + /// + /// https://tools.ietf.org/html/draft-ietf-quic-http-24#section-6.2.1 + /// + Control = 0x00, + /// + /// https://tools.ietf.org/html/draft-ietf-quic-http-24#section-6.2.2 + /// + Push = 0x01, + /// + /// https://tools.ietf.org/html/draft-ietf-quic-qpack-11#section-4.2 + /// + QPackEncoder = 0x02, + /// + /// https://tools.ietf.org/html/draft-ietf-quic-qpack-11#section-4.2 + /// + QPackDecoder = 0x03 + } +} diff --git a/src/Shared/runtime/Http3/QPack/H3StaticTable.cs b/src/Shared/runtime/Http3/QPack/H3StaticTable.cs new file mode 100644 index 0000000000..13fc509cc6 --- /dev/null +++ b/src/Shared/runtime/Http3/QPack/H3StaticTable.cs @@ -0,0 +1,225 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Text; + +namespace System.Net.Http.QPack +{ + // TODO: make class static. + internal class H3StaticTable + { + private readonly Dictionary _statusIndex = new Dictionary + { + [103] = 24, + [200] = 25, + [304] = 26, + [404] = 27, + [503] = 28, + [100] = 63, + [204] = 64, + [206] = 65, + [302] = 66, + [400] = 67, + [403] = 68, + [421] = 69, + [425] = 70, + [500] = 71, + }; + + private readonly Dictionary _methodIndex = new Dictionary + { + // TODO connect is internal to system.net.http + [HttpMethod.Delete] = 16, + [HttpMethod.Get] = 17, + [HttpMethod.Head] = 18, + [HttpMethod.Options] = 19, + [HttpMethod.Post] = 20, + [HttpMethod.Put] = 21, + }; + + private H3StaticTable() + { + } + + public static H3StaticTable Instance { get; } = new H3StaticTable(); + + public int Count => _staticTable.Length; + + public HeaderField this[int index] => _staticTable[index]; + + // TODO: just use Dictionary directly to avoid interface dispatch. + public IReadOnlyDictionary StatusIndex => _statusIndex; + public IReadOnlyDictionary MethodIndex => _methodIndex; + + private readonly HeaderField[] _staticTable = new HeaderField[] + { + CreateHeaderField(":authority", ""), // 0 + CreateHeaderField(":path", "/"), // 1 + CreateHeaderField("age", "0"), // 2 + CreateHeaderField("content-disposition", ""), + CreateHeaderField("content-length", "0"), + CreateHeaderField("cookie", ""), + CreateHeaderField("date", ""), + CreateHeaderField("etag", ""), + CreateHeaderField("if-modified-since", ""), + CreateHeaderField("if-none-match", ""), + CreateHeaderField("last-modified", ""), // 10 + CreateHeaderField("link", ""), + CreateHeaderField("location", ""), + CreateHeaderField("referer", ""), + CreateHeaderField("set-cookie", ""), + CreateHeaderField(":method", "CONNECT"), + CreateHeaderField(":method", "DELETE"), + CreateHeaderField(":method", "GET"), + CreateHeaderField(":method", "HEAD"), + CreateHeaderField(":method", "OPTIONS"), + CreateHeaderField(":method", "POST"), // 20 + CreateHeaderField(":method", "PUT"), + CreateHeaderField(":scheme", "http"), + CreateHeaderField(":scheme", "https"), + CreateHeaderField(":status", "103"), + CreateHeaderField(":status", "200"), + CreateHeaderField(":status", "304"), + CreateHeaderField(":status", "404"), + CreateHeaderField(":status", "503"), + CreateHeaderField("accept", "*/*"), + CreateHeaderField("accept", "application/dns-message"), // 30 + CreateHeaderField("accept-encoding", "gzip, deflate, br"), + CreateHeaderField("accept-ranges", "bytes"), + CreateHeaderField("access-control-allow-headers", "cache-control"), + CreateHeaderField("access-control-allow-origin", "content-type"), + CreateHeaderField("access-control-allow-origin", "*"), + CreateHeaderField("cache-control", "max-age=0"), + CreateHeaderField("cache-control", "max-age=2592000"), + CreateHeaderField("cache-control", "max-age=604800"), + CreateHeaderField("cache-control", "no-cache"), + CreateHeaderField("cache-control", "no-store"), // 40 + CreateHeaderField("cache-control", "public, max-age=31536000"), + CreateHeaderField("content-encoding", "br"), + CreateHeaderField("content-encoding", "gzip"), + CreateHeaderField("content-type", "application/dns-message"), + CreateHeaderField("content-type", "application/javascript"), + CreateHeaderField("content-type", "application/json"), + CreateHeaderField("content-type", "application/x-www-form-urlencoded"), + CreateHeaderField("content-type", "image/gif"), + CreateHeaderField("content-type", "image/jpeg"), + CreateHeaderField("content-type", "image/png"), // 50 + CreateHeaderField("content-type", "text/css"), + CreateHeaderField("content-type", "text/html; charset=utf-8"), + CreateHeaderField("content-type", "text/plain"), + CreateHeaderField("content-type", "text/plain;charset=utf-8"), + CreateHeaderField("range", "bytes=0-"), + CreateHeaderField("strict-transport-security", "max-age=31536000"), + CreateHeaderField("strict-transport-security", "max-age=31536000;includesubdomains"), // TODO confirm spaces here don't matter? + CreateHeaderField("strict-transport-security", "max-age=31536000;includesubdomains; preload"), + CreateHeaderField("vary", "accept-encoding"), + CreateHeaderField("vary", "origin"), // 60 + CreateHeaderField("x-content-type-options", "nosniff"), + CreateHeaderField("x-xss-protection", "1; mode=block"), + CreateHeaderField(":status", "100"), + CreateHeaderField(":status", "204"), + CreateHeaderField(":status", "206"), + CreateHeaderField(":status", "302"), + CreateHeaderField(":status", "400"), + CreateHeaderField(":status", "403"), + CreateHeaderField(":status", "421"), + CreateHeaderField(":status", "425"), // 70 + CreateHeaderField(":status", "500"), + CreateHeaderField("accept-language", ""), + CreateHeaderField("access-control-allow-credentials", "FALSE"), + CreateHeaderField("access-control-allow-credentials", "TRUE"), + CreateHeaderField("access-control-allow-headers", "*"), + CreateHeaderField("access-control-allow-methods", "get"), + CreateHeaderField("access-control-allow-methods", "get, post, options"), + CreateHeaderField("access-control-allow-methods", "options"), + CreateHeaderField("access-control-expose-headers", "content-length"), + CreateHeaderField("access-control-request-headers", "content-type"), // 80 + CreateHeaderField("access-control-request-method", "get"), + CreateHeaderField("access-control-request-method", "post"), + CreateHeaderField("alt-svc", "clear"), + CreateHeaderField("authorization", ""), + CreateHeaderField("content-security-policy", "script-src 'none'; object-src 'none'; base-uri 'none'"), + CreateHeaderField("early-data", "1"), + CreateHeaderField("expect-ct", ""), + CreateHeaderField("forwarded", ""), + CreateHeaderField("if-range", ""), + CreateHeaderField("origin", ""), // 90 + CreateHeaderField("purpose", "prefetch"), + CreateHeaderField("server", ""), + CreateHeaderField("timing-allow-origin", "*"), + CreateHeaderField("upgrading-insecure-requests", "1"), + CreateHeaderField("user-agent", ""), + CreateHeaderField("x-forwarded-for", ""), + CreateHeaderField("x-frame-options", "deny"), + CreateHeaderField("x-frame-options", "sameorigin"), + }; + + private static HeaderField CreateHeaderField(string name, string value) + => new HeaderField(Encoding.ASCII.GetBytes(name), Encoding.ASCII.GetBytes(value)); + + public const int Authority = 0; + public const int PathSlash = 1; + public const int Age0 = 2; + public const int ContentDisposition = 3; + public const int ContentLength0 = 4; + public const int Cookie = 5; + public const int Date = 6; + public const int ETag = 7; + public const int IfModifiedSince = 8; + public const int IfNoneMatch = 9; + public const int LastModified = 10; + public const int Link = 11; + public const int Location = 12; + public const int Referer = 13; + public const int SetCookie = 14; + public const int MethodConnect = 15; + public const int MethodDelete = 16; + public const int MethodGet = 17; + public const int MethodHead = 18; + public const int MethodOptions = 19; + public const int MethodPost = 20; + public const int MethodPut = 21; + public const int SchemeHttps = 23; + public const int Status103 = 24; + public const int Status200 = 25; + public const int Status304 = 26; + public const int Status404 = 27; + public const int Status503 = 28; + public const int AcceptAny = 29; + public const int AcceptEncodingGzipDeflateBr = 31; + public const int AcceptRangesBytes = 32; + public const int AccessControlAllowHeadersCacheControl = 33; + public const int AccessControlAllowOriginAny = 35; + public const int CacheControlMaxAge0 = 36; + public const int ContentEncodingBr = 42; + public const int ContentTypeApplicationDnsMessage = 44; + public const int RangeBytes0ToAll = 55; + public const int StrictTransportSecurityMaxAge31536000 = 56; + public const int VaryAcceptEncoding = 59; + public const int XContentTypeOptionsNoSniff = 61; + public const int Status100 = 63; + public const int Status204 = 64; + public const int Status206 = 65; + public const int Status302 = 66; + public const int Status400 = 67; + public const int Status403 = 68; + public const int Status421 = 69; + public const int Status425 = 70; + public const int Status500 = 71; + public const int AcceptLanguage = 72; + public const int AccessControlAllowCredentials = 73; + public const int AccessControlAllowMethodsGet = 76; + public const int AccessControlExposeHeadersContentLength = 79; + public const int AltSvcClear = 83; + public const int Authorization = 84; + public const int ContentSecurityPolicyAllNone = 85; + public const int IfRange = 89; + public const int Origin = 90; + public const int Server = 92; + public const int UpgradeInsecureRequests1 = 94; + public const int UserAgent = 95; + public const int XFrameOptionsDeny = 97; + } +} diff --git a/src/Shared/runtime/Http3/QPack/HeaderField.cs b/src/Shared/runtime/Http3/QPack/HeaderField.cs new file mode 100644 index 0000000000..12594381bd --- /dev/null +++ b/src/Shared/runtime/Http3/QPack/HeaderField.cs @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Net.Http.QPack +{ + internal readonly struct HeaderField + { + public HeaderField(byte[] name, byte[] value) + { + Name = name; + Value = value; + } + + public byte[] Name { get; } + + public byte[] Value { get; } + + public int Length => Name.Length + Value.Length; + } +} diff --git a/src/Shared/runtime/Http3/QPack/QPackDecoder.cs b/src/Shared/runtime/Http3/QPack/QPackDecoder.cs new file mode 100644 index 0000000000..e3b85872ed --- /dev/null +++ b/src/Shared/runtime/Http3/QPack/QPackDecoder.cs @@ -0,0 +1,521 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable +using System.Buffers; +using System.Diagnostics; +using System.Net.Http.HPack; +using System.Numerics; + +#if KESTREL +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; +#endif + +namespace System.Net.Http.QPack +{ + internal class QPackDecoder : IDisposable + { + private enum State + { + RequiredInsertCount, + RequiredInsertCountContinue, + Base, + BaseContinue, + CompressedHeaders, + HeaderFieldIndex, + HeaderNameIndex, + HeaderNameLength, + HeaderNameLengthContinue, + HeaderName, + HeaderValueLength, + HeaderValueLengthContinue, + HeaderValue, + DynamicTableSizeUpdate, + PostBaseIndex, + LiteralHeaderFieldWithNameReference, + HeaderNameIndexPostBase + } + + //0 1 2 3 4 5 6 7 + //+---+---+---+---+---+---+---+---+ + //| Required Insert Count(8+) | + //+---+---------------------------+ + //| S | Delta Base(7+) | + //+---+---------------------------+ + //| Compressed Headers ... + private const int RequiredInsertCountPrefix = 8; + private const int BaseMask = 0x80; + private const int BasePrefix = 7; + //+-------------------------------+ + + //https://tools.ietf.org/html/draft-ietf-quic-qpack-09#section-4.5.2 + //0 1 2 3 4 5 6 7 + //+---+---+---+---+---+---+---+---+ + //| 1 | S | Index(6+) | + //+---+---+-----------------------+ + private const byte IndexedHeaderStaticMask = 0x40; + private const byte IndexedHeaderStaticRepresentation = 0x40; + private const byte IndexedHeaderFieldPrefixMask = 0x3F; + private const int IndexedHeaderFieldPrefix = 6; + + //0 1 2 3 4 5 6 7 + //+---+---+---+---+---+---+---+---+ + //| 0 | 0 | 0 | 1 | Index(4+) | + //+---+---+---+---+---------------+ + private const byte PostBaseIndexMask = 0xF0; + private const int PostBaseIndexPrefix = 4; + + //0 1 2 3 4 5 6 7 + //+---+---+---+---+---+---+---+---+ + //| 0 | 1 | N | S |Name Index(4+)| + //+---+---+---+---+---------------+ + //| H | Value Length(7+) | + //+---+---------------------------+ + //| Value String(Length bytes) | + //+-------------------------------+ + private const byte LiteralHeaderFieldStaticMask = 0x10; + private const byte LiteralHeaderFieldPrefixMask = 0x0F; + private const int LiteralHeaderFieldPrefix = 4; + + //0 1 2 3 4 5 6 7 + //+---+---+---+---+---+---+---+---+ + //| 0 | 0 | 0 | 0 | N |NameIdx(3+)| + //+---+---+---+---+---+-----------+ + //| H | Value Length(7+) | + //+---+---------------------------+ + //| Value String(Length bytes) | + //+-------------------------------+ + private const byte LiteralHeaderFieldPostBasePrefixMask = 0x07; + private const int LiteralHeaderFieldPostBasePrefix = 3; + + //0 1 2 3 4 5 6 7 + //+---+---+---+---+---+---+---+---+ + //| 0 | 0 | 1 | N | H |NameLen(3+)| + //+---+---+---+---+---+-----------+ + //| Name String(Length bytes) | + //+---+---------------------------+ + //| H | Value Length(7+) | + //+---+---------------------------+ + //| Value String(Length bytes) | + //+-------------------------------+ + private const byte LiteralHeaderFieldWithoutNameReferenceHuffmanMask = 0x08; + private const byte LiteralHeaderFieldWithoutNameReferencePrefixMask = 0x07; + private const int LiteralHeaderFieldWithoutNameReferencePrefix = 3; + + private const int StringLengthPrefix = 7; + private const byte HuffmanMask = 0x80; + + private const int DefaultStringBufferSize = 64; + + private readonly int _maxHeadersLength; + private State _state = State.RequiredInsertCount; + + private byte[] _stringOctets; + private byte[] _headerNameOctets; + private byte[] _headerValueOctets; + + // s is used for whatever s is in each field. This has multiple definition + private bool _huffman; + private int? _index; + + private byte[]? _headerName; + private int _headerNameLength; + private int _headerValueLength; + private int _stringLength; + private int _stringIndex; + private readonly IntegerDecoder _integerDecoder = new IntegerDecoder(); + + private static ArrayPool Pool => ArrayPool.Shared; + + private static void ReturnAndGetNewPooledArray(ref byte[] buffer, int newSize) + { + byte[] old = buffer; + buffer = null!; + + Pool.Return(old, clearArray: true); + buffer = Pool.Rent(newSize); + } + + public QPackDecoder(int maxHeadersLength) + { + _maxHeadersLength = maxHeadersLength; + + // TODO: make allocation lazy? with static entries it's possible no buffers will be needed. + _stringOctets = Pool.Rent(DefaultStringBufferSize); + _headerNameOctets = Pool.Rent(DefaultStringBufferSize); + _headerValueOctets = Pool.Rent(DefaultStringBufferSize); + } + + public void Dispose() + { + if (_stringOctets != null) + { + Pool.Return(_stringOctets, true); + _stringOctets = null!; + } + + if (_headerNameOctets != null) + { + Pool.Return(_headerNameOctets, true); + _headerNameOctets = null!; + } + + if (_headerValueOctets != null) + { + Pool.Return(_headerValueOctets, true); + _headerValueOctets = null!; + } + } + + public void Decode(in ReadOnlySequence headerBlock, IHttpHeadersHandler handler) + { + foreach (ReadOnlyMemory segment in headerBlock) + { + Decode(segment.Span, handler); + } + } + + public void Decode(ReadOnlySpan headerBlock, IHttpHeadersHandler handler) + { + foreach (byte b in headerBlock) + { + OnByte(b, handler); + } + } + + private void OnByte(byte b, IHttpHeadersHandler handler) + { + int intResult; + int prefixInt; + switch (_state) + { + case State.RequiredInsertCount: + if (_integerDecoder.BeginTryDecode(b, RequiredInsertCountPrefix, out intResult)) + { + OnRequiredInsertCount(intResult); + } + else + { + _state = State.RequiredInsertCountContinue; + } + break; + case State.RequiredInsertCountContinue: + if (_integerDecoder.TryDecode(b, out intResult)) + { + OnRequiredInsertCount(intResult); + } + break; + case State.Base: + prefixInt = ~BaseMask & b; + + if (_integerDecoder.BeginTryDecode(b, BasePrefix, out intResult)) + { + OnBase(intResult); + } + else + { + _state = State.BaseContinue; + } + break; + case State.BaseContinue: + if (_integerDecoder.TryDecode(b, out intResult)) + { + OnBase(intResult); + } + break; + case State.CompressedHeaders: + switch (BitOperations.LeadingZeroCount(b) - 24) // byte 'b' is extended to uint, so will have 24 extra 0s. + { + case 0: // Indexed Header Field + prefixInt = IndexedHeaderFieldPrefixMask & b; + + bool useStaticTable = (b & IndexedHeaderStaticMask) == IndexedHeaderStaticRepresentation; + + if (!useStaticTable) + { + ThrowDynamicTableNotSupported(); + } + + if (_integerDecoder.BeginTryDecode((byte)prefixInt, IndexedHeaderFieldPrefix, out intResult)) + { + OnIndexedHeaderField(intResult, handler); + } + else + { + _state = State.HeaderFieldIndex; + } + break; + case 1: // Literal Header Field With Name Reference + useStaticTable = (LiteralHeaderFieldStaticMask & b) == LiteralHeaderFieldStaticMask; + + if (!useStaticTable) + { + ThrowDynamicTableNotSupported(); + } + + prefixInt = b & LiteralHeaderFieldPrefixMask; + if (_integerDecoder.BeginTryDecode((byte)prefixInt, LiteralHeaderFieldPrefix, out intResult)) + { + OnIndexedHeaderName(intResult); + } + else + { + _state = State.HeaderNameIndex; + } + break; + case 2: // Literal Header Field Without Name Reference + _huffman = (b & LiteralHeaderFieldWithoutNameReferenceHuffmanMask) != 0; + prefixInt = b & LiteralHeaderFieldWithoutNameReferencePrefixMask; + + if (_integerDecoder.BeginTryDecode((byte)prefixInt, LiteralHeaderFieldWithoutNameReferencePrefix, out intResult)) + { + if (intResult == 0) + { + throw new QPackDecodingException(SR.Format(SR.net_http_invalid_header_name, "")); + } + OnStringLength(intResult, State.HeaderName); + } + else + { + _state = State.HeaderNameLength; + } + break; + case 3: // Indexed Header Field With Post-Base Index + prefixInt = ~PostBaseIndexMask & b; + if (_integerDecoder.BeginTryDecode((byte)prefixInt, PostBaseIndexPrefix, out intResult)) + { + OnPostBaseIndex(intResult, handler); + } + else + { + _state = State.PostBaseIndex; + } + break; + default: // Literal Header Field With Post-Base Name Reference (at least 4 zeroes, maybe more) + prefixInt = b & LiteralHeaderFieldPostBasePrefixMask; + if (_integerDecoder.BeginTryDecode((byte)prefixInt, LiteralHeaderFieldPostBasePrefix, out intResult)) + { + OnIndexedHeaderNamePostBase(intResult); + } + else + { + _state = State.HeaderNameIndexPostBase; + } + break; + } + break; + case State.HeaderNameLength: + if (_integerDecoder.TryDecode(b, out intResult)) + { + if (intResult == 0) + { + throw new QPackDecodingException(SR.Format(SR.net_http_invalid_header_name, "")); + } + OnStringLength(intResult, nextState: State.HeaderName); + } + break; + case State.HeaderName: + _stringOctets[_stringIndex++] = b; + + if (_stringIndex == _stringLength) + { + OnString(nextState: State.HeaderValueLength); + } + + break; + case State.HeaderNameIndex: + if (_integerDecoder.TryDecode(b, out intResult)) + { + OnIndexedHeaderName(intResult); + } + break; + case State.HeaderNameIndexPostBase: + if (_integerDecoder.TryDecode(b, out intResult)) + { + OnIndexedHeaderNamePostBase(intResult); + } + break; + case State.HeaderValueLength: + _huffman = (b & HuffmanMask) != 0; + + // TODO confirm this. + if (_integerDecoder.BeginTryDecode((byte)(b & ~HuffmanMask), StringLengthPrefix, out intResult)) + { + OnStringLength(intResult, nextState: State.HeaderValue); + if (intResult == 0) + { + ProcessHeaderValue(handler); + } + } + else + { + _state = State.HeaderValueLengthContinue; + } + break; + case State.HeaderValueLengthContinue: + if (_integerDecoder.TryDecode(b, out intResult)) + { + OnStringLength(intResult, nextState: State.HeaderValue); + if (intResult == 0) + { + ProcessHeaderValue(handler); + } + } + break; + case State.HeaderValue: + _stringOctets[_stringIndex++] = b; + if (_stringIndex == _stringLength) + { + ProcessHeaderValue(handler); + } + break; + case State.HeaderFieldIndex: + if (_integerDecoder.TryDecode(b, out intResult)) + { + OnIndexedHeaderField(intResult, handler); + } + break; + case State.PostBaseIndex: + if (_integerDecoder.TryDecode(b, out intResult)) + { + OnPostBaseIndex(intResult, handler); + } + break; + case State.LiteralHeaderFieldWithNameReference: + break; + } + } + + private void OnStringLength(int length, State nextState) + { + if (length > _stringOctets.Length) + { + if (length > _maxHeadersLength) + { + throw new QPackDecodingException(SR.Format(SR.net_http_headers_exceeded_length, _maxHeadersLength)); + } + + ReturnAndGetNewPooledArray(ref _stringOctets, length); + } + + _stringLength = length; + _stringIndex = 0; + _state = nextState; + } + + private void ProcessHeaderValue(IHttpHeadersHandler handler) + { + OnString(nextState: State.CompressedHeaders); + + Span headerNameSpan; + Span headerValueSpan = _headerValueOctets.AsSpan(0, _headerValueLength); + + if (_index is int index) + { + Debug.Assert(index >= 0 && index <= H3StaticTable.Instance.Count, $"The index should be a valid static index here. {nameof(QPackDecoder)} should have previously thrown if it read a dynamic index."); + handler.OnStaticIndexedHeader(index, headerValueSpan); + _index = null; + + return; + } + else + { + headerNameSpan = _headerNameOctets.AsSpan(0, _headerNameLength); + } + + handler.OnHeader(headerNameSpan, headerValueSpan); + } + + private void OnString(State nextState) + { + int Decode(ref byte[] dst) + { + if (_huffman) + { + return Huffman.Decode(new ReadOnlySpan(_stringOctets, 0, _stringLength), ref dst); + } + else + { + if (dst.Length < _stringLength) + { + ReturnAndGetNewPooledArray(ref dst, _stringLength); + } + + Buffer.BlockCopy(_stringOctets, 0, dst, 0, _stringLength); + return _stringLength; + } + } + + try + { + if (_state == State.HeaderName) + { + _headerNameLength = Decode(ref _headerNameOctets); + _headerName = _headerNameOctets; + } + else + { + _headerValueLength = Decode(ref _headerValueOctets); + } + } + catch (HuffmanDecodingException ex) + { + throw new QPackDecodingException(SR.net_http_hpack_huffman_decode_failed, ex); + } + + _state = nextState; + } + + + private void OnIndexedHeaderName(int index) + { + _index = index; + _state = State.HeaderValueLength; + } + + private void OnIndexedHeaderNamePostBase(int index) + { + ThrowDynamicTableNotSupported(); + // TODO update with postbase index + // _index = index; + // _state = State.HeaderValueLength; + } + + private void OnPostBaseIndex(int intResult, IHttpHeadersHandler handler) + { + ThrowDynamicTableNotSupported(); + // TODO + // _state = State.CompressedHeaders; + } + + private void OnBase(int deltaBase) + { + if (deltaBase != 0) + { + ThrowDynamicTableNotSupported(); + } + _state = State.CompressedHeaders; + } + + private void OnRequiredInsertCount(int requiredInsertCount) + { + if (requiredInsertCount != 0) + { + ThrowDynamicTableNotSupported(); + } + _state = State.Base; + } + + private void OnIndexedHeaderField(int index, IHttpHeadersHandler handler) + { + handler.OnStaticIndexedHeader(index); + _state = State.CompressedHeaders; + } + + private static void ThrowDynamicTableNotSupported() + { + throw new QPackDecodingException(SR.net_http_qpack_no_dynamic_table); + } + } +} diff --git a/src/Shared/runtime/Http3/QPack/QPackDecodingException.cs b/src/Shared/runtime/Http3/QPack/QPackDecodingException.cs new file mode 100644 index 0000000000..bbdba5d773 --- /dev/null +++ b/src/Shared/runtime/Http3/QPack/QPackDecodingException.cs @@ -0,0 +1,28 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Runtime.Serialization; + +namespace System.Net.Http.QPack +{ + [Serializable] + internal sealed class QPackDecodingException : Exception + { + public QPackDecodingException() + { + } + + public QPackDecodingException(string message) : base(message) + { + } + + public QPackDecodingException(string message, Exception innerException) : base(message, innerException) + { + } + + private QPackDecodingException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + } +} diff --git a/src/Shared/runtime/Http3/QPack/QPackEncoder.cs b/src/Shared/runtime/Http3/QPack/QPackEncoder.cs new file mode 100644 index 0000000000..c956095910 --- /dev/null +++ b/src/Shared/runtime/Http3/QPack/QPackEncoder.cs @@ -0,0 +1,433 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable +using System.Collections.Generic; +using System.Diagnostics; +using System.Net.Http.HPack; + +namespace System.Net.Http.QPack +{ + internal class QPackEncoder + { + private IEnumerator>? _enumerator; + + // https://tools.ietf.org/html/draft-ietf-quic-qpack-11#section-4.5.2 + // 0 1 2 3 4 5 6 7 + // +---+---+---+---+---+---+---+---+ + // | 1 | T | Index (6+) | + // +---+---+-----------------------+ + // + // Note for this method's implementation of above: + // - T is constant 1 here, indicating a static table reference. + public static bool EncodeStaticIndexedHeaderField(int index, Span destination, out int bytesWritten) + { + if (!destination.IsEmpty) + { + destination[0] = 0b11000000; + return IntegerEncoder.Encode(index, 6, destination, out bytesWritten); + } + else + { + bytesWritten = 0; + return false; + } + } + + public static byte[] EncodeStaticIndexedHeaderFieldToArray(int index) + { + Span buffer = stackalloc byte[IntegerEncoder.MaxInt32EncodedLength]; + + bool res = EncodeStaticIndexedHeaderField(index, buffer, out int bytesWritten); + Debug.Assert(res == true); + + return buffer.Slice(0, bytesWritten).ToArray(); + } + + // https://tools.ietf.org/html/draft-ietf-quic-qpack-11#section-4.5.4 + // 0 1 2 3 4 5 6 7 + // +---+---+---+---+---+---+---+---+ + // | 0 | 1 | N | T |Name Index (4+)| + // +---+---+---+---+---------------+ + // | H | Value Length (7+) | + // +---+---------------------------+ + // | Value String (Length bytes) | + // +-------------------------------+ + // + // Note for this method's implementation of above: + // - N is constant 0 here, indicating intermediates (proxies) can compress the header when fordwarding. + // - T is constant 1 here, indicating a static table reference. + // - H is constant 0 here, as we do not yet perform Huffman coding. + public static bool EncodeLiteralHeaderFieldWithStaticNameReference(int index, string value, Span destination, out int bytesWritten) + { + // Requires at least two bytes (one for name reference header, one for value length) + if (destination.Length >= 2) + { + destination[0] = 0b01010000; + if (IntegerEncoder.Encode(index, 4, destination, out int headerBytesWritten)) + { + destination = destination.Slice(headerBytesWritten); + + if (EncodeValueString(value, destination, out int valueBytesWritten)) + { + bytesWritten = headerBytesWritten + valueBytesWritten; + return true; + } + } + } + + bytesWritten = 0; + return false; + } + + /// + /// Encodes just the name part of a Literal Header Field With Static Name Reference. Must call after to encode the header's value. + /// + public static byte[] EncodeLiteralHeaderFieldWithStaticNameReferenceToArray(int index) + { + Span temp = stackalloc byte[IntegerEncoder.MaxInt32EncodedLength]; + + temp[0] = 0b01110000; + bool res = IntegerEncoder.Encode(index, 4, temp, out int headerBytesWritten); + Debug.Assert(res == true); + + return temp.Slice(0, headerBytesWritten).ToArray(); + } + + public static byte[] EncodeLiteralHeaderFieldWithStaticNameReferenceToArray(int index, string value) + { + Span temp = value.Length < 256 ? stackalloc byte[256 + IntegerEncoder.MaxInt32EncodedLength * 2] : new byte[value.Length + IntegerEncoder.MaxInt32EncodedLength * 2]; + bool res = EncodeLiteralHeaderFieldWithStaticNameReference(index, value, temp, out int bytesWritten); + Debug.Assert(res == true); + return temp.Slice(0, bytesWritten).ToArray(); + } + + // https://tools.ietf.org/html/draft-ietf-quic-qpack-11#section-4.5.6 + // 0 1 2 3 4 5 6 7 + // +---+---+---+---+---+---+---+---+ + // | 0 | 0 | 1 | N | H |NameLen(3+)| + // +---+---+---+---+---+-----------+ + // | Name String (Length bytes) | + // +---+---------------------------+ + // | H | Value Length (7+) | + // +---+---------------------------+ + // | Value String (Length bytes) | + // +-------------------------------+ + // + // Note for this method's implementation of above: + // - N is constant 0 here, indicating intermediates (proxies) can compress the header when fordwarding. + // - H is constant 0 here, as we do not yet perform Huffman coding. + public static bool EncodeLiteralHeaderFieldWithoutNameReference(string name, string value, Span destination, out int bytesWritten) + { + if (EncodeNameString(name, destination, out int nameLength) && EncodeValueString(value, destination.Slice(nameLength), out int valueLength)) + { + bytesWritten = nameLength + valueLength; + return true; + } + else + { + bytesWritten = 0; + return false; + } + } + + /// + /// Encodes a Literal Header Field Without Name Reference, building the value by concatenating a collection of strings with separators. + /// + public static bool EncodeLiteralHeaderFieldWithoutNameReference(string name, ReadOnlySpan values, string valueSeparator, Span destination, out int bytesWritten) + { + if (EncodeNameString(name, destination, out int nameLength) && EncodeValueString(values, valueSeparator, destination.Slice(nameLength), out int valueLength)) + { + bytesWritten = nameLength + valueLength; + return true; + } + + bytesWritten = 0; + return false; + } + + /// + /// Encodes just the value part of a Literawl Header Field Without Static Name Reference. Must call after to encode the header's value. + /// + public static byte[] EncodeLiteralHeaderFieldWithoutNameReferenceToArray(string name) + { + Span temp = name.Length < 256 ? stackalloc byte[256 + IntegerEncoder.MaxInt32EncodedLength] : new byte[name.Length + IntegerEncoder.MaxInt32EncodedLength]; + + bool res = EncodeNameString(name, temp, out int nameLength); + Debug.Assert(res == true); + + return temp.Slice(0, nameLength).ToArray(); + } + + public static byte[] EncodeLiteralHeaderFieldWithoutNameReferenceToArray(string name, string value) + { + Span temp = (name.Length + value.Length) < 256 ? stackalloc byte[256 + IntegerEncoder.MaxInt32EncodedLength * 2] : new byte[name.Length + value.Length + IntegerEncoder.MaxInt32EncodedLength * 2]; + + bool res = EncodeLiteralHeaderFieldWithoutNameReference(name, value, temp, out int bytesWritten); + Debug.Assert(res == true); + + return temp.Slice(0, bytesWritten).ToArray(); + } + + private static bool EncodeValueString(string s, Span buffer, out int length) + { + if (buffer.Length != 0) + { + buffer[0] = 0; + if (IntegerEncoder.Encode(s.Length, 7, buffer, out int nameLength)) + { + buffer = buffer.Slice(nameLength); + if (buffer.Length >= s.Length) + { + EncodeValueStringPart(s, buffer); + + length = nameLength + s.Length; + return true; + } + } + } + + length = 0; + return false; + } + + /// + /// Encodes a value by concatenating a collection of strings, separated by a separator string. + /// + public static bool EncodeValueString(ReadOnlySpan values, string? separator, Span buffer, out int length) + { + if (values.Length == 1) + { + return EncodeValueString(values[0], buffer, out length); + } + + if (values.Length == 0) + { + // TODO: this will be called with a string array from HttpHeaderCollection. Can we ever get a 0-length array from that? Assert if not. + return EncodeValueString(string.Empty, buffer, out length); + } + + if (buffer.Length > 0) + { + Debug.Assert(separator != null); + int valueLength = separator.Length * (values.Length - 1); + for (int i = 0; i < values.Length; ++i) + { + valueLength += values[i].Length; + } + + buffer[0] = 0; + if (IntegerEncoder.Encode(valueLength, 7, buffer, out int nameLength)) + { + buffer = buffer.Slice(nameLength); + if (buffer.Length >= valueLength) + { + string value = values[0]; + EncodeValueStringPart(value, buffer); + buffer = buffer.Slice(value.Length); + + for (int i = 1; i < values.Length; ++i) + { + EncodeValueStringPart(separator, buffer); + buffer = buffer.Slice(separator.Length); + + value = values[i]; + EncodeValueStringPart(value, buffer); + buffer = buffer.Slice(value.Length); + } + + length = nameLength + valueLength; + return true; + } + } + } + + length = 0; + return false; + } + + private static void EncodeValueStringPart(string s, Span buffer) + { + Debug.Assert(buffer.Length >= s.Length); + + for (int i = 0; i < s.Length; ++i) + { + char ch = s[i]; + + if (ch > 127) + { + throw new QPackEncodingException("ASCII header value."); + } + + buffer[i] = (byte)ch; + } + } + + private static bool EncodeNameString(string s, Span buffer, out int length) + { + const int toLowerMask = 0x20; + + if (buffer.Length != 0) + { + buffer[0] = 0x30; + + if (IntegerEncoder.Encode(s.Length, 3, buffer, out int nameLength)) + { + buffer = buffer.Slice(nameLength); + + if (buffer.Length >= s.Length) + { + for (int i = 0; i < s.Length; ++i) + { + int ch = s[i]; + Debug.Assert(ch <= 127, "HttpHeaders prevents adding non-ASCII header names."); + + if ((uint)(ch - 'A') <= 'Z' - 'A') + { + ch |= toLowerMask; + } + + buffer[i] = (byte)ch; + } + + length = nameLength + s.Length; + return true; + } + } + } + + length = 0; + return false; + } + + /* + * 0 1 2 3 4 5 6 7 + +---+---+---+---+---+---+---+---+ + | Required Insert Count (8+) | + +---+---------------------------+ + | S | Delta Base (7+) | + +---+---------------------------+ + | Compressed Headers ... + +-------------------------------+ + * + */ + private static bool EncodeHeaderBlockPrefix(Span destination, out int bytesWritten) + { + int length; + bytesWritten = 0; + // Required insert count as first int + if (!IntegerEncoder.Encode(0, 8, destination, out length)) + { + return false; + } + + bytesWritten += length; + destination = destination.Slice(length); + + // Delta base + if (destination.IsEmpty) + { + return false; + } + + destination[0] = 0x00; + if (!IntegerEncoder.Encode(0, 7, destination, out length)) + { + return false; + } + + bytesWritten += length; + + return true; + } + + public bool BeginEncode(IEnumerable> headers, Span buffer, out int length) + { + _enumerator = headers.GetEnumerator(); + + bool hasValue = _enumerator.MoveNext(); + Debug.Assert(hasValue == true); + + buffer[0] = 0; + buffer[1] = 0; + + bool doneEncode = Encode(buffer.Slice(2), out length); + + // Add two for the first two bytes. + length += 2; + return doneEncode; + } + + public bool BeginEncode(int statusCode, IEnumerable> headers, Span buffer, out int length) + { + _enumerator = headers.GetEnumerator(); + + bool hasValue = _enumerator.MoveNext(); + Debug.Assert(hasValue == true); + + // https://quicwg.org/base-drafts/draft-ietf-quic-qpack.html#header-prefix + buffer[0] = 0; + buffer[1] = 0; + + int statusCodeLength = EncodeStatusCode(statusCode, buffer.Slice(2)); + bool done = Encode(buffer.Slice(statusCodeLength + 2), throwIfNoneEncoded: false, out int headersLength); + length = statusCodeLength + headersLength + 2; + + return done; + } + + public bool Encode(Span buffer, out int length) + { + return Encode(buffer, throwIfNoneEncoded: true, out length); + } + + private bool Encode(Span buffer, bool throwIfNoneEncoded, out int length) + { + length = 0; + + do + { + if (!EncodeLiteralHeaderFieldWithoutNameReference(_enumerator!.Current.Key, _enumerator.Current.Value, buffer.Slice(length), out int headerLength)) + { + if (length == 0 && throwIfNoneEncoded) + { + throw new QPackEncodingException("TODO sync with corefx" /* CoreStrings.HPackErrorNotEnoughBuffer */); + } + return false; + } + + length += headerLength; + } while (_enumerator.MoveNext()); + + return true; + } + + // TODO: use H3StaticTable? + private int EncodeStatusCode(int statusCode, Span buffer) + { + switch (statusCode) + { + case 200: + case 204: + case 206: + case 304: + case 400: + case 404: + case 500: + // TODO this isn't safe, some index can be larger than 64. Encoded here! + buffer[0] = (byte)(0xC0 | H3StaticTable.Instance.StatusIndex[statusCode]); + return 1; + default: + // Send as Literal Header Field Without Indexing - Indexed Name + buffer[0] = 0x08; + + ReadOnlySpan statusBytes = StatusCodes.ToStatusBytes(statusCode); + buffer[1] = (byte)statusBytes.Length; + statusBytes.CopyTo(buffer.Slice(2)); + + return 2 + statusBytes.Length; + } + } + } +} diff --git a/src/Shared/runtime/Http3/QPack/QPackEncodingException.cs b/src/Shared/runtime/Http3/QPack/QPackEncodingException.cs new file mode 100644 index 0000000000..9c5907a3a9 --- /dev/null +++ b/src/Shared/runtime/Http3/QPack/QPackEncodingException.cs @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Runtime.Serialization; + +namespace System.Net.Http.QPack +{ + [Serializable] + internal sealed class QPackEncodingException : Exception + { + public QPackEncodingException(string message) + : base(message) + { + } + public QPackEncodingException(string message, Exception innerException) + : base(message, innerException) + { + } + + private QPackEncodingException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + } +} diff --git a/src/Shared/runtime/IHttpHeadersHandler.cs b/src/Shared/runtime/IHttpHeadersHandler.cs new file mode 100644 index 0000000000..11a7e3473e --- /dev/null +++ b/src/Shared/runtime/IHttpHeadersHandler.cs @@ -0,0 +1,26 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// Don't ever change this unless we are explicitly trying to remove IHttpHeadersHandler as public API. +#if KESTREL +using System; + +namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http +#else +namespace System.Net.Http +#endif +{ +#if KESTREL + public +#else + internal +#endif + interface IHttpHeadersHandler + { + void OnStaticIndexedHeader(int index); + void OnStaticIndexedHeader(int index, ReadOnlySpan value); + void OnHeader(ReadOnlySpan name, ReadOnlySpan value); + void OnHeadersComplete(bool endStream); + } +} diff --git a/src/Shared/runtime/NetEventSource.Common.cs b/src/Shared/runtime/NetEventSource.Common.cs new file mode 100644 index 0000000000..46cd2ee685 --- /dev/null +++ b/src/Shared/runtime/NetEventSource.Common.cs @@ -0,0 +1,738 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#if DEBUG +// Uncomment to enable runtime checks to help validate that NetEventSource isn't being misused +// in a way that will cause performance problems, e.g. unexpected boxing of value types. +//#define DEBUG_NETEVENTSOURCE_MISUSE +#endif + +#nullable enable +using System.Collections; +using System.Diagnostics; +using System.Diagnostics.Tracing; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +#if NET46 +using System.Security; +#endif + +#pragma warning disable CA1823 // not all IDs are used by all partial providers + +namespace System.Net +{ + // Implementation: + // This partial file is meant to be consumed into each System.Net.* assembly that needs to log. Each such assembly also provides + // its own NetEventSource partial class that adds an appropriate [EventSource] attribute, giving it a unique name for that assembly. + // Those partials can then also add additional events if needed, starting numbering from the NextAvailableEventId defined by this partial. + + // Usage: + // - Operations that may allocate (e.g. boxing a value type, using string interpolation, etc.) or that may have computations + // at call sites should guard access like: + // if (NetEventSource.IsEnabled) NetEventSource.Enter(this, refArg1, valueTypeArg2); // entering an instance method with a value type arg + // if (NetEventSource.IsEnabled) NetEventSource.Info(null, $"Found certificate: {cert}"); // info logging with a formattable string + // - Operations that have zero allocations / measurable computations at call sites can use a simpler pattern, calling methods like: + // NetEventSource.Enter(this); // entering an instance method + // NetEventSource.Info(this, "literal string"); // arbitrary message with a literal string + // NetEventSource.Enter(this, refArg1, regArg2); // entering an instance method with two reference type arguments + // NetEventSource.Enter(null); // entering a static method + // NetEventSource.Enter(null, refArg1); // entering a static method with one reference type argument + // Debug.Asserts inside the logging methods will help to flag some misuse if the DEBUG_NETEVENTSOURCE_MISUSE compilation constant is defined. + // However, because it can be difficult by observation to understand all of the costs involved, guarding can be done everywhere. + // - NetEventSource.Fail calls typically do not need to be prefixed with an IsEnabled check, even if they allocate, as FailMessage + // should only be used in cases similar to Debug.Fail, where they are not expected to happen in retail builds, and thus extra costs + // don't matter. + // - Messages can be strings, formattable strings, or any other object. Objects (including those used in formattable strings) have special + // formatting applied, controlled by the Format method. Partial specializations can also override this formatting by implementing a partial + // method that takes an object and optionally provides a string representation of it, in case a particular library wants to customize further. + + /// Provides logging facilities for System.Net libraries. +#if NET46 + [SecuritySafeCritical] +#endif + internal sealed partial class NetEventSource : EventSource + { + /// The single event source instance to use for all logging. + public static readonly NetEventSource Log = new NetEventSource(); + + #region Metadata + public class Keywords + { + public const EventKeywords Default = (EventKeywords)0x0001; + public const EventKeywords Debug = (EventKeywords)0x0002; + public const EventKeywords EnterExit = (EventKeywords)0x0004; + } + + private const string MissingMember = "(?)"; + private const string NullInstance = "(null)"; + private const string StaticMethodObject = "(static)"; + private const string NoParameters = ""; + private const int MaxDumpSize = 1024; + + private const int EnterEventId = 1; + private const int ExitEventId = 2; + private const int AssociateEventId = 3; + private const int InfoEventId = 4; + private const int ErrorEventId = 5; + private const int CriticalFailureEventId = 6; + private const int DumpArrayEventId = 7; + + // These events are implemented in NetEventSource.Security.cs. + // Define the ids here so that projects that include NetEventSource.Security.cs will not have conflicts. + private const int EnumerateSecurityPackagesId = 8; + private const int SspiPackageNotFoundId = 9; + private const int AcquireDefaultCredentialId = 10; + private const int AcquireCredentialsHandleId = 11; + private const int InitializeSecurityContextId = 12; + private const int SecurityContextInputBufferId = 13; + private const int SecurityContextInputBuffersId = 14; + private const int AcceptSecuritContextId = 15; + private const int OperationReturnedSomethingId = 16; + + private const int NextAvailableEventId = 17; // Update this value whenever new events are added. Derived types should base all events off of this to avoid conflicts. + #endregion + + #region Events + #region Enter + /// Logs entrance to a method. + /// `this`, or another object that serves to provide context for the operation. + /// A description of the entrance, including any arguments to the call. + /// The calling member. + [NonEvent] + public static void Enter(object? thisOrContextObject, FormattableString? formattableString = null, [CallerMemberName] string? memberName = null) + { + DebugValidateArg(thisOrContextObject); + DebugValidateArg(formattableString); + if (IsEnabled) Log.Enter(IdOf(thisOrContextObject), memberName, formattableString != null ? Format(formattableString) : NoParameters); + } + + /// Logs entrance to a method. + /// `this`, or another object that serves to provide context for the operation. + /// The object to log. + /// The calling member. + [NonEvent] + public static void Enter(object? thisOrContextObject, object arg0, [CallerMemberName] string? memberName = null) + { + DebugValidateArg(thisOrContextObject); + DebugValidateArg(arg0); + if (IsEnabled) Log.Enter(IdOf(thisOrContextObject), memberName, $"({Format(arg0)})"); + } + + /// Logs entrance to a method. + /// `this`, or another object that serves to provide context for the operation. + /// The first object to log. + /// The second object to log. + /// The calling member. + [NonEvent] + public static void Enter(object? thisOrContextObject, object arg0, object arg1, [CallerMemberName] string? memberName = null) + { + DebugValidateArg(thisOrContextObject); + DebugValidateArg(arg0); + DebugValidateArg(arg1); + if (IsEnabled) Log.Enter(IdOf(thisOrContextObject), memberName, $"({Format(arg0)}, {Format(arg1)})"); + } + + /// Logs entrance to a method. + /// `this`, or another object that serves to provide context for the operation. + /// The first object to log. + /// The second object to log. + /// The third object to log. + /// The calling member. + [NonEvent] + public static void Enter(object? thisOrContextObject, object arg0, object arg1, object arg2, [CallerMemberName] string? memberName = null) + { + DebugValidateArg(thisOrContextObject); + DebugValidateArg(arg0); + DebugValidateArg(arg1); + DebugValidateArg(arg2); + if (IsEnabled) Log.Enter(IdOf(thisOrContextObject), memberName, $"({Format(arg0)}, {Format(arg1)}, {Format(arg2)})"); + } + + [Event(EnterEventId, Level = EventLevel.Informational, Keywords = Keywords.EnterExit)] + private void Enter(string thisOrContextObject, string? memberName, string parameters) => + WriteEvent(EnterEventId, thisOrContextObject, memberName ?? MissingMember, parameters); + #endregion + + #region Exit + /// Logs exit from a method. + /// `this`, or another object that serves to provide context for the operation. + /// A description of the exit operation, including any return values. + /// The calling member. + [NonEvent] + public static void Exit(object? thisOrContextObject, FormattableString? formattableString = null, [CallerMemberName] string? memberName = null) + { + DebugValidateArg(thisOrContextObject); + DebugValidateArg(formattableString); + if (IsEnabled) Log.Exit(IdOf(thisOrContextObject), memberName, formattableString != null ? Format(formattableString) : NoParameters); + } + + /// Logs exit from a method. + /// `this`, or another object that serves to provide context for the operation. + /// A return value from the member. + /// The calling member. + [NonEvent] + public static void Exit(object? thisOrContextObject, object arg0, [CallerMemberName] string? memberName = null) + { + DebugValidateArg(thisOrContextObject); + DebugValidateArg(arg0); + if (IsEnabled) Log.Exit(IdOf(thisOrContextObject), memberName, Format(arg0).ToString()); + } + + /// Logs exit from a method. + /// `this`, or another object that serves to provide context for the operation. + /// A return value from the member. + /// A second return value from the member. + /// The calling member. + [NonEvent] + public static void Exit(object? thisOrContextObject, object arg0, object arg1, [CallerMemberName] string? memberName = null) + { + DebugValidateArg(thisOrContextObject); + DebugValidateArg(arg0); + DebugValidateArg(arg1); + if (IsEnabled) Log.Exit(IdOf(thisOrContextObject), memberName, $"{Format(arg0)}, {Format(arg1)}"); + } + + [Event(ExitEventId, Level = EventLevel.Informational, Keywords = Keywords.EnterExit)] + private void Exit(string thisOrContextObject, string? memberName, string? result) => + WriteEvent(ExitEventId, thisOrContextObject, memberName ?? MissingMember, result); + #endregion + + #region Info + /// Logs an information message. + /// `this`, or another object that serves to provide context for the operation. + /// The message to be logged. + /// The calling member. + [NonEvent] + public static void Info(object? thisOrContextObject, FormattableString? formattableString = null, [CallerMemberName] string? memberName = null) + { + DebugValidateArg(thisOrContextObject); + DebugValidateArg(formattableString); + if (IsEnabled) Log.Info(IdOf(thisOrContextObject), memberName, formattableString != null ? Format(formattableString) : NoParameters); + } + + /// Logs an information message. + /// `this`, or another object that serves to provide context for the operation. + /// The message to be logged. + /// The calling member. + [NonEvent] + public static void Info(object? thisOrContextObject, object? message, [CallerMemberName] string? memberName = null) + { + DebugValidateArg(thisOrContextObject); + DebugValidateArg(message); + if (IsEnabled) Log.Info(IdOf(thisOrContextObject), memberName, Format(message).ToString()); + } + + [Event(InfoEventId, Level = EventLevel.Informational, Keywords = Keywords.Default)] + private void Info(string thisOrContextObject, string? memberName, string? message) => + WriteEvent(InfoEventId, thisOrContextObject, memberName ?? MissingMember, message); + #endregion + + #region Error + /// Logs an error message. + /// `this`, or another object that serves to provide context for the operation. + /// The message to be logged. + /// The calling member. + [NonEvent] + public static void Error(object? thisOrContextObject, FormattableString formattableString, [CallerMemberName] string? memberName = null) + { + DebugValidateArg(thisOrContextObject); + DebugValidateArg(formattableString); + if (IsEnabled) Log.ErrorMessage(IdOf(thisOrContextObject), memberName, Format(formattableString)); + } + + /// Logs an error message. + /// `this`, or another object that serves to provide context for the operation. + /// The message to be logged. + /// The calling member. + [NonEvent] + public static void Error(object? thisOrContextObject, object message, [CallerMemberName] string? memberName = null) + { + DebugValidateArg(thisOrContextObject); + DebugValidateArg(message); + if (IsEnabled) Log.ErrorMessage(IdOf(thisOrContextObject), memberName, Format(message).ToString()); + } + + [Event(ErrorEventId, Level = EventLevel.Error, Keywords = Keywords.Default)] + private void ErrorMessage(string thisOrContextObject, string? memberName, string? message) => + WriteEvent(ErrorEventId, thisOrContextObject, memberName ?? MissingMember, message); + #endregion + + #region Fail + /// Logs a fatal error and raises an assert. + /// `this`, or another object that serves to provide context for the operation. + /// The message to be logged. + /// The calling member. + [NonEvent] + public static void Fail(object? thisOrContextObject, FormattableString formattableString, [CallerMemberName] string? memberName = null) + { + // Don't call DebugValidateArg on args, as we expect Fail to be used in assert/failure situations + // that should never happen in production, and thus we don't care about extra costs. + + if (IsEnabled) Log.CriticalFailure(IdOf(thisOrContextObject), memberName, Format(formattableString)); + Debug.Fail(Format(formattableString), $"{IdOf(thisOrContextObject)}.{memberName}"); + } + + /// Logs a fatal error and raises an assert. + /// `this`, or another object that serves to provide context for the operation. + /// The message to be logged. + /// The calling member. + [NonEvent] + public static void Fail(object? thisOrContextObject, object message, [CallerMemberName] string? memberName = null) + { + // Don't call DebugValidateArg on args, as we expect Fail to be used in assert/failure situations + // that should never happen in production, and thus we don't care about extra costs. + + if (IsEnabled) Log.CriticalFailure(IdOf(thisOrContextObject), memberName, Format(message).ToString()); + Debug.Fail(Format(message).ToString(), $"{IdOf(thisOrContextObject)}.{memberName}"); + } + + [Event(CriticalFailureEventId, Level = EventLevel.Critical, Keywords = Keywords.Debug)] + private void CriticalFailure(string thisOrContextObject, string? memberName, string? message) => + WriteEvent(CriticalFailureEventId, thisOrContextObject, memberName ?? MissingMember, message); + #endregion + + #region DumpBuffer + /// Logs the contents of a buffer. + /// `this`, or another object that serves to provide context for the operation. + /// The buffer to be logged. + /// The calling member. + [NonEvent] + public static void DumpBuffer(object? thisOrContextObject, byte[] buffer, [CallerMemberName] string? memberName = null) + { + DumpBuffer(thisOrContextObject, buffer, 0, buffer.Length, memberName); + } + + /// Logs the contents of a buffer. + /// `this`, or another object that serves to provide context for the operation. + /// The buffer to be logged. + /// The starting offset from which to log. + /// The number of bytes to log. + /// The calling member. + [NonEvent] + public static void DumpBuffer(object? thisOrContextObject, byte[] buffer, int offset, int count, [CallerMemberName] string? memberName = null) + { + if (IsEnabled) + { + if (offset < 0 || offset > buffer.Length - count) + { + Fail(thisOrContextObject, $"Invalid {nameof(DumpBuffer)} Args. Length={buffer.Length}, Offset={offset}, Count={count}", memberName); + return; + } + + count = Math.Min(count, MaxDumpSize); + + byte[] slice = buffer; + if (offset != 0 || count != buffer.Length) + { + slice = new byte[count]; + Buffer.BlockCopy(buffer, offset, slice, 0, count); + } + + Log.DumpBuffer(IdOf(thisOrContextObject), memberName, slice); + } + } + + /// Logs the contents of a buffer. + /// `this`, or another object that serves to provide context for the operation. + /// The starting location of the buffer to be logged. + /// The number of bytes to log. + /// The calling member. + [NonEvent] + public static unsafe void DumpBuffer(object? thisOrContextObject, IntPtr bufferPtr, int count, [CallerMemberName] string? memberName = null) + { + Debug.Assert(bufferPtr != IntPtr.Zero); + Debug.Assert(count >= 0); + + if (IsEnabled) + { + var buffer = new byte[Math.Min(count, MaxDumpSize)]; + fixed (byte* targetPtr = buffer) + { + Buffer.MemoryCopy((byte*)bufferPtr, targetPtr, buffer.Length, buffer.Length); + } + Log.DumpBuffer(IdOf(thisOrContextObject), memberName, buffer); + } + } + + [Event(DumpArrayEventId, Level = EventLevel.Verbose, Keywords = Keywords.Debug)] + private unsafe void DumpBuffer(string thisOrContextObject, string? memberName, byte[] buffer) => + WriteEvent(DumpArrayEventId, thisOrContextObject, memberName ?? MissingMember, buffer); + #endregion + + #region Associate + /// Logs a relationship between two objects. + /// The first object. + /// The second object. + /// The calling member. + [NonEvent] + public static void Associate(object first, object second, [CallerMemberName] string? memberName = null) + { + DebugValidateArg(first); + DebugValidateArg(second); + if (IsEnabled) Log.Associate(IdOf(first), memberName, IdOf(first), IdOf(second)); + } + + /// Logs a relationship between two objects. + /// `this`, or another object that serves to provide context for the operation. + /// The first object. + /// The second object. + /// The calling member. + [NonEvent] + public static void Associate(object? thisOrContextObject, object first, object second, [CallerMemberName] string? memberName = null) + { + DebugValidateArg(thisOrContextObject); + DebugValidateArg(first); + DebugValidateArg(second); + if (IsEnabled) Log.Associate(IdOf(thisOrContextObject), memberName, IdOf(first), IdOf(second)); + } + + [Event(AssociateEventId, Level = EventLevel.Informational, Keywords = Keywords.Default, Message = "[{2}]<-->[{3}]")] + private void Associate(string thisOrContextObject, string? memberName, string first, string second) => + WriteEvent(AssociateEventId, thisOrContextObject, memberName ?? MissingMember, first, second); + #endregion + #endregion + + #region Helpers + [Conditional("DEBUG_NETEVENTSOURCE_MISUSE")] + private static void DebugValidateArg(object? arg) + { + if (!IsEnabled) + { + Debug.Assert(!(arg is ValueType), $"Should not be passing value type {arg?.GetType()} to logging without IsEnabled check"); + Debug.Assert(!(arg is FormattableString), $"Should not be formatting FormattableString \"{arg}\" if tracing isn't enabled"); + } + } + + [Conditional("DEBUG_NETEVENTSOURCE_MISUSE")] + private static void DebugValidateArg(FormattableString? arg) + { + Debug.Assert(IsEnabled || arg == null, $"Should not be formatting FormattableString \"{arg}\" if tracing isn't enabled"); + } + + public static new bool IsEnabled => + Log.IsEnabled(); + + [NonEvent] + public static string IdOf(object? value) => value != null ? value.GetType().Name + "#" + GetHashCode(value) : NullInstance; + + [NonEvent] + public static int GetHashCode(object value) => value?.GetHashCode() ?? 0; + + [NonEvent] + public static object Format(object? value) + { + // If it's null, return a known string for null values + if (value == null) + { + return NullInstance; + } + + // Give another partial implementation a chance to provide its own string representation + string? result = null; + AdditionalCustomizedToString(value, ref result); + if (result != null) + { + return result; + } + + // Format arrays with their element type name and length + if (value is Array arr) + { + return $"{arr.GetType().GetElementType()}[{((Array)value).Length}]"; + } + + // Format ICollections as the name and count + if (value is ICollection c) + { + return $"{c.GetType().Name}({c.Count})"; + } + + // Format SafeHandles as their type, hash code, and pointer value + if (value is SafeHandle handle) + { + return $"{handle.GetType().Name}:{handle.GetHashCode()}(0x{handle.DangerousGetHandle():X})"; + } + + // Format IntPtrs as hex + if (value is IntPtr) + { + return $"0x{value:X}"; + } + + // If the string representation of the instance would just be its type name, + // use its id instead. + string? toString = value.ToString(); + if (toString == null || toString == value.GetType().FullName) + { + return IdOf(value); + } + + // Otherwise, return the original object so that the caller does default formatting. + return value; + } + + [NonEvent] + private static string Format(FormattableString s) + { + switch (s.ArgumentCount) + { + case 0: return s.Format; + case 1: return string.Format(s.Format, Format(s.GetArgument(0))); + case 2: return string.Format(s.Format, Format(s.GetArgument(0)), Format(s.GetArgument(1))); + case 3: return string.Format(s.Format, Format(s.GetArgument(0)), Format(s.GetArgument(1)), Format(s.GetArgument(2))); + default: + object?[] args = s.GetArguments(); + object[] formattedArgs = new object[args.Length]; + for (int i = 0; i < args.Length; i++) + { + formattedArgs[i] = Format(args[i]); + } + return string.Format(s.Format, formattedArgs); + } + } + + static partial void AdditionalCustomizedToString(T value, ref string? result); + #endregion + + #region Custom WriteEvent overloads + + [NonEvent] + private unsafe void WriteEvent(int eventId, string? arg1, string? arg2, string? arg3, string? arg4) + { + if (IsEnabled()) + { + if (arg1 == null) arg1 = ""; + if (arg2 == null) arg2 = ""; + if (arg3 == null) arg3 = ""; + if (arg4 == null) arg4 = ""; + + fixed (char* string1Bytes = arg1) + fixed (char* string2Bytes = arg2) + fixed (char* string3Bytes = arg3) + fixed (char* string4Bytes = arg4) + { + const int NumEventDatas = 4; + var descrs = stackalloc EventData[NumEventDatas]; + + descrs[0] = new EventData + { + DataPointer = (IntPtr)string1Bytes, + Size = ((arg1.Length + 1) * 2) + }; + descrs[1] = new EventData + { + DataPointer = (IntPtr)string2Bytes, + Size = ((arg2.Length + 1) * 2) + }; + descrs[2] = new EventData + { + DataPointer = (IntPtr)string3Bytes, + Size = ((arg3.Length + 1) * 2) + }; + descrs[3] = new EventData + { + DataPointer = (IntPtr)string4Bytes, + Size = ((arg4.Length + 1) * 2) + }; + + WriteEventCore(eventId, NumEventDatas, descrs); + } + } + } + + [NonEvent] + private unsafe void WriteEvent(int eventId, string? arg1, string? arg2, byte[]? arg3) + { + if (IsEnabled()) + { + if (arg1 == null) arg1 = ""; + if (arg2 == null) arg2 = ""; + if (arg3 == null) arg3 = Array.Empty(); + + fixed (char* arg1Ptr = arg1) + fixed (char* arg2Ptr = arg2) + fixed (byte* arg3Ptr = arg3) + { + int bufferLength = arg3.Length; + const int NumEventDatas = 4; + var descrs = stackalloc EventData[NumEventDatas]; + + descrs[0] = new EventData + { + DataPointer = (IntPtr)arg1Ptr, + Size = (arg1.Length + 1) * sizeof(char) + }; + descrs[1] = new EventData + { + DataPointer = (IntPtr)arg2Ptr, + Size = (arg2.Length + 1) * sizeof(char) + }; + descrs[2] = new EventData + { + DataPointer = (IntPtr)(&bufferLength), + Size = 4 + }; + descrs[3] = new EventData + { + DataPointer = (IntPtr)arg3Ptr, + Size = bufferLength + }; + + WriteEventCore(eventId, NumEventDatas, descrs); + } + } + } + + [NonEvent] + private unsafe void WriteEvent(int eventId, string? arg1, int arg2, int arg3, int arg4) + { + if (IsEnabled()) + { + if (arg1 == null) arg1 = ""; + + fixed (char* arg1Ptr = arg1) + { + const int NumEventDatas = 4; + var descrs = stackalloc EventData[NumEventDatas]; + + descrs[0] = new EventData + { + DataPointer = (IntPtr)(arg1Ptr), + Size = (arg1.Length + 1) * sizeof(char) + }; + descrs[1] = new EventData + { + DataPointer = (IntPtr)(&arg2), + Size = sizeof(int) + }; + descrs[2] = new EventData + { + DataPointer = (IntPtr)(&arg3), + Size = sizeof(int) + }; + descrs[3] = new EventData + { + DataPointer = (IntPtr)(&arg4), + Size = sizeof(int) + }; + + WriteEventCore(eventId, NumEventDatas, descrs); + } + } + } + + [NonEvent] + private unsafe void WriteEvent(int eventId, string? arg1, int arg2, string? arg3) + { + if (IsEnabled()) + { + if (arg1 == null) arg1 = ""; + if (arg3 == null) arg3 = ""; + + fixed (char* arg1Ptr = arg1) + fixed (char* arg3Ptr = arg3) + { + const int NumEventDatas = 3; + var descrs = stackalloc EventData[NumEventDatas]; + + descrs[0] = new EventData + { + DataPointer = (IntPtr)(arg1Ptr), + Size = (arg1.Length + 1) * sizeof(char) + }; + descrs[1] = new EventData + { + DataPointer = (IntPtr)(&arg2), + Size = sizeof(int) + }; + descrs[2] = new EventData + { + DataPointer = (IntPtr)(arg3Ptr), + Size = (arg3.Length + 1) * sizeof(char) + }; + + WriteEventCore(eventId, NumEventDatas, descrs); + } + } + } + + [NonEvent] + private unsafe void WriteEvent(int eventId, string? arg1, string? arg2, int arg3) + { + if (IsEnabled()) + { + if (arg1 == null) arg1 = ""; + if (arg2 == null) arg2 = ""; + + fixed (char* arg1Ptr = arg1) + fixed (char* arg2Ptr = arg2) + { + const int NumEventDatas = 3; + var descrs = stackalloc EventData[NumEventDatas]; + + descrs[0] = new EventData + { + DataPointer = (IntPtr)(arg1Ptr), + Size = (arg1.Length + 1) * sizeof(char) + }; + descrs[1] = new EventData + { + DataPointer = (IntPtr)(arg2Ptr), + Size = (arg2.Length + 1) * sizeof(char) + }; + descrs[2] = new EventData + { + DataPointer = (IntPtr)(&arg3), + Size = sizeof(int) + }; + + WriteEventCore(eventId, NumEventDatas, descrs); + } + } + } + + [NonEvent] + private unsafe void WriteEvent(int eventId, string? arg1, string? arg2, string? arg3, int arg4) + { + if (IsEnabled()) + { + if (arg1 == null) arg1 = ""; + if (arg2 == null) arg2 = ""; + if (arg3 == null) arg3 = ""; + + fixed (char* arg1Ptr = arg1) + fixed (char* arg2Ptr = arg2) + fixed (char* arg3Ptr = arg3) + { + const int NumEventDatas = 4; + var descrs = stackalloc EventData[NumEventDatas]; + + descrs[0] = new EventData + { + DataPointer = (IntPtr)(arg1Ptr), + Size = (arg1.Length + 1) * sizeof(char) + }; + descrs[1] = new EventData + { + DataPointer = (IntPtr)(arg2Ptr), + Size = (arg2.Length + 1) * sizeof(char) + }; + descrs[2] = new EventData + { + DataPointer = (IntPtr)(arg3Ptr), + Size = (arg3.Length + 1) * sizeof(char) + }; + descrs[3] = new EventData + { + DataPointer = (IntPtr)(&arg4), + Size = sizeof(int) + }; + + WriteEventCore(eventId, NumEventDatas, descrs); + } + } + } + #endregion + } +} diff --git a/src/Shared/runtime/Quic/Implementations/Mock/MockConnection.cs b/src/Shared/runtime/Quic/Implementations/Mock/MockConnection.cs new file mode 100644 index 0000000000..ae40406210 --- /dev/null +++ b/src/Shared/runtime/Quic/Implementations/Mock/MockConnection.cs @@ -0,0 +1,227 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable +using System.Buffers.Binary; +using System.Net.Security; +using System.Net.Sockets; +using System.Threading; +using System.Threading.Tasks; + +namespace System.Net.Quic.Implementations.Mock +{ + internal sealed class MockConnection : QuicConnectionProvider + { + private readonly bool _isClient; + private bool _disposed = false; + private IPEndPoint? _remoteEndPoint; + private IPEndPoint? _localEndPoint; + private object _syncObject = new object(); + private Socket? _socket = null; + private IPEndPoint? _peerListenEndPoint = null; + private TcpListener? _inboundListener = null; + private long _nextOutboundBidirectionalStream; + private long _nextOutboundUnidirectionalStream; + + // Constructor for outbound connections + internal MockConnection(IPEndPoint? remoteEndPoint, SslClientAuthenticationOptions? sslClientAuthenticationOptions, IPEndPoint? localEndPoint = null) + { + _remoteEndPoint = remoteEndPoint; + _localEndPoint = localEndPoint; + + _isClient = true; + _nextOutboundBidirectionalStream = 0; + _nextOutboundUnidirectionalStream = 2; + } + + // Constructor for accepted inbound connections + internal MockConnection(Socket socket, IPEndPoint peerListenEndPoint, TcpListener inboundListener) + { + _isClient = false; + _nextOutboundBidirectionalStream = 1; + _nextOutboundUnidirectionalStream = 3; + _socket = socket; + _peerListenEndPoint = peerListenEndPoint; + _inboundListener = inboundListener; + _localEndPoint = (IPEndPoint?)socket.LocalEndPoint; + _remoteEndPoint = (IPEndPoint?)socket.RemoteEndPoint; + } + + internal override bool Connected + { + get + { + CheckDisposed(); + + return _socket != null; + } + } + + internal override IPEndPoint LocalEndPoint => new IPEndPoint(_localEndPoint!.Address, _localEndPoint.Port); + + internal override IPEndPoint RemoteEndPoint => new IPEndPoint(_remoteEndPoint!.Address, _remoteEndPoint.Port); + + internal override SslApplicationProtocol NegotiatedApplicationProtocol => throw new NotImplementedException(); + + internal override async ValueTask ConnectAsync(CancellationToken cancellationToken = default) + { + CheckDisposed(); + + if (Connected) + { + // TODO: Exception text + throw new InvalidOperationException("Already connected"); + } + + Socket socket = new Socket(_remoteEndPoint!.AddressFamily, SocketType.Stream, ProtocolType.Tcp); + await socket.ConnectAsync(_remoteEndPoint).ConfigureAwait(false); + socket.NoDelay = true; + + _localEndPoint = (IPEndPoint?)socket.LocalEndPoint; + + // Listen on a new local endpoint for inbound streams + TcpListener inboundListener = new TcpListener(_localEndPoint!.Address, 0); + inboundListener.Start(); + int inboundListenPort = ((IPEndPoint)inboundListener.LocalEndpoint).Port; + + // Write inbound listen port to socket so server can read it + byte[] buffer = new byte[4]; + BinaryPrimitives.WriteInt32LittleEndian(buffer, inboundListenPort); + await socket.SendAsync(buffer, SocketFlags.None).ConfigureAwait(false); + + // Read first 4 bytes to get server listen port + int bytesRead = 0; + do + { + bytesRead += await socket.ReceiveAsync(buffer.AsMemory().Slice(bytesRead), SocketFlags.None).ConfigureAwait(false); + } while (bytesRead != buffer.Length); + + int peerListenPort = BinaryPrimitives.ReadInt32LittleEndian(buffer); + IPEndPoint peerListenEndPoint = new IPEndPoint(((IPEndPoint)socket.RemoteEndPoint!).Address, peerListenPort); + + _socket = socket; + _peerListenEndPoint = peerListenEndPoint; + _inboundListener = inboundListener; + } + + internal override QuicStreamProvider OpenUnidirectionalStream() + { + long streamId; + lock (_syncObject) + { + streamId = _nextOutboundUnidirectionalStream; + _nextOutboundUnidirectionalStream += 4; + } + + return new MockStream(this, streamId, bidirectional: false); + } + + internal override QuicStreamProvider OpenBidirectionalStream() + { + long streamId; + lock (_syncObject) + { + streamId = _nextOutboundBidirectionalStream; + _nextOutboundBidirectionalStream += 4; + } + + return new MockStream(this, streamId, bidirectional: true); + } + + internal override long GetRemoteAvailableUnidirectionalStreamCount() + { + throw new NotImplementedException(); + } + + internal override long GetRemoteAvailableBidirectionalStreamCount() + { + throw new NotImplementedException(); + } + + internal async Task CreateOutboundMockStreamAsync(long streamId) + { + Socket socket = new Socket(SocketType.Stream, ProtocolType.Tcp); + await socket.ConnectAsync(_peerListenEndPoint!).ConfigureAwait(false); + socket.NoDelay = true; + + // Write stream ID to socket so server can read it + byte[] buffer = new byte[8]; + BinaryPrimitives.WriteInt64LittleEndian(buffer, streamId); + await socket.SendAsync(buffer, SocketFlags.None).ConfigureAwait(false); + + return socket; + } + + internal override async ValueTask AcceptStreamAsync(CancellationToken cancellationToken = default) + { + CheckDisposed(); + + Socket socket = await _inboundListener!.AcceptSocketAsync().ConfigureAwait(false); + + // Read first bytes to get stream ID + byte[] buffer = new byte[8]; + int bytesRead = 0; + do + { + bytesRead += await socket.ReceiveAsync(buffer.AsMemory().Slice(bytesRead), SocketFlags.None).ConfigureAwait(false); + } while (bytesRead != buffer.Length); + + long streamId = BinaryPrimitives.ReadInt64LittleEndian(buffer); + + bool clientInitiated = ((streamId & 0b01) == 0); + if (clientInitiated == _isClient) + { + throw new Exception($"Wrong initiator on accepted stream??? streamId={streamId}, _isClient={_isClient}"); + } + + bool bidirectional = ((streamId & 0b10) == 0); + return new MockStream(socket, streamId, bidirectional: bidirectional); + } + + internal override ValueTask CloseAsync(long errorCode, CancellationToken cancellationToken = default) + { + Dispose(); + return default; + } + + private void CheckDisposed() + { + if (_disposed) + { + throw new ObjectDisposedException(nameof(QuicConnection)); + } + } + + private void Dispose(bool disposing) + { + if (!_disposed) + { + if (disposing) + { + _socket?.Dispose(); + _socket = null; + + _inboundListener?.Stop(); + _inboundListener = null; + } + + // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below. + // TODO: set large fields to null. + + _disposed = true; + } + } + + ~MockConnection() + { + Dispose(false); + } + + public override void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/Shared/runtime/Quic/Implementations/Mock/MockImplementationProvider.cs b/src/Shared/runtime/Quic/Implementations/Mock/MockImplementationProvider.cs new file mode 100644 index 0000000000..56a5a11679 --- /dev/null +++ b/src/Shared/runtime/Quic/Implementations/Mock/MockImplementationProvider.cs @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Net.Security; + +namespace System.Net.Quic.Implementations.Mock +{ + internal sealed class MockImplementationProvider : QuicImplementationProvider + { + internal override QuicListenerProvider CreateListener(QuicListenerOptions options) + { + return new MockListener(options.ListenEndPoint!, options.ServerAuthenticationOptions); + } + + internal override QuicConnectionProvider CreateConnection(QuicClientConnectionOptions options) + { + return new MockConnection(options.RemoteEndPoint, options.ClientAuthenticationOptions, options.LocalEndPoint); + } + } +} diff --git a/src/Shared/runtime/Quic/Implementations/Mock/MockListener.cs b/src/Shared/runtime/Quic/Implementations/Mock/MockListener.cs new file mode 100644 index 0000000000..88297bdfdd --- /dev/null +++ b/src/Shared/runtime/Quic/Implementations/Mock/MockListener.cs @@ -0,0 +1,121 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable +using System.Net.Sockets; +using System.Net.Security; +using System.Threading.Tasks; +using System.Threading; +using System.Buffers.Binary; + +namespace System.Net.Quic.Implementations.Mock +{ + internal sealed class MockListener : QuicListenerProvider + { + private bool _disposed = false; + private SslServerAuthenticationOptions? _sslOptions; + private IPEndPoint _listenEndPoint; + private TcpListener _tcpListener; + + internal MockListener(IPEndPoint listenEndPoint, SslServerAuthenticationOptions? sslServerAuthenticationOptions) + { + if (listenEndPoint == null) + { + throw new ArgumentNullException(nameof(listenEndPoint)); + } + + _sslOptions = sslServerAuthenticationOptions; + _listenEndPoint = listenEndPoint; + + _tcpListener = new TcpListener(listenEndPoint); + } + + // IPEndPoint is mutable, so we must create a new instance every time this is retrieved. + internal override IPEndPoint ListenEndPoint => new IPEndPoint(_listenEndPoint.Address, _listenEndPoint.Port); + + internal override async ValueTask AcceptConnectionAsync(CancellationToken cancellationToken = default) + { + CheckDisposed(); + + Socket socket = await _tcpListener.AcceptSocketAsync().ConfigureAwait(false); + socket.NoDelay = true; + + // Read first 4 bytes to get client listen port + byte[] buffer = new byte[4]; + int bytesRead = 0; + do + { + bytesRead += await socket.ReceiveAsync(buffer.AsMemory().Slice(bytesRead), SocketFlags.None).ConfigureAwait(false); + } while (bytesRead != buffer.Length); + + int peerListenPort = BinaryPrimitives.ReadInt32LittleEndian(buffer); + IPEndPoint peerListenEndPoint = new IPEndPoint(((IPEndPoint)socket.RemoteEndPoint!).Address, peerListenPort); + + // Listen on a new local endpoint for inbound streams + TcpListener inboundListener = new TcpListener(_listenEndPoint.Address, 0); + inboundListener.Start(); + int inboundListenPort = ((IPEndPoint)inboundListener.LocalEndpoint).Port; + + // Write inbound listen port to socket so client can read it + BinaryPrimitives.WriteInt32LittleEndian(buffer, inboundListenPort); + await socket.SendAsync(buffer, SocketFlags.None).ConfigureAwait(false); + + return new MockConnection(socket, peerListenEndPoint, inboundListener); + } + + internal override void Start() + { + CheckDisposed(); + + _tcpListener.Start(); + + if (_listenEndPoint.Port == 0) + { + // Get auto-assigned port + _listenEndPoint = (IPEndPoint)_tcpListener.LocalEndpoint; + } + } + + internal override void Close() + { + Dispose(); + } + + private void CheckDisposed() + { + if (_disposed) + { + throw new ObjectDisposedException(nameof(QuicListener)); + } + } + + private void Dispose(bool disposing) + { + if (!_disposed) + { + if (disposing) + { + _tcpListener?.Stop(); + _tcpListener = null!; + } + + // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below. + // TODO: set large fields to null. + + _disposed = true; + } + } + + ~MockListener() + { + Dispose(false); + } + + public override void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/Shared/runtime/Quic/Implementations/Mock/MockStream.cs b/src/Shared/runtime/Quic/Implementations/Mock/MockStream.cs new file mode 100644 index 0000000000..f367d981bd --- /dev/null +++ b/src/Shared/runtime/Quic/Implementations/Mock/MockStream.cs @@ -0,0 +1,260 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable +using System.Buffers; +using System.Diagnostics; +using System.Net.Sockets; +using System.Threading; +using System.Threading.Tasks; + +namespace System.Net.Quic.Implementations.Mock +{ + internal sealed class MockStream : QuicStreamProvider + { + private bool _disposed = false; + private readonly long _streamId; + private bool _canRead; + private bool _canWrite; + + private MockConnection? _connection; + + private Socket? _socket = null; + + // Constructor for outbound streams + internal MockStream(MockConnection connection, long streamId, bool bidirectional) + { + _connection = connection; + _streamId = streamId; + _canRead = bidirectional; + _canWrite = true; + } + + // Constructor for inbound streams + internal MockStream(Socket socket, long streamId, bool bidirectional) + { + _socket = socket; + _streamId = streamId; + _canRead = true; + _canWrite = bidirectional; + } + + private async ValueTask ConnectAsync(CancellationToken cancellationToken = default) + { + Debug.Assert(_connection != null, "Stream not connected but no connection???"); + + _socket = await _connection.CreateOutboundMockStreamAsync(_streamId).ConfigureAwait(false); + + // Don't need to hold on to the connection any longer. + _connection = null; + } + + internal override long StreamId + { + get + { + CheckDisposed(); + return _streamId; + } + } + + internal override bool CanRead => _canRead; + + internal override int Read(Span buffer) + { + CheckDisposed(); + + if (!_canRead) + { + throw new NotSupportedException(); + } + + return _socket!.Receive(buffer); + } + + internal override async ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default) + { + CheckDisposed(); + + if (!_canRead) + { + throw new NotSupportedException(); + } + + if (_socket == null) + { + await ConnectAsync(cancellationToken).ConfigureAwait(false); + } + + return await _socket!.ReceiveAsync(buffer, SocketFlags.None, cancellationToken).ConfigureAwait(false); + } + + internal override bool CanWrite => _canWrite; + + internal override void Write(ReadOnlySpan buffer) + { + CheckDisposed(); + + if (!_canWrite) + { + throw new NotSupportedException(); + } + + _socket!.Send(buffer); + } + + internal override ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default) + { + return WriteAsync(buffer, endStream: false, cancellationToken); + } + + internal override async ValueTask WriteAsync(ReadOnlyMemory buffer, bool endStream, CancellationToken cancellationToken = default) + { + CheckDisposed(); + + if (!_canWrite) + { + throw new NotSupportedException(); + } + + if (_socket == null) + { + await ConnectAsync(cancellationToken).ConfigureAwait(false); + } + await _socket!.SendAsync(buffer, SocketFlags.None, cancellationToken).ConfigureAwait(false); + + if (endStream) + { + _socket!.Shutdown(SocketShutdown.Send); + } + } + + internal override ValueTask WriteAsync(ReadOnlySequence buffers, CancellationToken cancellationToken = default) + { + return WriteAsync(buffers, endStream: false, cancellationToken); + } + internal override async ValueTask WriteAsync(ReadOnlySequence buffers, bool endStream, CancellationToken cancellationToken = default) + { + CheckDisposed(); + + if (!_canWrite) + { + throw new NotSupportedException(); + } + + if (_socket == null) + { + await ConnectAsync(cancellationToken).ConfigureAwait(false); + } + + foreach (ReadOnlyMemory buffer in buffers) + { + await _socket!.SendAsync(buffer, SocketFlags.None, cancellationToken).ConfigureAwait(false); + } + + if (endStream) + { + _socket!.Shutdown(SocketShutdown.Send); + } + } + + internal override ValueTask WriteAsync(ReadOnlyMemory> buffers, CancellationToken cancellationToken = default) + { + return WriteAsync(buffers, endStream: false, cancellationToken); + } + internal override async ValueTask WriteAsync(ReadOnlyMemory> buffers, bool endStream, CancellationToken cancellationToken = default) + { + CheckDisposed(); + + if (!_canWrite) + { + throw new NotSupportedException(); + } + + if (_socket == null) + { + await ConnectAsync(cancellationToken).ConfigureAwait(false); + } + + foreach (ReadOnlyMemory buffer in buffers.ToArray()) + { + await _socket!.SendAsync(buffer, SocketFlags.None, cancellationToken).ConfigureAwait(false); + } + + if (endStream) + { + _socket!.Shutdown(SocketShutdown.Send); + } + } + + internal override void Flush() + { + CheckDisposed(); + } + + internal override Task FlushAsync(CancellationToken cancellationToken) + { + CheckDisposed(); + + return Task.CompletedTask; + } + + internal override void AbortRead(long errorCode) + { + throw new NotImplementedException(); + } + + internal override void AbortWrite(long errorCode) + { + throw new NotImplementedException(); + } + + + internal override ValueTask ShutdownWriteCompleted(CancellationToken cancellationToken = default) + { + CheckDisposed(); + + return default; + } + + internal override void Shutdown() + { + CheckDisposed(); + + _socket!.Shutdown(SocketShutdown.Send); + } + + private void CheckDisposed() + { + if (_disposed) + { + throw new ObjectDisposedException(nameof(QuicStream)); + } + } + + public override void Dispose() + { + if (!_disposed) + { + _disposed = true; + + _socket?.Dispose(); + _socket = null; + } + } + + public override ValueTask DisposeAsync() + { + if (!_disposed) + { + _disposed = true; + + _socket?.Dispose(); + _socket = null; + } + + return default; + } + } +} diff --git a/src/Shared/runtime/Quic/Implementations/MsQuic/Internal/MsQuicAddressHelpers.cs b/src/Shared/runtime/Quic/Implementations/MsQuic/Internal/MsQuicAddressHelpers.cs new file mode 100644 index 0000000000..2ecf0eb210 --- /dev/null +++ b/src/Shared/runtime/Quic/Implementations/MsQuic/Internal/MsQuicAddressHelpers.cs @@ -0,0 +1,85 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Net.Sockets; +using static System.Net.Quic.Implementations.MsQuic.Internal.MsQuicNativeMethods; + +namespace System.Net.Quic.Implementations.MsQuic.Internal +{ + internal static class MsQuicAddressHelpers + { + internal const ushort IPv4 = 2; + internal const ushort IPv6 = 23; + + internal static unsafe IPEndPoint INetToIPEndPoint(SOCKADDR_INET inetAddress) + { + if (inetAddress.si_family == IPv4) + { + return new IPEndPoint(new IPAddress(inetAddress.Ipv4.Address), (ushort)IPAddress.NetworkToHostOrder((short)inetAddress.Ipv4.sin_port)); + } + else + { + return new IPEndPoint(new IPAddress(inetAddress.Ipv6.Address), (ushort)IPAddress.NetworkToHostOrder((short)inetAddress.Ipv6._port)); + } + } + + internal static SOCKADDR_INET IPEndPointToINet(IPEndPoint endpoint) + { + SOCKADDR_INET socketAddress = default; + byte[] buffer = endpoint.Address.GetAddressBytes(); + if (endpoint.Address != IPAddress.Any && endpoint.Address != IPAddress.IPv6Any) + { + switch (endpoint.Address.AddressFamily) + { + case AddressFamily.InterNetwork: + socketAddress.Ipv4.sin_addr0 = buffer[0]; + socketAddress.Ipv4.sin_addr1 = buffer[1]; + socketAddress.Ipv4.sin_addr2 = buffer[2]; + socketAddress.Ipv4.sin_addr3 = buffer[3]; + socketAddress.Ipv4.sin_family = IPv4; + break; + case AddressFamily.InterNetworkV6: + socketAddress.Ipv6._addr0 = buffer[0]; + socketAddress.Ipv6._addr1 = buffer[1]; + socketAddress.Ipv6._addr2 = buffer[2]; + socketAddress.Ipv6._addr3 = buffer[3]; + socketAddress.Ipv6._addr4 = buffer[4]; + socketAddress.Ipv6._addr5 = buffer[5]; + socketAddress.Ipv6._addr6 = buffer[6]; + socketAddress.Ipv6._addr7 = buffer[7]; + socketAddress.Ipv6._addr8 = buffer[8]; + socketAddress.Ipv6._addr9 = buffer[9]; + socketAddress.Ipv6._addr10 = buffer[10]; + socketAddress.Ipv6._addr11 = buffer[11]; + socketAddress.Ipv6._addr12 = buffer[12]; + socketAddress.Ipv6._addr13 = buffer[13]; + socketAddress.Ipv6._addr14 = buffer[14]; + socketAddress.Ipv6._addr15 = buffer[15]; + socketAddress.Ipv6._family = IPv6; + break; + default: + throw new ArgumentException("Only IPv4 or IPv6 are supported"); + } + } + + SetPort(endpoint.Address.AddressFamily, ref socketAddress, endpoint.Port); + return socketAddress; + } + + private static void SetPort(AddressFamily addressFamily, ref SOCKADDR_INET socketAddrInet, int originalPort) + { + ushort convertedPort = (ushort)IPAddress.HostToNetworkOrder((short)originalPort); + switch (addressFamily) + { + case AddressFamily.InterNetwork: + socketAddrInet.Ipv4.sin_port = convertedPort; + break; + case AddressFamily.InterNetworkV6: + default: + socketAddrInet.Ipv6._port = convertedPort; + break; + } + } + } +} diff --git a/src/Shared/runtime/Quic/Implementations/MsQuic/Internal/MsQuicApi.cs b/src/Shared/runtime/Quic/Implementations/MsQuic/Internal/MsQuicApi.cs new file mode 100644 index 0000000000..0b9893333d --- /dev/null +++ b/src/Shared/runtime/Quic/Implementations/MsQuic/Internal/MsQuicApi.cs @@ -0,0 +1,356 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable +using System.IO; +using System.Net.Security; +using System.Runtime.InteropServices; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using System.Threading.Tasks; + +namespace System.Net.Quic.Implementations.MsQuic.Internal +{ + internal class MsQuicApi : IDisposable + { + private bool _disposed; + + private readonly IntPtr _registrationContext; + + private unsafe MsQuicApi() + { + MsQuicNativeMethods.NativeApi* registration; + + try + { + uint status = Interop.MsQuic.MsQuicOpen(version: 1, out registration); + if (!MsQuicStatusHelper.SuccessfulStatusCode(status)) + { + throw new NotSupportedException(SR.net_quic_notsupported); + } + } + catch (DllNotFoundException) + { + throw new NotSupportedException(SR.net_quic_notsupported); + } + + MsQuicNativeMethods.NativeApi nativeRegistration = *registration; + + RegistrationOpenDelegate = + Marshal.GetDelegateForFunctionPointer( + nativeRegistration.RegistrationOpen); + RegistrationCloseDelegate = + Marshal.GetDelegateForFunctionPointer( + nativeRegistration.RegistrationClose); + + SecConfigCreateDelegate = + Marshal.GetDelegateForFunctionPointer( + nativeRegistration.SecConfigCreate); + SecConfigDeleteDelegate = + Marshal.GetDelegateForFunctionPointer( + nativeRegistration.SecConfigDelete); + SessionOpenDelegate = + Marshal.GetDelegateForFunctionPointer( + nativeRegistration.SessionOpen); + SessionCloseDelegate = + Marshal.GetDelegateForFunctionPointer( + nativeRegistration.SessionClose); + SessionShutdownDelegate = + Marshal.GetDelegateForFunctionPointer( + nativeRegistration.SessionShutdown); + + ListenerOpenDelegate = + Marshal.GetDelegateForFunctionPointer( + nativeRegistration.ListenerOpen); + ListenerCloseDelegate = + Marshal.GetDelegateForFunctionPointer( + nativeRegistration.ListenerClose); + ListenerStartDelegate = + Marshal.GetDelegateForFunctionPointer( + nativeRegistration.ListenerStart); + ListenerStopDelegate = + Marshal.GetDelegateForFunctionPointer( + nativeRegistration.ListenerStop); + + ConnectionOpenDelegate = + Marshal.GetDelegateForFunctionPointer( + nativeRegistration.ConnectionOpen); + ConnectionCloseDelegate = + Marshal.GetDelegateForFunctionPointer( + nativeRegistration.ConnectionClose); + ConnectionShutdownDelegate = + Marshal.GetDelegateForFunctionPointer( + nativeRegistration.ConnectionShutdown); + ConnectionStartDelegate = + Marshal.GetDelegateForFunctionPointer( + nativeRegistration.ConnectionStart); + + StreamOpenDelegate = + Marshal.GetDelegateForFunctionPointer( + nativeRegistration.StreamOpen); + StreamCloseDelegate = + Marshal.GetDelegateForFunctionPointer( + nativeRegistration.StreamClose); + StreamStartDelegate = + Marshal.GetDelegateForFunctionPointer( + nativeRegistration.StreamStart); + StreamShutdownDelegate = + Marshal.GetDelegateForFunctionPointer( + nativeRegistration.StreamShutdown); + StreamSendDelegate = + Marshal.GetDelegateForFunctionPointer( + nativeRegistration.StreamSend); + StreamReceiveCompleteDelegate = + Marshal.GetDelegateForFunctionPointer( + nativeRegistration.StreamReceiveComplete); + StreamReceiveSetEnabledDelegate = + Marshal.GetDelegateForFunctionPointer( + nativeRegistration.StreamReceiveSetEnabled); + SetContextDelegate = + Marshal.GetDelegateForFunctionPointer( + nativeRegistration.SetContext); + GetContextDelegate = + Marshal.GetDelegateForFunctionPointer( + nativeRegistration.GetContext); + SetCallbackHandlerDelegate = + Marshal.GetDelegateForFunctionPointer( + nativeRegistration.SetCallbackHandler); + + SetParamDelegate = + Marshal.GetDelegateForFunctionPointer( + nativeRegistration.SetParam); + GetParamDelegate = + Marshal.GetDelegateForFunctionPointer( + nativeRegistration.GetParam); + + RegistrationOpenDelegate(Encoding.UTF8.GetBytes("SystemNetQuic"), out IntPtr ctx); + _registrationContext = ctx; + } + + internal static MsQuicApi Api { get; } = null!; + + internal static bool IsQuicSupported { get; } + + static MsQuicApi() + { + // MsQuicOpen will succeed even if the platform will not support it. It will then fail with unspecified + // platform-specific errors in subsequent callbacks. For now, check for the minimum build we've tested it on. + + // TODO: + // - Hopefully, MsQuicOpen will perform this check for us and give us a consistent error code. + // - Otherwise, dial this in to reflect actual minimum requirements and add some sort of platform + // error code mapping when creating exceptions. + + OperatingSystem ver = Environment.OSVersion; + + // TODO: try to initialize TLS 1.3 in SslStream. + + try + { + Api = new MsQuicApi(); + IsQuicSupported = true; + } + catch (NotSupportedException) + { + IsQuicSupported = false; + } + } + + internal MsQuicNativeMethods.RegistrationOpenDelegate RegistrationOpenDelegate { get; } + internal MsQuicNativeMethods.RegistrationCloseDelegate RegistrationCloseDelegate { get; } + + internal MsQuicNativeMethods.SecConfigCreateDelegate SecConfigCreateDelegate { get; } + internal MsQuicNativeMethods.SecConfigDeleteDelegate SecConfigDeleteDelegate { get; } + + internal MsQuicNativeMethods.SessionOpenDelegate SessionOpenDelegate { get; } + internal MsQuicNativeMethods.SessionCloseDelegate SessionCloseDelegate { get; } + internal MsQuicNativeMethods.SessionShutdownDelegate SessionShutdownDelegate { get; } + + internal MsQuicNativeMethods.ListenerOpenDelegate ListenerOpenDelegate { get; } + internal MsQuicNativeMethods.ListenerCloseDelegate ListenerCloseDelegate { get; } + internal MsQuicNativeMethods.ListenerStartDelegate ListenerStartDelegate { get; } + internal MsQuicNativeMethods.ListenerStopDelegate ListenerStopDelegate { get; } + + internal MsQuicNativeMethods.ConnectionOpenDelegate ConnectionOpenDelegate { get; } + internal MsQuicNativeMethods.ConnectionCloseDelegate ConnectionCloseDelegate { get; } + internal MsQuicNativeMethods.ConnectionShutdownDelegate ConnectionShutdownDelegate { get; } + internal MsQuicNativeMethods.ConnectionStartDelegate ConnectionStartDelegate { get; } + + internal MsQuicNativeMethods.StreamOpenDelegate StreamOpenDelegate { get; } + internal MsQuicNativeMethods.StreamCloseDelegate StreamCloseDelegate { get; } + internal MsQuicNativeMethods.StreamStartDelegate StreamStartDelegate { get; } + internal MsQuicNativeMethods.StreamShutdownDelegate StreamShutdownDelegate { get; } + internal MsQuicNativeMethods.StreamSendDelegate StreamSendDelegate { get; } + internal MsQuicNativeMethods.StreamReceiveCompleteDelegate StreamReceiveCompleteDelegate { get; } + internal MsQuicNativeMethods.StreamReceiveSetEnabledDelegate StreamReceiveSetEnabledDelegate { get; } + + internal MsQuicNativeMethods.SetContextDelegate SetContextDelegate { get; } + internal MsQuicNativeMethods.GetContextDelegate GetContextDelegate { get; } + internal MsQuicNativeMethods.SetCallbackHandlerDelegate SetCallbackHandlerDelegate { get; } + + internal MsQuicNativeMethods.SetParamDelegate SetParamDelegate { get; } + internal MsQuicNativeMethods.GetParamDelegate GetParamDelegate { get; } + + internal unsafe uint UnsafeSetParam( + IntPtr Handle, + uint Level, + uint Param, + MsQuicNativeMethods.QuicBuffer Buffer) + { + return SetParamDelegate( + Handle, + Level, + Param, + Buffer.Length, + Buffer.Buffer); + } + + internal unsafe uint UnsafeGetParam( + IntPtr Handle, + uint Level, + uint Param, + ref MsQuicNativeMethods.QuicBuffer Buffer) + { + uint bufferLength = Buffer.Length; + byte* buf = Buffer.Buffer; + return GetParamDelegate( + Handle, + Level, + Param, + &bufferLength, + buf); + } + + public async ValueTask CreateSecurityConfig(X509Certificate certificate, string? certFilePath, string? privateKeyFilePath) + { + MsQuicSecurityConfig? secConfig = null; + var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + uint secConfigCreateStatus = MsQuicStatusCodes.InternalError; + uint createConfigStatus; + IntPtr unmanagedAddr = IntPtr.Zero; + MsQuicNativeMethods.CertFileParams fileParams = default; + + try + { + if (certFilePath != null && privateKeyFilePath != null) + { + fileParams = new MsQuicNativeMethods.CertFileParams + { + PrivateKeyFilePath = Marshal.StringToCoTaskMemUTF8(privateKeyFilePath), + CertificateFilePath = Marshal.StringToCoTaskMemUTF8(certFilePath) + }; + + unmanagedAddr = Marshal.AllocHGlobal(Marshal.SizeOf(fileParams)); + Marshal.StructureToPtr(fileParams, unmanagedAddr, fDeleteOld: false); + + createConfigStatus = SecConfigCreateDelegate( + _registrationContext, + (uint)QUIC_SEC_CONFIG_FLAG.CERT_FILE, + unmanagedAddr, + null, + IntPtr.Zero, + SecCfgCreateCallbackHandler); + } + else if (certificate != null) + { + createConfigStatus = SecConfigCreateDelegate( + _registrationContext, + (uint)QUIC_SEC_CONFIG_FLAG.CERT_CONTEXT, + certificate.Handle, + null, + IntPtr.Zero, + SecCfgCreateCallbackHandler); + } + else + { + // If no certificate is provided, provide a null one. + createConfigStatus = SecConfigCreateDelegate( + _registrationContext, + (uint)QUIC_SEC_CONFIG_FLAG.CERT_NULL, + IntPtr.Zero, + null, + IntPtr.Zero, + SecCfgCreateCallbackHandler); + } + + QuicExceptionHelpers.ThrowIfFailed( + createConfigStatus, + "Could not create security configuration."); + + void SecCfgCreateCallbackHandler( + IntPtr context, + uint status, + IntPtr securityConfig) + { + secConfig = new MsQuicSecurityConfig(this, securityConfig); + secConfigCreateStatus = status; + tcs.SetResult(null); + } + + await tcs.Task.ConfigureAwait(false); + + QuicExceptionHelpers.ThrowIfFailed( + secConfigCreateStatus, + "Could not create security configuration."); + } + finally + { + if (fileParams.CertificateFilePath != IntPtr.Zero) + { + Marshal.FreeCoTaskMem(fileParams.CertificateFilePath); + } + + if (fileParams.PrivateKeyFilePath != IntPtr.Zero) + { + Marshal.FreeCoTaskMem(fileParams.PrivateKeyFilePath); + } + + if (unmanagedAddr != IntPtr.Zero) + { + Marshal.FreeHGlobal(unmanagedAddr); + } + } + + return secConfig; + } + + public IntPtr SessionOpen(byte[] alpn) + { + IntPtr sessionPtr = IntPtr.Zero; + + uint status = SessionOpenDelegate( + _registrationContext, + alpn, + IntPtr.Zero, + ref sessionPtr); + + QuicExceptionHelpers.ThrowIfFailed(status, "Could not open session."); + + return sessionPtr; + } + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + + ~MsQuicApi() + { + Dispose(disposing: false); + } + + private void Dispose(bool disposing) + { + if (_disposed) + { + return; + } + + RegistrationCloseDelegate?.Invoke(_registrationContext); + + _disposed = true; + } + } +} diff --git a/src/Shared/runtime/Quic/Implementations/MsQuic/Internal/MsQuicParameterHelpers.cs b/src/Shared/runtime/Quic/Implementations/MsQuic/Internal/MsQuicParameterHelpers.cs new file mode 100644 index 0000000000..757bb0da05 --- /dev/null +++ b/src/Shared/runtime/Quic/Implementations/MsQuic/Internal/MsQuicParameterHelpers.cs @@ -0,0 +1,98 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using static System.Net.Quic.Implementations.MsQuic.Internal.MsQuicNativeMethods; + +namespace System.Net.Quic.Implementations.MsQuic.Internal +{ + internal static class MsQuicParameterHelpers + { + internal static unsafe SOCKADDR_INET GetINetParam(MsQuicApi api, IntPtr nativeObject, uint level, uint param) + { + byte* ptr = stackalloc byte[sizeof(SOCKADDR_INET)]; + QuicBuffer buffer = new QuicBuffer + { + Length = (uint)sizeof(SOCKADDR_INET), + Buffer = ptr + }; + + QuicExceptionHelpers.ThrowIfFailed( + api.UnsafeGetParam(nativeObject, level, param, ref buffer), + "Could not get SOCKADDR_INET."); + + return *(SOCKADDR_INET*)ptr; + } + + internal static unsafe ushort GetUShortParam(MsQuicApi api, IntPtr nativeObject, uint level, uint param) + { + byte* ptr = stackalloc byte[sizeof(ushort)]; + QuicBuffer buffer = new QuicBuffer() + { + Length = sizeof(ushort), + Buffer = ptr + }; + + QuicExceptionHelpers.ThrowIfFailed( + api.UnsafeGetParam(nativeObject, level, param, ref buffer), + "Could not get ushort."); + + return *(ushort*)ptr; + } + + internal static unsafe void SetUshortParam(MsQuicApi api, IntPtr nativeObject, uint level, uint param, ushort value) + { + QuicBuffer buffer = new QuicBuffer() + { + Length = sizeof(ushort), + Buffer = (byte*)&value + }; + + QuicExceptionHelpers.ThrowIfFailed( + api.UnsafeSetParam(nativeObject, level, param, buffer), + "Could not set ushort."); + } + + internal static unsafe ulong GetULongParam(MsQuicApi api, IntPtr nativeObject, uint level, uint param) + { + byte* ptr = stackalloc byte[sizeof(ulong)]; + QuicBuffer buffer = new QuicBuffer() + { + Length = sizeof(ulong), + Buffer = ptr + }; + + QuicExceptionHelpers.ThrowIfFailed( + api.UnsafeGetParam(nativeObject, level, param, ref buffer), + "Could not get ulong."); + + return *(ulong*)ptr; + } + + internal static unsafe void SetULongParam(MsQuicApi api, IntPtr nativeObject, uint level, uint param, ulong value) + { + QuicBuffer buffer = new QuicBuffer() + { + Length = sizeof(ulong), + Buffer = (byte*)&value + }; + + QuicExceptionHelpers.ThrowIfFailed( + api.UnsafeGetParam(nativeObject, level, param, ref buffer), + "Could not set ulong."); + } + + internal static unsafe void SetSecurityConfig(MsQuicApi api, IntPtr nativeObject, uint level, uint param, IntPtr value) + { + QuicBuffer buffer = new QuicBuffer() + { + Length = (uint)sizeof(void*), + Buffer = (byte*)&value + }; + + QuicExceptionHelpers.ThrowIfFailed( + api.UnsafeSetParam(nativeObject, level, param, buffer), + "Could not set security configuration."); + } + } +} diff --git a/src/Shared/runtime/Quic/Implementations/MsQuic/Internal/MsQuicSecurityConfig.cs b/src/Shared/runtime/Quic/Implementations/MsQuic/Internal/MsQuicSecurityConfig.cs new file mode 100644 index 0000000000..58fc811f7c --- /dev/null +++ b/src/Shared/runtime/Quic/Implementations/MsQuic/Internal/MsQuicSecurityConfig.cs @@ -0,0 +1,45 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Net.Quic.Implementations.MsQuic.Internal +{ + // TODO this will eventually be abstracted to support both Client and Server + // certificates + internal class MsQuicSecurityConfig : IDisposable + { + private bool _disposed; + private MsQuicApi _registration; + + public MsQuicSecurityConfig(MsQuicApi registration, IntPtr nativeObjPtr) + { + _registration = registration; + NativeObjPtr = nativeObjPtr; + } + + public IntPtr NativeObjPtr { get; private set; } + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool disposing) + { + if (_disposed) + { + return; + } + + _registration.SecConfigDeleteDelegate?.Invoke(NativeObjPtr); + NativeObjPtr = IntPtr.Zero; + _disposed = true; + } + + ~MsQuicSecurityConfig() + { + Dispose(disposing: false); + } + } +} diff --git a/src/Shared/runtime/Quic/Implementations/MsQuic/Internal/MsQuicSession.cs b/src/Shared/runtime/Quic/Implementations/MsQuic/Internal/MsQuicSession.cs new file mode 100644 index 0000000000..0f19506b81 --- /dev/null +++ b/src/Shared/runtime/Quic/Implementations/MsQuic/Internal/MsQuicSession.cs @@ -0,0 +1,156 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Net.Quic.Implementations.MsQuic.Internal +{ + internal sealed class MsQuicSession : IDisposable + { + private bool _disposed = false; + private IntPtr _nativeObjPtr; + private bool _opened; + + internal MsQuicSession() + { + if (!MsQuicApi.IsQuicSupported) + { + throw new NotSupportedException(SR.net_quic_notsupported); + } + } + + public IntPtr ConnectionOpen(QuicClientConnectionOptions options) + { + if (!_opened) + { + OpenSession(options.ClientAuthenticationOptions!.ApplicationProtocols![0].Protocol.ToArray(), + (ushort)options.MaxBidirectionalStreams, + (ushort)options.MaxUnidirectionalStreams); + } + + QuicExceptionHelpers.ThrowIfFailed(MsQuicApi.Api.ConnectionOpenDelegate( + _nativeObjPtr, + MsQuicConnection.NativeCallbackHandler, + IntPtr.Zero, + out IntPtr connectionPtr), + "Could not open the connection."); + + return connectionPtr; + } + + private void OpenSession(byte[] alpn, ushort bidirectionalStreamCount, ushort undirectionalStreamCount) + { + _opened = true; + _nativeObjPtr = MsQuicApi.Api.SessionOpen(alpn); + SetPeerBiDirectionalStreamCount(bidirectionalStreamCount); + SetPeerUnidirectionalStreamCount(undirectionalStreamCount); + } + + // TODO allow for a callback to select the certificate (SNI). + public IntPtr ListenerOpen(QuicListenerOptions options) + { + if (!_opened) + { + OpenSession(options.ServerAuthenticationOptions!.ApplicationProtocols![0].Protocol.ToArray(), + (ushort)options.MaxBidirectionalStreams, + (ushort)options.MaxUnidirectionalStreams); + } + + QuicExceptionHelpers.ThrowIfFailed(MsQuicApi.Api.ListenerOpenDelegate( + _nativeObjPtr, + MsQuicListener.NativeCallbackHandler, + IntPtr.Zero, + out IntPtr listenerPointer), + "Could not open listener."); + + return listenerPointer; + } + + // TODO call this for graceful shutdown? + public void ShutDown( + QUIC_CONNECTION_SHUTDOWN_FLAG Flags, + ushort ErrorCode) + { + MsQuicApi.Api.SessionShutdownDelegate( + _nativeObjPtr, + (uint)Flags, + ErrorCode); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + public void SetPeerBiDirectionalStreamCount(ushort count) + { + SetUshortParamter(QUIC_PARAM_SESSION.PEER_BIDI_STREAM_COUNT, count); + } + + public void SetPeerUnidirectionalStreamCount(ushort count) + { + SetUshortParamter(QUIC_PARAM_SESSION.PEER_UNIDI_STREAM_COUNT, count); + } + + private unsafe void SetUshortParamter(QUIC_PARAM_SESSION param, ushort count) + { + var buffer = new MsQuicNativeMethods.QuicBuffer() + { + Length = sizeof(ushort), + Buffer = (byte*)&count + }; + + SetParam(param, buffer); + } + + public void SetDisconnectTimeout(TimeSpan timeout) + { + SetULongParamter(QUIC_PARAM_SESSION.DISCONNECT_TIMEOUT, (ulong)timeout.TotalMilliseconds); + } + + public void SetIdleTimeout(TimeSpan timeout) + { + SetULongParamter(QUIC_PARAM_SESSION.IDLE_TIMEOUT, (ulong)timeout.TotalMilliseconds); + + } + private unsafe void SetULongParamter(QUIC_PARAM_SESSION param, ulong count) + { + var buffer = new MsQuicNativeMethods.QuicBuffer() + { + Length = sizeof(ulong), + Buffer = (byte*)&count + }; + SetParam(param, buffer); + } + + private void SetParam( + QUIC_PARAM_SESSION param, + MsQuicNativeMethods.QuicBuffer buf) + { + QuicExceptionHelpers.ThrowIfFailed(MsQuicApi.Api.UnsafeSetParam( + _nativeObjPtr, + (uint)QUIC_PARAM_LEVEL.SESSION, + (uint)param, + buf), + "Could not set parameter on session."); + } + + ~MsQuicSession() + { + Dispose(false); + } + + private void Dispose(bool disposing) + { + if (_disposed) + { + return; + } + + MsQuicApi.Api.SessionCloseDelegate?.Invoke(_nativeObjPtr); + _nativeObjPtr = IntPtr.Zero; + + _disposed = true; + } + } +} diff --git a/src/Shared/runtime/Quic/Implementations/MsQuic/Internal/QuicExceptionHelpers.cs b/src/Shared/runtime/Quic/Implementations/MsQuic/Internal/QuicExceptionHelpers.cs new file mode 100644 index 0000000000..bca44c017d --- /dev/null +++ b/src/Shared/runtime/Quic/Implementations/MsQuic/Internal/QuicExceptionHelpers.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable +namespace System.Net.Quic.Implementations.MsQuic.Internal +{ + internal static class QuicExceptionHelpers + { + internal static void ThrowIfFailed(uint status, string? message = null, Exception? innerException = null) + { + if (!MsQuicStatusHelper.SuccessfulStatusCode(status)) + { + throw new QuicException($"{message} Error Code: {MsQuicStatusCodes.GetError(status)}"); + } + } + } +} diff --git a/src/Shared/runtime/Quic/Implementations/MsQuic/Internal/ResettableCompletionSource.cs b/src/Shared/runtime/Quic/Implementations/MsQuic/Internal/ResettableCompletionSource.cs new file mode 100644 index 0000000000..4164663df9 --- /dev/null +++ b/src/Shared/runtime/Quic/Implementations/MsQuic/Internal/ResettableCompletionSource.cs @@ -0,0 +1,86 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable +using System.Threading.Tasks; +using System.Threading.Tasks.Sources; + +namespace System.Net.Quic.Implementations.MsQuic.Internal +{ + /// + /// A resettable completion source which can be completed multiple times. + /// Used to make methods async between completed events and their associated async method. + /// + internal class ResettableCompletionSource : IValueTaskSource, IValueTaskSource + { + protected ManualResetValueTaskSourceCore _valueTaskSource; + + public ResettableCompletionSource() + { + _valueTaskSource.RunContinuationsAsynchronously = true; + } + + public ValueTask GetValueTask() + { + return new ValueTask(this, _valueTaskSource.Version); + } + + public ValueTask GetTypelessValueTask() + { + return new ValueTask(this, _valueTaskSource.Version); + } + + public ValueTaskSourceStatus GetStatus(short token) + { + return _valueTaskSource.GetStatus(token); + } + + public void OnCompleted(Action continuation, object? state, short token, ValueTaskSourceOnCompletedFlags flags) + { + _valueTaskSource.OnCompleted(continuation, state, token, flags); + } + + public void Complete(T result) + { + _valueTaskSource.SetResult(result); + } + + public void CompleteException(Exception ex) + { + _valueTaskSource.SetException(ex); + } + + public T GetResult(short token) + { + bool isValid = token == _valueTaskSource.Version; + try + { + return _valueTaskSource.GetResult(token); + } + finally + { + if (isValid) + { + _valueTaskSource.Reset(); + } + } + } + + void IValueTaskSource.GetResult(short token) + { + bool isValid = token == _valueTaskSource.Version; + try + { + _valueTaskSource.GetResult(token); + } + finally + { + if (isValid) + { + _valueTaskSource.Reset(); + } + } + } + } + } diff --git a/src/Shared/runtime/Quic/Implementations/MsQuic/MsQuicConnection.cs b/src/Shared/runtime/Quic/Implementations/MsQuic/MsQuicConnection.cs new file mode 100644 index 0000000000..b7bd07d901 --- /dev/null +++ b/src/Shared/runtime/Quic/Implementations/MsQuic/MsQuicConnection.cs @@ -0,0 +1,417 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable +using System.IO; +using System.Net.Quic.Implementations.MsQuic.Internal; +using System.Net.Security; +using System.Runtime.InteropServices; +using System.Security.Cryptography.X509Certificates; +using System.Threading; +using System.Threading.Channels; +using System.Threading.Tasks; +using static System.Net.Quic.Implementations.MsQuic.Internal.MsQuicNativeMethods; + +namespace System.Net.Quic.Implementations.MsQuic +{ + internal sealed class MsQuicConnection : QuicConnectionProvider + { + private MsQuicSession? _session; + + // Pointer to the underlying connection + // TODO replace all IntPtr with SafeHandles + private IntPtr _ptr; + + // Handle to this object for native callbacks. + private GCHandle _handle; + + // Delegate that wraps the static function that will be called when receiving an event. + // TODO investigate if the delegate can be static instead. + private ConnectionCallbackDelegate? _connectionDelegate; + + // Endpoint to either connect to or the endpoint already accepted. + private IPEndPoint? _localEndPoint; + private readonly IPEndPoint _remoteEndPoint; + + private readonly ResettableCompletionSource _connectTcs = new ResettableCompletionSource(); + private readonly ResettableCompletionSource _shutdownTcs = new ResettableCompletionSource(); + + private bool _disposed; + private bool _connected; + private MsQuicSecurityConfig? _securityConfig; + private long _abortErrorCode = -1; + + // Queue for accepted streams + private readonly Channel _acceptQueue = Channel.CreateUnbounded(new UnboundedChannelOptions() + { + SingleReader = true, + SingleWriter = true, + }); + + // constructor for inbound connections + public MsQuicConnection(IPEndPoint localEndPoint, IPEndPoint remoteEndPoint, IntPtr nativeObjPtr) + { + if (NetEventSource.IsEnabled) NetEventSource.Enter(this); + _localEndPoint = localEndPoint; + _remoteEndPoint = remoteEndPoint; + _ptr = nativeObjPtr; + + SetCallbackHandler(); + SetIdleTimeout(TimeSpan.FromSeconds(120)); + if (NetEventSource.IsEnabled) NetEventSource.Exit(this); + } + + // constructor for outbound connections + public MsQuicConnection(QuicClientConnectionOptions options) + { + if (NetEventSource.IsEnabled) NetEventSource.Enter(this); + + // TODO need to figure out if/how we want to expose sessions + // Creating a session per connection isn't ideal. + _session = new MsQuicSession(); + _ptr = _session.ConnectionOpen(options); + _remoteEndPoint = options.RemoteEndPoint!; + + SetCallbackHandler(); + SetIdleTimeout(options.IdleTimeout); + + if (NetEventSource.IsEnabled) NetEventSource.Exit(this); + } + + internal override IPEndPoint LocalEndPoint + { + get + { + return new IPEndPoint(_localEndPoint!.Address, _localEndPoint.Port); + } + } + + internal async ValueTask SetSecurityConfigForConnection(X509Certificate cert, string? certFilePath, string? privateKeyFilePath) + { + _securityConfig = await MsQuicApi.Api.CreateSecurityConfig(cert, certFilePath, privateKeyFilePath).ConfigureAwait(false); + // TODO this isn't being set correctly + MsQuicParameterHelpers.SetSecurityConfig(MsQuicApi.Api, _ptr, (uint)QUIC_PARAM_LEVEL.CONNECTION, (uint)QUIC_PARAM_CONN.SEC_CONFIG, _securityConfig!.NativeObjPtr); + } + + internal override IPEndPoint RemoteEndPoint => new IPEndPoint(_remoteEndPoint.Address, _remoteEndPoint.Port); + + internal override SslApplicationProtocol NegotiatedApplicationProtocol => throw new NotImplementedException(); + + internal override bool Connected => _connected; + + internal uint HandleEvent(ref ConnectionEvent connectionEvent) + { + uint status = MsQuicStatusCodes.Success; + try + { + switch (connectionEvent.Type) + { + // Connection is connected, can start to create streams. + case QUIC_CONNECTION_EVENT.CONNECTED: + { + status = HandleEventConnected( + connectionEvent); + } + break; + + // Connection is being closed by the transport + case QUIC_CONNECTION_EVENT.SHUTDOWN_INITIATED_BY_TRANSPORT: + { + status = HandleEventShutdownInitiatedByTransport( + connectionEvent); + } + break; + + // Connection is being closed by the peer + case QUIC_CONNECTION_EVENT.SHUTDOWN_INITIATED_BY_PEER: + { + status = HandleEventShutdownInitiatedByPeer( + connectionEvent); + } + break; + + // Connection has been shutdown + case QUIC_CONNECTION_EVENT.SHUTDOWN_COMPLETE: + { + status = HandleEventShutdownComplete( + connectionEvent); + } + break; + + case QUIC_CONNECTION_EVENT.PEER_STREAM_STARTED: + { + status = HandleEventNewStream( + connectionEvent); + } + break; + + case QUIC_CONNECTION_EVENT.STREAMS_AVAILABLE: + { + status = HandleEventStreamsAvailable( + connectionEvent); + } + break; + + default: + break; + } + } + catch (Exception) + { + // TODO we may want to either add a debug assert here or return specific error codes + // based on the exception caught. + return MsQuicStatusCodes.InternalError; + } + + return status; + } + + private uint HandleEventConnected(ConnectionEvent connectionEvent) + { + if (NetEventSource.IsEnabled) NetEventSource.Enter(this); + + SOCKADDR_INET inetAddress = MsQuicParameterHelpers.GetINetParam(MsQuicApi.Api, _ptr, (uint)QUIC_PARAM_LEVEL.CONNECTION, (uint)QUIC_PARAM_CONN.LOCAL_ADDRESS); + _localEndPoint = MsQuicAddressHelpers.INetToIPEndPoint(inetAddress); + + _connected = true; + // I don't believe we need to lock here because + // handle event connected will not be called at the same time as + // handle event shutdown initiated by transport + _connectTcs.Complete(MsQuicStatusCodes.Success); + + if (NetEventSource.IsEnabled) NetEventSource.Exit(this); + return MsQuicStatusCodes.Success; + } + + private uint HandleEventShutdownInitiatedByTransport(ConnectionEvent connectionEvent) + { + if (NetEventSource.IsEnabled) NetEventSource.Enter(this); + + if (!_connected) + { + _connectTcs.CompleteException(new IOException("Connection has been shutdown.")); + } + + _acceptQueue.Writer.Complete(); + + if (NetEventSource.IsEnabled) NetEventSource.Exit(this); + + return MsQuicStatusCodes.Success; + } + + private uint HandleEventShutdownInitiatedByPeer(ConnectionEvent connectionEvent) + { + _abortErrorCode = connectionEvent.Data.ShutdownBeginPeer.ErrorCode; + _acceptQueue.Writer.Complete(); + return MsQuicStatusCodes.Success; + } + + private uint HandleEventShutdownComplete(ConnectionEvent connectionEvent) + { + if (NetEventSource.IsEnabled) NetEventSource.Enter(this); + + _shutdownTcs.Complete(MsQuicStatusCodes.Success); + + if (NetEventSource.IsEnabled) NetEventSource.Exit(this); + return MsQuicStatusCodes.Success; + } + + private uint HandleEventNewStream(ConnectionEvent connectionEvent) + { + if (NetEventSource.IsEnabled) NetEventSource.Enter(this); + + MsQuicStream msQuicStream = new MsQuicStream(this, connectionEvent.StreamFlags, connectionEvent.Data.NewStream.Stream, inbound: true); + + _acceptQueue.Writer.TryWrite(msQuicStream); + if (NetEventSource.IsEnabled) NetEventSource.Exit(this); + + return MsQuicStatusCodes.Success; + } + + private uint HandleEventStreamsAvailable(ConnectionEvent connectionEvent) + { + return MsQuicStatusCodes.Success; + } + + internal override async ValueTask AcceptStreamAsync(CancellationToken cancellationToken = default) + { + if (NetEventSource.IsEnabled) NetEventSource.Enter(this); + + ThrowIfDisposed(); + + MsQuicStream stream; + + try + { + stream = await _acceptQueue.Reader.ReadAsync(cancellationToken).ConfigureAwait(false); + } + catch (ChannelClosedException) + { + throw _abortErrorCode switch + { + -1 => new QuicOperationAbortedException(), // Shutdown initiated by us. + long err => new QuicConnectionAbortedException(err) // Shutdown initiated by peer. + }; + } + + if (NetEventSource.IsEnabled) NetEventSource.Exit(this); + return stream; + } + + internal override QuicStreamProvider OpenUnidirectionalStream() + { + ThrowIfDisposed(); + + return StreamOpen(QUIC_STREAM_OPEN_FLAG.UNIDIRECTIONAL); + } + + internal override QuicStreamProvider OpenBidirectionalStream() + { + ThrowIfDisposed(); + + return StreamOpen(QUIC_STREAM_OPEN_FLAG.NONE); + } + + internal override long GetRemoteAvailableUnidirectionalStreamCount() + { + return MsQuicParameterHelpers.GetUShortParam(MsQuicApi.Api, _ptr, (uint)QUIC_PARAM_LEVEL.CONNECTION, (uint)QUIC_PARAM_CONN.PEER_UNIDI_STREAM_COUNT); + } + + internal override long GetRemoteAvailableBidirectionalStreamCount() + { + return MsQuicParameterHelpers.GetUShortParam(MsQuicApi.Api, _ptr, (uint)QUIC_PARAM_LEVEL.CONNECTION, (uint)QUIC_PARAM_CONN.PEER_BIDI_STREAM_COUNT); + } + + private unsafe void SetIdleTimeout(TimeSpan timeout) + { + MsQuicParameterHelpers.SetULongParam(MsQuicApi.Api, _ptr, (uint)QUIC_PARAM_LEVEL.CONNECTION, (uint)QUIC_PARAM_CONN.IDLE_TIMEOUT, (ulong)timeout.TotalMilliseconds); + } + + internal override ValueTask ConnectAsync(CancellationToken cancellationToken = default) + { + ThrowIfDisposed(); + + QuicExceptionHelpers.ThrowIfFailed( + MsQuicApi.Api.ConnectionStartDelegate( + _ptr, + (ushort)_remoteEndPoint.AddressFamily, + _remoteEndPoint.Address.ToString(), + (ushort)_remoteEndPoint.Port), + "Failed to connect to peer."); + + return _connectTcs.GetTypelessValueTask(); + } + + private MsQuicStream StreamOpen( + QUIC_STREAM_OPEN_FLAG flags) + { + if (NetEventSource.IsEnabled) NetEventSource.Enter(this); + + IntPtr streamPtr = IntPtr.Zero; + QuicExceptionHelpers.ThrowIfFailed( + MsQuicApi.Api.StreamOpenDelegate( + _ptr, + (uint)flags, + MsQuicStream.NativeCallbackHandler, + IntPtr.Zero, + out streamPtr), + "Failed to open stream to peer."); + + MsQuicStream stream = new MsQuicStream(this, flags, streamPtr, inbound: false); + + if (NetEventSource.IsEnabled) NetEventSource.Exit(this); + return stream; + } + + private void SetCallbackHandler() + { + _handle = GCHandle.Alloc(this); + _connectionDelegate = new ConnectionCallbackDelegate(NativeCallbackHandler); + MsQuicApi.Api.SetCallbackHandlerDelegate( + _ptr, + _connectionDelegate, + GCHandle.ToIntPtr(_handle)); + } + + private ValueTask ShutdownAsync( + QUIC_CONNECTION_SHUTDOWN_FLAG Flags, + long ErrorCode) + { + if (NetEventSource.IsEnabled) NetEventSource.Enter(this); + + uint status = MsQuicApi.Api.ConnectionShutdownDelegate( + _ptr, + (uint)Flags, + ErrorCode); + QuicExceptionHelpers.ThrowIfFailed(status, "Failed to shutdown connection."); + + if (NetEventSource.IsEnabled) NetEventSource.Exit(this); + return _shutdownTcs.GetTypelessValueTask(); + } + + internal static uint NativeCallbackHandler( + IntPtr connection, + IntPtr context, + ref ConnectionEvent connectionEventStruct) + { + GCHandle handle = GCHandle.FromIntPtr(context); + MsQuicConnection quicConnection = (MsQuicConnection)handle.Target!; + return quicConnection.HandleEvent(ref connectionEventStruct); + } + + public override void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + ~MsQuicConnection() + { + Dispose(false); + } + + private void Dispose(bool disposing) + { + if (_disposed) + { + return; + } + + if (NetEventSource.IsEnabled) NetEventSource.Enter(this); + + if (_ptr != IntPtr.Zero) + { + MsQuicApi.Api.ConnectionCloseDelegate?.Invoke(_ptr); + } + + _ptr = IntPtr.Zero; + + if (disposing) + { + _handle.Free(); + _session?.Dispose(); + _securityConfig?.Dispose(); + } + + _disposed = true; + + if (NetEventSource.IsEnabled) NetEventSource.Exit(this); + } + + internal override ValueTask CloseAsync(long errorCode, CancellationToken cancellationToken = default) + { + ThrowIfDisposed(); + + return ShutdownAsync(QUIC_CONNECTION_SHUTDOWN_FLAG.NONE, errorCode); + } + + private void ThrowIfDisposed() + { + if (_disposed) + { + throw new ObjectDisposedException(nameof(MsQuicStream)); + } + } + } +} diff --git a/src/Shared/runtime/Quic/Implementations/MsQuic/MsQuicImplementationProvider.cs b/src/Shared/runtime/Quic/Implementations/MsQuic/MsQuicImplementationProvider.cs new file mode 100644 index 0000000000..55c5e524ec --- /dev/null +++ b/src/Shared/runtime/Quic/Implementations/MsQuic/MsQuicImplementationProvider.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Net.Quic.Implementations.MsQuic.Internal; +using System.Net.Security; + +namespace System.Net.Quic.Implementations.MsQuic +{ + internal sealed class MsQuicImplementationProvider : QuicImplementationProvider + { + internal override QuicListenerProvider CreateListener(QuicListenerOptions options) + { + return new MsQuicListener(options); + } + + internal override QuicConnectionProvider CreateConnection(QuicClientConnectionOptions options) + { + return new MsQuicConnection(options); + } + } +} diff --git a/src/Shared/runtime/Quic/Implementations/MsQuic/MsQuicListener.cs b/src/Shared/runtime/Quic/Implementations/MsQuic/MsQuicListener.cs new file mode 100644 index 0000000000..2418a71ed6 --- /dev/null +++ b/src/Shared/runtime/Quic/Implementations/MsQuic/MsQuicListener.cs @@ -0,0 +1,214 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable +using System.Net.Quic.Implementations.MsQuic.Internal; +using System.Net.Security; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Channels; +using System.Threading.Tasks; +using static System.Net.Quic.Implementations.MsQuic.Internal.MsQuicNativeMethods; + +namespace System.Net.Quic.Implementations.MsQuic +{ + internal sealed class MsQuicListener : QuicListenerProvider, IDisposable + { + // Security configuration for MsQuic + private MsQuicSession _session; + + // Pointer to the underlying listener + // TODO replace all IntPtr with SafeHandles + private IntPtr _ptr; + + // Handle to this object for native callbacks. + private GCHandle _handle; + + // Delegate that wraps the static function that will be called when receiving an event. + private ListenerCallbackDelegate? _listenerDelegate; + + // Ssl listening options (ALPN, cert, etc) + private SslServerAuthenticationOptions _sslOptions; + + private QuicListenerOptions _options; + private volatile bool _disposed; + private IPEndPoint _listenEndPoint; + + private readonly Channel _acceptConnectionQueue; + + internal MsQuicListener(QuicListenerOptions options) + { + _session = new MsQuicSession(); + _acceptConnectionQueue = Channel.CreateBounded(new BoundedChannelOptions(options.ListenBacklog) + { + SingleReader = true, + SingleWriter = true + }); + + _options = options; + _sslOptions = options.ServerAuthenticationOptions!; + _listenEndPoint = options.ListenEndPoint!; + + _ptr = _session.ListenerOpen(options); + } + + internal override IPEndPoint ListenEndPoint + { + get + { + return new IPEndPoint(_listenEndPoint.Address, _listenEndPoint.Port); + } + } + + internal override async ValueTask AcceptConnectionAsync(CancellationToken cancellationToken = default) + { + if (NetEventSource.IsEnabled) NetEventSource.Enter(this); + + ThrowIfDisposed(); + + MsQuicConnection connection; + + try + { + connection = await _acceptConnectionQueue.Reader.ReadAsync(cancellationToken).ConfigureAwait(false); + } + catch (ChannelClosedException) + { + throw new QuicOperationAbortedException(); + } + + await connection.SetSecurityConfigForConnection(_sslOptions.ServerCertificate!, + _options.CertificateFilePath, + _options.PrivateKeyFilePath).ConfigureAwait(false); + + if (NetEventSource.IsEnabled) NetEventSource.Exit(this); + return connection; + } + + public override void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + ~MsQuicListener() + { + Dispose(false); + } + + private void Dispose(bool disposing) + { + if (_disposed) + { + return; + } + + StopAcceptingConnections(); + + if (_ptr != IntPtr.Zero) + { + MsQuicApi.Api.ListenerStopDelegate(_ptr); + MsQuicApi.Api.ListenerCloseDelegate(_ptr); + } + + _ptr = IntPtr.Zero; + + // TODO this call to session dispose hangs. + //_session.Dispose(); + _disposed = true; + } + + internal override void Start() + { + ThrowIfDisposed(); + + SetCallbackHandler(); + + SOCKADDR_INET address = MsQuicAddressHelpers.IPEndPointToINet(_listenEndPoint); + + QuicExceptionHelpers.ThrowIfFailed(MsQuicApi.Api.ListenerStartDelegate( + _ptr, + ref address), + "Failed to start listener."); + + SetListenPort(); + } + + internal override void Close() + { + ThrowIfDisposed(); + + MsQuicApi.Api.ListenerStopDelegate(_ptr); + } + + private unsafe void SetListenPort() + { + SOCKADDR_INET inetAddress = MsQuicParameterHelpers.GetINetParam(MsQuicApi.Api, _ptr, (uint)QUIC_PARAM_LEVEL.LISTENER, (uint)QUIC_PARAM_LISTENER.LOCAL_ADDRESS); + + _listenEndPoint = MsQuicAddressHelpers.INetToIPEndPoint(inetAddress); + } + + internal unsafe uint ListenerCallbackHandler( + ref ListenerEvent evt) + { + try + { + switch (evt.Type) + { + case QUIC_LISTENER_EVENT.NEW_CONNECTION: + { + NewConnectionInfo connectionInfo = *(NewConnectionInfo*)evt.Data.NewConnection.Info; + IPEndPoint localEndPoint = MsQuicAddressHelpers.INetToIPEndPoint(*(SOCKADDR_INET*)connectionInfo.LocalAddress); + IPEndPoint remoteEndPoint = MsQuicAddressHelpers.INetToIPEndPoint(*(SOCKADDR_INET*)connectionInfo.RemoteAddress); + MsQuicConnection msQuicConnection = new MsQuicConnection(localEndPoint, remoteEndPoint, evt.Data.NewConnection.Connection); + _acceptConnectionQueue.Writer.TryWrite(msQuicConnection); + } + // Always pend the new connection to wait for the security config to be resolved + // TODO this doesn't need to be async always + return MsQuicStatusCodes.Pending; + default: + return MsQuicStatusCodes.InternalError; + } + } + catch (Exception) + { + return MsQuicStatusCodes.InternalError; + } + } + + private void StopAcceptingConnections() + { + _acceptConnectionQueue.Writer.TryComplete(); + } + + internal static uint NativeCallbackHandler( + IntPtr listener, + IntPtr context, + ref ListenerEvent connectionEventStruct) + { + GCHandle handle = GCHandle.FromIntPtr(context); + MsQuicListener quicListener = (MsQuicListener)handle.Target!; + + return quicListener.ListenerCallbackHandler(ref connectionEventStruct); + } + + internal void SetCallbackHandler() + { + _handle = GCHandle.Alloc(this); + _listenerDelegate = new ListenerCallbackDelegate(NativeCallbackHandler); + MsQuicApi.Api.SetCallbackHandlerDelegate( + _ptr, + _listenerDelegate, + GCHandle.ToIntPtr(_handle)); + } + + private void ThrowIfDisposed() + { + if (_disposed) + { + throw new ObjectDisposedException(nameof(MsQuicStream)); + } + } + } +} diff --git a/src/Shared/runtime/Quic/Implementations/MsQuic/MsQuicStream.cs b/src/Shared/runtime/Quic/Implementations/MsQuic/MsQuicStream.cs new file mode 100644 index 0000000000..6eed08f02d --- /dev/null +++ b/src/Shared/runtime/Quic/Implementations/MsQuic/MsQuicStream.cs @@ -0,0 +1,1043 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable +using System.Buffers; +using System.Collections.Generic; +using System.Diagnostics; +using System.Net.Quic.Implementations.MsQuic.Internal; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; +using static System.Net.Quic.Implementations.MsQuic.Internal.MsQuicNativeMethods; + +namespace System.Net.Quic.Implementations.MsQuic +{ + internal sealed class MsQuicStream : QuicStreamProvider + { + // Pointer to the underlying stream + // TODO replace all IntPtr with SafeHandles + private readonly IntPtr _ptr; + + // Handle to this object for native callbacks. + private GCHandle _handle; + + // Delegate that wraps the static function that will be called when receiving an event. + private StreamCallbackDelegate? _callback; + + // Backing for StreamId + private long _streamId = -1; + + // Resettable completions to be used for multiple calls to send, start, and shutdown. + private readonly ResettableCompletionSource _sendResettableCompletionSource; + + // Resettable completions to be used for multiple calls to receive. + private readonly ResettableCompletionSource _receiveResettableCompletionSource; + + private readonly ResettableCompletionSource _shutdownWriteResettableCompletionSource; + + // Buffers to hold during a call to send. + private MemoryHandle[] _bufferArrays = new MemoryHandle[1]; + private QuicBuffer[] _sendQuicBuffers = new QuicBuffer[1]; + + // Handle to hold when sending. + private GCHandle _sendHandle; + + // Used to check if StartAsync has been called. + private bool _started; + + private ReadState _readState; + private long _readErrorCode = -1; + + private ShutdownWriteState _shutdownState; + + private SendState _sendState; + private long _sendErrorCode = -1; + + // Used by the class to indicate that the stream is m_Readable. + private readonly bool _canRead; + + // Used by the class to indicate that the stream is writable. + private readonly bool _canWrite; + + private volatile bool _disposed = false; + + private List _receiveQuicBuffers = new List(); + + // TODO consider using Interlocked.Exchange instead of a sync if we can avoid it. + private object _sync = new object(); + + // Creates a new MsQuicStream + internal MsQuicStream(MsQuicConnection connection, QUIC_STREAM_OPEN_FLAG flags, IntPtr nativeObjPtr, bool inbound) + { + Debug.Assert(connection != null); + + _ptr = nativeObjPtr; + + _sendResettableCompletionSource = new ResettableCompletionSource(); + _receiveResettableCompletionSource = new ResettableCompletionSource(); + _shutdownWriteResettableCompletionSource = new ResettableCompletionSource(); + SetCallbackHandler(); + + if (inbound) + { + _started = true; + _canWrite = !flags.HasFlag(QUIC_STREAM_OPEN_FLAG.UNIDIRECTIONAL); + _canRead = true; + } + else + { + _canWrite = true; + _canRead = !flags.HasFlag(QUIC_STREAM_OPEN_FLAG.UNIDIRECTIONAL); + StartWrites(); + } + } + + internal override bool CanRead => _canRead; + + internal override bool CanWrite => _canWrite; + + internal override long StreamId + { + get + { + ThrowIfDisposed(); + + if (_streamId == -1) + { + _streamId = GetStreamId(); + } + + return _streamId; + } + } + + internal override ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default) + { + return WriteAsync(buffer, endStream: false, cancellationToken); + } + + internal override ValueTask WriteAsync(ReadOnlySequence buffers, CancellationToken cancellationToken = default) + { + return WriteAsync(buffers, endStream: false, cancellationToken); + } + + internal override async ValueTask WriteAsync(ReadOnlySequence buffers, bool endStream, CancellationToken cancellationToken = default) + { + if (NetEventSource.IsEnabled) NetEventSource.Enter(this); + + ThrowIfDisposed(); + + using CancellationTokenRegistration registration = await HandleWriteStartState(cancellationToken).ConfigureAwait(false); + + await SendReadOnlySequenceAsync(buffers, endStream ? QUIC_SEND_FLAG.FIN : QUIC_SEND_FLAG.NONE).ConfigureAwait(false); + + HandleWriteCompletedState(); + + if (NetEventSource.IsEnabled) NetEventSource.Exit(this); + } + + internal override ValueTask WriteAsync(ReadOnlyMemory> buffers, CancellationToken cancellationToken = default) + { + return WriteAsync(buffers, endStream: false, cancellationToken); + } + + internal override async ValueTask WriteAsync(ReadOnlyMemory> buffers, bool endStream, CancellationToken cancellationToken = default) + { + if (NetEventSource.IsEnabled) NetEventSource.Enter(this); + + ThrowIfDisposed(); + + using CancellationTokenRegistration registration = await HandleWriteStartState(cancellationToken).ConfigureAwait(false); + + await SendReadOnlyMemoryListAsync(buffers, endStream ? QUIC_SEND_FLAG.FIN : QUIC_SEND_FLAG.NONE).ConfigureAwait(false); + + HandleWriteCompletedState(); + + if (NetEventSource.IsEnabled) NetEventSource.Exit(this); + } + + internal override async ValueTask WriteAsync(ReadOnlyMemory buffer, bool endStream, CancellationToken cancellationToken = default) + { + if (NetEventSource.IsEnabled) NetEventSource.Enter(this); + + ThrowIfDisposed(); + + using CancellationTokenRegistration registration = await HandleWriteStartState(cancellationToken).ConfigureAwait(false); + + await SendReadOnlyMemoryAsync(buffer, endStream ? QUIC_SEND_FLAG.FIN : QUIC_SEND_FLAG.NONE).ConfigureAwait(false); + + HandleWriteCompletedState(); + + if (NetEventSource.IsEnabled) NetEventSource.Exit(this); + } + + private async ValueTask HandleWriteStartState(CancellationToken cancellationToken) + { + if (!_canWrite) + { + throw new InvalidOperationException("Writing is not allowed on stream."); + } + + lock (_sync) + { + if (_sendState == SendState.Aborted) + { + throw new OperationCanceledException("Sending has already been aborted on the stream"); + } + } + + CancellationTokenRegistration registration = cancellationToken.Register(() => + { + bool shouldComplete = false; + lock (_sync) + { + if (_sendState == SendState.None) + { + _sendState = SendState.Aborted; + shouldComplete = true; + } + } + + if (shouldComplete) + { + _sendResettableCompletionSource.CompleteException(new OperationCanceledException("Write was canceled", cancellationToken)); + } + }); + + // Make sure start has completed + if (!_started) + { + await _sendResettableCompletionSource.GetTypelessValueTask().ConfigureAwait(false); + _started = true; + } + + return registration; + } + + private void HandleWriteCompletedState() + { + lock (_sync) + { + if (_sendState == SendState.Finished) + { + _sendState = SendState.None; + } + } + } + + internal override async ValueTask ReadAsync(Memory destination, CancellationToken cancellationToken = default) + { + if (NetEventSource.IsEnabled) NetEventSource.Enter(this); + + ThrowIfDisposed(); + + if (!_canRead) + { + throw new InvalidOperationException("Reading is not allowed on stream."); + } + + lock (_sync) + { + if (_readState == ReadState.ReadsCompleted) + { + if (NetEventSource.IsEnabled) NetEventSource.Exit(this); + return 0; + } + else if (_readState == ReadState.Aborted) + { + throw _readErrorCode switch + { + -1 => new QuicOperationAbortedException(), + long err => new QuicStreamAbortedException(err) + }; + } + } + + using CancellationTokenRegistration registration = cancellationToken.Register(() => + { + bool shouldComplete = false; + lock (_sync) + { + if (_readState == ReadState.None) + { + shouldComplete = true; + } + + _readState = ReadState.Aborted; + } + + if (shouldComplete) + { + _receiveResettableCompletionSource.CompleteException(new OperationCanceledException("Read was canceled", cancellationToken)); + } + }); + + // TODO there could potentially be a perf gain by storing the buffer from the inital read + // This reduces the amount of async calls, however it makes it so MsQuic holds onto the buffers + // longer than it needs to. We will need to benchmark this. + int length = (int)await _receiveResettableCompletionSource.GetValueTask().ConfigureAwait(false); + + int actual = Math.Min(length, destination.Length); + + static unsafe void CopyToBuffer(Span destinationBuffer, List sourceBuffers) + { + Span slicedBuffer = destinationBuffer; + for (int i = 0; i < sourceBuffers.Count; i++) + { + QuicBuffer nativeBuffer = sourceBuffers[i]; + int length = Math.Min((int)nativeBuffer.Length, slicedBuffer.Length); + new Span(nativeBuffer.Buffer, length).CopyTo(slicedBuffer); + if (length < nativeBuffer.Length) + { + // The buffer passed in was larger that the received data, return + return; + } + slicedBuffer = slicedBuffer.Slice(length); + } + } + + CopyToBuffer(destination.Span, _receiveQuicBuffers); + + lock (_sync) + { + if (_readState == ReadState.IndividualReadComplete) + { + _receiveQuicBuffers.Clear(); + ReceiveComplete(actual); + EnableReceive(); + _readState = ReadState.None; + } + } + + if (NetEventSource.IsEnabled) NetEventSource.Exit(this); + + return actual; + } + + // TODO do we want this to be a synchronization mechanism to cancel a pending read + // If so, we need to complete the read here as well. + internal override void AbortRead(long errorCode) + { + if (NetEventSource.IsEnabled) NetEventSource.Enter(this); + + ThrowIfDisposed(); + + lock (_sync) + { + _readState = ReadState.Aborted; + } + + MsQuicApi.Api.StreamShutdownDelegate(_ptr, (uint)QUIC_STREAM_SHUTDOWN_FLAG.ABORT_RECV, errorCode); + + if (NetEventSource.IsEnabled) NetEventSource.Exit(this); + } + + internal override void AbortWrite(long errorCode) + { + if (NetEventSource.IsEnabled) NetEventSource.Enter(this); + + ThrowIfDisposed(); + + bool shouldComplete = false; + + lock (_sync) + { + if (_shutdownState == ShutdownWriteState.None) + { + _shutdownState = ShutdownWriteState.Canceled; + shouldComplete = true; + } + } + + if (shouldComplete) + { + _shutdownWriteResettableCompletionSource.CompleteException(new QuicStreamAbortedException("Shutdown was aborted.", errorCode)); + } + + MsQuicApi.Api.StreamShutdownDelegate(_ptr, (uint)QUIC_STREAM_SHUTDOWN_FLAG.ABORT_SEND, errorCode); + + if (NetEventSource.IsEnabled) NetEventSource.Exit(this); + } + + internal override ValueTask ShutdownWriteCompleted(CancellationToken cancellationToken = default) + { + if (NetEventSource.IsEnabled) NetEventSource.Enter(this); + + ThrowIfDisposed(); + + // TODO do anything to stop writes? + using CancellationTokenRegistration registration = cancellationToken.Register(() => + { + bool shouldComplete = false; + lock (_sync) + { + if (_shutdownState == ShutdownWriteState.None) + { + _shutdownState = ShutdownWriteState.Canceled; + shouldComplete = true; + } + } + + if (shouldComplete) + { + _shutdownWriteResettableCompletionSource.CompleteException(new OperationCanceledException("Shutdown was canceled", cancellationToken)); + } + }); + + if (NetEventSource.IsEnabled) NetEventSource.Exit(this); + + return _shutdownWriteResettableCompletionSource.GetTypelessValueTask(); + } + + internal override void Shutdown() + { + ThrowIfDisposed(); + + MsQuicApi.Api.StreamShutdownDelegate(_ptr, (uint)QUIC_STREAM_SHUTDOWN_FLAG.GRACEFUL, errorCode: 0); + } + + // TODO consider removing sync-over-async with blocking calls. + internal override int Read(Span buffer) + { + ThrowIfDisposed(); + + return ReadAsync(buffer.ToArray()).AsTask().GetAwaiter().GetResult(); + } + + internal override void Write(ReadOnlySpan buffer) + { + ThrowIfDisposed(); + + // TODO: optimize this. + WriteAsync(buffer.ToArray()).AsTask().GetAwaiter().GetResult(); + } + + // MsQuic doesn't support explicit flushing + internal override void Flush() + { + ThrowIfDisposed(); + } + + // MsQuic doesn't support explicit flushing + internal override Task FlushAsync(CancellationToken cancellationToken = default) + { + ThrowIfDisposed(); + + return default!; + } + + public override ValueTask DisposeAsync() + { + if (_disposed) + { + return default; + } + + if (NetEventSource.IsEnabled) NetEventSource.Enter(this); + + CleanupSendState(); + + if (_ptr != IntPtr.Zero) + { + // TODO resolve graceful vs abortive dispose here. Will file a separate issue. + //MsQuicApi.Api._streamShutdownDelegate(_ptr, (uint)QUIC_STREAM_SHUTDOWN_FLAG.ABORT, 1); + MsQuicApi.Api.StreamCloseDelegate?.Invoke(_ptr); + } + + _handle.Free(); + + _disposed = true; + if (NetEventSource.IsEnabled) NetEventSource.Exit(this); + + return default; + } + + public override void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + ~MsQuicStream() + { + Dispose(false); + } + + private void Dispose(bool disposing) + { + if (_disposed) + { + return; + } + + if (NetEventSource.IsEnabled) NetEventSource.Enter(this); + + CleanupSendState(); + + if (_ptr != IntPtr.Zero) + { + // TODO resolve graceful vs abortive dispose here. Will file a separate issue. + //MsQuicApi.Api._streamShutdownDelegate(_ptr, (uint)QUIC_STREAM_SHUTDOWN_FLAG.ABORT, 1); + MsQuicApi.Api.StreamCloseDelegate?.Invoke(_ptr); + } + + _handle.Free(); + + if (NetEventSource.IsEnabled) NetEventSource.Exit(this); + + _disposed = true; + } + + private void EnableReceive() + { + MsQuicApi.Api.StreamReceiveSetEnabledDelegate(_ptr, enabled: true); + } + + internal static uint NativeCallbackHandler( + IntPtr stream, + IntPtr context, + ref StreamEvent streamEvent) + { + var handle = GCHandle.FromIntPtr(context); + var quicStream = (MsQuicStream)handle.Target!; + + return quicStream.HandleEvent(ref streamEvent); + } + + private uint HandleEvent(ref StreamEvent evt) + { + uint status = MsQuicStatusCodes.Success; + + try + { + switch (evt.Type) + { + // Stream has started. + // Will only be done for outbound streams (inbound streams have already started) + case QUIC_STREAM_EVENT.START_COMPLETE: + status = HandleStartComplete(); + break; + // Received data on the stream + case QUIC_STREAM_EVENT.RECEIVE: + { + status = HandleEventRecv(ref evt); + } + break; + // Send has completed. + // Contains a canceled bool to indicate if the send was canceled. + case QUIC_STREAM_EVENT.SEND_COMPLETE: + { + status = HandleEventSendComplete(ref evt); + } + break; + // Peer has told us to shutdown the reading side of the stream. + case QUIC_STREAM_EVENT.PEER_SEND_SHUTDOWN: + { + status = HandleEventPeerSendShutdown(); + } + break; + // Peer has told us to abort the reading side of the stream. + case QUIC_STREAM_EVENT.PEER_SEND_ABORTED: + { + status = HandleEventPeerSendAborted(ref evt); + } + break; + // Peer has stopped receiving data, don't send anymore. + case QUIC_STREAM_EVENT.PEER_RECEIVE_ABORTED: + { + status = HandleEventPeerRecvAborted(ref evt); + } + break; + // Occurs when shutdown is completed for the send side. + // This only happens for shutdown on sending, not receiving + // Receive shutdown can only be abortive. + case QUIC_STREAM_EVENT.SEND_SHUTDOWN_COMPLETE: + { + status = HandleEventSendShutdownComplete(ref evt); + } + break; + // Shutdown for both sending and receiving is completed. + case QUIC_STREAM_EVENT.SHUTDOWN_COMPLETE: + { + status = HandleEventShutdownComplete(); + } + break; + default: + break; + } + } + catch (Exception) + { + return MsQuicStatusCodes.InternalError; + } + + return status; + } + + private unsafe uint HandleEventRecv(ref MsQuicNativeMethods.StreamEvent evt) + { + if (NetEventSource.IsEnabled) NetEventSource.Enter(this); + + StreamEventDataRecv receieveEvent = evt.Data.Recv; + for (int i = 0; i < receieveEvent.BufferCount; i++) + { + _receiveQuicBuffers.Add(receieveEvent.Buffers[i]); + } + + bool shouldComplete = false; + lock (_sync) + { + if (_readState == ReadState.None) + { + shouldComplete = true; + } + _readState = ReadState.IndividualReadComplete; + } + + if (shouldComplete) + { + _receiveResettableCompletionSource.Complete((uint)receieveEvent.TotalBufferLength); + } + + if (NetEventSource.IsEnabled) NetEventSource.Exit(this); + + return MsQuicStatusCodes.Pending; + } + + private uint HandleEventPeerRecvAborted(ref StreamEvent evt) + { + if (NetEventSource.IsEnabled) NetEventSource.Enter(this); + + bool shouldComplete = false; + lock (_sync) + { + if (_sendState == SendState.None) + { + shouldComplete = true; + } + _sendState = SendState.Aborted; + _sendErrorCode = evt.Data.PeerSendAbort.ErrorCode; + } + + if (shouldComplete) + { + _sendResettableCompletionSource.CompleteException(new QuicStreamAbortedException(_sendErrorCode)); + } + + if (NetEventSource.IsEnabled) NetEventSource.Exit(this); + + return MsQuicStatusCodes.Success; + } + + private uint HandleStartComplete() + { + if (NetEventSource.IsEnabled) NetEventSource.Enter(this); + + bool shouldComplete = false; + lock (_sync) + { + // Check send state before completing as send cancellation is shared between start and send. + if (_sendState == SendState.None) + { + shouldComplete = true; + } + } + + if (shouldComplete) + { + _sendResettableCompletionSource.Complete(MsQuicStatusCodes.Success); + } + + if (NetEventSource.IsEnabled) NetEventSource.Exit(this); + + return MsQuicStatusCodes.Success; + } + + private uint HandleEventSendShutdownComplete(ref MsQuicNativeMethods.StreamEvent evt) + { + if (NetEventSource.IsEnabled) NetEventSource.Enter(this); + bool shouldComplete = false; + lock (_sync) + { + if (_shutdownState == ShutdownWriteState.None) + { + _shutdownState = ShutdownWriteState.Finished; + shouldComplete = true; + } + } + + if (shouldComplete) + { + _shutdownWriteResettableCompletionSource.Complete(MsQuicStatusCodes.Success); + } + + if (NetEventSource.IsEnabled) NetEventSource.Exit(this); + + return MsQuicStatusCodes.Success; + } + + private uint HandleEventShutdownComplete() + { + if (NetEventSource.IsEnabled) NetEventSource.Enter(this); + + bool shouldReadComplete = false; + bool shouldShutdownWriteComplete = false; + + lock (_sync) + { + // This event won't occur within the middle of a receive. + if (NetEventSource.IsEnabled) NetEventSource.Info("Completing resettable event source."); + + if (_readState == ReadState.None) + { + shouldReadComplete = true; + } + + _readState = ReadState.ReadsCompleted; + + if (_shutdownState == ShutdownWriteState.None) + { + _shutdownState = ShutdownWriteState.Finished; + shouldShutdownWriteComplete = true; + } + } + + if (shouldReadComplete) + { + _receiveResettableCompletionSource.Complete(0); + } + + if (shouldShutdownWriteComplete) + { + _shutdownWriteResettableCompletionSource.Complete(MsQuicStatusCodes.Success); + } + + if (NetEventSource.IsEnabled) NetEventSource.Exit(this); + + return MsQuicStatusCodes.Success; + } + + private uint HandleEventPeerSendAborted(ref StreamEvent evt) + { + if (NetEventSource.IsEnabled) NetEventSource.Enter(this); + + bool shouldComplete = false; + lock (_sync) + { + if (_readState == ReadState.None) + { + shouldComplete = true; + } + _readState = ReadState.Aborted; + _readErrorCode = evt.Data.PeerSendAbort.ErrorCode; + } + + if (shouldComplete) + { + _receiveResettableCompletionSource.CompleteException(new QuicStreamAbortedException(_readErrorCode)); + } + + if (NetEventSource.IsEnabled) NetEventSource.Exit(this); + + return MsQuicStatusCodes.Success; + } + + private uint HandleEventPeerSendShutdown() + { + if (NetEventSource.IsEnabled) NetEventSource.Enter(this); + + bool shouldComplete = false; + + lock (_sync) + { + // This event won't occur within the middle of a receive. + if (NetEventSource.IsEnabled) NetEventSource.Info("Completing resettable event source."); + + if (_readState == ReadState.None) + { + shouldComplete = true; + } + + _readState = ReadState.ReadsCompleted; + } + + if (shouldComplete) + { + _receiveResettableCompletionSource.Complete(0); + } + + if (NetEventSource.IsEnabled) NetEventSource.Exit(this); + + return MsQuicStatusCodes.Success; + } + + private uint HandleEventSendComplete(ref StreamEvent evt) + { + if (NetEventSource.IsEnabled) NetEventSource.Enter(this); + + CleanupSendState(); + + // TODO throw if a write was canceled. + uint errorCode = evt.Data.SendComplete.Canceled; + + bool shouldComplete = false; + lock (_sync) + { + if (_sendState == SendState.None) + { + _sendState = SendState.Finished; + shouldComplete = true; + } + } + + if (shouldComplete) + { + _sendResettableCompletionSource.Complete(MsQuicStatusCodes.Success); + } + + if (NetEventSource.IsEnabled) NetEventSource.Exit(this); + + return MsQuicStatusCodes.Success; + } + + private void CleanupSendState() + { + if (_sendHandle.IsAllocated) + { + _sendHandle.Free(); + } + + // Callings dispose twice on a memory handle should be okay + foreach (MemoryHandle buffer in _bufferArrays) + { + buffer.Dispose(); + } + } + + private void SetCallbackHandler() + { + _handle = GCHandle.Alloc(this); + + _callback = new StreamCallbackDelegate(NativeCallbackHandler); + MsQuicApi.Api.SetCallbackHandlerDelegate( + _ptr, + _callback, + GCHandle.ToIntPtr(_handle)); + } + + // TODO prevent overlapping sends or consider supporting it. + private unsafe ValueTask SendReadOnlyMemoryAsync( + ReadOnlyMemory buffer, + QUIC_SEND_FLAG flags) + { + if (buffer.IsEmpty) + { + if ((flags & QUIC_SEND_FLAG.FIN) == QUIC_SEND_FLAG.FIN) + { + // Start graceful shutdown sequence if passed in the fin flag and there is an empty buffer. + MsQuicApi.Api.StreamShutdownDelegate(_ptr, (uint)QUIC_STREAM_SHUTDOWN_FLAG.GRACEFUL, errorCode: 0); + } + return default; + } + + MemoryHandle handle = buffer.Pin(); + _sendQuicBuffers[0].Length = (uint)buffer.Length; + _sendQuicBuffers[0].Buffer = (byte*)handle.Pointer; + + _bufferArrays[0] = handle; + + _sendHandle = GCHandle.Alloc(_sendQuicBuffers, GCHandleType.Pinned); + + var quicBufferPointer = (QuicBuffer*)Marshal.UnsafeAddrOfPinnedArrayElement(_sendQuicBuffers, 0); + + uint status = MsQuicApi.Api.StreamSendDelegate( + _ptr, + quicBufferPointer, + bufferCount: 1, + (uint)flags, + _ptr); + + if (!MsQuicStatusHelper.SuccessfulStatusCode(status)) + { + CleanupSendState(); + + // TODO this may need to be an aborted exception. + QuicExceptionHelpers.ThrowIfFailed(status, + "Could not send data to peer."); + } + + return _sendResettableCompletionSource.GetTypelessValueTask(); + } + + private unsafe ValueTask SendReadOnlySequenceAsync( + ReadOnlySequence buffers, + QUIC_SEND_FLAG flags) + { + if (buffers.IsEmpty) + { + if ((flags & QUIC_SEND_FLAG.FIN) == QUIC_SEND_FLAG.FIN) + { + // Start graceful shutdown sequence if passed in the fin flag and there is an empty buffer. + MsQuicApi.Api.StreamShutdownDelegate(_ptr, (uint)QUIC_STREAM_SHUTDOWN_FLAG.GRACEFUL, errorCode: 0); + } + return default; + } + + uint count = 0; + + foreach (ReadOnlyMemory buffer in buffers) + { + ++count; + } + + if (_sendQuicBuffers.Length < count) + { + _sendQuicBuffers = new QuicBuffer[count]; + _bufferArrays = new MemoryHandle[count]; + } + + count = 0; + + foreach (ReadOnlyMemory buffer in buffers) + { + MemoryHandle handle = buffer.Pin(); + _sendQuicBuffers[count].Length = (uint)buffer.Length; + _sendQuicBuffers[count].Buffer = (byte*)handle.Pointer; + _bufferArrays[count] = handle; + ++count; + } + + _sendHandle = GCHandle.Alloc(_sendQuicBuffers, GCHandleType.Pinned); + + var quicBufferPointer = (QuicBuffer*)Marshal.UnsafeAddrOfPinnedArrayElement(_sendQuicBuffers, 0); + + uint status = MsQuicApi.Api.StreamSendDelegate( + _ptr, + quicBufferPointer, + count, + (uint)flags, + _ptr); + + if (!MsQuicStatusHelper.SuccessfulStatusCode(status)) + { + CleanupSendState(); + + // TODO this may need to be an aborted exception. + QuicExceptionHelpers.ThrowIfFailed(status, + "Could not send data to peer."); + } + + return _sendResettableCompletionSource.GetTypelessValueTask(); + } + + private unsafe ValueTask SendReadOnlyMemoryListAsync( + ReadOnlyMemory> buffers, + QUIC_SEND_FLAG flags) + { + if (buffers.IsEmpty) + { + if ((flags & QUIC_SEND_FLAG.FIN) == QUIC_SEND_FLAG.FIN) + { + // Start graceful shutdown sequence if passed in the fin flag and there is an empty buffer. + MsQuicApi.Api.StreamShutdownDelegate(_ptr, (uint)QUIC_STREAM_SHUTDOWN_FLAG.GRACEFUL, errorCode: 0); + } + return default; + } + + ReadOnlyMemory[] array = buffers.ToArray(); + + uint length = (uint)array.Length; + + if (_sendQuicBuffers.Length < length) + { + _sendQuicBuffers = new QuicBuffer[length]; + _bufferArrays = new MemoryHandle[length]; + } + + for (int i = 0; i < length; i++) + { + ReadOnlyMemory buffer = array[i]; + MemoryHandle handle = buffer.Pin(); + _sendQuicBuffers[i].Length = (uint)buffer.Length; + _sendQuicBuffers[i].Buffer = (byte*)handle.Pointer; + _bufferArrays[i] = handle; + } + + _sendHandle = GCHandle.Alloc(_sendQuicBuffers, GCHandleType.Pinned); + + var quicBufferPointer = (QuicBuffer*)Marshal.UnsafeAddrOfPinnedArrayElement(_sendQuicBuffers, 0); + + uint status = MsQuicApi.Api.StreamSendDelegate( + _ptr, + quicBufferPointer, + length, + (uint)flags, + _ptr); + + if (!MsQuicStatusHelper.SuccessfulStatusCode(status)) + { + CleanupSendState(); + + // TODO this may need to be an aborted exception. + QuicExceptionHelpers.ThrowIfFailed(status, + "Could not send data to peer."); + } + + return _sendResettableCompletionSource.GetTypelessValueTask(); + } + + private void StartWrites() + { + Debug.Assert(!_started); + uint status = MsQuicApi.Api.StreamStartDelegate( + _ptr, + (uint)QUIC_STREAM_START_FLAG.ASYNC); + + QuicExceptionHelpers.ThrowIfFailed(status, "Could not start stream."); + } + + private void ReceiveComplete(int bufferLength) + { + uint status = MsQuicApi.Api.StreamReceiveCompleteDelegate(_ptr, (ulong)bufferLength); + QuicExceptionHelpers.ThrowIfFailed(status, "Could not complete receive call."); + } + + // This can fail if the stream isn't started. + private unsafe long GetStreamId() + { + return (long)MsQuicParameterHelpers.GetULongParam(MsQuicApi.Api, _ptr, (uint)QUIC_PARAM_LEVEL.STREAM, (uint)QUIC_PARAM_STREAM.ID); + } + + private void ThrowIfDisposed() + { + if (_disposed) + { + throw new ObjectDisposedException(nameof(MsQuicStream)); + } + } + + private enum ReadState + { + None, + IndividualReadComplete, + ReadsCompleted, + Aborted + } + + private enum ShutdownWriteState + { + None, + Canceled, + Finished + } + + private enum SendState + { + None, + Aborted, + Finished + } + } +} diff --git a/src/Shared/runtime/Quic/Implementations/QuicConnectionProvider.cs b/src/Shared/runtime/Quic/Implementations/QuicConnectionProvider.cs new file mode 100644 index 0000000000..d77bf1df76 --- /dev/null +++ b/src/Shared/runtime/Quic/Implementations/QuicConnectionProvider.cs @@ -0,0 +1,36 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Threading; +using System.Threading.Tasks; + +namespace System.Net.Quic.Implementations +{ + internal abstract class QuicConnectionProvider : IDisposable + { + internal abstract bool Connected { get; } + + internal abstract IPEndPoint LocalEndPoint { get; } + + internal abstract IPEndPoint RemoteEndPoint { get; } + + internal abstract ValueTask ConnectAsync(CancellationToken cancellationToken = default); + + internal abstract QuicStreamProvider OpenUnidirectionalStream(); + + internal abstract QuicStreamProvider OpenBidirectionalStream(); + + internal abstract long GetRemoteAvailableUnidirectionalStreamCount(); + + internal abstract long GetRemoteAvailableBidirectionalStreamCount(); + + internal abstract ValueTask AcceptStreamAsync(CancellationToken cancellationToken = default); + + internal abstract System.Net.Security.SslApplicationProtocol NegotiatedApplicationProtocol { get; } + + internal abstract ValueTask CloseAsync(long errorCode, CancellationToken cancellationToken = default); + + public abstract void Dispose(); + } +} diff --git a/src/Shared/runtime/Quic/Implementations/QuicImplementationProvider.cs b/src/Shared/runtime/Quic/Implementations/QuicImplementationProvider.cs new file mode 100644 index 0000000000..906f456266 --- /dev/null +++ b/src/Shared/runtime/Quic/Implementations/QuicImplementationProvider.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Net.Security; + +namespace System.Net.Quic.Implementations +{ + internal abstract class QuicImplementationProvider + { + internal QuicImplementationProvider() { } + + internal abstract QuicListenerProvider CreateListener(QuicListenerOptions options); + + internal abstract QuicConnectionProvider CreateConnection(QuicClientConnectionOptions options); + } +} diff --git a/src/Shared/runtime/Quic/Implementations/QuicListenerProvider.cs b/src/Shared/runtime/Quic/Implementations/QuicListenerProvider.cs new file mode 100644 index 0000000000..f533a8bb38 --- /dev/null +++ b/src/Shared/runtime/Quic/Implementations/QuicListenerProvider.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Threading; +using System.Threading.Tasks; + +namespace System.Net.Quic.Implementations +{ + internal abstract class QuicListenerProvider : IDisposable + { + internal abstract IPEndPoint ListenEndPoint { get; } + + internal abstract ValueTask AcceptConnectionAsync(CancellationToken cancellationToken = default); + + internal abstract void Start(); + + internal abstract void Close(); + + public abstract void Dispose(); + } +} diff --git a/src/Shared/runtime/Quic/Implementations/QuicStreamProvider.cs b/src/Shared/runtime/Quic/Implementations/QuicStreamProvider.cs new file mode 100644 index 0000000000..1e96e1597a --- /dev/null +++ b/src/Shared/runtime/Quic/Implementations/QuicStreamProvider.cs @@ -0,0 +1,53 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Buffers; +using System.Threading; +using System.Threading.Tasks; + +namespace System.Net.Quic.Implementations +{ + internal abstract class QuicStreamProvider : IDisposable, IAsyncDisposable + { + internal abstract long StreamId { get; } + + internal abstract bool CanRead { get; } + + internal abstract int Read(Span buffer); + + internal abstract ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default); + + internal abstract void AbortRead(long errorCode); + + internal abstract void AbortWrite(long errorCode); + + internal abstract bool CanWrite { get; } + + internal abstract void Write(ReadOnlySpan buffer); + + internal abstract ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default); + + internal abstract ValueTask WriteAsync(ReadOnlyMemory buffer, bool endStream, CancellationToken cancellationToken = default); + + internal abstract ValueTask WriteAsync(ReadOnlySequence buffers, CancellationToken cancellationToken = default); + + internal abstract ValueTask WriteAsync(ReadOnlySequence buffers, bool endStream, CancellationToken cancellationToken = default); + + internal abstract ValueTask WriteAsync(ReadOnlyMemory> buffers, CancellationToken cancellationToken = default); + + internal abstract ValueTask WriteAsync(ReadOnlyMemory> buffers, bool endStream, CancellationToken cancellationToken = default); + + internal abstract ValueTask ShutdownWriteCompleted(CancellationToken cancellationToken = default); + + internal abstract void Shutdown(); + + internal abstract void Flush(); + + internal abstract Task FlushAsync(CancellationToken cancellationToken); + + public abstract void Dispose(); + + public abstract ValueTask DisposeAsync(); + } +} diff --git a/src/Shared/runtime/Quic/Interop/Interop.MsQuic.cs b/src/Shared/runtime/Quic/Interop/Interop.MsQuic.cs new file mode 100644 index 0000000000..25a5e57755 --- /dev/null +++ b/src/Shared/runtime/Quic/Interop/Interop.MsQuic.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Net.Quic.Implementations.MsQuic.Internal; +using System.Runtime.InteropServices; + +internal static partial class Interop +{ + internal static class MsQuic + { + [DllImport(Libraries.MsQuic)] + internal static unsafe extern uint MsQuicOpen(int version, out MsQuicNativeMethods.NativeApi* registration); + } +} diff --git a/src/Shared/runtime/Quic/Interop/MsQuicEnums.cs b/src/Shared/runtime/Quic/Interop/MsQuicEnums.cs new file mode 100644 index 0000000000..3d294bce9c --- /dev/null +++ b/src/Shared/runtime/Quic/Interop/MsQuicEnums.cs @@ -0,0 +1,167 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Net.Quic.Implementations.MsQuic.Internal +{ + /// + /// Flags to pass when creating a security config. + /// + [Flags] + internal enum QUIC_SEC_CONFIG_FLAG : uint + { + NONE = 0, + CERT_HASH = 0x00000001, + CERT_HASH_STORE = 0x00000002, + CERT_CONTEXT = 0x00000004, + CERT_FILE = 0x00000008, + ENABL_OCSP = 0x00000010, + CERT_NULL = 0xF0000000, + } + + [Flags] + internal enum QUIC_CONNECTION_SHUTDOWN_FLAG : uint + { + NONE = 0x0, + SILENT = 0x1 + } + + [Flags] + internal enum QUIC_STREAM_OPEN_FLAG : uint + { + NONE = 0, + UNIDIRECTIONAL = 0x1, + ZERO_RTT = 0x2, + } + + [Flags] + internal enum QUIC_STREAM_START_FLAG : uint + { + NONE = 0, + FAIL_BLOCKED = 0x1, + IMMEDIATE = 0x2, + ASYNC = 0x4, + } + + [Flags] + internal enum QUIC_STREAM_SHUTDOWN_FLAG : uint + { + NONE = 0, + GRACEFUL = 0x1, + ABORT_SEND = 0x2, + ABORT_RECV = 0x4, + ABORT = ABORT_SEND | ABORT_RECV, + IMMEDIATE = 0x8 + } + + [Flags] + internal enum QUIC_RECEIVE_FLAG : uint + { + NONE = 0, + ZERO_RTT = 0x1, + FIN = 0x02 + } + + [Flags] + internal enum QUIC_SEND_FLAG : uint + { + NONE = 0, + ALLOW_0_RTT = 0x00000001, + FIN = 0x00000002, + } + + internal enum QUIC_PARAM_LEVEL : uint + { + REGISTRATION = 0, + SESSION = 1, + LISTENER = 2, + CONNECTION = 3, + TLS = 4, + STREAM = 5, + } + + internal enum QUIC_PARAM_REGISTRATION : uint + { + RETRY_MEMORY_PERCENT = 0, + CID_PREFIX = 1 + } + + internal enum QUIC_PARAM_SESSION : uint + { + TLS_TICKET_KEY = 0, + PEER_BIDI_STREAM_COUNT = 1, + PEER_UNIDI_STREAM_COUNT = 2, + IDLE_TIMEOUT = 3, + DISCONNECT_TIMEOUT = 4, + MAX_BYTES_PER_KEY = 5 + } + + internal enum QUIC_PARAM_LISTENER : uint + { + LOCAL_ADDRESS = 0, + STATS = 1 + } + + internal enum QUIC_PARAM_CONN : uint + { + QUIC_VERSION = 0, + LOCAL_ADDRESS = 1, + REMOTE_ADDRESS = 2, + IDLE_TIMEOUT = 3, + PEER_BIDI_STREAM_COUNT = 4, + PEER_UNIDI_STREAM_COUNT = 5, + LOCAL_BIDI_STREAM_COUNT = 6, + LOCAL_UNIDI_STREAM_COUNT = 7, + CLOSE_REASON_PHRASE = 8, + STATISTICS = 9, + STATISTICS_PLAT = 10, + CERT_VALIDATION_FLAGS = 11, + KEEP_ALIVE = 12, + DISCONNECT_TIMEOUT = 13, + SEC_CONFIG = 14, + SEND_BUFFERING = 15, + SEND_PACING = 16, + SHARE_UDP_BINDING = 17, + IDEAL_PROCESSOR = 18, + MAX_STREAM_IDS = 19 + } + + internal enum QUIC_PARAM_STREAM : uint + { + ID = 0, + ZERORTT_LENGTH = 1, + IDEAL_SEND_BUFFER = 2 + } + + internal enum QUIC_LISTENER_EVENT : uint + { + NEW_CONNECTION = 0 + } + + internal enum QUIC_CONNECTION_EVENT : uint + { + CONNECTED = 0, + SHUTDOWN_INITIATED_BY_TRANSPORT = 1, + SHUTDOWN_INITIATED_BY_PEER = 2, + SHUTDOWN_COMPLETE = 3, + LOCAL_ADDRESS_CHANGED = 4, + PEER_ADDRESS_CHANGED = 5, + PEER_STREAM_STARTED = 6, + STREAMS_AVAILABLE = 7, + PEER_NEEDS_STREAMS = 8, + IDEAL_PROCESSOR_CHANGED = 9, + } + + internal enum QUIC_STREAM_EVENT : uint + { + START_COMPLETE = 0, + RECEIVE = 1, + SEND_COMPLETE = 2, + PEER_SEND_SHUTDOWN = 3, + PEER_SEND_ABORTED = 4, + PEER_RECEIVE_ABORTED = 5, + SEND_SHUTDOWN_COMPLETE = 6, + SHUTDOWN_COMPLETE = 7, + IDEAL_SEND_BUFFER_SIZE = 8, + } +} diff --git a/src/Shared/runtime/Quic/Interop/MsQuicNativeMethods.cs b/src/Shared/runtime/Quic/Interop/MsQuicNativeMethods.cs new file mode 100644 index 0000000000..31ab416fbd --- /dev/null +++ b/src/Shared/runtime/Quic/Interop/MsQuicNativeMethods.cs @@ -0,0 +1,489 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable +using System.Net.Sockets; +using System.Runtime.InteropServices; +using System.Text; + +namespace System.Net.Quic.Implementations.MsQuic.Internal +{ + /// + /// Contains all native delegates and structs that are used with MsQuic. + /// + internal static unsafe class MsQuicNativeMethods + { + [StructLayout(LayoutKind.Sequential)] + internal struct NativeApi + { + internal uint Version; + + internal IntPtr SetContext; + internal IntPtr GetContext; + internal IntPtr SetCallbackHandler; + + internal IntPtr SetParam; + internal IntPtr GetParam; + + internal IntPtr RegistrationOpen; + internal IntPtr RegistrationClose; + + internal IntPtr SecConfigCreate; + internal IntPtr SecConfigDelete; + + internal IntPtr SessionOpen; + internal IntPtr SessionClose; + internal IntPtr SessionShutdown; + + internal IntPtr ListenerOpen; + internal IntPtr ListenerClose; + internal IntPtr ListenerStart; + internal IntPtr ListenerStop; + + internal IntPtr ConnectionOpen; + internal IntPtr ConnectionClose; + internal IntPtr ConnectionShutdown; + internal IntPtr ConnectionStart; + + internal IntPtr StreamOpen; + internal IntPtr StreamClose; + internal IntPtr StreamStart; + internal IntPtr StreamShutdown; + internal IntPtr StreamSend; + internal IntPtr StreamReceiveComplete; + internal IntPtr StreamReceiveSetEnabled; + } + + internal delegate uint SetContextDelegate( + IntPtr handle, + IntPtr context); + + internal delegate IntPtr GetContextDelegate( + IntPtr handle); + + internal delegate void SetCallbackHandlerDelegate( + IntPtr handle, + Delegate del, + IntPtr context); + + internal delegate uint SetParamDelegate( + IntPtr handle, + uint level, + uint param, + uint bufferLength, + byte* buffer); + + internal delegate uint GetParamDelegate( + IntPtr handle, + uint level, + uint param, + uint* bufferLength, + byte* buffer); + + internal delegate uint RegistrationOpenDelegate(byte[] appName, out IntPtr registrationContext); + + internal delegate void RegistrationCloseDelegate(IntPtr registrationContext); + + internal delegate void SecConfigCreateCompleteDelegate(IntPtr context, uint status, IntPtr securityConfig); + + internal delegate uint SecConfigCreateDelegate( + IntPtr registrationContext, + uint flags, + IntPtr certificate, + [MarshalAs(UnmanagedType.LPStr)]string? principal, + IntPtr context, + SecConfigCreateCompleteDelegate completionHandler); + + internal delegate void SecConfigDeleteDelegate( + IntPtr securityConfig); + + internal delegate uint SessionOpenDelegate( + IntPtr registrationContext, + byte[] utf8String, + IntPtr context, + ref IntPtr session); + + internal delegate void SessionCloseDelegate( + IntPtr session); + + internal delegate void SessionShutdownDelegate( + IntPtr session, + uint flags, + ushort errorCode); + + [StructLayout(LayoutKind.Sequential)] + internal struct ListenerEvent + { + internal QUIC_LISTENER_EVENT Type; + internal ListenerEventDataUnion Data; + } + + [StructLayout(LayoutKind.Explicit)] + internal struct ListenerEventDataUnion + { + [FieldOffset(0)] + internal ListenerEventDataNewConnection NewConnection; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct ListenerEventDataNewConnection + { + internal IntPtr Info; + internal IntPtr Connection; + internal IntPtr SecurityConfig; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct NewConnectionInfo + { + internal uint QuicVersion; + internal IntPtr LocalAddress; + internal IntPtr RemoteAddress; + internal ushort CryptoBufferLength; + internal ushort AlpnListLength; + internal ushort ServerNameLength; + internal IntPtr CryptoBuffer; + internal IntPtr AlpnList; + internal IntPtr ServerName; + } + + internal delegate uint ListenerCallbackDelegate( + IntPtr listener, + IntPtr context, + ref ListenerEvent evt); + + internal delegate uint ListenerOpenDelegate( + IntPtr session, + ListenerCallbackDelegate handler, + IntPtr context, + out IntPtr listener); + + internal delegate uint ListenerCloseDelegate( + IntPtr listener); + + internal delegate uint ListenerStartDelegate( + IntPtr listener, + ref SOCKADDR_INET localAddress); + + internal delegate uint ListenerStopDelegate( + IntPtr listener); + + [StructLayout(LayoutKind.Sequential)] + internal struct ConnectionEventDataConnected + { + internal bool EarlyDataAccepted; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct ConnectionEventDataShutdownBegin + { + internal uint Status; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct ConnectionEventDataShutdownBeginPeer + { + internal long ErrorCode; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct ConnectionEventDataShutdownComplete + { + internal bool TimedOut; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct ConnectionEventDataLocalAddrChanged + { + internal IntPtr Address; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct ConnectionEventDataPeerAddrChanged + { + internal IntPtr Address; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct ConnectionEventDataNewStream + { + internal IntPtr Stream; + internal QUIC_STREAM_OPEN_FLAG Flags; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct ConnectionEventDataStreamsAvailable + { + internal ushort BiDirectionalCount; + internal ushort UniDirectionalCount; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct ConnectionEventDataIdealSendBuffer + { + internal ulong NumBytes; + } + + [StructLayout(LayoutKind.Explicit)] + internal struct ConnectionEventDataUnion + { + [FieldOffset(0)] + internal ConnectionEventDataConnected Connected; + + [FieldOffset(0)] + internal ConnectionEventDataShutdownBegin ShutdownBegin; + + [FieldOffset(0)] + internal ConnectionEventDataShutdownBeginPeer ShutdownBeginPeer; + + [FieldOffset(0)] + internal ConnectionEventDataShutdownComplete ShutdownComplete; + + [FieldOffset(0)] + internal ConnectionEventDataLocalAddrChanged LocalAddrChanged; + + [FieldOffset(0)] + internal ConnectionEventDataPeerAddrChanged PeerAddrChanged; + + [FieldOffset(0)] + internal ConnectionEventDataNewStream NewStream; + + [FieldOffset(0)] + internal ConnectionEventDataStreamsAvailable StreamsAvailable; + + [FieldOffset(0)] + internal ConnectionEventDataIdealSendBuffer IdealSendBuffer; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct ConnectionEvent + { + internal QUIC_CONNECTION_EVENT Type; + internal ConnectionEventDataUnion Data; + + internal bool EarlyDataAccepted => Data.Connected.EarlyDataAccepted; + internal ulong NumBytes => Data.IdealSendBuffer.NumBytes; + internal uint ShutdownBeginStatus => Data.ShutdownBegin.Status; + internal long ShutdownBeginPeerStatus => Data.ShutdownBeginPeer.ErrorCode; + internal bool ShutdownTimedOut => Data.ShutdownComplete.TimedOut; + internal ushort BiDirectionalCount => Data.StreamsAvailable.BiDirectionalCount; + internal ushort UniDirectionalCount => Data.StreamsAvailable.UniDirectionalCount; + internal QUIC_STREAM_OPEN_FLAG StreamFlags => Data.NewStream.Flags; + } + + internal delegate uint ConnectionCallbackDelegate( + IntPtr connection, + IntPtr context, + ref ConnectionEvent connectionEvent); + + internal delegate uint ConnectionOpenDelegate( + IntPtr session, + ConnectionCallbackDelegate handler, + IntPtr context, + out IntPtr connection); + + internal delegate uint ConnectionCloseDelegate( + IntPtr connection); + + internal delegate uint ConnectionStartDelegate( + IntPtr connection, + ushort family, + [MarshalAs(UnmanagedType.LPStr)] + string serverName, + ushort serverPort); + + internal delegate uint ConnectionShutdownDelegate( + IntPtr connection, + uint flags, + long errorCode); + + [StructLayout(LayoutKind.Sequential)] + internal struct StreamEventDataRecv + { + internal ulong AbsoluteOffset; + internal ulong TotalBufferLength; + internal QuicBuffer* Buffers; + internal uint BufferCount; + internal uint Flags; + } + + [StructLayout(LayoutKind.Explicit)] + internal struct StreamEventDataSendComplete + { + [FieldOffset(0)] + internal byte Canceled; + [FieldOffset(1)] + internal IntPtr ClientContext; + + internal bool IsCanceled() + { + return Canceled != 0; + } + } + + [StructLayout(LayoutKind.Sequential)] + internal struct StreamEventDataPeerSendAbort + { + internal long ErrorCode; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct StreamEventDataPeerRecvAbort + { + internal long ErrorCode; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct StreamEventDataSendShutdownComplete + { + internal byte Graceful; + } + + [StructLayout(LayoutKind.Explicit)] + internal struct StreamEventDataUnion + { + [FieldOffset(0)] + internal StreamEventDataRecv Recv; + + [FieldOffset(0)] + internal StreamEventDataSendComplete SendComplete; + + [FieldOffset(0)] + internal StreamEventDataPeerSendAbort PeerSendAbort; + + [FieldOffset(0)] + internal StreamEventDataPeerRecvAbort PeerRecvAbort; + + [FieldOffset(0)] + internal StreamEventDataSendShutdownComplete SendShutdownComplete; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct StreamEvent + { + internal QUIC_STREAM_EVENT Type; + internal StreamEventDataUnion Data; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct SOCKADDR_IN + { + internal ushort sin_family; + internal ushort sin_port; + internal byte sin_addr0; + internal byte sin_addr1; + internal byte sin_addr2; + internal byte sin_addr3; + + internal byte[] Address + { + get + { + return new byte[] { sin_addr0, sin_addr1, sin_addr2, sin_addr3 }; + } + } + } + + [StructLayout(LayoutKind.Sequential)] + internal struct SOCKADDR_IN6 + { + internal ushort _family; + internal ushort _port; + internal uint _flowinfo; + internal byte _addr0; + internal byte _addr1; + internal byte _addr2; + internal byte _addr3; + internal byte _addr4; + internal byte _addr5; + internal byte _addr6; + internal byte _addr7; + internal byte _addr8; + internal byte _addr9; + internal byte _addr10; + internal byte _addr11; + internal byte _addr12; + internal byte _addr13; + internal byte _addr14; + internal byte _addr15; + internal uint _scope_id; + + internal byte[] Address + { + get + { + return new byte[] { + _addr0, _addr1, _addr2, _addr3, + _addr4, _addr5, _addr6, _addr7, + _addr8, _addr9, _addr10, _addr11, + _addr12, _addr13, _addr14, _addr15 }; + } + } + } + + [StructLayout(LayoutKind.Explicit, CharSet = CharSet.Ansi)] + internal struct SOCKADDR_INET + { + [FieldOffset(0)] + internal SOCKADDR_IN Ipv4; + [FieldOffset(0)] + internal SOCKADDR_IN6 Ipv6; + [FieldOffset(0)] + internal ushort si_family; + } + + internal delegate uint StreamCallbackDelegate( + IntPtr stream, + IntPtr context, + ref StreamEvent streamEvent); + + internal delegate uint StreamOpenDelegate( + IntPtr connection, + uint flags, + StreamCallbackDelegate handler, + IntPtr context, + out IntPtr stream); + + internal delegate uint StreamStartDelegate( + IntPtr stream, + uint flags); + + internal delegate uint StreamCloseDelegate( + IntPtr stream); + + internal delegate uint StreamShutdownDelegate( + IntPtr stream, + uint flags, + long errorCode); + + internal delegate uint StreamSendDelegate( + IntPtr stream, + QuicBuffer* buffers, + uint bufferCount, + uint flags, + IntPtr clientSendContext); + + internal delegate uint StreamReceiveCompleteDelegate( + IntPtr stream, + ulong bufferLength); + + internal delegate uint StreamReceiveSetEnabledDelegate( + IntPtr stream, + bool enabled); + + [StructLayout(LayoutKind.Sequential)] + internal unsafe struct QuicBuffer + { + internal uint Length; + internal byte* Buffer; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct CertFileParams + { + internal IntPtr PrivateKeyFilePath; + internal IntPtr CertificateFilePath; + } + } +} diff --git a/src/Shared/runtime/Quic/Interop/MsQuicStatusCodes.cs b/src/Shared/runtime/Quic/Interop/MsQuicStatusCodes.cs new file mode 100644 index 0000000000..72c35687eb --- /dev/null +++ b/src/Shared/runtime/Quic/Interop/MsQuicStatusCodes.cs @@ -0,0 +1,121 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Runtime.InteropServices; + +namespace System.Net.Quic.Implementations.MsQuic.Internal +{ + internal static class MsQuicStatusCodes + { + internal static readonly uint Success = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? Windows.Success : Linux.Success; + internal static readonly uint Pending = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? Windows.Pending : Linux.Pending; + internal static readonly uint InternalError = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? Windows.InternalError : Linux.InternalError; + + // TODO return better error messages here. + public static string GetError(uint status) + { + return RuntimeInformation.IsOSPlatform(OSPlatform.Windows) + ? Windows.GetError(status) : Linux.GetError(status); + } + + private static class Windows + { + internal const uint Success = 0; + internal const uint Pending = 0x703E5; + internal const uint Continue = 0x704DE; + internal const uint OutOfMemory = 0x8007000E; + internal const uint InvalidParameter = 0x80070057; + internal const uint InvalidState = 0x8007139F; + internal const uint NotSupported = 0x80004002; + internal const uint NotFound = 0x80070490; + internal const uint BufferTooSmall = 0x8007007A; + internal const uint HandshakeFailure = 0x80410000; + internal const uint Aborted = 0x80004004; + internal const uint AddressInUse = 0x80072740; + internal const uint ConnectionTimeout = 0x800704CF; + internal const uint ConnectionIdle = 0x800704D4; + internal const uint InternalError = 0x80004005; + internal const uint ServerBusy = 0x800704C9; + internal const uint ProtocolError = 0x800704CD; + internal const uint HostUnreachable = 0x800704D0; + internal const uint VerNegError = 0x80410001; + + // TODO return better error messages here. + public static string GetError(uint status) + { + return status switch + { + Success => "SUCCESS", + Pending => "PENDING", + Continue => "CONTINUE", + OutOfMemory => "OUT_OF_MEMORY", + InvalidParameter => "INVALID_PARAMETER", + InvalidState => "INVALID_STATE", + NotSupported => "NOT_SUPPORTED", + NotFound => "NOT_FOUND", + BufferTooSmall => "BUFFER_TOO_SMALL", + HandshakeFailure => "HANDSHAKE_FAILURE", + Aborted => "ABORTED", + AddressInUse => "ADDRESS_IN_USE", + ConnectionTimeout => "CONNECTION_TIMEOUT", + ConnectionIdle => "CONNECTION_IDLE", + InternalError => "INTERNAL_ERROR", + ServerBusy => "SERVER_BUSY", + ProtocolError => "PROTOCOL_ERROR", + VerNegError => "VER_NEG_ERROR", + _ => status.ToString() + }; + } + } + + private static class Linux + { + internal const uint Success = 0; + internal const uint Pending = unchecked((uint)-2); + internal const uint Continue = unchecked((uint)-1); + internal const uint OutOfMemory = 12; + internal const uint InvalidParameter = 22; + internal const uint InvalidState = 200000002; + internal const uint NotSupported = 95; + internal const uint NotFound = 2; + internal const uint BufferTooSmall = 75; + internal const uint HandshakeFailure = 200000009; + internal const uint Aborted = 200000008; + internal const uint AddressInUse = 98; + internal const uint ConnectionTimeout = 110; + internal const uint ConnectionIdle = 200000011; + internal const uint InternalError = 200000012; + internal const uint ServerBusy = 200000007; + internal const uint ProtocolError = 200000013; + internal const uint VerNegError = 200000014; + + // TODO return better error messages here. + public static string GetError(uint status) + { + return status switch + { + Success => "SUCCESS", + Pending => "PENDING", + Continue => "CONTINUE", + OutOfMemory => "OUT_OF_MEMORY", + InvalidParameter => "INVALID_PARAMETER", + InvalidState => "INVALID_STATE", + NotSupported => "NOT_SUPPORTED", + NotFound => "NOT_FOUND", + BufferTooSmall => "BUFFER_TOO_SMALL", + HandshakeFailure => "HANDSHAKE_FAILURE", + Aborted => "ABORTED", + AddressInUse => "ADDRESS_IN_USE", + ConnectionTimeout => "CONNECTION_TIMEOUT", + ConnectionIdle => "CONNECTION_IDLE", + InternalError => "INTERNAL_ERROR", + ServerBusy => "SERVER_BUSY", + ProtocolError => "PROTOCOL_ERROR", + VerNegError => "VER_NEG_ERROR", + _ => status.ToString() + }; + } + } + } +} diff --git a/src/Shared/runtime/Quic/Interop/MsQuicStatusHelper.cs b/src/Shared/runtime/Quic/Interop/MsQuicStatusHelper.cs new file mode 100644 index 0000000000..f08eb861d2 --- /dev/null +++ b/src/Shared/runtime/Quic/Interop/MsQuicStatusHelper.cs @@ -0,0 +1,26 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Runtime.InteropServices; + +namespace System.Net.Quic.Implementations.MsQuic.Internal +{ + internal static class MsQuicStatusHelper + { + internal static bool SuccessfulStatusCode(uint status) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return status < 0x80000000; + } + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + return (int)status <= 0; + } + + return false; + } + } +} diff --git a/src/Shared/runtime/Quic/NetEventSource.Quic.cs b/src/Shared/runtime/Quic/NetEventSource.Quic.cs new file mode 100644 index 0000000000..921808829d --- /dev/null +++ b/src/Shared/runtime/Quic/NetEventSource.Quic.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics.Tracing; + +namespace System.Net +{ + [EventSource(Name = "Microsoft-System-Net-Quic")] + internal sealed partial class NetEventSource : EventSource + { + } +} diff --git a/src/Shared/runtime/Quic/QuicClientConnectionOptions.cs b/src/Shared/runtime/Quic/QuicClientConnectionOptions.cs new file mode 100644 index 0000000000..3e7d10a199 --- /dev/null +++ b/src/Shared/runtime/Quic/QuicClientConnectionOptions.cs @@ -0,0 +1,51 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable +using System.Net.Security; + +namespace System.Net.Quic +{ + /// + /// Options to provide to the when connecting to a Listener. + /// + internal class QuicClientConnectionOptions + { + /// + /// Client authentication options to use when establishing a . + /// + public SslClientAuthenticationOptions? ClientAuthenticationOptions { get; set; } + + /// + /// The local endpoint that will be bound to. + /// + public IPEndPoint? LocalEndPoint { get; set; } + + /// + /// The endpoint to connect to. + /// + public IPEndPoint? RemoteEndPoint { get; set; } + + /// + /// Limit on the number of bidirectional streams the peer connection can create + /// on an accepted connection. + /// Default is 100. + /// + // TODO consider constraining these limits to 0 to whatever the max of the QUIC library we are using. + public long MaxBidirectionalStreams { get; set; } = 100; + + /// + /// Limit on the number of unidirectional streams the peer connection can create + /// on an accepted connection. + /// Default is 100. + /// + // TODO consider constraining these limits to 0 to whatever the max of the QUIC library we are using. + public long MaxUnidirectionalStreams { get; set; } = 100; + + /// + /// Idle timeout for connections, afterwhich the connection will be closed. + /// + public TimeSpan IdleTimeout { get; set; } = TimeSpan.FromMinutes(2); + } +} diff --git a/src/Shared/runtime/Quic/QuicConnection.cs b/src/Shared/runtime/Quic/QuicConnection.cs new file mode 100644 index 0000000000..c2bbceb43c --- /dev/null +++ b/src/Shared/runtime/Quic/QuicConnection.cs @@ -0,0 +1,100 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable +using System.Net.Quic.Implementations; +using System.Net.Quic.Implementations.MsQuic.Internal; +using System.Net.Security; +using System.Threading; +using System.Threading.Tasks; + +namespace System.Net.Quic +{ + internal sealed class QuicConnection : IDisposable + { + private readonly QuicConnectionProvider _provider; + + public static bool IsQuicSupported => MsQuicApi.IsQuicSupported; + + /// + /// Create an outbound QUIC connection. + /// + /// The remote endpoint to connect to. + /// TLS options + /// The local endpoint to connect from. + public QuicConnection(IPEndPoint remoteEndPoint, SslClientAuthenticationOptions? sslClientAuthenticationOptions, IPEndPoint? localEndPoint = null) + : this(QuicImplementationProviders.Default, remoteEndPoint, sslClientAuthenticationOptions, localEndPoint) + { + } + + // !!! TEMPORARY: Remove "implementationProvider" before shipping + public QuicConnection(QuicImplementationProvider implementationProvider, IPEndPoint remoteEndPoint, SslClientAuthenticationOptions? sslClientAuthenticationOptions, IPEndPoint? localEndPoint = null) + : this(implementationProvider, new QuicClientConnectionOptions() { RemoteEndPoint = remoteEndPoint, ClientAuthenticationOptions = sslClientAuthenticationOptions, LocalEndPoint = localEndPoint }) + { + } + + public QuicConnection(QuicImplementationProvider implementationProvider, QuicClientConnectionOptions options) + { + _provider = implementationProvider.CreateConnection(options); + } + + internal QuicConnection(QuicConnectionProvider provider) + { + _provider = provider; + } + + /// + /// Indicates whether the QuicConnection is connected. + /// + public bool Connected => _provider.Connected; + + public IPEndPoint LocalEndPoint => _provider.LocalEndPoint; + + public IPEndPoint RemoteEndPoint => _provider.RemoteEndPoint; + + public SslApplicationProtocol NegotiatedApplicationProtocol => _provider.NegotiatedApplicationProtocol; + + /// + /// Connect to the remote endpoint. + /// + /// + /// + public ValueTask ConnectAsync(CancellationToken cancellationToken = default) => _provider.ConnectAsync(cancellationToken); + + /// + /// Create an outbound unidirectional stream. + /// + /// + public QuicStream OpenUnidirectionalStream() => new QuicStream(_provider.OpenUnidirectionalStream()); + + /// + /// Create an outbound bidirectional stream. + /// + /// + public QuicStream OpenBidirectionalStream() => new QuicStream(_provider.OpenBidirectionalStream()); + + /// + /// Accept an incoming stream. + /// + /// + public async ValueTask AcceptStreamAsync(CancellationToken cancellationToken = default) => new QuicStream(await _provider.AcceptStreamAsync(cancellationToken).ConfigureAwait(false)); + + /// + /// Close the connection and terminate any active streams. + /// + public ValueTask CloseAsync(long errorCode, CancellationToken cancellationToken = default) => _provider.CloseAsync(errorCode, cancellationToken); + + public void Dispose() => _provider.Dispose(); + + /// + /// Gets the maximum number of bidirectional streams that can be made to the peer. + /// + public long GetRemoteAvailableUnidirectionalStreamCount() => _provider.GetRemoteAvailableUnidirectionalStreamCount(); + + /// + /// Gets the maximum number of unidirectional streams that can be made to the peer. + /// + public long GetRemoteAvailableBidirectionalStreamCount() => _provider.GetRemoteAvailableBidirectionalStreamCount(); + } +} diff --git a/src/Shared/runtime/Quic/QuicConnectionAbortedException.cs b/src/Shared/runtime/Quic/QuicConnectionAbortedException.cs new file mode 100644 index 0000000000..41f4b32998 --- /dev/null +++ b/src/Shared/runtime/Quic/QuicConnectionAbortedException.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Net.Quic +{ + internal class QuicConnectionAbortedException : QuicException + { + internal QuicConnectionAbortedException(long errorCode) + : this(SR.Format(SR.net_quic_connectionaborted, errorCode), errorCode) + { + } + + public QuicConnectionAbortedException(string message, long errorCode) + : base (message) + { + ErrorCode = errorCode; + } + + public long ErrorCode { get; } + } +} diff --git a/src/Shared/runtime/Quic/QuicException.cs b/src/Shared/runtime/Quic/QuicException.cs new file mode 100644 index 0000000000..843c2f7592 --- /dev/null +++ b/src/Shared/runtime/Quic/QuicException.cs @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Net.Quic +{ + internal class QuicException : Exception + { + public QuicException(string message) + : base (message) + { + } + } +} diff --git a/src/Shared/runtime/Quic/QuicImplementationProviders.cs b/src/Shared/runtime/Quic/QuicImplementationProviders.cs new file mode 100644 index 0000000000..66a7e0d6df --- /dev/null +++ b/src/Shared/runtime/Quic/QuicImplementationProviders.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Net.Quic +{ + internal static class QuicImplementationProviders + { + public static Implementations.QuicImplementationProvider Mock { get; } = new Implementations.Mock.MockImplementationProvider(); + public static Implementations.QuicImplementationProvider MsQuic { get; } = new Implementations.MsQuic.MsQuicImplementationProvider(); + public static Implementations.QuicImplementationProvider Default => MsQuic; + } +} diff --git a/src/Shared/runtime/Quic/QuicListener.cs b/src/Shared/runtime/Quic/QuicListener.cs new file mode 100644 index 0000000000..8fb0c1e337 --- /dev/null +++ b/src/Shared/runtime/Quic/QuicListener.cs @@ -0,0 +1,55 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Net.Quic.Implementations; +using System.Net.Security; +using System.Threading; +using System.Threading.Tasks; + +namespace System.Net.Quic +{ + internal sealed class QuicListener : IDisposable + { + private readonly QuicListenerProvider _provider; + + /// + /// Create a QUIC listener on the specified local endpoint and start listening. + /// + /// The local endpoint to listen on. + /// TLS options for the listener. + public QuicListener(IPEndPoint listenEndPoint, SslServerAuthenticationOptions sslServerAuthenticationOptions) + : this(QuicImplementationProviders.Default, listenEndPoint, sslServerAuthenticationOptions) + { + } + + // !!! TEMPORARY: Remove "implementationProvider" before shipping + public QuicListener(QuicImplementationProvider implementationProvider, IPEndPoint listenEndPoint, SslServerAuthenticationOptions sslServerAuthenticationOptions) + : this(implementationProvider, new QuicListenerOptions() { ListenEndPoint = listenEndPoint, ServerAuthenticationOptions = sslServerAuthenticationOptions }) + { + } + + public QuicListener(QuicImplementationProvider implementationProvider, QuicListenerOptions options) + { + _provider = implementationProvider.CreateListener(options); + } + + public IPEndPoint ListenEndPoint => _provider.ListenEndPoint; + + /// + /// Accept a connection. + /// + /// + public async ValueTask AcceptConnectionAsync(CancellationToken cancellationToken = default) => + new QuicConnection(await _provider.AcceptConnectionAsync(cancellationToken).ConfigureAwait(false)); + + public void Start() => _provider.Start(); + + /// + /// Stop listening and close the listener. + /// + public void Close() => _provider.Close(); + + public void Dispose() => _provider.Dispose(); + } +} diff --git a/src/Shared/runtime/Quic/QuicListenerOptions.cs b/src/Shared/runtime/Quic/QuicListenerOptions.cs new file mode 100644 index 0000000000..934c7a8e24 --- /dev/null +++ b/src/Shared/runtime/Quic/QuicListenerOptions.cs @@ -0,0 +1,60 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable +using System.Net.Security; + +namespace System.Net.Quic +{ + /// + /// Options to provide to the . + /// + internal class QuicListenerOptions + { + /// + /// Server Ssl options to use for ALPN, SNI, etc. + /// + public SslServerAuthenticationOptions? ServerAuthenticationOptions { get; set; } + + /// + /// Optional path to certificate file to configure the security configuration. + /// + public string? CertificateFilePath { get; set; } + + /// + /// Optional path to private key file to configure the security configuration. + /// + public string? PrivateKeyFilePath { get; set; } + + /// + /// The endpoint to listen on. + /// + public IPEndPoint? ListenEndPoint { get; set; } + + /// + /// Number of connections to be held without accepting the connection. + /// + public int ListenBacklog { get; set; } = 512; + + /// + /// Limit on the number of bidirectional streams an accepted connection can create + /// back to the client. + /// Default is 100. + /// + // TODO consider constraining these limits to 0 to whatever the max of the QUIC library we are using. + public long MaxBidirectionalStreams { get; set; } = 100; + + /// + /// Limit on the number of unidirectional streams the peer connection can create. + /// Default is 100. + /// + // TODO consider constraining these limits to 0 to whatever the max of the QUIC library we are using. + public long MaxUnidirectionalStreams { get; set; } = 100; + + /// + /// Idle timeout for connections, afterwhich the connection will be closed. + /// + public TimeSpan IdleTimeout { get; set; } = TimeSpan.FromMinutes(10); + } +} diff --git a/src/Shared/runtime/Quic/QuicOperationAbortedException.cs b/src/Shared/runtime/Quic/QuicOperationAbortedException.cs new file mode 100644 index 0000000000..25cd145ee6 --- /dev/null +++ b/src/Shared/runtime/Quic/QuicOperationAbortedException.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Net.Quic +{ + internal class QuicOperationAbortedException : QuicException + { + internal QuicOperationAbortedException() + : base(SR.net_quic_operationaborted) + { + } + + public QuicOperationAbortedException(string message) : base(message) + { + } + } +} diff --git a/src/Shared/runtime/Quic/QuicStream.cs b/src/Shared/runtime/Quic/QuicStream.cs new file mode 100644 index 0000000000..4e1af6dff6 --- /dev/null +++ b/src/Shared/runtime/Quic/QuicStream.cs @@ -0,0 +1,134 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable +using System.Buffers; +using System.IO; +using System.Net.Quic.Implementations; +using System.Threading; +using System.Threading.Tasks; + +namespace System.Net.Quic +{ + internal sealed class QuicStream : Stream + { + private readonly QuicStreamProvider _provider; + + internal QuicStream(QuicStreamProvider provider) + { + _provider = provider; + } + + // + // Boilerplate implementation stuff + // + + public override bool CanSeek => false; + public override long Length => throw new NotSupportedException(); + public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); + public override void SetLength(long value) => throw new NotSupportedException(); + public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); } + + public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback? callback, object? state) => + TaskToApm.Begin(ReadAsync(buffer, offset, count, default), callback, state); + + public override int EndRead(IAsyncResult asyncResult) => + TaskToApm.End(asyncResult); + + public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback? callback, object? state) => + TaskToApm.Begin(WriteAsync(buffer, offset, count, default), callback, state); + + public override void EndWrite(IAsyncResult asyncResult) => + TaskToApm.End(asyncResult); + + private static void ValidateBufferArgs(byte[] buffer, int offset, int count) + { + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + if ((uint)offset > buffer.Length) + { + throw new ArgumentOutOfRangeException(nameof(offset)); + } + + if ((uint)count > buffer.Length - offset) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + } + + public override int Read(byte[] buffer, int offset, int count) + { + ValidateBufferArgs(buffer, offset, count); + return Read(buffer.AsSpan(offset, count)); + } + + public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + ValidateBufferArgs(buffer, offset, count); + return ReadAsync(new Memory(buffer, offset, count), cancellationToken).AsTask(); + } + + public override void Write(byte[] buffer, int offset, int count) + { + ValidateBufferArgs(buffer, offset, count); + Write(buffer.AsSpan(offset, count)); + } + + public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + ValidateBufferArgs(buffer, offset, count); + return WriteAsync(new ReadOnlyMemory(buffer, offset, count), cancellationToken).AsTask(); + } + + /// + /// QUIC stream ID. + /// + public long StreamId => _provider.StreamId; + + public override bool CanRead => _provider.CanRead; + + public override int Read(Span buffer) => _provider.Read(buffer); + + public override ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default) => _provider.ReadAsync(buffer, cancellationToken); + + public override bool CanWrite => _provider.CanWrite; + + public override void Write(ReadOnlySpan buffer) => _provider.Write(buffer); + + public override ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default) => _provider.WriteAsync(buffer, cancellationToken); + + public override void Flush() => _provider.Flush(); + + public override Task FlushAsync(CancellationToken cancellationToken) => _provider.FlushAsync(cancellationToken); + + public void AbortRead(long errorCode) => _provider.AbortRead(errorCode); + + public void AbortWrite(long errorCode) => _provider.AbortWrite(errorCode); + + public ValueTask WriteAsync(ReadOnlyMemory buffer, bool endStream, CancellationToken cancellationToken = default) => _provider.WriteAsync(buffer, endStream, cancellationToken); + + public ValueTask WriteAsync(ReadOnlySequence buffers, CancellationToken cancellationToken = default) => _provider.WriteAsync(buffers, cancellationToken); + + public ValueTask WriteAsync(ReadOnlySequence buffers, bool endStream, CancellationToken cancellationToken = default) => _provider.WriteAsync(buffers, endStream, cancellationToken); + + public ValueTask WriteAsync(ReadOnlyMemory> buffers, CancellationToken cancellationToken = default) => _provider.WriteAsync(buffers, cancellationToken); + + public ValueTask WriteAsync(ReadOnlyMemory> buffers, bool endStream, CancellationToken cancellationToken = default) => _provider.WriteAsync(buffers, endStream, cancellationToken); + + public ValueTask ShutdownWriteCompleted(CancellationToken cancellationToken = default) => _provider.ShutdownWriteCompleted(cancellationToken); + + public void Shutdown() => _provider.Shutdown(); + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _provider.Dispose(); + } + } + } +} diff --git a/src/Shared/runtime/Quic/QuicStreamAbortedException.cs b/src/Shared/runtime/Quic/QuicStreamAbortedException.cs new file mode 100644 index 0000000000..6e25335f99 --- /dev/null +++ b/src/Shared/runtime/Quic/QuicStreamAbortedException.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Net.Quic +{ + internal class QuicStreamAbortedException : QuicException + { + internal QuicStreamAbortedException(long errorCode) + : this(SR.Format(SR.net_quic_streamaborted, errorCode), errorCode) + { + } + + public QuicStreamAbortedException(string message, long errorCode) + : base(message) + { + ErrorCode = errorCode; + } + + public long ErrorCode { get; } + } +} diff --git a/src/Shared/runtime/ReadMe.SharedCode.md b/src/Shared/runtime/ReadMe.SharedCode.md new file mode 100644 index 0000000000..f24980b8a2 --- /dev/null +++ b/src/Shared/runtime/ReadMe.SharedCode.md @@ -0,0 +1,36 @@ +The code in this directory is shared between dotnet/runtime and dotnet/AspNetCore. This contains HTTP/2 and HTTP/3 protocol infrastructure such as an HPACK implementation. Any changes to this dir need to be checked into both repositories. + +dotnet/runtime code paths: +- runtime\src\libraries\Common\src\System\Net\Http\aspnetcore +- runtime\src\libraries\Common\tests\Tests\System\Net\aspnetcore + +dotnet/AspNetCore code paths: +- AspNetCore\src\Shared\runtime +- AspNetCore\src\Shared\test\Shared.Tests\runtime + +## Copying code +- To copy code from dotnet/runtime to dotnet/AspNetCore, set ASPNETCORE_REPO to the AspNetCore repo root and then run CopyToAspNetCore.cmd. +- To copy code from dotnet/AspNetCore to dotnet/runtime, set RUNTIME_REPO to the runtime repo root and then run CopyToRuntime.cmd. + +## Building dotnet/runtime code: +- https://github.com/dotnet/runtime/tree/master/docs/workflow +- Run *build.cmd* from the root once: `PS D:\github\runtime> .\build.cmd -runtimeConfiguration Release -subsetCategory coreclr-libraries` +- Build the individual projects: +- `PS D:\github\dotnet\src\libraries\Common\tests> dotnet build` +- `PS D:\github\dotnet\src\libraries\System.Net.Http\src> dotnet build` + +### Running dotnet/runtime tests: +- `PS D:\github\runtime\src\libraries\Common\tests> dotnet build /t:test` +- `PS D:\github\runtime\src\libraries\System.Net.Http\tests\UnitTests> dotnet build /t:test` + +## Building dotnet/AspNetCore code: +- https://github.com/dotnet/AspNetCore/blob/master/docs/BuildFromSource.md +- Run restore in the root once: `PS D:\github\AspNetCore> .\restore.cmd` +- Activate to use the repo local runtime: `PS D:\github\AspNetCore> . .\activate.ps1` +- Build the individual projects: +- `(AspNetCore) PS D:\github\AspNetCore\src\Shared\test\Shared.Tests> dotnet build` +- `(AspNetCore) PS D:\github\AspNetCore\src\servers\Kestrel\core\src> dotnet build` + +### Running dotnet/AspNetCore tests: +- `(AspNetCore) PS D:\github\AspNetCore\src\Shared\test\Shared.Tests> dotnet test` +- `(AspNetCore) PS D:\github\AspNetCore\src\servers\Kestrel\core\test> dotnet test` diff --git a/src/Shared/runtime/SR.Quic.cs b/src/Shared/runtime/SR.Quic.cs new file mode 100644 index 0000000000..6a756f9ca6 --- /dev/null +++ b/src/Shared/runtime/SR.Quic.cs @@ -0,0 +1,20 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace System.Net.Quic +{ + internal static partial class SR + { + // The resource generator used in AspNetCore does not create this method. This file fills in that functional gap + // so we don't have to modify the shared source. + internal static string Format(string resourceFormat, params object[] args) + { + if (args != null) + { + return string.Format(resourceFormat, args); + } + + return resourceFormat; + } + } +} diff --git a/src/Shared/runtime/SR.cs b/src/Shared/runtime/SR.cs new file mode 100644 index 0000000000..7dd7cc5991 --- /dev/null +++ b/src/Shared/runtime/SR.cs @@ -0,0 +1,20 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace System.Net.Http +{ + internal static partial class SR + { + // The resource generator used in AspNetCore does not create this method. This file fills in that functional gap + // so we don't have to modify the shared source. + internal static string Format(string resourceFormat, params object[] args) + { + if (args != null) + { + return string.Format(resourceFormat, args); + } + + return resourceFormat; + } + } +} diff --git a/src/Shared/runtime/SR.resx b/src/Shared/runtime/SR.resx new file mode 100644 index 0000000000..c6c8b9bd5b --- /dev/null +++ b/src/Shared/runtime/SR.resx @@ -0,0 +1,171 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The HTTP headers length exceeded the set limit of {0} bytes. + + + The header name format is invalid. + + + HPACK integer exceeds limits or has an overlong encoding. + + + Failed to HPACK encode the headers. + + + Huffman-coded literal string failed to decode. + + + Incomplete header block received. + + + Invalid header index: {0} is outside of static table and no dynamic table entry found. + + + A dynamic table size update of {0} octets is greater than the configured maximum size of {1} octets. + + + Dynamic table size update received after beginning of header block. + + + End of headers reached with incomplete token. + + + Received an invalid header name: '{0}'. + + + No dynamic table support + + + Request headers must contain only ASCII characters. + + + Connection aborted by peer ({0}). + + + QUIC is not supported on this platform. See http://aka.ms/dotnetquic + + + Operation aborted. + + + Stream aborted by peer ({0}). + + \ No newline at end of file diff --git a/src/Shared/startvs.cmd b/src/Shared/startvs.cmd new file mode 100644 index 0000000000..7891e16f46 --- /dev/null +++ b/src/Shared/startvs.cmd @@ -0,0 +1,3 @@ +@ECHO OFF + +%~dp0..\..\startvs.cmd %~dp0Shared.sln diff --git a/src/Shared/test/Certificates/validSelfSignedPrimaryRootCertificate.cer b/src/Shared/test/Certificates/validSelfSignedPrimaryRootCertificate.cer new file mode 100644 index 0000000000..a7420c8493 --- /dev/null +++ b/src/Shared/test/Certificates/validSelfSignedPrimaryRootCertificate.cer @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDPTCCAiWgAwIBAgIQTmWeCzG8SbRJ0y+osLWwDjANBgkqhkiG9w0BAQsFADAp +MScwJQYDVQQDDB5WYWxpZCBTZWxmIFNpZ25lZCBQcmltYXJ5IFJvb3QwHhcNMjAw +MTIxMDAwMDAwWhcNNDUwMTIxMDAwMDAwWjApMScwJQYDVQQDDB5WYWxpZCBTZWxm +IFNpZ25lZCBQcmltYXJ5IFJvb3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQDMK6v6HsJ19iRlXIRQVBJiy9xnJWBddLjm+2RRkyiiffEitBExiXVyrQ8L +DlegQQH3oJR0xgXwisJctjpHHz54dfbw5LwC9j9EVtu/UgDgK4lo6X3WLNYMJ1pX +xxjGfXcyzGGhcI0KATlyWhWgOrZNFzE+v0KY/LtZvcZ290Y4X7MQLge+V/09Lohx +pj6vsHkpoK8tD8ksJp+O8jk45TXTxs4yo8BRXbIv0oMmuZ9+gVkiaGurCCe/o+nw +vjEQre9oKNFI9KOgen6l1152BVQaXMDd22vemGIz738Scl9kcBQhy1D0dPuL6QV3 +rR8HoNG3i0cuYxB4xgFF5GY2fhQBAgMBAAGjYTBfMA4GA1UdDwEB/wQEAwIBhjAd +BgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDwYDVR0TAQH/BAUwAwEB/zAd +BgNVHQ4EFgQU/cAC4CkVEtEj2UCYMVS/mlFCdBAwDQYJKoZIhvcNAQELBQADggEB +AInTuEG8Kv2aWy7MJg/N/AvEmC7USMgTceFY+bKhVogYCE9m2VAa4Tz5DEEJwYQV +IBnEamQN1eWP/R7dxcg+gIck8TevZC6r7wKMUATCcn9Ti0I0Hxdplts9+YIksJJ7 +GbgyPS3UWnXl2D0374KrqTKSRjEXPOzaNyJ0HB4Pr9bibuSZ6Qc0gSltz7xOPFYS +7cedTqpABJXF6hZM7tDsxPfXmBHDy2sU84yXTQQghmU5S7fLWgy3so4g/DUqxffS +hmYPagc9DsmRGc2CCZz8IHlVc7byZ/NF4FgqB3IATbqYBAw4S/RyKHfWpURie2hC +OtYLcOTzVJG4uD3FGxyXP1k= +-----END CERTIFICATE----- diff --git a/src/Shared/test/Certificates/validSignedClientCertificate.cer b/src/Shared/test/Certificates/validSignedClientCertificate.cer new file mode 100644 index 0000000000..a8034b05a8 --- /dev/null +++ b/src/Shared/test/Certificates/validSignedClientCertificate.cer @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDTTCCAjWgAwIBAgIQaaZ/qIdm4K5JhFdDzzj53DANBgkqhkiG9w0BAQsFADAm +MSQwIgYDVQQDDBtWYWxpZCBTaWduZWQgU2Vjb25kYXJ5IFJvb3QwHhcNMjAwMTIx +MDAwMDAwWhcNMjIwMTIxMDAwMDAwWjAeMRwwGgYDVQQDDBNWYWxpZCBTaWduZWQg +Q2xpZW50MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu3dVA8q1GewW +i1K0Pw0gKqgv92RrX3JI6tTiLbU6FBpFdV63b1M3jgFhUSXJi7L8A/dxh2HwvKBJ +p+4KW7V+aXQXOY8iShQwrIud5IExFdtEjyGVtfFSvfYmDgbfjFKIGswxsLenlfEt +7mp303GH99JVFql1n7S+bib79vKkrjFBqixhnXisXjNlBlfH6kRBYiwQ1Gc5oyib +fZQkfakXo896UwIvQjc0W27c0tiGY6xyGLSesLih2yECADiGa+cc5rnRc7R9/IMB +N7o5gLpbe71WBopI1uq1VSuXwH9xy0bq307dZEMaX0b4SqhkuQHsBVtOV1mYAskE +K3W8VUZy7QIDAQABo38wfTAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYB +BQUHAwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHwYDVR0jBBgwFoAUx9BZtKX7 +/z2mmoO2Ec127GkibsAwHQYDVR0OBBYEFDmtlIR/fVwtDseOG/NNQ/QEv3/PMA0G +CSqGSIb3DQEBCwUAA4IBAQBQMhsmlwF5JKEkfay7uLCH9IrJZGk1ZDxK/qcVaOk5 +mQ4IcCBq+Wp7Hg/D92b5diwlkXJDrYZZ7OHSEcD/PrxUKyZkoBQIvlNKDgmjp0wV +lXYUISZHaXbWZ0XNFAS0KyqoLZ8c2xmhuI21L3hyOoRcoqKleO1kzYfb2seBaRHk +Iu4la0opKGFoI/o7gC9uLrcizpj3SoPF9+vJz/FJmeBbKzKe1zA479a74tjfOODy +LZVbsGDhKRQ02GftFqXRl257hVX+6etQiOePj7S++R1B/QXRjvKrOTMs/NpMLAeK +8uWXSx+boL/8j/3u+65Udh614C5dXSrgDjMGJ7/OchC1 +-----END CERTIFICATE----- diff --git a/src/Shared/test/Certificates/validSignedSecondaryRootCertificate.cer b/src/Shared/test/Certificates/validSignedSecondaryRootCertificate.cer new file mode 100644 index 0000000000..ef31f31a98 --- /dev/null +++ b/src/Shared/test/Certificates/validSignedSecondaryRootCertificate.cer @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDXTCCAkWgAwIBAgIQE2HFYAdh7b5NSBsomRG9cjANBgkqhkiG9w0BAQsFADAp +MScwJQYDVQQDDB5WYWxpZCBTZWxmIFNpZ25lZCBQcmltYXJ5IFJvb3QwHhcNMjAw +MTIxMDAwMDAwWhcNMzUwMTIxMDAwMDAwWjAmMSQwIgYDVQQDDBtWYWxpZCBTaWdu +ZWQgU2Vjb25kYXJ5IFJvb3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +AQCw1Wvr8SeMdXM0ZMN3/NyZhGzXC/IcqJyI1tM1IQNpO9OxJWDkfxGh14ORRZ3f +4pTdXOXRCcPYxHk8d3kuH9EEo78WRrLV4XHw31vGrQkHAPn3ZMl/Qre1mYvzkKbn +DIpScfPYMuqydOvx1YSsTP2G37pNszOAXTkHPPH9smTo/W7Dv/1mnroAru/FU+Hv +zOMqNirIz1EpCEopLeBS41lcohyuCMzHPKJJZOnNbV3wV3AnpEriRLQVNO9WiaGs +Nwj8ffai9M4vncRQ9wLK866lx6iA7istjod9hourKQWC1284pv+RLtIeJaGrpXkV +mSbk9ebabz1fPC2/WgPtd1JVAgMBAAGjgYMwgYAwDgYDVR0PAQH/BAQDAgGGMB0G +A1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAPBgNVHRMBAf8EBTADAQH/MB8G +A1UdIwQYMBaAFP3AAuApFRLRI9lAmDFUv5pRQnQQMB0GA1UdDgQWBBTH0Fm0pfv/ +Paaag7YRzXbsaSJuwDANBgkqhkiG9w0BAQsFAAOCAQEAT5fEUkVP3Allay2ODcjQ +GzM135mV718DS84B4bVDBWr+CW9i89bzYgRZgClTABqddotHEqmLEan/bV4suBSt +QuACy7m39Q8kj/S/ydBhvHx9fxqWnAsacQ+fuAPviBQ11UZB19zWj1zikw1/Xfow +V9OIf4gYtY2aBPyygWN3HwpszhJWQIuFGl4rwqAxli7Wp2eUBXxDtYBHAscsclG4 +1rduhiV5eUZXZ11mbA7KBH9XwWKoFpRza049I0WC+V0PWqlK4H4P0QzCWUlXmTC8 +kN04cnPtyciOlP9J3Uro5xTXaDC0Cge82JmRxnCovGKGBEdjIxMC4nbPB4emmcth +BA== +-----END CERTIFICATE----- diff --git a/src/Shared/test/Shared.Tests/ArgumentEscaperTests.cs b/src/Shared/test/Shared.Tests/ArgumentEscaperTests.cs new file mode 100644 index 0000000000..a706b05bfc --- /dev/null +++ b/src/Shared/test/Shared.Tests/ArgumentEscaperTests.cs @@ -0,0 +1,24 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Xunit; + +namespace Microsoft.Extensions.CommandLineUtils +{ + public class ArgumentEscaperTests + { + [Theory] + [InlineData(new[] { "one", "two", "three" }, "one two three")] + [InlineData(new[] { "line1\nline2", "word1\tword2" }, "\"line1\nline2\" \"word1\tword2\"")] + [InlineData(new[] { "with spaces" }, "\"with spaces\"")] + [InlineData(new[] { @"with\backslash" }, @"with\backslash")] + [InlineData(new[] { @"""quotedwith\backslash""" }, @"\""quotedwith\backslash\""")] + [InlineData(new[] { @"C:\Users\" }, @"C:\Users\")] + [InlineData(new[] { @"C:\Program Files\dotnet\" }, @"""C:\Program Files\dotnet\\""")] + [InlineData(new[] { @"backslash\""preceedingquote" }, @"backslash\\\""preceedingquote")] + public void EscapesArguments(string[] args, string expected) + { + Assert.Equal(expected, ArgumentEscaper.EscapeAndConcatenate(args)); + } + } +} diff --git a/src/Shared/test/Shared.Tests/CommandLineApplicationTests.cs b/src/Shared/test/Shared.Tests/CommandLineApplicationTests.cs new file mode 100644 index 0000000000..0bdc4a8f1d --- /dev/null +++ b/src/Shared/test/Shared.Tests/CommandLineApplicationTests.cs @@ -0,0 +1,1224 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; +using System.Linq; +using Microsoft.Extensions.CommandLineUtils; +using Xunit; + +namespace Microsoft.Extensions.Internal +{ + public class CommandLineApplicationTests + { + [Fact] + public void CommandNameCanBeMatched() + { + var called = false; + + var app = new CommandLineApplication(); + app.Command("test", c => + { + c.OnExecute(() => + { + called = true; + return 5; + }); + }); + + var result = app.Execute("test"); + Assert.Equal(5, result); + Assert.True(called); + } + + [Fact] + public void RemainingArgsArePassed() + { + CommandArgument first = null; + CommandArgument second = null; + + var app = new CommandLineApplication(); + + app.Command("test", c => + { + first = c.Argument("first", "First argument"); + second = c.Argument("second", "Second argument"); + c.OnExecute(() => 0); + }); + + app.Execute("test", "one", "two"); + + Assert.Equal("one", first.Value); + Assert.Equal("two", second.Value); + } + + [Fact] + public void ExtraArgumentCausesException() + { + CommandArgument first = null; + CommandArgument second = null; + + var app = new CommandLineApplication(); + + app.Command("test", c => + { + first = c.Argument("first", "First argument"); + second = c.Argument("second", "Second argument"); + c.OnExecute(() => 0); + }); + + var ex = Assert.Throws(() => app.Execute("test", "one", "two", "three")); + + Assert.Contains("three", ex.Message); + } + + [Fact] + public void ExtraArgumentAddedToRemaining() + { + CommandArgument first = null; + CommandArgument second = null; + + var app = new CommandLineApplication(); + + var testCommand = app.Command("test", c => + { + first = c.Argument("first", "First argument"); + second = c.Argument("second", "Second argument"); + c.OnExecute(() => 0); + }, + throwOnUnexpectedArg: false); + + app.Execute("test", "one", "two", "three"); + + Assert.Equal("one", first.Value); + Assert.Equal("two", second.Value); + var remaining = Assert.Single(testCommand.RemainingArguments); + Assert.Equal("three", remaining); + } + + [Fact] + public void UnknownCommandCausesException() + { + var app = new CommandLineApplication(); + + app.Command("test", c => + { + c.Argument("first", "First argument"); + c.Argument("second", "Second argument"); + c.OnExecute(() => 0); + }); + + var ex = Assert.Throws(() => app.Execute("test2", "one", "two", "three")); + + Assert.Contains("test2", ex.Message); + } + + [Fact] + public void MultipleValuesArgumentConsumesAllArgumentValues() + { + CommandArgument argument = null; + + var app = new CommandLineApplication(); + + app.Command("test", c => + { + argument = c.Argument("arg", "Argument that allows multiple values", multipleValues: true); + c.OnExecute(() => 0); + }); + + app.Execute("test", "one", "two", "three", "four", "five"); + + Assert.Equal(new[] { "one", "two", "three", "four", "five" }, argument.Values); + } + + [Fact] + public void MultipleValuesArgumentConsumesAllRemainingArgumentValues() + { + CommandArgument first = null; + CommandArgument second = null; + CommandArgument third = null; + + var app = new CommandLineApplication(); + + app.Command("test", c => + { + first = c.Argument("first", "First argument"); + second = c.Argument("second", "Second argument"); + third = c.Argument("third", "Third argument that allows multiple values", multipleValues: true); + c.OnExecute(() => 0); + }); + + app.Execute("test", "one", "two", "three", "four", "five"); + + Assert.Equal("one", first.Value); + Assert.Equal("two", second.Value); + Assert.Equal(new[] { "three", "four", "five" }, third.Values); + } + + [Fact] + public void MultipleValuesArgumentMustBeTheLastArgument() + { + var app = new CommandLineApplication(); + app.Argument("first", "First argument", multipleValues: true); + var ex = Assert.Throws(() => app.Argument("second", "Second argument")); + + Assert.Contains($"The last argument 'first' accepts multiple values. No more argument can be added.", + ex.Message); + } + + [Fact] + public void OptionSwitchMayBeProvided() + { + CommandOption first = null; + CommandOption second = null; + + var app = new CommandLineApplication(); + + app.Command("test", c => + { + first = c.Option("--first ", "First argument", CommandOptionType.SingleValue); + second = c.Option("--second ", "Second argument", CommandOptionType.SingleValue); + c.OnExecute(() => 0); + }); + + app.Execute("test", "--first", "one", "--second", "two"); + + Assert.Equal("one", first.Values[0]); + Assert.Equal("two", second.Values[0]); + } + + [Fact] + public void OptionValueMustBeProvided() + { + CommandOption first = null; + + var app = new CommandLineApplication(); + + app.Command("test", c => + { + first = c.Option("--first ", "First argument", CommandOptionType.SingleValue); + c.OnExecute(() => 0); + }); + + var ex = Assert.Throws(() => app.Execute("test", "--first")); + + Assert.Contains($"Missing value for option '{first.LongName}'", ex.Message); + } + + [Fact] + public void ValuesMayBeAttachedToSwitch() + { + CommandOption first = null; + CommandOption second = null; + + var app = new CommandLineApplication(); + + app.Command("test", c => + { + first = c.Option("--first ", "First argument", CommandOptionType.SingleValue); + second = c.Option("--second ", "Second argument", CommandOptionType.SingleValue); + c.OnExecute(() => 0); + }); + + app.Execute("test", "--first=one", "--second:two"); + + Assert.Equal("one", first.Values[0]); + Assert.Equal("two", second.Values[0]); + } + + [Fact] + public void ShortNamesMayBeDefined() + { + CommandOption first = null; + CommandOption second = null; + + var app = new CommandLineApplication(); + + app.Command("test", c => + { + first = c.Option("-1 --first ", "First argument", CommandOptionType.SingleValue); + second = c.Option("-2 --second ", "Second argument", CommandOptionType.SingleValue); + c.OnExecute(() => 0); + }); + + app.Execute("test", "-1=one", "-2", "two"); + + Assert.Equal("one", first.Values[0]); + Assert.Equal("two", second.Values[0]); + } + + [Fact] + public void ThrowsExceptionOnUnexpectedCommandOrArgumentByDefault() + { + var unexpectedArg = "UnexpectedArg"; + var app = new CommandLineApplication(); + + app.Command("test", c => + { + c.OnExecute(() => 0); + }); + + var exception = Assert.Throws(() => app.Execute("test", unexpectedArg)); + Assert.Equal($"Unrecognized command or argument '{unexpectedArg}'", exception.Message); + } + + [Fact] + public void AllowNoThrowBehaviorOnUnexpectedArgument() + { + var unexpectedArg = "UnexpectedArg"; + var app = new CommandLineApplication(); + + var testCmd = app.Command("test", c => + { + c.OnExecute(() => 0); + }, + throwOnUnexpectedArg: false); + + // (does not throw) + app.Execute("test", unexpectedArg); + var arg = Assert.Single(testCmd.RemainingArguments); + Assert.Equal(unexpectedArg, arg); + } + + [Fact] + public void AllowArgumentBeforeNoValueOption() + { + var app = new CommandLineApplication(); + var argument = app.Argument("first", "first argument"); + var option = app.Option("--first", "first option", CommandOptionType.NoValue); + + app.Execute("one", "--first"); + + Assert.Equal("one", argument.Value); + Assert.True(option.HasValue()); + } + + [Fact] + public void AllowArgumentAfterNoValueOption() + { + var app = new CommandLineApplication(); + var argument = app.Argument("first", "first argument"); + var option = app.Option("--first", "first option", CommandOptionType.NoValue); + + app.Execute("--first", "one"); + + Assert.Equal("one", argument.Value); + Assert.True(option.HasValue()); + } + + [Fact] + public void AllowArgumentBeforeSingleValueOption() + { + var app = new CommandLineApplication(); + var argument = app.Argument("first", "first argument"); + var option = app.Option("--first ", "first option", CommandOptionType.SingleValue); + + app.Execute("one", "--first", "two"); + + Assert.Equal("one", argument.Value); + Assert.Equal("two", option.Value()); + } + + [Fact] + public void AllowArgumentAfterSingleValueOption() + { + var app = new CommandLineApplication(); + var argument = app.Argument("first", "first argument"); + var option = app.Option("--first ", "first option", CommandOptionType.SingleValue); + + app.Execute("--first", "one", "two"); + + Assert.Equal("two", argument.Value); + Assert.Equal("one", option.Value()); + } + + [Fact] + public void AllowNoThrowBehaviorOnUnexpectedArgumentBeforeNoValueOption_Default() + { + var arguments = new[] { "UnexpectedArg", "--first" }; + var app = new CommandLineApplication(throwOnUnexpectedArg: false); + var option = app.Option("--first", "first option", CommandOptionType.NoValue); + + // (does not throw) + app.Execute(arguments); + + Assert.Equal(arguments, app.RemainingArguments.ToArray()); + Assert.False(option.HasValue()); + } + + [Fact] + public void AllowNoThrowBehaviorOnUnexpectedArgumentBeforeNoValueOption_Continue() + { + var unexpectedArg = "UnexpectedArg"; + var app = new CommandLineApplication(throwOnUnexpectedArg: false, continueAfterUnexpectedArg: true); + var option = app.Option("--first", "first option", CommandOptionType.NoValue); + + // (does not throw) + app.Execute(unexpectedArg, "--first"); + + var arg = Assert.Single(app.RemainingArguments); + Assert.Equal(unexpectedArg, arg); + Assert.True(option.HasValue()); + } + + [Fact] + public void AllowNoThrowBehaviorOnUnexpectedArgumentAfterNoValueOption() + { + var unexpectedArg = "UnexpectedArg"; + var app = new CommandLineApplication(throwOnUnexpectedArg: false); + var option = app.Option("--first", "first option", CommandOptionType.NoValue); + + // (does not throw) + app.Execute("--first", unexpectedArg); + + var arg = Assert.Single(app.RemainingArguments); + Assert.Equal(unexpectedArg, arg); + Assert.True(option.HasValue()); + } + + [Fact] + public void AllowNoThrowBehaviorOnUnexpectedArgumentBeforeSingleValueOption_Default() + { + var arguments = new[] { "UnexpectedArg", "--first", "one" }; + var app = new CommandLineApplication(throwOnUnexpectedArg: false); + app.Option("--first", "first option", CommandOptionType.SingleValue); + + // (does not throw) + app.Execute(arguments); + + Assert.Equal(arguments, app.RemainingArguments.ToArray()); + } + + [Fact] + public void AllowNoThrowBehaviorOnUnexpectedArgumentBeforeSingleValueOption_Continue() + { + var unexpectedArg = "UnexpectedArg"; + var app = new CommandLineApplication(throwOnUnexpectedArg: false, continueAfterUnexpectedArg: true); + var option = app.Option("--first", "first option", CommandOptionType.SingleValue); + + // (does not throw) + app.Execute(unexpectedArg, "--first", "one"); + + var arg = Assert.Single(app.RemainingArguments); + Assert.Equal(unexpectedArg, arg); + Assert.Equal("one", option.Value()); + } + + [Fact] + public void AllowNoThrowBehaviorOnUnexpectedArgumentAfterSingleValueOption() + { + var unexpectedArg = "UnexpectedArg"; + var app = new CommandLineApplication(throwOnUnexpectedArg: false); + var option = app.Option("--first", "first option", CommandOptionType.SingleValue); + + // (does not throw) + app.Execute("--first", "one", unexpectedArg); + + var arg = Assert.Single(app.RemainingArguments); + Assert.Equal(unexpectedArg, arg); + Assert.Equal("one", option.Value()); + } + + [Fact] + public void ThrowsExceptionOnUnexpectedLongOptionByDefault() + { + var unexpectedOption = "--UnexpectedOption"; + var app = new CommandLineApplication(); + + app.Command("test", c => + { + c.OnExecute(() => 0); + }); + + var exception = Assert.Throws(() => app.Execute("test", unexpectedOption)); + Assert.Equal($"Unrecognized option '{unexpectedOption}'", exception.Message); + } + + [Fact] + public void AllowNoThrowBehaviorOnUnexpectedLongOption() + { + var unexpectedOption = "--UnexpectedOption"; + var app = new CommandLineApplication(); + + var testCmd = app.Command("test", c => + { + c.OnExecute(() => 0); + }, + throwOnUnexpectedArg: false); + + // (does not throw) + app.Execute("test", unexpectedOption); + var arg = Assert.Single(testCmd.RemainingArguments); + Assert.Equal(unexpectedOption, arg); + } + + [Fact] + public void AllowNoThrowBehaviorOnUnexpectedLongOptionBeforeNoValueOption_Default() + { + var arguments = new[] { "--unexpected", "--first" }; + var app = new CommandLineApplication(throwOnUnexpectedArg: false); + app.Option("--first", "first option", CommandOptionType.NoValue); + + // (does not throw) + app.Execute(arguments); + + Assert.Equal(arguments, app.RemainingArguments.ToArray()); + } + + [Fact] + public void AllowNoThrowBehaviorOnUnexpectedLongOptionBeforeNoValueOption_Continue() + { + var unexpectedOption = "--unexpected"; + var app = new CommandLineApplication(throwOnUnexpectedArg: false, continueAfterUnexpectedArg: true); + var option = app.Option("--first", "first option", CommandOptionType.NoValue); + + // (does not throw) + app.Execute(unexpectedOption, "--first"); + + var arg = Assert.Single(app.RemainingArguments); + Assert.Equal(unexpectedOption, arg); + Assert.True(option.HasValue()); + } + + [Fact] + public void AllowNoThrowBehaviorOnUnexpectedLongOptionAfterNoValueOption() + { + var unexpectedOption = "--unexpected"; + var app = new CommandLineApplication(throwOnUnexpectedArg: false); + var option = app.Option("--first", "first option", CommandOptionType.NoValue); + + // (does not throw) + app.Execute("--first", unexpectedOption); + + var arg = Assert.Single(app.RemainingArguments); + Assert.Equal(unexpectedOption, arg); + Assert.True(option.HasValue()); + } + + [Fact] + public void AllowNoThrowBehaviorOnUnexpectedLongOptionBeforeSingleValueOption_Default() + { + var arguments = new[] { "--unexpected", "--first", "one" }; + var app = new CommandLineApplication(throwOnUnexpectedArg: false); + app.Option("--first", "first option", CommandOptionType.SingleValue); + + // (does not throw) + app.Execute(arguments); + + Assert.Equal(arguments, app.RemainingArguments.ToArray()); + } + + [Fact] + public void AllowNoThrowBehaviorOnUnexpectedLongOptionBeforeSingleValueOption_Continue() + { + var unexpectedOption = "--unexpected"; + var app = new CommandLineApplication(throwOnUnexpectedArg: false, continueAfterUnexpectedArg: true); + var option = app.Option("--first", "first option", CommandOptionType.SingleValue); + + // (does not throw) + app.Execute(unexpectedOption, "--first", "one"); + + var arg = Assert.Single(app.RemainingArguments); + Assert.Equal(unexpectedOption, arg); + Assert.Equal("one", option.Value()); + } + + [Fact] + public void AllowNoThrowBehaviorOnUnexpectedLongOptionAfterSingleValueOption() + { + var unexpectedOption = "--unexpected"; + var app = new CommandLineApplication(throwOnUnexpectedArg: false); + var option = app.Option("--first", "first option", CommandOptionType.SingleValue); + + // (does not throw) + app.Execute("--first", "one", unexpectedOption); + + var arg = Assert.Single(app.RemainingArguments); + Assert.Equal(unexpectedOption, arg); + Assert.Equal("one", option.Value()); + } + + [Fact] + public void AllowNoThrowBehaviorOnUnexpectedLongOptionWithValueBeforeNoValueOption_Default() + { + var arguments = new[] { "--unexpected", "value", "--first" }; + var app = new CommandLineApplication(throwOnUnexpectedArg: false); + app.Option("--first", "first option", CommandOptionType.NoValue); + + // (does not throw) + app.Execute(arguments); + + Assert.Equal(arguments, app.RemainingArguments.ToArray()); + } + + [Fact] + public void AllowNoThrowBehaviorOnUnexpectedLongOptionWithValueBeforeNoValueOption_Continue() + { + var unexpectedOption = "--unexpected"; + var unexpectedValue = "value"; + var app = new CommandLineApplication(throwOnUnexpectedArg: false, continueAfterUnexpectedArg: true); + var option = app.Option("--first", "first option", CommandOptionType.NoValue); + + // (does not throw) + app.Execute(unexpectedOption, unexpectedValue, "--first"); + + Assert.Equal(new[] { unexpectedOption, unexpectedValue }, app.RemainingArguments.ToArray()); + Assert.True(option.HasValue()); + } + + [Fact] + public void AllowNoThrowBehaviorOnUnexpectedLongOptionWithValueAfterNoValueOption() + { + var unexpectedOption = "--unexpected"; + var unexpectedValue = "value"; + var app = new CommandLineApplication(throwOnUnexpectedArg: false); + var option = app.Option("--first", "first option", CommandOptionType.NoValue); + + // (does not throw) + app.Execute("--first", unexpectedOption, unexpectedValue); + + Assert.Equal(new[] { unexpectedOption, unexpectedValue }, app.RemainingArguments.ToArray()); + Assert.True(option.HasValue()); + } + + [Fact] + public void AllowNoThrowBehaviorOnUnexpectedLongOptionWithValueBeforeSingleValueOption_Default() + { + var unexpectedOption = "--unexpected"; + var unexpectedValue = "value"; + var app = new CommandLineApplication(throwOnUnexpectedArg: false); + app.Option("--first", "first option", CommandOptionType.SingleValue); + + // (does not throw) + app.Execute(unexpectedOption, unexpectedValue, "--first", "one"); + + Assert.Equal( + new[] { unexpectedOption, unexpectedValue, "--first", "one" }, + app.RemainingArguments.ToArray()); + } + + [Fact] + public void AllowNoThrowBehaviorOnUnexpectedLongOptionWithValueBeforeSingleValueOption_Continue() + { + var unexpectedOption = "--unexpected"; + var unexpectedValue = "value"; + var app = new CommandLineApplication(throwOnUnexpectedArg: false, continueAfterUnexpectedArg: true); + var option = app.Option("--first", "first option", CommandOptionType.SingleValue); + + // (does not throw) + app.Execute(unexpectedOption, unexpectedValue, "--first", "one"); + + Assert.Equal( + new[] { unexpectedOption, unexpectedValue }, + app.RemainingArguments.ToArray()); + Assert.Equal("one", option.Value()); + } + + [Fact] + public void AllowNoThrowBehaviorOnUnexpectedLongOptionWithValueAfterSingleValueOption() + { + var unexpectedOption = "--unexpected"; + var unexpectedValue = "value"; + var app = new CommandLineApplication(throwOnUnexpectedArg: false); + var option = app.Option("--first", "first option", CommandOptionType.SingleValue); + + // (does not throw) + app.Execute("--first", "one", unexpectedOption, unexpectedValue); + + Assert.Equal(new[] { unexpectedOption, unexpectedValue }, app.RemainingArguments.ToArray()); + Assert.Equal("one", option.Value()); + } + + [Fact] + public void ThrowsExceptionOnUnexpectedShortOptionByDefault() + { + var unexpectedOption = "-uexp"; + var app = new CommandLineApplication(); + + app.Command("test", c => + { + c.OnExecute(() => 0); + }); + + var exception = Assert.Throws(() => app.Execute("test", unexpectedOption)); + Assert.Equal($"Unrecognized option '{unexpectedOption}'", exception.Message); + } + + [Fact] + public void AllowNoThrowBehaviorOnUnexpectedShortOption() + { + var unexpectedOption = "-uexp"; + var app = new CommandLineApplication(); + + var testCmd = app.Command("test", c => + { + c.OnExecute(() => 0); + }, + throwOnUnexpectedArg: false); + + // (does not throw) + app.Execute("test", unexpectedOption); + var arg = Assert.Single(testCmd.RemainingArguments); + Assert.Equal(unexpectedOption, arg); + } + + [Fact] + public void ThrowsExceptionOnUnexpectedSymbolOptionByDefault() + { + var unexpectedOption = "-?"; + var app = new CommandLineApplication(); + + app.Command("test", c => + { + c.OnExecute(() => 0); + }); + + var exception = Assert.Throws(() => app.Execute("test", unexpectedOption)); + Assert.Equal($"Unrecognized option '{unexpectedOption}'", exception.Message); + } + + [Fact] + public void AllowNoThrowBehaviorOnUnexpectedSymbolOption() + { + var unexpectedOption = "-?"; + var app = new CommandLineApplication(); + + var testCmd = app.Command("test", c => + { + c.OnExecute(() => 0); + }, + throwOnUnexpectedArg: false); + + // (does not throw) + app.Execute("test", unexpectedOption); + var arg = Assert.Single(testCmd.RemainingArguments); + Assert.Equal(unexpectedOption, arg); + } + + [Fact] + public void ThrowsExceptionOnUnexpectedOptionBeforeValidSubcommandByDefault() + { + var unexpectedOption = "--unexpected"; + CommandLineApplication subCmd = null; + var app = new CommandLineApplication(); + + app.Command("k", c => + { + subCmd = c.Command("run", _ => { }); + c.OnExecute(() => 0); + }); + + var exception = Assert.Throws(() => app.Execute("k", unexpectedOption, "run")); + Assert.Equal($"Unrecognized option '{unexpectedOption}'", exception.Message); + } + + [Fact] + public void AllowNoThrowBehaviorOnUnexpectedOptionBeforeSubcommand() + { + var unexpectedOption = "--unexpected"; + var app = new CommandLineApplication(); + + CommandLineApplication subCmd = null; + var testCmd = app.Command("k", c => + { + subCmd = c.Command("run", _ => { }); + c.OnExecute(() => 0); + }, + throwOnUnexpectedArg: false); + + // (does not throw) + app.Execute("k", unexpectedOption, "run"); + + Assert.Empty(app.RemainingArguments); + Assert.Equal(new[] { unexpectedOption, "run" }, testCmd.RemainingArguments.ToArray()); + Assert.Empty(subCmd.RemainingArguments); + } + + [Fact] + public void AllowNoThrowBehaviorOnUnexpectedOptionAfterSubcommand() + { + var unexpectedOption = "--unexpected"; + var app = new CommandLineApplication(); + + CommandLineApplication subCmd = null; + var testCmd = app.Command("k", c => + { + subCmd = c.Command("run", _ => { }, throwOnUnexpectedArg: false); + c.OnExecute(() => 0); + }); + + // (does not throw) + app.Execute("k", "run", unexpectedOption); + + Assert.Empty(app.RemainingArguments); + Assert.Empty(testCmd.RemainingArguments); + var arg = Assert.Single(subCmd.RemainingArguments); + Assert.Equal(unexpectedOption, arg); + } + + [Fact] + public void AllowNoThrowBehaviorOnUnexpectedOptionBeforeValidCommand_Default() + { + var arguments = new[] { "--unexpected", "run" }; + var app = new CommandLineApplication(throwOnUnexpectedArg: false); + var commandRan = false; + app.Command("run", c => c.OnExecute(() => { commandRan = true; return 0; })); + app.OnExecute(() => 0); + + app.Execute(arguments); + + Assert.False(commandRan); + Assert.Equal(arguments, app.RemainingArguments.ToArray()); + } + + [Fact] + public void AllowNoThrowBehaviorOnUnexpectedOptionBeforeValidCommand_Continue() + { + var unexpectedOption = "--unexpected"; + var app = new CommandLineApplication(throwOnUnexpectedArg: false, continueAfterUnexpectedArg: true); + var commandRan = false; + app.Command("run", c => c.OnExecute(() => { commandRan = true; return 0; })); + app.OnExecute(() => 0); + + app.Execute(unexpectedOption, "run"); + + Assert.True(commandRan); + var remaining = Assert.Single(app.RemainingArguments); + Assert.Equal(unexpectedOption, remaining); + } + + [Fact] + public void OptionsCanBeInherited() + { + var app = new CommandLineApplication(); + var optionA = app.Option("-a|--option-a", "", CommandOptionType.SingleValue, inherited: true); + string optionAValue = null; + + var optionB = app.Option("-b", "", CommandOptionType.SingleValue, inherited: false); + + var subcmd = app.Command("subcmd", c => + { + c.OnExecute(() => + { + optionAValue = optionA.Value(); + return 0; + }); + }); + + Assert.Equal(2, app.GetOptions().Count()); + Assert.Single(subcmd.GetOptions()); + + app.Execute("-a", "A1", "subcmd"); + Assert.Equal("A1", optionAValue); + + Assert.Throws(() => app.Execute("subcmd", "-b", "B")); + + Assert.Contains("-a|--option-a", subcmd.GetHelpText()); + } + + [Fact] + public void NestedOptionConflictThrows() + { + var app = new CommandLineApplication(); + app.Option("-a|--always", "Top-level", CommandOptionType.SingleValue, inherited: true); + app.Command("subcmd", c => + { + c.Option("-a|--ask", "Nested", CommandOptionType.SingleValue); + }); + + Assert.Throws(() => app.Execute("subcmd", "-a", "b")); + } + + [Fact] + public void OptionsWithSameName() + { + var app = new CommandLineApplication(); + var top = app.Option("-a|--always", "Top-level", CommandOptionType.SingleValue, inherited: false); + CommandOption nested = null; + app.Command("subcmd", c => + { + nested = c.Option("-a|--ask", "Nested", CommandOptionType.SingleValue); + }); + + app.Execute("-a", "top"); + Assert.Equal("top", top.Value()); + Assert.Null(nested.Value()); + + top.Values.Clear(); + + app.Execute("subcmd", "-a", "nested"); + Assert.Null(top.Value()); + Assert.Equal("nested", nested.Value()); + } + + [Fact] + public void NestedInheritedOptions() + { + string globalOptionValue = null, nest1OptionValue = null, nest2OptionValue = null; + + var app = new CommandLineApplication(); + CommandLineApplication subcmd2 = null; + var g = app.Option("-g|--global", "Global option", CommandOptionType.SingleValue, inherited: true); + var subcmd1 = app.Command("lvl1", s1 => + { + var n1 = s1.Option("--nest1", "Nested one level down", CommandOptionType.SingleValue, inherited: true); + subcmd2 = s1.Command("lvl2", s2 => + { + var n2 = s2.Option("--nest2", "Nested one level down", CommandOptionType.SingleValue, inherited: true); + s2.HelpOption("-h|--help"); + s2.OnExecute(() => + { + globalOptionValue = g.Value(); + nest1OptionValue = n1.Value(); + nest2OptionValue = n2.Value(); + return 0; + }); + }); + }); + + Assert.DoesNotContain(app.GetOptions(), o => o.LongName == "nest2"); + Assert.DoesNotContain(app.GetOptions(), o => o.LongName == "nest1"); + Assert.Contains(app.GetOptions(), o => o.LongName == "global"); + + Assert.DoesNotContain(subcmd1.GetOptions(), o => o.LongName == "nest2"); + Assert.Contains(subcmd1.GetOptions(), o => o.LongName == "nest1"); + Assert.Contains(subcmd1.GetOptions(), o => o.LongName == "global"); + + Assert.Contains(subcmd2.GetOptions(), o => o.LongName == "nest2"); + Assert.Contains(subcmd2.GetOptions(), o => o.LongName == "nest1"); + Assert.Contains(subcmd2.GetOptions(), o => o.LongName == "global"); + + Assert.Throws(() => app.Execute("--nest2", "N2", "--nest1", "N1", "-g", "G")); + Assert.Throws(() => app.Execute("lvl1", "--nest2", "N2", "--nest1", "N1", "-g", "G")); + + app.Execute("lvl1", "lvl2", "--nest2", "N2", "-g", "G", "--nest1", "N1"); + Assert.Equal("G", globalOptionValue); + Assert.Equal("N1", nest1OptionValue); + Assert.Equal("N2", nest2OptionValue); + } + + [Theory] + [InlineData(new string[0], new string[0], null)] + [InlineData(new[] { "--" }, new string[0], null)] + [InlineData(new[] { "-t", "val" }, new string[0], "val")] + [InlineData(new[] { "-t", "val", "--" }, new string[0], "val")] + [InlineData(new[] { "--top", "val", "--", "a" }, new[] { "a" }, "val")] + [InlineData(new[] { "--", "a", "--top", "val" }, new[] { "a", "--top", "val" }, null)] + [InlineData(new[] { "-t", "val", "--", "a", "--", "b" }, new[] { "a", "--", "b" }, "val")] + [InlineData(new[] { "--", "--help" }, new[] { "--help" }, null)] + [InlineData(new[] { "--", "--version" }, new[] { "--version" }, null)] + public void ArgumentSeparator(string[] input, string[] expectedRemaining, string topLevelValue) + { + var app = new CommandLineApplication(throwOnUnexpectedArg: false) + { + AllowArgumentSeparator = true + }; + var optHelp = app.HelpOption("--help"); + var optVersion = app.VersionOption("--version", "1", "1.0"); + var optTop = app.Option("-t|--top ", "arg for command", CommandOptionType.SingleValue); + app.Execute(input); + + Assert.Equal(topLevelValue, optTop.Value()); + Assert.False(optHelp.HasValue()); + Assert.False(optVersion.HasValue()); + Assert.Equal(expectedRemaining, app.RemainingArguments.ToArray()); + } + + [Theory] + [InlineData(new string[0], new string[0], null, false)] + [InlineData(new[] { "--" }, new[] { "--" }, null, false)] + [InlineData(new[] { "-t", "val" }, new string[0], "val", false)] + [InlineData(new[] { "-t", "val", "--" }, new[] { "--" }, "val", false)] + [InlineData(new[] { "--top", "val", "--", "a" }, new[] { "--", "a" }, "val", false)] + [InlineData(new[] { "-t", "val", "--", "a", "--", "b" }, new[] { "--", "a", "--", "b" }, "val", false)] + [InlineData(new[] { "--help", "--" }, new string[0], null, true)] + [InlineData(new[] { "--version", "--" }, new string[0], null, true)] + public void ArgumentSeparator_TreatedAsUexpected( + string[] input, + string[] expectedRemaining, + string topLevelValue, + bool isShowingInformation) + { + var app = new CommandLineApplication(throwOnUnexpectedArg: false); + var optHelp = app.HelpOption("--help"); + var optVersion = app.VersionOption("--version", "1", "1.0"); + var optTop = app.Option("-t|--top ", "arg for command", CommandOptionType.SingleValue); + + app.Execute(input); + + Assert.Equal(topLevelValue, optTop.Value()); + Assert.Equal(expectedRemaining, app.RemainingArguments.ToArray()); + Assert.Equal(isShowingInformation, app.IsShowingInformation); + + // Help and Version options never get values; parsing ends when encountered. + Assert.False(optHelp.HasValue()); + Assert.False(optVersion.HasValue()); + } + + [Theory] + [InlineData(new[] { "--", "a", "--top", "val" }, new[] { "--", "a", "--top", "val" }, null, false)] + [InlineData(new[] { "--", "--help" }, new[] { "--", "--help" }, null, false)] + [InlineData(new[] { "--", "--version" }, new[] { "--", "--version" }, null, false)] + [InlineData(new[] { "unexpected", "--", "--version" }, new[] { "unexpected", "--", "--version" }, null, false)] + public void ArgumentSeparator_TreatedAsUexpected_Default( + string[] input, + string[] expectedRemaining, + string topLevelValue, + bool isShowingInformation) + { + var app = new CommandLineApplication(throwOnUnexpectedArg: false); + var optHelp = app.HelpOption("--help"); + var optVersion = app.VersionOption("--version", "1", "1.0"); + var optTop = app.Option("-t|--top ", "arg for command", CommandOptionType.SingleValue); + + app.Execute(input); + + Assert.Equal(topLevelValue, optTop.Value()); + Assert.Equal(expectedRemaining, app.RemainingArguments.ToArray()); + Assert.Equal(isShowingInformation, app.IsShowingInformation); + + // Help and Version options never get values; parsing ends when encountered. + Assert.False(optHelp.HasValue()); + Assert.False(optVersion.HasValue()); + } + + [Theory] + [InlineData(new[] { "--", "a", "--top", "val" }, new[] { "--", "a" }, "val", false)] + [InlineData(new[] { "--", "--help" }, new[] { "--" }, null, true)] + [InlineData(new[] { "--", "--version" }, new[] { "--" }, null, true)] + [InlineData(new[] { "unexpected", "--", "--version" }, new[] { "unexpected", "--" }, null, true)] + public void ArgumentSeparator_TreatedAsUexpected_Continue( + string[] input, + string[] expectedRemaining, + string topLevelValue, + bool isShowingInformation) + { + var app = new CommandLineApplication(throwOnUnexpectedArg: false, continueAfterUnexpectedArg: true); + var optHelp = app.HelpOption("--help"); + var optVersion = app.VersionOption("--version", "1", "1.0"); + var optTop = app.Option("-t|--top ", "arg for command", CommandOptionType.SingleValue); + + app.Execute(input); + + Assert.Equal(topLevelValue, optTop.Value()); + Assert.Equal(expectedRemaining, app.RemainingArguments.ToArray()); + Assert.Equal(isShowingInformation, app.IsShowingInformation); + + // Help and Version options never get values; parsing ends when encountered. + Assert.False(optHelp.HasValue()); + Assert.False(optVersion.HasValue()); + } + + [Fact] + public void HelpTextIgnoresHiddenItems() + { + var app = new CommandLineApplication() + { + Name = "ninja-app", + Description = "You can't see it until it is too late" + }; + + app.Command("star", c => + { + c.Option("--points

      ", "How many", CommandOptionType.MultipleValue); + c.ShowInHelpText = false; + }); + app.Option("--smile", "Be a nice ninja", CommandOptionType.NoValue, o => { o.ShowInHelpText = false; }); + + var a = app.Argument("name", "Pseudonym, of course"); + a.ShowInHelpText = false; + + var help = app.GetHelpText(); + + Assert.Contains("ninja-app", help); + Assert.DoesNotContain("--points", help); + Assert.DoesNotContain("--smile", help); + Assert.DoesNotContain("name", help); + } + + [Fact] + public void HelpTextUsesHelpOptionName() + { + var app = new CommandLineApplication + { + Name = "superhombre" + }; + + app.HelpOption("--ayuda-me"); + var help = app.GetHelpText(); + Assert.Contains("--ayuda-me", help); + } + + [Fact] + public void HelpTextShowsArgSeparator() + { + var app = new CommandLineApplication(throwOnUnexpectedArg: false) + { + Name = "proxy-command", + AllowArgumentSeparator = true + }; + app.HelpOption("-h|--help"); + Assert.Contains("Usage: proxy-command [options] [[--] ...]", app.GetHelpText()); + } + + [Fact] + public void HelpTextShowsExtendedHelp() + { + var app = new CommandLineApplication() + { + Name = "befuddle", + ExtendedHelpText = @" +Remarks: + This command is so confusing that I want to include examples in the help text. + +Examples: + dotnet befuddle -- I Can Haz Confusion Arguments +" + }; + + Assert.Contains(app.ExtendedHelpText, app.GetHelpText()); + } + + [Theory] + [InlineData(new[] { "--version", "--flag" }, "1.0")] + [InlineData(new[] { "-V", "-f" }, "1.0")] + [InlineData(new[] { "--help", "--flag" }, "some flag")] + [InlineData(new[] { "-h", "-f" }, "some flag")] + public void HelpAndVersionOptionStopProcessing(string[] input, string expectedOutData) + { + using var outWriter = new StringWriter(); + var app = new CommandLineApplication { Out = outWriter }; + app.HelpOption("-h --help"); + app.VersionOption("-V --version", "1", "1.0"); + var optFlag = app.Option("-f |--flag", "some flag", CommandOptionType.NoValue); + + app.Execute(input); + + outWriter.Flush(); + var outData = outWriter.ToString(); + Assert.Contains(expectedOutData, outData); + Assert.False(optFlag.HasValue()); + } + + // disable inaccurate analyzer error https://github.com/xunit/xunit/issues/1274 +#pragma warning disable xUnit1010 +#pragma warning disable xUnit1011 + [Theory] + [InlineData("-f:File1", "-f:File2")] + [InlineData("--file:File1", "--file:File2")] + [InlineData("--file", "File1", "--file", "File2")] +#pragma warning restore xUnit1010 +#pragma warning restore xUnit1011 + public void ThrowsExceptionOnSingleValueOptionHavingTwoValues(params string[] inputOptions) + { + var app = new CommandLineApplication(); + app.Option("-f |--file", "some file", CommandOptionType.SingleValue); + + var exception = Assert.Throws(() => app.Execute(inputOptions)); + + Assert.Equal("Unexpected value 'File2' for option 'file'", exception.Message); + } + + [Theory] + [InlineData("-v")] + [InlineData("--verbose")] + public void NoValueOptionCanBeSet(string input) + { + var app = new CommandLineApplication(); + var optVerbose = app.Option("-v |--verbose", "be verbose", CommandOptionType.NoValue); + + app.Execute(input); + + Assert.True(optVerbose.HasValue()); + } + + [Theory] + [InlineData("-v:true")] + [InlineData("--verbose:true")] + public void ThrowsExceptionOnNoValueOptionHavingValue(string inputOption) + { + var app = new CommandLineApplication(); + app.Option("-v |--verbose", "be verbose", CommandOptionType.NoValue); + + var exception = Assert.Throws(() => app.Execute(inputOption)); + + Assert.Equal("Unexpected value 'true' for option 'verbose'", exception.Message); + } + + [Fact] + public void ThrowsExceptionOnEmptyCommandOrArgument() + { + var inputOption = String.Empty; + var app = new CommandLineApplication(); + + var exception = Assert.Throws(() => app.Execute(inputOption)); + + Assert.Equal($"Unrecognized command or argument '{inputOption}'", exception.Message); + } + + [Fact] + public void ThrowsExceptionOnInvalidOption() + { + var inputOption = "-"; + var app = new CommandLineApplication(); + + var exception = Assert.Throws(() => app.Execute(inputOption)); + + Assert.Equal($"Unrecognized option '{inputOption}'", exception.Message); + } + + [Fact] + public void TreatUnmatchedOptionsAsArguments() + { + CommandArgument first = null; + CommandArgument second = null; + + CommandOption firstOption = null; + CommandOption secondOption = null; + + var firstUnmatchedOption = "-firstUnmatchedOption"; + var firstActualOption = "-firstActualOption"; + var seconUnmatchedOption = "--secondUnmatchedOption"; + var secondActualOption = "--secondActualOption"; + + var app = new CommandLineApplication(treatUnmatchedOptionsAsArguments: true); + + app.Command("test", c => + { + firstOption = c.Option("-firstActualOption", "first option", CommandOptionType.NoValue); + secondOption = c.Option("--secondActualOption", "second option", CommandOptionType.NoValue); + + first = c.Argument("first", "First argument"); + second = c.Argument("second", "Second argument"); + c.OnExecute(() => 0); + }); + + app.Execute("test", firstUnmatchedOption, firstActualOption, seconUnmatchedOption, secondActualOption); + + Assert.Equal(firstUnmatchedOption, first.Value); + Assert.Equal(seconUnmatchedOption, second.Value); + + Assert.Equal(firstActualOption, firstOption.Template); + Assert.Equal(secondActualOption, secondOption.Template); + } + + [Fact] + public void ThrowExceptionWhenUnmatchedOptionAndTreatUnmatchedOptionsAsArgumentsIsFalse() + { + CommandArgument first = null; + + var firstOption = "-firstUnmatchedOption"; + + var app = new CommandLineApplication(treatUnmatchedOptionsAsArguments: false); + app.Command("test", c => + { + first = c.Argument("first", "First argument"); + c.OnExecute(() => 0); + }); + + var exception = Assert.Throws(() => app.Execute("test", firstOption)); + + Assert.Equal($"Unrecognized option '{firstOption}'", exception.Message); + } + } +} diff --git a/src/Shared/test/Shared.Tests/DotNetMuxerTests.cs b/src/Shared/test/Shared.Tests/DotNetMuxerTests.cs new file mode 100644 index 0000000000..8840d87bb8 --- /dev/null +++ b/src/Shared/test/Shared.Tests/DotNetMuxerTests.cs @@ -0,0 +1,24 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +#if NETCOREAPP +using System.IO; +using System.Runtime.InteropServices; +using Xunit; + +namespace Microsoft.Extensions.CommandLineUtils +{ + public class DotNetMuxerTests + { + [Fact] + public void FindsTheMuxer() + { + var muxerPath = DotNetMuxer.MuxerPath; + Assert.NotNull(muxerPath); + Assert.True(File.Exists(muxerPath), "The file did not exist"); + Assert.True(Path.IsPathRooted(muxerPath), "The path should be rooted"); + Assert.Equal("dotnet", Path.GetFileNameWithoutExtension(muxerPath), ignoreCase: true); + } + } +} +#endif diff --git a/src/Shared/test/Shared.Tests/HashCodeCombinerTest.cs b/src/Shared/test/Shared.Tests/HashCodeCombinerTest.cs new file mode 100644 index 0000000000..fab3e30979 --- /dev/null +++ b/src/Shared/test/Shared.Tests/HashCodeCombinerTest.cs @@ -0,0 +1,39 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Xunit; + +namespace Microsoft.Extensions.Internal +{ + public class HashCodeCombinerTest + { + [Fact] + public void GivenTheSameInputs_ItProducesTheSameOutput() + { + var hashCode1 = new HashCodeCombiner(); + var hashCode2 = new HashCodeCombiner(); + + hashCode1.Add(42); + hashCode1.Add("foo"); + hashCode2.Add(42); + hashCode2.Add("foo"); + + Assert.Equal(hashCode1.CombinedHash, hashCode2.CombinedHash); + } + + [Fact] + public void HashCode_Is_OrderSensitive() + { + var hashCode1 = HashCodeCombiner.Start(); + var hashCode2 = HashCodeCombiner.Start(); + + hashCode1.Add(42); + hashCode1.Add("foo"); + + hashCode2.Add("foo"); + hashCode2.Add(42); + + Assert.NotEqual(hashCode1.CombinedHash, hashCode2.CombinedHash); + } + } +} diff --git a/src/Shared/test/Shared.Tests/HostFactoryResolverTests.cs b/src/Shared/test/Shared.Tests/HostFactoryResolverTests.cs new file mode 100644 index 0000000000..a26fb7b133 --- /dev/null +++ b/src/Shared/test/Shared.Tests/HostFactoryResolverTests.cs @@ -0,0 +1,116 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using MockHostTypes; +using System; +using Xunit; + +namespace Microsoft.Extensions.Hosting.Tests +{ + public class HostFactoryResolverTests + { + [Fact] + public void BuildWebHostPattern_CanFindWebHost() + { + var factory = HostFactoryResolver.ResolveWebHostFactory(typeof(BuildWebHostPatternTestSite.Program).Assembly); + + Assert.NotNull(factory); + Assert.IsAssignableFrom(factory(Array.Empty())); + } + + [Fact] + public void BuildWebHostPattern_CanFindServiceProvider() + { + var factory = HostFactoryResolver.ResolveServiceProviderFactory(typeof(BuildWebHostPatternTestSite.Program).Assembly); + + Assert.NotNull(factory); + Assert.IsAssignableFrom(factory(Array.Empty())); + } + + [Fact] + public void BuildWebHostPattern__Invalid_CantFindWebHost() + { + var factory = HostFactoryResolver.ResolveWebHostFactory(typeof(BuildWebHostInvalidSignature.Program).Assembly); + + Assert.Null(factory); + } + + [Fact] + public void BuildWebHostPattern__Invalid_CantFindServiceProvider() + { + var factory = HostFactoryResolver.ResolveServiceProviderFactory(typeof(BuildWebHostInvalidSignature.Program).Assembly); + + Assert.Null(factory); + } + + [Fact] + public void CreateWebHostBuilderPattern_CanFindWebHostBuilder() + { + var factory = HostFactoryResolver.ResolveWebHostBuilderFactory(typeof(CreateWebHostBuilderPatternTestSite.Program).Assembly); + + Assert.NotNull(factory); + Assert.IsAssignableFrom(factory(Array.Empty())); + } + + [Fact] + public void CreateWebHostBuilderPattern_CanFindServiceProvider() + { + var factory = HostFactoryResolver.ResolveServiceProviderFactory(typeof(CreateWebHostBuilderPatternTestSite.Program).Assembly); + + Assert.NotNull(factory); + Assert.IsAssignableFrom(factory(Array.Empty())); + } + + [Fact] + public void CreateWebHostBuilderPattern__Invalid_CantFindWebHostBuilder() + { + var factory = HostFactoryResolver.ResolveWebHostBuilderFactory(typeof(CreateWebHostBuilderInvalidSignature.Program).Assembly); + + Assert.Null(factory); + } + + [Fact] + public void CreateWebHostBuilderPattern__InvalidReturnType_CanFindServiceProvider() + { + var factory = HostFactoryResolver.ResolveServiceProviderFactory(typeof(CreateWebHostBuilderInvalidSignature.Program).Assembly); + + Assert.NotNull(factory); + Assert.Null(factory(Array.Empty())); + + } + + [Fact] + public void CreateHostBuilderPattern_CanFindHostBuilder() + { + var factory = HostFactoryResolver.ResolveHostBuilderFactory(typeof(CreateHostBuilderPatternTestSite.Program).Assembly); + + Assert.NotNull(factory); + Assert.IsAssignableFrom(factory(Array.Empty())); + } + + [Fact] + public void CreateHostBuilderPattern_CanFindServiceProvider() + { + var factory = HostFactoryResolver.ResolveServiceProviderFactory(typeof(CreateHostBuilderPatternTestSite.Program).Assembly); + + Assert.NotNull(factory); + Assert.IsAssignableFrom(factory(Array.Empty())); + } + + [Fact] + public void CreateHostBuilderPattern__Invalid_CantFindHostBuilder() + { + var factory = HostFactoryResolver.ResolveHostBuilderFactory(typeof(CreateHostBuilderInvalidSignature.Program).Assembly); + + Assert.Null(factory); + } + + [Fact] + public void CreateHostBuilderPattern__Invalid_CantFindServiceProvider() + { + var factory = HostFactoryResolver.ResolveServiceProviderFactory(typeof(CreateHostBuilderInvalidSignature.Program).Assembly); + + Assert.Null(factory); + } + } +} diff --git a/src/Shared/test/Shared.Tests/Microsoft.AspNetCore.Shared.Tests.csproj b/src/Shared/test/Shared.Tests/Microsoft.AspNetCore.Shared.Tests.csproj index 83fe4babb7..bc6eb225c1 100644 --- a/src/Shared/test/Shared.Tests/Microsoft.AspNetCore.Shared.Tests.csproj +++ b/src/Shared/test/Shared.Tests/Microsoft.AspNetCore.Shared.Tests.csproj @@ -4,32 +4,58 @@ $(DefaultNetCoreTargetFramework) portable true + CS0649;CS0436 - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + System.Net.Http.SR + + - + \ No newline at end of file diff --git a/src/Shared/test/Shared.Tests/NonCapturingTimerTest.cs b/src/Shared/test/Shared.Tests/NonCapturingTimerTest.cs new file mode 100644 index 0000000000..ef21ce5f3b --- /dev/null +++ b/src/Shared/test/Shared.Tests/NonCapturingTimerTest.cs @@ -0,0 +1,40 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace Microsoft.Extensions.Internal +{ + public class NonCapturingTimerTest + { + [Fact] + public async Task NonCapturingTimer_DoesntCaptureExecutionContext() + { + // Arrange + var message = new AsyncLocal(); + message.Value = "Hey, this is a value stored in the execuion context"; + + var tcs = new TaskCompletionSource(); + + // Act + var timer = NonCapturingTimer.Create((_) => + { + // Observe the value based on the current execution context + tcs.SetResult(message.Value); + }, state: null, dueTime: TimeSpan.FromMilliseconds(1), Timeout.InfiniteTimeSpan); + + // Assert + var messageFromTimer = await tcs.Task; + timer.Dispose(); + + // ExecutionContext didn't flow to timer callback + Assert.Null(messageFromTimer); + + // ExecutionContext was restored + Assert.NotNull(await Task.Run(() => message.Value)); + } + } +} diff --git a/src/Shared/test/Shared.Tests/PropertyActivatorTest.cs b/src/Shared/test/Shared.Tests/PropertyActivatorTest.cs index a5cb1605b3..8775d692ca 100644 --- a/src/Shared/test/Shared.Tests/PropertyActivatorTest.cs +++ b/src/Shared/test/Shared.Tests/PropertyActivatorTest.cs @@ -153,7 +153,7 @@ namespace Microsoft.Extensions.Internal } [TestActivate] - public static int StaticActivatablProperty { get; set; } + public static int StaticActivatableProperty { get; set; } } private class TestClassWithPropertyVisiblity diff --git a/src/Shared/test/Shared.Tests/SingleThreadedSynchronizationContext.cs b/src/Shared/test/Shared.Tests/SingleThreadedSynchronizationContext.cs new file mode 100644 index 0000000000..77312e0a05 --- /dev/null +++ b/src/Shared/test/Shared.Tests/SingleThreadedSynchronizationContext.cs @@ -0,0 +1,45 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Concurrent; +using System.Threading; + +namespace Microsoft.Extensions.Internal +{ + internal class SingleThreadedSynchronizationContext : SynchronizationContext + { + private readonly BlockingCollection<(SendOrPostCallback Callback, object State)> _queue = new BlockingCollection<(SendOrPostCallback Callback, object State)>(); + + public override void Send(SendOrPostCallback d, object state) // Sync operations + { + throw new NotSupportedException($"{nameof(SingleThreadedSynchronizationContext)} does not support synchronous operations."); + } + + public override void Post(SendOrPostCallback d, object state) // Async operations + { + _queue.Add((d, state)); + } + + public static void Run(Action action) + { + var previous = Current; + var context = new SingleThreadedSynchronizationContext(); + SetSynchronizationContext(context); + try + { + action(); + + while (context._queue.TryTake(out var item)) + { + item.Callback(item.State); + } + } + finally + { + context._queue.CompleteAdding(); + SetSynchronizationContext(previous); + } + } + } +} diff --git a/src/Shared/test/Shared.Tests/StackTraceHelperTest.cs b/src/Shared/test/Shared.Tests/StackTraceHelperTest.cs index 657a310b6e..b6a2d0c2bf 100644 --- a/src/Shared/test/Shared.Tests/StackTraceHelperTest.cs +++ b/src/Shared/test/Shared.Tests/StackTraceHelperTest.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; @@ -33,7 +33,7 @@ namespace Microsoft.Extensions.Internal } // Act - var stackFrames = StackTraceHelper.GetFrames(exception); + var stackFrames = StackTraceHelper.GetFrames(exception, out _); // Assert Assert.Collection(stackFrames, @@ -55,7 +55,7 @@ namespace Microsoft.Extensions.Internal var exception = Record.Exception(() => GenericMethod(null)); // Act - var stackFrames = StackTraceHelper.GetFrames(exception); + var stackFrames = StackTraceHelper.GetFrames(exception, out _); // Assert var methods = stackFrames.Select(frame => frame.MethodDisplayInfo.ToString()).ToArray(); @@ -69,7 +69,7 @@ namespace Microsoft.Extensions.Internal var exception = Record.Exception(() => MethodWithOutParameter(out var value)); // Act - var stackFrames = StackTraceHelper.GetFrames(exception); + var stackFrames = StackTraceHelper.GetFrames(exception, out _); // Assert var methods = stackFrames.Select(frame => frame.MethodDisplayInfo.ToString()).ToArray(); @@ -83,7 +83,7 @@ namespace Microsoft.Extensions.Internal var exception = Record.Exception(() => MethodWithGenericOutParameter("Test", out int value)); // Act - var stackFrames = StackTraceHelper.GetFrames(exception); + var stackFrames = StackTraceHelper.GetFrames(exception, out _); // Assert var methods = stackFrames.Select(frame => frame.MethodDisplayInfo.ToString()).ToArray(); @@ -98,7 +98,7 @@ namespace Microsoft.Extensions.Internal var exception = Record.Exception(() => MethodWithRefParameter(ref value)); // Act - var stackFrames = StackTraceHelper.GetFrames(exception); + var stackFrames = StackTraceHelper.GetFrames(exception, out _); // Assert var methods = stackFrames.Select(frame => frame.MethodDisplayInfo.ToString()).ToArray(); @@ -113,7 +113,7 @@ namespace Microsoft.Extensions.Internal var exception = Record.Exception(() => MethodWithGenericRefParameter(ref value)); // Act - var stackFrames = StackTraceHelper.GetFrames(exception); + var stackFrames = StackTraceHelper.GetFrames(exception, out _); // Assert var methods = stackFrames.Select(frame => frame.MethodDisplayInfo.ToString()).ToArray(); @@ -128,7 +128,7 @@ namespace Microsoft.Extensions.Internal var exception = Record.Exception(() => MethodWithNullableParameter(value)); // Act - var stackFrames = StackTraceHelper.GetFrames(exception); + var stackFrames = StackTraceHelper.GetFrames(exception, out _); // Assert var methods = stackFrames.Select(frame => frame.MethodDisplayInfo.ToString()).ToArray(); @@ -142,7 +142,7 @@ namespace Microsoft.Extensions.Internal var exception = Record.Exception(() => new GenericClass().Throw(0)); // Act - var stackFrames = StackTraceHelper.GetFrames(exception); + var stackFrames = StackTraceHelper.GetFrames(exception, out _); // Assert var methods = stackFrames.Select(frame => frame.MethodDisplayInfo.ToString()).ToArray(); @@ -175,7 +175,7 @@ namespace Microsoft.Extensions.Internal } // Act - var stackFrames = StackTraceHelper.GetFrames(exception); + var stackFrames = StackTraceHelper.GetFrames(exception, out _); var methodNames = stackFrames.Select(stackFrame => stackFrame.MethodDisplayInfo.ToString()).ToArray(); // Assert @@ -189,7 +189,7 @@ namespace Microsoft.Extensions.Internal var exception = Record.Exception(() => InvokeMethodOnTypeWithStackTraceHiddenAttribute()); // Act - var stackFrames = StackTraceHelper.GetFrames(exception); + var stackFrames = StackTraceHelper.GetFrames(exception, out _); // Assert var methods = stackFrames.Select(frame => frame.MethodDisplayInfo.ToString()).ToArray(); @@ -204,7 +204,7 @@ namespace Microsoft.Extensions.Internal var exception = Record.Exception(() => InvokeStaticMethodOnTypeWithStackTraceHiddenAttribute()); // Act - var stackFrames = StackTraceHelper.GetFrames(exception); + var stackFrames = StackTraceHelper.GetFrames(exception, out _); // Assert var methods = stackFrames.Select(frame => frame.MethodDisplayInfo.ToString()).ToArray(); @@ -219,7 +219,7 @@ namespace Microsoft.Extensions.Internal var exception = Record.Exception(() => new TypeWithMethodWithStackTraceHiddenAttribute().Throw()); // Act - var stackFrames = StackTraceHelper.GetFrames(exception); + var stackFrames = StackTraceHelper.GetFrames(exception, out _); // Assert var methods = stackFrames.Select(frame => frame.MethodDisplayInfo.ToString()).ToArray(); @@ -237,12 +237,14 @@ namespace Microsoft.Extensions.Internal var exception = Record.Exception(action); // Act - var frames = StackTraceHelper.GetFrames(exception).ToArray(); + var frames = StackTraceHelper.GetFrames(exception, out _).ToArray(); // Assert var frame = frames[0]; Assert.Null(frame.FilePath); - Assert.Equal($"lambda_method(Closure )", frame.MethodDisplayInfo.ToString()); + // lambda_method{RandomNumber}(Closure ) + Assert.StartsWith("lambda_method", frame.MethodDisplayInfo.ToString()); + Assert.EndsWith("(Closure )", frame.MethodDisplayInfo.ToString()); } [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] diff --git a/src/Shared/test/Shared.Tests/TypeNameHelperTest.cs b/src/Shared/test/Shared.Tests/TypeNameHelperTest.cs new file mode 100644 index 0000000000..bd29f647d1 --- /dev/null +++ b/src/Shared/test/Shared.Tests/TypeNameHelperTest.cs @@ -0,0 +1,304 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using Xunit; + +namespace Microsoft.Extensions.Internal +{ + public class TypeNameHelperTest + { + [Theory] + // Predefined Types + [InlineData(typeof(int), "int")] + [InlineData(typeof(List), "System.Collections.Generic.List")] + [InlineData(typeof(Dictionary), "System.Collections.Generic.Dictionary")] + [InlineData(typeof(Dictionary>), "System.Collections.Generic.Dictionary>")] + [InlineData(typeof(List>), "System.Collections.Generic.List>")] + // Classes inside NonGeneric class + [InlineData(typeof(A), + "Microsoft.Extensions.Internal.TypeNameHelperTest+A")] + [InlineData(typeof(B), + "Microsoft.Extensions.Internal.TypeNameHelperTest+B")] + [InlineData(typeof(C), + "Microsoft.Extensions.Internal.TypeNameHelperTest+C")] + [InlineData(typeof(B>), + "Microsoft.Extensions.Internal.TypeNameHelperTest+B>")] + [InlineData(typeof(C>), + "Microsoft.Extensions.Internal.TypeNameHelperTest+C>")] + // Classes inside Generic class + [InlineData(typeof(Outer.D), + "Microsoft.Extensions.Internal.TypeNameHelperTest+Outer+D")] + [InlineData(typeof(Outer.E), + "Microsoft.Extensions.Internal.TypeNameHelperTest+Outer+E")] + [InlineData(typeof(Outer.F), + "Microsoft.Extensions.Internal.TypeNameHelperTest+Outer+F")] + [InlineData(typeof(Level1.Level2.Level3), + "Microsoft.Extensions.Internal.TypeNameHelperTest+Level1+Level2+Level3")] + [InlineData(typeof(Outer.E.E>), + "Microsoft.Extensions.Internal.TypeNameHelperTest+Outer+E+E>")] + [InlineData(typeof(Outer.F.E>), + "Microsoft.Extensions.Internal.TypeNameHelperTest+Outer+F+E>")] + [InlineData(typeof(OuterGeneric.InnerNonGeneric.InnerGeneric.InnerGenericLeafNode), + "Microsoft.Extensions.Internal.TypeNameHelperTest+OuterGeneric+InnerNonGeneric+InnerGeneric+InnerGenericLeafNode")] + public void Can_pretty_print_CLR_full_name(Type type, string expected) + { + Assert.Equal(expected, TypeNameHelper.GetTypeDisplayName(type)); + } + + [Fact] + public void DoesNotPrintNamespace_ForGenericTypes_IfNullOrEmpty() + { + // Arrange + var type = typeof(ClassInGlobalNamespace); + + // Act & Assert + Assert.Equal("ClassInGlobalNamespace", TypeNameHelper.GetTypeDisplayName(type)); + } + + [Theory] + // Predefined Types + [InlineData(typeof(int), "int")] + [InlineData(typeof(List), "List")] + [InlineData(typeof(Dictionary), "Dictionary")] + [InlineData(typeof(Dictionary>), "Dictionary>")] + [InlineData(typeof(List>), "List>")] + // Classes inside NonGeneric class + [InlineData(typeof(A), "A")] + [InlineData(typeof(B), "B")] + [InlineData(typeof(C), "C")] + [InlineData(typeof(C>), "C>")] + [InlineData(typeof(B>), "B>")] + // Classes inside Generic class + [InlineData(typeof(Outer.D), "D")] + [InlineData(typeof(Outer.E), "E")] + [InlineData(typeof(Outer.F), "F")] + [InlineData(typeof(Outer.F.E>), "F>")] + [InlineData(typeof(Outer.E.E>), "E>")] + [InlineData(typeof(OuterGeneric.InnerNonGeneric.InnerGeneric.InnerGenericLeafNode), "InnerGenericLeafNode")] + public void Can_pretty_print_CLR_name(Type type, string expected) + { + Assert.Equal(expected, TypeNameHelper.GetTypeDisplayName(type, false)); + } + + [Theory] + [InlineData(typeof(void), "void")] + [InlineData(typeof(bool), "bool")] + [InlineData(typeof(byte), "byte")] + [InlineData(typeof(char), "char")] + [InlineData(typeof(decimal), "decimal")] + [InlineData(typeof(double), "double")] + [InlineData(typeof(float), "float")] + [InlineData(typeof(int), "int")] + [InlineData(typeof(long), "long")] + [InlineData(typeof(object), "object")] + [InlineData(typeof(sbyte), "sbyte")] + [InlineData(typeof(short), "short")] + [InlineData(typeof(string), "string")] + [InlineData(typeof(uint), "uint")] + [InlineData(typeof(ulong), "ulong")] + [InlineData(typeof(ushort), "ushort")] + public void Returns_common_name_for_built_in_types(Type type, string expected) + { + Assert.Equal(expected, TypeNameHelper.GetTypeDisplayName(type)); + } + + [Theory] + [InlineData(typeof(int[]), true, "int[]")] + [InlineData(typeof(string[][]), true, "string[][]")] + [InlineData(typeof(int[,]), true, "int[,]")] + [InlineData(typeof(bool[,,,]), true, "bool[,,,]")] + [InlineData(typeof(A[,][,,]), true, "Microsoft.Extensions.Internal.TypeNameHelperTest+A[,][,,]")] + [InlineData(typeof(List), true, "System.Collections.Generic.List")] + [InlineData(typeof(List[,][,,]), false, "List[,][,,]")] + public void Can_pretty_print_array_name(Type type, bool fullName, string expected) + { + Assert.Equal(expected, TypeNameHelper.GetTypeDisplayName(type, fullName)); + } + + public static TheoryData GetOpenGenericsTestData() + { + var openDictionaryType = typeof(Dictionary<,>); + var genArgsDictionary = openDictionaryType.GetGenericArguments(); + genArgsDictionary[0] = typeof(B<>); + var closedDictionaryType = openDictionaryType.MakeGenericType(genArgsDictionary); + + var openLevelType = typeof(Level1<>.Level2<>.Level3<>); + var genArgsLevel = openLevelType.GetGenericArguments(); + genArgsLevel[1] = typeof(string); + var closedLevelType = openLevelType.MakeGenericType(genArgsLevel); + + var openInnerType = typeof(OuterGeneric<>.InnerNonGeneric.InnerGeneric<,>.InnerGenericLeafNode<>); + var genArgsInnerType = openInnerType.GetGenericArguments(); + genArgsInnerType[3] = typeof(bool); + var closedInnerType = openInnerType.MakeGenericType(genArgsInnerType); + + return new TheoryData + { + { typeof(List<>), false, "List<>" }, + { typeof(Dictionary<,>), false , "Dictionary<,>" }, + { typeof(List<>), true , "System.Collections.Generic.List<>" }, + { typeof(Dictionary<,>), true , "System.Collections.Generic.Dictionary<,>" }, + { typeof(Level1<>.Level2<>.Level3<>), true, "Microsoft.Extensions.Internal.TypeNameHelperTest+Level1<>+Level2<>+Level3<>" }, + { + typeof(PartiallyClosedGeneric<>).BaseType, + true, + "Microsoft.Extensions.Internal.TypeNameHelperTest+C<, int>" + }, + { + typeof(OuterGeneric<>.InnerNonGeneric.InnerGeneric<,>.InnerGenericLeafNode<>), + true, + "Microsoft.Extensions.Internal.TypeNameHelperTest+OuterGeneric<>+InnerNonGeneric+InnerGeneric<,>+InnerGenericLeafNode<>" + }, + { + closedDictionaryType, + true, + "System.Collections.Generic.Dictionary,>" + }, + { + closedLevelType, + true, + "Microsoft.Extensions.Internal.TypeNameHelperTest+Level1<>+Level2+Level3<>" + }, + { + closedInnerType, + true, + "Microsoft.Extensions.Internal.TypeNameHelperTest+OuterGeneric<>+InnerNonGeneric+InnerGeneric<,>+InnerGenericLeafNode" + } + }; + } + + [Theory] + [MemberData(nameof(GetOpenGenericsTestData))] + public void Can_pretty_print_open_generics(Type type, bool fullName, string expected) + { + Assert.Equal(expected, TypeNameHelper.GetTypeDisplayName(type, fullName)); + } + + public static TheoryData GetTypeDisplayName_IncludesGenericParameterNamesWhenOptionIsSetData => + new TheoryData + { + { typeof(B<>),"Microsoft.Extensions.Internal.TypeNameHelperTest+B" }, + { typeof(C<,>),"Microsoft.Extensions.Internal.TypeNameHelperTest+C" }, + { typeof(PartiallyClosedGeneric<>).BaseType,"Microsoft.Extensions.Internal.TypeNameHelperTest+C" }, + { typeof(Level1<>.Level2<>),"Microsoft.Extensions.Internal.TypeNameHelperTest+Level1+Level2" }, + }; + + [Theory] + [MemberData(nameof(GetTypeDisplayName_IncludesGenericParameterNamesWhenOptionIsSetData))] + public void GetTypeDisplayName_IncludesGenericParameterNamesWhenOptionIsSet(Type type, string expected) + { + // Arrange & Act + var actual = TypeNameHelper.GetTypeDisplayName(type, fullName: true, includeGenericParameterNames: true); + + // Assert + Assert.Equal(expected, actual); + } + + public static TheoryData GetTypeDisplayName_WithoutFullName_IncludesGenericParameterNamesWhenOptionIsSetData => + new TheoryData + { + { typeof(B<>),"B" }, + { typeof(C<,>),"C" }, + { typeof(PartiallyClosedGeneric<>).BaseType,"C" }, + { typeof(Level1<>.Level2<>),"Level2" }, + }; + + [Theory] + [MemberData(nameof(GetTypeDisplayName_WithoutFullName_IncludesGenericParameterNamesWhenOptionIsSetData))] + public void GetTypeDisplayName_WithoutFullName_IncludesGenericParameterNamesWhenOptionIsSet(Type type, string expected) + { + // Arrange & Act + var actual = TypeNameHelper.GetTypeDisplayName(type, fullName: false, includeGenericParameterNames: true); + + // Assert + Assert.Equal(expected, actual); + } + + public static TheoryData FullTypeNameData + { + get + { + return new TheoryData + { + // Predefined Types + { typeof(int), "int" }, + { typeof(List), "System.Collections.Generic.List" }, + { typeof(Dictionary), "System.Collections.Generic.Dictionary" }, + { typeof(Dictionary>), "System.Collections.Generic.Dictionary" }, + { typeof(List>), "System.Collections.Generic.List" }, + + // Classes inside NonGeneric class + { typeof(A), "Microsoft.Extensions.Internal.TypeNameHelperTest.A" }, + { typeof(B), "Microsoft.Extensions.Internal.TypeNameHelperTest.B" }, + { typeof(C), "Microsoft.Extensions.Internal.TypeNameHelperTest.C" }, + { typeof(C>), "Microsoft.Extensions.Internal.TypeNameHelperTest.C" }, + { typeof(B>), "Microsoft.Extensions.Internal.TypeNameHelperTest.B" }, + + // Classes inside Generic class + { typeof(Outer.D), "Microsoft.Extensions.Internal.TypeNameHelperTest.Outer.D" }, + { typeof(Outer.E), "Microsoft.Extensions.Internal.TypeNameHelperTest.Outer.E" }, + { typeof(Outer.F), "Microsoft.Extensions.Internal.TypeNameHelperTest.Outer.F" }, + { typeof(Outer.F.E>),"Microsoft.Extensions.Internal.TypeNameHelperTest.Outer.F" }, + { typeof(Outer.E.E>), "Microsoft.Extensions.Internal.TypeNameHelperTest.Outer.E" } + }; + } + } + + [Theory] + [MemberData(nameof(FullTypeNameData))] + public void Can_PrettyPrint_FullTypeName_WithoutGenericParametersAndNestedTypeDelimiter(Type type, string expectedTypeName) + { + // Arrange & Act + var displayName = TypeNameHelper.GetTypeDisplayName(type, fullName: true, includeGenericParameters: false, nestedTypeDelimiter: '.'); + + // Assert + Assert.Equal(expectedTypeName, displayName); + } + + private class A { } + + private class B { } + + private class C { } + + private class PartiallyClosedGeneric : C { } + + private class Outer + { + public class D { } + + public class E { } + + public class F { } + } + + private class OuterGeneric + { + public class InnerNonGeneric + { + public class InnerGeneric + { + public class InnerGenericLeafNode { } + + public class InnerLeafNode { } + } + } + } + + private class Level1 + { + public class Level2 + { + public class Level3 + { + } + } + } + } +} + +internal class ClassInGlobalNamespace +{ +} diff --git a/src/Shared/test/Shared.Tests/ValueStopwatchTest.cs b/src/Shared/test/Shared.Tests/ValueStopwatchTest.cs new file mode 100644 index 0000000000..fffc2c6656 --- /dev/null +++ b/src/Shared/test/Shared.Tests/ValueStopwatchTest.cs @@ -0,0 +1,39 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Threading.Tasks; +using Xunit; + +namespace Microsoft.Extensions.Internal.Test +{ + public class ValueStopwatchTest + { + [Fact] + public void IsActiveIsFalseForDefaultValueStopwatch() + { + Assert.False(default(ValueStopwatch).IsActive); + } + + [Fact] + public void IsActiveIsTrueWhenValueStopwatchStartedWithStartNew() + { + Assert.True(ValueStopwatch.StartNew().IsActive); + } + + [Fact] + public void GetElapsedTimeThrowsIfValueStopwatchIsDefaultValue() + { + var stopwatch = default(ValueStopwatch); + Assert.Throws(() => stopwatch.GetElapsedTime()); + } + + [Fact] + public async Task GetElapsedTimeReturnsTimeElapsedSinceStart() + { + var stopwatch = ValueStopwatch.StartNew(); + await Task.Delay(200); + Assert.True(stopwatch.GetElapsedTime().TotalMilliseconds > 0); + } + } +} diff --git a/src/Shared/test/Shared.Tests/runtime/Http2/DynamicTableTest.cs b/src/Shared/test/Shared.Tests/runtime/Http2/DynamicTableTest.cs new file mode 100644 index 0000000000..4abe4c5e68 --- /dev/null +++ b/src/Shared/test/Shared.Tests/runtime/Http2/DynamicTableTest.cs @@ -0,0 +1,255 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Net.Http.HPack; +using System.Reflection; +using System.Text; +using Xunit; + +namespace System.Net.Http.Unit.Tests.HPack +{ + public class DynamicTableTest + { + private readonly HeaderField _header1 = new HeaderField(Encoding.ASCII.GetBytes("header-1"), Encoding.ASCII.GetBytes("value1")); + private readonly HeaderField _header2 = new HeaderField(Encoding.ASCII.GetBytes("header-02"), Encoding.ASCII.GetBytes("value_2")); + + [Fact] + public void DynamicTable_IsInitiallyEmpty() + { + DynamicTable dynamicTable = new DynamicTable(4096); + Assert.Equal(0, dynamicTable.Count); + Assert.Equal(0, dynamicTable.Size); + Assert.Equal(4096, dynamicTable.MaxSize); + } + + [Fact] + public void DynamicTable_Count_IsNumberOfEntriesInDynamicTable() + { + DynamicTable dynamicTable = new DynamicTable(4096); + + dynamicTable.Insert(_header1.Name, _header1.Value); + Assert.Equal(1, dynamicTable.Count); + + dynamicTable.Insert(_header2.Name, _header2.Value); + Assert.Equal(2, dynamicTable.Count); + } + + [Fact] + public void DynamicTable_Size_IsCurrentDynamicTableSize() + { + DynamicTable dynamicTable = new DynamicTable(4096); + Assert.Equal(0, dynamicTable.Size); + + dynamicTable.Insert(_header1.Name, _header1.Value); + Assert.Equal(_header1.Length, dynamicTable.Size); + + dynamicTable.Insert(_header2.Name, _header2.Value); + Assert.Equal(_header1.Length + _header2.Length, dynamicTable.Size); + } + + [Fact] + public void DynamicTable_FirstEntry_IsMostRecentEntry() + { + DynamicTable dynamicTable = new DynamicTable(4096); + dynamicTable.Insert(_header1.Name, _header1.Value); + dynamicTable.Insert(_header2.Name, _header2.Value); + + VerifyTableEntries(dynamicTable, _header2, _header1); + } + + [Fact] + public void BoundsCheck_ThrowsIndexOutOfRangeException() + { + DynamicTable dynamicTable = new DynamicTable(4096); + Assert.Throws(() => dynamicTable[0]); + + dynamicTable.Insert(_header1.Name, _header1.Value); + Assert.Throws(() => dynamicTable[1]); + } + + [Fact] + public void DynamicTable_InsertEntryLargerThanMaxSize_NoOp() + { + DynamicTable dynamicTable = new DynamicTable(_header1.Length - 1); + dynamicTable.Insert(_header1.Name, _header1.Value); + + Assert.Equal(0, dynamicTable.Count); + Assert.Equal(0, dynamicTable.Size); + } + + [Fact] + public void DynamicTable_InsertEntryLargerThanRemainingSpace_NoOp() + { + DynamicTable dynamicTable = new DynamicTable(_header1.Length); + dynamicTable.Insert(_header1.Name, _header1.Value); + + VerifyTableEntries(dynamicTable, _header1); + + dynamicTable.Insert(_header2.Name, _header2.Value); + + Assert.Equal(0, dynamicTable.Count); + Assert.Equal(0, dynamicTable.Size); + } + + [Theory] + [InlineData(0)] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + public void DynamicTable_WrapsRingBuffer_Success(int targetInsertIndex) + { + FieldInfo insertIndexField = typeof(DynamicTable).GetField("_insertIndex", BindingFlags.NonPublic | BindingFlags.Instance); + DynamicTable table = new DynamicTable(maxSize: 256); + Stack insertedHeaders = new Stack(); + + // Insert into dynamic table until its insert index into its ring buffer loops back to 0. + do + { + InsertOne(); + } + while ((int)insertIndexField.GetValue(table) != 0); + + // Finally loop until the insert index reaches the target. + while ((int)insertIndexField.GetValue(table) != targetInsertIndex) + { + InsertOne(); + } + + void InsertOne() + { + byte[] data = Encoding.ASCII.GetBytes($"header-{insertedHeaders.Count}"); + + insertedHeaders.Push(data); + table.Insert(data, data); + } + + // Now check to see that we can retrieve the remaining headers. + // Some headers will have been evacuated from the table during this process, so we don't exhaust the entire insertedHeaders stack. + Assert.True(table.Count > 0); + Assert.True(table.Count < insertedHeaders.Count); + + for (int i = 0; i < table.Count; ++i) + { + HeaderField dynamicField = table[i]; + byte[] expectedData = insertedHeaders.Pop(); + + Assert.True(expectedData.AsSpan().SequenceEqual(dynamicField.Name)); + Assert.True(expectedData.AsSpan().SequenceEqual(dynamicField.Value)); + } + } + + [Theory] + [MemberData(nameof(CreateResizeData))] + public void DynamicTable_Resize_Success(int initialMaxSize, int finalMaxSize, int insertSize) + { + // This is purely to make it simple to perfectly reach our initial max size to test growing a full but non-wrapping buffer. + Debug.Assert((insertSize % 64) == 0, $"{nameof(insertSize)} must be a multiple of 64 ({nameof(HeaderField)}.{nameof(HeaderField.RfcOverhead)} * 2)"); + + DynamicTable dynamicTable = new DynamicTable(maxSize: initialMaxSize); + int insertedSize = 0; + + while (insertedSize != insertSize) + { + byte[] data = Encoding.ASCII.GetBytes($"header-{dynamicTable.Size}".PadRight(16, ' ')); + Debug.Assert(data.Length == 16); + + dynamicTable.Insert(data, data); + insertedSize += data.Length * 2 + HeaderField.RfcOverhead; + } + + List headers = new List(); + + for (int i = 0; i < dynamicTable.Count; ++i) + { + headers.Add(dynamicTable[i]); + } + + dynamicTable.Resize(finalMaxSize); + + int expectedCount = Math.Min(finalMaxSize / 64, headers.Count); + Assert.Equal(expectedCount, dynamicTable.Count); + + for (int i = 0; i < dynamicTable.Count; ++i) + { + Assert.True(headers[i].Name.AsSpan().SequenceEqual(dynamicTable[i].Name)); + Assert.True(headers[i].Value.AsSpan().SequenceEqual(dynamicTable[i].Value)); + } + } + + [Fact] + public void DynamicTable_ResizingEvictsOldestEntries() + { + DynamicTable dynamicTable = new DynamicTable(4096); + dynamicTable.Insert(_header1.Name, _header1.Value); + dynamicTable.Insert(_header2.Name, _header2.Value); + + VerifyTableEntries(dynamicTable, _header2, _header1); + + dynamicTable.Resize(_header2.Length); + + VerifyTableEntries(dynamicTable, _header2); + } + + [Fact] + public void DynamicTable_ResizingToZeroEvictsAllEntries() + { + DynamicTable dynamicTable = new DynamicTable(4096); + dynamicTable.Insert(_header1.Name, _header1.Value); + dynamicTable.Insert(_header2.Name, _header2.Value); + + dynamicTable.Resize(0); + + Assert.Equal(0, dynamicTable.Count); + Assert.Equal(0, dynamicTable.Size); + } + + [Fact] + public void DynamicTable_CanBeResizedToLargerMaxSize() + { + DynamicTable dynamicTable = new DynamicTable(_header1.Length); + dynamicTable.Insert(_header1.Name, _header1.Value); + dynamicTable.Insert(_header2.Name, _header2.Value); + + // _header2 is larger than _header1, so an attempt at inserting it + // would first clear the table then return without actually inserting it, + // given it is larger than the current max size. + Assert.Equal(0, dynamicTable.Count); + Assert.Equal(0, dynamicTable.Size); + + dynamicTable.Resize(dynamicTable.MaxSize + _header2.Length); + dynamicTable.Insert(_header2.Name, _header2.Value); + + VerifyTableEntries(dynamicTable, _header2); + } + + public static IEnumerable CreateResizeData() + { + int[] values = new[] { 128, 256, 384, 512 }; + return from initialMaxSize in values + from finalMaxSize in values + from insertSize in values + select new object[] { initialMaxSize, finalMaxSize, insertSize }; + } + + private void VerifyTableEntries(DynamicTable dynamicTable, params HeaderField[] entries) + { + Assert.Equal(entries.Length, dynamicTable.Count); + Assert.Equal(entries.Sum(e => e.Length), dynamicTable.Size); + + for (int i = 0; i < entries.Length; i++) + { + HeaderField headerField = dynamicTable[i]; + + Assert.NotSame(entries[i].Name, headerField.Name); + Assert.Equal(entries[i].Name, headerField.Name); + + Assert.NotSame(entries[i].Value, headerField.Value); + Assert.Equal(entries[i].Value, headerField.Value); + } + } + } +} diff --git a/src/Servers/Kestrel/Core/test/HPackDecoderTests.cs b/src/Shared/test/Shared.Tests/runtime/Http2/HPackDecoderTest.cs similarity index 78% rename from src/Servers/Kestrel/Core/test/HPackDecoderTests.cs rename to src/Shared/test/Shared.Tests/runtime/Http2/HPackDecoderTest.cs index d9318e01e9..2afdb29901 100644 --- a/src/Servers/Kestrel/Core/test/HPackDecoderTests.cs +++ b/src/Shared/test/Shared.Tests/runtime/Http2/HPackDecoderTest.cs @@ -1,23 +1,23 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. -using System; using System.Buffers; -using System.Collections.Generic; using System.Linq; +using System.Collections.Generic; using System.Text; -using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; -using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack; -using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; -using Microsoft.Net.Http.Headers; +using System.Net.Http.HPack; using Xunit; +#if KESTREL +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; +#endif -namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests +namespace System.Net.Http.Unit.Tests.HPack { public class HPackDecoderTests : IHttpHeadersHandler { private const int DynamicTableInitialMaxSize = 4096; - private const int MaxRequestHeaderFieldSize = 8192; + private const int MaxHeaderFieldSize = 8192; // Indexed Header Field Representation - Static Table - Index 2 (:method: GET) private static readonly byte[] _indexedHeaderStatic = new byte[] { 0x82 }; @@ -61,7 +61,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests // v a l u e * // 11101110 00111010 00101101 00101111 - private static readonly byte[] _headerValueHuffmanBytes = new byte [] { 0xee, 0x3a, 0x2d, 0x2f }; + private static readonly byte[] _headerValueHuffmanBytes = new byte[] { 0xee, 0x3a, 0x2d, 0x2f }; private static readonly byte[] _headerName = new byte[] { (byte)_headerNameBytes.Length } .Concat(_headerNameBytes) @@ -95,21 +95,36 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests public HPackDecoderTests() { _dynamicTable = new DynamicTable(DynamicTableInitialMaxSize); - _decoder = new HPackDecoder(DynamicTableInitialMaxSize, MaxRequestHeaderFieldSize, _dynamicTable); + _decoder = new HPackDecoder(DynamicTableInitialMaxSize, MaxHeaderFieldSize, _dynamicTable); } - void IHttpHeadersHandler.OnHeader(Span name, Span value) + void IHttpHeadersHandler.OnHeader(ReadOnlySpan name, ReadOnlySpan value) { - _decodedHeaders[name.GetAsciiStringNonNullCharacters()] = value.GetAsciiStringNonNullCharacters(); + string headerName = Encoding.ASCII.GetString(name); + string headerValue = Encoding.ASCII.GetString(value); + + _decodedHeaders[headerName] = headerValue; } - void IHttpHeadersHandler.OnHeadersComplete() { } + void IHttpHeadersHandler.OnStaticIndexedHeader(int index) + { + // Not yet implemented for HPACK. + throw new NotImplementedException(); + } + + void IHttpHeadersHandler.OnStaticIndexedHeader(int index, ReadOnlySpan value) + { + // Not yet implemented for HPACK. + throw new NotImplementedException(); + } + + void IHttpHeadersHandler.OnHeadersComplete(bool endStream) { } [Fact] public void DecodesIndexedHeaderField_StaticTable() { - _decoder.Decode(new ReadOnlySequence(_indexedHeaderStatic), endHeaders: true, handler: this); - Assert.Equal("GET", _decodedHeaders[HeaderNames.Method]); + _decoder.Decode(_indexedHeaderStatic, endHeaders: true, handler: this); + Assert.Equal("GET", _decodedHeaders[":method"]); } [Fact] @@ -119,23 +134,23 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests _dynamicTable.Insert(_headerNameBytes, _headerValueBytes); // Index it - _decoder.Decode(new ReadOnlySequence(_indexedHeaderDynamic), endHeaders: true, handler: this); + _decoder.Decode(_indexedHeaderDynamic, endHeaders: true, handler: this); Assert.Equal(_headerValueString, _decodedHeaders[_headerNameString]); } [Fact] public void DecodesIndexedHeaderField_OutOfRange_Error() { - var exception = Assert.Throws(() => - _decoder.Decode(new ReadOnlySequence(_indexedHeaderDynamic), endHeaders: true, handler: this)); - Assert.Equal(CoreStrings.FormatHPackErrorIndexOutOfRange(62), exception.Message); + HPackDecodingException exception = Assert.Throws(() => + _decoder.Decode(_indexedHeaderDynamic, endHeaders: true, handler: this)); + Assert.Equal(SR.Format(SR.net_http_hpack_invalid_index, 62), exception.Message); Assert.Empty(_decodedHeaders); } [Fact] public void DecodesLiteralHeaderFieldWithIncrementalIndexing_NewName() { - var encoded = _literalHeaderFieldWithIndexingNewName + byte[] encoded = _literalHeaderFieldWithIndexingNewName .Concat(_headerName) .Concat(_headerValue) .ToArray(); @@ -146,7 +161,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [Fact] public void DecodesLiteralHeaderFieldWithIncrementalIndexing_NewName_HuffmanEncodedName() { - var encoded = _literalHeaderFieldWithIndexingNewName + byte[] encoded = _literalHeaderFieldWithIndexingNewName .Concat(_headerNameHuffman) .Concat(_headerValue) .ToArray(); @@ -157,7 +172,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [Fact] public void DecodesLiteralHeaderFieldWithIncrementalIndexing_NewName_HuffmanEncodedValue() { - var encoded = _literalHeaderFieldWithIndexingNewName + byte[] encoded = _literalHeaderFieldWithIndexingNewName .Concat(_headerName) .Concat(_headerValueHuffman) .ToArray(); @@ -168,7 +183,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [Fact] public void DecodesLiteralHeaderFieldWithIncrementalIndexing_NewName_HuffmanEncodedNameAndValue() { - var encoded = _literalHeaderFieldWithIndexingNewName + byte[] encoded = _literalHeaderFieldWithIndexingNewName .Concat(_headerNameHuffman) .Concat(_headerValueHuffman) .ToArray(); @@ -179,7 +194,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [Fact] public void DecodesLiteralHeaderFieldWithIncrementalIndexing_IndexedName() { - var encoded = _literalHeaderFieldWithIndexingIndexedName + byte[] encoded = _literalHeaderFieldWithIndexingIndexedName .Concat(_headerValue) .ToArray(); @@ -189,7 +204,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [Fact] public void DecodesLiteralHeaderFieldWithIncrementalIndexing_IndexedName_HuffmanEncodedValue() { - var encoded = _literalHeaderFieldWithIndexingIndexedName + byte[] encoded = _literalHeaderFieldWithIndexingIndexedName .Concat(_headerValueHuffman) .ToArray(); @@ -203,15 +218,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests // 11 1110 (Indexed Name - Index 62 encoded with 6-bit prefix - see http://httpwg.org/specs/rfc7541.html#integer.representation) // Index 62 is the first entry in the dynamic table. If there's nothing there, the decoder should throw. - var exception = Assert.Throws(() => _decoder.Decode(new ReadOnlySequence(new byte[] { 0x7e }), endHeaders: true, handler: this)); - Assert.Equal(CoreStrings.FormatHPackErrorIndexOutOfRange(62), exception.Message); + HPackDecodingException exception = Assert.Throws(() => _decoder.Decode(new byte[] { 0x7e }, endHeaders: true, handler: this)); + Assert.Equal(SR.Format(SR.net_http_hpack_invalid_index, 62), exception.Message); Assert.Empty(_decodedHeaders); } [Fact] public void DecodesLiteralHeaderFieldWithoutIndexing_NewName() { - var encoded = _literalHeaderFieldWithoutIndexingNewName + byte[] encoded = _literalHeaderFieldWithoutIndexingNewName .Concat(_headerName) .Concat(_headerValue) .ToArray(); @@ -222,7 +237,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [Fact] public void DecodesLiteralHeaderFieldWithoutIndexing_NewName_HuffmanEncodedName() { - var encoded = _literalHeaderFieldWithoutIndexingNewName + byte[] encoded = _literalHeaderFieldWithoutIndexingNewName .Concat(_headerNameHuffman) .Concat(_headerValue) .ToArray(); @@ -233,7 +248,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [Fact] public void DecodesLiteralHeaderFieldWithoutIndexing_NewName_HuffmanEncodedValue() { - var encoded = _literalHeaderFieldWithoutIndexingNewName + byte[] encoded = _literalHeaderFieldWithoutIndexingNewName .Concat(_headerName) .Concat(_headerValueHuffman) .ToArray(); @@ -244,7 +259,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [Fact] public void DecodesLiteralHeaderFieldWithoutIndexing_NewName_HuffmanEncodedNameAndValue() { - var encoded = _literalHeaderFieldWithoutIndexingNewName + byte[] encoded = _literalHeaderFieldWithoutIndexingNewName .Concat(_headerNameHuffman) .Concat(_headerValueHuffman) .ToArray(); @@ -255,7 +270,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [Fact] public void DecodesLiteralHeaderFieldWithoutIndexing_IndexedName() { - var encoded = _literalHeaderFieldWithoutIndexingIndexedName + byte[] encoded = _literalHeaderFieldWithoutIndexingIndexedName .Concat(_headerValue) .ToArray(); @@ -265,7 +280,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [Fact] public void DecodesLiteralHeaderFieldWithoutIndexing_IndexedName_HuffmanEncodedValue() { - var encoded = _literalHeaderFieldWithoutIndexingIndexedName + byte[] encoded = _literalHeaderFieldWithoutIndexingIndexedName .Concat(_headerValueHuffman) .ToArray(); @@ -279,15 +294,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests // 1111 0010 1111 (Indexed Name - Index 62 encoded with 4-bit prefix - see http://httpwg.org/specs/rfc7541.html#integer.representation) // Index 62 is the first entry in the dynamic table. If there's nothing there, the decoder should throw. - var exception = Assert.Throws(() => _decoder.Decode(new ReadOnlySequence(new byte[] { 0x0f, 0x2f }), endHeaders: true, handler: this)); - Assert.Equal(CoreStrings.FormatHPackErrorIndexOutOfRange(62), exception.Message); + HPackDecodingException exception = Assert.Throws(() => _decoder.Decode(new byte[] { 0x0f, 0x2f }, endHeaders: true, handler: this)); + Assert.Equal(SR.Format(SR.net_http_hpack_invalid_index, 62), exception.Message); Assert.Empty(_decodedHeaders); } [Fact] public void DecodesLiteralHeaderFieldNeverIndexed_NewName() { - var encoded = _literalHeaderFieldNeverIndexedNewName + byte[] encoded = _literalHeaderFieldNeverIndexedNewName .Concat(_headerName) .Concat(_headerValue) .ToArray(); @@ -298,7 +313,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [Fact] public void DecodesLiteralHeaderFieldNeverIndexed_NewName_HuffmanEncodedName() { - var encoded = _literalHeaderFieldNeverIndexedNewName + byte[] encoded = _literalHeaderFieldNeverIndexedNewName .Concat(_headerNameHuffman) .Concat(_headerValue) .ToArray(); @@ -309,7 +324,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [Fact] public void DecodesLiteralHeaderFieldNeverIndexed_NewName_HuffmanEncodedValue() { - var encoded = _literalHeaderFieldNeverIndexedNewName + byte[] encoded = _literalHeaderFieldNeverIndexedNewName .Concat(_headerName) .Concat(_headerValueHuffman) .ToArray(); @@ -320,7 +335,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [Fact] public void DecodesLiteralHeaderFieldNeverIndexed_NewName_HuffmanEncodedNameAndValue() { - var encoded = _literalHeaderFieldNeverIndexedNewName + byte[] encoded = _literalHeaderFieldNeverIndexedNewName .Concat(_headerNameHuffman) .Concat(_headerValueHuffman) .ToArray(); @@ -334,7 +349,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests // 0001 (Literal Header Field Never Indexed Representation) // 1111 0010 1011 (Indexed Name - Index 58 encoded with 4-bit prefix - see http://httpwg.org/specs/rfc7541.html#integer.representation) // Concatenated with value bytes - var encoded = _literalHeaderFieldNeverIndexedIndexedName + byte[] encoded = _literalHeaderFieldNeverIndexedIndexedName .Concat(_headerValue) .ToArray(); @@ -347,7 +362,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests // 0001 (Literal Header Field Never Indexed Representation) // 1111 0010 1011 (Indexed Name - Index 58 encoded with 4-bit prefix - see http://httpwg.org/specs/rfc7541.html#integer.representation) // Concatenated with Huffman encoded value bytes - var encoded = _literalHeaderFieldNeverIndexedIndexedName + byte[] encoded = _literalHeaderFieldNeverIndexedIndexedName .Concat(_headerValueHuffman) .ToArray(); @@ -361,8 +376,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests // 1111 0010 1111 (Indexed Name - Index 62 encoded with 4-bit prefix - see http://httpwg.org/specs/rfc7541.html#integer.representation) // Index 62 is the first entry in the dynamic table. If there's nothing there, the decoder should throw. - var exception = Assert.Throws(() => _decoder.Decode(new ReadOnlySequence(new byte[] { 0x1f, 0x2f }), endHeaders: true, handler: this)); - Assert.Equal(CoreStrings.FormatHPackErrorIndexOutOfRange(62), exception.Message); + HPackDecodingException exception = Assert.Throws(() => _decoder.Decode(new byte[] { 0x1f, 0x2f }, endHeaders: true, handler: this)); + Assert.Equal(SR.Format(SR.net_http_hpack_invalid_index, 62), exception.Message); Assert.Empty(_decodedHeaders); } @@ -374,7 +389,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests Assert.Equal(DynamicTableInitialMaxSize, _dynamicTable.MaxSize); - _decoder.Decode(new ReadOnlySequence(new byte[] { 0x3e }), endHeaders: true, handler: this); + _decoder.Decode(new byte[] { 0x3e }, endHeaders: true, handler: this); Assert.Equal(30, _dynamicTable.MaxSize); Assert.Empty(_decodedHeaders); @@ -388,9 +403,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests Assert.Equal(DynamicTableInitialMaxSize, _dynamicTable.MaxSize); - var data = new ReadOnlySequence(_indexedHeaderStatic.Concat(new byte[] { 0x3e }).ToArray()); - var exception = Assert.Throws(() => _decoder.Decode(data, endHeaders: true, handler: this)); - Assert.Equal(CoreStrings.HPackErrorDynamicTableSizeUpdateNotAtBeginningOfHeaderBlock, exception.Message); + byte[] data = _indexedHeaderStatic.Concat(new byte[] { 0x3e }).ToArray(); + HPackDecodingException exception = Assert.Throws(() => _decoder.Decode(data, endHeaders: true, handler: this)); + Assert.Equal(SR.net_http_hpack_late_dynamic_table_size_update, exception.Message); } [Fact] @@ -398,14 +413,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests { Assert.Equal(DynamicTableInitialMaxSize, _dynamicTable.MaxSize); - _decoder.Decode(new ReadOnlySequence(_indexedHeaderStatic), endHeaders: false, handler: this); - Assert.Equal("GET", _decodedHeaders[HeaderNames.Method]); + _decoder.Decode(_indexedHeaderStatic, endHeaders: false, handler: this); + Assert.Equal("GET", _decodedHeaders[":method"]); // 001 (Dynamic Table Size Update) // 11110 (30 encoded with 5-bit prefix - see http://httpwg.org/specs/rfc7541.html#integer.representation) - var data = new ReadOnlySequence(new byte[] { 0x3e }); - var exception = Assert.Throws(() => _decoder.Decode(data, endHeaders: true, handler: this)); - Assert.Equal(CoreStrings.HPackErrorDynamicTableSizeUpdateNotAtBeginningOfHeaderBlock, exception.Message); + byte[] data = new byte[] { 0x3e }; + HPackDecodingException exception = Assert.Throws(() => _decoder.Decode(data, endHeaders: true, handler: this)); + Assert.Equal(SR.net_http_hpack_late_dynamic_table_size_update, exception.Message); } [Fact] @@ -413,12 +428,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests { Assert.Equal(DynamicTableInitialMaxSize, _dynamicTable.MaxSize); - _decoder.Decode(new ReadOnlySequence(_indexedHeaderStatic), endHeaders: true, handler: this); - Assert.Equal("GET", _decodedHeaders[HeaderNames.Method]); + _decoder.Decode(_indexedHeaderStatic, endHeaders: true, handler: this); + Assert.Equal("GET", _decodedHeaders[":method"]); // 001 (Dynamic Table Size Update) // 11110 (30 encoded with 5-bit prefix - see http://httpwg.org/specs/rfc7541.html#integer.representation) - _decoder.Decode(new ReadOnlySequence(new byte[] { 0x3e }), endHeaders: true, handler: this); + _decoder.Decode(new byte[] { 0x3e }, endHeaders: true, handler: this); Assert.Equal(30, _dynamicTable.MaxSize); } @@ -431,38 +446,38 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests Assert.Equal(DynamicTableInitialMaxSize, _dynamicTable.MaxSize); - var exception = Assert.Throws(() => - _decoder.Decode(new ReadOnlySequence(new byte[] { 0x3f, 0xe2, 0x1f }), endHeaders: true, handler: this)); - Assert.Equal(CoreStrings.FormatHPackErrorDynamicTableSizeUpdateTooLarge(4097, DynamicTableInitialMaxSize), exception.Message); + HPackDecodingException exception = Assert.Throws(() => + _decoder.Decode(new byte[] { 0x3f, 0xe2, 0x1f }, endHeaders: true, handler: this)); + Assert.Equal(SR.Format(SR.net_http_hpack_large_table_size_update, 4097, DynamicTableInitialMaxSize), exception.Message); Assert.Empty(_decodedHeaders); } [Fact] public void DecodesStringLength_GreaterThanLimit_Error() { - var encoded = _literalHeaderFieldWithoutIndexingNewName + byte[] encoded = _literalHeaderFieldWithoutIndexingNewName .Concat(new byte[] { 0xff, 0x82, 0x3f }) // 8193 encoded with 7-bit prefix .ToArray(); - var exception = Assert.Throws(() => _decoder.Decode(new ReadOnlySequence(encoded), endHeaders: true, handler: this)); - Assert.Equal(CoreStrings.FormatHPackStringLengthTooLarge(MaxRequestHeaderFieldSize + 1, MaxRequestHeaderFieldSize), exception.Message); + HPackDecodingException exception = Assert.Throws(() => _decoder.Decode(encoded, endHeaders: true, handler: this)); + Assert.Equal(SR.Format(SR.net_http_headers_exceeded_length, MaxHeaderFieldSize), exception.Message); Assert.Empty(_decodedHeaders); } [Fact] public void DecodesStringLength_LimitConfigurable() { - var decoder = new HPackDecoder(DynamicTableInitialMaxSize, MaxRequestHeaderFieldSize + 1); - var string8193 = new string('a', MaxRequestHeaderFieldSize + 1); + HPackDecoder decoder = new HPackDecoder(DynamicTableInitialMaxSize, MaxHeaderFieldSize + 1); + string string8193 = new string('a', MaxHeaderFieldSize + 1); - var encoded = _literalHeaderFieldWithoutIndexingNewName + byte[] encoded = _literalHeaderFieldWithoutIndexingNewName .Concat(new byte[] { 0x7f, 0x82, 0x3f }) // 8193 encoded with 7-bit prefix, no Huffman encoding .Concat(Encoding.ASCII.GetBytes(string8193)) .Concat(new byte[] { 0x7f, 0x82, 0x3f }) // 8193 encoded with 7-bit prefix, no Huffman encoding .Concat(Encoding.ASCII.GetBytes(string8193)) .ToArray(); - decoder.Decode(new ReadOnlySequence(encoded), endHeaders: true, handler: this); + decoder.Decode(encoded, endHeaders: true, handler: this); Assert.Equal(string8193, _decodedHeaders[string8193]); } @@ -552,8 +567,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [MemberData(nameof(_incompleteHeaderBlockData))] public void DecodesIncompleteHeaderBlock_Error(byte[] encoded) { - var exception = Assert.Throws(() => _decoder.Decode(new ReadOnlySequence(encoded), endHeaders: true, handler: this)); - Assert.Equal(CoreStrings.HPackErrorIncompleteHeaderBlock, exception.Message); + HPackDecodingException exception = Assert.Throws(() => _decoder.Decode(encoded, endHeaders: true, handler: this)); + Assert.Equal(SR.net_http_hpack_incomplete_header_block, exception.Message); Assert.Empty(_decodedHeaders); } @@ -586,8 +601,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [MemberData(nameof(_huffmanDecodingErrorData))] public void WrapsHuffmanDecodingExceptionInHPackDecodingException(byte[] encoded) { - var exception = Assert.Throws(() => _decoder.Decode(new ReadOnlySequence(encoded), endHeaders: true, handler: this)); - Assert.Equal(CoreStrings.HPackHuffmanError, exception.Message); + HPackDecodingException exception = Assert.Throws(() => _decoder.Decode(encoded, endHeaders: true, handler: this)); + Assert.Equal(SR.net_http_hpack_huffman_decode_failed, exception.Message); Assert.IsType(exception.InnerException); Assert.Empty(_decodedHeaders); } @@ -607,7 +622,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests Assert.Equal(0, _dynamicTable.Count); Assert.Equal(0, _dynamicTable.Size); - _decoder.Decode(new ReadOnlySequence(encoded), endHeaders: true, handler: this); + _decoder.Decode(encoded, endHeaders: true, handler: this); Assert.Equal(expectedHeaderValue, _decodedHeaders[expectedHeaderName]); diff --git a/src/Shared/test/Shared.Tests/runtime/Http2/HPackIntegerTest.cs b/src/Shared/test/Shared.Tests/runtime/Http2/HPackIntegerTest.cs new file mode 100644 index 0000000000..f7362aee4e --- /dev/null +++ b/src/Shared/test/Shared.Tests/runtime/Http2/HPackIntegerTest.cs @@ -0,0 +1,129 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Net.Http.HPack; +using Xunit; + +namespace System.Net.Http.Unit.Tests.HPack +{ + public class HPackIntegerTest + { + [Theory] + [MemberData(nameof(IntegerCodecExactSamples))] + public void HPack_IntegerEncode(int value, int bits, byte[] expectedResult) + { + Span actualResult = new byte[64]; + bool success = IntegerEncoder.Encode(value, bits, actualResult, out int bytesWritten); + + Assert.True(success); + Assert.Equal(expectedResult.Length, bytesWritten); + Assert.True(actualResult.Slice(0, bytesWritten).SequenceEqual(expectedResult)); + } + + [Theory] + [MemberData(nameof(IntegerCodecExactSamples))] + public void HPack_IntegerEncode_ShortBuffer(int value, int bits, byte[] expectedResult) + { + Span actualResult = new byte[expectedResult.Length - 1]; + bool success = IntegerEncoder.Encode(value, bits, actualResult, out int bytesWritten); + + Assert.False(success); + } + + [Theory] + [MemberData(nameof(IntegerCodecExactSamples))] + public void HPack_IntegerDecode(int expectedResult, int bits, byte[] encoded) + { + IntegerDecoder integerDecoder = new IntegerDecoder(); + + bool finished = integerDecoder.BeginTryDecode(encoded[0], bits, out int actualResult); + + int i = 1; + for (; !finished && i < encoded.Length; ++i) + { + finished = integerDecoder.TryDecode(encoded[i], out actualResult); + } + + Assert.True(finished); + Assert.Equal(encoded.Length, i); + + Assert.Equal(expectedResult, actualResult); + } + + [Fact] + public void IntegerEncoderDecoderRoundtrips() + { + IntegerDecoder decoder = new IntegerDecoder(); + + for (int i = 0; i < 2048; ++i) + { + for (int prefixLength = 1; prefixLength <= 8; ++prefixLength) + { + Span integerBytes = stackalloc byte[5]; + Assert.True(IntegerEncoder.Encode(i, prefixLength, integerBytes, out int length)); + + bool decodeResult = decoder.BeginTryDecode(integerBytes[0], prefixLength, out int intResult); + + for (int j = 1; j < length; j++) + { + Assert.False(decodeResult); + decodeResult = decoder.TryDecode(integerBytes[j], out intResult); + } + + Assert.True(decodeResult); + Assert.Equal(i, intResult); + } + } + } + + public static IEnumerable IntegerCodecExactSamples() + { + yield return new object[] { 10, 5, new byte[] { 0x0A } }; + yield return new object[] { 1337, 5, new byte[] { 0x1F, 0x9A, 0x0A } }; + yield return new object[] { 42, 8, new byte[] { 0x2A } }; + yield return new object[] { 7, 3, new byte[] { 0x7, 0x0 } }; + yield return new object[] { int.MaxValue, 1, new byte[] { 0x01, 0xfe, 0xff, 0xff, 0xff, 0x07 } }; + yield return new object[] { int.MaxValue, 8, new byte[] { 0xff, 0x80, 0xfe, 0xff, 0xff, 0x07 } }; + } + + [Theory] + [MemberData(nameof(IntegerData_OverMax))] + public void IntegerDecode_Throws_IfMaxExceeded(int prefixLength, byte[] octets) + { + var decoder = new IntegerDecoder(); + var result = decoder.BeginTryDecode(octets[0], prefixLength, out var intResult); + + for (var j = 1; j < octets.Length - 1; j++) + { + Assert.False(decoder.TryDecode(octets[j], out intResult)); + } + + Assert.Throws(() => decoder.TryDecode(octets[octets.Length - 1], out intResult)); + } + + public static TheoryData IntegerData_OverMax + { + get + { + var data = new TheoryData(); + + data.Add(1, new byte[] { 0x01, 0xff, 0xff, 0xff, 0xff, 0x07 }); // Int32.MaxValue + 1 + data.Add(1, new byte[] { 0x01, 0xff, 0xff, 0xff, 0xff, 0x08 }); // MSB exceeds maximum + data.Add(1, new byte[] { 0x01, 0xff, 0xff, 0xff, 0xff, 0x80 }); // Undefined since continuation bit set + + data.Add(7, new byte[] { 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0x08 }); // 1 bit too large + data.Add(7, new byte[] { 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F }); + data.Add(7, new byte[] { 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0x80 }); // A continuation byte (0x80) where the byte after it would be too large. + data.Add(7, new byte[] { 0x7F, 0xFF, 0x00 }); // Encoded with 1 byte too many. + + data.Add(8, new byte[] { 0xff, 0x81, 0xfe, 0xff, 0xff, 0x07 }); // Int32.MaxValue + 1 + data.Add(8, new byte[] { 0xff, 0x81, 0xfe, 0xff, 0xff, 0x08 }); // MSB exceeds maximum + data.Add(8, new byte[] { 0xff, 0x81, 0xfe, 0xff, 0xff, 0x80 }); // Undefined since continuation bit set + + return data; + } + } + } +} diff --git a/src/Servers/Kestrel/Core/test/HuffmanTests.cs b/src/Shared/test/Shared.Tests/runtime/Http2/HuffmanDecodingTests.cs similarity index 72% rename from src/Servers/Kestrel/Core/test/HuffmanTests.cs rename to src/Shared/test/Shared.Tests/runtime/Http2/HuffmanDecodingTests.cs index dfae6afe6e..4383de415b 100644 --- a/src/Servers/Kestrel/Core/test/HuffmanTests.cs +++ b/src/Shared/test/Shared.Tests/runtime/Http2/HuffmanDecodingTests.cs @@ -1,15 +1,193 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. -using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http.HPack; using System.Text; -using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack; using Xunit; -namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests +namespace System.Net.Http.Unit.Tests.HPack { - public class HuffmanTests + public class HuffmanDecodingTests { + // Encoded values are 30 bits at most, so are stored in the table in a uint. + // Convert to ulong here and put the encoded value in the most significant bits. + // This makes the encoding logic below simpler. + private static (ulong code, int bitLength) GetEncodedValue(byte b) + { + (uint code, int bitLength) = Huffman.Encode(b); + return (((ulong)code) << 32, bitLength); + } + + private static int Encode(byte[] source, byte[] destination, bool injectEOS) + { + ulong currentBits = 0; // We can have 7 bits of rollover plus 30 bits for the next encoded value, so use a ulong + int currentBitCount = 0; + int dstOffset = 0; + + for (int i = 0; i < source.Length; i++) + { + (ulong code, int bitLength) = GetEncodedValue(source[i]); + + // inject EOS if instructed to + if (injectEOS) + { + code |= (ulong)0b11111111_11111111_11111111_11111100 << (32 - bitLength); + bitLength += 30; + injectEOS = false; + } + + currentBits |= code >> currentBitCount; + currentBitCount += bitLength; + + while (currentBitCount >= 8) + { + destination[dstOffset++] = (byte)(currentBits >> 56); + currentBits = currentBits << 8; + currentBitCount -= 8; + } + } + + // Fill any trailing bits with ones, per RFC + if (currentBitCount > 0) + { + currentBits |= 0xFFFFFFFFFFFFFFFF >> currentBitCount; + destination[dstOffset++] = (byte)(currentBits >> 56); + } + + return dstOffset; + } + + [Fact] + public void HuffmanDecoding_ValidEncoding_Succeeds() + { + foreach (byte[] input in TestData()) + { + // Worst case encoding is 30 bits per input byte, so make the encoded buffer 4 times as big + byte[] encoded = new byte[input.Length * 4]; + int encodedByteCount = Encode(input, encoded, false); + + // Worst case decoding is an output byte per 5 input bits, so make the decoded buffer 2 times as big + byte[] decoded = new byte[encoded.Length * 2]; + + int decodedByteCount = Huffman.Decode(new ReadOnlySpan(encoded, 0, encodedByteCount), ref decoded); + + Assert.Equal(input.Length, decodedByteCount); + Assert.Equal(input, decoded.Take(decodedByteCount)); + } + } + + [Fact] + public void HuffmanDecoding_InvalidEncoding_Throws() + { + foreach (byte[] encoded in InvalidEncodingData()) + { + // Worst case decoding is an output byte per 5 input bits, so make the decoded buffer 2 times as big + byte[] decoded = new byte[encoded.Length * 2]; + + Assert.Throws(() => Huffman.Decode(encoded, ref decoded)); + } + } + + // This input sequence will encode to 17 bits, thus offsetting the next character to encode + // by exactly one bit. We use this below to generate a prefix that encodes all of the possible starting + // bit offsets for a character, from 0 to 7. + private static readonly byte[] s_offsetByOneBit = new byte[] { (byte)'c', (byte)'l', (byte)'r' }; + + public static IEnumerable TestData() + { + // Single byte data + for (int i = 0; i < 256; i++) + { + yield return new byte[] { (byte)i }; + } + + // Ensure that decoding every possible value leaves the decoder in a correct state so that + // a subsequent value can be decoded (here, 'a') + for (int i = 0; i < 256; i++) + { + yield return new byte[] { (byte)i, (byte)'a' }; + } + + // Ensure that every possible bit starting position for every value is encoded properly + // s_offsetByOneBit encodes to exactly 17 bits, leaving 1 bit for the next byte + // So by repeating this sequence, we can generate any starting bit position we want. + byte[] currentPrefix = new byte[0]; + for (int prefixBits = 1; prefixBits <= 8; prefixBits++) + { + currentPrefix = currentPrefix.Concat(s_offsetByOneBit).ToArray(); + + // Make sure we're actually getting the correct number of prefix bits + int encodedBits = currentPrefix.Select(b => Huffman.Encode(b).bitLength).Sum(); + Assert.Equal(prefixBits % 8, encodedBits % 8); + + for (int i = 0; i < 256; i++) + { + yield return currentPrefix.Concat(new byte[] { (byte)i }.Concat(currentPrefix)).ToArray(); + } + } + + // Finally, one really big chunk of randomly generated data. + byte[] data = new byte[1024 * 1024]; + new Random(42).NextBytes(data); + yield return data; + } + + private static IEnumerable InvalidEncodingData() + { + // For encodings greater than 8 bits, truncate one or more bytes to generate an invalid encoding + byte[] source = new byte[1]; + byte[] destination = new byte[10]; + for (int i = 0; i < 256; i++) + { + source[0] = (byte)i; + int encodedByteCount = Encode(source, destination, false); + if (encodedByteCount > 1) + { + yield return destination.Take(encodedByteCount - 1).ToArray(); + if (encodedByteCount > 2) + { + yield return destination.Take(encodedByteCount - 2).ToArray(); + if (encodedByteCount > 3) + { + yield return destination.Take(encodedByteCount - 3).ToArray(); + } + } + } + } + + // Pad encodings with invalid trailing one bits. This is disallowed. + byte[] pad1 = new byte[] { 0xFF }; + byte[] pad2 = new byte[] { 0xFF, 0xFF, }; + byte[] pad3 = new byte[] { 0xFF, 0xFF, 0xFF }; + byte[] pad4 = new byte[] { 0xFF, 0xFF, 0xFF, 0xFF }; + + for (int i = 0; i < 256; i++) + { + source[0] = (byte)i; + int encodedByteCount = Encode(source, destination, false); + yield return destination.Take(encodedByteCount).Concat(pad1).ToArray(); + yield return destination.Take(encodedByteCount).Concat(pad2).ToArray(); + yield return destination.Take(encodedByteCount).Concat(pad3).ToArray(); + yield return destination.Take(encodedByteCount).Concat(pad4).ToArray(); + } + + // send single EOS + yield return new byte[] { 0b11111111, 0b11111111, 0b11111111, 0b11111100 }; + + // send combinations with EOS in the middle + source = new byte[2]; + destination = new byte[24]; + for (int i = 0; i < 256; i++) + { + source[0] = source[1] = (byte)i; + int encodedByteCount = Encode(source, destination, true); + yield return destination.Take(encodedByteCount).ToArray(); + } + } + public static readonly TheoryData _validData = new TheoryData { // Single 5-bit symbol @@ -67,8 +245,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [MemberData(nameof(_validData))] public void HuffmanDecodeArray(byte[] encoded, byte[] expected) { - var dst = new byte[expected.Length]; - Assert.Equal(expected.Length, Huffman.Decode(new ReadOnlySpan(encoded), dst)); + byte[] dst = new byte[expected.Length]; + Assert.Equal(expected.Length, Huffman.Decode(new ReadOnlySpan(encoded), ref dst)); Assert.Equal(expected, dst); } @@ -88,8 +266,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [MemberData(nameof(_longPaddingData))] public void ThrowsOnPaddingLongerThanSevenBits(byte[] encoded) { - var exception = Assert.Throws(() => Huffman.Decode(new ReadOnlySpan(encoded), new byte[encoded.Length * 2])); - Assert.Equal(CoreStrings.HPackHuffmanErrorIncomplete, exception.Message); + byte[] dst = new byte[encoded.Length * 2]; + Exception exception = Assert.Throws(() => Huffman.Decode(new ReadOnlySpan(encoded), ref dst)); + Assert.Equal(SR.net_http_hpack_huffman_decode_failed, exception.Message); } public static readonly TheoryData _eosData = new TheoryData @@ -104,17 +283,21 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [MemberData(nameof(_eosData))] public void ThrowsOnEOS(byte[] encoded) { - var exception = Assert.Throws(() => Huffman.Decode(new ReadOnlySpan(encoded), new byte[encoded.Length * 2])); - Assert.Equal(CoreStrings.HPackHuffmanErrorEOS, exception.Message); + byte[] dst = new byte[encoded.Length * 2]; + Exception exception = Assert.Throws(() => Huffman.Decode(new ReadOnlySpan(encoded), ref dst)); + Assert.Equal(SR.net_http_hpack_huffman_decode_failed, exception.Message); } [Fact] - public void ThrowsOnDestinationBufferTooSmall() + public void ResizesOnDestinationBufferTooSmall() { // h e l l o * - var encoded = new byte[] { 0b100111_00, 0b101_10100, 0b0_101000_0, 0b0111_1111 }; - var exception = Assert.Throws(() => Huffman.Decode(new ReadOnlySpan(encoded), new byte[encoded.Length])); - Assert.Equal(CoreStrings.HPackHuffmanErrorDestinationTooSmall, exception.Message); + byte[] encoded = new byte[] { 0b100111_00, 0b101_10100, 0b0_101000_0, 0b0111_1111 }; + byte[] originalDestination = new byte[encoded.Length]; + byte[] actualDestination = originalDestination; + int decodedCount = Huffman.Decode(new ReadOnlySpan(encoded), ref actualDestination); + Assert.Equal(5, decodedCount); + Assert.NotSame(originalDestination, actualDestination); } public static readonly TheoryData _incompleteSymbolData = new TheoryData @@ -145,28 +328,29 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [MemberData(nameof(_incompleteSymbolData))] public void ThrowsOnIncompleteSymbol(byte[] encoded) { - var exception = Assert.Throws(() => Huffman.Decode(new ReadOnlySpan(encoded), new byte[encoded.Length * 2])); - Assert.Equal(CoreStrings.HPackHuffmanErrorIncomplete, exception.Message); + byte[] dst = new byte[encoded.Length * 2]; + Exception exception = Assert.Throws(() => Huffman.Decode(new ReadOnlySpan(encoded), ref dst)); + Assert.Equal(SR.net_http_hpack_huffman_decode_failed, exception.Message); } [Fact] public void DecodeCharactersThatSpans5Octets() { - var expectedLength = 2; - var decodedBytes = new byte[expectedLength]; + int expectedLength = 2; + byte[] decodedBytes = new byte[expectedLength]; // B LF EOS - var encoded = new byte[] { 0b1011101_1, 0b11111111, 0b11111111, 0b11111111, 0b11100_111 }; - var decodedLength = Huffman.Decode(new ReadOnlySpan(encoded, 0, encoded.Length), decodedBytes); + byte[] encoded = new byte[] { 0b1011101_1, 0b11111111, 0b11111111, 0b11111111, 0b11100_111 }; + int decodedLength = Huffman.Decode(new ReadOnlySpan(encoded, 0, encoded.Length), ref decodedBytes); Assert.Equal(expectedLength, decodedLength); - Assert.Equal(new byte [] { (byte)'B', (byte)'\n' }, decodedBytes); + Assert.Equal(new byte[] { (byte)'B', (byte)'\n' }, decodedBytes); } [Theory] [MemberData(nameof(HuffmanData))] public void HuffmanEncode(int code, uint expectedEncoded, int expectedBitLength) { - var (encoded, bitLength) = Huffman.Encode(code); + (uint encoded, int bitLength) = Huffman.Encode(code); Assert.Equal(expectedEncoded, encoded); Assert.Equal(expectedBitLength, bitLength); } @@ -175,7 +359,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [MemberData(nameof(HuffmanData))] public void HuffmanDecode(int code, uint encoded, int bitLength) { - Assert.Equal(code, Huffman.DecodeValue(encoded, bitLength, out var decodedBits)); + Assert.Equal(code, Huffman.DecodeValue(encoded, bitLength, out int decodedBits)); Assert.Equal(bitLength, decodedBits); } @@ -183,14 +367,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [MemberData(nameof(HuffmanData))] public void HuffmanEncodeDecode( int code, -// Suppresses the warning about an unused theory parameter because -// this test shares data with other methods + // Suppresses the warning about an unused theory parameter because + // this test shares data with other methods #pragma warning disable xUnit1026 uint encoded, #pragma warning restore xUnit1026 int bitLength) { - Assert.Equal(code, Huffman.DecodeValue(Huffman.Encode(code).encoded, bitLength, out var decodedBits)); + Assert.Equal(code, Huffman.DecodeValue(Huffman.Encode(code).encoded, bitLength, out int decodedBits)); Assert.Equal(bitLength, decodedBits); } @@ -198,7 +382,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests { get { - var data = new TheoryData(); + TheoryData data = new TheoryData(); data.Add(0, 0b11111111_11000000_00000000_00000000, 13); data.Add(1, 0b11111111_11111111_10110000_00000000, 23); diff --git a/src/Shared/test/Shared.Tests/runtime/ReadMe.SharedCode.md b/src/Shared/test/Shared.Tests/runtime/ReadMe.SharedCode.md new file mode 100644 index 0000000000..cd18bcb663 --- /dev/null +++ b/src/Shared/test/Shared.Tests/runtime/ReadMe.SharedCode.md @@ -0,0 +1,5 @@ +The code in this directory is shared between the runtime libraries and AspNetCore. This contains tests for HTTP/2 and HTTP/3 protocol infrastructure such as HPACK. Any changes to this dir need to be checked into both repositories. + +For additional details see: +- runtime/src/libraries/Common/src/System/Net/Http/aspnetcore/ReadMe.SharedCode.md +- AspNetCore/src/Shared/runtime/ReadMe.SharedCode.md diff --git a/src/Shared/test/testassets/BuildWebHostInvalidSignature/BuildWebHostInvalidSignature.csproj b/src/Shared/test/testassets/BuildWebHostInvalidSignature/BuildWebHostInvalidSignature.csproj new file mode 100644 index 0000000000..05ca293b6e --- /dev/null +++ b/src/Shared/test/testassets/BuildWebHostInvalidSignature/BuildWebHostInvalidSignature.csproj @@ -0,0 +1,12 @@ + + + + $(DefaultNetCoreTargetFramework);net472 + Exe + + + + + + + diff --git a/src/Shared/test/testassets/BuildWebHostInvalidSignature/Program.cs b/src/Shared/test/testassets/BuildWebHostInvalidSignature/Program.cs new file mode 100644 index 0000000000..ba9e3dab6a --- /dev/null +++ b/src/Shared/test/testassets/BuildWebHostInvalidSignature/Program.cs @@ -0,0 +1,17 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using MockHostTypes; + +namespace BuildWebHostInvalidSignature +{ + public class Program + { + static void Main(string[] args) + { + } + + // Missing string[] args + public static IWebHost BuildWebHost() => null; + } +} diff --git a/src/Shared/test/testassets/BuildWebHostPatternTestSite/BuildWebHostPatternTestSite.csproj b/src/Shared/test/testassets/BuildWebHostPatternTestSite/BuildWebHostPatternTestSite.csproj new file mode 100644 index 0000000000..05ca293b6e --- /dev/null +++ b/src/Shared/test/testassets/BuildWebHostPatternTestSite/BuildWebHostPatternTestSite.csproj @@ -0,0 +1,12 @@ + + + + $(DefaultNetCoreTargetFramework);net472 + Exe + + + + + + + diff --git a/src/Shared/test/testassets/BuildWebHostPatternTestSite/Program.cs b/src/Shared/test/testassets/BuildWebHostPatternTestSite/Program.cs new file mode 100644 index 0000000000..b1d0655e4d --- /dev/null +++ b/src/Shared/test/testassets/BuildWebHostPatternTestSite/Program.cs @@ -0,0 +1,16 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using MockHostTypes; + +namespace BuildWebHostPatternTestSite +{ + public class Program + { + static void Main(string[] args) + { + } + + public static IWebHost BuildWebHost(string[] args) => new WebHost(); + } +} diff --git a/src/Shared/test/testassets/CreateHostBuilderInvalidSignature/CreateHostBuilderInvalidSignature.csproj b/src/Shared/test/testassets/CreateHostBuilderInvalidSignature/CreateHostBuilderInvalidSignature.csproj new file mode 100644 index 0000000000..05ca293b6e --- /dev/null +++ b/src/Shared/test/testassets/CreateHostBuilderInvalidSignature/CreateHostBuilderInvalidSignature.csproj @@ -0,0 +1,12 @@ + + + + $(DefaultNetCoreTargetFramework);net472 + Exe + + + + + + + diff --git a/src/Shared/test/testassets/CreateHostBuilderInvalidSignature/Program.cs b/src/Shared/test/testassets/CreateHostBuilderInvalidSignature/Program.cs new file mode 100644 index 0000000000..8451301a20 --- /dev/null +++ b/src/Shared/test/testassets/CreateHostBuilderInvalidSignature/Program.cs @@ -0,0 +1,18 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using MockHostTypes; + +namespace CreateHostBuilderInvalidSignature +{ + public class Program + { + public static void Main(string[] args) + { + var webHost = CreateHostBuilder(null, args).Build(); + } + + // Extra parameter + private static IHostBuilder CreateHostBuilder(object extraParam, string[] args) => null; + } +} diff --git a/src/Shared/test/testassets/CreateHostBuilderPatternTestSite/CreateHostBuilderPatternTestSite.csproj b/src/Shared/test/testassets/CreateHostBuilderPatternTestSite/CreateHostBuilderPatternTestSite.csproj new file mode 100644 index 0000000000..05ca293b6e --- /dev/null +++ b/src/Shared/test/testassets/CreateHostBuilderPatternTestSite/CreateHostBuilderPatternTestSite.csproj @@ -0,0 +1,12 @@ + + + + $(DefaultNetCoreTargetFramework);net472 + Exe + + + + + + + diff --git a/src/Shared/test/testassets/CreateHostBuilderPatternTestSite/Program.cs b/src/Shared/test/testassets/CreateHostBuilderPatternTestSite/Program.cs new file mode 100644 index 0000000000..70edf16097 --- /dev/null +++ b/src/Shared/test/testassets/CreateHostBuilderPatternTestSite/Program.cs @@ -0,0 +1,19 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using MockHostTypes; + +namespace CreateHostBuilderPatternTestSite +{ + public class Program + { + public static void Main(string[] args) + { + var webHost = CreateHostBuilder(args).Build(); + } + + // Do not change the signature of this method. It's used for tests. + private static HostBuilder CreateHostBuilder(string[] args) => + new HostBuilder(); + } +} diff --git a/src/Shared/test/testassets/CreateWebHostBuilderInvalidSignature/CreateWebHostBuilderInvalidSignature.csproj b/src/Shared/test/testassets/CreateWebHostBuilderInvalidSignature/CreateWebHostBuilderInvalidSignature.csproj new file mode 100644 index 0000000000..05ca293b6e --- /dev/null +++ b/src/Shared/test/testassets/CreateWebHostBuilderInvalidSignature/CreateWebHostBuilderInvalidSignature.csproj @@ -0,0 +1,12 @@ + + + + $(DefaultNetCoreTargetFramework);net472 + Exe + + + + + + + diff --git a/src/Shared/test/testassets/CreateWebHostBuilderInvalidSignature/Program.cs b/src/Shared/test/testassets/CreateWebHostBuilderInvalidSignature/Program.cs new file mode 100644 index 0000000000..1533acbf57 --- /dev/null +++ b/src/Shared/test/testassets/CreateWebHostBuilderInvalidSignature/Program.cs @@ -0,0 +1,17 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using MockHostTypes; + +namespace CreateWebHostBuilderInvalidSignature +{ + public class Program + { + static void Main(string[] args) + { + } + + // Wrong return type + public static IWebHost CreateWebHostBuilder(string[] args) => new WebHost(); + } +} diff --git a/src/Shared/test/testassets/CreateWebHostBuilderPatternTestSite/CreateWebHostBuilderPatternTestSite.csproj b/src/Shared/test/testassets/CreateWebHostBuilderPatternTestSite/CreateWebHostBuilderPatternTestSite.csproj new file mode 100644 index 0000000000..05ca293b6e --- /dev/null +++ b/src/Shared/test/testassets/CreateWebHostBuilderPatternTestSite/CreateWebHostBuilderPatternTestSite.csproj @@ -0,0 +1,12 @@ + + + + $(DefaultNetCoreTargetFramework);net472 + Exe + + + + + + + diff --git a/src/Shared/test/testassets/CreateWebHostBuilderPatternTestSite/Program.cs b/src/Shared/test/testassets/CreateWebHostBuilderPatternTestSite/Program.cs new file mode 100644 index 0000000000..caab3cb224 --- /dev/null +++ b/src/Shared/test/testassets/CreateWebHostBuilderPatternTestSite/Program.cs @@ -0,0 +1,19 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using MockHostTypes; + +namespace CreateWebHostBuilderPatternTestSite +{ + public class Program + { + public static void Main(string[] args) + { + var webHost = CreateWebHostBuilder(args).Build(); + } + + // Do not change the signature of this method. It's used for tests. + private static IWebHostBuilder CreateWebHostBuilder(string[] args) => + new WebHostBuilder(); + } +} diff --git a/src/Shared/test/testassets/MockHostTypes/Host.cs b/src/Shared/test/testassets/MockHostTypes/Host.cs new file mode 100644 index 0000000000..412ab63ef3 --- /dev/null +++ b/src/Shared/test/testassets/MockHostTypes/Host.cs @@ -0,0 +1,12 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; + +namespace MockHostTypes +{ + public class Host : IHost + { + public IServiceProvider Services { get; } = new ServiceProvider(); + } +} diff --git a/src/Shared/test/testassets/MockHostTypes/HostBuilder.cs b/src/Shared/test/testassets/MockHostTypes/HostBuilder.cs new file mode 100644 index 0000000000..eb62e9a4b1 --- /dev/null +++ b/src/Shared/test/testassets/MockHostTypes/HostBuilder.cs @@ -0,0 +1,10 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace MockHostTypes +{ + public class HostBuilder : IHostBuilder + { + public IHost Build() => new Host(); + } +} diff --git a/src/Shared/test/testassets/MockHostTypes/IHost.cs b/src/Shared/test/testassets/MockHostTypes/IHost.cs new file mode 100644 index 0000000000..27c6dbaf71 --- /dev/null +++ b/src/Shared/test/testassets/MockHostTypes/IHost.cs @@ -0,0 +1,12 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; + +namespace MockHostTypes +{ + public interface IHost + { + IServiceProvider Services { get; } + } +} diff --git a/src/Shared/test/testassets/MockHostTypes/IHostBuilder.cs b/src/Shared/test/testassets/MockHostTypes/IHostBuilder.cs new file mode 100644 index 0000000000..2053b52106 --- /dev/null +++ b/src/Shared/test/testassets/MockHostTypes/IHostBuilder.cs @@ -0,0 +1,10 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace MockHostTypes +{ + public interface IHostBuilder + { + IHost Build(); + } +} diff --git a/src/Shared/test/testassets/MockHostTypes/IWebHost.cs b/src/Shared/test/testassets/MockHostTypes/IWebHost.cs new file mode 100644 index 0000000000..f93bba440c --- /dev/null +++ b/src/Shared/test/testassets/MockHostTypes/IWebHost.cs @@ -0,0 +1,12 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; + +namespace MockHostTypes +{ + public interface IWebHost + { + IServiceProvider Services { get; } + } +} diff --git a/src/Shared/test/testassets/MockHostTypes/IWebHostBuilder.cs b/src/Shared/test/testassets/MockHostTypes/IWebHostBuilder.cs new file mode 100644 index 0000000000..1159ae103e --- /dev/null +++ b/src/Shared/test/testassets/MockHostTypes/IWebHostBuilder.cs @@ -0,0 +1,10 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace MockHostTypes +{ + public interface IWebHostBuilder + { + IWebHost Build(); + } +} diff --git a/src/Shared/test/testassets/MockHostTypes/MockHostTypes.csproj b/src/Shared/test/testassets/MockHostTypes/MockHostTypes.csproj new file mode 100644 index 0000000000..57b6e1ae58 --- /dev/null +++ b/src/Shared/test/testassets/MockHostTypes/MockHostTypes.csproj @@ -0,0 +1,7 @@ + + + + $(DefaultNetCoreTargetFramework);net472 + + + diff --git a/src/Shared/test/testassets/MockHostTypes/ServiceProvider.cs b/src/Shared/test/testassets/MockHostTypes/ServiceProvider.cs new file mode 100644 index 0000000000..7b550c9d32 --- /dev/null +++ b/src/Shared/test/testassets/MockHostTypes/ServiceProvider.cs @@ -0,0 +1,12 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; + +namespace MockHostTypes +{ + public class ServiceProvider : IServiceProvider + { + public object GetService(Type serviceType) => null; + } +} diff --git a/src/Shared/test/testassets/MockHostTypes/WebHost.cs b/src/Shared/test/testassets/MockHostTypes/WebHost.cs new file mode 100644 index 0000000000..77d3d58ca4 --- /dev/null +++ b/src/Shared/test/testassets/MockHostTypes/WebHost.cs @@ -0,0 +1,12 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; + +namespace MockHostTypes +{ + public class WebHost : IWebHost + { + public IServiceProvider Services { get; } = new ServiceProvider(); + } +} diff --git a/src/Shared/test/testassets/MockHostTypes/WebHostBuilder.cs b/src/Shared/test/testassets/MockHostTypes/WebHostBuilder.cs new file mode 100644 index 0000000000..216fb28d60 --- /dev/null +++ b/src/Shared/test/testassets/MockHostTypes/WebHostBuilder.cs @@ -0,0 +1,10 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace MockHostTypes +{ + public class WebHostBuilder : IWebHostBuilder + { + public IWebHost Build() => new WebHost(); + } +} diff --git a/src/SignalR/README.md b/src/SignalR/README.md index 084b5fbf71..ac3d241c04 100644 --- a/src/SignalR/README.md +++ b/src/SignalR/README.md @@ -1,13 +1,13 @@ ASP.NET Core SignalR ==================== -ASP.NET Core SignalR is a new library for ASP.NET Core developers that makes it incredibly simple to add real-time web functionality to your applications. What is "real-time web" functionality? It's the ability to have your server-side code push content to the connected clients as it happens, in real-time. +ASP.NET Core SignalR is a library for ASP.NET Core developers that makes it incredibly simple to add real-time web functionality to your applications. What is "real-time web" functionality? It's the ability to have your server-side code push content to the connected clients as it happens, in real-time. You can watch an introductory presentation here - [ASP.NET Core SignalR: Build 2018](https://www.youtube.com/watch?v=Lws0zOaseIM) ## Documentation -Documentation for ASP.NET Core SignalR can be found in the [Real-time Apps](https://docs.microsoft.com/en-us/aspnet/core/signalr/introduction?view=aspnetcore-2.1) section of the ASP.NET Core Documentation site. +Documentation for ASP.NET Core SignalR can be found in the [Real-time Apps](https://docs.microsoft.com/aspnet/core/signalr/introduction) section of the ASP.NET Core Documentation site. ## TypeScript Version @@ -20,33 +20,33 @@ When in doubt, check the version of TypeScript referenced by our [package.json]( You can install the latest released JavaScript client from npm with the following command: ```bash -npm install @aspnet/signalr +npm install @microsoft/signalr ``` -The `@aspnet/signalr` package (and it's dependencies) require NPM 5.6.0 or higher. +The `@microsoft/signalr` package (and it's dependencies) require NPM 5.6.0 or higher. -**NOTE:** Previous previews of the SignalR client library for JavaScript were named `@aspnet/signalr-client`. This has been deprecated as of Preview 1. +**NOTE:** Previous versions of the SignalR client were named `@aspnet/signalr` or `@aspnet/signalr-client`. **IMPORTANT:** When using preview builds, you should always ensure you are using the same version of both the JavaScript client and the Server. The version numbers should align as they are produced in the same build process. -The CI build publishes the latest dev version of the JavaScript client to our dev npm registry as @aspnet/signalr. You can install the module as follows: +The CI build publishes the latest dev version of the JavaScript client to our dev npm registry as @microsoft/signalr. You can install the module as follows: - Create an .npmrc file with the following line: - `@aspnet:registry=https://dotnet.myget.org/f/aspnetcore-dev/npm/` + `@microsoft:registry=https://dotnet.myget.org/f/aspnetcore-dev/npm/` - Run: - `npm install @aspnet/signalr` + `npm install @microsoft/signalr` Alternatively, if you don't want to create the .npmrc file run the following commands: ``` -npm install @aspnet/signalr --registry https://dotnet.myget.org/f/aspnetcore-dev/npm/ +npm install @microsoft/signalr --registry https://dotnet.myget.org/f/aspnetcore-dev/npm/ ``` We also have a MsgPack protocol library which is installed via: ```bash -npm install @aspnet/signalr-protocol-msgpack +npm install @microsoft/signalr-protocol-msgpack ``` ## Deploying -Once you've installed the NPM modules, they will be located in the `node_modules/@aspnet/signalr` and `node_modules/@aspnet/signalr-protocol-msgpack` folders. If you are building a NodeJS application or using an ECMAScript module loader/bundler (such as [webpack](https://webpack.js.org)), you can load them directly. If you are building a browser application without using a module bundler, you can find UMD-compatible bundles in the `dist/browser` folder; minified versions are provided as well. Simply copy these to your project as appropriate and use a build task to keep them up-to-date. +Once you've installed the NPM modules, they will be located in the `node_modules/@microsoft/signalr` and `node_modules/@microsoft/signalr-protocol-msgpack` folders. If you are building a NodeJS application or using an ECMAScript module loader/bundler (such as [webpack](https://webpack.js.org)), you can load them directly. If you are building a browser application without using a module bundler, you can find UMD-compatible bundles in the `dist/browser` folder; minified versions are provided as well. Simply copy these to your project as appropriate and use a build task to keep them up-to-date. diff --git a/src/SignalR/clients/csharp/Client.Core/src/HubConnection.cs b/src/SignalR/clients/csharp/Client.Core/src/HubConnection.cs index e8d35d753f..8738545ffb 100644 --- a/src/SignalR/clients/csharp/Client.Core/src/HubConnection.cs +++ b/src/SignalR/clients/csharp/Client.Core/src/HubConnection.cs @@ -22,7 +22,6 @@ using Microsoft.AspNetCore.SignalR.Internal; using Microsoft.AspNetCore.SignalR.Protocol; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; -using Microsoft.Extensions.Options; namespace Microsoft.AspNetCore.SignalR.Client { @@ -34,7 +33,7 @@ namespace Microsoft.AspNetCore.SignalR.Client /// Before hub methods can be invoked the connection must be started using . /// Clean up a connection using or . /// - public partial class HubConnection + public partial class HubConnection : IAsyncDisposable { public static readonly TimeSpan DefaultServerTimeout = TimeSpan.FromSeconds(30); // Server ping rate is 15 sec, this is 2 times that. public static readonly TimeSpan DefaultHandshakeTimeout = TimeSpan.FromSeconds(15); @@ -292,8 +291,8 @@ namespace Microsoft.AspNetCore.SignalR.Client ///

      /// Disposes the . /// - /// A that represents the asynchronous dispose. - public async Task DisposeAsync() + /// A that represents the asynchronous dispose. + public async ValueTask DisposeAsync() { if (!_disposed) { @@ -501,11 +500,24 @@ namespace Microsoft.AspNetCore.SignalR.Client { connectionState.Stopping = true; } + else + { + // Reset StopCts if there isn't an active connection so that the next StartAsync wont immediately fail due to the token being canceled + _state.StopCts = new CancellationTokenSource(); + } if (disposing) { - (_serviceProvider as IDisposable)?.Dispose(); + // Must set this before calling DisposeAsync because the service provider has a reference to the HubConnection and will try to dispose it again _disposed = true; + if (_serviceProvider is IAsyncDisposable asyncDispose) + { + await asyncDispose.DisposeAsync(); + } + else + { + (_serviceProvider as IDisposable)?.Dispose(); + } } } finally @@ -1628,6 +1640,7 @@ namespace Microsoft.AspNetCore.SignalR.Client { private readonly HubConnection _hubConnection; private readonly ILogger _logger; + private readonly bool _hasInherentKeepAlive; private readonly object _lock = new object(); private readonly Dictionary _pendingCalls = new Dictionary(StringComparer.Ordinal); @@ -1639,7 +1652,6 @@ namespace Microsoft.AspNetCore.SignalR.Client private long _nextActivationServerTimeout; private long _nextActivationSendPing; - private bool _hasInherentKeepAlive; public ConnectionContext Connection { get; } public Task ReceiveTask { get; set; } @@ -1768,7 +1780,10 @@ namespace Microsoft.AspNetCore.SignalR.Client // Old clients never ping, and shouldn't be timed out, so ping to tell the server that we should be timed out if we stop. // The TimerLoop is started from the ReceiveLoop with the connection lock still acquired. _hubConnection._state.AssertInConnectionLock(); - await _hubConnection.SendHubMessage(this, PingMessage.Instance); + if (!_hasInherentKeepAlive) + { + await _hubConnection.SendHubMessage(this, PingMessage.Instance); + } // initialize the timers timer.Start(); @@ -1798,7 +1813,12 @@ namespace Microsoft.AspNetCore.SignalR.Client // Internal for testing internal async Task RunTimerActions() { - if (!_hasInherentKeepAlive && DateTime.UtcNow.Ticks > Volatile.Read(ref _nextActivationServerTimeout)) + if (_hasInherentKeepAlive) + { + return; + } + + if (DateTime.UtcNow.Ticks > Volatile.Read(ref _nextActivationServerTimeout)) { OnServerTimeout(); } diff --git a/src/SignalR/clients/csharp/Client.Core/src/Microsoft.AspNetCore.SignalR.Client.Core.csproj b/src/SignalR/clients/csharp/Client.Core/src/Microsoft.AspNetCore.SignalR.Client.Core.csproj index df54c48fec..c1aca1c275 100644 --- a/src/SignalR/clients/csharp/Client.Core/src/Microsoft.AspNetCore.SignalR.Client.Core.csproj +++ b/src/SignalR/clients/csharp/Client.Core/src/Microsoft.AspNetCore.SignalR.Client.Core.csproj @@ -4,7 +4,7 @@ Client for ASP.NET Core SignalR netstandard2.0;netstandard2.1 Microsoft.AspNetCore.SignalR.Client - true + true @@ -32,7 +32,7 @@ - + diff --git a/src/SignalR/clients/csharp/Client/src/Microsoft.AspNetCore.SignalR.Client.csproj b/src/SignalR/clients/csharp/Client/src/Microsoft.AspNetCore.SignalR.Client.csproj index 7bfb5b895c..56be28a14c 100644 --- a/src/SignalR/clients/csharp/Client/src/Microsoft.AspNetCore.SignalR.Client.csproj +++ b/src/SignalR/clients/csharp/Client/src/Microsoft.AspNetCore.SignalR.Client.csproj @@ -3,7 +3,7 @@ Client for ASP.NET Core SignalR netstandard2.0 - true + true diff --git a/src/SignalR/clients/csharp/Client/test/FunctionalTests/HubConnectionTests.cs b/src/SignalR/clients/csharp/Client/test/FunctionalTests/HubConnectionTests.cs index 6dbd4e032e..2220685e82 100644 --- a/src/SignalR/clients/csharp/Client/test/FunctionalTests/HubConnectionTests.cs +++ b/src/SignalR/clients/csharp/Client/test/FunctionalTests/HubConnectionTests.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; +using System.Text.Json; using System.Threading; using System.Threading.Channels; using System.Threading.Tasks; @@ -85,7 +86,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests public async Task CheckFixedMessage(string protocolName, HttpTransportType transportType, string path) { var protocol = HubProtocols[protocolName]; - using (StartServer(out var server)) + using (var server = await StartServer()) { var connectionBuilder = new HubConnectionBuilder() .WithLoggerFactory(LoggerFactory) @@ -124,7 +125,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests } var protocol = HubProtocols["json"]; - using (StartServer(out var server, ExpectedError)) + using (var server = await StartServer(ExpectedError)) { var connectionBuilder = new HubConnectionBuilder() .WithLoggerFactory(LoggerFactory) @@ -154,7 +155,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests public async Task ClientCanConnectToServerWithLowerMinimumProtocol() { var protocol = HubProtocols["json"]; - using (StartServer(out var server)) + using (var server = await StartServer()) { var connectionBuilder = new HubConnectionBuilder() .WithLoggerFactory(LoggerFactory) @@ -184,7 +185,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests public async Task CanSendAndReceiveMessage(string protocolName, HttpTransportType transportType, string path) { var protocol = HubProtocols[protocolName]; - using (StartServer(out var server)) + using (var server = await StartServer()) { const string originalMessage = "SignalR"; var connection = CreateHubConnection(server.Url, path, transportType, protocol, LoggerFactory); @@ -213,7 +214,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests public async Task CanSendNull(string protocolName) { var protocol = HubProtocols[protocolName]; - using (StartServer(out var server)) + using (var server = await StartServer()) { var connection = CreateHubConnection(server.Url, "/default", HttpTransportType.LongPolling, protocol, LoggerFactory); try @@ -242,7 +243,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests public async Task CanStopAndStartConnection(string protocolName, HttpTransportType transportType, string path) { var protocol = HubProtocols[protocolName]; - using (StartServer(out var server)) + using (var server = await StartServer()) { const string originalMessage = "SignalR"; var connection = CreateHubConnection(server.Url, path, transportType, protocol, LoggerFactory); @@ -274,7 +275,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests public async Task CanAccessConnectionIdFromHubConnection(string protocolName, HttpTransportType transportType, string path) { var protocol = HubProtocols[protocolName]; - using (StartServer(out var server)) + using (var server = await StartServer()) { var connection = CreateHubConnection(server.Url, path, transportType, protocol, LoggerFactory); try @@ -309,7 +310,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests public async Task CanStartConnectionFromClosedEvent(string protocolName, HttpTransportType transportType, string path) { var protocol = HubProtocols[protocolName]; - using (StartServer(out var server)) + using (var server = await StartServer()) { var logger = LoggerFactory.CreateLogger(); const string originalMessage = "SignalR"; @@ -371,7 +372,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests public async Task MethodsAreCaseInsensitive(string protocolName, HttpTransportType transportType, string path) { var protocol = HubProtocols[protocolName]; - using (StartServer(out var server)) + using (var server = await StartServer()) { const string originalMessage = "SignalR"; var connection = CreateHubConnection(server.Url, path, transportType, protocol, LoggerFactory); @@ -401,7 +402,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests public async Task CanInvokeFromOnHandler(string protocolName, HttpTransportType transportType, string path) { var protocol = HubProtocols[protocolName]; - using (StartServer(out var server)) + using (var server = await StartServer()) { const string originalMessage = "SignalR"; @@ -441,7 +442,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests public async Task StreamAsyncCoreTest(string protocolName, HttpTransportType transportType, string path) { var protocol = HubProtocols[protocolName]; - using (StartServer(out var server)) + using (var server = await StartServer()) { var connection = CreateHubConnection(server.Url, path, transportType, protocol, LoggerFactory); try @@ -476,7 +477,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests public async Task CanStreamToHubWithIAsyncEnumerableMethodArg(string protocolName) { var protocol = HubProtocols[protocolName]; - using (StartServer(out var server)) + using (var server = await StartServer()) { var connection = CreateHubConnection(server.Url, "/default", HttpTransportType.WebSockets, protocol, LoggerFactory); try @@ -522,7 +523,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests public async Task StreamAsyncTest(string protocolName, HttpTransportType transportType, string path) { var protocol = HubProtocols[protocolName]; - using (StartServer(out var server)) + using (var server = await StartServer()) { var connection = CreateHubConnection(server.Url, path, transportType, protocol, LoggerFactory); try @@ -557,7 +558,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests public async Task StreamAsyncDoesNotStartIfTokenAlreadyCanceled(string protocolName, HttpTransportType transportType, string path) { var protocol = HubProtocols[protocolName]; - using (StartServer(out var server)) + using (var server = await StartServer()) { var connection = CreateHubConnection(server.Url, path, transportType, protocol, LoggerFactory); try @@ -594,7 +595,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests public async Task StreamAsyncCanBeCanceled(string protocolName, HttpTransportType transportType, string path) { var protocol = HubProtocols[protocolName]; - using (StartServer(out var server)) + using (var server = await StartServer()) { var connection = CreateHubConnection(server.Url, path, transportType, protocol, LoggerFactory); try @@ -642,7 +643,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests } var protocol = HubProtocols[protocolName]; - using (StartServer(out var server, ExpectedErrors)) + using (var server = await StartServer(ExpectedErrors)) { var connection = CreateHubConnection(server.Url, path, transportType, protocol, LoggerFactory); try @@ -678,7 +679,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests public async Task CanInvokeClientMethodFromServer(string protocolName, HttpTransportType transportType, string path) { var protocol = HubProtocols[protocolName]; - using (StartServer(out var server)) + using (var server = await StartServer()) { const string originalMessage = "SignalR"; @@ -712,7 +713,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests public async Task InvokeNonExistantClientMethodFromServer(string protocolName, HttpTransportType transportType, string path) { var protocol = HubProtocols[protocolName]; - using (StartServer(out var server)) + using (var server = await StartServer()) { var connection = CreateHubConnection(server.Url, path, transportType, protocol, LoggerFactory); var closeTcs = new TaskCompletionSource(); @@ -754,7 +755,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests public async Task CanStreamClientMethodFromServer(string protocolName, HttpTransportType transportType, string path) { var protocol = HubProtocols[protocolName]; - using (StartServer(out var server)) + using (var server = await StartServer()) { var connection = CreateHubConnection(server.Url, path, transportType, protocol, LoggerFactory); try @@ -784,7 +785,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests public async Task CanStreamToAndFromClientInSameInvocation(string protocolName, HttpTransportType transportType, string path) { var protocol = HubProtocols[protocolName]; - using (StartServer(out var server)) + using (var server = await StartServer()) { var connection = CreateHubConnection(server.Url, path, transportType, protocol, LoggerFactory); try @@ -821,7 +822,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests public async Task CanStreamToServerWithIAsyncEnumerable(string protocolName, HttpTransportType transportType, string path) { var protocol = HubProtocols[protocolName]; - using (StartServer(out var server)) + using (var server = await StartServer()) { var connection = CreateHubConnection(server.Url, path, transportType, protocol, LoggerFactory); try @@ -868,7 +869,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests public async Task CanCancelIAsyncEnumerableClientToServerUpload(string protocolName, HttpTransportType transportType, string path) { var protocol = HubProtocols[protocolName]; - using (StartServer(out var server)) + using (var server = await StartServer()) { var connection = CreateHubConnection(server.Url, path, transportType, protocol, LoggerFactory); try @@ -921,7 +922,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests public async Task StreamAsyncCanBeCanceledThroughGetAsyncEnumerator(string protocolName, HttpTransportType transportType, string path) { var protocol = HubProtocols[protocolName]; - using (StartServer(out var server)) + using (var server = await StartServer()) { var connection = CreateHubConnection(server.Url, path, transportType, protocol, LoggerFactory); try @@ -962,7 +963,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests public async Task CanCloseStreamMethodEarly(string protocolName, HttpTransportType transportType, string path) { var protocol = HubProtocols[protocolName]; - using (StartServer(out var server)) + using (var server = await StartServer()) { var connection = CreateHubConnection(server.Url, path, transportType, protocol, LoggerFactory); try @@ -1003,7 +1004,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests public async Task StreamDoesNotStartIfTokenAlreadyCanceled(string protocolName, HttpTransportType transportType, string path) { var protocol = HubProtocols[protocolName]; - using (StartServer(out var server)) + using (var server = await StartServer()) { var connection = CreateHubConnection(server.Url, path, transportType, protocol, LoggerFactory); try @@ -1038,7 +1039,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests } var protocol = HubProtocols[protocolName]; - using (StartServer(out var server, ExpectedErrors)) + using (var server = await StartServer(ExpectedErrors)) { var connection = CreateHubConnection(server.Url, path, transportType, protocol, LoggerFactory); try @@ -1066,7 +1067,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests public async Task ServerThrowsHubExceptionIfHubMethodCannotBeResolved(string hubProtocolName, HttpTransportType transportType, string hubPath) { var hubProtocol = HubProtocols[hubProtocolName]; - using (StartServer(out var server)) + using (var server = await StartServer()) { var connection = CreateHubConnection(server.Url, hubPath, transportType, hubProtocol, LoggerFactory); try @@ -1093,7 +1094,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests public async Task ServerThrowsHubExceptionIfHubMethodCannotBeResolvedAndArgumentsPassedIn(string hubProtocolName, HttpTransportType transportType, string hubPath) { var hubProtocol = HubProtocols[hubProtocolName]; - using (StartServer(out var server)) + using (var server = await StartServer()) { var connection = CreateHubConnection(server.Url, hubPath, transportType, hubProtocol, LoggerFactory); try @@ -1120,7 +1121,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests public async Task ServerThrowsHubExceptionOnHubMethodArgumentCountMismatch(string hubProtocolName) { var hubProtocol = HubProtocols[hubProtocolName]; - using (StartServer(out var server)) + using (var server = await StartServer()) { var connection = CreateHubConnection(server.Url, "/default", HttpTransportType.LongPolling, hubProtocol, LoggerFactory); try @@ -1147,7 +1148,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests public async Task ServerThrowsHubExceptionOnHubMethodArgumentTypeMismatch(string hubProtocolName, HttpTransportType transportType, string hubPath) { var hubProtocol = HubProtocols[hubProtocolName]; - using (StartServer(out var server)) + using (var server = await StartServer()) { var connection = CreateHubConnection(server.Url, hubPath, transportType, hubProtocol, LoggerFactory); try @@ -1174,7 +1175,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests public async Task ServerThrowsHubExceptionIfStreamingHubMethodCannotBeResolved(string hubProtocolName, HttpTransportType transportType, string hubPath) { var hubProtocol = HubProtocols[hubProtocolName]; - using (StartServer(out var server)) + using (var server = await StartServer()) { var connection = CreateHubConnection(server.Url, hubPath, transportType, hubProtocol, LoggerFactory); try @@ -1202,7 +1203,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests public async Task ServerThrowsHubExceptionOnStreamingHubMethodArgumentCountMismatch(string hubProtocolName, HttpTransportType transportType, string hubPath) { var hubProtocol = HubProtocols[hubProtocolName]; - using (StartServer(out var server)) + using (var server = await StartServer()) { var connection = CreateHubConnection(server.Url, hubPath, transportType, hubProtocol, LoggerFactory); try @@ -1230,7 +1231,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests public async Task ServerThrowsHubExceptionOnStreamingHubMethodArgumentTypeMismatch(string hubProtocolName, HttpTransportType transportType, string hubPath) { var hubProtocol = HubProtocols[hubProtocolName]; - using (StartServer(out var server)) + using (var server = await StartServer()) { var connection = CreateHubConnection(server.Url, hubPath, transportType, hubProtocol, LoggerFactory); try @@ -1258,7 +1259,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests public async Task ServerThrowsHubExceptionIfNonStreamMethodInvokedWithStreamAsync(string hubProtocolName, HttpTransportType transportType, string hubPath) { var hubProtocol = HubProtocols[hubProtocolName]; - using (StartServer(out var server)) + using (var server = await StartServer()) { var connection = CreateHubConnection(server.Url, hubPath, transportType, hubProtocol, LoggerFactory); try @@ -1285,7 +1286,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests public async Task ServerThrowsHubExceptionIfStreamMethodInvokedWithInvoke(string hubProtocolName, HttpTransportType transportType, string hubPath) { var hubProtocol = HubProtocols[hubProtocolName]; - using (StartServer(out var server)) + using (var server = await StartServer()) { var connection = CreateHubConnection(server.Url, hubPath, transportType, hubProtocol, LoggerFactory); try @@ -1312,7 +1313,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests public async Task ServerThrowsHubExceptionIfBuildingAsyncEnumeratorIsNotPossible(string hubProtocolName, HttpTransportType transportType, string hubPath) { var hubProtocol = HubProtocols[hubProtocolName]; - using (StartServer(out var server)) + using (var server = await StartServer()) { var connection = CreateHubConnection(server.Url, hubPath, transportType, hubProtocol, LoggerFactory); try @@ -1341,7 +1342,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests var hubProtocol = HubProtocols.First().Value; var transportType = TransportTypes().First().Cast().First(); - using (StartServer(out var server)) + using (var server = await StartServer()) { var connection = CreateHubConnection(server.Url, hubPath, transportType, hubProtocol, LoggerFactory); await connection.StartAsync().OrTimeout(); @@ -1358,7 +1359,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests [MemberData(nameof(TransportTypes))] public async Task ClientCanUseJwtBearerTokenForAuthentication(HttpTransportType transportType) { - using (StartServer(out var server)) + using (var server = await StartServer()) { async Task AccessTokenProvider() { @@ -1401,7 +1402,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests return writeContext.Exception is HttpRequestException; } - using (StartServer(out var server, ExpectedErrors)) + using (var server = await StartServer(ExpectedErrors)) { var hubConnection = new HubConnectionBuilder() .WithLoggerFactory(LoggerFactory) @@ -1423,7 +1424,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests [MemberData(nameof(TransportTypes))] public async Task ClientCanUseJwtBearerTokenForAuthenticationWhenRedirected(HttpTransportType transportType) { - using (StartServer(out var server)) + using (var server = await StartServer()) { var hubConnection = new HubConnectionBuilder() .WithLoggerFactory(LoggerFactory) @@ -1451,7 +1452,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests [MemberData(nameof(TransportTypes))] public async Task ClientCanSendHeaders(HttpTransportType transportType) { - using (StartServer(out var server)) + using (var server = await StartServer()) { var hubConnection = new HubConnectionBuilder() .WithLoggerFactory(LoggerFactory) @@ -1479,11 +1480,123 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests } } + [Fact] + public async Task UserAgentIsSet() + { + using (var server = await StartServer()) + { + var hubConnection = new HubConnectionBuilder() + .WithLoggerFactory(LoggerFactory) + .WithUrl(server.Url + "/default", HttpTransportType.LongPolling, options => + { + options.Headers["X-test"] = "42"; + options.Headers["X-42"] = "test"; + }) + .Build(); + try + { + await hubConnection.StartAsync().OrTimeout(); + var headerValues = await hubConnection.InvokeAsync(nameof(TestHub.GetHeaderValues), new[] { "User-Agent" }).OrTimeout(); + Assert.NotNull(headerValues); + Assert.Single(headerValues); + + var userAgent = headerValues[0]; + + Assert.StartsWith("Microsoft SignalR/", userAgent); + + var majorVersion = typeof(HttpConnection).Assembly.GetName().Version.Major; + var minorVersion = typeof(HttpConnection).Assembly.GetName().Version.Minor; + + Assert.Contains($"{majorVersion}.{minorVersion}", userAgent); + + } + catch (Exception ex) + { + LoggerFactory.CreateLogger().LogError(ex, "{ExceptionType} from test", ex.GetType().FullName); + throw; + } + finally + { + await hubConnection.DisposeAsync().OrTimeout(); + } + } + } + + [Fact] + public async Task UserAgentCanBeCleared() + { + using (var server = await StartServer()) + { + var hubConnection = new HubConnectionBuilder() + .WithLoggerFactory(LoggerFactory) + .WithUrl(server.Url + "/default", HttpTransportType.LongPolling, options => + { + options.Headers["User-Agent"] = ""; + }) + .Build(); + try + { + await hubConnection.StartAsync().OrTimeout(); + var headerValues = await hubConnection.InvokeAsync(nameof(TestHub.GetHeaderValues), new[] { "User-Agent" }).OrTimeout(); + Assert.NotNull(headerValues); + Assert.Single(headerValues); + + var userAgent = headerValues[0]; + + Assert.Null(userAgent); + } + catch (Exception ex) + { + LoggerFactory.CreateLogger().LogError(ex, "{ExceptionType} from test", ex.GetType().FullName); + throw; + } + finally + { + await hubConnection.DisposeAsync().OrTimeout(); + } + } + } + + [Fact] + public async Task UserAgentCanBeSet() + { + using (var server = await StartServer()) + { + var hubConnection = new HubConnectionBuilder() + .WithLoggerFactory(LoggerFactory) + .WithUrl(server.Url + "/default", HttpTransportType.LongPolling, options => + { + options.Headers["User-Agent"] = "User Value"; + }) + .Build(); + try + { + await hubConnection.StartAsync().OrTimeout(); + var headerValues = await hubConnection.InvokeAsync(nameof(TestHub.GetHeaderValues), new[] { "User-Agent" }).OrTimeout(); + Assert.NotNull(headerValues); + Assert.Single(headerValues); + + var userAgent = headerValues[0]; + + Assert.Equal("User Value", userAgent); + } + catch (Exception ex) + { + LoggerFactory.CreateLogger().LogError(ex, "{ExceptionType} from test", ex.GetType().FullName); + throw; + } + finally + { + await hubConnection.DisposeAsync().OrTimeout(); + } + } + } + [ConditionalFact] [WebSocketsSupportedCondition] public async Task WebSocketOptionsAreApplied() { - using (StartServer(out var server)) + using (var server = await StartServer()) { // System.Net has a HttpTransportType type which means we need to fully-qualify this rather than 'use' the namespace var cookieJar = new System.Net.CookieContainer(); @@ -1514,10 +1627,10 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests } } - [Fact(Skip = "Returning object from Hub method not support by System.Text.Json yet")] + [Fact] public async Task CheckHttpConnectionFeatures() { - using (StartServer(out var server)) + using (var server = await StartServer()) { var hubConnection = new HubConnectionBuilder() .WithLoggerFactory(LoggerFactory) @@ -1527,11 +1640,11 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests { await hubConnection.StartAsync().OrTimeout(); - var features = await hubConnection.InvokeAsync(nameof(TestHub.GetIHttpConnectionFeatureProperties)).OrTimeout(); - var localPort = (long)features[0]; - var remotePort = (long)features[1]; - var localIP = (string)features[2]; - var remoteIP = (string)features[3]; + var features = await hubConnection.InvokeAsync(nameof(TestHub.GetIHttpConnectionFeatureProperties)).OrTimeout(); + var localPort = features[0].GetInt64(); + var remotePort = features[1].GetInt64(); + var localIP = features[2].GetString(); + var remoteIP = features[3].GetString(); Assert.True(localPort > 0L); Assert.True(remotePort > 0L); @@ -1553,7 +1666,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests [Fact] public async Task UserIdProviderCanAccessHttpContext() { - using (StartServer(out var server)) + using (var server = await StartServer()) { var hubConnection = new HubConnectionBuilder() .WithLoggerFactory(LoggerFactory) @@ -1584,7 +1697,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests [Fact] public async Task NegotiationSkipsServerSentEventsWhenUsingBinaryProtocol() { - using (StartServer(out var server)) + using (var server = await StartServer()) { var hubConnectionBuilder = new HubConnectionBuilder() .WithLoggerFactory(LoggerFactory) @@ -1614,7 +1727,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests [Fact] public async Task StopCausesPollToReturnImmediately() { - using (StartServer(out var server)) + using (var server = await StartServer()) { PollTrackingMessageHandler pollTracker = null; var hubConnection = new HubConnectionBuilder() @@ -1662,7 +1775,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests writeContext.EventId.Name == "ReconnectingWithError"; } - using (StartServer(out var server, ExpectedErrors)) + using (var server = await StartServer(ExpectedErrors)) { var connection = CreateHubConnection( server.Url, @@ -1724,7 +1837,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests writeContext.EventId.Name == "ReconnectingWithError"; } - using (StartServer(out var server, ExpectedErrors)) + using (var server = await StartServer(ExpectedErrors)) { var connection = new HubConnectionBuilder() .WithLoggerFactory(LoggerFactory) @@ -1784,7 +1897,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests writeContext.EventId.Name == "ReconnectingWithError"; } - using (StartServer(out var server, ExpectedErrors)) + using (var server = await StartServer(ExpectedErrors)) { var connectionBuilder = new HubConnectionBuilder() .WithLoggerFactory(LoggerFactory) diff --git a/src/SignalR/clients/csharp/Client/test/FunctionalTests/HubProtocolVersionTests.cs b/src/SignalR/clients/csharp/Client/test/FunctionalTests/HubProtocolVersionTests.cs index c9e7b65e66..b7f3206313 100644 --- a/src/SignalR/clients/csharp/Client/test/FunctionalTests/HubProtocolVersionTests.cs +++ b/src/SignalR/clients/csharp/Client/test/FunctionalTests/HubProtocolVersionTests.cs @@ -35,7 +35,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests [MemberData(nameof(TransportTypes))] public async Task ClientUsingOldCallWithOriginalProtocol(HttpTransportType transportType) { - using (StartServer(out var server)) + using (var server = await StartServer()) { var connectionBuilder = new HubConnectionBuilder() .WithLoggerFactory(LoggerFactory) @@ -67,7 +67,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests [MemberData(nameof(TransportTypes))] public async Task ClientUsingOldCallWithNewProtocol(HttpTransportType transportType) { - using (StartServer(out var server)) + using (var server = await StartServer()) { var connectionBuilder = new HubConnectionBuilder() .WithLoggerFactory(LoggerFactory) @@ -100,7 +100,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests [MemberData(nameof(TransportTypes))] public async Task ClientUsingNewCallWithNewProtocol(HttpTransportType transportType) { - using (StartServer(out var server)) + using (var server = await StartServer()) { var httpConnectionFactory = new HttpConnectionFactory( Options.Create(new HttpConnectionOptions @@ -166,7 +166,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests return writeContext.LoggerName == typeof(HubConnection).FullName; } - using (StartServer(out var server, ExpectedErrors)) + using (var server = await StartServer(ExpectedErrors)) { var connectionBuilder = new HubConnectionBuilder() .WithLoggerFactory(LoggerFactory) diff --git a/src/SignalR/clients/csharp/Client/test/UnitTests/HttpConnectionTests.Transport.cs b/src/SignalR/clients/csharp/Client/test/UnitTests/HttpConnectionTests.Transport.cs index 142e40546c..a15f18faa1 100644 --- a/src/SignalR/clients/csharp/Client/test/UnitTests/HttpConnectionTests.Transport.cs +++ b/src/SignalR/clients/csharp/Client/test/UnitTests/HttpConnectionTests.Transport.cs @@ -3,6 +3,7 @@ using System; using System.IO.Pipelines; +using System.Linq; using System.Net; using System.Net.Http; using System.Reflection; @@ -14,6 +15,7 @@ using Microsoft.AspNetCore.Http.Connections; using Microsoft.AspNetCore.Http.Connections.Client; using Microsoft.AspNetCore.Http.Connections.Client.Internal; using Microsoft.AspNetCore.SignalR.Tests; +using Microsoft.Net.Http.Headers; using Xunit; namespace Microsoft.AspNetCore.SignalR.Client.Tests @@ -113,16 +115,17 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests testHttpHandler.OnRequest(async (request, next, token) => { - var userAgentHeaderCollection = request.Headers.UserAgent; - var userAgentHeader = Assert.Single(userAgentHeaderCollection); - Assert.Equal("Microsoft.AspNetCore.Http.Connections.Client", userAgentHeader.Product.Name); + var userAgentHeader = request.Headers.UserAgent.ToString(); + + Assert.NotNull(userAgentHeader); + Assert.StartsWith("Microsoft SignalR/", userAgentHeader); // user agent version should come from version embedded in assembly metadata var assemblyVersion = typeof(Constants) .Assembly .GetCustomAttribute(); - Assert.Equal(assemblyVersion.InformationalVersion, userAgentHeader.Product.Version); + Assert.Contains(assemblyVersion.InformationalVersion, userAgentHeader); requestsExecuted = true; @@ -160,7 +163,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests testHttpHandler.OnRequest(async (request, next, token) => { - var requestedWithHeader = request.Headers.GetValues("X-Requested-With"); + var requestedWithHeader = request.Headers.GetValues(HeaderNames.XRequestedWith); var requestedWithValue = Assert.Single(requestedWithHeader); Assert.Equal("XMLHttpRequest", requestedWithValue); diff --git a/src/SignalR/clients/csharp/Client/test/UnitTests/HubConnectionTests.ConnectionLifecycle.cs b/src/SignalR/clients/csharp/Client/test/UnitTests/HubConnectionTests.ConnectionLifecycle.cs index 3c669ef94d..f1d191ee8c 100644 --- a/src/SignalR/clients/csharp/Client/test/UnitTests/HubConnectionTests.ConnectionLifecycle.cs +++ b/src/SignalR/clients/csharp/Client/test/UnitTests/HubConnectionTests.ConnectionLifecycle.cs @@ -334,6 +334,26 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests }); } + [Fact] + public async Task StopAsyncOnInactiveConnectionDoesNotAffectNextStartAsync() + { + // Regression test: + // If there wasn't an active underlying connection, StopAsync would leave a CTS canceled which would cause the next StartAsync to fail + var testConnection = new TestConnection(); + await AsyncUsing(CreateHubConnection(testConnection), async connection => + { + Assert.Equal(HubConnectionState.Disconnected, connection.State); + + await connection.StopAsync().OrTimeout(); + Assert.False(testConnection.Disposed.IsCompleted); + Assert.Equal(HubConnectionState.Disconnected, connection.State); + + await connection.StartAsync().OrTimeout(); + Assert.True(testConnection.Started.IsCompleted); + Assert.Equal(HubConnectionState.Connected, connection.State); + }); + } + [Fact] public async Task CompletingTheTransportSideMarksConnectionAsClosed() { diff --git a/src/SignalR/clients/csharp/Client/test/UnitTests/HubConnectionTests.Protocol.cs b/src/SignalR/clients/csharp/Client/test/UnitTests/HubConnectionTests.Protocol.cs index 93cc3dd863..e254a79c5c 100644 --- a/src/SignalR/clients/csharp/Client/test/UnitTests/HubConnectionTests.Protocol.cs +++ b/src/SignalR/clients/csharp/Client/test/UnitTests/HubConnectionTests.Protocol.cs @@ -648,6 +648,33 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests await connection.DisposeAsync().OrTimeout(); } } + + [Fact] + public async Task ClientWithInherentKeepAliveDoesNotPing() + { + var connection = new TestConnection(hasInherentKeepAlive: true); + var hubConnection = CreateHubConnection(connection); + + hubConnection.TickRate = TimeSpan.FromMilliseconds(30); + hubConnection.KeepAliveInterval = TimeSpan.FromMilliseconds(80); + + try + { + await hubConnection.StartAsync().OrTimeout(); + + await Task.Delay(1000); + + await hubConnection.DisposeAsync().OrTimeout(); + await connection.DisposeAsync().OrTimeout(); + + Assert.Equal(0, (await connection.ReadAllSentMessagesAsync(ignorePings: false).OrTimeout()).Count); + } + finally + { + await hubConnection.DisposeAsync().OrTimeout(); + await connection.DisposeAsync().OrTimeout(); + } + } } } } diff --git a/src/SignalR/clients/csharp/Client/test/UnitTests/HubConnectionTests.cs b/src/SignalR/clients/csharp/Client/test/UnitTests/HubConnectionTests.cs index cd76bd7655..cbaec6f40d 100644 --- a/src/SignalR/clients/csharp/Client/test/UnitTests/HubConnectionTests.cs +++ b/src/SignalR/clients/csharp/Client/test/UnitTests/HubConnectionTests.cs @@ -460,6 +460,17 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests } } + [Fact] + public async Task CanAwaitUsingHubConnection() + { + using (StartVerifiableLog()) + { + var connection = new TestConnection(); + await using var hubConnection = CreateHubConnection(connection, loggerFactory: LoggerFactory); + await hubConnection.StartAsync().OrTimeout(); + } + } + private class SampleObject { public SampleObject(string foo, int bar) diff --git a/src/SignalR/clients/csharp/Client/test/UnitTests/TestConnection.cs b/src/SignalR/clients/csharp/Client/test/UnitTests/TestConnection.cs index fbc516e95c..0da16cf160 100644 --- a/src/SignalR/clients/csharp/Client/test/UnitTests/TestConnection.cs +++ b/src/SignalR/clients/csharp/Client/test/UnitTests/TestConnection.cs @@ -10,6 +10,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; +using Microsoft.AspNetCore.Connections.Features; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Internal; using Microsoft.AspNetCore.SignalR.Protocol; @@ -18,7 +19,7 @@ using Newtonsoft.Json.Linq; namespace Microsoft.AspNetCore.SignalR.Client.Tests { - internal class TestConnection : ConnectionContext + internal class TestConnection : ConnectionContext, IConnectionInherentKeepAliveFeature { private readonly bool _autoHandshake; private readonly TaskCompletionSource _started = new TaskCompletionSource(); @@ -30,6 +31,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests private readonly Func _onStart; private readonly Func _onDispose; + private readonly bool _hasInherentKeepAlive; public override string ConnectionId { get; set; } @@ -41,17 +43,22 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests public override IDictionary Items { get; set; } = new ConnectionItems(); - public TestConnection(Func onStart = null, Func onDispose = null, bool autoHandshake = true) + bool IConnectionInherentKeepAliveFeature.HasInherentKeepAlive => _hasInherentKeepAlive; + + public TestConnection(Func onStart = null, Func onDispose = null, bool autoHandshake = true, bool hasInherentKeepAlive = false) { _autoHandshake = autoHandshake; _onStart = onStart ?? (() => Task.CompletedTask); _onDispose = onDispose ?? (() => Task.CompletedTask); + _hasInherentKeepAlive = hasInherentKeepAlive; var options = new PipeOptions(readerScheduler: PipeScheduler.Inline, writerScheduler: PipeScheduler.Inline, useSynchronizationContext: false); var pair = DuplexPipe.CreateConnectionPair(options, options); Application = pair.Application; Transport = pair.Transport; + + Features.Set(this); } public override ValueTask DisposeAsync() => DisposeCoreAsync(); @@ -119,6 +126,10 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests while (true) { var result = await ReadSentTextMessageAsyncInner(); + if (result == null) + { + return null; + } var receivedMessageType = (int?)JObject.Parse(result)["type"]; diff --git a/src/SignalR/clients/csharp/Http.Connections.Client/src/HttpConnection.cs b/src/SignalR/clients/csharp/Http.Connections.Client/src/HttpConnection.cs index 4375d11285..acfdce1010 100644 --- a/src/SignalR/clients/csharp/Http.Connections.Client/src/HttpConnection.cs +++ b/src/SignalR/clients/csharp/Http.Connections.Client/src/HttpConnection.cs @@ -562,14 +562,34 @@ namespace Microsoft.AspNetCore.Http.Connections.Client httpClient.Timeout = HttpClientTimeout; // Start with the user agent header - httpClient.DefaultRequestHeaders.UserAgent.Add(Constants.UserAgentHeader); + httpClient.DefaultRequestHeaders.Add(Constants.UserAgent, Constants.UserAgentHeader); // Apply any headers configured on the HttpConnectionOptions if (_httpConnectionOptions?.Headers != null) { foreach (var header in _httpConnectionOptions.Headers) { - httpClient.DefaultRequestHeaders.Add(header.Key, header.Value); + // Check if the key is User-Agent and remove if empty string then replace if it exists. + if (string.Equals(header.Key, Constants.UserAgent, StringComparison.OrdinalIgnoreCase)) + { + if (string.IsNullOrEmpty(header.Value)) + { + httpClient.DefaultRequestHeaders.Remove(header.Key); + } + else if (httpClient.DefaultRequestHeaders.Contains(header.Key)) + { + httpClient.DefaultRequestHeaders.Remove(header.Key); + httpClient.DefaultRequestHeaders.Add(header.Key, header.Value); + } + else + { + httpClient.DefaultRequestHeaders.Add(header.Key, header.Value); + } + } + else + { + httpClient.DefaultRequestHeaders.Add(header.Key, header.Value); + } } } diff --git a/src/SignalR/clients/csharp/Http.Connections.Client/src/Internal/Constants.cs b/src/SignalR/clients/csharp/Http.Connections.Client/src/Internal/Constants.cs index 22b41d56f3..6b6809b837 100644 --- a/src/SignalR/clients/csharp/Http.Connections.Client/src/Internal/Constants.cs +++ b/src/SignalR/clients/csharp/Http.Connections.Client/src/Internal/Constants.cs @@ -1,21 +1,21 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.Diagnostics; using System.Linq; -using System.Net.Http.Headers; using System.Reflection; +using System.Runtime.InteropServices; namespace Microsoft.AspNetCore.Http.Connections.Client.Internal { internal static class Constants { - public static readonly ProductInfoHeaderValue UserAgentHeader; + public const string UserAgent = "User-Agent"; + public static readonly string UserAgentHeader; static Constants() { - var userAgent = "Microsoft.AspNetCore.Http.Connections.Client"; - var assemblyVersion = typeof(Constants) .Assembly .GetCustomAttributes() @@ -23,14 +23,68 @@ namespace Microsoft.AspNetCore.Http.Connections.Client.Internal Debug.Assert(assemblyVersion != null); - // assembly version attribute should always be present - // but in case it isn't then don't include version in user-agent - if (assemblyVersion != null) + var runtime = ".NET"; + var runtimeVersion = RuntimeInformation.FrameworkDescription; + + UserAgentHeader = ConstructUserAgent(typeof(Constants).Assembly.GetName().Version, assemblyVersion?.InformationalVersion, GetOS(), runtime, runtimeVersion); + } + + private static string GetOS() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - userAgent += "/" + assemblyVersion.InformationalVersion; + return "Windows NT"; + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + return "macOS"; + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + return "Linux"; + } + else + { + return ""; + } + } + + public static string ConstructUserAgent(Version version, string detailedVersion, string os, string runtime, string runtimeVersion) + { + var userAgent = $"Microsoft SignalR/{version.Major}.{version.Minor} ("; + + if (!string.IsNullOrEmpty(detailedVersion)) + { + userAgent += $"{detailedVersion}"; + } + else + { + userAgent += "Unknown Version"; } - UserAgentHeader = ProductInfoHeaderValue.Parse(userAgent); + if (!string.IsNullOrEmpty(os)) + { + userAgent += $"; {os}"; + } + else + { + userAgent += "; Unknown OS"; + } + + userAgent += $"; {runtime}"; + + if (!string.IsNullOrEmpty(runtimeVersion)) + { + userAgent += $"; {runtimeVersion}"; + } + else + { + userAgent += "; Unknown Runtime Version"; + } + + userAgent += ")"; + + return userAgent; } } } diff --git a/src/SignalR/clients/csharp/Http.Connections.Client/src/Internal/ServerSentEventsMessageParser.cs b/src/SignalR/clients/csharp/Http.Connections.Client/src/Internal/ServerSentEventsMessageParser.cs index 6d4b5b8ffb..ed4caa274a 100644 --- a/src/SignalR/clients/csharp/Http.Connections.Client/src/Internal/ServerSentEventsMessageParser.cs +++ b/src/SignalR/clients/csharp/Http.Connections.Client/src/Internal/ServerSentEventsMessageParser.cs @@ -15,8 +15,9 @@ namespace Microsoft.AspNetCore.Http.Connections.Client.Internal private const byte ByteLF = (byte)'\n'; private const byte ByteColon = (byte)':'; - private static ReadOnlySpan _dataPrefix => new byte[] { (byte)'d', (byte)'a', (byte)'t', (byte)'a', (byte)':', (byte)' ' }; - private static ReadOnlySpan _sseLineEnding => new byte[] { (byte)'\r', (byte)'\n' }; + // This uses C# compiler's ability to refer to static data directly. For more information see https://vcsjones.dev/2019/02/01/csharp-readonly-span-bytes-static + private static ReadOnlySpan DataPrefix => new byte[] { (byte)'d', (byte)'a', (byte)'t', (byte)'a', (byte)':', (byte)' ' }; + private static ReadOnlySpan SseLineEnding => new byte[] { (byte)'\r', (byte)'\n' }; private static readonly byte[] _newLine = Encoding.UTF8.GetBytes(Environment.NewLine); private InternalParseState _internalParserState = InternalParseState.ReadMessagePayload; @@ -74,7 +75,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Client.Internal // data: foo\n\bar should be encoded as // data: foo\r\n // data: bar\r\n - else if (line[line.Length - _sseLineEnding.Length] != ByteCR) + else if (line[line.Length - SseLineEnding.Length] != ByteCR) { throw new FormatException("Unexpected '\\n' in message. A '\\n' character can only be used as part of the newline sequence '\\r\\n'"); } @@ -90,8 +91,8 @@ namespace Microsoft.AspNetCore.Http.Connections.Client.Internal EnsureStartsWithDataPrefix(line); // Slice away the 'data: ' - var payloadLength = line.Length - (_dataPrefix.Length + _sseLineEnding.Length); - var newData = line.Slice(_dataPrefix.Length, payloadLength).ToArray(); + var payloadLength = line.Length - (DataPrefix.Length + SseLineEnding.Length); + var newData = line.Slice(DataPrefix.Length, payloadLength).ToArray(); _data.Add(newData); start = lineEnd; @@ -162,7 +163,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Client.Internal private void EnsureStartsWithDataPrefix(ReadOnlySpan line) { - if (!line.StartsWith(_dataPrefix)) + if (!line.StartsWith(DataPrefix)) { throw new FormatException("Expected the message prefix 'data: '"); } @@ -170,7 +171,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Client.Internal private bool IsMessageEnd(ReadOnlySpan line) { - return line.Length == _sseLineEnding.Length && line.SequenceEqual(_sseLineEnding); + return line.Length == SseLineEnding.Length && line.SequenceEqual(SseLineEnding); } public enum ParseResult diff --git a/src/SignalR/clients/csharp/Http.Connections.Client/src/Internal/WebSocketsTransport.cs b/src/SignalR/clients/csharp/Http.Connections.Client/src/Internal/WebSocketsTransport.cs index 905a965841..e41dc35022 100644 --- a/src/SignalR/clients/csharp/Http.Connections.Client/src/Internal/WebSocketsTransport.cs +++ b/src/SignalR/clients/csharp/Http.Connections.Client/src/Internal/WebSocketsTransport.cs @@ -36,8 +36,14 @@ namespace Microsoft.AspNetCore.Http.Connections.Client.Internal { _webSocket = new ClientWebSocket(); - // Issue in ClientWebSocket prevents user-agent being set - https://github.com/dotnet/corefx/issues/26627 - //_webSocket.Options.SetRequestHeader("User-Agent", Constants.UserAgentHeader.ToString()); + // Full Framework will throw when trying to set the User-Agent header + // So avoid setting it in netstandard2.0 and only set it in netstandard2.1 and higher +#if !NETSTANDARD2_0 + _webSocket.Options.SetRequestHeader("User-Agent", Constants.UserAgentHeader.ToString()); +#else + // Set an alternative user agent header on Full framework + _webSocket.Options.SetRequestHeader("X-SignalR-User-Agent", Constants.UserAgentHeader.ToString()); +#endif if (httpConnectionOptions != null) { diff --git a/src/SignalR/clients/csharp/Http.Connections.Client/src/Microsoft.AspNetCore.Http.Connections.Client.csproj b/src/SignalR/clients/csharp/Http.Connections.Client/src/Microsoft.AspNetCore.Http.Connections.Client.csproj index 850f263a7d..ee2e93c707 100644 --- a/src/SignalR/clients/csharp/Http.Connections.Client/src/Microsoft.AspNetCore.Http.Connections.Client.csproj +++ b/src/SignalR/clients/csharp/Http.Connections.Client/src/Microsoft.AspNetCore.Http.Connections.Client.csproj @@ -3,7 +3,7 @@ Client for ASP.NET Core Connection Handlers netstandard2.0;netstandard2.1 - true + true diff --git a/src/SignalR/clients/java/signalr/.gitignore b/src/SignalR/clients/java/signalr/.gitignore index eabba7738e..3e9534ce39 100644 --- a/src/SignalR/clients/java/signalr/.gitignore +++ b/src/SignalR/clients/java/signalr/.gitignore @@ -2,6 +2,7 @@ .gradletasknamecache .gradle/ build/ +/test-results .settings/ out/ *.class diff --git a/src/SignalR/clients/java/signalr/build.gradle b/src/SignalR/clients/java/signalr/build.gradle index 61b170a40d..b845d83949 100644 --- a/src/SignalR/clients/java/signalr/build.gradle +++ b/src/SignalR/clients/java/signalr/build.gradle @@ -6,6 +6,7 @@ buildscript { } dependencies { classpath "com.diffplug.spotless:spotless-plugin-gradle:3.14.0" + classpath 'org.junit.platform:junit-platform-gradle-plugin:1.0.0' } } @@ -16,6 +17,7 @@ plugins { apply plugin: "java-library" apply plugin: "com.diffplug.gradle.spotless" +apply plugin: 'org.junit.platform.gradle.plugin' group 'com.microsoft.signalr' @@ -64,8 +66,8 @@ spotless { } } -test { - useJUnitPlatform() +junitPlatform { + reportsDir file('test-results') } task sourceJar(type: Jar) { @@ -83,7 +85,7 @@ task generatePOM { project { inceptionYear '2018' description 'ASP.NET Core SignalR Client for Java applications' - url 'https://github.com/aspnet/AspNetCore' + url 'https://github.com/dotnet/aspnetcore' name groupId + ':' + artifactId licenses { license { @@ -93,9 +95,9 @@ task generatePOM { } } scm { - connection 'scm:git:git://github.com/aspnet/AspNetCore.git' - developerConnection 'scm:git:git://github.com/aspnet/AspNetCore.git' - url 'http://github.com/aspnet/AspNetCore/tree/master' + connection 'scm:git:git://github.com/dotnet/aspnetcore.git' + developerConnection 'scm:git:git://github.com/dotnet/aspnetcore.git' + url 'http://github.com/dotnet/aspnetcore/tree/master' } developers { developer { @@ -108,3 +110,27 @@ task generatePOM { } task createPackage(dependsOn: [jar,sourceJar,javadocJar,generatePOM]) + +task generateVersionClass { + inputs.property "version", project.version + outputs.dir "$buildDir/generated" + doFirst { + def versionFile = file("$buildDir/../src/main/java/com/microsoft/signalr/Version.java") + versionFile.parentFile.mkdirs() + versionFile.text = + """ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +package com.microsoft.signalr; + +class Version { + public static String getDetailedVersion() { + return "$project.version"; + } +} +""" + } +} + +compileJava.dependsOn generateVersionClass diff --git a/src/SignalR/clients/java/signalr/gradle/wrapper/gradle-wrapper.jar b/src/SignalR/clients/java/signalr/gradle/wrapper/gradle-wrapper.jar index 1948b9074f..f3d88b1c2f 100644 Binary files a/src/SignalR/clients/java/signalr/gradle/wrapper/gradle-wrapper.jar and b/src/SignalR/clients/java/signalr/gradle/wrapper/gradle-wrapper.jar differ diff --git a/src/SignalR/clients/java/signalr/gradle/wrapper/gradle-wrapper.properties b/src/SignalR/clients/java/signalr/gradle/wrapper/gradle-wrapper.properties index 838e6bc85a..407e71a6cf 100644 --- a/src/SignalR/clients/java/signalr/gradle/wrapper/gradle-wrapper.properties +++ b/src/SignalR/clients/java/signalr/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.3-bin.zip +distributionSha256Sum=d0c43d14e1c70a48b82442f435d06186351a2d290d72afd5b8866f15e6d7038a +distributionUrl=https\://services.gradle.org/distributions/gradle-6.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/src/SignalR/clients/java/signalr/gradlew b/src/SignalR/clients/java/signalr/gradlew index cccdd3d517..2fe81a7d95 100755 --- a/src/SignalR/clients/java/signalr/gradlew +++ b/src/SignalR/clients/java/signalr/gradlew @@ -1,5 +1,21 @@ #!/usr/bin/env sh +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + ############################################################################## ## ## Gradle start up script for UN*X @@ -28,7 +44,7 @@ APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" @@ -109,8 +125,8 @@ if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` JAVACMD=`cygpath --unix "$JAVACMD"` @@ -138,19 +154,19 @@ if $cygwin ; then else eval `echo args$i`="\"$arg\"" fi - i=$((i+1)) + i=`expr $i + 1` done case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi @@ -159,14 +175,9 @@ save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } -APP_ARGS=$(save "$@") +APP_ARGS=`save "$@"` # Collect all arguments for the java command, following the shell quoting and substitution rules eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" -# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong -if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then - cd "$(dirname "$0")" -fi - exec "$JAVACMD" "$@" diff --git a/src/SignalR/clients/java/signalr/gradlew.bat b/src/SignalR/clients/java/signalr/gradlew.bat index f9553162f1..9618d8d960 100644 --- a/src/SignalR/clients/java/signalr/gradlew.bat +++ b/src/SignalR/clients/java/signalr/gradlew.bat @@ -1,3 +1,19 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @@ -14,7 +30,7 @@ set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome diff --git a/src/SignalR/clients/java/signalr/settings.gradle b/src/SignalR/clients/java/signalr/settings.gradle index 44af7e9984..80beacb801 100644 --- a/src/SignalR/clients/java/signalr/settings.gradle +++ b/src/SignalR/clients/java/signalr/settings.gradle @@ -1,6 +1,2 @@ rootProject.name = 'signalr' include 'main' - -// This is required for Gradle 4.6+ to support importing BOMs, like we do for the Microsoft super pom. -// See here: https://docs.gradle.org/4.6/release-notes.html?_ga=2.220409368.162752831.1539212384-1601231980.1538950297#bom-import -enableFeaturePreview('IMPROVED_POM_SUPPORT') \ No newline at end of file diff --git a/src/SignalR/clients/java/signalr/signalr.client.java.Tests.javaproj b/src/SignalR/clients/java/signalr/signalr.client.java.Tests.javaproj new file mode 100644 index 0000000000..6a1851569e --- /dev/null +++ b/src/SignalR/clients/java/signalr/signalr.client.java.Tests.javaproj @@ -0,0 +1,91 @@ + + + + + + + java:signalr + + + true + + true + + + true + + + $(GradleOptions) -Dorg.gradle.daemon=false + $(OutputPath) + true + + + + + + + + + + + + + + $(PackDependsOn); + Build + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + $(GradleOptions) -PpackageVersion="$(PackageVersion)" + chmod +x ./gradlew && ./gradlew $(GradleOptions) test + call gradlew $(GradleOptions) test + + + + + + + + diff --git a/src/SignalR/clients/java/signalr/signalr.client.java.javaproj b/src/SignalR/clients/java/signalr/signalr.client.java.javaproj deleted file mode 100644 index 78bf2cc36b..0000000000 --- a/src/SignalR/clients/java/signalr/signalr.client.java.javaproj +++ /dev/null @@ -1,53 +0,0 @@ - - - - - true - true - - - $(GradleOptions) -Dorg.gradle.daemon=false - - - - - - - - - - - - - $(PackDependsOn); - Build - - - - - - - - - - - - - - - - - - - - - - - - - -PpackageVersion="$(PackageVersion)" - - diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java index 36dca4f808..5b04191f4c 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java @@ -328,6 +328,7 @@ public class HubConnection { handshakeResponseSubject = CompletableSubject.create(); handshakeReceived = false; CompletableSubject tokenCompletable = CompletableSubject.create(); + localHeaders.put(UserAgentHelper.getUserAgentName(), UserAgentHelper.createUserAgentString()); if (headers != null) { this.localHeaders.putAll(headers); } @@ -517,6 +518,11 @@ public class HubConnection { connectionState = null; } + if (pingTimer != null) { + pingTimer.cancel(); + pingTimer = null; + } + logger.info("HubConnection stopped."); hubConnectionState = HubConnectionState.DISCONNECTED; handshakeResponseSubject.onComplete(); diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/UserAgentHelper.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/UserAgentHelper.java new file mode 100644 index 0000000000..977797af5f --- /dev/null +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/UserAgentHelper.java @@ -0,0 +1,81 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +package com.microsoft.signalr; + +public class UserAgentHelper { + + private final static String USER_AGENT = "User-Agent"; + + public static String getUserAgentName() { + return USER_AGENT; + } + + public static String createUserAgentString() { + return constructUserAgentString(Version.getDetailedVersion(), getOS(), "Java", getJavaVersion(), getJavaVendor()); + } + + public static String constructUserAgentString(String detailedVersion, String os, String runtime, String runtimeVersion, String vendor) { + StringBuilder agentBuilder = new StringBuilder("Microsoft SignalR/"); + + agentBuilder.append(getVersion(detailedVersion)); + agentBuilder.append(" ("); + agentBuilder.append(detailedVersion); + + agentBuilder.append("; "); + if (!os.isEmpty()) { + agentBuilder.append(os); + } else { + agentBuilder.append("Unknown OS"); + } + + agentBuilder.append("; "); + agentBuilder.append(runtime); + + agentBuilder.append("; "); + if (!runtimeVersion.isEmpty()) { + agentBuilder.append(runtimeVersion); + } else { + agentBuilder.append("Unknown Runtime Version"); + } + + agentBuilder.append("; "); + if (!vendor.isEmpty()) { + agentBuilder.append(vendor); + } else { + agentBuilder.append("Unknown Vendor"); + } + + agentBuilder.append(")"); + + return agentBuilder.toString(); + } + + static String getVersion(String detailedVersion) { + // Getting the index of the second . so we can return just the major and minor version. + int shortVersionIndex = detailedVersion.indexOf(".", detailedVersion.indexOf(".") + 1); + return detailedVersion.substring(0, shortVersionIndex); + } + + static String getJavaVendor() { + return System.getProperty("java.vendor"); + } + + static String getJavaVersion() { + return System.getProperty("java.version"); + } + + static String getOS() { + String osName = System.getProperty("os.name").toLowerCase(); + + if (osName.indexOf("win") >= 0) { + return "Windows NT"; + } else if (osName.contains("mac") || osName.contains("darwin")) { + return "macOS"; + } else if (osName.contains("linux")) { + return "Linux"; + } else { + return osName; + } + } +} diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/Version.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/Version.java new file mode 100644 index 0000000000..4c73a498f6 --- /dev/null +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/Version.java @@ -0,0 +1,11 @@ + +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +package com.microsoft.signalr; + +class Version { + public static String getDetailedVersion() { + return "99.99.99-dev"; + } +} diff --git a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/HubConnectionTest.java b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/HubConnectionTest.java index 3f1d7d5b3f..52a6bf3487 100644 --- a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/HubConnectionTest.java +++ b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/HubConnectionTest.java @@ -1116,7 +1116,7 @@ class HubConnectionTest { hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); AtomicBoolean done = new AtomicBoolean(); - Single result = hubConnection.invoke(String.class, "fixedMessage", null); + Single result = hubConnection.invoke(String.class, "fixedMessage", (Object)null); result.doOnSuccess(value -> done.set(true)).subscribe(); assertEquals("{\"type\":1,\"invocationId\":\"1\",\"target\":\"fixedMessage\",\"arguments\":[null]}" + RECORD_SEPARATOR, mockTransport.getSentMessages()[1]); assertFalse(done.get()); @@ -2256,16 +2256,85 @@ class HubConnectionTest { hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); - TimeUnit.MILLISECONDS.sleep(100); - hubConnection.stop().timeout(1, TimeUnit.SECONDS).blockingAwait(); + String message = mockTransport.getNextSentMessage().timeout(1, TimeUnit.SECONDS).blockingGet(); + assertEquals("{\"type\":6}" + RECORD_SEPARATOR, message); + message = mockTransport.getNextSentMessage().timeout(1, TimeUnit.SECONDS).blockingGet(); + assertEquals("{\"type\":6}" + RECORD_SEPARATOR, message); - String[] sentMessages = mockTransport.getSentMessages(); - assertTrue(sentMessages.length > 1); - for (int i = 1; i < sentMessages.length; i++) { - assertEquals("{\"type\":6}" + RECORD_SEPARATOR, sentMessages[i]); - } + hubConnection.stop().timeout(1, TimeUnit.SECONDS).blockingAwait(); } + @Test + public void userAgentHeaderIsSet() { + AtomicReference header = new AtomicReference<>(); + TestHttpClient client = new TestHttpClient() + .on("POST", "http://example.com/negotiate?negotiateVersion=1", + (req) -> { + header.set(req.getHeaders().get("User-Agent")); + return Single.just(new HttpResponse(200, "", "{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" + + "availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}")); + }); + + MockTransport transport = new MockTransport(); + HubConnection hubConnection = HubConnectionBuilder.create("http://example.com") + .withTransportImplementation(transport) + .withHttpClient(client) + .build(); + + hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); + assertEquals(HubConnectionState.CONNECTED, hubConnection.getConnectionState()); + hubConnection.stop(); + + assertTrue(header.get().startsWith("Microsoft SignalR/")); + } + + @Test + public void userAgentHeaderCanBeOverwritten() { + AtomicReference header = new AtomicReference<>(); + TestHttpClient client = new TestHttpClient() + .on("POST", "http://example.com/negotiate?negotiateVersion=1", + (req) -> { + header.set(req.getHeaders().get("User-Agent")); + return Single.just(new HttpResponse(200, "", "{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" + + "availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}")); + }); + + MockTransport transport = new MockTransport(); + HubConnection hubConnection = HubConnectionBuilder.create("http://example.com") + .withTransportImplementation(transport) + .withHttpClient(client) + .withHeader("User-Agent", "Updated Value") + .build(); + + hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); + assertEquals(HubConnectionState.CONNECTED, hubConnection.getConnectionState()); + hubConnection.stop(); + assertEquals("Updated Value", header.get()); + } + + @Test + public void userAgentCanBeCleared() { + AtomicReference header = new AtomicReference<>(); + TestHttpClient client = new TestHttpClient() + .on("POST", "http://example.com/negotiate?negotiateVersion=1", + (req) -> { + header.set(req.getHeaders().get("User-Agent")); + return Single.just(new HttpResponse(200, "", "{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" + + "availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}")); + }); + + MockTransport transport = new MockTransport(); + HubConnection hubConnection = HubConnectionBuilder.create("http://example.com") + .withTransportImplementation(transport) + .withHttpClient(client) + .withHeader("User-Agent", "") + .build(); + + hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); + assertEquals(HubConnectionState.CONNECTED, hubConnection.getConnectionState()); + hubConnection.stop(); + assertEquals("", header.get()); + } @Test public void headersAreSetAndSentThroughBuilder() { AtomicReference header = new AtomicReference<>(); diff --git a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/MockTransport.java b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/MockTransport.java index 02e2224565..6b65067c62 100644 --- a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/MockTransport.java +++ b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/MockTransport.java @@ -7,6 +7,7 @@ import java.util.ArrayList; import io.reactivex.Completable; import io.reactivex.subjects.CompletableSubject; +import io.reactivex.subjects.SingleSubject; class MockTransport implements Transport { private OnReceiveCallBack onReceiveCallBack; @@ -17,6 +18,7 @@ class MockTransport implements Transport { final private boolean autoHandshake; final private CompletableSubject startSubject = CompletableSubject.create(); final private CompletableSubject stopSubject = CompletableSubject.create(); + private SingleSubject sendSubject = SingleSubject.create(); private static final String RECORD_SEPARATOR = "\u001e"; @@ -51,6 +53,8 @@ class MockTransport implements Transport { public Completable send(String message) { if (!(ignorePings && message.equals("{\"type\":6}" + RECORD_SEPARATOR))) { sentMessages.add(message); + sendSubject.onSuccess(message); + sendSubject = SingleSubject.create(); } return Completable.complete(); } @@ -89,6 +93,10 @@ class MockTransport implements Transport { return sentMessages.toArray(new String[sentMessages.size()]); } + public SingleSubject getNextSentMessage() { + return sendSubject; + } + public String getUrl() { return this.url; } diff --git a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/UserAgentTest.java b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/UserAgentTest.java new file mode 100644 index 0000000000..1a4256897c --- /dev/null +++ b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/UserAgentTest.java @@ -0,0 +1,67 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +package com.microsoft.signalr; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class UserAgentTest { + + private static Stream Versions() { + return Stream.of( + Arguments.of("1.0.0", "1.0"), + Arguments.of("3.1.4-preview9-12345", "3.1"), + Arguments.of("3.1.4-preview9-12345-extrastuff", "3.1"), + Arguments.of("99.99.99-dev", "99.99")); + } + + @ParameterizedTest + @MethodSource("Versions") + public void getVersionFromDetailedVersion(String detailedVersion, String version) { + assertEquals(version, UserAgentHelper.getVersion(detailedVersion)); + } + + @Test + public void verifyJavaVendor() { + assertEquals(System.getProperty("java.vendor"), UserAgentHelper.getJavaVendor()); + } + + @Test + public void verifyJavaVersion() { + assertEquals(System.getProperty("java.version"), UserAgentHelper.getJavaVersion()); + } + + @Test + public void checkUserAgentString() { + String userAgent = UserAgentHelper.createUserAgentString(); + assertNotNull(userAgent); + + String detailedVersion = Version.getDetailedVersion(); + String handMadeUserAgent = "Microsoft SignalR/" + UserAgentHelper.getVersion(detailedVersion) + + " (" + detailedVersion + "; " + UserAgentHelper.getOS() + "; Java; " + + UserAgentHelper.getJavaVersion() + "; " + UserAgentHelper.getJavaVendor() + ")"; + + assertEquals(handMadeUserAgent, userAgent); + } + + @ParameterizedTest + @MethodSource("UserAgents") + public void UserAgentHeaderIsCorrect(String detailedVersion, String os, String runtime, String runtimeVersion, String vendor, String expected) { + assertEquals(expected, UserAgentHelper.constructUserAgentString(detailedVersion, os, runtime, runtimeVersion, vendor)); + } + + private static Stream UserAgents() { + return Stream.of( + Arguments.of("1.4.5-dev", "Windows NT", "Java", "7.0.1", "Oracle", "Microsoft SignalR/1.4 (1.4.5-dev; Windows NT; Java; 7.0.1; Oracle)"), + Arguments.of("3.1.0", "", "Java", "7.0.1", "", "Microsoft SignalR/3.1 (3.1.0; Unknown OS; Java; 7.0.1; Unknown Vendor)"), + Arguments.of("5.0.2", "macOS", "Java", "", "Android", "Microsoft SignalR/5.0 (5.0.2; macOS; Java; Unknown Runtime Version; Android)")); + } +} diff --git a/src/SignalR/clients/ts/FunctionalTests/EchoConnectionHandler.cs b/src/SignalR/clients/ts/FunctionalTests/EchoConnectionHandler.cs index 646d328a5b..8dbdfd28f8 100644 --- a/src/SignalR/clients/ts/FunctionalTests/EchoConnectionHandler.cs +++ b/src/SignalR/clients/ts/FunctionalTests/EchoConnectionHandler.cs @@ -4,6 +4,7 @@ using System.Buffers; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; +using Microsoft.AspNetCore.Http.Connections; namespace FunctionalTests { @@ -11,6 +12,13 @@ namespace FunctionalTests { public async override Task OnConnectedAsync(ConnectionContext connection) { + var context = connection.GetHttpContext(); + // The 'withCredentials' tests wont send a cookie for cross-site requests + if (!context.WebSockets.IsWebSocketRequest && !context.Request.Cookies.ContainsKey("testCookie")) + { + return; + } + while (true) { var result = await connection.Transport.Input.ReadAsync(); diff --git a/src/SignalR/clients/ts/FunctionalTests/Program.cs b/src/SignalR/clients/ts/FunctionalTests/Program.cs index a762f99e55..2f67e3d5cd 100644 --- a/src/SignalR/clients/ts/FunctionalTests/Program.cs +++ b/src/SignalR/clients/ts/FunctionalTests/Program.cs @@ -30,9 +30,14 @@ namespace FunctionalTests var hostBuilder = new WebHostBuilder() .ConfigureLogging(factory => { - factory.AddConsole(options => options.IncludeScopes = true); + factory.AddConsole(options => + { + options.IncludeScopes = true; + options.TimestampFormat = "[HH:mm:ss] "; + options.UseUtcTimestamp = true; + }); factory.AddDebug(); - factory.SetMinimumLevel(LogLevel.Information); + factory.SetMinimumLevel(LogLevel.Debug); }) .UseKestrel((builderContext, options) => { diff --git a/src/SignalR/clients/ts/FunctionalTests/SignalR.Npm.FunctionalTests.npmproj b/src/SignalR/clients/ts/FunctionalTests/SignalR.Npm.FunctionalTests.npmproj index f90738b447..8f89229340 100644 --- a/src/SignalR/clients/ts/FunctionalTests/SignalR.Npm.FunctionalTests.npmproj +++ b/src/SignalR/clients/ts/FunctionalTests/SignalR.Npm.FunctionalTests.npmproj @@ -9,6 +9,7 @@ <_TestSauceArgs Condition="'$(BrowserTestHostName)' != ''">$(_TestSauceArgs) --use-hostname "$(BrowserTestHostName)" run test:inner --no-color --configuration $(Configuration) run build:inner + false @@ -18,7 +19,8 @@ - + + diff --git a/src/SignalR/clients/ts/FunctionalTests/Startup.cs b/src/SignalR/clients/ts/FunctionalTests/Startup.cs index a7ad5a3886..8a3ccfa767 100644 --- a/src/SignalR/clients/ts/FunctionalTests/Startup.cs +++ b/src/SignalR/clients/ts/FunctionalTests/Startup.cs @@ -18,6 +18,7 @@ using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.SignalR; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Primitives; using Microsoft.IdentityModel.Tokens; using Microsoft.Net.Http.Headers; @@ -104,7 +105,7 @@ namespace FunctionalTests }); } - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILogger logger) { if (env.IsDevelopment()) { @@ -120,6 +121,7 @@ namespace FunctionalTests var originHeader = context.Request.Headers[HeaderNames.Origin]; if (!StringValues.IsNullOrEmpty(originHeader)) { + logger.LogInformation("Setting CORS headers."); context.Response.Headers[HeaderNames.AccessControlAllowOrigin] = originHeader; context.Response.Headers[HeaderNames.AccessControlAllowCredentials] = "true"; @@ -136,8 +138,9 @@ namespace FunctionalTests } } - if (string.Equals(context.Request.Method, "OPTIONS", StringComparison.OrdinalIgnoreCase)) + if (HttpMethods.IsOptions(context.Request.Method)) { + logger.LogInformation("Setting '204' CORS response."); context.Response.StatusCode = StatusCodes.Status204NoContent; return Task.CompletedTask; } diff --git a/src/SignalR/clients/ts/FunctionalTests/TestHub.cs b/src/SignalR/clients/ts/FunctionalTests/TestHub.cs index 5bd4732c1a..04d88409a4 100644 --- a/src/SignalR/clients/ts/FunctionalTests/TestHub.cs +++ b/src/SignalR/clients/ts/FunctionalTests/TestHub.cs @@ -10,6 +10,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Http.Connections; using Microsoft.AspNetCore.Http.Connections.Features; using Microsoft.AspNetCore.SignalR; +using Microsoft.Net.Http.Headers; namespace FunctionalTests { @@ -136,6 +137,11 @@ namespace FunctionalTests return Context.GetHttpContext().Request.Headers["Content-Type"]; } + public string GetHeader(string headerName) + { + return Context.GetHttpContext().Request.Headers[headerName]; + } + public string GetCookie(string cookieName) { var cookies = Context.GetHttpContext().Request.Cookies; diff --git a/src/SignalR/clients/ts/FunctionalTests/package.json b/src/SignalR/clients/ts/FunctionalTests/package.json index 85b58b4e28..de0f565097 100644 --- a/src/SignalR/clients/ts/FunctionalTests/package.json +++ b/src/SignalR/clients/ts/FunctionalTests/package.json @@ -18,12 +18,12 @@ "es6-promise": "^4.2.4", "jasmine": "^3.2.0", "jasmine-core": "^3.2.1", - "karma": "^3.0.0", + "karma": "^4.4.1", "karma-chrome-launcher": "^2.2.0", "karma-edge-launcher": "^0.4.2", - "karma-firefox-launcher": "^1.1.0", + "karma-firefox-launcher": "^1.3.0", "karma-ie-launcher": "^1.0.0", - "karma-jasmine": "^1.1.2", + "karma-jasmine": "^3.1.0", "karma-junit-reporter": "^1.2.0", "karma-mocha-reporter": "^2.2.5", "karma-safari-launcher": "^1.0.0", @@ -31,8 +31,8 @@ "karma-sourcemap-loader": "^0.3.7", "karma-summary-reporter": "^1.6.0", "rxjs": "^6.3.3", - "ts-node": "^4.1.0", - "typescript": "^2.7.1", + "ts-node": "^8.6.2", + "typescript": "^3.7.5", "ws": " ^6.0.0" }, "scripts": { diff --git a/src/SignalR/clients/ts/FunctionalTests/scripts/karma.local.conf.js b/src/SignalR/clients/ts/FunctionalTests/scripts/karma.local.conf.js index 8837d36e88..aff36430d9 100644 --- a/src/SignalR/clients/ts/FunctionalTests/scripts/karma.local.conf.js +++ b/src/SignalR/clients/ts/FunctionalTests/scripts/karma.local.conf.js @@ -43,17 +43,17 @@ try { } // We use the launchers themselves to figure out if the browser exists. It's a bit sneaky, but it works. - tryAddBrowser("ChromeHeadlessNoSandbox", new ChromeHeadlessBrowser(() => { }, {})); - tryAddBrowser("ChromiumHeadlessIgnoreCert", new ChromiumHeadlessBrowser(() => { }, {})); - if (!tryAddBrowser("FirefoxHeadless", new FirefoxHeadlessBrowser(0, () => { }, {}))) { - tryAddBrowser("FirefoxDeveloperHeadless", new FirefoxDeveloperHeadlessBrowser(0, () => { }, {})); + tryAddBrowser("ChromeHeadlessNoSandbox", ChromeHeadlessBrowser.prototype); + tryAddBrowser("ChromiumHeadlessIgnoreCert", ChromiumHeadlessBrowser.prototype); + if (!tryAddBrowser("FirefoxHeadless", FirefoxHeadlessBrowser.prototype)) { + tryAddBrowser("FirefoxDeveloperHeadless", FirefoxDeveloperHeadlessBrowser.prototype); } // We need to receive an argument from the caller, but globals don't seem to work, so we use an environment variable. if (process.env.ASPNETCORE_SIGNALR_TEST_ALL_BROWSERS === "true") { - tryAddBrowser("Edge", new EdgeBrowser(() => { }, { create() { } })); - tryAddBrowser("IE", new IEBrowser(() => { }, { create() { } }, {})); - tryAddBrowser("Safari", new SafariBrowser(() => { }, {})); + tryAddBrowser("Edge", EdgeBrowser.prototype); + tryAddBrowser("IE", IEBrowser.prototype); + tryAddBrowser("Safari", SafariBrowser.prototype); } module.exports = createKarmaConfig({ diff --git a/src/SignalR/clients/ts/FunctionalTests/scripts/run-tests.ts b/src/SignalR/clients/ts/FunctionalTests/scripts/run-tests.ts index 5720a4e30e..ad6f65db08 100644 --- a/src/SignalR/clients/ts/FunctionalTests/scripts/run-tests.ts +++ b/src/SignalR/clients/ts/FunctionalTests/scripts/run-tests.ts @@ -245,7 +245,7 @@ function runJest(httpsUrl: string, httpUrl: string) { (async () => { try { - const serverPath = path.resolve(ARTIFACTS_DIR, "bin", "SignalR.Client.FunctionalTestApp", configuration, "netcoreapp3.1", "SignalR.Client.FunctionalTestApp.dll"); + const serverPath = path.resolve(ARTIFACTS_DIR, "bin", "SignalR.Client.FunctionalTestApp", configuration, "netcoreapp5.0", "SignalR.Client.FunctionalTestApp.dll"); debug(`Launching Functional Test Server: ${serverPath}`); let desiredServerUrl = "https://127.0.0.1:0;http://127.0.0.1:0"; diff --git a/src/SignalR/clients/ts/FunctionalTests/ts/Common.ts b/src/SignalR/clients/ts/FunctionalTests/ts/Common.ts index 2bb33c1c11..e7ed2cd668 100644 --- a/src/SignalR/clients/ts/FunctionalTests/ts/Common.ts +++ b/src/SignalR/clients/ts/FunctionalTests/ts/Common.ts @@ -1,8 +1,13 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -import { HttpTransportType, IHubProtocol, JsonHubProtocol } from "@microsoft/signalr"; +import { HttpClient, HttpTransportType, IHubProtocol, JsonHubProtocol } from "@microsoft/signalr"; import { MessagePackHubProtocol } from "@microsoft/signalr-protocol-msgpack"; +import { TestLogger } from "./TestLogger"; + +import { FetchHttpClient } from "@microsoft/signalr/dist/esm/FetchHttpClient"; +import { Platform } from "@microsoft/signalr/dist/esm/Utils"; +import { XhrHttpClient } from "@microsoft/signalr/dist/esm/XhrHttpClient"; // On slower CI machines, these tests sometimes take longer than 5s jasmine.DEFAULT_TIMEOUT_INTERVAL = 20 * 1000; @@ -97,6 +102,31 @@ export function eachTransportAndProtocol(action: (transport: HttpTransportType, }); } +export function eachTransportAndProtocolAndHttpClient(action: (transport: HttpTransportType, protocol: IHubProtocol, httpClient: HttpClient) => void) { + eachTransportAndProtocol((transport, protocol) => { + getHttpClients().forEach((httpClient) => { + action(transport, protocol, httpClient); + }); + }); +} + export function getGlobalObject(): any { return typeof window !== "undefined" ? window : global; } + +export function getHttpClients(): HttpClient[] { + const httpClients: HttpClient[] = []; + if (typeof XMLHttpRequest !== "undefined") { + httpClients.push(new XhrHttpClient(TestLogger.instance)); + } + if (typeof fetch !== "undefined" || Platform.isNode) { + httpClients.push(new FetchHttpClient(TestLogger.instance)); + } + return httpClients; +} + +export function eachHttpClient(action: (transport: HttpClient) => void) { + return getHttpClients().forEach((t) => { + return action(t); + }); +} diff --git a/src/SignalR/clients/ts/FunctionalTests/ts/ConnectionTests.ts b/src/SignalR/clients/ts/FunctionalTests/ts/ConnectionTests.ts index 7760294123..14c1091b49 100644 --- a/src/SignalR/clients/ts/FunctionalTests/ts/ConnectionTests.ts +++ b/src/SignalR/clients/ts/FunctionalTests/ts/ConnectionTests.ts @@ -5,11 +5,12 @@ // tslint:disable:no-floating-promises import { HttpTransportType, IHttpConnectionOptions, TransferFormat } from "@microsoft/signalr"; -import { eachTransport, ECHOENDPOINT_URL } from "./Common"; +import { eachHttpClient, eachTransport, ECHOENDPOINT_URL } from "./Common"; import { TestLogger } from "./TestLogger"; // We want to continue testing HttpConnection, but we don't export it anymore. So just pull it in directly from the source file. import { HttpConnection } from "@microsoft/signalr/dist/esm/HttpConnection"; +import { Platform } from "@microsoft/signalr/dist/esm/Utils"; import "./LogBannerReporter"; const commonOptions: IHttpConnectionOptions = { @@ -44,110 +45,150 @@ describe("connection", () => { }); eachTransport((transportType) => { - describe(`over ${HttpTransportType[transportType]}`, () => { - it("can send and receive messages", (done) => { - const message = "Hello World!"; - // the url should be resolved relative to the document.location.host - // and the leading '/' should be automatically added to the url - const connection = new HttpConnection(ECHOENDPOINT_URL, { - ...commonOptions, - transport: transportType, - }); + eachHttpClient((httpClient) => { + describe(`over ${HttpTransportType[transportType]} with ${(httpClient.constructor as any).name}`, () => { + it("can send and receive messages", (done) => { + const message = "Hello World!"; + // the url should be resolved relative to the document.location.host + // and the leading '/' should be automatically added to the url + const connection = new HttpConnection(ECHOENDPOINT_URL, { + ...commonOptions, + httpClient, + transport: transportType, + }); - connection.onreceive = (data: any) => { - if (data === message) { - connection.stop(); - } - }; - - connection.onclose = (error: any) => { - expect(error).toBeUndefined(); - done(); - }; - - connection.start(TransferFormat.Text).then(() => { - connection.send(message); - }).catch((e: any) => { - fail(e); - done(); - }); - }); - - it("does not log content of messages sent or received by default", (done) => { - TestLogger.saveLogsAndReset(); - const message = "Hello World!"; - - // DON'T use commonOptions because we want to specifically test the scenario where logMessageContent is not set. - const connection = new HttpConnection(ECHOENDPOINT_URL, { - logger: TestLogger.instance, - transport: transportType, - }); - - connection.onreceive = (data: any) => { - if (data === message) { - connection.stop(); - } - }; - - // @ts-ignore: We don't use the error parameter intentionally. - connection.onclose = (error) => { - // Search the logs for the message content - expect(TestLogger.instance.currentLog.messages.length).toBeGreaterThan(0); - // @ts-ignore: We don't use the _ or __ parameters intentionally. - for (const [_, __, logMessage] of TestLogger.instance.currentLog.messages) { - expect(logMessage).not.toContain(message); - } - done(); - }; - - connection.start(TransferFormat.Text).then(() => { - connection.send(message); - }).catch((e) => { - fail(e); - done(); - }); - }); - - it("does log content of messages sent or received when enabled", (done) => { - TestLogger.saveLogsAndReset(); - const message = "Hello World!"; - - // DON'T use commonOptions because we want to specifically test the scenario where logMessageContent is set to true (even if commonOptions changes). - const connection = new HttpConnection(ECHOENDPOINT_URL, { - logMessageContent: true, - logger: TestLogger.instance, - transport: transportType, - }); - - connection.onreceive = (data: any) => { - if (data === message) { - connection.stop(); - } - }; - - // @ts-ignore: We don't use the error parameter intentionally. - connection.onclose = (error) => { - // Search the logs for the message content - let matches = 0; - expect(TestLogger.instance.currentLog.messages.length).toBeGreaterThan(0); - // @ts-ignore: We don't use the _ or __ parameters intentionally. - for (const [_, __, logMessage] of TestLogger.instance.currentLog.messages) { - if (logMessage.indexOf(message) !== -1) { - matches += 1; + connection.onreceive = (data: any) => { + if (data === message) { + connection.stop(); } - } + }; - // One match for send, one for receive. - expect(matches).toEqual(2); - done(); - }; + connection.onclose = (error: any) => { + expect(error).toBeUndefined(); + done(); + }; - connection.start(TransferFormat.Text).then(() => { - connection.send(message); - }).catch((e: any) => { - fail(e); - done(); + connection.start(TransferFormat.Text).then(() => { + connection.send(message); + }).catch((e: any) => { + fail(e); + done(); + }); }); + + it("does not log content of messages sent or received by default", (done) => { + TestLogger.saveLogsAndReset(); + const message = "Hello World!"; + + // DON'T use commonOptions because we want to specifically test the scenario where logMessageContent is not set. + const connection = new HttpConnection(ECHOENDPOINT_URL, { + httpClient, + logger: TestLogger.instance, + transport: transportType, + }); + + connection.onreceive = (data: any) => { + if (data === message) { + connection.stop(); + } + }; + + // @ts-ignore: We don't use the error parameter intentionally. + connection.onclose = (error) => { + // Search the logs for the message content + expect(TestLogger.instance.currentLog.messages.length).toBeGreaterThan(0); + // @ts-ignore: We don't use the _ or __ parameters intentionally. + for (const [_, __, logMessage] of TestLogger.instance.currentLog.messages) { + expect(logMessage).not.toContain(message); + } + done(); + }; + + connection.start(TransferFormat.Text).then(() => { + connection.send(message); + }).catch((e) => { + fail(e); + done(); + }); + }); + + it("does log content of messages sent or received when enabled", (done) => { + TestLogger.saveLogsAndReset(); + const message = "Hello World!"; + + // DON'T use commonOptions because we want to specifically test the scenario where logMessageContent is set to true (even if commonOptions changes). + const connection = new HttpConnection(ECHOENDPOINT_URL, { + httpClient, + logMessageContent: true, + logger: TestLogger.instance, + transport: transportType, + }); + + connection.onreceive = (data: any) => { + if (data === message) { + connection.stop(); + } + }; + + // @ts-ignore: We don't use the error parameter intentionally. + connection.onclose = (error) => { + // Search the logs for the message content + let matches = 0; + expect(TestLogger.instance.currentLog.messages.length).toBeGreaterThan(0); + // @ts-ignore: We don't use the _ or __ parameters intentionally. + for (const [_, __, logMessage] of TestLogger.instance.currentLog.messages) { + if (logMessage.indexOf(message) !== -1) { + matches += 1; + } + } + + // One match for send, one for receive. + expect(matches).toEqual(2); + done(); + }; + + connection.start(TransferFormat.Text).then(() => { + connection.send(message); + }).catch((e: any) => { + fail(e); + done(); + }); + }); + + // withCredentials doesn't make sense in Node or when using WebSockets + if (!Platform.isNode && transportType !== HttpTransportType.WebSockets && + // tests run through karma during automation which is cross-site, but manually running the server will result in these tests failing + // so we check for cross-site + !(window && ECHOENDPOINT_URL.match(`^${window.location.href}`))) { + it("honors withCredentials flag", (done) => { + TestLogger.saveLogsAndReset(); + const message = "Hello World!"; + + // The server will set some response headers for the '/negotiate' endpoint + const connection = new HttpConnection(ECHOENDPOINT_URL, { + ...commonOptions, + httpClient, + transport: transportType, + withCredentials: false, + }); + + connection.onreceive = (data: any) => { + fail(new Error(`Unexpected messaged received '${data}'.`)); + }; + + // @ts-ignore: We don't use the error parameter intentionally. + connection.onclose = (error) => { + done(); + }; + + connection.start(TransferFormat.Text).then(() => { + connection.send(message); + }).catch((e: any) => { + fail(e); + done(); + }); + }); + } }); }); }); diff --git a/src/SignalR/clients/ts/FunctionalTests/ts/HubConnectionTests.ts b/src/SignalR/clients/ts/FunctionalTests/ts/HubConnectionTests.ts index 3d5f434a17..93275f5336 100644 --- a/src/SignalR/clients/ts/FunctionalTests/ts/HubConnectionTests.ts +++ b/src/SignalR/clients/ts/FunctionalTests/ts/HubConnectionTests.ts @@ -6,8 +6,9 @@ import { AbortError, DefaultHttpClient, HttpClient, HttpRequest, HttpResponse, HttpTransportType, HubConnectionBuilder, IHttpConnectionOptions, JsonHubProtocol, NullLogger } from "@microsoft/signalr"; import { MessagePackHubProtocol } from "@microsoft/signalr-protocol-msgpack"; +import { getUserAgentHeader, Platform } from "@microsoft/signalr/dist/esm/Utils"; -import { eachTransport, eachTransportAndProtocol, ENDPOINT_BASE_HTTPS_URL, ENDPOINT_BASE_URL } from "./Common"; +import { eachTransport, eachTransportAndProtocolAndHttpClient, ENDPOINT_BASE_HTTPS_URL, ENDPOINT_BASE_URL } from "./Common"; import "./LogBannerReporter"; import { TestLogger } from "./TestLogger"; @@ -49,12 +50,12 @@ function getConnectionBuilder(transportType?: HttpTransportType, url?: string, o } describe("hubConnection", () => { - eachTransportAndProtocol((transportType, protocol) => { + eachTransportAndProtocolAndHttpClient((transportType, protocol, httpClient) => { describe("using " + protocol.name + " over " + HttpTransportType[transportType] + " transport", () => { it("can invoke server method and receive result", (done) => { const message = "你好,世界!"; - const hubConnection = getConnectionBuilder(transportType) + const hubConnection = getConnectionBuilder(transportType, undefined, { httpClient }) .withHubProtocol(protocol) .build(); @@ -81,7 +82,7 @@ describe("hubConnection", () => { it("using https, can invoke server method and receive result", (done) => { const message = "你好,世界!"; - const hubConnection = getConnectionBuilder(transportType, TESTHUBENDPOINT_HTTPS_URL) + const hubConnection = getConnectionBuilder(transportType, TESTHUBENDPOINT_HTTPS_URL, { httpClient }) .withHubProtocol(protocol) .build(); @@ -108,7 +109,7 @@ describe("hubConnection", () => { it("can invoke server method non-blocking and not receive result", (done) => { const message = "你好,世界!"; - const hubConnection = getConnectionBuilder(transportType) + const hubConnection = getConnectionBuilder(transportType, undefined, { httpClient }) .withHubProtocol(protocol) .build(); @@ -130,7 +131,7 @@ describe("hubConnection", () => { }); it("can invoke server method structural object and receive structural result", (done) => { - const hubConnection = getConnectionBuilder(transportType) + const hubConnection = getConnectionBuilder(transportType, undefined, { httpClient }) .withHubProtocol(protocol) .build(); @@ -154,7 +155,7 @@ describe("hubConnection", () => { }); it("can stream server method and receive result", (done) => { - const hubConnection = getConnectionBuilder(transportType) + const hubConnection = getConnectionBuilder(transportType, undefined, { httpClient }) .withHubProtocol(protocol) .build(); @@ -185,7 +186,7 @@ describe("hubConnection", () => { }); it("can stream server method and cancel stream", (done) => { - const hubConnection = getConnectionBuilder(transportType) + const hubConnection = getConnectionBuilder(transportType, undefined, { httpClient }) .withHubProtocol(protocol) .build(); @@ -219,7 +220,7 @@ describe("hubConnection", () => { it("rethrows an exception from the server when invoking", (done) => { const errorMessage = "An unexpected error occurred invoking 'ThrowException' on the server. InvalidOperationException: An error occurred."; - const hubConnection = getConnectionBuilder(transportType) + const hubConnection = getConnectionBuilder(transportType, undefined, { httpClient }) .withHubProtocol(protocol) .build(); @@ -241,7 +242,7 @@ describe("hubConnection", () => { }); it("throws an exception when invoking streaming method with invoke", (done) => { - const hubConnection = getConnectionBuilder(transportType) + const hubConnection = getConnectionBuilder(transportType, undefined, { httpClient }) .withHubProtocol(protocol) .build(); @@ -263,7 +264,7 @@ describe("hubConnection", () => { }); it("throws an exception when receiving a streaming result for method called with invoke", (done) => { - const hubConnection = getConnectionBuilder(transportType) + const hubConnection = getConnectionBuilder(transportType, undefined, { httpClient }) .withHubProtocol(protocol) .build(); @@ -286,7 +287,7 @@ describe("hubConnection", () => { it("rethrows an exception from the server when streaming", (done) => { const errorMessage = "An unexpected error occurred invoking 'StreamThrowException' on the server. InvalidOperationException: An error occurred."; - const hubConnection = getConnectionBuilder(transportType) + const hubConnection = getConnectionBuilder(transportType, undefined, { httpClient }) .withHubProtocol(protocol) .build(); @@ -313,7 +314,7 @@ describe("hubConnection", () => { }); it("throws an exception when invoking hub method with stream", (done) => { - const hubConnection = getConnectionBuilder(transportType) + const hubConnection = getConnectionBuilder(transportType, undefined, { httpClient }) .withHubProtocol(protocol) .build(); @@ -340,7 +341,7 @@ describe("hubConnection", () => { }); it("can receive server calls", (done) => { - const hubConnection = getConnectionBuilder(transportType) + const hubConnection = getConnectionBuilder(transportType, undefined, { httpClient }) .withHubProtocol(protocol) .build(); @@ -370,7 +371,7 @@ describe("hubConnection", () => { }); it("can receive server calls without rebinding handler when restarted", (done) => { - const hubConnection = getConnectionBuilder(transportType) + const hubConnection = getConnectionBuilder(transportType, undefined, { httpClient }) .withHubProtocol(protocol) .build(); @@ -425,7 +426,7 @@ describe("hubConnection", () => { }); it("closed with error or start fails if hub cannot be created", async (done) => { - const hubConnection = getConnectionBuilder(transportType, ENDPOINT_BASE_URL + "/uncreatable") + const hubConnection = getConnectionBuilder(transportType, ENDPOINT_BASE_URL + "/uncreatable", { httpClient }) .withHubProtocol(protocol) .build(); @@ -446,7 +447,7 @@ describe("hubConnection", () => { }); it("can handle different types", (done) => { - const hubConnection = getConnectionBuilder(transportType) + const hubConnection = getConnectionBuilder(transportType, undefined, { httpClient }) .withHubProtocol(protocol) .build(); @@ -489,7 +490,7 @@ describe("hubConnection", () => { }); it("can receive different types", (done) => { - const hubConnection = getConnectionBuilder(transportType) + const hubConnection = getConnectionBuilder(transportType, undefined, { httpClient }) .withHubProtocol(protocol) .build(); @@ -534,7 +535,7 @@ describe("hubConnection", () => { it("can be restarted", (done) => { const message = "你好,世界!"; - const hubConnection = getConnectionBuilder(transportType) + const hubConnection = getConnectionBuilder(transportType, undefined, { httpClient }) .withHubProtocol(protocol) .build(); @@ -577,7 +578,7 @@ describe("hubConnection", () => { }); it("can stream from client to server with rxjs", async (done) => { - const hubConnection = getConnectionBuilder(transportType) + const hubConnection = getConnectionBuilder(transportType, undefined, { httpClient }) .withHubProtocol(protocol) .build(); @@ -594,7 +595,7 @@ describe("hubConnection", () => { }); it("can stream from client to server and close with error with rxjs", async (done) => { - const hubConnection = getConnectionBuilder(transportType) + const hubConnection = getConnectionBuilder(transportType, undefined, { httpClient }) .withHubProtocol(protocol) .build(); @@ -678,7 +679,6 @@ describe("hubConnection", () => { if (transportType !== HttpTransportType.LongPolling) { it("terminates if no messages received within timeout interval", async (done) => { const hubConnection = getConnectionBuilder(transportType).build(); - hubConnection.serverTimeoutInMilliseconds = 100; hubConnection.onclose((error) => { expect(error).toEqual(new Error("Server timeout elapsed without receiving a message from the server.")); @@ -686,6 +686,12 @@ describe("hubConnection", () => { }); await hubConnection.start(); + + // set this after start completes to avoid network issues with the handshake taking over 100ms and causing a failure + hubConnection.serverTimeoutInMilliseconds = 1; + + // invoke a method with a response to reset the timeout using the new value + await hubConnection.invoke("Echo", ""); }); } @@ -1092,6 +1098,33 @@ describe("hubConnection", () => { } }); + eachTransport((t) => { + it("sets the user agent header", async (done) => { + const hubConnection = getConnectionBuilder(t, TESTHUBENDPOINT_URL) + .withHubProtocol(new JsonHubProtocol()) + .build(); + + try { + await hubConnection.start(); + + // Check to see that the Content-Type header is set the expected value + const [name, value] = getUserAgentHeader(); + const headerValue = await hubConnection.invoke("GetHeader", name); + + if ((t === HttpTransportType.ServerSentEvents || t === HttpTransportType.WebSockets) && !Platform.isNode) { + expect(headerValue).toBeNull(); + } else { + expect(headerValue).toEqual(value); + } + + await hubConnection.stop(); + done(); + } catch (e) { + fail(e); + } + }); + }); + function getJwtToken(url: string): Promise { return new Promise((resolve, reject) => { const httpClient = new DefaultHttpClient(NullLogger.instance); diff --git a/src/SignalR/clients/ts/FunctionalTests/webpack.config.js b/src/SignalR/clients/ts/FunctionalTests/webpack.config.js index 7e82e91b9b..a1f307e072 100644 --- a/src/SignalR/clients/ts/FunctionalTests/webpack.config.js +++ b/src/SignalR/clients/ts/FunctionalTests/webpack.config.js @@ -37,6 +37,5 @@ module.exports = { externals: { "@microsoft/signalr": "signalR", "@microsoft/signalr-protocol-msgpack": "signalR.protocols.msgpack", - "request": "request", }, }; \ No newline at end of file diff --git a/src/SignalR/clients/ts/FunctionalTests/yarn.lock b/src/SignalR/clients/ts/FunctionalTests/yarn.lock index 8fb80c73b9..48220dea8b 100644 --- a/src/SignalR/clients/ts/FunctionalTests/yarn.lock +++ b/src/SignalR/clients/ts/FunctionalTests/yarn.lock @@ -43,20 +43,12 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-9.6.49.tgz#ab4df6e505db088882c8ce5417ae0bc8cbb7a8a6" integrity sha512-YY0Okyn4QXC4ugJI+Kng5iWjK8A6eIHiQVaGIhJkyn0YL6Iqo0E0tBC8BuhvYcBK87vykBijM5FtMnCqaa5anA== -"@types/strip-bom@^3.0.0": +abort-controller@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/@types/strip-bom/-/strip-bom-3.0.0.tgz#14a8ec3956c2e81edb7520790aecf21c290aebd2" - integrity sha1-FKjsOVbC6B7bdSB5CuzyHCkK69I= - -"@types/strip-json-comments@0.0.30": - version "0.0.30" - resolved "https://registry.yarnpkg.com/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz#9aa30c04db212a9a0649d6ae6fd50accc40748a1" - integrity sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ== - -abbrev@1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" - integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== + resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" + integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== + dependencies: + event-target-shim "^5.0.0" accepts@~1.3.4: version "1.3.7" @@ -93,11 +85,6 @@ ajv@^6.5.5: json-schema-traverse "^0.4.1" uri-js "^4.2.2" -ansi-regex@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" - integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= - ansi-regex@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" @@ -110,18 +97,13 @@ ansi-styles@^3.2.1: dependencies: color-convert "^1.9.0" -anymatch@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" - integrity sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw== +anymatch@~3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.1.tgz#c55ecf02185e2469259399310c173ce31233b142" + integrity sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg== dependencies: - micromatch "^3.1.4" - normalize-path "^2.1.1" - -aproba@^1.0.3: - version "1.2.0" - resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" - integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== + normalize-path "^3.0.0" + picomatch "^2.0.4" archiver-utils@^1.3.0: version "1.3.0" @@ -149,54 +131,16 @@ archiver@2.1.1: tar-stream "^1.5.0" zip-stream "^1.2.0" -are-we-there-yet@~1.1.2: - version "1.1.5" - resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21" - integrity sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w== - dependencies: - delegates "^1.0.0" - readable-stream "^2.0.6" - -arr-diff@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" - integrity sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA= - -arr-flatten@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" - integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg== - -arr-union@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" - integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= - -array-slice@^0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/array-slice/-/array-slice-0.2.3.tgz#dd3cfb80ed7973a75117cdac69b0b99ec86186f5" - integrity sha1-3Tz7gO15c6dRF82sabC5nshhhvU= - -array-unique@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.2.1.tgz#a1d97ccafcbc2625cc70fadceb36a50c58b01a53" - integrity sha1-odl8yvy8JiXMcPrc6zalDFiwGlM= - -array-unique@^0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" - integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= +arg@^4.1.0: + version "4.1.2" + resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.2.tgz#e70c90579e02c63d80e3ad4e31d8bfdb8bd50064" + integrity sha512-+ytCkGcBtHZ3V2r2Z06AncYO8jz46UEamcspGoU8lHcEbpn6J77QK0vdWvChsclg/tM5XIJC5tnjmPp7Eq6Obg== arraybuffer.slice@~0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz#3bbc4275dd584cc1b10809b89d4e8b63a69e7675" integrity sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog== -arrify@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" - integrity sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0= - asn1@~0.2.3: version "0.2.4" resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" @@ -209,16 +153,6 @@ assert-plus@1.0.0, assert-plus@^1.0.0: resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= -assign-symbols@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" - integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= - -async-each@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf" - integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ== - async-limiter@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8" @@ -231,7 +165,7 @@ async@2.0.1: dependencies: lodash "^4.8.0" -async@^2.0.0, async@^2.1.2: +async@^2.0.0, async@^2.1.2, async@^2.6.2: version "2.6.3" resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff" integrity sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg== @@ -243,11 +177,6 @@ asynckit@^0.4.0: resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= -atob@^2.1.1: - version "2.1.2" - resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" - integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== - aws-sign2@~0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" @@ -283,19 +212,6 @@ base64id@1.0.0: resolved "https://registry.yarnpkg.com/base64id/-/base64id-1.0.0.tgz#47688cb99bb6804f0e06d3e763b1c32e57d8e6b6" integrity sha1-R2iMuZu2gE8OBtPnY7HDLlfY5rY= -base@^0.11.1: - version "0.11.2" - resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" - integrity sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg== - dependencies: - cache-base "^1.0.1" - class-utils "^0.3.5" - component-emitter "^1.2.1" - define-property "^1.0.0" - isobject "^3.0.1" - mixin-deep "^1.2.0" - pascalcase "^0.1.1" - bcrypt-pbkdf@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" @@ -310,10 +226,10 @@ better-assert@~1.0.0: dependencies: callsite "1.0.0" -binary-extensions@^1.0.0: - version "1.13.1" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65" - integrity sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw== +binary-extensions@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.0.0.tgz#23c0df14f6a88077f5f986c0d167ec03c3d5537c" + integrity sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow== bl@^1.0.0: version "1.2.2" @@ -365,28 +281,12 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" -braces@^0.1.2: - version "0.1.5" - resolved "https://registry.yarnpkg.com/braces/-/braces-0.1.5.tgz#c085711085291d8b75fdd74eab0f8597280711e6" - integrity sha1-wIVxEIUpHYt1/ddOqw+FlygHEeY= +braces@^3.0.2, braces@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== dependencies: - expand-range "^0.1.0" - -braces@^2.3.1, braces@^2.3.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" - integrity sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w== - dependencies: - arr-flatten "^1.1.0" - array-unique "^0.3.2" - extend-shallow "^2.0.1" - fill-range "^4.0.0" - isobject "^3.0.1" - repeat-element "^1.1.2" - snapdragon "^0.8.1" - snapdragon-node "^2.0.1" - split-string "^3.0.2" - to-regex "^3.0.1" + fill-range "^7.0.1" buffer-alloc-unsafe@^1.1.0: version "1.1.0" @@ -429,21 +329,6 @@ bytes@3.1.0: resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg== -cache-base@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" - integrity sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ== - dependencies: - collection-visit "^1.0.0" - component-emitter "^1.2.1" - get-value "^2.0.6" - has-value "^1.0.0" - isobject "^3.0.1" - set-value "^2.0.0" - to-object-path "^0.3.0" - union-value "^1.0.0" - unset-value "^1.0.0" - callsite@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/callsite/-/callsite-1.0.0.tgz#280398e5d664bd74038b6f0905153e6e8af1bc20" @@ -454,7 +339,7 @@ caseless@~0.12.0: resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= -"chalk@^1.1.3 || 2.x", chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0: +"chalk@^1.1.3 || 2.x", chalk@^2.0.1, chalk@^2.1.0: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -463,57 +348,20 @@ caseless@~0.12.0: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chokidar@^2.0.3: - version "2.1.6" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.6.tgz#b6cad653a929e244ce8a834244164d241fa954c5" - integrity sha512-V2jUo67OKkc6ySiRpJrjlpJKl9kDuG+Xb8VgsGzb+aEouhgS1D0weyPU4lEzdAcsCAvrih2J2BqyXqHWvVLw5g== +chokidar@^3.0.0: + version "3.3.1" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.3.1.tgz#c84e5b3d18d9a4d77558fef466b1bf16bbeb3450" + integrity sha512-4QYCEWOcK3OJrxwvyyAOxFuhpvOVCYkr33LPfFNBjAD/w3sEzWsp2BUOkI4l9bHvWioAd0rc6NlHUOEaWkTeqg== dependencies: - anymatch "^2.0.0" - async-each "^1.0.1" - braces "^2.3.2" - glob-parent "^3.1.0" - inherits "^2.0.3" - is-binary-path "^1.0.0" - is-glob "^4.0.0" - normalize-path "^3.0.0" - path-is-absolute "^1.0.0" - readdirp "^2.2.1" - upath "^1.1.1" + anymatch "~3.1.1" + braces "~3.0.2" + glob-parent "~5.1.0" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.3.0" optionalDependencies: - fsevents "^1.2.7" - -chownr@^1.1.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.2.tgz#a18f1e0b269c8a6a5d3c86eb298beb14c3dd7bf6" - integrity sha512-GkfeAQh+QNy3wquu9oIZr6SS5x7wGdSgNQvD10X3r+AZr1Oys22HW8kAmDMvNg2+Dm0TeGaEuO8gFwdBXxwO8A== - -circular-json@^0.5.5: - version "0.5.9" - resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.5.9.tgz#932763ae88f4f7dead7a0d09c8a51a4743a53b1d" - integrity sha512-4ivwqHpIFJZBuhN3g/pEcdbnGUywkBblloGbkglyloVjjR3uT6tieI89MVOfbP2tHX5sgb01FuLgAOzebNlJNQ== - -class-utils@^0.3.5: - version "0.3.6" - resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" - integrity sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg== - dependencies: - arr-union "^3.1.0" - define-property "^0.2.5" - isobject "^3.0.0" - static-extend "^0.1.1" - -code-point-at@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" - integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= - -collection-visit@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" - integrity sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA= - dependencies: - map-visit "^1.0.0" - object-visit "^1.0.0" + fsevents "~2.1.2" color-convert@^1.9.0: version "1.9.3" @@ -532,13 +380,6 @@ colors@^1.1.0: resolved "https://registry.yarnpkg.com/colors/-/colors-1.3.3.tgz#39e005d546afe01e01f9c4ca8fa50f686a01205d" integrity sha512-mmGt/1pZqYRjMxB1axhTo16/snVZ5krrKkcmMeVKxzECMMXoCgnvTPp10QgHfcbQZw8Dq2jMNG6je4JlWU0gWg== -combine-lists@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/combine-lists/-/combine-lists-1.0.1.tgz#458c07e09e0d900fc28b70a3fec2dacd1d2cb7f6" - integrity sha1-RYwH4J4NkA/Ci3Cj/sLazR0st/Y= - dependencies: - lodash "^4.5.0" - combined-stream@^1.0.6, combined-stream@~1.0.6: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" @@ -556,11 +397,6 @@ component-emitter@1.2.1: resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6" integrity sha1-E3kY1teCg/ffemt8WmPhQOaUJeY= -component-emitter@^1.2.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" - integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== - component-inherit@0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/component-inherit/-/component-inherit-0.0.3.tgz#645fc4adf58b72b649d5cae65135619db26ff143" @@ -591,11 +427,6 @@ connect@^3.6.0: parseurl "~1.3.3" utils-merge "1.0.1" -console-control-strings@^1.0.0, console-control-strings@~1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" - integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= - content-type@~1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" @@ -606,16 +437,6 @@ cookie@0.3.1: resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" integrity sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s= -copy-descriptor@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" - integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= - -core-js@^2.2.0: - version "2.6.9" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.9.tgz#6b4b214620c834152e179323727fc19741b084f2" - integrity sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A== - core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" @@ -648,12 +469,12 @@ dashdash@^1.12.0: dependencies: assert-plus "^1.0.0" -date-format@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/date-format/-/date-format-1.2.0.tgz#615e828e233dd1ab9bb9ae0950e0ceccfa6ecad8" - integrity sha1-YV6CjiM90aubua4JUODOzPpuytg= +date-format@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/date-format/-/date-format-2.1.0.tgz#31d5b5ea211cf5fd764cd38baf9d033df7e125cf" + integrity sha512-bYQuGLeFxhkxNOF3rcMtiZxvCBAquGzZm6oWA1oZ0g2THUzivaRhv8uOhdr19LmoobSOLoIAxeUK2RdbM8IFTA== -debug@2.6.9, debug@^2.2.0, debug@^2.3.3: +debug@2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== @@ -667,6 +488,13 @@ debug@^3.1.0, debug@^3.2.6: dependencies: ms "^2.1.1" +debug@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" + integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== + dependencies: + ms "^2.1.1" + debug@~3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" @@ -674,67 +502,25 @@ debug@~3.1.0: dependencies: ms "2.0.0" -decode-uri-component@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" - integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= - -deep-extend@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" - integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== - -define-property@^0.2.5: - version "0.2.5" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" - integrity sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY= - dependencies: - is-descriptor "^0.1.0" - -define-property@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6" - integrity sha1-dp66rz9KY6rTr56NMEybvnm/sOY= - dependencies: - is-descriptor "^1.0.0" - -define-property@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-2.0.2.tgz#d459689e8d654ba77e02a817f8710d702cb16e9d" - integrity sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ== - dependencies: - is-descriptor "^1.0.2" - isobject "^3.0.1" - delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= -delegates@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" - integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= - depd@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= -detect-libc@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" - integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= - di@^0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/di/-/di-0.0.1.tgz#806649326ceaa7caa3306d75d985ea2748ba913c" integrity sha1-gGZJMmzqp8qjMG112YXqJ0i6kTw= -diff@^3.1.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" - integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== +diff@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== dom-serialize@^2.2.0: version "2.2.1" @@ -821,6 +607,11 @@ ent@~2.2.0: resolved "https://registry.yarnpkg.com/ent/-/ent-2.2.0.tgz#e964219325a21d05f44466a2f686ed6ce5f5dd1d" integrity sha1-6WQhkyWiHQX0RGai9obtbOX13R0= +es6-denodeify@^0.1.1: + version "0.1.5" + resolved "https://registry.yarnpkg.com/es6-denodeify/-/es6-denodeify-0.1.5.tgz#31d4d5fe9c5503e125460439310e16a2a3f39c1f" + integrity sha1-MdTV/pxVA+ElRgQ5MQ4WoqPznB8= + es6-promise@^4.0.3, es6-promise@^4.2.4: version "4.2.8" resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a" @@ -843,6 +634,11 @@ escape-string-regexp@^1.0.5: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= +event-target-shim@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" + integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== + eventemitter3@^3.0.0: version "3.1.2" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.2.tgz#2d3d48f9c346698fce83a85d7d664e98535df6e7" @@ -855,70 +651,11 @@ eventsource@^1.0.7: dependencies: original "^1.0.0" -expand-braces@^0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/expand-braces/-/expand-braces-0.1.2.tgz#488b1d1d2451cb3d3a6b192cfc030f44c5855fea" - integrity sha1-SIsdHSRRyz06axks/AMPRMWFX+o= - dependencies: - array-slice "^0.2.3" - array-unique "^0.2.1" - braces "^0.1.2" - -expand-brackets@^2.1.4: - version "2.1.4" - resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" - integrity sha1-t3c14xXOMPa27/D4OwQVGiJEliI= - dependencies: - debug "^2.3.3" - define-property "^0.2.5" - extend-shallow "^2.0.1" - posix-character-classes "^0.1.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - -expand-range@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/expand-range/-/expand-range-0.1.1.tgz#4cb8eda0993ca56fa4f41fc42f3cbb4ccadff044" - integrity sha1-TLjtoJk8pW+k9B/ELzy7TMrf8EQ= - dependencies: - is-number "^0.1.1" - repeat-string "^0.2.2" - -extend-shallow@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" - integrity sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8= - dependencies: - is-extendable "^0.1.0" - -extend-shallow@^3.0.0, extend-shallow@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8" - integrity sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg= - dependencies: - assign-symbols "^1.0.0" - is-extendable "^1.0.1" - extend@^3.0.0, extend@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== -extglob@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" - integrity sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw== - dependencies: - array-unique "^0.3.2" - define-property "^1.0.0" - expand-brackets "^2.1.4" - extend-shallow "^2.0.1" - fragment-cache "^0.2.1" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - extsprintf@1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" @@ -939,15 +676,20 @@ fast-json-stable-stringify@^2.0.0: resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" integrity sha1-1RQsDK7msRifh9OnYREGT4bIu/I= -fill-range@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" - integrity sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc= +fetch-cookie@^0.7.3: + version "0.7.3" + resolved "https://registry.yarnpkg.com/fetch-cookie/-/fetch-cookie-0.7.3.tgz#b8d023f421dd2b2f4a0eca9cd7318a967ed4eed8" + integrity sha512-rZPkLnI8x5V+zYAiz8QonAHsTb4BY+iFowFBI1RFn0zrO343AVp9X7/yUj/9wL6Ef/8fLls8b/vGtzUvmyAUGA== dependencies: - extend-shallow "^2.0.1" - is-number "^3.0.0" - repeat-string "^1.6.1" - to-regex-range "^2.1.0" + es6-denodeify "^0.1.1" + tough-cookie "^2.3.3" + +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" finalhandler@1.1.2: version "1.1.2" @@ -974,11 +716,6 @@ follow-redirects@^1.0.0: dependencies: debug "^3.2.6" -for-in@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" - integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA= - forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" @@ -993,13 +730,6 @@ form-data@~2.3.2: combined-stream "^1.0.6" mime-types "^2.1.12" -fragment-cache@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" - integrity sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk= - dependencies: - map-cache "^0.2.2" - fs-access@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/fs-access/-/fs-access-1.0.1.tgz#d6a87f262271cefebec30c553407fb995da8777a" @@ -1012,44 +742,24 @@ fs-constants@^1.0.0: resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== -fs-minipass@^1.2.5: - version "1.2.6" - resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.6.tgz#2c5cc30ded81282bfe8a0d7c7c1853ddeb102c07" - integrity sha512-crhvyXcMejjv3Z5d2Fa9sf5xLYVCF5O1c71QxbVnbLsmYMBEvDAftewesN/HhY03YRoA7zOMxjNGrF5svGaaeQ== +fs-extra@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9" + integrity sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw== dependencies: - minipass "^2.2.1" + graceful-fs "^4.1.2" + jsonfile "^4.0.0" + universalify "^0.1.0" fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= -fsevents@^1.2.7: - version "1.2.9" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.9.tgz#3f5ed66583ccd6f400b5a00db6f7e861363e388f" - integrity sha512-oeyj2H3EjjonWcFjD5NvZNE9Rqe4UW+nQBU2HNeKw0koVLEFIhtyETyAakeAM3de7Z/SW5kcA+fZUait9EApnw== - dependencies: - nan "^2.12.1" - node-pre-gyp "^0.12.0" - -gauge@~2.7.3: - version "2.7.4" - resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" - integrity sha1-LANAXHU4w51+s3sxcCLjJfsBi/c= - dependencies: - aproba "^1.0.3" - console-control-strings "^1.0.0" - has-unicode "^2.0.0" - object-assign "^4.1.0" - signal-exit "^3.0.0" - string-width "^1.0.1" - strip-ansi "^3.0.1" - wide-align "^1.1.0" - -get-value@^2.0.3, get-value@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" - integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg= +fsevents@~2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.2.tgz#4c0a1fb34bc68e543b4b82a9ec392bfbda840805" + integrity sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA== getpass@^0.1.1: version "0.1.7" @@ -1058,13 +768,12 @@ getpass@^0.1.1: dependencies: assert-plus "^1.0.0" -glob-parent@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" - integrity sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4= +glob-parent@~5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.0.tgz#5f4c1d1e748d30cd73ad2944b3577a81b081e8c2" + integrity sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw== dependencies: - is-glob "^3.1.0" - path-dirname "^1.0.0" + is-glob "^4.0.1" glob@^7.0.0, glob@^7.1.1, glob@^7.1.3: version "7.1.4" @@ -1078,11 +787,16 @@ glob@^7.0.0, glob@^7.1.1, glob@^7.1.3: once "^1.3.0" path-is-absolute "^1.0.0" -graceful-fs@^4.1.0, graceful-fs@^4.1.11, graceful-fs@^4.1.2: +graceful-fs@^4.1.0, graceful-fs@^4.1.2: version "4.2.0" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.0.tgz#8d8fdc73977cb04104721cb53666c1ca64cd328b" integrity sha512-jpSvDPV4Cq/bgtpndIWbI5hmYxhQGHPC4d4cqBPb4DLniCfhJokdXhwhaDuLBGLQdvvRum/UiX6ECVIPvDXqdg== +graceful-fs@^4.1.6: + version "4.2.3" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423" + integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ== + har-schema@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" @@ -1113,49 +827,6 @@ has-flag@^3.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= -has-unicode@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" - integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= - -has-value@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" - integrity sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8= - dependencies: - get-value "^2.0.3" - has-values "^0.1.4" - isobject "^2.0.0" - -has-value@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177" - integrity sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc= - dependencies: - get-value "^2.0.6" - has-values "^1.0.0" - isobject "^3.0.0" - -has-values@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771" - integrity sha1-bWHeldkd/Km5oCCJrThL/49it3E= - -has-values@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f" - integrity sha1-lbC2P+whRmGab+V/51Yo1aOe/k8= - dependencies: - is-number "^3.0.0" - kind-of "^4.0.0" - -homedir-polyfill@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz#743298cef4e5af3e194161fbadcc2151d3a058e8" - integrity sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA== - dependencies: - parse-passwd "^1.0.0" - http-errors@1.7.2: version "1.7.2" resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f" @@ -1193,7 +864,7 @@ https-proxy-agent@^2.2.1: agent-base "^4.3.0" debug "^3.1.0" -iconv-lite@0.4.24, iconv-lite@^0.4.4: +iconv-lite@0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== @@ -1205,13 +876,6 @@ ieee754@^1.1.4: resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg== -ignore-walk@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.1.tgz#a83e62e7d272ac0e3b551aaa82831a19b69f82f8" - integrity sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ== - dependencies: - minimatch "^3.0.4" - indexof@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/indexof/-/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d" @@ -1235,151 +899,50 @@ inherits@2.0.3: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= -ini@~1.3.0: - version "1.3.5" - resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" - integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== - -is-accessor-descriptor@^0.1.6: - version "0.1.6" - resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" - integrity sha1-qeEss66Nh2cn7u84Q/igiXtcmNY= +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== dependencies: - kind-of "^3.0.2" + binary-extensions "^2.0.0" -is-accessor-descriptor@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz#169c2f6d3df1f992618072365c9b0ea1f6878656" - integrity sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ== - dependencies: - kind-of "^6.0.0" - -is-binary-path@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898" - integrity sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg= - dependencies: - binary-extensions "^1.0.0" - -is-buffer@^1.1.5: - version "1.1.6" - resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" - integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== - -is-data-descriptor@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" - integrity sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y= - dependencies: - kind-of "^3.0.2" - -is-data-descriptor@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz#d84876321d0e7add03990406abbbbd36ba9268c7" - integrity sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ== - dependencies: - kind-of "^6.0.0" - -is-descriptor@^0.1.0: - version "0.1.6" - resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" - integrity sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg== - dependencies: - is-accessor-descriptor "^0.1.6" - is-data-descriptor "^0.1.4" - kind-of "^5.0.0" - -is-descriptor@^1.0.0, is-descriptor@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec" - integrity sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg== - dependencies: - is-accessor-descriptor "^1.0.0" - is-data-descriptor "^1.0.0" - kind-of "^6.0.2" - -is-extendable@^0.1.0, is-extendable@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" - integrity sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik= - -is-extendable@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4" - integrity sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA== - dependencies: - is-plain-object "^2.0.4" - -is-extglob@^2.1.0, is-extglob@^2.1.1: +is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= -is-fullwidth-code-point@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" - integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs= - dependencies: - number-is-nan "^1.0.0" - -is-fullwidth-code-point@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" - integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= - -is-glob@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a" - integrity sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo= - dependencies: - is-extglob "^2.1.0" - -is-glob@^4.0.0: +is-glob@^4.0.1, is-glob@~4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== dependencies: is-extglob "^2.1.1" -is-number@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-0.1.1.tgz#69a7af116963d47206ec9bd9b48a14216f1e3806" - integrity sha1-aaevEWlj1HIG7JvZtIoUIW8eOAY= - -is-number@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" - integrity sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU= - dependencies: - kind-of "^3.0.2" - -is-plain-object@^2.0.3, is-plain-object@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" - integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== - dependencies: - isobject "^3.0.1" +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== is-typedarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= -is-windows@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" - integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== - -isarray@1.0.0, isarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= +is-wsl@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.1.1.tgz#4a1c152d429df3d441669498e2486d3596ebaf1d" + integrity sha512-umZHcSrwlDHo2TGMXv0DZ8dIUGunZ2Iv68YZnrmCiBPkZ4aaOhtv7pXJKeki9k3qJ3RJr0cDyitcl5wEH3AYog== isarray@2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.1.tgz#a37d94ed9cda2d59865c9f76fe596ee1f338741e" integrity sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4= +isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= + isbinaryfile@^3.0.0: version "3.0.3" resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-3.0.3.tgz#5d6def3edebf6e8ca8cae9c30183a804b5f8be80" @@ -1392,18 +955,6 @@ isexe@^2.0.0: resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= -isobject@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" - integrity sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk= - dependencies: - isarray "1.0.0" - -isobject@^3.0.0, isobject@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" - integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= - isstream@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" @@ -1414,6 +965,11 @@ jasmine-core@^3.2.1, jasmine-core@~3.4.0: resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-3.4.0.tgz#2a74618e966026530c3518f03e9f845d26473ce3" integrity sha512-HU/YxV4i6GcmiH4duATwAbJQMlE0MsDIR5XmSVxURxKHn3aGAdbY1/ZJFmVRbKtnLwIxxMJD7gYaPsypcbYimg== +jasmine-core@^3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-3.5.0.tgz#132c23e645af96d85c8bca13c8758b18429fc1e4" + integrity sha512-nCeAiw37MIMA9w9IXso7bRaLl+c/ef3wnxsoSAlYrzS+Ot0zTG6nU8G/cIfGkqpkjX2wNaIW9RFG0TwIFnG6bA== + jasmine@^3.2.0: version "3.4.0" resolved "https://registry.yarnpkg.com/jasmine/-/jasmine-3.4.0.tgz#0fa68903ff0c9697459cd044b44f4dcef5ec8bdc" @@ -1442,6 +998,13 @@ json-stringify-safe@~5.0.1: resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= +jsonfile@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" + integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss= + optionalDependencies: + graceful-fs "^4.1.6" + jsprim@^1.2.2: version "1.4.1" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" @@ -1467,10 +1030,12 @@ karma-edge-launcher@^0.4.2: dependencies: edge-launcher "1.2.2" -karma-firefox-launcher@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/karma-firefox-launcher/-/karma-firefox-launcher-1.1.0.tgz#2c47030452f04531eb7d13d4fc7669630bb93339" - integrity sha512-LbZ5/XlIXLeQ3cqnCbYLn+rOVhuMIK9aZwlP6eOLGzWdo1UVp7t6CN3DP4SafiRLjexKwHeKHDm0c38Mtd3VxA== +karma-firefox-launcher@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/karma-firefox-launcher/-/karma-firefox-launcher-1.3.0.tgz#ebcbb1d1ddfada6be900eb8fae25bcf2dcdc8171" + integrity sha512-Fi7xPhwrRgr+94BnHX0F5dCl1miIW4RHnzjIGxF8GaIEp7rNqX7LSi7ok63VXs3PS/5MQaQMhGxw+bvD+pibBQ== + dependencies: + is-wsl "^2.1.0" karma-ie-launcher@^1.0.0: version "1.0.0" @@ -1479,10 +1044,12 @@ karma-ie-launcher@^1.0.0: dependencies: lodash "^4.6.1" -karma-jasmine@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/karma-jasmine/-/karma-jasmine-1.1.2.tgz#394f2b25ffb4a644b9ada6f22d443e2fd08886c3" - integrity sha1-OU8rJf+0pkS5rabyLUQ+L9CIhsM= +karma-jasmine@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/karma-jasmine/-/karma-jasmine-3.1.0.tgz#e234e2a50bcf4d040c79b8b1826465f783590245" + integrity sha512-IVGbC8gap5x5NNCEOsAE77ic8rZtHDt6wmO0fFC5yT5FeB8qKnGTeud2mtKyQ41xl7vZkZ7ZxKr4wMGR6tWN+A== + dependencies: + jasmine-core "^3.5.0" karma-junit-reporter@^1.2.0: version "1.2.0" @@ -1530,28 +1097,26 @@ karma-summary-reporter@^1.6.0: dependencies: chalk "^1.1.3 || 2.x" -karma@^3.0.0: - version "3.1.4" - resolved "https://registry.yarnpkg.com/karma/-/karma-3.1.4.tgz#3890ca9722b10d1d14b726e1335931455788499e" - integrity sha512-31Vo8Qr5glN+dZEVIpnPCxEGleqE0EY6CtC2X9TagRV3rRQ3SNrvfhddICkJgUK3AgqpeKSZau03QumTGhGoSw== +karma@^4.4.1: + version "4.4.1" + resolved "https://registry.yarnpkg.com/karma/-/karma-4.4.1.tgz#6d9aaab037a31136dc074002620ee11e8c2e32ab" + integrity sha512-L5SIaXEYqzrh6b1wqYC42tNsFMx2PWuxky84pK9coK09MvmL7mxii3G3bZBh/0rvD27lqDd0le9jyhzvwif73A== dependencies: bluebird "^3.3.0" body-parser "^1.16.1" - chokidar "^2.0.3" + braces "^3.0.2" + chokidar "^3.0.0" colors "^1.1.0" - combine-lists "^1.0.0" connect "^3.6.0" - core-js "^2.2.0" di "^0.0.1" dom-serialize "^2.2.0" - expand-braces "^0.1.1" flatted "^2.0.0" glob "^7.1.1" graceful-fs "^4.1.2" http-proxy "^1.13.0" isbinaryfile "^3.0.0" - lodash "^4.17.5" - log4js "^3.0.0" + lodash "^4.17.14" + log4js "^4.0.0" mime "^2.3.1" minimatch "^3.0.2" optimist "^0.6.1" @@ -1564,30 +1129,6 @@ karma@^3.0.0: tmp "0.0.33" useragent "2.3.0" -kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: - version "3.2.2" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" - integrity sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ= - dependencies: - is-buffer "^1.1.5" - -kind-of@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" - integrity sha1-IIE989cSkosgc3hpGkUGb65y3Vc= - dependencies: - is-buffer "^1.1.5" - -kind-of@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" - integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw== - -kind-of@^6.0.0, kind-of@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051" - integrity sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA== - lazystream@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/lazystream/-/lazystream-1.0.0.tgz#f6995fe0f820392f61396be89462407bb77168e4" @@ -1595,7 +1136,7 @@ lazystream@^1.0.0: dependencies: readable-stream "^2.0.5" -lodash@4.17.11, lodash@>=4.7.14, lodash@^4.16.6, lodash@^4.17.14, lodash@^4.17.5, lodash@^4.5.0, lodash@^4.6.1, lodash@^4.8.0: +lodash@4.17.11, lodash@>=4.7.14, lodash@^4.16.6, lodash@^4.17.14, lodash@^4.6.1, lodash@^4.8.0: version "4.17.14" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.14.tgz#9ce487ae66c96254fe20b599f21b6816028078ba" integrity sha512-mmKYbW3GLuJeX+iGP+Y7Gp1AiGHGbXHCOh/jZmrawMmsE7MS4znI3RL2FsjbqOyMayHInjOeykW7PEajUk1/xw== @@ -1607,16 +1148,16 @@ log-symbols@^2.1.0: dependencies: chalk "^2.0.1" -log4js@^3.0.0: - version "3.0.6" - resolved "https://registry.yarnpkg.com/log4js/-/log4js-3.0.6.tgz#e6caced94967eeeb9ce399f9f8682a4b2b28c8ff" - integrity sha512-ezXZk6oPJCWL483zj64pNkMuY/NcRX5MPiB0zE6tjZM137aeusrOnW1ecxgF9cmwMWkBMhjteQxBPoZBh9FDxQ== +log4js@^4.0.0: + version "4.5.1" + resolved "https://registry.yarnpkg.com/log4js/-/log4js-4.5.1.tgz#e543625e97d9e6f3e6e7c9fc196dd6ab2cae30b5" + integrity sha512-EEEgFcE9bLgaYUKuozyFfytQM2wDHtXn4tAN41pkaxpNjAykv11GVdeI4tHtmPWW4Xrgh9R/2d7XYghDVjbKKw== dependencies: - circular-json "^0.5.5" - date-format "^1.2.0" - debug "^3.1.0" - rfdc "^1.1.2" - streamroller "0.7.0" + date-format "^2.0.0" + debug "^4.1.1" + flatted "^2.0.0" + rfdc "^1.1.4" + streamroller "^1.0.6" lru-cache@4.1.x: version "4.1.5" @@ -1631,42 +1172,11 @@ make-error@^1.1.1: resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.5.tgz#efe4e81f6db28cadd605c70f29c831b58ef776c8" integrity sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g== -map-cache@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" - integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8= - -map-visit@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" - integrity sha1-7Nyo8TFE5mDxtb1B8S80edmN+48= - dependencies: - object-visit "^1.0.0" - media-typer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= -micromatch@^3.1.10, micromatch@^3.1.4: - version "3.1.10" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" - integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== - dependencies: - arr-diff "^4.0.0" - array-unique "^0.3.2" - braces "^2.3.1" - define-property "^2.0.2" - extend-shallow "^3.0.2" - extglob "^2.0.4" - fragment-cache "^0.2.1" - kind-of "^6.0.2" - nanomatch "^1.2.9" - object.pick "^1.3.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.2" - mime-db@1.40.0: version "1.40.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.40.0.tgz#a65057e998db090f732a68f6c276d387d4126c32" @@ -1696,40 +1206,12 @@ minimist@0.0.8: resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= -minimist@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" - integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= - minimist@~0.0.1: version "0.0.10" resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf" integrity sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8= -minipass@^2.2.1, minipass@^2.3.5: - version "2.3.5" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.5.tgz#cacebe492022497f656b0f0f51e2682a9ed2d848" - integrity sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA== - dependencies: - safe-buffer "^5.1.2" - yallist "^3.0.0" - -minizlib@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.2.1.tgz#dd27ea6136243c7c880684e8672bb3a45fd9b614" - integrity sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA== - dependencies: - minipass "^2.2.1" - -mixin-deep@^1.2.0: - version "1.3.2" - resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566" - integrity sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA== - dependencies: - for-in "^1.0.2" - is-extendable "^1.0.1" - -mkdirp@^0.5.0, mkdirp@^0.5.1: +mkdirp@^0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= @@ -1756,149 +1238,43 @@ msgpack5@^4.0.2: readable-stream "^2.3.6" safe-buffer "^5.1.2" -nan@^2.12.1: - version "2.14.0" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" - integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg== - -nanomatch@^1.2.9: - version "1.2.13" - resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" - integrity sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA== - dependencies: - arr-diff "^4.0.0" - array-unique "^0.3.2" - define-property "^2.0.2" - extend-shallow "^3.0.2" - fragment-cache "^0.2.1" - is-windows "^1.0.2" - kind-of "^6.0.2" - object.pick "^1.3.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - -needle@^2.2.1: - version "2.4.0" - resolved "https://registry.yarnpkg.com/needle/-/needle-2.4.0.tgz#6833e74975c444642590e15a750288c5f939b57c" - integrity sha512-4Hnwzr3mi5L97hMYeNl8wRW/Onhy4nUKR/lVemJ8gJedxxUyBLm9kkrDColJvoSfwi0jCNhD+xCdOtiGDQiRZg== - dependencies: - debug "^3.2.6" - iconv-lite "^0.4.4" - sax "^1.2.4" - negotiator@0.6.2: version "0.6.2" resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== -node-pre-gyp@^0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.12.0.tgz#39ba4bb1439da030295f899e3b520b7785766149" - integrity sha512-4KghwV8vH5k+g2ylT+sLTjy5wmUOb9vPhnM8NHvRf9dHmnW/CndrFXy2aRPaPST6dugXSdHXfeaHQm77PIz/1A== - dependencies: - detect-libc "^1.0.2" - mkdirp "^0.5.1" - needle "^2.2.1" - nopt "^4.0.1" - npm-packlist "^1.1.6" - npmlog "^4.0.2" - rc "^1.2.7" - rimraf "^2.6.1" - semver "^5.3.0" - tar "^4" +node-fetch@^2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd" + integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA== -nopt@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d" - integrity sha1-0NRoWv1UFRk8jHUFYC0NF81kR00= - dependencies: - abbrev "1" - osenv "^0.1.4" - -normalize-path@^2.0.0, normalize-path@^2.1.1: +normalize-path@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" integrity sha1-GrKLVW4Zg2Oowab35vogE3/mrtk= dependencies: remove-trailing-separator "^1.0.1" -normalize-path@^3.0.0: +normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== -npm-bundled@^1.0.1: - version "1.0.6" - resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.0.6.tgz#e7ba9aadcef962bb61248f91721cd932b3fe6bdd" - integrity sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g== - -npm-packlist@^1.1.6: - version "1.4.4" - resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.4.4.tgz#866224233850ac534b63d1a6e76050092b5d2f44" - integrity sha512-zTLo8UcVYtDU3gdeaFu2Xu0n0EvelfHDGuqtNIn5RO7yQj4H1TqNdBc/yZjxnWA0PVB8D3Woyp0i5B43JwQ6Vw== - dependencies: - ignore-walk "^3.0.1" - npm-bundled "^1.0.1" - -npmlog@^4.0.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" - integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg== - dependencies: - are-we-there-yet "~1.1.2" - console-control-strings "~1.1.0" - gauge "~2.7.3" - set-blocking "~2.0.0" - null-check@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/null-check/-/null-check-1.0.0.tgz#977dffd7176012b9ec30d2a39db5cf72a0439edd" integrity sha1-l33/1xdgErnsMNKjnbXPcqBDnt0= -number-is-nan@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" - integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= - oauth-sign@~0.9.0: version "0.9.0" resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== -object-assign@^4.1.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" - integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= - object-component@0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/object-component/-/object-component-0.0.3.tgz#f0c69aa50efc95b866c186f400a33769cb2f1291" integrity sha1-8MaapQ78lbhmwYb0AKM3acsvEpE= -object-copy@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" - integrity sha1-fn2Fi3gb18mRpBupde04EnVOmYw= - dependencies: - copy-descriptor "^0.1.0" - define-property "^0.2.5" - kind-of "^3.0.3" - -object-visit@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" - integrity sha1-95xEk68MU3e1n+OdOV5BBC3QRbs= - dependencies: - isobject "^3.0.0" - -object.pick@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" - integrity sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c= - dependencies: - isobject "^3.0.1" - on-finished@~2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" @@ -1928,29 +1304,11 @@ original@^1.0.0: dependencies: url-parse "^1.4.3" -os-homedir@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" - integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M= - -os-tmpdir@^1.0.0, os-tmpdir@~1.0.2: +os-tmpdir@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= -osenv@^0.1.4: - version "0.1.5" - resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410" - integrity sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g== - dependencies: - os-homedir "^1.0.0" - os-tmpdir "^1.0.0" - -parse-passwd@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6" - integrity sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY= - parseqs@0.0.5: version "0.0.5" resolved "https://registry.yarnpkg.com/parseqs/-/parseqs-0.0.5.tgz#d5208a3738e46766e291ba2ea173684921a8b89d" @@ -1970,16 +1328,6 @@ parseurl@~1.3.3: resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== -pascalcase@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" - integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= - -path-dirname@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0" - integrity sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA= - path-is-absolute@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" @@ -1990,10 +1338,10 @@ performance-now@^2.1.0: resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= -posix-character-classes@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" - integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= +picomatch@^2.0.4, picomatch@^2.0.7: + version "2.2.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.1.tgz#21bac888b6ed8601f831ce7816e335bc779f0a4a" + integrity sha512-ISBaA8xQNmwELC7eOjqFKMESB2VIqt4PPDD0nsS95b/9dZXvVKOlz9keMSnoGGKcOHXfTvDD6WMaRoSc9UuhRA== process-nextick-args@~2.0.0: version "2.0.1" @@ -2010,12 +1358,17 @@ psl@^1.1.24: resolved "https://registry.yarnpkg.com/psl/-/psl-1.2.0.tgz#df12b5b1b3a30f51c329eacbdef98f3a6e136dc6" integrity sha512-GEn74ZffufCmkDDLNcl3uuyF/aSD6exEyh1v/ZSdAomB82t6G9hzJVRx0jBmLDW+VfZqks3aScmMw9DszwUalA== +psl@^1.1.28: + version "1.7.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.7.0.tgz#f1c4c47a8ef97167dea5d6bbf4816d736e884a3c" + integrity sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ== + punycode@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= -punycode@^2.1.0: +punycode@^2.1.0, punycode@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== @@ -2065,17 +1418,7 @@ raw-body@2.4.0: iconv-lite "0.4.24" unpipe "1.0.0" -rc@^1.2.7: - version "1.2.8" - resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" - integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== - dependencies: - deep-extend "^0.6.0" - ini "~1.3.0" - minimist "^1.2.0" - strip-json-comments "~2.0.1" - -readable-stream@^2.0.0, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.3.0, readable-stream@^2.3.5, readable-stream@^2.3.6: +readable-stream@^2.0.0, readable-stream@^2.0.5, readable-stream@^2.3.0, readable-stream@^2.3.5, readable-stream@^2.3.6: version "2.3.6" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw== @@ -2088,44 +1431,19 @@ readable-stream@^2.0.0, readable-stream@^2.0.2, readable-stream@^2.0.5, readable string_decoder "~1.1.1" util-deprecate "~1.0.1" -readdirp@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525" - integrity sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ== +readdirp@~3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.3.0.tgz#984458d13a1e42e2e9f5841b129e162f369aff17" + integrity sha512-zz0pAkSPOXXm1viEwygWIPSPkcBYjW1xU5j/JBh5t9bGCJwa6f9+BJa6VaB2g+b55yVrmXzqkyLf4xaWYM0IkQ== dependencies: - graceful-fs "^4.1.11" - micromatch "^3.1.10" - readable-stream "^2.0.2" - -regex-not@^1.0.0, regex-not@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" - integrity sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A== - dependencies: - extend-shallow "^3.0.2" - safe-regex "^1.1.0" + picomatch "^2.0.7" remove-trailing-separator@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8= -repeat-element@^1.1.2: - version "1.1.3" - resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce" - integrity sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g== - -repeat-string@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-0.2.2.tgz#c7a8d3236068362059a7e4651fc6884e8b1fb4ae" - integrity sha1-x6jTI2BoNiBZp+RlH8aITosftK4= - -repeat-string@^1.6.1: - version "1.6.1" - resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" - integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= - -request@2.88.0, request@^2.88.0: +request@2.88.0: version "2.88.0" resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg== @@ -2156,22 +1474,12 @@ requires-port@^1.0.0: resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8= -resolve-url@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" - integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= - -ret@~0.1.10: - version "0.1.15" - resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" - integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== - -rfdc@^1.1.2: +rfdc@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.1.4.tgz#ba72cc1367a0ccd9cf81a870b3b58bd3ad07f8c2" integrity sha512-5C9HXdzK8EAqN7JDif30jqsBzavB7wLpaubisuQIGHWf2gUXSpzy6ArX/+Da8RjFpagWsCn+pIgxTMAmKw9Zug== -rimraf@^2.5.4, rimraf@^2.6.0, rimraf@^2.6.1: +rimraf@^2.5.4, rimraf@^2.6.0: version "2.6.3" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== @@ -2195,13 +1503,6 @@ safe-buffer@~5.1.0, safe-buffer@~5.1.1: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -safe-regex@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" - integrity sha1-QKNmnzsHfR6UPURinhV91IAjvy4= - dependencies: - ret "~0.1.10" - "safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" @@ -2225,71 +1526,11 @@ saucelabs@^1.4.0: dependencies: https-proxy-agent "^2.2.1" -sax@^1.2.4: - version "1.2.4" - resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" - integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== - -semver@^5.3.0: - version "5.7.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.0.tgz#790a7cf6fea5459bac96110b29b60412dc8ff96b" - integrity sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA== - -set-blocking@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" - integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= - -set-value@^2.0.0, set-value@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b" - integrity sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw== - dependencies: - extend-shallow "^2.0.1" - is-extendable "^0.1.1" - is-plain-object "^2.0.3" - split-string "^3.0.1" - setprototypeof@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw== -signal-exit@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" - integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0= - -snapdragon-node@^2.0.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" - integrity sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw== - dependencies: - define-property "^1.0.0" - isobject "^3.0.0" - snapdragon-util "^3.0.1" - -snapdragon-util@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz#f956479486f2acd79700693f6f7b805e45ab56e2" - integrity sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ== - dependencies: - kind-of "^3.2.0" - -snapdragon@^0.8.1: - version "0.8.2" - resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.2.tgz#64922e7c565b0e14204ba1aa7d6964278d25182d" - integrity sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg== - dependencies: - base "^0.11.1" - debug "^2.2.0" - define-property "^0.2.5" - extend-shallow "^2.0.1" - map-cache "^0.2.2" - source-map "^0.5.6" - source-map-resolve "^0.5.0" - use "^3.1.0" - socket.io-adapter@~1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-1.1.1.tgz#2a805e8a14d6372124dd9159ad4502f8cb07f06b" @@ -2336,47 +1577,19 @@ socket.io@2.1.1: socket.io-client "2.1.1" socket.io-parser "~3.2.0" -source-map-resolve@^0.5.0: - version "0.5.2" - resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.2.tgz#72e2cc34095543e43b2c62b2c4c10d4a9054f259" - integrity sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA== - dependencies: - atob "^2.1.1" - decode-uri-component "^0.2.0" - resolve-url "^0.2.1" - source-map-url "^0.4.0" - urix "^0.1.0" - -source-map-support@^0.5.0: - version "0.5.12" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.12.tgz#b4f3b10d51857a5af0138d3ce8003b201613d599" - integrity sha512-4h2Pbvyy15EE02G+JOZpUCmqWJuqrs+sEkzewTm++BPi7Hvn/HwcqLAcNxYAyI0x13CpPPn+kMjl+hplXMHITQ== +source-map-support@^0.5.6: + version "0.5.16" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.16.tgz#0ae069e7fe3ba7538c64c98515e35339eac5a042" + integrity sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ== dependencies: buffer-from "^1.0.0" source-map "^0.6.0" -source-map-url@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" - integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM= - -source-map@^0.5.6: - version "0.5.7" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" - integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= - source-map@^0.6.0, source-map@^0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== -split-string@^3.0.1, split-string@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" - integrity sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw== - dependencies: - extend-shallow "^3.0.0" - sshpk@^1.7.0: version "1.16.1" resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" @@ -2392,45 +1605,21 @@ sshpk@^1.7.0: safer-buffer "^2.0.2" tweetnacl "~0.14.0" -static-extend@^0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" - integrity sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY= - dependencies: - define-property "^0.2.5" - object-copy "^0.1.0" - "statuses@>= 1.5.0 < 2", statuses@~1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= -streamroller@0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/streamroller/-/streamroller-0.7.0.tgz#a1d1b7cf83d39afb0d63049a5acbf93493bdf64b" - integrity sha512-WREzfy0r0zUqp3lGO096wRuUp7ho1X6uo/7DJfTlEi0Iv/4gT7YHqXDjKC2ioVGBZtE8QzsQD9nx1nIuoZ57jQ== +streamroller@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/streamroller/-/streamroller-1.0.6.tgz#8167d8496ed9f19f05ee4b158d9611321b8cacd9" + integrity sha512-3QC47Mhv3/aZNFpDDVO44qQb9gwB9QggMEE0sQmkTAwBVYdBRWISdsywlkfm5II1Q5y/pmrHflti/IgmIzdDBg== dependencies: - date-format "^1.2.0" - debug "^3.1.0" - mkdirp "^0.5.1" - readable-stream "^2.3.0" - -string-width@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" - integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M= - dependencies: - code-point-at "^1.0.0" - is-fullwidth-code-point "^1.0.0" - strip-ansi "^3.0.0" - -"string-width@^1.0.2 || 2": - version "2.1.1" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" - integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== - dependencies: - is-fullwidth-code-point "^2.0.0" - strip-ansi "^4.0.0" + async "^2.6.2" + date-format "^2.0.0" + debug "^3.2.6" + fs-extra "^7.0.1" + lodash "^4.17.14" string_decoder@~1.1.1: version "1.1.1" @@ -2439,13 +1628,6 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -strip-ansi@^3.0.0, strip-ansi@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" - integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= - dependencies: - ansi-regex "^2.0.0" - strip-ansi@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" @@ -2453,16 +1635,6 @@ strip-ansi@^4.0.0: dependencies: ansi-regex "^3.0.0" -strip-bom@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" - integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= - -strip-json-comments@^2.0.0, strip-json-comments@~2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" - integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= - supports-color@^5.3.0: version "5.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" @@ -2483,19 +1655,6 @@ tar-stream@^1.5.0: to-buffer "^1.1.1" xtend "^4.0.0" -tar@^4: - version "4.4.10" - resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.10.tgz#946b2810b9a5e0b26140cf78bea6b0b0d689eba1" - integrity sha512-g2SVs5QIxvo6OLp0GudTqEf05maawKUxXru104iaayWA09551tFCTI8f1Asb4lPfkBr91k07iL4c11XO3/b0tA== - dependencies: - chownr "^1.1.1" - fs-minipass "^1.2.5" - minipass "^2.3.5" - minizlib "^1.2.1" - mkdirp "^0.5.0" - safe-buffer "^5.1.2" - yallist "^3.0.3" - tmp@0.0.33, tmp@0.0.x: version "0.0.33" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" @@ -2513,36 +1672,26 @@ to-buffer@^1.1.1: resolved "https://registry.yarnpkg.com/to-buffer/-/to-buffer-1.1.1.tgz#493bd48f62d7c43fcded313a03dcadb2e1213a80" integrity sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg== -to-object-path@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" - integrity sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68= +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== dependencies: - kind-of "^3.0.2" - -to-regex-range@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" - integrity sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg= - dependencies: - is-number "^3.0.0" - repeat-string "^1.6.1" - -to-regex@^3.0.1, to-regex@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" - integrity sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw== - dependencies: - define-property "^2.0.2" - extend-shallow "^3.0.2" - regex-not "^1.0.2" - safe-regex "^1.1.0" + is-number "^7.0.0" toidentifier@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== +tough-cookie@^2.3.3: + version "2.5.0" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" + integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== + dependencies: + psl "^1.1.28" + punycode "^2.1.1" + tough-cookie@~2.4.3: version "2.4.3" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781" @@ -2551,31 +1700,16 @@ tough-cookie@~2.4.3: psl "^1.1.24" punycode "^1.4.1" -ts-node@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-4.1.0.tgz#36d9529c7b90bb993306c408cd07f7743de20712" - integrity sha512-xcZH12oVg9PShKhy3UHyDmuDLV3y7iKwX25aMVPt1SIXSuAfWkFiGPEkg+th8R4YKW/QCxDoW7lJdb15lx6QWg== +ts-node@^8.6.2: + version "8.6.2" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-8.6.2.tgz#7419a01391a818fbafa6f826a33c1a13e9464e35" + integrity sha512-4mZEbofxGqLL2RImpe3zMJukvEvcO1XP8bj8ozBPySdCUXEcU5cIRwR0aM3R+VoZq7iXc8N86NC0FspGRqP4gg== dependencies: - arrify "^1.0.0" - chalk "^2.3.0" - diff "^3.1.0" + arg "^4.1.0" + diff "^4.0.1" make-error "^1.1.1" - minimist "^1.2.0" - mkdirp "^0.5.1" - source-map-support "^0.5.0" - tsconfig "^7.0.0" - v8flags "^3.0.0" - yn "^2.0.0" - -tsconfig@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/tsconfig/-/tsconfig-7.0.0.tgz#84538875a4dc216e5c4a5432b3a4dec3d54e91b7" - integrity sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw== - dependencies: - "@types/strip-bom" "^3.0.0" - "@types/strip-json-comments" "0.0.30" - strip-bom "^3.0.0" - strip-json-comments "^2.0.0" + source-map-support "^0.5.6" + yn "3.1.1" tslib@^1.9.0: version "1.10.0" @@ -2602,44 +1736,26 @@ type-is@~1.6.17: media-typer "0.3.0" mime-types "~2.1.24" -typescript@^2.7.1: - version "2.9.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.9.2.tgz#1cbf61d05d6b96269244eb6a3bce4bd914e0f00c" - integrity sha512-Gr4p6nFNaoufRIY4NMdpQRNmgxVIGMs4Fcu/ujdYk3nAZqk7supzBE9idmvfZIlH/Cuj//dvi+019qEue9lV0w== +typescript@^3.7.5: + version "3.7.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.5.tgz#0692e21f65fd4108b9330238aac11dd2e177a1ae" + integrity sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw== ultron@~1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.1.1.tgz#9fe1536a10a664a65266a1e3ccf85fd36302bc9c" integrity sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og== -union-value@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847" - integrity sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg== - dependencies: - arr-union "^3.1.0" - get-value "^2.0.6" - is-extendable "^0.1.1" - set-value "^2.0.1" +universalify@^0.1.0: + version "0.1.2" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" + integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= -unset-value@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" - integrity sha1-g3aHP30jNRef+x5vw6jtDfyKtVk= - dependencies: - has-value "^0.3.1" - isobject "^3.0.0" - -upath@^1.1.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/upath/-/upath-1.1.2.tgz#3db658600edaeeccbe6db5e684d67ee8c2acd068" - integrity sha512-kXpym8nmDmlCBr7nKdIx8P2jNBa+pBpIUFRnKJ4dr8htyYGJFokkr2ZvERRtUN+9SY+JqXouNgUPtv6JQva/2Q== - uri-js@^4.2.2: version "4.2.2" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" @@ -2647,11 +1763,6 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" -urix@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" - integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= - url-parse@^1.4.3: version "1.4.7" resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.7.tgz#a8a83535e8c00a316e403a5db4ac1b9b853ae278" @@ -2660,11 +1771,6 @@ url-parse@^1.4.3: querystringify "^2.1.1" requires-port "^1.0.0" -use@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" - integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== - useragent@2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/useragent/-/useragent-2.3.0.tgz#217f943ad540cb2128658ab23fc960f6a88c9972" @@ -2688,13 +1794,6 @@ uuid@^3.3.2: resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA== -v8flags@^3.0.0: - version "3.1.3" - resolved "https://registry.yarnpkg.com/v8flags/-/v8flags-3.1.3.tgz#fc9dc23521ca20c5433f81cc4eb9b3033bb105d8" - integrity sha512-amh9CCg3ZxkzQ48Mhcb8iX7xpAfYJgePHxWMQCBWECpOSqJUXgY26ncA61UTV0BkPqfhcy6mzwCIoP4ygxpW8w== - dependencies: - homedir-polyfill "^1.0.1" - vargs@0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/vargs/-/vargs-0.1.0.tgz#6b6184da6520cc3204ce1b407cac26d92609ebff" @@ -2734,13 +1833,6 @@ which@^1.2.1: dependencies: isexe "^2.0.0" -wide-align@^1.1.0: - version "1.1.3" - resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" - integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA== - dependencies: - string-width "^1.0.2 || 2" - wordwrap@~0.0.2: version "0.0.3" resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107" @@ -2787,20 +1879,15 @@ yallist@^2.1.2: resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI= -yallist@^3.0.0, yallist@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.3.tgz#b4b049e314be545e3ce802236d6cd22cd91c3de9" - integrity sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A== - yeast@0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/yeast/-/yeast-0.1.2.tgz#008e06d8094320c372dbc2f8ed76a0ca6c8ac419" integrity sha1-AI4G2AlDIMNy28L47XagymyKxBk= -yn@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/yn/-/yn-2.0.0.tgz#e5adabc8acf408f6385fc76495684c88e6af689a" - integrity sha1-5a2ryKz0CPY4X8dklWhMiOavaJo= +yn@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" + integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== zip-stream@^1.2.0: version "1.2.0" diff --git a/src/SignalR/clients/ts/signalr-protocol-msgpack/README.md b/src/SignalR/clients/ts/signalr-protocol-msgpack/README.md index e840374319..d7687bcff6 100644 --- a/src/SignalR/clients/ts/signalr-protocol-msgpack/README.md +++ b/src/SignalR/clients/ts/signalr-protocol-msgpack/README.md @@ -4,15 +4,21 @@ MsgPack support for SignalR for ASP.NET Core ```bash npm install @microsoft/signalr-protocol-msgpack -``` -or -```bash +# or yarn add @microsoft/signalr-protocol-msgpack ``` +To try previews of the next version, use the `next` tag on NPM: + +```bash +npm install @microsoft/signalr-protocol-msgpack@next +# or +yarn add @microsoft/signalr-protocol-msgpack@next +``` + ## Usage -See the [SignalR Documentation](https://docs.microsoft.com/en-us/aspnet/core/signalr) at docs.microsoft.com for documentation on the latest release. [API Reference Documentation](https://docs.microsoft.com/javascript/api/%40aspnet/signalr-protocol-msgpack/?view=signalr-js-latest) is also available on docs.microsoft.com. +See the [SignalR Documentation](https://docs.microsoft.com/aspnet/core/signalr) at docs.microsoft.com for documentation on the latest release. [API Reference Documentation](https://docs.microsoft.com/javascript/api/%40aspnet/signalr-protocol-msgpack/?view=signalr-js-latest) is also available on docs.microsoft.com. ### Browser diff --git a/src/SignalR/clients/ts/signalr-protocol-msgpack/package.json b/src/SignalR/clients/ts/signalr-protocol-msgpack/package.json index 3d262cc056..58e4966e10 100644 --- a/src/SignalR/clients/ts/signalr-protocol-msgpack/package.json +++ b/src/SignalR/clients/ts/signalr-protocol-msgpack/package.json @@ -1,6 +1,6 @@ { "name": "@microsoft/signalr-protocol-msgpack", - "version": "3.0.0-dev", + "version": "5.0.0-dev", "description": "MsgPack Protocol support for ASP.NET Core SignalR", "main": "./dist/cjs/index.js", "module": "./dist/esm/index.js", @@ -29,14 +29,14 @@ ], "repository": { "type": "git", - "url": "git+https://github.com/aspnet/AspNetCore.git" + "url": "git+https://github.com/dotnet/aspnetcore.git" }, "author": "Microsoft", "license": "Apache-2.0", "bugs": { - "url": "https://github.com/aspnet/AspNetCore/issues" + "url": "https://github.com/dotnet/aspnetcore/issues" }, - "homepage": "https://github.com/aspnet/AspNetCore/tree/master/src/SignalR#readme", + "homepage": "https://github.com/dotnet/aspnetcore/tree/master/src/SignalR#readme", "files": [ "dist/**/*", "src/**/*" diff --git a/src/SignalR/clients/ts/signalr-protocol-msgpack/signalr-protocol-msgpack.npmproj b/src/SignalR/clients/ts/signalr-protocol-msgpack/signalr-protocol-msgpack.npmproj index 72978faa2d..61d5c7477c 100644 --- a/src/SignalR/clients/ts/signalr-protocol-msgpack/signalr-protocol-msgpack.npmproj +++ b/src/SignalR/clients/ts/signalr-protocol-msgpack/signalr-protocol-msgpack.npmproj @@ -5,7 +5,7 @@ @microsoft/signalr-protocol-msgpack true false - true + true true @@ -13,5 +13,9 @@ + + + + diff --git a/src/SignalR/clients/ts/signalr-protocol-msgpack/src/MessagePackHubProtocol.ts b/src/SignalR/clients/ts/signalr-protocol-msgpack/src/MessagePackHubProtocol.ts index 66b0a893af..edf8d8dc72 100644 --- a/src/SignalR/clients/ts/signalr-protocol-msgpack/src/MessagePackHubProtocol.ts +++ b/src/SignalR/clients/ts/signalr-protocol-msgpack/src/MessagePackHubProtocol.ts @@ -221,16 +221,28 @@ export class MessagePackHubProtocol implements IHubProtocol { private writeInvocation(invocationMessage: InvocationMessage): ArrayBuffer { const msgpack = msgpack5(); - const payload = msgpack.encode([MessageType.Invocation, invocationMessage.headers || {}, invocationMessage.invocationId || null, - invocationMessage.target, invocationMessage.arguments, invocationMessage.streamIds]); + let payload: any; + if (invocationMessage.streamIds) { + payload = msgpack.encode([MessageType.Invocation, invocationMessage.headers || {}, invocationMessage.invocationId || null, + invocationMessage.target, invocationMessage.arguments, invocationMessage.streamIds]); + } else { + payload = msgpack.encode([MessageType.Invocation, invocationMessage.headers || {}, invocationMessage.invocationId || null, + invocationMessage.target, invocationMessage.arguments]); + } return BinaryMessageFormat.write(payload.slice()); } private writeStreamInvocation(streamInvocationMessage: StreamInvocationMessage): ArrayBuffer { const msgpack = msgpack5(); - const payload = msgpack.encode([MessageType.StreamInvocation, streamInvocationMessage.headers || {}, streamInvocationMessage.invocationId, - streamInvocationMessage.target, streamInvocationMessage.arguments, streamInvocationMessage.streamIds]); + let payload: any; + if (streamInvocationMessage.streamIds) { + payload = msgpack.encode([MessageType.StreamInvocation, streamInvocationMessage.headers || {}, streamInvocationMessage.invocationId, + streamInvocationMessage.target, streamInvocationMessage.arguments, streamInvocationMessage.streamIds]); + } else { + payload = msgpack.encode([MessageType.StreamInvocation, streamInvocationMessage.headers || {}, streamInvocationMessage.invocationId, + streamInvocationMessage.target, streamInvocationMessage.arguments]); + } return BinaryMessageFormat.write(payload.slice()); } diff --git a/src/SignalR/clients/ts/signalr-protocol-msgpack/yarn.lock b/src/SignalR/clients/ts/signalr-protocol-msgpack/yarn.lock index 6d68509ff9..5d4f4a5bc7 100644 --- a/src/SignalR/clients/ts/signalr-protocol-msgpack/yarn.lock +++ b/src/SignalR/clients/ts/signalr-protocol-msgpack/yarn.lock @@ -25,60 +25,23 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-8.5.5.tgz#6f9e8164ae1a55a9beb1d2571cfb7acf9d720c61" integrity sha512-JRnfoh0Ll4ElmIXKxbUfcOodkGvcNHljct6mO1X9hE/mlrMzAx0hYCLAD7sgT53YAY1HdlpzUcV0CkmDqUqTuA== -ajv@^6.5.5: - version "6.10.0" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.10.0.tgz#90d0d54439da587cd7e843bfb7045f50bd22bdf1" - integrity sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg== +abort-controller@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" + integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== dependencies: - fast-deep-equal "^2.0.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" - -asn1@~0.2.3: - version "0.2.4" - resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" - integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg== - dependencies: - safer-buffer "~2.1.0" - -assert-plus@1.0.0, assert-plus@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" - integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= + event-target-shim "^5.0.0" async-limiter@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8" integrity sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg== -asynckit@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" - integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= - -aws-sign2@~0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" - integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= - -aws4@^1.8.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f" - integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ== - base64-js@^1.0.2: version "1.2.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.2.1.tgz#a91947da1f4a516ea38e5b4ec0ec3773675e0886" integrity sha512-dwVUVIXsBZXwTuwnXI9RK8sBmgq09NDHzyR9SAph9eqk76gKK2JSQmZARC2zRC81JC2QTtxD0ARU5qTS25gIGw== -bcrypt-pbkdf@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" - integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4= - dependencies: - tweetnacl "^0.14.3" - bl@^1.2.1: version "1.2.2" resolved "https://registry.yarnpkg.com/bl/-/bl-1.2.2.tgz#a160911717103c07410cef63ef51b397c025af9c" @@ -95,42 +58,20 @@ buffer@^5.0.8: base64-js "^1.0.2" ieee754 "^1.1.4" -caseless@~0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" - integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= - -combined-stream@^1.0.6, combined-stream@~1.0.6: - version "1.0.8" - resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" - integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== - dependencies: - delayed-stream "~1.0.0" - -core-util-is@1.0.2, core-util-is@~1.0.0: +core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= -dashdash@^1.12.0: - version "1.14.1" - resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" - integrity sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA= - dependencies: - assert-plus "^1.0.0" +es6-denodeify@^0.1.1: + version "0.1.5" + resolved "https://registry.yarnpkg.com/es6-denodeify/-/es6-denodeify-0.1.5.tgz#31d4d5fe9c5503e125460439310e16a2a3f39c1f" + integrity sha1-MdTV/pxVA+ElRgQ5MQ4WoqPznB8= -delayed-stream@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" - integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= - -ecc-jsbn@~0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" - integrity sha1-OoOpBOVDUyh4dMVkt1SThoSamMk= - dependencies: - jsbn "~0.1.0" - safer-buffer "^2.1.0" +event-target-shim@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" + integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== eventsource@^1.0.7: version "1.0.7" @@ -139,73 +80,13 @@ eventsource@^1.0.7: dependencies: original "^1.0.0" -extend@~3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" - integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== - -extsprintf@1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" - integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU= - -extsprintf@^1.2.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" - integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= - -fast-deep-equal@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" - integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk= - -fast-json-stable-stringify@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" - integrity sha1-1RQsDK7msRifh9OnYREGT4bIu/I= - -forever-agent@~0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" - integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= - -form-data@~2.3.2: - version "2.3.3" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" - integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== +fetch-cookie@^0.7.3: + version "0.7.3" + resolved "https://registry.yarnpkg.com/fetch-cookie/-/fetch-cookie-0.7.3.tgz#b8d023f421dd2b2f4a0eca9cd7318a967ed4eed8" + integrity sha512-rZPkLnI8x5V+zYAiz8QonAHsTb4BY+iFowFBI1RFn0zrO343AVp9X7/yUj/9wL6Ef/8fLls8b/vGtzUvmyAUGA== dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.6" - mime-types "^2.1.12" - -getpass@^0.1.1: - version "0.1.7" - resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" - integrity sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo= - dependencies: - assert-plus "^1.0.0" - -har-schema@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" - integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= - -har-validator@~5.1.0: - version "5.1.3" - resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080" - integrity sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g== - dependencies: - ajv "^6.5.5" - har-schema "^2.0.0" - -http-signature@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" - integrity sha1-muzZJRFHcvPZW2WmCruPfBj7rOE= - dependencies: - assert-plus "^1.0.0" - jsprim "^1.2.2" - sshpk "^1.7.0" + es6-denodeify "^0.1.1" + tough-cookie "^2.3.3" ieee754@^1.1.4: version "1.1.8" @@ -217,63 +98,11 @@ inherits@^2.0.3, inherits@~2.0.3: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= -is-typedarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" - integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= - isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= -isstream@~0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" - integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= - -jsbn@~0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" - integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= - -json-schema-traverse@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" - integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== - -json-schema@0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" - integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= - -json-stringify-safe@~5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" - integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= - -jsprim@^1.2.2: - version "1.4.1" - resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" - integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI= - dependencies: - assert-plus "1.0.0" - extsprintf "1.3.0" - json-schema "0.2.3" - verror "1.10.0" - -mime-db@1.40.0: - version "1.40.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.40.0.tgz#a65057e998db090f732a68f6c276d387d4126c32" - integrity sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA== - -mime-types@^2.1.12, mime-types@~2.1.19: - version "2.1.24" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.24.tgz#b6f8d0b3e951efb77dedeca194cff6d16f676f81" - integrity sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ== - dependencies: - mime-db "1.40.0" - msgpack5@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/msgpack5/-/msgpack5-4.0.2.tgz#88be70e432d142b7a379bfb170f433bd41447508" @@ -284,10 +113,10 @@ msgpack5@^4.0.2: readable-stream "^2.3.3" safe-buffer "^5.1.1" -oauth-sign@~0.9.0: - version "0.9.0" - resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" - integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== +node-fetch@^2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd" + integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA== original@^1.0.0: version "1.0.2" @@ -296,36 +125,21 @@ original@^1.0.0: dependencies: url-parse "^1.4.3" -performance-now@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" - integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= - process-nextick-args@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa" integrity sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw== -psl@^1.1.24: - version "1.1.31" - resolved "https://registry.yarnpkg.com/psl/-/psl-1.1.31.tgz#e9aa86d0101b5b105cbe93ac6b784cd547276184" - integrity sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw== +psl@^1.1.28: + version "1.7.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.7.0.tgz#f1c4c47a8ef97167dea5d6bbf4816d736e884a3c" + integrity sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ== -punycode@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" - integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= - -punycode@^2.1.0: +punycode@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== -qs@~6.5.2: - version "6.5.2" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" - integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== - querystringify@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.1.1.tgz#60e5a5fd64a7f8bfa4d2ab2ed6fdf4c85bad154e" @@ -344,67 +158,16 @@ readable-stream@^2.3.3, readable-stream@^2.3.5: string_decoder "~1.1.1" util-deprecate "~1.0.1" -request@^2.88.0: - version "2.88.0" - resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" - integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg== - dependencies: - aws-sign2 "~0.7.0" - aws4 "^1.8.0" - caseless "~0.12.0" - combined-stream "~1.0.6" - extend "~3.0.2" - forever-agent "~0.6.1" - form-data "~2.3.2" - har-validator "~5.1.0" - http-signature "~1.2.0" - is-typedarray "~1.0.0" - isstream "~0.1.2" - json-stringify-safe "~5.0.1" - mime-types "~2.1.19" - oauth-sign "~0.9.0" - performance-now "^2.1.0" - qs "~6.5.2" - safe-buffer "^5.1.2" - tough-cookie "~2.4.3" - tunnel-agent "^0.6.0" - uuid "^3.3.2" - requires-port@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8= -safe-buffer@^5.0.1, safe-buffer@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" - integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== - safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" integrity sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg== -safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: - version "2.1.2" - resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" - integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== - -sshpk@^1.7.0: - version "1.16.1" - resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" - integrity sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg== - dependencies: - asn1 "~0.2.3" - assert-plus "^1.0.0" - bcrypt-pbkdf "^1.0.0" - dashdash "^1.12.0" - ecc-jsbn "~0.1.1" - getpass "^0.1.1" - jsbn "~0.1.0" - safer-buffer "^2.0.2" - tweetnacl "~0.14.0" - string_decoder@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" @@ -412,32 +175,13 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -tough-cookie@~2.4.3: - version "2.4.3" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781" - integrity sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ== +tough-cookie@^2.3.3: + version "2.5.0" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" + integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== dependencies: - psl "^1.1.24" - punycode "^1.4.1" - -tunnel-agent@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" - integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0= - dependencies: - safe-buffer "^5.0.1" - -tweetnacl@^0.14.3, tweetnacl@~0.14.0: - version "0.14.5" - resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" - integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= - -uri-js@^4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" - integrity sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ== - dependencies: - punycode "^2.1.0" + psl "^1.1.28" + punycode "^2.1.1" url-parse@^1.4.3: version "1.4.7" @@ -452,20 +196,6 @@ util-deprecate@~1.0.1: resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= -uuid@^3.3.2: - version "3.3.2" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" - integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA== - -verror@1.10.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" - integrity sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA= - dependencies: - assert-plus "^1.0.0" - core-util-is "1.0.2" - extsprintf "^1.2.0" - ws@^6.0.0: version "6.2.1" resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb" diff --git a/src/SignalR/clients/ts/signalr/README.md b/src/SignalR/clients/ts/signalr/README.md index ec8f34b227..921556949e 100644 --- a/src/SignalR/clients/ts/signalr/README.md +++ b/src/SignalR/clients/ts/signalr/README.md @@ -1,18 +1,26 @@ -JavaScript and TypeScript clients for SignalR for ASP.NET Core +JavaScript and TypeScript clients for SignalR for ASP.NET Core and Azure SignalR Service ## Installation ```bash npm install @microsoft/signalr -``` -or -```bash +# or yarn add @microsoft/signalr ``` +To try previews of the next version, use the `next` tag on NPM: + +```bash +npm install @microsoft/signalr@next +# or +yarn add @microsoft/signalr@next +``` + ## Usage -See the [SignalR Documentation](https://docs.microsoft.com/en-us/aspnet/core/signalr) at docs.microsoft.com for documentation on the latest release. [API Reference Documentation](https://docs.microsoft.com/javascript/api/%40aspnet/signalr/?view=signalr-js-latest) is also available on docs.microsoft.com. +See the [SignalR Documentation](https://docs.microsoft.com/aspnet/core/signalr) at docs.microsoft.com for documentation on the latest release. [API Reference Documentation](https://docs.microsoft.com/javascript/api/%40aspnet/signalr/?view=signalr-js-latest) is also available on docs.microsoft.com. + +For documentation on using this client with Azure SignalR Service and Azure Functions, see the [SignalR Service serverless developer guide](https://docs.microsoft.com/azure/azure-signalr/signalr-concept-serverless-development-config). ### Browser diff --git a/src/SignalR/clients/ts/signalr/package.json b/src/SignalR/clients/ts/signalr/package.json index 6b50f24d35..75d0b4642c 100644 --- a/src/SignalR/clients/ts/signalr/package.json +++ b/src/SignalR/clients/ts/signalr/package.json @@ -1,6 +1,6 @@ { "name": "@microsoft/signalr", - "version": "3.0.0-dev", + "version": "5.0.0-dev", "description": "ASP.NET Core SignalR Client", "main": "./dist/cjs/index.js", "module": "./dist/esm/index.js", @@ -32,14 +32,14 @@ ], "repository": { "type": "git", - "url": "git+https://github.com/aspnet/AspNetCore.git" + "url": "git+https://github.com/dotnet/aspnetcore.git" }, "author": "Microsoft", "license": "Apache-2.0", "bugs": { - "url": "https://github.com/aspnet/AspNetCore/issues" + "url": "https://github.com/dotnet/aspnetcore/issues" }, - "homepage": "https://github.com/aspnet/AspNetCore/tree/master/src/SignalR#readme", + "homepage": "https://github.com/dotnet/aspnetcore/tree/master/src/SignalR#readme", "files": [ "dist/**/*", "src/**/*" @@ -48,12 +48,14 @@ "@types/eventsource": "^1.0.2", "@types/jest": "^23.3.2", "@types/node": "^10.9.4", - "@types/request": "^2.47.1", + "@types/tough-cookie": "^2.3.6", "es6-promise": "^4.2.2" }, "dependencies": { "eventsource": "^1.0.7", - "request": "^2.88.0", + "node-fetch": "^2.6.0", + "abort-controller": "^3.0.0", + "fetch-cookie": "^0.7.3", "ws": "^6.0.0" } } diff --git a/src/SignalR/clients/ts/signalr/signalr.npmproj b/src/SignalR/clients/ts/signalr/signalr.npmproj index e6a6c1d993..2aa54d01fe 100644 --- a/src/SignalR/clients/ts/signalr/signalr.npmproj +++ b/src/SignalR/clients/ts/signalr/signalr.npmproj @@ -5,8 +5,13 @@ @microsoft/signalr true false - true + true + + + + + diff --git a/src/SignalR/clients/ts/signalr/src/DefaultHttpClient.ts b/src/SignalR/clients/ts/signalr/src/DefaultHttpClient.ts index fece43020d..7ed6033399 100644 --- a/src/SignalR/clients/ts/signalr/src/DefaultHttpClient.ts +++ b/src/SignalR/clients/ts/signalr/src/DefaultHttpClient.ts @@ -2,9 +2,10 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. import { AbortError } from "./Errors"; +import { FetchHttpClient } from "./FetchHttpClient"; import { HttpClient, HttpRequest, HttpResponse } from "./HttpClient"; import { ILogger } from "./ILogger"; -import { NodeHttpClient } from "./NodeHttpClient"; +import { Platform } from "./Utils"; import { XhrHttpClient } from "./XhrHttpClient"; /** Default implementation of {@link @microsoft/signalr.HttpClient}. */ @@ -15,10 +16,12 @@ export class DefaultHttpClient extends HttpClient { public constructor(logger: ILogger) { super(); - if (typeof XMLHttpRequest !== "undefined") { + if (typeof fetch !== "undefined" || Platform.isNode) { + this.httpClient = new FetchHttpClient(logger); + } else if (typeof XMLHttpRequest !== "undefined") { this.httpClient = new XhrHttpClient(logger); } else { - this.httpClient = new NodeHttpClient(logger); + throw new Error("No usable HttpClient found."); } } diff --git a/src/SignalR/clients/ts/signalr/src/EmptyNodeHttpClient.ts b/src/SignalR/clients/ts/signalr/src/EmptyNodeHttpClient.ts deleted file mode 100644 index 662bae5406..0000000000 --- a/src/SignalR/clients/ts/signalr/src/EmptyNodeHttpClient.ts +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -// This is an empty implementation of the NodeHttpClient that will be included in browser builds so the output file will be smaller - -import { HttpClient, HttpResponse } from "./HttpClient"; -import { ILogger } from "./ILogger"; - -/** @private */ -export class NodeHttpClient extends HttpClient { - // @ts-ignore: Need ILogger to compile, but unused variables generate errors - public constructor(logger: ILogger) { - super(); - } - - public send(): Promise { - return Promise.reject(new Error("If using Node either provide an XmlHttpRequest polyfill or consume the cjs or esm script instead of the browser/signalr.js one.")); - } -} diff --git a/src/SignalR/clients/ts/signalr/src/FetchHttpClient.ts b/src/SignalR/clients/ts/signalr/src/FetchHttpClient.ts new file mode 100644 index 0000000000..2700660f64 --- /dev/null +++ b/src/SignalR/clients/ts/signalr/src/FetchHttpClient.ts @@ -0,0 +1,158 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +// @ts-ignore: This will be removed from built files and is here to make the types available during dev work +import * as tough from "@types/tough-cookie"; + +import { AbortError, HttpError, TimeoutError } from "./Errors"; +import { HttpClient, HttpRequest, HttpResponse } from "./HttpClient"; +import { ILogger, LogLevel } from "./ILogger"; +import { Platform } from "./Utils"; + +export class FetchHttpClient extends HttpClient { + private readonly abortControllerType: { prototype: AbortController, new(): AbortController }; + private readonly fetchType: (input: RequestInfo, init?: RequestInit) => Promise; + private readonly jar?: tough.CookieJar; + + private readonly logger: ILogger; + + public constructor(logger: ILogger) { + super(); + this.logger = logger; + + if (typeof fetch === "undefined") { + // In order to ignore the dynamic require in webpack builds we need to do this magic + // @ts-ignore: TS doesn't know about these names + const requireFunc = typeof __webpack_require__ === "function" ? __non_webpack_require__ : require; + + // Cookies aren't automatically handled in Node so we need to add a CookieJar to preserve cookies across requests + this.jar = new (requireFunc("tough-cookie")).CookieJar(); + this.fetchType = requireFunc("node-fetch"); + + // node-fetch doesn't have a nice API for getting and setting cookies + // fetch-cookie will wrap a fetch implementation with a default CookieJar or a provided one + this.fetchType = requireFunc("fetch-cookie")(this.fetchType, this.jar); + + // Node needs EventListener methods on AbortController which our custom polyfill doesn't provide + this.abortControllerType = requireFunc("abort-controller"); + } else { + this.fetchType = fetch.bind(self); + this.abortControllerType = AbortController; + } + } + + /** @inheritDoc */ + public async send(request: HttpRequest): Promise { + // Check that abort was not signaled before calling send + if (request.abortSignal && request.abortSignal.aborted) { + throw new AbortError(); + } + + if (!request.method) { + throw new Error("No method defined."); + } + if (!request.url) { + throw new Error("No url defined."); + } + + const abortController = new this.abortControllerType(); + + let error: any; + // Hook our abortSignal into the abort controller + if (request.abortSignal) { + request.abortSignal.onabort = () => { + abortController.abort(); + error = new AbortError(); + }; + } + + // If a timeout has been passed in, setup a timeout to call abort + // Type needs to be any to fit window.setTimeout and NodeJS.setTimeout + let timeoutId: any = null; + if (request.timeout) { + const msTimeout = request.timeout!; + timeoutId = setTimeout(() => { + abortController.abort(); + this.logger.log(LogLevel.Warning, `Timeout from HTTP request.`); + error = new TimeoutError(); + }, msTimeout); + } + + let response: Response; + try { + response = await this.fetchType(request.url!, { + body: request.content!, + cache: "no-cache", + credentials: request.withCredentials === true ? "include" : "same-origin", + headers: { + "Content-Type": "text/plain;charset=UTF-8", + "X-Requested-With": "XMLHttpRequest", + ...request.headers, + }, + method: request.method!, + mode: "cors", + redirect: "manual", + signal: abortController.signal, + }); + } catch (e) { + if (error) { + throw error; + } + this.logger.log( + LogLevel.Warning, + `Error from HTTP request. ${e}.`, + ); + throw e; + } finally { + if (timeoutId) { + clearTimeout(timeoutId); + } + if (request.abortSignal) { + request.abortSignal.onabort = null; + } + } + + if (!response.ok) { + throw new HttpError(response.statusText, response.status); + } + + const content = deserializeContent(response, request.responseType); + const payload = await content; + + return new HttpResponse( + response.status, + response.statusText, + payload, + ); + } + + public getCookieString(url: string): string { + let cookies: string = ""; + if (Platform.isNode && this.jar) { + // @ts-ignore: unused variable + this.jar.getCookies(url, (e, c) => cookies = c.join("; ")); + } + return cookies; + } +} + +function deserializeContent(response: Response, responseType?: XMLHttpRequestResponseType): Promise { + let content; + switch (responseType) { + case "arraybuffer": + content = response.arrayBuffer(); + break; + case "text": + content = response.text(); + break; + case "blob": + case "document": + case "json": + throw new Error(`${responseType} is not supported.`); + default: + content = response.text(); + break; + } + + return content; +} diff --git a/src/SignalR/clients/ts/signalr/src/HttpClient.ts b/src/SignalR/clients/ts/signalr/src/HttpClient.ts index 9685feca5e..57614bb86b 100644 --- a/src/SignalR/clients/ts/signalr/src/HttpClient.ts +++ b/src/SignalR/clients/ts/signalr/src/HttpClient.ts @@ -25,6 +25,9 @@ export interface HttpRequest { /** The time to wait for the request to complete before throwing a TimeoutError. Measured in milliseconds. */ timeout?: number; + + /** This controls whether credentials such as cookies are sent in cross-site requests. */ + withCredentials?: boolean; } /** Represents an HTTP response. */ @@ -57,6 +60,14 @@ export class HttpResponse { * @param {ArrayBuffer} content The content of the response. */ constructor(statusCode: number, statusText: string, content: ArrayBuffer); + + /** Constructs a new instance of {@link @microsoft/signalr.HttpResponse} with the specified status code, message and binary content. + * + * @param {number} statusCode The status code of the response. + * @param {string} statusText The status message of the response. + * @param {string | ArrayBuffer} content The content of the response. + */ + constructor(statusCode: number, statusText: string, content: string | ArrayBuffer); constructor( public readonly statusCode: number, public readonly statusText?: string, diff --git a/src/SignalR/clients/ts/signalr/src/HttpConnection.ts b/src/SignalR/clients/ts/signalr/src/HttpConnection.ts index 075860006f..e439ba159b 100644 --- a/src/SignalR/clients/ts/signalr/src/HttpConnection.ts +++ b/src/SignalR/clients/ts/signalr/src/HttpConnection.ts @@ -9,12 +9,12 @@ import { ILogger, LogLevel } from "./ILogger"; import { HttpTransportType, ITransport, TransferFormat } from "./ITransport"; import { LongPollingTransport } from "./LongPollingTransport"; import { ServerSentEventsTransport } from "./ServerSentEventsTransport"; -import { Arg, createLogger, Platform } from "./Utils"; +import { Arg, createLogger, getUserAgentHeader, Platform } from "./Utils"; import { WebSocketTransport } from "./WebSocketTransport"; /** @private */ const enum ConnectionState { - Connecting = "Connecting ", + Connecting = "Connecting", Connected = "Connected", Disconnected = "Disconnected", Disconnecting = "Disconnecting", @@ -39,16 +39,6 @@ export interface IAvailableTransport { const MAX_REDIRECTS = 100; -let WebSocketModule: any = null; -let EventSourceModule: any = null; -if (Platform.isNode && typeof require !== "undefined") { - // In order to ignore the dynamic require in webpack builds we need to do this magic - // @ts-ignore: TS doesn't know about these names - const requireFunc = typeof __webpack_require__ === "function" ? __non_webpack_require__ : require; - WebSocketModule = requireFunc("ws"); - EventSourceModule = requireFunc("eventsource"); -} - /** @private */ export class HttpConnection implements IConnection { private connectionState: ConnectionState; @@ -81,21 +71,37 @@ export class HttpConnection implements IConnection { this.baseUrl = this.resolveUrl(url); options = options || {}; - options.logMessageContent = options.logMessageContent || false; + options.logMessageContent = options.logMessageContent === undefined ? false : options.logMessageContent; + if (typeof options.withCredentials === "boolean" || options.withCredentials === undefined) { + options.withCredentials = options.withCredentials === undefined ? true : options.withCredentials; + } else { + throw new Error("withCredentials option was not a 'boolean' or 'undefined' value"); + } + + let webSocketModule: any = null; + let eventSourceModule: any = null; + + if (Platform.isNode && typeof require !== "undefined") { + // In order to ignore the dynamic require in webpack builds we need to do this magic + // @ts-ignore: TS doesn't know about these names + const requireFunc = typeof __webpack_require__ === "function" ? __non_webpack_require__ : require; + webSocketModule = requireFunc("ws"); + eventSourceModule = requireFunc("eventsource"); + } if (!Platform.isNode && typeof WebSocket !== "undefined" && !options.WebSocket) { options.WebSocket = WebSocket; } else if (Platform.isNode && !options.WebSocket) { - if (WebSocketModule) { - options.WebSocket = WebSocketModule; + if (webSocketModule) { + options.WebSocket = webSocketModule; } } if (!Platform.isNode && typeof EventSource !== "undefined" && !options.EventSource) { options.EventSource = EventSource; } else if (Platform.isNode && !options.EventSource) { - if (typeof EventSourceModule !== "undefined") { - options.EventSource = EventSourceModule; + if (typeof eventSourceModule !== "undefined") { + options.EventSource = eventSourceModule; } } @@ -194,15 +200,6 @@ export class HttpConnection implements IConnection { // This exception is returned to the user as a rejected Promise from the start method. } - if (this.sendQueue) { - try { - await this.sendQueue.stop(); - } catch (e) { - this.logger.log(LogLevel.Error, `TransportSendQueue.stop() threw error '${e}'.`); - } - this.sendQueue = undefined; - } - // The transport's onclose will trigger stopConnection which will run our onclose event. // The transport should always be set if currently connected. If it wasn't set, it's likely because // stop was called during start() and start() failed. @@ -302,26 +299,28 @@ export class HttpConnection implements IConnection { } private async getNegotiationResponse(url: string): Promise { - let headers; + const headers = {}; if (this.accessTokenFactory) { const token = await this.accessTokenFactory(); if (token) { - headers = { - ["Authorization"]: `Bearer ${token}`, - }; + headers[`Authorization`] = `Bearer ${token}`; } } + const [name, value] = getUserAgentHeader(); + headers[name] = value; + const negotiateUrl = this.resolveNegotiateUrl(url); this.logger.log(LogLevel.Debug, `Sending negotiation request: ${negotiateUrl}.`); try { const response = await this.httpClient.post(negotiateUrl, { content: "", headers, + withCredentials: this.options.withCredentials, }); if (response.statusCode !== 200) { - return Promise.reject(new Error(`Unexpected status code returned from negotiate ${response.statusCode}`)); + return Promise.reject(new Error(`Unexpected status code returned from negotiate '${response.statusCode}'`)); } const negotiateResponse = JSON.parse(response.content as string) as INegotiateResponse; @@ -409,9 +408,9 @@ export class HttpConnection implements IConnection { if (!this.options.EventSource) { throw new Error("'EventSource' is not supported in your environment."); } - return new ServerSentEventsTransport(this.httpClient, this.accessTokenFactory, this.logger, this.options.logMessageContent || false, this.options.EventSource); + return new ServerSentEventsTransport(this.httpClient, this.accessTokenFactory, this.logger, this.options.logMessageContent || false, this.options.EventSource, this.options.withCredentials!); case HttpTransportType.LongPolling: - return new LongPollingTransport(this.httpClient, this.accessTokenFactory, this.logger, this.options.logMessageContent || false); + return new LongPollingTransport(this.httpClient, this.accessTokenFactory, this.logger, this.options.logMessageContent || false, this.options.withCredentials!); default: throw new Error(`Unknown transport: ${transport}.`); } @@ -474,8 +473,8 @@ export class HttpConnection implements IConnection { } if (this.connectionState === ConnectionState.Connecting) { - this.logger.log(LogLevel.Warning, `Call to HttpConnection.stopConnection(${error}) was ignored because the connection hasn't yet left the in the connecting state.`); - return; + this.logger.log(LogLevel.Warning, `Call to HttpConnection.stopConnection(${error}) was ignored because the connection is still in the connecting state.`); + throw new Error(`HttpConnection.stopConnection(${error}) was called while the connection is still in the connecting state.`); } if (this.connectionState === ConnectionState.Disconnecting) { @@ -490,14 +489,22 @@ export class HttpConnection implements IConnection { this.logger.log(LogLevel.Information, "Connection disconnected."); } + if (this.sendQueue) { + this.sendQueue.stop().catch((e) => { + this.logger.log(LogLevel.Error, `TransportSendQueue.stop() threw error '${e}'.`); + }); + this.sendQueue = undefined; + } + this.connectionId = undefined; this.connectionState = ConnectionState.Disconnected; - if (this.onclose && this.connectionStarted) { + if (this.connectionStarted) { this.connectionStarted = false; - try { - this.onclose(error); + if (this.onclose) { + this.onclose(error); + } } catch (e) { this.logger.log(LogLevel.Error, `HttpConnection.onclose(${error}) threw error '${e}'.`); } @@ -626,7 +633,7 @@ export class TransportSendQueue { offset += item.byteLength; } - return result; + return result.buffer; } } diff --git a/src/SignalR/clients/ts/signalr/src/HubConnection.ts b/src/SignalR/clients/ts/signalr/src/HubConnection.ts index 62190c166c..505631fa8d 100644 --- a/src/SignalR/clients/ts/signalr/src/HubConnection.ts +++ b/src/SignalR/clients/ts/signalr/src/HubConnection.ts @@ -599,6 +599,10 @@ export class HubConnection { } private resetKeepAliveInterval() { + if (this.connection.features.inherentKeepAlive) { + return; + } + this.cleanupPingTimer(); this.pingServerHandle = setTimeout(async () => { if (this.connectionState === HubConnectionState.Connected) { @@ -814,23 +818,40 @@ export class HubConnection { private createInvocation(methodName: string, args: any[], nonblocking: boolean, streamIds: string[]): InvocationMessage { if (nonblocking) { - return { - arguments: args, - streamIds, - target: methodName, - type: MessageType.Invocation, - }; + if (streamIds.length !== 0) { + return { + arguments: args, + streamIds, + target: methodName, + type: MessageType.Invocation, + }; + } else { + return { + arguments: args, + target: methodName, + type: MessageType.Invocation, + }; + } } else { const invocationId = this.invocationId; this.invocationId++; - return { - arguments: args, - invocationId: invocationId.toString(), - streamIds, - target: methodName, - type: MessageType.Invocation, - }; + if (streamIds.length !== 0) { + return { + arguments: args, + invocationId: invocationId.toString(), + streamIds, + target: methodName, + type: MessageType.Invocation, + }; + } else { + return { + arguments: args, + invocationId: invocationId.toString(), + target: methodName, + type: MessageType.Invocation, + }; + } } } @@ -899,13 +920,22 @@ export class HubConnection { const invocationId = this.invocationId; this.invocationId++; - return { - arguments: args, - invocationId: invocationId.toString(), - streamIds, - target: methodName, - type: MessageType.StreamInvocation, - }; + if (streamIds.length !== 0) { + return { + arguments: args, + invocationId: invocationId.toString(), + streamIds, + target: methodName, + type: MessageType.StreamInvocation, + }; + } else { + return { + arguments: args, + invocationId: invocationId.toString(), + target: methodName, + type: MessageType.StreamInvocation, + }; + } } private createCancelInvocation(id: string): CancelInvocationMessage { diff --git a/src/SignalR/clients/ts/signalr/src/HubConnectionBuilder.ts b/src/SignalR/clients/ts/signalr/src/HubConnectionBuilder.ts index 98a23a3b3d..fa5d7432b9 100644 --- a/src/SignalR/clients/ts/signalr/src/HubConnectionBuilder.ts +++ b/src/SignalR/clients/ts/signalr/src/HubConnectionBuilder.ts @@ -70,14 +70,14 @@ export class HubConnectionBuilder { /** Configures custom logging for the {@link @microsoft/signalr.HubConnection}. * * @param {string} logLevel A string representing a LogLevel setting a minimum level of messages to log. - * See {@link https://docs.microsoft.com/en-us/aspnet/core/signalr/configuration#configure-logging|the documentation for client logging configuration} for more details. + * See {@link https://docs.microsoft.com/aspnet/core/signalr/configuration#configure-logging|the documentation for client logging configuration} for more details. */ public configureLogging(logLevel: string): HubConnectionBuilder; /** Configures custom logging for the {@link @microsoft/signalr.HubConnection}. * * @param {LogLevel | string | ILogger} logging A {@link @microsoft/signalr.LogLevel}, a string representing a LogLevel, or an object implementing the {@link @microsoft/signalr.ILogger} interface. - * See {@link https://docs.microsoft.com/en-us/aspnet/core/signalr/configuration#configure-logging|the documentation for client logging configuration} for more details. + * See {@link https://docs.microsoft.com/aspnet/core/signalr/configuration#configure-logging|the documentation for client logging configuration} for more details. * @returns The {@link @microsoft/signalr.HubConnectionBuilder} instance, for chaining. */ public configureLogging(logging: LogLevel | string | ILogger): HubConnectionBuilder; diff --git a/src/SignalR/clients/ts/signalr/src/IHttpConnectionOptions.ts b/src/SignalR/clients/ts/signalr/src/IHttpConnectionOptions.ts index a980370b64..128872722b 100644 --- a/src/SignalR/clients/ts/signalr/src/IHttpConnectionOptions.ts +++ b/src/SignalR/clients/ts/signalr/src/IHttpConnectionOptions.ts @@ -53,4 +53,12 @@ export interface IHttpConnectionOptions { * @internal */ EventSource?: EventSourceConstructor; + + /** + * Default value is 'true'. + * This controls whether credentials such as cookies are sent in cross-site requests. + * + * Cookies are used by many load-balancers for sticky sessions which is required when your app is deployed with multiple servers. + */ + withCredentials?: boolean; } diff --git a/src/SignalR/clients/ts/signalr/src/IHubProtocol.ts b/src/SignalR/clients/ts/signalr/src/IHubProtocol.ts index 7a250dbc41..9ed7338b63 100644 --- a/src/SignalR/clients/ts/signalr/src/IHubProtocol.ts +++ b/src/SignalR/clients/ts/signalr/src/IHubProtocol.ts @@ -65,7 +65,7 @@ export interface InvocationMessage extends HubInvocationMessage { /** The target method arguments. */ readonly arguments: any[]; /** The target methods stream IDs. */ - readonly streamIds: string[]; + readonly streamIds?: string[]; } /** A hub message representing a streaming invocation. */ @@ -80,7 +80,7 @@ export interface StreamInvocationMessage extends HubInvocationMessage { /** The target method arguments. */ readonly arguments: any[]; /** The target methods stream IDs. */ - readonly streamIds: string[]; + readonly streamIds?: string[]; } /** A hub message representing a single item produced as part of a result stream. */ diff --git a/src/SignalR/clients/ts/signalr/src/LongPollingTransport.ts b/src/SignalR/clients/ts/signalr/src/LongPollingTransport.ts index 7ad1c7d129..f7a7da1784 100644 --- a/src/SignalR/clients/ts/signalr/src/LongPollingTransport.ts +++ b/src/SignalR/clients/ts/signalr/src/LongPollingTransport.ts @@ -6,7 +6,7 @@ import { HttpError, TimeoutError } from "./Errors"; import { HttpClient, HttpRequest } from "./HttpClient"; import { ILogger, LogLevel } from "./ILogger"; import { ITransport, TransferFormat } from "./ITransport"; -import { Arg, getDataDetail, sendMessage } from "./Utils"; +import { Arg, getDataDetail, getUserAgentHeader, sendMessage } from "./Utils"; // Not exported from 'index', this type is internal. /** @private */ @@ -15,6 +15,7 @@ export class LongPollingTransport implements ITransport { private readonly accessTokenFactory: (() => string | Promise) | undefined; private readonly logger: ILogger; private readonly logMessageContent: boolean; + private readonly withCredentials: boolean; private readonly pollAbort: AbortController; private url?: string; @@ -30,12 +31,13 @@ export class LongPollingTransport implements ITransport { return this.pollAbort.aborted; } - constructor(httpClient: HttpClient, accessTokenFactory: (() => string | Promise) | undefined, logger: ILogger, logMessageContent: boolean) { + constructor(httpClient: HttpClient, accessTokenFactory: (() => string | Promise) | undefined, logger: ILogger, logMessageContent: boolean, withCredentials: boolean) { this.httpClient = httpClient; this.accessTokenFactory = accessTokenFactory; this.logger = logger; this.pollAbort = new AbortController(); this.logMessageContent = logMessageContent; + this.withCredentials = withCredentials; this.running = false; @@ -58,10 +60,15 @@ export class LongPollingTransport implements ITransport { throw new Error("Binary protocols over XmlHttpRequest not implementing advanced features are not supported."); } + const headers = {}; + const [name, value] = getUserAgentHeader(); + headers[name] = value; + const pollOptions: HttpRequest = { abortSignal: this.pollAbort.signal, - headers: {}, + headers, timeout: 100000, + withCredentials: this.withCredentials, }; if (transferFormat === TransferFormat.Binary) { @@ -178,7 +185,7 @@ export class LongPollingTransport implements ITransport { if (!this.running) { return Promise.reject(new Error("Cannot send until the transport is connected")); } - return sendMessage(this.logger, "LongPolling", this.httpClient, this.url!, this.accessTokenFactory, data, this.logMessageContent); + return sendMessage(this.logger, "LongPolling", this.httpClient, this.url!, this.accessTokenFactory, data, this.logMessageContent, this.withCredentials); } public async stop(): Promise { @@ -194,8 +201,13 @@ export class LongPollingTransport implements ITransport { // Send DELETE to clean up long polling on the server this.logger.log(LogLevel.Trace, `(LongPolling transport) sending DELETE request to ${this.url}.`); + const headers = {}; + const [name, value] = getUserAgentHeader(); + headers[name] = value; + const deleteOptions: HttpRequest = { - headers: {}, + headers, + withCredentials: this.withCredentials, }; const token = await this.getAccessToken(); this.updateHeaderToken(deleteOptions, token); diff --git a/src/SignalR/clients/ts/signalr/src/NodeHttpClient.ts b/src/SignalR/clients/ts/signalr/src/NodeHttpClient.ts deleted file mode 100644 index c0435fd057..0000000000 --- a/src/SignalR/clients/ts/signalr/src/NodeHttpClient.ts +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -// @ts-ignore: This will be removed from built files and is here to make the types available during dev work -import * as Request from "@types/request"; - -import { AbortError, HttpError, TimeoutError } from "./Errors"; -import { HttpClient, HttpRequest, HttpResponse } from "./HttpClient"; -import { ILogger, LogLevel } from "./ILogger"; -import { isArrayBuffer } from "./Utils"; - -let requestModule: Request.RequestAPI; -if (typeof XMLHttpRequest === "undefined") { - // In order to ignore the dynamic require in webpack builds we need to do this magic - // @ts-ignore: TS doesn't know about these names - const requireFunc = typeof __webpack_require__ === "function" ? __non_webpack_require__ : require; - requestModule = requireFunc("request"); -} - -/** @private */ -export class NodeHttpClient extends HttpClient { - private readonly logger: ILogger; - private readonly request: typeof requestModule; - private readonly cookieJar: Request.CookieJar; - - public constructor(logger: ILogger) { - super(); - if (typeof requestModule === "undefined") { - throw new Error("The 'request' module could not be loaded."); - } - - this.logger = logger; - this.cookieJar = requestModule.jar(); - this.request = requestModule.defaults({ jar: this.cookieJar }); - } - - public send(httpRequest: HttpRequest): Promise { - return new Promise((resolve, reject) => { - - let requestBody: Buffer | string; - if (isArrayBuffer(httpRequest.content)) { - requestBody = Buffer.from(httpRequest.content); - } else { - requestBody = httpRequest.content || ""; - } - - const currentRequest = this.request(httpRequest.url!, { - body: requestBody, - // If binary is expected 'null' should be used, otherwise for text 'utf8' - encoding: httpRequest.responseType === "arraybuffer" ? null : "utf8", - headers: { - // Tell auth middleware to 401 instead of redirecting - "X-Requested-With": "XMLHttpRequest", - ...httpRequest.headers, - }, - method: httpRequest.method, - timeout: httpRequest.timeout, - }, - (error, response, body) => { - if (httpRequest.abortSignal) { - httpRequest.abortSignal.onabort = null; - } - - if (error) { - if (error.code === "ETIMEDOUT") { - this.logger.log(LogLevel.Warning, `Timeout from HTTP request.`); - reject(new TimeoutError()); - } - this.logger.log(LogLevel.Warning, `Error from HTTP request. ${error}`); - reject(error); - return; - } - - if (response.statusCode >= 200 && response.statusCode < 300) { - resolve(new HttpResponse(response.statusCode, response.statusMessage || "", body)); - } else { - reject(new HttpError(response.statusMessage || "", response.statusCode || 0)); - } - }); - - if (httpRequest.abortSignal) { - httpRequest.abortSignal.onabort = () => { - currentRequest.abort(); - reject(new AbortError()); - }; - } - }); - } - - public getCookieString(url: string): string { - return this.cookieJar.getCookieString(url); - } -} diff --git a/src/SignalR/clients/ts/signalr/src/ServerSentEventsTransport.ts b/src/SignalR/clients/ts/signalr/src/ServerSentEventsTransport.ts index 09463c5e70..de4bf3e2b7 100644 --- a/src/SignalR/clients/ts/signalr/src/ServerSentEventsTransport.ts +++ b/src/SignalR/clients/ts/signalr/src/ServerSentEventsTransport.ts @@ -5,7 +5,7 @@ import { HttpClient } from "./HttpClient"; import { ILogger, LogLevel } from "./ILogger"; import { ITransport, TransferFormat } from "./ITransport"; import { EventSourceConstructor } from "./Polyfills"; -import { Arg, getDataDetail, Platform, sendMessage } from "./Utils"; +import { Arg, getDataDetail, getUserAgentHeader, Platform, sendMessage } from "./Utils"; /** @private */ export class ServerSentEventsTransport implements ITransport { @@ -13,6 +13,7 @@ export class ServerSentEventsTransport implements ITransport { private readonly accessTokenFactory: (() => string | Promise) | undefined; private readonly logger: ILogger; private readonly logMessageContent: boolean; + private readonly withCredentials: boolean; private readonly eventSourceConstructor: EventSourceConstructor; private eventSource?: EventSource; private url?: string; @@ -21,11 +22,12 @@ export class ServerSentEventsTransport implements ITransport { public onclose: ((error?: Error) => void) | null; constructor(httpClient: HttpClient, accessTokenFactory: (() => string | Promise) | undefined, logger: ILogger, - logMessageContent: boolean, eventSourceConstructor: EventSourceConstructor) { + logMessageContent: boolean, eventSourceConstructor: EventSourceConstructor, withCredentials: boolean) { this.httpClient = httpClient; this.accessTokenFactory = accessTokenFactory; this.logger = logger; this.logMessageContent = logMessageContent; + this.withCredentials = withCredentials; this.eventSourceConstructor = eventSourceConstructor; this.onreceive = null; @@ -58,11 +60,17 @@ export class ServerSentEventsTransport implements ITransport { let eventSource: EventSource; if (Platform.isBrowser || Platform.isWebWorker) { - eventSource = new this.eventSourceConstructor(url, { withCredentials: true }); + eventSource = new this.eventSourceConstructor(url, { withCredentials: this.withCredentials }); } else { // Non-browser passes cookies via the dictionary const cookies = this.httpClient.getCookieString(url); - eventSource = new this.eventSourceConstructor(url, { withCredentials: true, headers: { Cookie: cookies } } as EventSourceInit); + const headers = { + Cookie: cookies, + }; + const [name, value] = getUserAgentHeader(); + headers[name] = value; + + eventSource = new this.eventSourceConstructor(url, { withCredentials: this.withCredentials, headers } as EventSourceInit); } try { @@ -104,7 +112,7 @@ export class ServerSentEventsTransport implements ITransport { if (!this.eventSource) { return Promise.reject(new Error("Cannot send until the transport is connected")); } - return sendMessage(this.logger, "SSE", this.httpClient, this.url!, this.accessTokenFactory, data, this.logMessageContent); + return sendMessage(this.logger, "SSE", this.httpClient, this.url!, this.accessTokenFactory, data, this.logMessageContent, this.withCredentials); } public stop(): Promise { diff --git a/src/SignalR/clients/ts/signalr/src/Utils.ts b/src/SignalR/clients/ts/signalr/src/Utils.ts index 1233ce6c51..3f7318cd68 100644 --- a/src/SignalR/clients/ts/signalr/src/Utils.ts +++ b/src/SignalR/clients/ts/signalr/src/Utils.ts @@ -7,6 +7,10 @@ import { NullLogger } from "./Loggers"; import { IStreamSubscriber, ISubscription } from "./Stream"; import { Subject } from "./Subject"; +// Version token that will be replaced by the prepack command +/** The version of the SignalR client. */ +export const VERSION: string = "0.0.0-DEV_BUILD"; + /** @private */ export class Arg { public static isRequired(val: any, name: string): void { @@ -25,7 +29,6 @@ export class Arg { /** @private */ export class Platform { - public static get isBrowser(): boolean { return typeof window === "object"; } @@ -81,8 +84,9 @@ export function isArrayBuffer(val: any): val is ArrayBuffer { } /** @private */ -export async function sendMessage(logger: ILogger, transportName: string, httpClient: HttpClient, url: string, accessTokenFactory: (() => string | Promise) | undefined, content: string | ArrayBuffer, logMessageContent: boolean): Promise { - let headers; +export async function sendMessage(logger: ILogger, transportName: string, httpClient: HttpClient, url: string, accessTokenFactory: (() => string | Promise) | undefined, + content: string | ArrayBuffer, logMessageContent: boolean, withCredentials: boolean): Promise { + let headers = {}; if (accessTokenFactory) { const token = await accessTokenFactory(); if (token) { @@ -92,6 +96,9 @@ export async function sendMessage(logger: ILogger, transportName: string, httpCl } } + const [name, value] = getUserAgentHeader(); + headers[name] = value; + logger.log(LogLevel.Trace, `(${transportName} transport) sending data. ${getDataDetail(content, logMessageContent)}.`); const responseType = isArrayBuffer(content) ? "arraybuffer" : "text"; @@ -99,6 +106,7 @@ export async function sendMessage(logger: ILogger, transportName: string, httpCl content, headers, responseType, + withCredentials, }); logger.log(LogLevel.Trace, `(${transportName} transport) request complete. Response status: ${response.statusCode}.`); @@ -181,3 +189,71 @@ export class ConsoleLogger implements ILogger { } } } + +/** @private */ +export function getUserAgentHeader(): [string, string] { + let userAgentHeaderName = "X-SignalR-User-Agent"; + if (Platform.isNode) { + userAgentHeaderName = "User-Agent"; + } + return [ userAgentHeaderName, constructUserAgent(VERSION, getOsName(), getRuntime(), getRuntimeVersion()) ]; +} + +/** @private */ +export function constructUserAgent(version: string, os: string, runtime: string, runtimeVersion: string | undefined): string { + // Microsoft SignalR/[Version] ([Detailed Version]; [Operating System]; [Runtime]; [Runtime Version]) + let userAgent: string = "Microsoft SignalR/"; + + const majorAndMinor = version.split("."); + userAgent += `${majorAndMinor[0]}.${majorAndMinor[1]}`; + userAgent += ` (${version}; `; + + if (os && os !== "") { + userAgent += `${os}; `; + } else { + userAgent += "Unknown OS; "; + } + + userAgent += `${runtime}`; + + if (runtimeVersion) { + userAgent += `; ${runtimeVersion}`; + } else { + userAgent += "; Unknown Runtime Version"; + } + + userAgent += ")"; + return userAgent; +} + +function getOsName(): string { + if (Platform.isNode) { + switch (process.platform) { + case "win32": + return "Windows NT"; + case "darwin": + return "macOS"; + case "linux": + return "Linux"; + default: + return process.platform; + } + } else { + return ""; + } +} + +function getRuntimeVersion(): string | undefined { + if (Platform.isNode) { + return process.versions.node; + } + return undefined; +} + +function getRuntime(): string { + if (Platform.isNode) { + return "NodeJS"; + } else { + return "Browser"; + } +} diff --git a/src/SignalR/clients/ts/signalr/src/WebSocketTransport.ts b/src/SignalR/clients/ts/signalr/src/WebSocketTransport.ts index 418c306055..4eb723f76e 100644 --- a/src/SignalR/clients/ts/signalr/src/WebSocketTransport.ts +++ b/src/SignalR/clients/ts/signalr/src/WebSocketTransport.ts @@ -5,7 +5,7 @@ import { HttpClient } from "./HttpClient"; import { ILogger, LogLevel } from "./ILogger"; import { ITransport, TransferFormat } from "./ITransport"; import { WebSocketConstructor } from "./Polyfills"; -import { Arg, getDataDetail, Platform } from "./Utils"; +import { Arg, getDataDetail, getUserAgentHeader, Platform } from "./Utils"; /** @private */ export class WebSocketTransport implements ITransport { @@ -35,7 +35,6 @@ export class WebSocketTransport implements ITransport { Arg.isRequired(url, "url"); Arg.isRequired(transferFormat, "transferFormat"); Arg.isIn(transferFormat, TransferFormat, "transferFormat"); - this.logger.log(LogLevel.Trace, "(WebSockets transport) Connecting."); if (this.accessTokenFactory) { @@ -51,12 +50,18 @@ export class WebSocketTransport implements ITransport { const cookies = this.httpClient.getCookieString(url); let opened = false; - if (Platform.isNode && cookies) { + if (Platform.isNode) { + const headers = {}; + const [name, value] = getUserAgentHeader(); + headers[name] = value; + + if (cookies) { + headers[`Cookie`] = `${cookies}`; + } + // Only pass cookies when in non-browser environments webSocket = new this.webSocketConstructor(url, undefined, { - headers: { - Cookie: `${cookies}`, - }, + headers, }); } @@ -92,7 +97,12 @@ export class WebSocketTransport implements ITransport { webSocket.onmessage = (message: MessageEvent) => { this.logger.log(LogLevel.Trace, `(WebSockets transport) data received. ${getDataDetail(message.data, this.logMessageContent)}.`); if (this.onreceive) { - this.onreceive(message.data); + try { + this.onreceive(message.data); + } catch (error) { + this.close(error); + return; + } } }; @@ -128,13 +138,6 @@ export class WebSocketTransport implements ITransport { public stop(): Promise { if (this.webSocket) { - // Clear websocket handlers because we are considering the socket closed now - this.webSocket.onclose = () => {}; - this.webSocket.onmessage = () => {}; - this.webSocket.onerror = () => {}; - this.webSocket.close(); - this.webSocket = undefined; - // Manually invoke onclose callback inline so we know the HttpConnection was closed properly before returning // This also solves an issue where websocket.onclose could take 18+ seconds to trigger during network disconnects this.close(undefined); @@ -143,15 +146,30 @@ export class WebSocketTransport implements ITransport { return Promise.resolve(); } - private close(event?: CloseEvent): void { + private close(event?: CloseEvent | Error): void { // webSocket will be null if the transport did not start successfully + if (this.webSocket) { + // Clear websocket handlers because we are considering the socket closed now + this.webSocket.onclose = () => {}; + this.webSocket.onmessage = () => {}; + this.webSocket.onerror = () => {}; + this.webSocket.close(); + this.webSocket = undefined; + } + this.logger.log(LogLevel.Trace, "(WebSockets transport) socket closed."); if (this.onclose) { - if (event && (event.wasClean === false || event.code !== 1000)) { + if (this.isCloseEvent(event) && (event.wasClean === false || event.code !== 1000)) { this.onclose(new Error(`WebSocket closed with status code: ${event.code} (${event.reason}).`)); + } else if (event instanceof Error) { + this.onclose(event); } else { this.onclose(); } } } + + private isCloseEvent(event?: any): event is CloseEvent { + return event && typeof event.wasClean === "boolean" && typeof event.code === "number"; + } } diff --git a/src/SignalR/clients/ts/signalr/src/XhrHttpClient.ts b/src/SignalR/clients/ts/signalr/src/XhrHttpClient.ts index 3b27bdded5..41eba9c564 100644 --- a/src/SignalR/clients/ts/signalr/src/XhrHttpClient.ts +++ b/src/SignalR/clients/ts/signalr/src/XhrHttpClient.ts @@ -31,7 +31,7 @@ export class XhrHttpClient extends HttpClient { const xhr = new XMLHttpRequest(); xhr.open(request.method!, request.url!, true); - xhr.withCredentials = true; + xhr.withCredentials = request.withCredentials === undefined ? true : request.withCredentials; xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest"); // Explicitly setting the Content-Type header for React Native on Android platform. xhr.setRequestHeader("Content-Type", "text/plain;charset=UTF-8"); diff --git a/src/SignalR/clients/ts/signalr/src/index.ts b/src/SignalR/clients/ts/signalr/src/index.ts index dc2086c661..df4e0fba4a 100644 --- a/src/SignalR/clients/ts/signalr/src/index.ts +++ b/src/SignalR/clients/ts/signalr/src/index.ts @@ -1,10 +1,6 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// Version token that will be replaced by the prepack command -/** The version of the SignalR client. */ -export const VERSION: string = "0.0.0-DEV_BUILD"; - // Everything that users need to access must be exported here. Including interfaces. export { AbortSignal } from "./AbortController"; export { AbortError, HttpError, TimeoutError } from "./Errors"; @@ -22,3 +18,4 @@ export { NullLogger } from "./Loggers"; export { JsonHubProtocol } from "./JsonHubProtocol"; export { Subject } from "./Subject"; export { IRetryPolicy, RetryContext } from "./IRetryPolicy"; +export { VERSION } from "./Utils"; diff --git a/src/SignalR/clients/ts/signalr/tests/HttpConnection.test.ts b/src/SignalR/clients/ts/signalr/tests/HttpConnection.test.ts index d010fbdbc6..67d889974e 100644 --- a/src/SignalR/clients/ts/signalr/tests/HttpConnection.test.ts +++ b/src/SignalR/clients/ts/signalr/tests/HttpConnection.test.ts @@ -5,8 +5,10 @@ import { HttpResponse } from "../src/HttpClient"; import { HttpConnection, INegotiateResponse, TransportSendQueue } from "../src/HttpConnection"; import { IHttpConnectionOptions } from "../src/IHttpConnectionOptions"; import { HttpTransportType, ITransport, TransferFormat } from "../src/ITransport"; +import { getUserAgentHeader } from "../src/Utils"; import { HttpError } from "../src/Errors"; +import { ILogger, LogLevel } from "../src/ILogger"; import { NullLogger } from "../src/Loggers"; import { EventSourceConstructor, WebSocketConstructor } from "../src/Polyfills"; @@ -191,9 +193,9 @@ describe("HttpConnection", () => { const connection = new HttpConnection("http://tempuri.org", options); await expect(connection.start(TransferFormat.Text)) .rejects - .toThrow("Unexpected status code returned from negotiate 999"); + .toThrow("Unexpected status code returned from negotiate '999'"); }, - "Failed to start the connection: Error: Unexpected status code returned from negotiate 999"); + "Failed to start the connection: Error: Unexpected status code returned from negotiate '999'"); }); it("all transport failure errors get aggregated", async () => { @@ -1124,6 +1126,117 @@ describe("HttpConnection", () => { "Failed to start the transport 'WebSockets': Error: There was an error with the transport."); }); + it("user agent header set on negotiate", async () => { + await VerifyLogger.run(async (logger) => { + let userAgentValue: string = ""; + const options: IHttpConnectionOptions = { + ...commonOptions, + httpClient: new TestHttpClient() + .on("POST", (r) => { + userAgentValue = r.headers![`User-Agent`]; + return new HttpResponse(200, "", "{\"error\":\"nope\"}"); + }), + logger, + } as IHttpConnectionOptions; + + const connection = new HttpConnection("http://tempuri.org", options); + try { + await connection.start(TransferFormat.Text); + } catch { + } finally { + await connection.stop(); + } + + const [, value] = getUserAgentHeader(); + expect(userAgentValue).toEqual(value); + }, "Failed to start the connection: Error: nope"); + }); + + it("logMessageContent displays correctly with binary data", async () => { + await VerifyLogger.run(async (logger) => { + const availableTransport = { transport: "LongPolling", transferFormats: ["Text", "Binary"] }; + + let sentMessage = ""; + const captureLogger: ILogger = { + log: (logLevel: LogLevel, message: string) => { + if (logLevel === LogLevel.Trace && message.search("data of length") > 0) { + sentMessage = message; + } + + logger.log(logLevel, message); + }, + }; + + let httpClientGetCount = 0; + const options: IHttpConnectionOptions = { + ...commonOptions, + httpClient: new TestHttpClient() + .on("POST", () => ({ connectionId: "42", availableTransports: [availableTransport] })) + .on("GET", () => { + httpClientGetCount++; + if (httpClientGetCount === 1) { + // First long polling request must succeed so start completes + return ""; + } + return Promise.resolve(); + }) + .on("DELETE", () => new HttpResponse(202)), + logMessageContent: true, + logger: captureLogger, + transport: HttpTransportType.LongPolling, + } as IHttpConnectionOptions; + + const connection = new HttpConnection("http://tempuri.org", options); + connection.onreceive = () => null; + try { + await connection.start(TransferFormat.Binary); + await connection.send(new Uint8Array([0x68, 0x69, 0x20, 0x3a, 0x29])); + } finally { + await connection.stop(); + } + + expect(sentMessage).toBe("(LongPolling transport) sending data. Binary data of length 5. Content: '0x68 0x69 0x20 0x3a 0x29'."); + }); + }); + + it("send after restarting connection works", async () => { + await VerifyLogger.run(async (logger) => { + const options: IHttpConnectionOptions = { + ...commonOptions, + WebSocket: TestWebSocket, + httpClient: new TestHttpClient() + .on("POST", () => defaultNegotiateResponse) + .on("GET", () => ""), + logger, + } as IHttpConnectionOptions; + + const connection = new HttpConnection("http://tempuri.org", options); + const closePromise = new PromiseSource(); + connection.onclose = (e) => { + closePromise.resolve(); + }; + + TestWebSocket.webSocketSet = new PromiseSource(); + let startPromise = connection.start(TransferFormat.Text); + await TestWebSocket.webSocketSet; + await TestWebSocket.webSocket.openSet; + TestWebSocket.webSocket.onopen(new TestEvent()); + await startPromise; + + await connection.send("text"); + TestWebSocket.webSocket.close(); + TestWebSocket.webSocketSet = new PromiseSource(); + + await closePromise; + + startPromise = connection.start(TransferFormat.Text); + await TestWebSocket.webSocketSet; + TestWebSocket.webSocket.onopen(new TestEvent()); + await startPromise; + await connection.send("text"); + }); + }); + describe(".constructor", () => { it("throws if no Url is provided", async () => { // Force TypeScript to let us call the constructor incorrectly :) @@ -1386,7 +1499,7 @@ describe("TransportSendQueue", () => { const queue = new TransportSendQueue(transport); - const first = queue.send(new Uint8Array([4, 5, 6])); + const first = queue.send(new Uint8Array([4, 5, 6]).buffer); // This should allow first to enter transport.send promiseSource1.resolve(); // Wait until we're inside transport.send @@ -1401,8 +1514,8 @@ describe("TransportSendQueue", () => { await Promise.all([first, second, third]); expect(sendMock.mock.calls.length).toBe(2); - expect(sendMock.mock.calls[0][0]).toEqual(new Uint8Array([4, 5, 6])); - expect(sendMock.mock.calls[1][0]).toEqual(new Uint8Array([7, 8, 10, 12, 14])); + expect(sendMock.mock.calls[0][0]).toEqual(new Uint8Array([4, 5, 6]).buffer); + expect(sendMock.mock.calls[1][0]).toEqual(new Uint8Array([7, 8, 10, 12, 14]).buffer); await queue.stop(); }); diff --git a/src/SignalR/clients/ts/signalr/tests/HubConnection.test.ts b/src/SignalR/clients/ts/signalr/tests/HubConnection.test.ts index 1cb682cabb..797e0e1ea1 100644 --- a/src/SignalR/clients/ts/signalr/tests/HubConnection.test.ts +++ b/src/SignalR/clients/ts/signalr/tests/HubConnection.test.ts @@ -127,6 +127,25 @@ describe("HubConnection", () => { } }); }); + + it("does not send pings for connection with inherentKeepAlive", async () => { + await VerifyLogger.run(async (logger) => { + const connection = new TestConnection(true, true); + const hubConnection = createHubConnection(connection, logger); + + hubConnection.keepAliveIntervalInMilliseconds = 5; + + try { + await hubConnection.start(); + await delayUntil(500); + + const numPings = connection.sentData.filter((s) => JSON.parse(s).type === MessageType.Ping).length; + expect(numPings).toEqual(0); + } finally { + await hubConnection.stop(); + } + }); + }); }); describe("stop", () => { @@ -165,7 +184,6 @@ describe("HubConnection", () => { "arg", 42, ], - streamIds: [], target: "testMethod", type: MessageType.Invocation, }); @@ -194,7 +212,6 @@ describe("HubConnection", () => { "arg", null, ], - streamIds: [], target: "testMethod", type: MessageType.Invocation, }); @@ -226,7 +243,6 @@ describe("HubConnection", () => { 42, ], invocationId: connection.lastInvocationId, - streamIds: [], target: "testMethod", type: MessageType.Invocation, }); @@ -979,7 +995,6 @@ describe("HubConnection", () => { 42, ], invocationId: connection.lastInvocationId, - streamIds: [], target: "testStream", type: MessageType.StreamInvocation, }); diff --git a/src/SignalR/clients/ts/signalr/tests/LongPollingTransport.test.ts b/src/SignalR/clients/ts/signalr/tests/LongPollingTransport.test.ts index 2e3dc670e4..853b3527ec 100644 --- a/src/SignalR/clients/ts/signalr/tests/LongPollingTransport.test.ts +++ b/src/SignalR/clients/ts/signalr/tests/LongPollingTransport.test.ts @@ -4,6 +4,7 @@ import { HttpResponse } from "../src/HttpClient"; import { TransferFormat } from "../src/ITransport"; import { LongPollingTransport } from "../src/LongPollingTransport"; +import { getUserAgentHeader } from "../src/Utils"; import { VerifyLogger } from "./Common"; import { TestHttpClient } from "./TestHttpClient"; @@ -39,7 +40,7 @@ describe("LongPollingTransport", () => { } }) .on("DELETE", () => new HttpResponse(202)); - const transport = new LongPollingTransport(client, undefined, logger, false); + const transport = new LongPollingTransport(client, undefined, logger, false, true); await transport.connect("http://example.com", TransferFormat.Text); const stopPromise = transport.stop(); @@ -63,7 +64,7 @@ describe("LongPollingTransport", () => { return new HttpResponse(204); } }); - const transport = new LongPollingTransport(client, undefined, logger, false); + const transport = new LongPollingTransport(client, undefined, logger, false, true); const stopPromise = makeClosedPromise(transport); @@ -96,7 +97,7 @@ describe("LongPollingTransport", () => { return new HttpResponse(202); }); - const transport = new LongPollingTransport(httpClient, undefined, logger, false); + const transport = new LongPollingTransport(httpClient, undefined, logger, false, true); await transport.connect("http://tempuri.org", TransferFormat.Text); @@ -120,6 +121,50 @@ describe("LongPollingTransport", () => { await stopPromise; }); }); + + it("user agent header set on sends and polls", async () => { + await VerifyLogger.run(async (logger) => { + let firstPoll = true; + let firstPollUserAgent = ""; + let secondPollUserAgent = ""; + let deleteUserAgent = ""; + const pollingPromiseSource = new PromiseSource(); + const httpClient = new TestHttpClient() + .on("GET", async (r) => { + if (firstPoll) { + firstPoll = false; + firstPollUserAgent = r.headers![`User-Agent`]; + return new HttpResponse(200); + } else { + secondPollUserAgent = r.headers![`User-Agent`]; + await pollingPromiseSource.promise; + return new HttpResponse(204); + } + }) + .on("DELETE", async (r) => { + deleteUserAgent = r.headers![`User-Agent`]; + return new HttpResponse(202); + }); + + const transport = new LongPollingTransport(httpClient, undefined, logger, false, true); + + await transport.connect("http://tempuri.org", TransferFormat.Text); + + // Begin stopping transport + const stopPromise = transport.stop(); + + // Allow polling to complete + pollingPromiseSource.resolve(); + + // Wait for stop to complete + await stopPromise; + + const [, value] = getUserAgentHeader(); + expect(firstPollUserAgent).toEqual(value); + expect(deleteUserAgent).toEqual(value); + expect(secondPollUserAgent).toEqual(value); + }); + }); }); function makeClosedPromise(transport: LongPollingTransport): Promise { diff --git a/src/SignalR/clients/ts/signalr/tests/MessageSize.test.ts b/src/SignalR/clients/ts/signalr/tests/MessageSize.test.ts new file mode 100644 index 0000000000..1d06879acf --- /dev/null +++ b/src/SignalR/clients/ts/signalr/tests/MessageSize.test.ts @@ -0,0 +1,175 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +import { HubConnection } from "../src/HubConnection"; +import { IConnection } from "../src/IConnection"; +import { IHubProtocol, MessageType } from "../src/IHubProtocol"; +import { ILogger } from "../src/ILogger"; +import { JsonHubProtocol } from "../src/JsonHubProtocol"; +import { NullLogger } from "../src/Loggers"; +import { Subject } from "../src/Subject"; +import { VerifyLogger } from "./Common"; +import { TestConnection } from "./TestConnection"; +import { delayUntil, registerUnhandledRejectionHandler } from "./Utils"; + +registerUnhandledRejectionHandler(); + +function createHubConnection(connection: IConnection, logger?: ILogger | null, protocol?: IHubProtocol | null) { + return HubConnection.create(connection, logger || NullLogger.instance, protocol || new JsonHubProtocol()); +} + +// These tests check that the message size doesn't change without us being aware of it and making a conscious decision to increase the size + +describe("Message size", () => { + it("send invocation", async () => { + await VerifyLogger.run(async (logger) => { + const connection = new TestConnection(); + + const hubConnection = createHubConnection(connection, logger); + try { + // We don't actually care to wait for the send. + // tslint:disable-next-line:no-floating-promises + hubConnection.send("target", 1) + .catch((_) => { }); // Suppress exception and unhandled promise rejection warning. + + // Verify the message is sent + expect(connection.sentData.length).toBe(1); + expect(connection.parsedSentData[0].type).toEqual(MessageType.Invocation); + expect((connection.sentData[0] as string).length).toEqual(44); + } finally { + // Close the connection + await hubConnection.stop(); + } + }); + }); + + it("invoke invocation", async () => { + await VerifyLogger.run(async (logger) => { + const connection = new TestConnection(); + + const hubConnection = createHubConnection(connection, logger); + try { + // We don't actually care to wait for the invoke. + // tslint:disable-next-line:no-floating-promises + hubConnection.invoke("target", 1) + .catch((_) => { }); // Suppress exception and unhandled promise rejection warning. + + // Verify the message is sent + expect(connection.sentData.length).toBe(1); + expect(connection.parsedSentData[0].type).toEqual(MessageType.Invocation); + expect((connection.sentData[0] as string).length).toEqual(63); + } finally { + // Close the connection + await hubConnection.stop(); + } + }); + }); + + it("stream invocation", async () => { + await VerifyLogger.run(async (logger) => { + const connection = new TestConnection(); + + const hubConnection = createHubConnection(connection, logger); + try { + hubConnection.stream("target", 1); + + // Verify the message is sent + expect(connection.sentData.length).toBe(1); + expect(connection.parsedSentData[0].type).toEqual(MessageType.StreamInvocation); + expect((connection.sentData[0] as string).length).toEqual(63); + } finally { + // Close the connection + await hubConnection.stop(); + } + }); + }); + + it("upload invocation", async () => { + await VerifyLogger.run(async (logger) => { + const connection = new TestConnection(); + + const hubConnection = createHubConnection(connection, logger); + try { + // We don't actually care to wait for the invoke. + // tslint:disable-next-line:no-floating-promises + hubConnection.invoke("target", 1, new Subject()) + .catch((_) => { }); // Suppress exception and unhandled promise rejection warning. + + // Verify the message is sent + expect(connection.sentData.length).toBe(1); + expect(connection.parsedSentData[0].type).toEqual(MessageType.Invocation); + expect((connection.sentData[0] as string).length).toEqual(81); + } finally { + // Close the connection + await hubConnection.stop(); + } + }); + }); + + it("upload stream invocation", async () => { + await VerifyLogger.run(async (logger) => { + const connection = new TestConnection(); + + const hubConnection = createHubConnection(connection, logger); + try { + hubConnection.stream("target", 1, new Subject()); + + // Verify the message is sent + expect(connection.sentData.length).toBe(1); + expect(connection.parsedSentData[0].type).toEqual(MessageType.StreamInvocation); + expect((connection.sentData[0] as string).length).toEqual(81); + } finally { + // Close the connection + await hubConnection.stop(); + } + }); + }); + + it("completion message", async () => { + await VerifyLogger.run(async (logger) => { + const connection = new TestConnection(); + + const hubConnection = createHubConnection(connection, logger); + try { + const subject = new Subject(); + hubConnection.stream("target", 1, subject); + subject.complete(); + + await delayUntil(1000, () => connection.sentData.length === 2); + + // Verify the message is sent + expect(connection.sentData.length).toBe(2); + expect(connection.parsedSentData[1].type).toEqual(MessageType.Completion); + expect((connection.sentData[1] as string).length).toEqual(29); + } finally { + // Close the connection + await hubConnection.stop(); + } + }); + }); + + it("cancel message", async () => { + await VerifyLogger.run(async (logger) => { + const connection = new TestConnection(); + + const hubConnection = createHubConnection(connection, logger); + try { + hubConnection.stream("target", 1).subscribe({ + complete: () => {}, + error: () => {}, + next: () => {}, + }).dispose(); + + await delayUntil(1000, () => connection.sentData.length === 2); + + // Verify the message is sent + expect(connection.sentData.length).toBe(2); + expect(connection.parsedSentData[1].type).toEqual(MessageType.CancelInvocation); + expect((connection.sentData[1] as string).length).toEqual(29); + } finally { + // Close the connection + await hubConnection.stop(); + } + }); + }); +}); diff --git a/src/SignalR/clients/ts/signalr/tests/ServerSentEventsTransport.test.ts b/src/SignalR/clients/ts/signalr/tests/ServerSentEventsTransport.test.ts index bad6a9fb3a..9fb038fa0f 100644 --- a/src/SignalR/clients/ts/signalr/tests/ServerSentEventsTransport.test.ts +++ b/src/SignalR/clients/ts/signalr/tests/ServerSentEventsTransport.test.ts @@ -6,6 +6,7 @@ import { TransferFormat } from "../src/ITransport"; import { HttpClient, HttpRequest } from "../src/HttpClient"; import { ILogger } from "../src/ILogger"; import { ServerSentEventsTransport } from "../src/ServerSentEventsTransport"; +import { getUserAgentHeader } from "../src/Utils"; import { VerifyLogger } from "./Common"; import { TestEventSource, TestMessageEvent } from "./TestEventSource"; import { TestHttpClient } from "./TestHttpClient"; @@ -16,7 +17,7 @@ registerUnhandledRejectionHandler(); describe("ServerSentEventsTransport", () => { it("does not allow non-text formats", async () => { await VerifyLogger.run(async (logger) => { - const sse = new ServerSentEventsTransport(new TestHttpClient(), undefined, logger, true, TestEventSource); + const sse = new ServerSentEventsTransport(new TestHttpClient(), undefined, logger, true, TestEventSource, true); await expect(sse.connect("", TransferFormat.Binary)) .rejects @@ -26,7 +27,7 @@ describe("ServerSentEventsTransport", () => { it("connect waits for EventSource to be connected", async () => { await VerifyLogger.run(async (logger) => { - const sse = new ServerSentEventsTransport(new TestHttpClient(), undefined, logger, true, TestEventSource); + const sse = new ServerSentEventsTransport(new TestHttpClient(), undefined, logger, true, TestEventSource, true); let connectComplete: boolean = false; const connectPromise = (async () => { @@ -47,7 +48,7 @@ describe("ServerSentEventsTransport", () => { it("connect failure does not call onclose handler", async () => { await VerifyLogger.run(async (logger) => { - const sse = new ServerSentEventsTransport(new TestHttpClient(), undefined, logger, true, TestEventSource); + const sse = new ServerSentEventsTransport(new TestHttpClient(), undefined, logger, true, TestEventSource, true); let closeCalled = false; sse.onclose = () => closeCalled = true; @@ -168,7 +169,7 @@ describe("ServerSentEventsTransport", () => { it("send throws if not connected", async () => { await VerifyLogger.run(async (logger) => { - const sse = new ServerSentEventsTransport(new TestHttpClient(), undefined, logger, true, TestEventSource); + const sse = new ServerSentEventsTransport(new TestHttpClient(), undefined, logger, true, TestEventSource, true); await expect(sse.send("")) .rejects @@ -200,10 +201,30 @@ describe("ServerSentEventsTransport", () => { expect(error).toEqual(new Error("error parsing")); }); }); + + it("sets user agent header on connect and sends", async () => { + await VerifyLogger.run(async (logger) => { + let request: HttpRequest; + const httpClient = new TestHttpClient().on((r) => { + request = r; + return ""; + }); + + const sse = await createAndStartSSE(logger, "http://example.com", undefined, httpClient); + + let [, value] = getUserAgentHeader(); + expect((TestEventSource.eventSource.eventSourceInitDict as any).headers[`User-Agent`]).toEqual(value); + await sse.send(""); + + [, value] = getUserAgentHeader(); + expect(request!.headers![`User-Agent`]).toBe(value); + expect(request!.url).toBe("http://example.com"); + }); + }); }); async function createAndStartSSE(logger: ILogger, url?: string, accessTokenFactory?: (() => string | Promise), httpClient?: HttpClient): Promise { - const sse = new ServerSentEventsTransport(httpClient || new TestHttpClient(), accessTokenFactory, logger, true, TestEventSource); + const sse = new ServerSentEventsTransport(httpClient || new TestHttpClient(), accessTokenFactory, logger, true, TestEventSource, true); const connectPromise = sse.connect(url || "http://example.com", TransferFormat.Text); await TestEventSource.eventSource.openSet; diff --git a/src/SignalR/clients/ts/signalr/tests/TestConnection.ts b/src/SignalR/clients/ts/signalr/tests/TestConnection.ts index 0ad7d89a95..0506d76ea9 100644 --- a/src/SignalR/clients/ts/signalr/tests/TestConnection.ts +++ b/src/SignalR/clients/ts/signalr/tests/TestConnection.ts @@ -13,17 +13,20 @@ export class TestConnection implements IConnection { public onclose: ((error?: Error) => void) | null; public sentData: any[]; + public parsedSentData: any[]; public lastInvocationId: string | null; private autoHandshake: boolean | null; - constructor(autoHandshake: boolean = true) { + constructor(autoHandshake: boolean = true, hasInherentKeepAlive: boolean = false) { this.onreceive = null; this.onclose = null; this.sentData = []; + this.parsedSentData = []; this.lastInvocationId = null; this.autoHandshake = autoHandshake; this.baseUrl = "http://example.com"; + this.features.inherentKeepAlive = hasInherentKeepAlive; } public start(): Promise { @@ -42,8 +45,10 @@ export class TestConnection implements IConnection { } if (this.sentData) { this.sentData.push(invocation); + this.parsedSentData.push(parsedInvocation); } else { this.sentData = [invocation]; + this.parsedSentData = [parsedInvocation]; } return Promise.resolve(); } diff --git a/src/SignalR/clients/ts/signalr/tests/TestEventSource.ts b/src/SignalR/clients/ts/signalr/tests/TestEventSource.ts index d77f350616..49b63afe40 100644 --- a/src/SignalR/clients/ts/signalr/tests/TestEventSource.ts +++ b/src/SignalR/clients/ts/signalr/tests/TestEventSource.ts @@ -11,6 +11,7 @@ export class TestEventSource { public onmessage!: (evt: MessageEvent) => any; public readyState: number = 0; public url: string = ""; + public eventSourceInitDict?: EventSourceInit; public withCredentials: boolean = false; // tslint:disable-next-line:variable-name @@ -31,6 +32,7 @@ export class TestEventSource { constructor(url: string, eventSourceInitDict?: EventSourceInit) { this.url = url; + this.eventSourceInitDict = eventSourceInitDict; TestEventSource.eventSource = this; diff --git a/src/SignalR/clients/ts/signalr/tests/TestWebSocket.ts b/src/SignalR/clients/ts/signalr/tests/TestWebSocket.ts index 1eaf631d5d..ebdc6a07fb 100644 --- a/src/SignalR/clients/ts/signalr/tests/TestWebSocket.ts +++ b/src/SignalR/clients/ts/signalr/tests/TestWebSocket.ts @@ -12,6 +12,8 @@ export class TestWebSocket { public protocol: string; public readyState: number = 1; public url: string; + public options?: any; + public closed: boolean = false; public static webSocketSet: PromiseSource; public static webSocket: TestWebSocket; @@ -26,7 +28,10 @@ export class TestWebSocket { } public get onopen(): (this: WebSocket, evt: Event) => any { - return this._onopen!; + return (e) => { + this._onopen!(e); + this.readyState = this.OPEN; + }; } // tslint:disable-next-line:variable-name @@ -38,18 +43,26 @@ export class TestWebSocket { } public get onclose(): (this: WebSocket, evt: Event) => any { - return this._onclose!; + return (e) => { + this._onclose!(e); + this.readyState = this.CLOSED; + }; } public close(code?: number | undefined, reason?: string | undefined): void { + this.closed = true; const closeEvent = new TestCloseEvent(); closeEvent.code = code || 1000; closeEvent.reason = reason!; closeEvent.wasClean = closeEvent.code === 1000; + this.readyState = this.CLOSED; this.onclose(closeEvent); } public send(data: string | ArrayBuffer | Blob | ArrayBufferView): void { + if (this.closed) { + throw new Error(`cannot send from a closed transport: '${data}'`); + } this.receivedData.push(data); } @@ -67,10 +80,11 @@ export class TestWebSocket { throw new Error("Method not implemented."); } - constructor(url: string, protocols?: string | string[]) { + constructor(url: string, protocols?: string | string[], options?: any) { this.url = url; this.protocol = protocols ? (typeof protocols === "string" ? protocols : protocols[0]) : ""; this.receivedData = []; + this.options = options; TestWebSocket.webSocket = this; diff --git a/src/SignalR/clients/ts/signalr/tests/UserAgent.test.ts b/src/SignalR/clients/ts/signalr/tests/UserAgent.test.ts new file mode 100644 index 0000000000..f1e26f0d88 --- /dev/null +++ b/src/SignalR/clients/ts/signalr/tests/UserAgent.test.ts @@ -0,0 +1,17 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +import { constructUserAgent } from "../src/Utils"; + +describe("User Agent", () => { + [["1.0.4-build.10", "Linux", "NodeJS", "10", "Microsoft SignalR/1.0 (1.0.4-build.10; Linux; NodeJS; 10)"], + ["1.4.7-build.10", "", "Browser", "", "Microsoft SignalR/1.4 (1.4.7-build.10; Unknown OS; Browser; Unknown Runtime Version)"], + ["3.1.1-build.10", "macOS", "Browser", "", "Microsoft SignalR/3.1 (3.1.1-build.10; macOS; Browser; Unknown Runtime Version)"], + ["3.1.3-build.10", "", "Browser", "4", "Microsoft SignalR/3.1 (3.1.3-build.10; Unknown OS; Browser; 4)"]] + .forEach(([version, os, runtime, runtimeVersion, expected]) => { + it(`is in correct format`, async () => { + const userAgent = constructUserAgent(version, os, runtime, runtimeVersion); + expect(userAgent).toBe(expected); + }); + }); +}); diff --git a/src/SignalR/clients/ts/signalr/tests/WebSocketTransport.test.ts b/src/SignalR/clients/ts/signalr/tests/WebSocketTransport.test.ts index e26eaa86a3..cb4455a517 100644 --- a/src/SignalR/clients/ts/signalr/tests/WebSocketTransport.test.ts +++ b/src/SignalR/clients/ts/signalr/tests/WebSocketTransport.test.ts @@ -3,6 +3,7 @@ import { ILogger } from "../src/ILogger"; import { TransferFormat } from "../src/ITransport"; +import { getUserAgentHeader } from "../src/Utils"; import { WebSocketTransport } from "../src/WebSocketTransport"; import { VerifyLogger } from "./Common"; import { TestMessageEvent } from "./TestEventSource"; @@ -229,6 +230,92 @@ describe("WebSocketTransport", () => { }); }); }); + + it("sets user agent header on connect", async () => { + await VerifyLogger.run(async (logger) => { + (global as any).ErrorEvent = TestEvent; + const webSocket = await createAndStartWebSocket(logger); + + let closeCalled: boolean = false; + let error: Error; + webSocket.onclose = (e) => { + closeCalled = true; + error = e!; + }; + + const [, value] = getUserAgentHeader(); + expect(TestWebSocket.webSocket.options!.headers[`User-Agent`]).toEqual(value); + + await webSocket.stop(); + + expect(closeCalled).toBe(true); + expect(error!).toBeUndefined(); + + await expect(webSocket.send("")) + .rejects + .toBe("WebSocket is not in the OPEN state"); + }); + }); + + it("is closed from 'onreceive' callback throwing", async () => { + await VerifyLogger.run(async (logger) => { + (global as any).ErrorEvent = TestEvent; + const webSocket = await createAndStartWebSocket(logger); + + let closeCalled: boolean = false; + let error: Error; + webSocket.onclose = (e) => { + closeCalled = true; + error = e!; + }; + + const receiveError = new Error("callback error"); + webSocket.onreceive = (data) => { + throw receiveError; + }; + + const message = new TestMessageEvent(); + message.data = "receive data"; + TestWebSocket.webSocket.onmessage(message); + + expect(closeCalled).toBe(true); + expect(error!).toBe(receiveError); + + await expect(webSocket.send("")) + .rejects + .toBe("WebSocket is not in the OPEN state"); + }); + }); + + it("does not run onclose callback if Transport does not fully connect and exits", async () => { + await VerifyLogger.run(async (logger) => { + (global as any).ErrorEvent = TestErrorEvent; + const webSocket = new WebSocketTransport(new TestHttpClient(), undefined, logger, true, TestWebSocket); + + const connectPromise = webSocket.connect("http://example.com", TransferFormat.Text); + + await TestWebSocket.webSocket.closeSet; + + let closeCalled: boolean = false; + let error: Error; + webSocket.onclose = (e) => { + closeCalled = true; + error = e!; + }; + + const message = new TestCloseEvent(); + message.wasClean = false; + message.code = 1; + message.reason = "just cause"; + TestWebSocket.webSocket.onclose(message); + + expect(closeCalled).toBe(false); + expect(error!).toBeUndefined(); + + TestWebSocket.webSocket.onerror(new TestEvent()); + await expect(connectPromise).rejects.toThrow("There was an error with the transport."); + }); + }); }); async function createAndStartWebSocket(logger: ILogger, url?: string, accessTokenFactory?: (() => string | Promise), format?: TransferFormat): Promise { diff --git a/src/SignalR/clients/ts/signalr/webpack.config.js b/src/SignalR/clients/ts/signalr/webpack.config.js index 75ce866f89..5f3be06dda 100644 --- a/src/SignalR/clients/ts/signalr/webpack.config.js +++ b/src/SignalR/clients/ts/signalr/webpack.config.js @@ -9,6 +9,8 @@ module.exports = env => baseConfig(__dirname, "signalr", { externals: [ "websocket", "eventsource", - "request" + "node-fetch", + "abort-controller", + "fetch-cookie", ] }); \ No newline at end of file diff --git a/src/SignalR/clients/ts/signalr/yarn.lock b/src/SignalR/clients/ts/signalr/yarn.lock index a1429d66f4..e971d5439f 100644 --- a/src/SignalR/clients/ts/signalr/yarn.lock +++ b/src/SignalR/clients/ts/signalr/yarn.lock @@ -2,144 +2,53 @@ # yarn lockfile v1 -"@types/caseless@*": - version "0.12.1" - resolved "https://registry.yarnpkg.com/@types/caseless/-/caseless-0.12.1.tgz#9794c69c8385d0192acc471a540d1f8e0d16218a" - integrity sha512-FhlMa34NHp9K5MY1Uz8yb+ZvuX0pnvn3jScRSNAb75KHGB8d3rEU6hqMs3Z2vjuytcMfRg6c5CHMc3wtYyD2/A== - "@types/eventsource@^1.0.2": version "1.0.2" resolved "https://registry.yarnpkg.com/@types/eventsource/-/eventsource-1.0.2.tgz#5f53734831da4748f9a7b394d79d5822ed110924" integrity sha512-CprOekOB/lzAiGDF1MPWHX053RVTCYyYU3M8HOQXpdD0QfXijM//Na/hZxHaQv4ydsiB1uOBQ3p8S5nXpP4nNQ== -"@types/form-data@*": - version "2.2.1" - resolved "https://registry.yarnpkg.com/@types/form-data/-/form-data-2.2.1.tgz#ee2b3b8eaa11c0938289953606b745b738c54b1e" - integrity sha512-JAMFhOaHIciYVh8fb5/83nmuO/AHwmto+Hq7a9y8FzLDcC1KCU344XDOMEmahnrTFlHjgh4L0WJFczNIX2GxnQ== - dependencies: - "@types/node" "*" - "@types/jest@^23.3.2": version "23.3.14" resolved "https://registry.yarnpkg.com/@types/jest/-/jest-23.3.14.tgz#37daaf78069e7948520474c87b80092ea912520a" integrity sha512-Q5hTcfdudEL2yOmluA1zaSyPbzWPmJ3XfSWeP3RyoYvS9hnje1ZyagrZOuQ6+1nQC1Gw+7gap3pLNL3xL6UBug== -"@types/node@*", "@types/node@^10.9.4": +"@types/node@^10.9.4": version "10.9.4" resolved "https://registry.yarnpkg.com/@types/node/-/node-10.9.4.tgz#0f4cb2dc7c1de6096055357f70179043c33e9897" integrity sha512-fCHV45gS+m3hH17zgkgADUSi2RR1Vht6wOZ0jyHP8rjiQra9f+mIcgwPQHllmDocYOstIEbKlxbFDYlgrTPYqw== -"@types/request@^2.47.1": - version "2.47.1" - resolved "https://registry.yarnpkg.com/@types/request/-/request-2.47.1.tgz#25410d3afbdac04c91a94ad9efc9824100735824" - integrity sha512-TV3XLvDjQbIeVxJ1Z3oCTDk/KuYwwcNKVwz2YaT0F5u86Prgc4syDAp6P96rkTQQ4bIdh+VswQIC9zS6NjY7/g== +"@types/tough-cookie@^2.3.6": + version "2.3.6" + resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-2.3.6.tgz#c880579e087d7a0db13777ff8af689f4ffc7b0d5" + integrity sha512-wHNBMnkoEBiRAd3s8KTKwIuO9biFtTf0LehITzBhSco+HQI0xkXZbLOD55SW3Aqw3oUkHstkm5SPv58yaAdFPQ== + +abort-controller@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" + integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== dependencies: - "@types/caseless" "*" - "@types/form-data" "*" - "@types/node" "*" - "@types/tough-cookie" "*" - -"@types/tough-cookie@*": - version "2.3.3" - resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-2.3.3.tgz#7f226d67d654ec9070e755f46daebf014628e9d9" - integrity sha512-MDQLxNFRLasqS4UlkWMSACMKeSm1x4Q3TxzUC7KQUsh6RK1ZrQ0VEyE3yzXcBu+K8ejVj4wuX32eUG02yNp+YQ== - -ajv@^5.3.0: - version "5.5.2" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.2.tgz#73b5eeca3fab653e3d3f9422b341ad42205dc965" - integrity sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU= - dependencies: - co "^4.6.0" - fast-deep-equal "^1.0.0" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.3.0" - -asn1@~0.2.3: - version "0.2.4" - resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" - integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg== - dependencies: - safer-buffer "~2.1.0" - -assert-plus@1.0.0, assert-plus@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" - integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= + event-target-shim "^5.0.0" async-limiter@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8" integrity sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg== -asynckit@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" - integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= - -aws-sign2@~0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" - integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= - -aws4@^1.8.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f" - integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ== - -bcrypt-pbkdf@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" - integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4= - dependencies: - tweetnacl "^0.14.3" - -caseless@~0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" - integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= - -co@^4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" - integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ= - -combined-stream@^1.0.6, combined-stream@~1.0.6: - version "1.0.7" - resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.7.tgz#2d1d24317afb8abe95d6d2c0b07b57813539d828" - integrity sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w== - dependencies: - delayed-stream "~1.0.0" - -core-util-is@1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" - integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= - -dashdash@^1.12.0: - version "1.14.1" - resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" - integrity sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA= - dependencies: - assert-plus "^1.0.0" - -delayed-stream@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" - integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= - -ecc-jsbn@~0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" - integrity sha1-OoOpBOVDUyh4dMVkt1SThoSamMk= - dependencies: - jsbn "~0.1.0" - safer-buffer "^2.1.0" +es6-denodeify@^0.1.1: + version "0.1.5" + resolved "https://registry.yarnpkg.com/es6-denodeify/-/es6-denodeify-0.1.5.tgz#31d4d5fe9c5503e125460439310e16a2a3f39c1f" + integrity sha1-MdTV/pxVA+ElRgQ5MQ4WoqPznB8= es6-promise@^4.2.2: version "4.2.2" resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.2.tgz#f722d7769af88bd33bc13ec6605e1f92966b82d9" integrity sha512-LSas5vsuA6Q4nEdf9wokY5/AJYXry98i0IzXsv49rYsgDGDNDPbqAYR1Pe23iFxygfbGZNR/5VrHXBCh2BhvUQ== +event-target-shim@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" + integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== + eventsource@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-1.0.7.tgz#8fbc72c93fcd34088090bc0a4e64f4b5cee6d8d0" @@ -147,125 +56,18 @@ eventsource@^1.0.7: dependencies: original "^1.0.0" -extend@~3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" - integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== - -extsprintf@1.3.0, extsprintf@^1.2.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" - integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU= - -fast-deep-equal@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz#c053477817c86b51daa853c81e059b733d023614" - integrity sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ= - -fast-json-stable-stringify@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" - integrity sha1-1RQsDK7msRifh9OnYREGT4bIu/I= - -forever-agent@~0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" - integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= - -form-data@~2.3.2: - version "2.3.3" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" - integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== +fetch-cookie@^0.7.3: + version "0.7.3" + resolved "https://registry.yarnpkg.com/fetch-cookie/-/fetch-cookie-0.7.3.tgz#b8d023f421dd2b2f4a0eca9cd7318a967ed4eed8" + integrity sha512-rZPkLnI8x5V+zYAiz8QonAHsTb4BY+iFowFBI1RFn0zrO343AVp9X7/yUj/9wL6Ef/8fLls8b/vGtzUvmyAUGA== dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.6" - mime-types "^2.1.12" + es6-denodeify "^0.1.1" + tough-cookie "^2.3.3" -getpass@^0.1.1: - version "0.1.7" - resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" - integrity sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo= - dependencies: - assert-plus "^1.0.0" - -har-schema@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" - integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= - -har-validator@~5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.0.tgz#44657f5688a22cfd4b72486e81b3a3fb11742c29" - integrity sha512-+qnmNjI4OfH2ipQ9VQOw23bBd/ibtfbVdK2fYbY4acTDqKTW/YDp9McimZdDbG8iV9fZizUqQMD5xvriB146TA== - dependencies: - ajv "^5.3.0" - har-schema "^2.0.0" - -http-signature@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" - integrity sha1-muzZJRFHcvPZW2WmCruPfBj7rOE= - dependencies: - assert-plus "^1.0.0" - jsprim "^1.2.2" - sshpk "^1.7.0" - -is-typedarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" - integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= - -isstream@~0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" - integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= - -jsbn@~0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" - integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= - -json-schema-traverse@^0.3.0: - version "0.3.1" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340" - integrity sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A= - -json-schema@0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" - integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= - -json-stringify-safe@~5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" - integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= - -jsprim@^1.2.2: - version "1.4.1" - resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" - integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI= - dependencies: - assert-plus "1.0.0" - extsprintf "1.3.0" - json-schema "0.2.3" - verror "1.10.0" - -mime-db@~1.37.0: - version "1.37.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.37.0.tgz#0b6a0ce6fdbe9576e25f1f2d2fde8830dc0ad0d8" - integrity sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg== - -mime-types@^2.1.12, mime-types@~2.1.19: - version "2.1.21" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.21.tgz#28995aa1ecb770742fe6ae7e58f9181c744b3f96" - integrity sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg== - dependencies: - mime-db "~1.37.0" - -oauth-sign@~0.9.0: - version "0.9.0" - resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" - integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== +node-fetch@^2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd" + integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA== original@^1.0.0: version "1.0.2" @@ -274,106 +76,33 @@ original@^1.0.0: dependencies: url-parse "^1.4.3" -performance-now@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" - integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= +psl@^1.1.28: + version "1.7.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.7.0.tgz#f1c4c47a8ef97167dea5d6bbf4816d736e884a3c" + integrity sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ== -psl@^1.1.24: - version "1.1.29" - resolved "https://registry.yarnpkg.com/psl/-/psl-1.1.29.tgz#60f580d360170bb722a797cc704411e6da850c67" - integrity sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ== - -punycode@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" - integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= - -qs@~6.5.2: - version "6.5.2" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" - integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== +punycode@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== querystringify@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.0.0.tgz#fa3ed6e68eb15159457c89b37bc6472833195755" integrity sha512-eTPo5t/4bgaMNZxyjWx6N2a6AuE0mq51KWvpc7nU/MAqixcI6v6KrGUKES0HaomdnolQBBXU/++X6/QQ9KL4tw== -request@^2.88.0: - version "2.88.0" - resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" - integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg== - dependencies: - aws-sign2 "~0.7.0" - aws4 "^1.8.0" - caseless "~0.12.0" - combined-stream "~1.0.6" - extend "~3.0.2" - forever-agent "~0.6.1" - form-data "~2.3.2" - har-validator "~5.1.0" - http-signature "~1.2.0" - is-typedarray "~1.0.0" - isstream "~0.1.2" - json-stringify-safe "~5.0.1" - mime-types "~2.1.19" - oauth-sign "~0.9.0" - performance-now "^2.1.0" - qs "~6.5.2" - safe-buffer "^5.1.2" - tough-cookie "~2.4.3" - tunnel-agent "^0.6.0" - uuid "^3.3.2" - requires-port@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8= -safe-buffer@^5.0.1, safe-buffer@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" - integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== - -safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: - version "2.1.2" - resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" - integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== - -sshpk@^1.7.0: - version "1.15.1" - resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.15.1.tgz#b79a089a732e346c6e0714830f36285cd38191a2" - integrity sha512-mSdgNUaidk+dRU5MhYtN9zebdzF2iG0cNPWy8HG+W8y+fT1JnSkh0fzzpjOa0L7P8i1Rscz38t0h4gPcKz43xA== +tough-cookie@^2.3.3: + version "2.5.0" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" + integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== dependencies: - asn1 "~0.2.3" - assert-plus "^1.0.0" - bcrypt-pbkdf "^1.0.0" - dashdash "^1.12.0" - ecc-jsbn "~0.1.1" - getpass "^0.1.1" - jsbn "~0.1.0" - safer-buffer "^2.0.2" - tweetnacl "~0.14.0" - -tough-cookie@~2.4.3: - version "2.4.3" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781" - integrity sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ== - dependencies: - psl "^1.1.24" - punycode "^1.4.1" - -tunnel-agent@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" - integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0= - dependencies: - safe-buffer "^5.0.1" - -tweetnacl@^0.14.3, tweetnacl@~0.14.0: - version "0.14.5" - resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" - integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= + psl "^1.1.28" + punycode "^2.1.1" url-parse@^1.4.3: version "1.4.3" @@ -383,20 +112,6 @@ url-parse@^1.4.3: querystringify "^2.0.0" requires-port "^1.0.0" -uuid@^3.3.2: - version "3.3.2" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" - integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA== - -verror@1.10.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" - integrity sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA= - dependencies: - assert-plus "^1.0.0" - core-util-is "1.0.2" - extsprintf "^1.2.0" - ws@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/ws/-/ws-6.0.0.tgz#eaa494aded00ac4289d455bac8d84c7c651cef35" diff --git a/src/SignalR/clients/ts/webpack.config.base.js b/src/SignalR/clients/ts/webpack.config.base.js index 57ac83c6a9..a93e6a9941 100644 --- a/src/SignalR/clients/ts/webpack.config.base.js +++ b/src/SignalR/clients/ts/webpack.config.base.js @@ -41,7 +41,6 @@ module.exports = function (modulePath, browserBaseName, options) { resolve: { extensions: [".ts", ".js"], alias: { - "./NodeHttpClient": path.resolve(__dirname, "signalr/src/EmptyNodeHttpClient.ts"), ...options.alias, } }, diff --git a/src/SignalR/common/Http.Connections.Common/ref/Microsoft.AspNetCore.Http.Connections.Common.csproj b/src/SignalR/common/Http.Connections.Common/ref/Microsoft.AspNetCore.Http.Connections.Common.csproj index 8ae1a927db..83ec067e20 100644 --- a/src/SignalR/common/Http.Connections.Common/ref/Microsoft.AspNetCore.Http.Connections.Common.csproj +++ b/src/SignalR/common/Http.Connections.Common/ref/Microsoft.AspNetCore.Http.Connections.Common.csproj @@ -2,6 +2,7 @@ netstandard2.0;$(DefaultNetCoreTargetFramework) + $(DefaultNetCoreTargetFramework) diff --git a/src/SignalR/common/Http.Connections.Common/ref/Microsoft.AspNetCore.Http.Connections.Common.netcoreapp.cs b/src/SignalR/common/Http.Connections.Common/ref/Microsoft.AspNetCore.Http.Connections.Common.netcoreapp.cs index f557b74f58..d5d38749c7 100644 --- a/src/SignalR/common/Http.Connections.Common/ref/Microsoft.AspNetCore.Http.Connections.Common.netcoreapp.cs +++ b/src/SignalR/common/Http.Connections.Common/ref/Microsoft.AspNetCore.Http.Connections.Common.netcoreapp.cs @@ -6,8 +6,8 @@ namespace Microsoft.AspNetCore.Http.Connections public partial class AvailableTransport { public AvailableTransport() { } - public System.Collections.Generic.IList TransferFormats { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public string Transport { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public System.Collections.Generic.IList TransferFormats { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public string Transport { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } public static partial class HttpTransports { @@ -31,12 +31,12 @@ namespace Microsoft.AspNetCore.Http.Connections public partial class NegotiationResponse { public NegotiationResponse() { } - public string AccessToken { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public System.Collections.Generic.IList AvailableTransports { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public string ConnectionId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public string ConnectionToken { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public string Error { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public string Url { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public int Version { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public string AccessToken { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public System.Collections.Generic.IList AvailableTransports { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public string ConnectionId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public string ConnectionToken { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public string Error { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public string Url { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public int Version { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } } diff --git a/src/SignalR/common/Http.Connections.Common/ref/Microsoft.AspNetCore.Http.Connections.Common.netstandard2.0.cs b/src/SignalR/common/Http.Connections.Common/ref/Microsoft.AspNetCore.Http.Connections.Common.netstandard2.0.cs index f557b74f58..d5d38749c7 100644 --- a/src/SignalR/common/Http.Connections.Common/ref/Microsoft.AspNetCore.Http.Connections.Common.netstandard2.0.cs +++ b/src/SignalR/common/Http.Connections.Common/ref/Microsoft.AspNetCore.Http.Connections.Common.netstandard2.0.cs @@ -6,8 +6,8 @@ namespace Microsoft.AspNetCore.Http.Connections public partial class AvailableTransport { public AvailableTransport() { } - public System.Collections.Generic.IList TransferFormats { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public string Transport { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public System.Collections.Generic.IList TransferFormats { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public string Transport { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } public static partial class HttpTransports { @@ -31,12 +31,12 @@ namespace Microsoft.AspNetCore.Http.Connections public partial class NegotiationResponse { public NegotiationResponse() { } - public string AccessToken { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public System.Collections.Generic.IList AvailableTransports { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public string ConnectionId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public string ConnectionToken { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public string Error { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public string Url { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public int Version { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public string AccessToken { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public System.Collections.Generic.IList AvailableTransports { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public string ConnectionId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public string ConnectionToken { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public string Error { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public string Url { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public int Version { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } } diff --git a/src/SignalR/common/Http.Connections.Common/src/Microsoft.AspNetCore.Http.Connections.Common.csproj b/src/SignalR/common/Http.Connections.Common/src/Microsoft.AspNetCore.Http.Connections.Common.csproj index dbf0e04aaf..4277aaef64 100644 --- a/src/SignalR/common/Http.Connections.Common/src/Microsoft.AspNetCore.Http.Connections.Common.csproj +++ b/src/SignalR/common/Http.Connections.Common/src/Microsoft.AspNetCore.Http.Connections.Common.csproj @@ -23,7 +23,7 @@ - + diff --git a/src/SignalR/common/Http.Connections.Common/src/NegotiateProtocol.cs b/src/SignalR/common/Http.Connections.Common/src/NegotiateProtocol.cs index a98e0ba94c..ae69b56cdd 100644 --- a/src/SignalR/common/Http.Connections.Common/src/NegotiateProtocol.cs +++ b/src/SignalR/common/Http.Connections.Common/src/NegotiateProtocol.cs @@ -264,13 +264,11 @@ namespace Microsoft.AspNetCore.Http.Connections switch (reader.TokenType) { case JsonTokenType.PropertyName: - var memberName = reader.ValueSpan; - - if (memberName.SequenceEqual(TransportPropertyNameBytes.EncodedUtf8Bytes)) + if (reader.ValueTextEquals(TransportPropertyNameBytes.EncodedUtf8Bytes)) { availableTransport.Transport = reader.ReadAsString(TransportPropertyName); } - else if (memberName.SequenceEqual(TransferFormatsPropertyNameBytes.EncodedUtf8Bytes)) + else if (reader.ValueTextEquals(TransferFormatsPropertyNameBytes.EncodedUtf8Bytes)) { reader.CheckRead(); reader.EnsureArrayStart(); diff --git a/src/SignalR/common/Http.Connections/ref/Microsoft.AspNetCore.Http.Connections.csproj b/src/SignalR/common/Http.Connections/ref/Microsoft.AspNetCore.Http.Connections.csproj index e081a5c399..f0707f786a 100644 --- a/src/SignalR/common/Http.Connections/ref/Microsoft.AspNetCore.Http.Connections.csproj +++ b/src/SignalR/common/Http.Connections/ref/Microsoft.AspNetCore.Http.Connections.csproj @@ -11,7 +11,6 @@ - diff --git a/src/SignalR/common/Http.Connections/ref/Microsoft.AspNetCore.Http.Connections.netcoreapp.cs b/src/SignalR/common/Http.Connections/ref/Microsoft.AspNetCore.Http.Connections.netcoreapp.cs index 5ee369727c..130b8a4a42 100644 --- a/src/SignalR/common/Http.Connections/ref/Microsoft.AspNetCore.Http.Connections.netcoreapp.cs +++ b/src/SignalR/common/Http.Connections/ref/Microsoft.AspNetCore.Http.Connections.netcoreapp.cs @@ -15,18 +15,13 @@ namespace Microsoft.AspNetCore.Builder public static Microsoft.AspNetCore.Builder.ConnectionEndpointRouteBuilder MapConnections(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints, string pattern, Microsoft.AspNetCore.Http.Connections.HttpConnectionDispatcherOptions options, System.Action configure) { throw null; } public static Microsoft.AspNetCore.Builder.ConnectionEndpointRouteBuilder MapConnections(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints, string pattern, System.Action configure) { throw null; } } - public static partial class ConnectionsAppBuilderExtensions - { - [System.ObsoleteAttribute("This method is obsolete and will be removed in a future version. The recommended alternative is to use MapConnections or MapConnectionHandler inside Microsoft.AspNetCore.Builder.UseEndpoints(...).")] - public static Microsoft.AspNetCore.Builder.IApplicationBuilder UseConnections(this Microsoft.AspNetCore.Builder.IApplicationBuilder app, System.Action configure) { throw null; } - } } namespace Microsoft.AspNetCore.Http.Connections { public partial class ConnectionOptions { public ConnectionOptions() { } - public System.TimeSpan? DisconnectTimeout { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public System.TimeSpan? DisconnectTimeout { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } public partial class ConnectionOptionsSetup : Microsoft.Extensions.Options.IConfigureOptions { @@ -34,15 +29,6 @@ namespace Microsoft.AspNetCore.Http.Connections public ConnectionOptionsSetup() { } public void Configure(Microsoft.AspNetCore.Http.Connections.ConnectionOptions options) { } } - [System.ObsoleteAttribute("This class is obsolete and will be removed in a future version. The recommended alternative is to use MapConnection and MapConnectionHandler inside Microsoft.AspNetCore.Builder.UseEndpoints(...).")] - public partial class ConnectionsRouteBuilder - { - internal ConnectionsRouteBuilder() { } - public void MapConnectionHandler(Microsoft.AspNetCore.Http.PathString path) where TConnectionHandler : Microsoft.AspNetCore.Connections.ConnectionHandler { } - public void MapConnectionHandler(Microsoft.AspNetCore.Http.PathString path, System.Action configureOptions) where TConnectionHandler : Microsoft.AspNetCore.Connections.ConnectionHandler { } - public void MapConnections(Microsoft.AspNetCore.Http.PathString path, Microsoft.AspNetCore.Http.Connections.HttpConnectionDispatcherOptions options, System.Action configure) { } - public void MapConnections(Microsoft.AspNetCore.Http.PathString path, System.Action configure) { } - } public static partial class HttpConnectionContextExtensions { public static Microsoft.AspNetCore.Http.HttpContext GetHttpContext(this Microsoft.AspNetCore.Connections.ConnectionContext connection) { throw null; } @@ -50,18 +36,18 @@ namespace Microsoft.AspNetCore.Http.Connections public partial class HttpConnectionDispatcherOptions { public HttpConnectionDispatcherOptions() { } - public long ApplicationMaxBufferSize { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public System.Collections.Generic.IList AuthorizationData { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public Microsoft.AspNetCore.Http.Connections.LongPollingOptions LongPolling { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public int MinimumProtocolVersion { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public long TransportMaxBufferSize { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public Microsoft.AspNetCore.Http.Connections.HttpTransportType Transports { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public Microsoft.AspNetCore.Http.Connections.WebSocketOptions WebSockets { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public long ApplicationMaxBufferSize { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public System.Collections.Generic.IList AuthorizationData { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public Microsoft.AspNetCore.Http.Connections.LongPollingOptions LongPolling { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public int MinimumProtocolVersion { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public long TransportMaxBufferSize { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public Microsoft.AspNetCore.Http.Connections.HttpTransportType Transports { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public Microsoft.AspNetCore.Http.Connections.WebSocketOptions WebSockets { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } } public partial class LongPollingOptions { public LongPollingOptions() { } - public System.TimeSpan PollTimeout { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public System.TimeSpan PollTimeout { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } public partial class NegotiateMetadata { @@ -70,8 +56,8 @@ namespace Microsoft.AspNetCore.Http.Connections public partial class WebSocketOptions { public WebSocketOptions() { } - public System.TimeSpan CloseTimeout { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public System.Func, string> SubProtocolSelector { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public System.TimeSpan CloseTimeout { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public System.Func, string> SubProtocolSelector { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } } namespace Microsoft.AspNetCore.Http.Connections.Features diff --git a/src/SignalR/common/Http.Connections/src/ConnectionsAppBuilderExtensions.cs b/src/SignalR/common/Http.Connections/src/ConnectionsAppBuilderExtensions.cs deleted file mode 100644 index 05227d6f39..0000000000 --- a/src/SignalR/common/Http.Connections/src/ConnectionsAppBuilderExtensions.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using Microsoft.AspNetCore.Http.Connections; - -namespace Microsoft.AspNetCore.Builder -{ - /// - /// Extension methods for . - /// - public static class ConnectionsAppBuilderExtensions - { - /// - /// Adds support for ASP.NET Core Connection Handlers to the request execution pipeline. - /// - /// This method is obsolete and will be removed in a future version. - /// The recommended alternative is to use MapConnections or MapConnectionHandler<TConnectionHandler> inside Microsoft.AspNetCore.Builder.UseEndpoints(...). - /// - /// - /// The . - /// A callback to configure connection routes. - /// The same instance of the for chaining. - [Obsolete("This method is obsolete and will be removed in a future version. The recommended alternative is to use MapConnections or MapConnectionHandler inside Microsoft.AspNetCore.Builder.UseEndpoints(...).")] - public static IApplicationBuilder UseConnections(this IApplicationBuilder app, Action configure) - { - if (configure == null) - { - throw new ArgumentNullException(nameof(configure)); - } - - app.UseWebSockets(); - app.UseRouting(); - app.UseAuthorization(); - app.UseEndpoints(endpoints => - { - configure(new ConnectionsRouteBuilder(endpoints)); - }); - return app; - } - } -} diff --git a/src/SignalR/common/Http.Connections/src/ConnectionsRouteBuilder.cs b/src/SignalR/common/Http.Connections/src/ConnectionsRouteBuilder.cs deleted file mode 100644 index b76d5688f0..0000000000 --- a/src/SignalR/common/Http.Connections/src/ConnectionsRouteBuilder.cs +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Connections; -using Microsoft.AspNetCore.Routing; - -namespace Microsoft.AspNetCore.Http.Connections -{ - /// - /// Maps routes to ASP.NET Core Connection Handlers. - /// - /// This class is obsolete and will be removed in a future version. - /// The recommended alternative is to use MapConnection and MapConnectionHandler<TConnectionHandler> inside Microsoft.AspNetCore.Builder.UseEndpoints(...). - /// - /// - [Obsolete("This class is obsolete and will be removed in a future version. The recommended alternative is to use MapConnection and MapConnectionHandler inside Microsoft.AspNetCore.Builder.UseEndpoints(...).")] - public class ConnectionsRouteBuilder - { - private readonly IEndpointRouteBuilder _endpoints; - - internal ConnectionsRouteBuilder(IEndpointRouteBuilder endpoints) - { - _endpoints = endpoints; - } - - /// - /// Maps incoming requests with the specified path to the provided connection pipeline. - /// - /// The request path. - /// A callback to configure the connection. - public void MapConnections(PathString path, Action configure) => - MapConnections(path, new HttpConnectionDispatcherOptions(), configure); - - /// - /// Maps incoming requests with the specified path to the provided connection pipeline. - /// - /// The request path. - /// Options used to configure the connection. - /// A callback to configure the connection. - public void MapConnections(PathString path, HttpConnectionDispatcherOptions options, Action configure) => - _endpoints.MapConnections(path, options, configure); - - /// - /// Maps incoming requests with the specified path to the provided connection pipeline. - /// - /// The type. - /// The request path. - public void MapConnectionHandler(PathString path) where TConnectionHandler : ConnectionHandler => - MapConnectionHandler(path, configureOptions: null); - - /// - /// Maps incoming requests with the specified path to the provided connection pipeline. - /// - /// The type. - /// The request path. - /// A callback to configure dispatcher options. - public void MapConnectionHandler(PathString path, Action configureOptions) where TConnectionHandler : ConnectionHandler => - _endpoints.MapConnectionHandler(path, configureOptions); - } -} diff --git a/src/SignalR/common/Http.Connections/src/Internal/HttpConnectionDispatcher.Log.cs b/src/SignalR/common/Http.Connections/src/Internal/HttpConnectionDispatcher.Log.cs index 80f3d32800..5f5158a1fb 100644 --- a/src/SignalR/common/Http.Connections/src/Internal/HttpConnectionDispatcher.Log.cs +++ b/src/SignalR/common/Http.Connections/src/Internal/HttpConnectionDispatcher.Log.cs @@ -113,7 +113,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal _receivedDeleteRequestForUnsupportedTransport(logger, transportType, null); } - public static void TerminatingConection(ILogger logger) + public static void TerminatingConnection(ILogger logger) { _terminatingConnection(logger, null); } diff --git a/src/SignalR/common/Http.Connections/src/Internal/HttpConnectionDispatcher.cs b/src/SignalR/common/Http.Connections/src/Internal/HttpConnectionDispatcher.cs index c40a80b9ce..969d4a5935 100644 --- a/src/SignalR/common/Http.Connections/src/Internal/HttpConnectionDispatcher.cs +++ b/src/SignalR/common/Http.Connections/src/Internal/HttpConnectionDispatcher.cs @@ -489,7 +489,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal return; } - Log.TerminatingConection(_logger); + Log.TerminatingConnection(_logger); // Dispose the connection, but don't wait for it. We assign it here so we can wait in tests connection.DisposeAndRemoveTask = _manager.DisposeAndRemoveAsync(connection, closeGracefully: false); @@ -572,12 +572,31 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal private static void CloneUser(HttpContext newContext, HttpContext oldContext) { - if (oldContext.User.Identity is WindowsIdentity) + // If the identity is a WindowsIdentity we need to clone the User. + // This is because the WindowsIdentity uses SafeHandle's which are disposed at the end of the request + // and accessing the identity can happen outside of the request scope. + if (oldContext.User.Identity is WindowsIdentity windowsIdentity) { - newContext.User = new ClaimsPrincipal(); + var skipFirstIdentity = false; + if (oldContext.User is WindowsPrincipal) + { + // We want to explicitly create a WindowsPrincipal instead of a ClaimsPrincipal + // so methods that WindowsPrincipal overrides like 'IsInRole', work as expected. + newContext.User = new WindowsPrincipal((WindowsIdentity)(windowsIdentity.Clone())); + skipFirstIdentity = true; + } + else + { + newContext.User = new ClaimsPrincipal(); + } foreach (var identity in oldContext.User.Identities) { + if (skipFirstIdentity) + { + skipFirstIdentity = false; + continue; + } newContext.User.AddIdentity(identity.Clone()); } } diff --git a/src/SignalR/common/Http.Connections/src/Microsoft.AspNetCore.Http.Connections.csproj b/src/SignalR/common/Http.Connections/src/Microsoft.AspNetCore.Http.Connections.csproj index 1e8738d9a9..dc545e48ba 100644 --- a/src/SignalR/common/Http.Connections/src/Microsoft.AspNetCore.Http.Connections.csproj +++ b/src/SignalR/common/Http.Connections/src/Microsoft.AspNetCore.Http.Connections.csproj @@ -31,8 +31,9 @@ - + + diff --git a/src/SignalR/common/Http.Connections/test/HttpConnectionDispatcherTests.cs b/src/SignalR/common/Http.Connections/test/HttpConnectionDispatcherTests.cs index 33d72eddc2..34626ca05b 100644 --- a/src/SignalR/common/Http.Connections/test/HttpConnectionDispatcherTests.cs +++ b/src/SignalR/common/Http.Connections/test/HttpConnectionDispatcherTests.cs @@ -1336,6 +1336,18 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests await request1.OrTimeout(); Assert.Equal(StatusCodes.Status204NoContent, context1.Response.StatusCode); + + count = 0; + // Wait until the second request has started internally + while (connection.TransportTask.IsCompleted && count < 50) + { + count++; + await Task.Delay(15); + } + if (count == 50) + { + Assert.True(false, "Poll took too long to start"); + } Assert.Equal(HttpConnectionStatus.Active, connection.Status); Assert.False(request2.IsCompleted); @@ -1626,7 +1638,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests [ConditionalFact] [OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX)] - public async Task LongPollingKeepsWindowsIdentityBetweenRequests() + public async Task LongPollingKeepsWindowsPrincipalAndIdentityBetweenRequests() { using (StartVerifiableLog()) { @@ -1655,6 +1667,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests var windowsIdentity = WindowsIdentity.GetAnonymous(); context.User = new WindowsPrincipal(windowsIdentity); + context.User.AddIdentity(new ClaimsIdentity()); // would get stuck if EndPoint was running await dispatcher.ExecuteAsync(context, options, app).OrTimeout(); @@ -1668,6 +1681,60 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests // This is the important check Assert.Same(currentUser, connection.User); + Assert.IsType(currentUser); + Assert.Equal(2, connection.User.Identities.Count()); + + Assert.Equal(StatusCodes.Status200OK, context.Response.StatusCode); + } + } + + [ConditionalFact] + [OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX)] + public async Task LongPollingKeepsWindowsIdentityWithoutWindowsPrincipalBetweenRequests() + { + using (StartVerifiableLog()) + { + var manager = CreateConnectionManager(LoggerFactory); + var connection = manager.CreateConnection(); + connection.TransportType = HttpTransportType.LongPolling; + var dispatcher = new HttpConnectionDispatcher(manager, LoggerFactory); + var context = new DefaultHttpContext(); + var services = new ServiceCollection(); + services.AddOptions(); + services.AddSingleton(); + services.AddLogging(); + var sp = services.BuildServiceProvider(); + context.Request.Path = "/foo"; + context.Request.Method = "GET"; + context.RequestServices = sp; + var values = new Dictionary(); + values["id"] = connection.ConnectionToken; + values["negotiateVersion"] = "1"; + var qs = new QueryCollection(values); + context.Request.Query = qs; + var builder = new ConnectionBuilder(sp); + builder.UseConnectionHandler(); + var app = builder.Build(); + var options = new HttpConnectionDispatcherOptions(); + + var windowsIdentity = WindowsIdentity.GetAnonymous(); + context.User = new ClaimsPrincipal(windowsIdentity); + context.User.AddIdentity(new ClaimsIdentity()); + + // would get stuck if EndPoint was running + await dispatcher.ExecuteAsync(context, options, app).OrTimeout(); + + Assert.Equal(StatusCodes.Status200OK, context.Response.StatusCode); + var currentUser = connection.User; + + var connectionHandlerTask = dispatcher.ExecuteAsync(context, options, app); + await connection.Transport.Output.WriteAsync(Encoding.UTF8.GetBytes("Unblock")).AsTask().OrTimeout(); + await connectionHandlerTask.OrTimeout(); + + // This is the important check + Assert.Same(currentUser, connection.User); + Assert.IsNotType(currentUser); + Assert.Equal(2, connection.User.Identities.Count()); Assert.Equal(StatusCodes.Status200OK, context.Response.StatusCode); } diff --git a/src/SignalR/common/Http.Connections/test/MapConnectionHandlerTests.cs b/src/SignalR/common/Http.Connections/test/MapConnectionHandlerTests.cs index 68e086c560..d603c0d083 100644 --- a/src/SignalR/common/Http.Connections/test/MapConnectionHandlerTests.cs +++ b/src/SignalR/common/Http.Connections/test/MapConnectionHandlerTests.cs @@ -150,10 +150,10 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests public void MapConnectionHandlerEndPointRoutingFindsAttributesOnHub() { var authCount = 0; - using (var host = BuildWebHostWithEndPointRouting(routes => routes.MapConnectionHandler("/path", options => + using (var host = BuildWebHost("/path", options => { authCount += options.AuthorizationData.Count; - }))) + })) { host.Start(); @@ -179,11 +179,11 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests public void MapConnectionHandlerEndPointRoutingFindsAttributesFromOptions() { var authCount = 0; - using (var host = BuildWebHostWithEndPointRouting(routes => routes.MapConnectionHandler("/path", options => + using (var host = BuildWebHost("/path", options => { authCount += options.AuthorizationData.Count; options.AuthorizationData.Add(new AuthorizeAttribute()); - }))) + })) { host.Start(); @@ -215,7 +215,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests .RequireAuthorization(new AuthorizeAttribute("Foo")); } - using (var host = BuildWebHostWithEndPointRouting(ConfigureRoutes)) + using (var host = BuildWebHost(ConfigureRoutes)) { host.Start(); @@ -253,7 +253,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests endpoints.MapConnectionHandler("/path"); } - using (var host = BuildWebHostWithEndPointRouting(ConfigureRoutes)) + using (var host = BuildWebHost(ConfigureRoutes)) { host.Start(); @@ -281,7 +281,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests endpoints.MapConnectionHandler("/path"); } - using (var host = BuildWebHostWithEndPointRouting(ConfigureRoutes)) + using (var host = BuildWebHost(ConfigureRoutes)) { host.Start(); @@ -308,7 +308,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests var host = BuildWebHost("/socket", options => options.WebSockets.SubProtocolSelector = subprotocols => { - Assert.Equal(new [] { "protocol1", "protocol2" }, subprotocols.ToArray()); + Assert.Equal(new[] { "protocol1", "protocol2" }, subprotocols.ToArray()); return "protocol1"; }); @@ -377,7 +377,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests } } - private IWebHost BuildWebHostWithEndPointRouting(Action configure) + private IWebHost BuildWebHost(Action configure) { return new WebHostBuilder() .UseKestrel() @@ -405,12 +405,11 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests }) .Configure(app => { -#pragma warning disable CS0618 // Type or member is obsolete - app.UseConnections(routes => + app.UseRouting(); + app.UseEndpoints(routes => { routes.MapConnectionHandler(path, configureOptions); }); -#pragma warning restore CS0618 // Type or member is obsolete }) .ConfigureLogging(factory => { diff --git a/src/SignalR/common/Http.Connections/test/NegotiateProtocolTests.cs b/src/SignalR/common/Http.Connections/test/NegotiateProtocolTests.cs index 704f0f4d27..00d803ffdd 100644 --- a/src/SignalR/common/Http.Connections/test/NegotiateProtocolTests.cs +++ b/src/SignalR/common/Http.Connections/test/NegotiateProtocolTests.cs @@ -18,6 +18,8 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests [InlineData("{\"url\": \"http://foo.com/chat\"}", null, null, "http://foo.com/chat", null, 0, null)] [InlineData("{\"url\": \"http://foo.com/chat\", \"accessToken\": \"token\"}", null, null, "http://foo.com/chat", "token", 0, null)] [InlineData("{\"connectionId\":\"123\",\"availableTransports\":[{\"transport\":\"test\",\"transferFormats\":[]}]}", "123", new[] { "test" }, null, null, 0, null)] + [InlineData("{\"connectionId\":\"123\",\"availableTransports\":[{\"\\u0074ransport\":\"test\",\"transferFormats\":[]}]}", "123", new[] { "test" }, null, null, 0, null)] + [InlineData("{\"negotiateVersion\":123,\"connectionId\":\"123\",\"connectionToken\":\"789\",\"availableTransports\":[{\"\\u0074ransport\":\"test\",\"transferFormats\":[]}]}", "123", new[] { "test" }, null, null, 123, "789")] [InlineData("{\"negotiateVersion\":123,\"negotiateVersion\":321, \"connectionToken\":\"789\",\"connectionId\":\"123\",\"availableTransports\":[]}", "123", new string[0], null, null, 321, "789")] [InlineData("{\"ignore\":123,\"negotiateVersion\":123, \"connectionToken\":\"789\",\"connectionId\":\"123\",\"availableTransports\":[]}", "123", new string[0], null, null, 123, "789")] [InlineData("{\"connectionId\":\"123\",\"availableTransports\":[],\"negotiateVersion\":123, \"connectionToken\":\"789\"}", "123", new string[0], null, null, 123, "789")] diff --git a/src/SignalR/common/Protocols.Json/ref/Microsoft.AspNetCore.SignalR.Protocols.Json.csproj b/src/SignalR/common/Protocols.Json/ref/Microsoft.AspNetCore.SignalR.Protocols.Json.csproj index 19480b336f..e437919dfb 100644 --- a/src/SignalR/common/Protocols.Json/ref/Microsoft.AspNetCore.SignalR.Protocols.Json.csproj +++ b/src/SignalR/common/Protocols.Json/ref/Microsoft.AspNetCore.SignalR.Protocols.Json.csproj @@ -2,6 +2,7 @@ netstandard2.0;$(DefaultNetCoreTargetFramework) + $(DefaultNetCoreTargetFramework) diff --git a/src/SignalR/common/Protocols.Json/ref/Microsoft.AspNetCore.SignalR.Protocols.Json.netcoreapp.cs b/src/SignalR/common/Protocols.Json/ref/Microsoft.AspNetCore.SignalR.Protocols.Json.netcoreapp.cs index 28b4ec1d30..7cab28e40b 100644 --- a/src/SignalR/common/Protocols.Json/ref/Microsoft.AspNetCore.SignalR.Protocols.Json.netcoreapp.cs +++ b/src/SignalR/common/Protocols.Json/ref/Microsoft.AspNetCore.SignalR.Protocols.Json.netcoreapp.cs @@ -6,7 +6,7 @@ namespace Microsoft.AspNetCore.SignalR public partial class JsonHubProtocolOptions { public JsonHubProtocolOptions() { } - public System.Text.Json.JsonSerializerOptions PayloadSerializerOptions { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public System.Text.Json.JsonSerializerOptions PayloadSerializerOptions { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } } namespace Microsoft.AspNetCore.SignalR.Protocol diff --git a/src/SignalR/common/Protocols.Json/ref/Microsoft.AspNetCore.SignalR.Protocols.Json.netstandard2.0.cs b/src/SignalR/common/Protocols.Json/ref/Microsoft.AspNetCore.SignalR.Protocols.Json.netstandard2.0.cs index 28b4ec1d30..7cab28e40b 100644 --- a/src/SignalR/common/Protocols.Json/ref/Microsoft.AspNetCore.SignalR.Protocols.Json.netstandard2.0.cs +++ b/src/SignalR/common/Protocols.Json/ref/Microsoft.AspNetCore.SignalR.Protocols.Json.netstandard2.0.cs @@ -6,7 +6,7 @@ namespace Microsoft.AspNetCore.SignalR public partial class JsonHubProtocolOptions { public JsonHubProtocolOptions() { } - public System.Text.Json.JsonSerializerOptions PayloadSerializerOptions { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public System.Text.Json.JsonSerializerOptions PayloadSerializerOptions { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } } namespace Microsoft.AspNetCore.SignalR.Protocol diff --git a/src/SignalR/common/Protocols.Json/src/JsonHubProtocolOptions.cs b/src/SignalR/common/Protocols.Json/src/JsonHubProtocolOptions.cs index 1164497f9a..46f5003a78 100644 --- a/src/SignalR/common/Protocols.Json/src/JsonHubProtocolOptions.cs +++ b/src/SignalR/common/Protocols.Json/src/JsonHubProtocolOptions.cs @@ -7,7 +7,7 @@ using Microsoft.AspNetCore.SignalR.Protocol; namespace Microsoft.AspNetCore.SignalR { /// - /// Options used to configure a instance. + /// Options used to configure a instance. /// public class JsonHubProtocolOptions { diff --git a/src/SignalR/common/Protocols.Json/src/Microsoft.AspNetCore.SignalR.Protocols.Json.csproj b/src/SignalR/common/Protocols.Json/src/Microsoft.AspNetCore.SignalR.Protocols.Json.csproj index dc3a19e0c5..98865bf6c9 100644 --- a/src/SignalR/common/Protocols.Json/src/Microsoft.AspNetCore.SignalR.Protocols.Json.csproj +++ b/src/SignalR/common/Protocols.Json/src/Microsoft.AspNetCore.SignalR.Protocols.Json.csproj @@ -22,7 +22,7 @@ - + diff --git a/src/SignalR/common/Protocols.Json/src/Protocol/JsonHubProtocol.cs b/src/SignalR/common/Protocols.Json/src/Protocol/JsonHubProtocol.cs index a5696467bf..e8d5b514f0 100644 --- a/src/SignalR/common/Protocols.Json/src/Protocol/JsonHubProtocol.cs +++ b/src/SignalR/common/Protocols.Json/src/Protocol/JsonHubProtocol.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Runtime.ExceptionServices; +using System.Text.Encodings.Web; using System.Text.Json; using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Internal; @@ -508,7 +509,14 @@ namespace Microsoft.AspNetCore.SignalR.Protocol else if (message.HasResult) { writer.WritePropertyName(ResultPropertyNameBytes); - JsonSerializer.Serialize(writer, message.Result, message.Result?.GetType(), _payloadSerializerOptions); + if (message.Result == null) + { + writer.WriteNullValue(); + } + else + { + JsonSerializer.Serialize(writer, message.Result, message.Result?.GetType(), _payloadSerializerOptions); + } } } @@ -522,7 +530,14 @@ namespace Microsoft.AspNetCore.SignalR.Protocol WriteInvocationId(message, writer); writer.WritePropertyName(ItemPropertyNameBytes); - JsonSerializer.Serialize(writer, message.Item, message.Item?.GetType(), _payloadSerializerOptions); + if (message.Item == null) + { + writer.WriteNullValue(); + } + else + { + JsonSerializer.Serialize(writer, message.Item, message.Item?.GetType(), _payloadSerializerOptions); + } } private void WriteInvocationMessage(InvocationMessage message, Utf8JsonWriter writer) @@ -563,18 +578,13 @@ namespace Microsoft.AspNetCore.SignalR.Protocol writer.WriteStartArray(ArgumentsPropertyNameBytes); foreach (var argument in arguments) { - var type = argument?.GetType(); - if (type == typeof(DateTime)) + if (argument == null) { - writer.WriteStringValue((DateTime)argument); - } - else if (type == typeof(DateTimeOffset)) - { - writer.WriteStringValue((DateTimeOffset)argument); + writer.WriteNullValue(); } else { - JsonSerializer.Serialize(writer, argument, type, _payloadSerializerOptions); + JsonSerializer.Serialize(writer, argument, argument?.GetType(), _payloadSerializerOptions); } } writer.WriteEndArray(); @@ -757,19 +767,20 @@ namespace Microsoft.AspNetCore.SignalR.Protocol internal static JsonSerializerOptions CreateDefaultSerializerSettings() { - var options = new JsonSerializerOptions(); - options.WriteIndented = false; - options.ReadCommentHandling = JsonCommentHandling.Disallow; - options.AllowTrailingCommas = false; - options.IgnoreNullValues = false; - options.IgnoreReadOnlyProperties = false; - options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; - options.PropertyNameCaseInsensitive = true; - options.MaxDepth = 64; - options.DictionaryKeyPolicy = null; - options.DefaultBufferSize = 16 * 1024; - - return options; + return new JsonSerializerOptions() + { + WriteIndented = false, + ReadCommentHandling = JsonCommentHandling.Disallow, + AllowTrailingCommas = false, + IgnoreNullValues = false, + IgnoreReadOnlyProperties = false, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + PropertyNameCaseInsensitive = true, + MaxDepth = 64, + DictionaryKeyPolicy = null, + DefaultBufferSize = 16 * 1024, + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + }; } } } diff --git a/src/SignalR/common/Protocols.MessagePack/src/.editorconfig b/src/SignalR/common/Protocols.MessagePack/src/.editorconfig new file mode 100644 index 0000000000..6b409a9a5d --- /dev/null +++ b/src/SignalR/common/Protocols.MessagePack/src/.editorconfig @@ -0,0 +1,12 @@ +# EditorConfig is awesome:http://EditorConfig.org +# NOTE: Requires **VS2019 16.3** or later + +# New Rule Set +# Description: +# MsgPack001 : MsgPack001 Avoid static default for MessagePackSerializerOptions +# MsgPack002 : MsgPack002 Avoid using a mutable static value for MessagePackSerializerOptions + +# Code files +[*.{cs,vb}] +dotnet_diagnostic.MsgPack001.severity = error +dotnet_diagnostic.MsgPack002.severity = error diff --git a/src/SignalR/common/Protocols.MessagePack/src/Microsoft.AspNetCore.SignalR.Protocols.MessagePack.csproj b/src/SignalR/common/Protocols.MessagePack/src/Microsoft.AspNetCore.SignalR.Protocols.MessagePack.csproj index b76c522f1c..43f4873c80 100644 --- a/src/SignalR/common/Protocols.MessagePack/src/Microsoft.AspNetCore.SignalR.Protocols.MessagePack.csproj +++ b/src/SignalR/common/Protocols.MessagePack/src/Microsoft.AspNetCore.SignalR.Protocols.MessagePack.csproj @@ -1,11 +1,11 @@ - + Implements the SignalR Hub Protocol over MsgPack. netstandard2.0 Microsoft.AspNetCore.SignalR true - true + true @@ -17,6 +17,7 @@ + diff --git a/src/SignalR/common/Protocols.MessagePack/src/Protocol/MessagePackHubProtocol.cs b/src/SignalR/common/Protocols.MessagePack/src/Protocol/MessagePackHubProtocol.cs index 78631d3c7f..e1a762937b 100644 --- a/src/SignalR/common/Protocols.MessagePack/src/Protocol/MessagePackHubProtocol.cs +++ b/src/SignalR/common/Protocols.MessagePack/src/Protocol/MessagePackHubProtocol.cs @@ -6,10 +6,11 @@ using System.Buffers; using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Linq; using System.Runtime.ExceptionServices; -using System.Runtime.InteropServices; using MessagePack; using MessagePack.Formatters; +using MessagePack.Resolvers; using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Internal; using Microsoft.Extensions.Options; @@ -25,8 +26,7 @@ namespace Microsoft.AspNetCore.SignalR.Protocol private const int VoidResult = 2; private const int NonVoidResult = 3; - private IFormatterResolver _resolver; - + private readonly MessagePackSerializerOptions _msgPackSerializerOptions; private static readonly string ProtocolName = "messagepack"; private static readonly int ProtocolVersion = 1; @@ -53,31 +53,36 @@ namespace Microsoft.AspNetCore.SignalR.Protocol public MessagePackHubProtocol(IOptions options) { var msgPackOptions = options.Value; - SetupResolver(msgPackOptions); - } + var resolver = SignalRResolver.Instance; + var hasCustomFormatterResolver = false; - private void SetupResolver(MessagePackHubProtocolOptions options) - { - // if counts don't match then we know users customized resolvers so we set up the options - // with the provided resolvers - if (options.FormatterResolvers.Count != SignalRResolver.Resolvers.Count) + // if counts don't match then we know users customized resolvers so we set up the options with the provided resolvers + if (msgPackOptions.FormatterResolvers.Count != SignalRResolver.Resolvers.Count) { - _resolver = new CombinedResolvers(options.FormatterResolvers); - return; + hasCustomFormatterResolver = true; } - - for (var i = 0; i < options.FormatterResolvers.Count; i++) + else { - // check if the user customized the resolvers - if (options.FormatterResolvers[i] != SignalRResolver.Resolvers[i]) + // Compare each "reference" in the FormatterResolvers IList<> against the default "SignalRResolver.Resolvers" IList<> + for (var i = 0; i < msgPackOptions.FormatterResolvers.Count; i++) { - _resolver = new CombinedResolvers(options.FormatterResolvers); - return; + // check if the user customized the resolvers + if (msgPackOptions.FormatterResolvers[i] != SignalRResolver.Resolvers[i]) + { + hasCustomFormatterResolver = true; + break; + } } } - // Use optimized cached resolver if the default is chosen - _resolver = SignalRResolver.Instance; + if (hasCustomFormatterResolver) + { + resolver = CompositeResolver.Create(Array.Empty(), (IReadOnlyList)msgPackOptions.FormatterResolvers); + } + + _msgPackSerializerOptions = MessagePackSerializerOptions.Standard + .WithResolver(resolver) + .WithSecurity(MessagePackSecurity.UntrustedData); } /// @@ -95,59 +100,43 @@ namespace Microsoft.AspNetCore.SignalR.Protocol return false; } - var arraySegment = GetArraySegment(payload); - - message = ParseMessage(arraySegment.Array, arraySegment.Offset, binder, _resolver); + var reader = new MessagePackReader(payload); + message = ParseMessage(ref reader, binder, _msgPackSerializerOptions); return true; } - private static ArraySegment GetArraySegment(in ReadOnlySequence input) + private static HubMessage ParseMessage(ref MessagePackReader reader, IInvocationBinder binder, MessagePackSerializerOptions msgPackSerializerOptions) { - if (input.IsSingleSegment) - { - var isArray = MemoryMarshal.TryGetArray(input.First, out var arraySegment); - // This will never be false unless we started using un-managed buffers - Debug.Assert(isArray); - return arraySegment; - } + var itemCount = reader.ReadArrayHeader(); - // Should be rare - return new ArraySegment(input.ToArray()); - } - - private static HubMessage ParseMessage(byte[] input, int startOffset, IInvocationBinder binder, IFormatterResolver resolver) - { - var itemCount = MessagePackBinary.ReadArrayHeader(input, startOffset, out var readSize); - startOffset += readSize; - - var messageType = ReadInt32(input, ref startOffset, "messageType"); + var messageType = ReadInt32(ref reader, "messageType"); switch (messageType) { case HubProtocolConstants.InvocationMessageType: - return CreateInvocationMessage(input, ref startOffset, binder, resolver, itemCount); + return CreateInvocationMessage(ref reader, binder, msgPackSerializerOptions, itemCount); case HubProtocolConstants.StreamInvocationMessageType: - return CreateStreamInvocationMessage(input, ref startOffset, binder, resolver, itemCount); + return CreateStreamInvocationMessage(ref reader, binder, msgPackSerializerOptions, itemCount); case HubProtocolConstants.StreamItemMessageType: - return CreateStreamItemMessage(input, ref startOffset, binder, resolver); + return CreateStreamItemMessage(ref reader, binder, msgPackSerializerOptions); case HubProtocolConstants.CompletionMessageType: - return CreateCompletionMessage(input, ref startOffset, binder, resolver); + return CreateCompletionMessage(ref reader, binder, msgPackSerializerOptions); case HubProtocolConstants.CancelInvocationMessageType: - return CreateCancelInvocationMessage(input, ref startOffset); + return CreateCancelInvocationMessage(ref reader); case HubProtocolConstants.PingMessageType: return PingMessage.Instance; case HubProtocolConstants.CloseMessageType: - return CreateCloseMessage(input, ref startOffset, itemCount); + return CreateCloseMessage(ref reader, itemCount); default: // Future protocol changes can add message types, old clients can ignore them return null; } } - private static HubMessage CreateInvocationMessage(byte[] input, ref int offset, IInvocationBinder binder, IFormatterResolver resolver, int itemCount) + private static HubMessage CreateInvocationMessage(ref MessagePackReader reader, IInvocationBinder binder, MessagePackSerializerOptions msgPackSerializerOptions, int itemCount) { - var headers = ReadHeaders(input, ref offset); - var invocationId = ReadInvocationId(input, ref offset); + var headers = ReadHeaders(ref reader); + var invocationId = ReadInvocationId(ref reader); // For MsgPack, we represent an empty invocation ID as an empty string, // so we need to normalize that to "null", which is what indicates a non-blocking invocation. @@ -156,13 +145,13 @@ namespace Microsoft.AspNetCore.SignalR.Protocol invocationId = null; } - var target = ReadString(input, ref offset, "target"); + var target = ReadString(ref reader, "target"); object[] arguments = null; try { var parameterTypes = binder.GetParameterTypes(target); - arguments = BindArguments(input, ref offset, parameterTypes, resolver); + arguments = BindArguments(ref reader, parameterTypes, msgPackSerializerOptions); } catch (Exception ex) { @@ -173,23 +162,23 @@ namespace Microsoft.AspNetCore.SignalR.Protocol // Previous clients will send 5 items, so we check if they sent a stream array or not if (itemCount > 5) { - streams = ReadStreamIds(input, ref offset); + streams = ReadStreamIds(ref reader); } return ApplyHeaders(headers, new InvocationMessage(invocationId, target, arguments, streams)); } - private static HubMessage CreateStreamInvocationMessage(byte[] input, ref int offset, IInvocationBinder binder, IFormatterResolver resolver, int itemCount) + private static HubMessage CreateStreamInvocationMessage(ref MessagePackReader reader, IInvocationBinder binder, MessagePackSerializerOptions msgPackSerializerOptions, int itemCount) { - var headers = ReadHeaders(input, ref offset); - var invocationId = ReadInvocationId(input, ref offset); - var target = ReadString(input, ref offset, "target"); + var headers = ReadHeaders(ref reader); + var invocationId = ReadInvocationId(ref reader); + var target = ReadString(ref reader, "target"); object[] arguments = null; try { var parameterTypes = binder.GetParameterTypes(target); - arguments = BindArguments(input, ref offset, parameterTypes, resolver); + arguments = BindArguments(ref reader, parameterTypes, msgPackSerializerOptions); } catch (Exception ex) { @@ -200,21 +189,21 @@ namespace Microsoft.AspNetCore.SignalR.Protocol // Previous clients will send 5 items, so we check if they sent a stream array or not if (itemCount > 5) { - streams = ReadStreamIds(input, ref offset); + streams = ReadStreamIds(ref reader); } return ApplyHeaders(headers, new StreamInvocationMessage(invocationId, target, arguments, streams)); } - private static HubMessage CreateStreamItemMessage(byte[] input, ref int offset, IInvocationBinder binder, IFormatterResolver resolver) + private static HubMessage CreateStreamItemMessage(ref MessagePackReader reader, IInvocationBinder binder, MessagePackSerializerOptions msgPackSerializerOptions) { - var headers = ReadHeaders(input, ref offset); - var invocationId = ReadInvocationId(input, ref offset); + var headers = ReadHeaders(ref reader); + var invocationId = ReadInvocationId(ref reader); object value; try { var itemType = binder.GetStreamItemType(invocationId); - value = DeserializeObject(input, ref offset, itemType, "item", resolver); + value = DeserializeObject(ref reader, itemType, "item", msgPackSerializerOptions); } catch (Exception ex) { @@ -224,11 +213,11 @@ namespace Microsoft.AspNetCore.SignalR.Protocol return ApplyHeaders(headers, new StreamItemMessage(invocationId, value)); } - private static CompletionMessage CreateCompletionMessage(byte[] input, ref int offset, IInvocationBinder binder, IFormatterResolver resolver) + private static CompletionMessage CreateCompletionMessage(ref MessagePackReader reader, IInvocationBinder binder, MessagePackSerializerOptions msgPackSerializerOptions) { - var headers = ReadHeaders(input, ref offset); - var invocationId = ReadInvocationId(input, ref offset); - var resultKind = ReadInt32(input, ref offset, "resultKind"); + var headers = ReadHeaders(ref reader); + var invocationId = ReadInvocationId(ref reader); + var resultKind = ReadInt32(ref reader, "resultKind"); string error = null; object result = null; @@ -237,11 +226,11 @@ namespace Microsoft.AspNetCore.SignalR.Protocol switch (resultKind) { case ErrorResult: - error = ReadString(input, ref offset, "error"); + error = ReadString(ref reader, "error"); break; case NonVoidResult: var itemType = binder.GetReturnType(invocationId); - result = DeserializeObject(input, ref offset, itemType, "argument", resolver); + result = DeserializeObject(ref reader, itemType, "argument", msgPackSerializerOptions); hasResult = true; break; case VoidResult: @@ -254,21 +243,21 @@ namespace Microsoft.AspNetCore.SignalR.Protocol return ApplyHeaders(headers, new CompletionMessage(invocationId, error, result, hasResult)); } - private static CancelInvocationMessage CreateCancelInvocationMessage(byte[] input, ref int offset) + private static CancelInvocationMessage CreateCancelInvocationMessage(ref MessagePackReader reader) { - var headers = ReadHeaders(input, ref offset); - var invocationId = ReadInvocationId(input, ref offset); + var headers = ReadHeaders(ref reader); + var invocationId = ReadInvocationId(ref reader); return ApplyHeaders(headers, new CancelInvocationMessage(invocationId)); } - private static CloseMessage CreateCloseMessage(byte[] input, ref int offset, int itemCount) + private static CloseMessage CreateCloseMessage(ref MessagePackReader reader, int itemCount) { - var error = ReadString(input, ref offset, "error"); + var error = ReadString(ref reader, "error"); var allowReconnect = false; if (itemCount > 2) { - allowReconnect = ReadBoolean(input, ref offset, "allowReconnect"); + allowReconnect = ReadBoolean(ref reader, "allowReconnect"); } // An empty string is still an error @@ -280,17 +269,17 @@ namespace Microsoft.AspNetCore.SignalR.Protocol return new CloseMessage(error, allowReconnect); } - private static Dictionary ReadHeaders(byte[] input, ref int offset) + private static Dictionary ReadHeaders(ref MessagePackReader reader) { - var headerCount = ReadMapLength(input, ref offset, "headers"); + var headerCount = ReadMapLength(ref reader, "headers"); if (headerCount > 0) { var headers = new Dictionary(StringComparer.Ordinal); for (var i = 0; i < headerCount; i++) { - var key = ReadString(input, ref offset, $"headers[{i}].Key"); - var value = ReadString(input, ref offset, $"headers[{i}].Value"); + var key = ReadString(ref reader, $"headers[{i}].Key"); + var value = ReadString(ref reader, $"headers[{i}].Value"); headers.Add(key, value); } return headers; @@ -301,9 +290,9 @@ namespace Microsoft.AspNetCore.SignalR.Protocol } } - private static string[] ReadStreamIds(byte[] input, ref int offset) + private static string[] ReadStreamIds(ref MessagePackReader reader) { - var streamIdCount = ReadArrayLength(input, ref offset, "streamIds"); + var streamIdCount = ReadArrayLength(ref reader, "streamIds"); List streams = null; if (streamIdCount > 0) @@ -311,17 +300,16 @@ namespace Microsoft.AspNetCore.SignalR.Protocol streams = new List(); for (var i = 0; i < streamIdCount; i++) { - streams.Add(MessagePackBinary.ReadString(input, offset, out var read)); - offset += read; + streams.Add(reader.ReadString()); } } return streams?.ToArray(); } - private static object[] BindArguments(byte[] input, ref int offset, IReadOnlyList parameterTypes, IFormatterResolver resolver) + private static object[] BindArguments(ref MessagePackReader reader, IReadOnlyList parameterTypes, MessagePackSerializerOptions msgPackSerializerOptions) { - var argumentCount = ReadArrayLength(input, ref offset, "arguments"); + var argumentCount = ReadArrayLength(ref reader, "arguments"); if (parameterTypes.Count != argumentCount) { @@ -334,7 +322,7 @@ namespace Microsoft.AspNetCore.SignalR.Protocol var arguments = new object[argumentCount]; for (var i = 0; i < argumentCount; i++) { - arguments[i] = DeserializeObject(input, ref offset, parameterTypes[i], "argument", resolver); + arguments[i] = DeserializeObject(ref reader, parameterTypes[i], "argument", msgPackSerializerOptions); } return arguments; @@ -358,339 +346,314 @@ namespace Microsoft.AspNetCore.SignalR.Protocol /// public void WriteMessage(HubMessage message, IBufferWriter output) { - var writer = MemoryBufferWriter.Get(); + var memoryBufferWriter = MemoryBufferWriter.Get(); try { + var writer = new MessagePackWriter(memoryBufferWriter); + // Write message to a buffer so we can get its length - WriteMessageCore(message, writer); + WriteMessageCore(message, ref writer); // Write length then message to output - BinaryMessageFormatter.WriteLengthPrefix(writer.Length, output); - writer.CopyTo(output); + BinaryMessageFormatter.WriteLengthPrefix(memoryBufferWriter.Length, output); + memoryBufferWriter.CopyTo(output); } finally { - MemoryBufferWriter.Return(writer); + MemoryBufferWriter.Return(memoryBufferWriter); } } /// public ReadOnlyMemory GetMessageBytes(HubMessage message) { - var writer = MemoryBufferWriter.Get(); + var memoryBufferWriter = MemoryBufferWriter.Get(); try { - // Write message to a buffer so we can get its length - WriteMessageCore(message, writer); + var writer = new MessagePackWriter(memoryBufferWriter); - var dataLength = writer.Length; - var prefixLength = BinaryMessageFormatter.LengthPrefixLength(writer.Length); + // Write message to a buffer so we can get its length + WriteMessageCore(message, ref writer); + + var dataLength = memoryBufferWriter.Length; + var prefixLength = BinaryMessageFormatter.LengthPrefixLength(memoryBufferWriter.Length); var array = new byte[dataLength + prefixLength]; var span = array.AsSpan(); // Write length then message to output - var written = BinaryMessageFormatter.WriteLengthPrefix(writer.Length, span); + var written = BinaryMessageFormatter.WriteLengthPrefix(memoryBufferWriter.Length, span); Debug.Assert(written == prefixLength); - writer.CopyTo(span.Slice(prefixLength)); + memoryBufferWriter.CopyTo(span.Slice(prefixLength)); return array; } finally { - MemoryBufferWriter.Return(writer); + MemoryBufferWriter.Return(memoryBufferWriter); } } - private void WriteMessageCore(HubMessage message, Stream packer) + private void WriteMessageCore(HubMessage message, ref MessagePackWriter writer) { switch (message) { case InvocationMessage invocationMessage: - WriteInvocationMessage(invocationMessage, packer); + WriteInvocationMessage(invocationMessage, ref writer); break; case StreamInvocationMessage streamInvocationMessage: - WriteStreamInvocationMessage(streamInvocationMessage, packer); + WriteStreamInvocationMessage(streamInvocationMessage, ref writer); break; case StreamItemMessage streamItemMessage: - WriteStreamingItemMessage(streamItemMessage, packer); + WriteStreamingItemMessage(streamItemMessage, ref writer); break; case CompletionMessage completionMessage: - WriteCompletionMessage(completionMessage, packer); + WriteCompletionMessage(completionMessage, ref writer); break; case CancelInvocationMessage cancelInvocationMessage: - WriteCancelInvocationMessage(cancelInvocationMessage, packer); + WriteCancelInvocationMessage(cancelInvocationMessage, ref writer); break; case PingMessage pingMessage: - WritePingMessage(pingMessage, packer); + WritePingMessage(pingMessage, ref writer); break; case CloseMessage closeMessage: - WriteCloseMessage(closeMessage, packer); + WriteCloseMessage(closeMessage, ref writer); break; default: throw new InvalidDataException($"Unexpected message type: {message.GetType().Name}"); } + + writer.Flush(); } - private void WriteInvocationMessage(InvocationMessage message, Stream packer) + private void WriteInvocationMessage(InvocationMessage message, ref MessagePackWriter writer) { - MessagePackBinary.WriteArrayHeader(packer, 6); + writer.WriteArrayHeader(6); - MessagePackBinary.WriteInt32(packer, HubProtocolConstants.InvocationMessageType); - PackHeaders(packer, message.Headers); + writer.Write(HubProtocolConstants.InvocationMessageType); + PackHeaders(message.Headers, ref writer); if (string.IsNullOrEmpty(message.InvocationId)) { - MessagePackBinary.WriteNil(packer); + writer.WriteNil(); } else { - MessagePackBinary.WriteString(packer, message.InvocationId); + writer.Write(message.InvocationId); } - MessagePackBinary.WriteString(packer, message.Target); - MessagePackBinary.WriteArrayHeader(packer, message.Arguments.Length); + writer.Write(message.Target); + writer.WriteArrayHeader(message.Arguments.Length); foreach (var arg in message.Arguments) { - WriteArgument(arg, packer); + WriteArgument(arg, ref writer); } - WriteStreamIds(message.StreamIds, packer); + WriteStreamIds(message.StreamIds, ref writer); } - private void WriteStreamInvocationMessage(StreamInvocationMessage message, Stream packer) + private void WriteStreamInvocationMessage(StreamInvocationMessage message, ref MessagePackWriter writer) { - MessagePackBinary.WriteArrayHeader(packer, 6); + writer.WriteArrayHeader(6); - MessagePackBinary.WriteInt16(packer, HubProtocolConstants.StreamInvocationMessageType); - PackHeaders(packer, message.Headers); - MessagePackBinary.WriteString(packer, message.InvocationId); - MessagePackBinary.WriteString(packer, message.Target); + writer.Write(HubProtocolConstants.StreamInvocationMessageType); + PackHeaders(message.Headers, ref writer); + writer.Write(message.InvocationId); + writer.Write(message.Target); - MessagePackBinary.WriteArrayHeader(packer, message.Arguments.Length); + writer.WriteArrayHeader(message.Arguments.Length); foreach (var arg in message.Arguments) { - WriteArgument(arg, packer); + WriteArgument(arg, ref writer); } - WriteStreamIds(message.StreamIds, packer); + WriteStreamIds(message.StreamIds, ref writer); } - private void WriteStreamingItemMessage(StreamItemMessage message, Stream packer) + private void WriteStreamingItemMessage(StreamItemMessage message, ref MessagePackWriter writer) { - MessagePackBinary.WriteArrayHeader(packer, 4); - MessagePackBinary.WriteInt16(packer, HubProtocolConstants.StreamItemMessageType); - PackHeaders(packer, message.Headers); - MessagePackBinary.WriteString(packer, message.InvocationId); - WriteArgument(message.Item, packer); + writer.WriteArrayHeader(4); + writer.Write(HubProtocolConstants.StreamItemMessageType); + PackHeaders(message.Headers, ref writer); + writer.Write(message.InvocationId); + WriteArgument(message.Item, ref writer); } - private void WriteArgument(object argument, Stream stream) + private void WriteArgument(object argument, ref MessagePackWriter writer) { if (argument == null) { - MessagePackBinary.WriteNil(stream); + writer.WriteNil(); } else { - MessagePackSerializer.NonGeneric.Serialize(argument.GetType(), stream, argument, _resolver); + MessagePackSerializer.Serialize(argument.GetType(), ref writer, argument, _msgPackSerializerOptions); } } - private void WriteStreamIds(string[] streamIds, Stream packer) + private void WriteStreamIds(string[] streamIds, ref MessagePackWriter writer) { if (streamIds != null) { - MessagePackBinary.WriteArrayHeader(packer, streamIds.Length); + writer.WriteArrayHeader(streamIds.Length); foreach (var streamId in streamIds) { - MessagePackBinary.WriteString(packer, streamId); + writer.Write(streamId); } } else { - MessagePackBinary.WriteArrayHeader(packer, 0); + writer.WriteArrayHeader(0); } } - private void WriteCompletionMessage(CompletionMessage message, Stream packer) + private void WriteCompletionMessage(CompletionMessage message, ref MessagePackWriter writer) { var resultKind = message.Error != null ? ErrorResult : message.HasResult ? NonVoidResult : VoidResult; - MessagePackBinary.WriteArrayHeader(packer, 4 + (resultKind != VoidResult ? 1 : 0)); - MessagePackBinary.WriteInt32(packer, HubProtocolConstants.CompletionMessageType); - PackHeaders(packer, message.Headers); - MessagePackBinary.WriteString(packer, message.InvocationId); - MessagePackBinary.WriteInt32(packer, resultKind); + writer.WriteArrayHeader(4 + (resultKind != VoidResult ? 1 : 0)); + writer.Write(HubProtocolConstants.CompletionMessageType); + PackHeaders(message.Headers, ref writer); + writer.Write(message.InvocationId); + writer.Write(resultKind); switch (resultKind) { case ErrorResult: - MessagePackBinary.WriteString(packer, message.Error); + writer.Write(message.Error); break; case NonVoidResult: - WriteArgument(message.Result, packer); + WriteArgument(message.Result, ref writer); break; } } - private void WriteCancelInvocationMessage(CancelInvocationMessage message, Stream packer) + private void WriteCancelInvocationMessage(CancelInvocationMessage message, ref MessagePackWriter writer) { - MessagePackBinary.WriteArrayHeader(packer, 3); - MessagePackBinary.WriteInt16(packer, HubProtocolConstants.CancelInvocationMessageType); - PackHeaders(packer, message.Headers); - MessagePackBinary.WriteString(packer, message.InvocationId); + writer.WriteArrayHeader(3); + writer.Write(HubProtocolConstants.CancelInvocationMessageType); + PackHeaders(message.Headers, ref writer); + writer.Write(message.InvocationId); } - private void WriteCloseMessage(CloseMessage message, Stream packer) + private void WriteCloseMessage(CloseMessage message, ref MessagePackWriter writer) { - MessagePackBinary.WriteArrayHeader(packer, 3); - MessagePackBinary.WriteInt16(packer, HubProtocolConstants.CloseMessageType); + writer.WriteArrayHeader(3); + writer.Write(HubProtocolConstants.CloseMessageType); if (string.IsNullOrEmpty(message.Error)) { - MessagePackBinary.WriteNil(packer); + writer.WriteNil(); } else { - MessagePackBinary.WriteString(packer, message.Error); + writer.Write(message.Error); } - MessagePackBinary.WriteBoolean(packer, message.AllowReconnect); + writer.Write(message.AllowReconnect); } - private void WritePingMessage(PingMessage pingMessage, Stream packer) + private void WritePingMessage(PingMessage pingMessage, ref MessagePackWriter writer) { - MessagePackBinary.WriteArrayHeader(packer, 1); - MessagePackBinary.WriteInt32(packer, HubProtocolConstants.PingMessageType); + writer.WriteArrayHeader(1); + writer.Write(HubProtocolConstants.PingMessageType); } - private void PackHeaders(Stream packer, IDictionary headers) + private void PackHeaders(IDictionary headers, ref MessagePackWriter writer) { if (headers != null) { - MessagePackBinary.WriteMapHeader(packer, headers.Count); + writer.WriteMapHeader(headers.Count); if (headers.Count > 0) { foreach (var header in headers) { - MessagePackBinary.WriteString(packer, header.Key); - MessagePackBinary.WriteString(packer, header.Value); + writer.Write(header.Key); + writer.Write(header.Value); } } } else { - MessagePackBinary.WriteMapHeader(packer, 0); + writer.WriteMapHeader(0); } } - private static string ReadInvocationId(byte[] input, ref int offset) - { - return ReadString(input, ref offset, "invocationId"); - } + private static string ReadInvocationId(ref MessagePackReader reader) => + ReadString(ref reader, "invocationId"); - private static bool ReadBoolean(byte[] input, ref int offset, string field) + private static bool ReadBoolean(ref MessagePackReader reader, string field) { - Exception msgPackException = null; try { - var readBool = MessagePackBinary.ReadBoolean(input, offset, out var readSize); - offset += readSize; - return readBool; - } - catch (Exception e) - { - msgPackException = e; - } - - throw new InvalidDataException($"Reading '{field}' as Boolean failed.", msgPackException); - } - - private static int ReadInt32(byte[] input, ref int offset, string field) - { - Exception msgPackException = null; - try - { - var readInt = MessagePackBinary.ReadInt32(input, offset, out var readSize); - offset += readSize; - return readInt; - } - catch (Exception e) - { - msgPackException = e; - } - - throw new InvalidDataException($"Reading '{field}' as Int32 failed.", msgPackException); - } - - private static string ReadString(byte[] input, ref int offset, string field) - { - Exception msgPackException = null; - try - { - var readString = MessagePackBinary.ReadString(input, offset, out var readSize); - offset += readSize; - return readString; - } - catch (Exception e) - { - msgPackException = e; - } - - throw new InvalidDataException($"Reading '{field}' as String failed.", msgPackException); - } - - private static long ReadMapLength(byte[] input, ref int offset, string field) - { - Exception msgPackException = null; - try - { - var readMap = MessagePackBinary.ReadMapHeader(input, offset, out var readSize); - offset += readSize; - return readMap; - } - catch (Exception e) - { - msgPackException = e; - } - - throw new InvalidDataException($"Reading map length for '{field}' failed.", msgPackException); - } - - private static long ReadArrayLength(byte[] input, ref int offset, string field) - { - Exception msgPackException = null; - try - { - var readArray = MessagePackBinary.ReadArrayHeader(input, offset, out var readSize); - offset += readSize; - return readArray; - } - catch (Exception e) - { - msgPackException = e; - } - - throw new InvalidDataException($"Reading array length for '{field}' failed.", msgPackException); - } - - private static object DeserializeObject(byte[] input, ref int offset, Type type, string field, IFormatterResolver resolver) - { - Exception msgPackException = null; - try - { - var obj = MessagePackSerializer.NonGeneric.Deserialize(type, new ArraySegment(input, offset, input.Length - offset), resolver); - offset += MessagePackBinary.ReadNextBlock(input, offset); - return obj; + return reader.ReadBoolean(); } catch (Exception ex) { - msgPackException = ex; + throw new InvalidDataException($"Reading '{field}' as Boolean failed.", ex); + } + } + + private static int ReadInt32(ref MessagePackReader reader, string field) + { + try + { + return reader.ReadInt32(); + } + catch (Exception ex) + { + throw new InvalidDataException($"Reading '{field}' as Int32 failed.", ex); + } + } + + private static string ReadString(ref MessagePackReader reader, string field) + { + try + { + return reader.ReadString(); + } + catch (Exception ex) + { + throw new InvalidDataException($"Reading '{field}' as String failed.", ex); + } + } + + private static long ReadMapLength(ref MessagePackReader reader, string field) + { + try + { + return reader.ReadMapHeader(); + } + catch (Exception ex) + { + throw new InvalidDataException($"Reading map length for '{field}' failed.", ex); } - throw new InvalidDataException($"Deserializing object of the `{type.Name}` type for '{field}' failed.", msgPackException); + } + + private static long ReadArrayLength(ref MessagePackReader reader, string field) + { + try + { + return reader.ReadArrayHeader(); + } + catch (Exception ex) + { + throw new InvalidDataException($"Reading array length for '{field}' failed.", ex); + } + } + + private static object DeserializeObject(ref MessagePackReader reader, Type type, string field, MessagePackSerializerOptions msgPackSerializerOptions) + { + try + { + return MessagePackSerializer.Deserialize(type, ref reader, msgPackSerializerOptions); + } + catch (Exception ex) + { + throw new InvalidDataException($"Deserializing object of the `{type.Name}` type for '{field}' failed.", ex); + } } internal static List CreateDefaultFormatterResolvers() @@ -703,10 +666,10 @@ namespace Microsoft.AspNetCore.SignalR.Protocol { public static readonly IFormatterResolver Instance = new SignalRResolver(); - public static readonly IList Resolvers = new[] + public static readonly IList Resolvers = new IFormatterResolver[] { - MessagePack.Resolvers.DynamicEnumAsStringResolver.Instance, - MessagePack.Resolvers.ContractlessStandardResolver.Instance, + DynamicEnumAsStringResolver.Instance, + ContractlessStandardResolver.Instance, }; public IMessagePackFormatter GetFormatter() @@ -731,30 +694,5 @@ namespace Microsoft.AspNetCore.SignalR.Protocol } } } - - // Support for users making their own Formatter lists - internal class CombinedResolvers : IFormatterResolver - { - private readonly IList _resolvers; - - public CombinedResolvers(IList resolvers) - { - _resolvers = resolvers; - } - - public IMessagePackFormatter GetFormatter() - { - foreach (var resolver in _resolvers) - { - var formatter = resolver.GetFormatter(); - if (formatter != null) - { - return formatter; - } - } - - return null; - } - } } } diff --git a/src/SignalR/common/Protocols.NewtonsoftJson/src/Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson.csproj b/src/SignalR/common/Protocols.NewtonsoftJson/src/Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson.csproj index 04f27fbe95..73eff24cd1 100644 --- a/src/SignalR/common/Protocols.NewtonsoftJson/src/Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson.csproj +++ b/src/SignalR/common/Protocols.NewtonsoftJson/src/Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson.csproj @@ -5,7 +5,7 @@ netstandard2.0 Microsoft.AspNetCore.SignalR true - true + true diff --git a/src/SignalR/common/Shared/ReusableUtf8JsonWriter.cs b/src/SignalR/common/Shared/ReusableUtf8JsonWriter.cs index 1dc980d750..c05c0397e6 100644 --- a/src/SignalR/common/Shared/ReusableUtf8JsonWriter.cs +++ b/src/SignalR/common/Shared/ReusableUtf8JsonWriter.cs @@ -3,6 +3,7 @@ using System; using System.Buffers; +using System.Text.Encodings.Web; using System.Text.Json; namespace Microsoft.AspNetCore.Internal @@ -20,7 +21,13 @@ namespace Microsoft.AspNetCore.Internal public ReusableUtf8JsonWriter(IBufferWriter stream) { - _writer = new Utf8JsonWriter(stream, new JsonWriterOptions() { SkipValidation = true }); + _writer = new Utf8JsonWriter(stream, new JsonWriterOptions() + { +#if !DEBUG + SkipValidation = true, +#endif + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping + }); } public static ReusableUtf8JsonWriter Get(IBufferWriter stream) diff --git a/src/SignalR/common/SignalR.Common/ref/Microsoft.AspNetCore.SignalR.Common.csproj b/src/SignalR/common/SignalR.Common/ref/Microsoft.AspNetCore.SignalR.Common.csproj index ce6ebe8ce1..b1ad909f34 100644 --- a/src/SignalR/common/SignalR.Common/ref/Microsoft.AspNetCore.SignalR.Common.csproj +++ b/src/SignalR/common/SignalR.Common/ref/Microsoft.AspNetCore.SignalR.Common.csproj @@ -2,6 +2,7 @@ netstandard2.0;$(DefaultNetCoreTargetFramework) + $(DefaultNetCoreTargetFramework) diff --git a/src/SignalR/common/SignalR.Common/ref/Microsoft.AspNetCore.SignalR.Common.netcoreapp.cs b/src/SignalR/common/SignalR.Common/ref/Microsoft.AspNetCore.SignalR.Common.netcoreapp.cs index 7b8c7751c8..7a0c3b3617 100644 --- a/src/SignalR/common/SignalR.Common/ref/Microsoft.AspNetCore.SignalR.Common.netcoreapp.cs +++ b/src/SignalR/common/SignalR.Common/ref/Microsoft.AspNetCore.SignalR.Common.netcoreapp.cs @@ -32,15 +32,15 @@ namespace Microsoft.AspNetCore.SignalR.Protocol public static readonly Microsoft.AspNetCore.SignalR.Protocol.CloseMessage Empty; public CloseMessage(string error) { } public CloseMessage(string error, bool allowReconnect) { } - public bool AllowReconnect { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public string Error { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public bool AllowReconnect { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public string Error { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } } public partial class CompletionMessage : Microsoft.AspNetCore.SignalR.Protocol.HubInvocationMessage { public CompletionMessage(string invocationId, string error, object result, bool hasResult) : base (default(string)) { } - public string Error { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public bool HasResult { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public object Result { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public string Error { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public bool HasResult { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public object Result { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } public static Microsoft.AspNetCore.SignalR.Protocol.CompletionMessage Empty(string invocationId) { throw null; } public override string ToString() { throw null; } public static Microsoft.AspNetCore.SignalR.Protocol.CompletionMessage WithError(string invocationId, string error) { throw null; } @@ -57,20 +57,20 @@ namespace Microsoft.AspNetCore.SignalR.Protocol public partial class HandshakeRequestMessage : Microsoft.AspNetCore.SignalR.Protocol.HubMessage { public HandshakeRequestMessage(string protocol, int version) { } - public string Protocol { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public int Version { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public string Protocol { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public int Version { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } } public partial class HandshakeResponseMessage : Microsoft.AspNetCore.SignalR.Protocol.HubMessage { public static readonly Microsoft.AspNetCore.SignalR.Protocol.HandshakeResponseMessage Empty; public HandshakeResponseMessage(string error) { } - public string Error { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public string Error { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } } public abstract partial class HubInvocationMessage : Microsoft.AspNetCore.SignalR.Protocol.HubMessage { protected HubInvocationMessage(string invocationId) { } - public System.Collections.Generic.IDictionary Headers { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public string InvocationId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public System.Collections.Generic.IDictionary Headers { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public string InvocationId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } } public abstract partial class HubMessage { @@ -80,9 +80,9 @@ namespace Microsoft.AspNetCore.SignalR.Protocol { protected HubMethodInvocationMessage(string invocationId, string target, object[] arguments) : base (default(string)) { } protected HubMethodInvocationMessage(string invocationId, string target, object[] arguments, string[] streamIds) : base (default(string)) { } - public object[] Arguments { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public string[] StreamIds { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public string Target { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public object[] Arguments { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public string[] StreamIds { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public string Target { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } } public static partial class HubProtocolConstants { @@ -111,8 +111,8 @@ namespace Microsoft.AspNetCore.SignalR.Protocol public partial class InvocationBindingFailureMessage : Microsoft.AspNetCore.SignalR.Protocol.HubInvocationMessage { public InvocationBindingFailureMessage(string invocationId, string target, System.Runtime.ExceptionServices.ExceptionDispatchInfo bindingFailure) : base (default(string)) { } - public System.Runtime.ExceptionServices.ExceptionDispatchInfo BindingFailure { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public string Target { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public System.Runtime.ExceptionServices.ExceptionDispatchInfo BindingFailure { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public string Target { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } } public partial class InvocationMessage : Microsoft.AspNetCore.SignalR.Protocol.HubMethodInvocationMessage { @@ -129,8 +129,8 @@ namespace Microsoft.AspNetCore.SignalR.Protocol public partial class StreamBindingFailureMessage : Microsoft.AspNetCore.SignalR.Protocol.HubMessage { public StreamBindingFailureMessage(string id, System.Runtime.ExceptionServices.ExceptionDispatchInfo bindingFailure) { } - public System.Runtime.ExceptionServices.ExceptionDispatchInfo BindingFailure { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public string Id { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public System.Runtime.ExceptionServices.ExceptionDispatchInfo BindingFailure { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public string Id { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } } public partial class StreamInvocationMessage : Microsoft.AspNetCore.SignalR.Protocol.HubMethodInvocationMessage { @@ -141,7 +141,7 @@ namespace Microsoft.AspNetCore.SignalR.Protocol public partial class StreamItemMessage : Microsoft.AspNetCore.SignalR.Protocol.HubInvocationMessage { public StreamItemMessage(string invocationId, object item) : base (default(string)) { } - public object Item { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public object Item { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } public override string ToString() { throw null; } } } diff --git a/src/SignalR/common/SignalR.Common/ref/Microsoft.AspNetCore.SignalR.Common.netstandard2.0.cs b/src/SignalR/common/SignalR.Common/ref/Microsoft.AspNetCore.SignalR.Common.netstandard2.0.cs index 7b8c7751c8..7a0c3b3617 100644 --- a/src/SignalR/common/SignalR.Common/ref/Microsoft.AspNetCore.SignalR.Common.netstandard2.0.cs +++ b/src/SignalR/common/SignalR.Common/ref/Microsoft.AspNetCore.SignalR.Common.netstandard2.0.cs @@ -32,15 +32,15 @@ namespace Microsoft.AspNetCore.SignalR.Protocol public static readonly Microsoft.AspNetCore.SignalR.Protocol.CloseMessage Empty; public CloseMessage(string error) { } public CloseMessage(string error, bool allowReconnect) { } - public bool AllowReconnect { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public string Error { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public bool AllowReconnect { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public string Error { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } } public partial class CompletionMessage : Microsoft.AspNetCore.SignalR.Protocol.HubInvocationMessage { public CompletionMessage(string invocationId, string error, object result, bool hasResult) : base (default(string)) { } - public string Error { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public bool HasResult { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public object Result { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public string Error { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public bool HasResult { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public object Result { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } public static Microsoft.AspNetCore.SignalR.Protocol.CompletionMessage Empty(string invocationId) { throw null; } public override string ToString() { throw null; } public static Microsoft.AspNetCore.SignalR.Protocol.CompletionMessage WithError(string invocationId, string error) { throw null; } @@ -57,20 +57,20 @@ namespace Microsoft.AspNetCore.SignalR.Protocol public partial class HandshakeRequestMessage : Microsoft.AspNetCore.SignalR.Protocol.HubMessage { public HandshakeRequestMessage(string protocol, int version) { } - public string Protocol { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public int Version { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public string Protocol { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public int Version { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } } public partial class HandshakeResponseMessage : Microsoft.AspNetCore.SignalR.Protocol.HubMessage { public static readonly Microsoft.AspNetCore.SignalR.Protocol.HandshakeResponseMessage Empty; public HandshakeResponseMessage(string error) { } - public string Error { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public string Error { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } } public abstract partial class HubInvocationMessage : Microsoft.AspNetCore.SignalR.Protocol.HubMessage { protected HubInvocationMessage(string invocationId) { } - public System.Collections.Generic.IDictionary Headers { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public string InvocationId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public System.Collections.Generic.IDictionary Headers { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public string InvocationId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } } public abstract partial class HubMessage { @@ -80,9 +80,9 @@ namespace Microsoft.AspNetCore.SignalR.Protocol { protected HubMethodInvocationMessage(string invocationId, string target, object[] arguments) : base (default(string)) { } protected HubMethodInvocationMessage(string invocationId, string target, object[] arguments, string[] streamIds) : base (default(string)) { } - public object[] Arguments { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public string[] StreamIds { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public string Target { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public object[] Arguments { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public string[] StreamIds { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public string Target { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } } public static partial class HubProtocolConstants { @@ -111,8 +111,8 @@ namespace Microsoft.AspNetCore.SignalR.Protocol public partial class InvocationBindingFailureMessage : Microsoft.AspNetCore.SignalR.Protocol.HubInvocationMessage { public InvocationBindingFailureMessage(string invocationId, string target, System.Runtime.ExceptionServices.ExceptionDispatchInfo bindingFailure) : base (default(string)) { } - public System.Runtime.ExceptionServices.ExceptionDispatchInfo BindingFailure { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public string Target { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public System.Runtime.ExceptionServices.ExceptionDispatchInfo BindingFailure { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public string Target { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } } public partial class InvocationMessage : Microsoft.AspNetCore.SignalR.Protocol.HubMethodInvocationMessage { @@ -129,8 +129,8 @@ namespace Microsoft.AspNetCore.SignalR.Protocol public partial class StreamBindingFailureMessage : Microsoft.AspNetCore.SignalR.Protocol.HubMessage { public StreamBindingFailureMessage(string id, System.Runtime.ExceptionServices.ExceptionDispatchInfo bindingFailure) { } - public System.Runtime.ExceptionServices.ExceptionDispatchInfo BindingFailure { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public string Id { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public System.Runtime.ExceptionServices.ExceptionDispatchInfo BindingFailure { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public string Id { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } } public partial class StreamInvocationMessage : Microsoft.AspNetCore.SignalR.Protocol.HubMethodInvocationMessage { @@ -141,7 +141,7 @@ namespace Microsoft.AspNetCore.SignalR.Protocol public partial class StreamItemMessage : Microsoft.AspNetCore.SignalR.Protocol.HubInvocationMessage { public StreamItemMessage(string invocationId, object item) : base (default(string)) { } - public object Item { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public object Item { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } public override string ToString() { throw null; } } } diff --git a/src/SignalR/common/SignalR.Common/src/Microsoft.AspNetCore.SignalR.Common.csproj b/src/SignalR/common/SignalR.Common/src/Microsoft.AspNetCore.SignalR.Common.csproj index 8d374d387d..17252e2b1f 100644 --- a/src/SignalR/common/SignalR.Common/src/Microsoft.AspNetCore.SignalR.Common.csproj +++ b/src/SignalR/common/SignalR.Common/src/Microsoft.AspNetCore.SignalR.Common.csproj @@ -28,7 +28,7 @@ - + diff --git a/src/SignalR/common/SignalR.Common/test/Internal/Formatters/BinaryMessageParserTests.cs b/src/SignalR/common/SignalR.Common/test/Internal/Formatters/BinaryMessageParserTests.cs index c2f02811e7..8cd0170c9f 100644 --- a/src/SignalR/common/SignalR.Common/test/Internal/Formatters/BinaryMessageParserTests.cs +++ b/src/SignalR/common/SignalR.Common/test/Internal/Formatters/BinaryMessageParserTests.cs @@ -67,7 +67,7 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Formatters { var ex = Assert.Throws(() => { - var buffer = new ReadOnlySequence(payload);; + var buffer = new ReadOnlySequence(payload); BinaryMessageParser.TryParseMessage(ref buffer, out var message); }); Assert.Equal("Messages over 2GB in size are not supported.", ex.Message); diff --git a/src/SignalR/common/SignalR.Common/test/Internal/Protocol/HandshakeProtocolTests.cs b/src/SignalR/common/SignalR.Common/test/Internal/Protocol/HandshakeProtocolTests.cs index 27560c502a..9a88d32a57 100644 --- a/src/SignalR/common/SignalR.Common/test/Internal/Protocol/HandshakeProtocolTests.cs +++ b/src/SignalR/common/SignalR.Common/test/Internal/Protocol/HandshakeProtocolTests.cs @@ -15,6 +15,12 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol [InlineData("{\"protocol\":\"dummy\",\"version\":1}\u001e", "dummy", 1)] [InlineData("{\"protocol\":\"\",\"version\":10}\u001e", "", 10)] [InlineData("{\"protocol\":\"\",\"version\":10,\"unknown\":null}\u001e", "", 10)] + [InlineData("{\"protocol\":\"firstProtocol\",\"protocol\":\"secondProtocol\",\"version\":1}\u001e", "secondProtocol", 1)] + [InlineData("{\"protocol\":\"firstProtocol\",\"protocol\":\"secondProtocol\",\"version\":1,\"version\":75}\u001e", "secondProtocol", 75)] + [InlineData("{\"protocol\":\"dummy\",\"version\":1,\"ignoredField\":99}\u001e", "dummy", 1)] + [InlineData("{\"protocol\":\"dummy\",\"version\":1}{\"protocol\":\"wrong\",\"version\":99}\u001e", "dummy", 1)] + [InlineData("{\"protocol\":\"\\u0064ummy\",\"version\":1}\u001e", "dummy", 1)] + [InlineData("{\"\\u0070rotoco\\u006c\":\"\\u0064ummy\",\"version\":1}\u001e", "dummy", 1)] public void ParsingHandshakeRequestMessageSuccessForValidMessages(string json, string protocol, int version) { var message = new ReadOnlySequence(Encoding.UTF8.GetBytes(json)); diff --git a/src/SignalR/common/SignalR.Common/test/Internal/Protocol/JsonHubProtocolTests.cs b/src/SignalR/common/SignalR.Common/test/Internal/Protocol/JsonHubProtocolTests.cs index a2f696ab17..6f9ba5cb60 100644 --- a/src/SignalR/common/SignalR.Common/test/Internal/Protocol/JsonHubProtocolTests.cs +++ b/src/SignalR/common/SignalR.Common/test/Internal/Protocol/JsonHubProtocolTests.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; +using System.Text.Encodings.Web; using System.Text.Json; using Microsoft.AspNetCore.Internal; using Microsoft.AspNetCore.SignalR.Protocol; @@ -28,7 +29,8 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol PayloadSerializerOptions = new JsonSerializerOptions() { IgnoreNullValues = ignoreNullValues, - PropertyNamingPolicy = useCamelCase ? JsonNamingPolicy.CamelCase : null + PropertyNamingPolicy = useCamelCase ? JsonNamingPolicy.CamelCase : null, + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, } }; @@ -39,6 +41,7 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol [InlineData("", "Error reading JSON.")] [InlineData("42", "Unexpected JSON Token Type 'Number'. Expected a JSON Object.")] [InlineData("{\"type\":\"foo\"}", "Expected 'type' to be of type Number.")] + [InlineData("{\"type\":3,\"invocationId\":\"42\",\"result\":true", "Error reading JSON.")] public void CustomInvalidMessages(string input, string expectedMessage) { input = Frame(input); @@ -101,98 +104,18 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol Assert.Equal(expectedMessage, message); } - [Fact] - public void ReadCaseInsensitivePropertiesByDefault() - { - var input = Frame("{\"type\":2,\"invocationId\":\"123\",\"item\":{\"StrIngProp\":\"test\",\"DoublePrOp\":3.14159,\"IntProp\":43,\"DateTimeProp\":\"2019-06-03T22:00:00\",\"NuLLProp\":null,\"ByteARRProp\":\"AgQG\"}}"); - - var binder = new TestBinder(null, typeof(TemporaryCustomObject)); - var data = new ReadOnlySequence(Encoding.UTF8.GetBytes(input)); - JsonHubProtocol.TryParseMessage(ref data, binder, out var message); - - var streamItemMessage = Assert.IsType(message); - Assert.Equal(new TemporaryCustomObject() - { - ByteArrProp = new byte[] { 2, 4, 6 }, - IntProp = 43, - DoubleProp = 3.14159, - StringProp = "test", - DateTimeProp = DateTime.Parse("6/3/2019 10:00:00 PM") - }, streamItemMessage.Item); - } - public static IDictionary CustomProtocolTestData => new[] { new JsonProtocolTestData("InvocationMessage_HasFloatArgument", new InvocationMessage(null, "Target", new object[] { 1, "Foo", 2.0f }), true, true, "{\"type\":1,\"target\":\"Target\",\"arguments\":[1,\"Foo\",2]}"), - new JsonProtocolTestData("InvocationMessage_StringIsoDateArgument", new InvocationMessage("Method", new object[] { "2016-05-10T13:51:20+12:34" }), true, true, "{\"type\":1,\"target\":\"Method\",\"arguments\":[\"2016-05-10T13:51:20\\u002B12:34\"]}"), - new JsonProtocolTestData("InvocationMessage_HasCustomArgumentWithNoCamelCase", new InvocationMessage(null, "Target", new object[] { new TemporaryCustomObject() { ByteArrProp = new byte[] { 1, 2, 3 } } }), false, true, "{\"type\":1,\"target\":\"Target\",\"arguments\":[{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"ByteArrProp\":\"AQID\"}]}"), - new JsonProtocolTestData("InvocationMessage_HasCustomArgumentWithNullValueIgnore", new InvocationMessage(null, "Target", new object[] { new TemporaryCustomObject() { ByteArrProp = new byte[] { 1, 2, 3 } } }), true, true, "{\"type\":1,\"target\":\"Target\",\"arguments\":[{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"byteArrProp\":\"AQID\"}]}"), - new JsonProtocolTestData("InvocationMessage_HasCustomArgumentWithNullValueIgnoreAndNoCamelCase", new InvocationMessage(null, "Target", new object[] { new TemporaryCustomObject() { ByteArrProp = new byte[] { 1, 2, 3 } } }), false, false, "{\"type\":1,\"target\":\"Target\",\"arguments\":[{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"NullProp\":null,\"ByteArrProp\":\"AQID\"}]}"), - new JsonProtocolTestData("InvocationMessage_HasCustomArgumentWithNullValueInclude", new InvocationMessage(null, "Target", new object[] { new TemporaryCustomObject() { ByteArrProp = new byte[] { 1, 2, 3 } } }), true, false, "{\"type\":1,\"target\":\"Target\",\"arguments\":[{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}]}"), new JsonProtocolTestData("InvocationMessage_HasHeaders", AddHeaders(TestHeaders, new InvocationMessage("123", "Target", new object[] { 1, "Foo", 2.0f })), true, true, "{\"type\":1," + SerializedHeaders + ",\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[1,\"Foo\",2]}"), - new JsonProtocolTestData("StreamItemMessage_HasCustomItemWithNoCamelCase", new StreamItemMessage("123", new TemporaryCustomObject() { ByteArrProp = new byte[] { 1, 2, 3 } }), false, true, "{\"type\":2,\"invocationId\":\"123\",\"item\":{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"ByteArrProp\":\"AQID\"}}"), - new JsonProtocolTestData("StreamItemMessage_HasCustomItemWithNullValueIgnore", new StreamItemMessage("123", new TemporaryCustomObject() { ByteArrProp = new byte[] { 1, 2, 3 } }), true, true, "{\"type\":2,\"invocationId\":\"123\",\"item\":{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"byteArrProp\":\"AQID\"}}"), - new JsonProtocolTestData("StreamItemMessage_HasCustomItemWithNullValueIgnoreAndNoCamelCase", new StreamItemMessage("123", new TemporaryCustomObject() { ByteArrProp = new byte[] { 1, 2, 3 } }), false, false, "{\"type\":2,\"invocationId\":\"123\",\"item\":{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"NullProp\":null,\"ByteArrProp\":\"AQID\"}}"), - new JsonProtocolTestData("StreamItemMessage_HasCustomItemWithNullValueInclude", new StreamItemMessage("123", new TemporaryCustomObject() { ByteArrProp = new byte[] { 1, 2, 3 } }), true, false, "{\"type\":2,\"invocationId\":\"123\",\"item\":{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}}"), new JsonProtocolTestData("StreamItemMessage_HasFloatItem", new StreamItemMessage("123", 2.0f), true, true, "{\"type\":2,\"invocationId\":\"123\",\"item\":2}"), - new JsonProtocolTestData("StreamItemMessage_HasHeaders", AddHeaders(TestHeaders, new StreamItemMessage("123", new TemporaryCustomObject() { ByteArrProp = new byte[] { 1, 2, 3 } })), true, false, "{\"type\":2," + SerializedHeaders + ",\"invocationId\":\"123\",\"item\":{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}}"), new JsonProtocolTestData("CompletionMessage_HasFloatResult", CompletionMessage.WithResult("123", 2.0f), true, true, "{\"type\":3,\"invocationId\":\"123\",\"result\":2}"), - new JsonProtocolTestData("CompletionMessage_HasCustomResultWithNoCamelCase", CompletionMessage.WithResult("123", new TemporaryCustomObject() { ByteArrProp = new byte[] { 1, 2, 3 } }), false, true, "{\"type\":3,\"invocationId\":\"123\",\"result\":{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"ByteArrProp\":\"AQID\"}}"), - new JsonProtocolTestData("CompletionMessage_HasCustomResultWithNullValueIgnore", CompletionMessage.WithResult("123", new TemporaryCustomObject() { ByteArrProp = new byte[] { 1, 2, 3 } }), true, true, "{\"type\":3,\"invocationId\":\"123\",\"result\":{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"byteArrProp\":\"AQID\"}}"), - new JsonProtocolTestData("CompletionMessage_HasCustomResultWithNullValueIncludeAndNoCamelCase", CompletionMessage.WithResult("123", new TemporaryCustomObject() { ByteArrProp = new byte[] { 1, 2, 3 } }), false, false, "{\"type\":3,\"invocationId\":\"123\",\"result\":{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"NullProp\":null,\"ByteArrProp\":\"AQID\"}}"), - new JsonProtocolTestData("CompletionMessage_HasCustomResultWithNullValueInclude", CompletionMessage.WithResult("123", new TemporaryCustomObject() { ByteArrProp = new byte[] { 1, 2, 3 } }), true, false, "{\"type\":3,\"invocationId\":\"123\",\"result\":{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}}"), - new JsonProtocolTestData("CompletionMessage_HasErrorAndCamelCase", CompletionMessage.Empty("123"), true, true, "{\"type\":3,\"invocationId\":\"123\"}"), - new JsonProtocolTestData("StreamInvocationMessage_HasCustomArgumentWithNoCamelCase", new StreamInvocationMessage("123", "Target", new object[] { new TemporaryCustomObject() { ByteArrProp = new byte[] { 1, 2, 3 } } }), false, true, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"ByteArrProp\":\"AQID\"}]}"), - new JsonProtocolTestData("StreamInvocationMessage_HasCustomArgumentWithNullValueIgnore", new StreamInvocationMessage("123", "Target", new object[] { new TemporaryCustomObject() { ByteArrProp = new byte[] { 1, 2, 3 } } }), true, true, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"byteArrProp\":\"AQID\"}]}"), - new JsonProtocolTestData("StreamInvocationMessage_HasCustomArgumentWithNullValueIgnoreAndNoCamelCase", new StreamInvocationMessage("123", "Target", new object[] { new TemporaryCustomObject() { ByteArrProp = new byte[] { 1, 2, 3 } } }), false, false, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"NullProp\":null,\"ByteArrProp\":\"AQID\"}]}"), - new JsonProtocolTestData("StreamInvocationMessage_HasCustomArgumentWithNullValueInclude", new StreamInvocationMessage("123", "Target", new object[] { new TemporaryCustomObject() { ByteArrProp = new byte[] { 1, 2, 3 } } }), true, false, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}]}"), new JsonProtocolTestData("StreamInvocationMessage_HasFloatArgument", new StreamInvocationMessage("123", "Target", new object[] { 1, "Foo", 2.0f }), true, true, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[1,\"Foo\",2]}"), - new JsonProtocolTestData("StreamInvocationMessage_HasHeaders", AddHeaders(TestHeaders, new StreamInvocationMessage("123", "Target", new object[] { new TemporaryCustomObject() { ByteArrProp = new byte[] { 1, 2, 3 } } })), true, false, "{\"type\":4," + SerializedHeaders + ",\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}]}"), }.ToDictionary(t => t.Name); public static IEnumerable CustomProtocolTestDataNames => CustomProtocolTestData.Keys.Select(name => new object[] { name }); } - - // Revert back to CustomObject when initial values on arrays are supported - // e.g. byte[] arr { get; set; } = byte[] { 1, 2, 3 }; - internal class TemporaryCustomObject : IEquatable - { - // Not intended to be a full set of things, just a smattering of sample serializations - public string StringProp { get; set; } = "SignalR!"; - - public double DoubleProp { get; set; } = 6.2831853071; - - public int IntProp { get; set; } = 42; - - public DateTime DateTimeProp { get; set; } = new DateTime(2017, 4, 11, 0, 0, 0, DateTimeKind.Utc); - - public object NullProp { get; set; } = null; - - public byte[] ByteArrProp { get; set; } - - public override bool Equals(object obj) - { - return obj is TemporaryCustomObject o && Equals(o); - } - - public override int GetHashCode() - { - // This is never used in a hash table - return 0; - } - - public bool Equals(TemporaryCustomObject right) - { - // This allows the comparer below to properly compare the object in the test. - return string.Equals(StringProp, right.StringProp, StringComparison.Ordinal) && - DoubleProp == right.DoubleProp && - IntProp == right.IntProp && - DateTime.Equals(DateTimeProp, right.DateTimeProp) && - NullProp == right.NullProp && - System.Linq.Enumerable.SequenceEqual(ByteArrProp, right.ByteArrProp); - } - } } diff --git a/src/SignalR/common/SignalR.Common/test/Internal/Protocol/JsonHubProtocolTestsBase.cs b/src/SignalR/common/SignalR.Common/test/Internal/Protocol/JsonHubProtocolTestsBase.cs index 6134d51f16..a38ad6e3e7 100644 --- a/src/SignalR/common/SignalR.Common/test/Internal/Protocol/JsonHubProtocolTestsBase.cs +++ b/src/SignalR/common/SignalR.Common/test/Internal/Protocol/JsonHubProtocolTestsBase.cs @@ -40,12 +40,30 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol new JsonProtocolTestData("InvocationMessage_HasStreamAndNormalArgument", new InvocationMessage(null, "Target", new object[] { 42 }, new string[] { "__test_id__" }), true, true, "{\"type\":1,\"target\":\"Target\",\"arguments\":[42],\"streamIds\":[\"__test_id__\"]}"), new JsonProtocolTestData("InvocationMessage_HasMultipleStreams", new InvocationMessage(null, "Target", Array.Empty(), new string[] { "__test_id__", "__test_id2__" }), true, true, "{\"type\":1,\"target\":\"Target\",\"arguments\":[],\"streamIds\":[\"__test_id__\",\"__test_id2__\"]}"), new JsonProtocolTestData("InvocationMessage_DateTimeOffsetArgument", new InvocationMessage("Method", new object[] { DateTimeOffset.Parse("2016-05-10T13:51:20+12:34") }), true, true, "{\"type\":1,\"target\":\"Method\",\"arguments\":[\"2016-05-10T13:51:20+12:34\"]}"), + new JsonProtocolTestData("InvocationMessage_StringIsoDateArgument", new InvocationMessage("Method", new object[] { "2016-05-10T13:51:20+12:34" }), true, true, "{\"type\":1,\"target\":\"Method\",\"arguments\":[\"2016-05-10T13:51:20+12:34\"]}"), + new JsonProtocolTestData("InvocationMessage_HasNonAsciiArgument", new InvocationMessage("Method", new object[] { "מחרוזת כלשהי" }), true, true, "{\"type\":1,\"target\":\"Method\",\"arguments\":[\"מחרוזת כלשהי\"]}"), + new JsonProtocolTestData("InvocationMessage_HasCustomArgumentWithNoCamelCase", new InvocationMessage(null, "Target", new object[] { new CustomObject() }), false, true, "{\"type\":1,\"target\":\"Target\",\"arguments\":[{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"ByteArrProp\":\"AQID\"}]}"), + new JsonProtocolTestData("InvocationMessage_HasCustomArgumentWithNullValueIgnore", new InvocationMessage(null, "Target", new object[] { new CustomObject() }), true, true, "{\"type\":1,\"target\":\"Target\",\"arguments\":[{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"byteArrProp\":\"AQID\"}]}"), + new JsonProtocolTestData("InvocationMessage_HasCustomArgumentWithNullValueIgnoreAndNoCamelCase", new InvocationMessage(null, "Target", new object[] { new CustomObject() }), false, false, "{\"type\":1,\"target\":\"Target\",\"arguments\":[{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"NullProp\":null,\"ByteArrProp\":\"AQID\"}]}"), + new JsonProtocolTestData("InvocationMessage_HasCustomArgumentWithNullValueInclude", new InvocationMessage(null, "Target", new object[] { new CustomObject() }), true, false, "{\"type\":1,\"target\":\"Target\",\"arguments\":[{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}]}"), + new JsonProtocolTestData("StreamItemMessage_HasHeaders", AddHeaders(TestHeaders, new StreamItemMessage("123", new CustomObject())), true, false, "{\"type\":2," + SerializedHeaders + ",\"invocationId\":\"123\",\"item\":{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}}"), + new JsonProtocolTestData("StreamItemMessage_HasCustomItemWithNoCamelCase", new StreamItemMessage("123", new CustomObject()), false, true, "{\"type\":2,\"invocationId\":\"123\",\"item\":{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"ByteArrProp\":\"AQID\"}}"), + new JsonProtocolTestData("StreamItemMessage_HasCustomItemWithNullValueIgnore", new StreamItemMessage("123", new CustomObject()), true, true, "{\"type\":2,\"invocationId\":\"123\",\"item\":{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"byteArrProp\":\"AQID\"}}"), + new JsonProtocolTestData("StreamItemMessage_HasCustomItemWithNullValueIgnoreAndNoCamelCase", new StreamItemMessage("123", new CustomObject()), false, false, "{\"type\":2,\"invocationId\":\"123\",\"item\":{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"NullProp\":null,\"ByteArrProp\":\"AQID\"}}"), + new JsonProtocolTestData("StreamItemMessage_HasCustomItemWithNullValueInclude", new StreamItemMessage("123", new CustomObject()), true, false, "{\"type\":2,\"invocationId\":\"123\",\"item\":{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}}"), new JsonProtocolTestData("StreamItemMessage_HasIntegerItem", new StreamItemMessage("123", 1), true, true, "{\"type\":2,\"invocationId\":\"123\",\"item\":1}"), new JsonProtocolTestData("StreamItemMessage_HasStringItem", new StreamItemMessage("123", "Foo"), true, true, "{\"type\":2,\"invocationId\":\"123\",\"item\":\"Foo\"}"), new JsonProtocolTestData("StreamItemMessage_HasBoolItem", new StreamItemMessage("123", true), true, true, "{\"type\":2,\"invocationId\":\"123\",\"item\":true}"), new JsonProtocolTestData("StreamItemMessage_HasNullItem", new StreamItemMessage("123", null), true, true, "{\"type\":2,\"invocationId\":\"123\",\"item\":null}"), + new JsonProtocolTestData("CompletionMessage_HasCustomResultWithNoCamelCase", CompletionMessage.WithResult("123", new CustomObject()), false, true, "{\"type\":3,\"invocationId\":\"123\",\"result\":{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"ByteArrProp\":\"AQID\"}}"), + new JsonProtocolTestData("CompletionMessage_HasCustomResultWithNullValueIgnore", CompletionMessage.WithResult("123", new CustomObject()), true, true, "{\"type\":3,\"invocationId\":\"123\",\"result\":{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"byteArrProp\":\"AQID\"}}"), + new JsonProtocolTestData("CompletionMessage_HasCustomResultWithNullValueIncludeAndNoCamelCase", CompletionMessage.WithResult("123", new CustomObject()), false, false, "{\"type\":3,\"invocationId\":\"123\",\"result\":{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"NullProp\":null,\"ByteArrProp\":\"AQID\"}}"), + new JsonProtocolTestData("CompletionMessage_HasCustomResultWithNullValueInclude", CompletionMessage.WithResult("123", new CustomObject()), true, false, "{\"type\":3,\"invocationId\":\"123\",\"result\":{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}}"), + new JsonProtocolTestData("CompletionMessage_HasErrorAndCamelCase", CompletionMessage.Empty("123"), true, true, "{\"type\":3,\"invocationId\":\"123\"}"), + new JsonProtocolTestData("CompletionMessage_HasTestHeadersAndCustomItemResult", AddHeaders(TestHeaders, CompletionMessage.WithResult("123", new CustomObject())), true, false, "{\"type\":3," + SerializedHeaders + ",\"invocationId\":\"123\",\"result\":{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}}"), + new JsonProtocolTestData("CompletionMessage_HasErrorAndHeadersAndCamelCase", AddHeaders(TestHeaders, CompletionMessage.Empty("123")), true, true, "{\"type\":3," + SerializedHeaders + ",\"invocationId\":\"123\"}"), new JsonProtocolTestData("CompletionMessage_HasIntegerResult", CompletionMessage.WithResult("123", 1), true, true, "{\"type\":3,\"invocationId\":\"123\",\"result\":1}"), new JsonProtocolTestData("CompletionMessage_HasStringResult", CompletionMessage.WithResult("123", "Foo"), true, true, "{\"type\":3,\"invocationId\":\"123\",\"result\":\"Foo\"}"), new JsonProtocolTestData("CompletionMessage_HasBoolResult", CompletionMessage.WithResult("123", true), true, true, "{\"type\":3,\"invocationId\":\"123\",\"result\":true}"), @@ -53,6 +71,11 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol new JsonProtocolTestData("CompletionMessage_HasError", CompletionMessage.WithError("123", "Whoops!"), true, true, "{\"type\":3,\"invocationId\":\"123\",\"error\":\"Whoops!\"}"), new JsonProtocolTestData("CompletionMessage_HasErrorAndHeaders", AddHeaders(TestHeaders, CompletionMessage.WithError("123", "Whoops!")), true, true, "{\"type\":3," + SerializedHeaders + ",\"invocationId\":\"123\",\"error\":\"Whoops!\"}"), + new JsonProtocolTestData("StreamInvocationMessage_HasHeaders", AddHeaders(TestHeaders, new StreamInvocationMessage("123", "Target", new object[] { new CustomObject() })), true, false, "{\"type\":4," + SerializedHeaders + ",\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}]}"), + new JsonProtocolTestData("StreamInvocationMessage_HasCustomArgumentWithNoCamelCase", new StreamInvocationMessage("123", "Target", new object[] { new CustomObject() }), false, true, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"ByteArrProp\":\"AQID\"}]}"), + new JsonProtocolTestData("StreamInvocationMessage_HasCustomArgumentWithNullValueIgnore", new StreamInvocationMessage("123", "Target", new object[] { new CustomObject() }), true, true, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"byteArrProp\":\"AQID\"}]}"), + new JsonProtocolTestData("StreamInvocationMessage_HasCustomArgumentWithNullValueIgnoreAndNoCamelCase", new StreamInvocationMessage("123", "Target", new object[] { new CustomObject() }), false, false, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"NullProp\":null,\"ByteArrProp\":\"AQID\"}]}"), + new JsonProtocolTestData("StreamInvocationMessage_HasCustomArgumentWithNullValueInclude", new StreamInvocationMessage("123", "Target", new object[] { new CustomObject() }), true, false, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}]}"), new JsonProtocolTestData("StreamInvocationMessage_HasInvocationId", new StreamInvocationMessage("123", "Target", new object[] { 1, "Foo" }), true, true, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[1,\"Foo\"]}"), new JsonProtocolTestData("StreamInvocationMessage_HasBoolArgument", new StreamInvocationMessage("123", "Target", new object[] { true }), true, true, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[true]}"), new JsonProtocolTestData("StreamInvocationMessage_HasNullArgument", new StreamInvocationMessage("123", "Target", new object[] { null }), true, true, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[null]}"), @@ -158,8 +181,7 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol [InlineData("{\"type\":4,\"invocationId\":\"42\",\"target\":\"foo\"}", "Missing required property 'arguments'.")] [InlineData("{\"type\":4,\"invocationId\":\"42\",\"target\":\"foo\",\"arguments\":{}}", "Expected 'arguments' to be of type Array.")] - //[InlineData("{\"type\":3,\"invocationId\":\"42\",\"error\":\"foo\",\"result\":true}", "The 'error' and 'result' properties are mutually exclusive.")] - //[InlineData("{\"type\":3,\"invocationId\":\"42\",\"result\":true", "Unexpected end when reading JSON.")] + [InlineData("{\"type\":3,\"invocationId\":\"42\",\"error\":\"foo\",\"result\":true}", "The 'error' and 'result' properties are mutually exclusive.")] public void InvalidMessages(string input, string expectedMessage) { input = Frame(input); @@ -272,6 +294,71 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol Assert.Equal("foo", bindingFailure.Target); } + [Fact] + public void ReadCaseInsensitivePropertiesByDefault() + { + var input = Frame("{\"type\":2,\"invocationId\":\"123\",\"item\":{\"StrIngProp\":\"test\",\"DoublePrOp\":3.14159,\"IntProp\":43,\"DateTimeProp\":\"2019-06-03T22:00:00\",\"NuLLProp\":null,\"ByteARRProp\":\"AgQG\"}}"); + + var binder = new TestBinder(null, typeof(CustomObject)); + var data = new ReadOnlySequence(Encoding.UTF8.GetBytes(input)); + JsonHubProtocol.TryParseMessage(ref data, binder, out var message); + + var streamItemMessage = Assert.IsType(message); + Assert.Equal(new CustomObject() + { + ByteArrProp = new byte[] { 2, 4, 6 }, + IntProp = 43, + DoubleProp = 3.14159, + StringProp = "test", + DateTimeProp = DateTime.Parse("6/3/2019 10:00:00 PM") + }, streamItemMessage.Item); + } + + public static IDictionary MessageSizeData => new[] + { + new MessageSizeTestData("InvocationMessage_WithoutInvocationId", new InvocationMessage("Target", new object[] { 1 }), 45), + new MessageSizeTestData("InvocationMessage_WithInvocationId", new InvocationMessage("1", "Target", new object[] { 1 }), 64), + new MessageSizeTestData("InvocationMessage_WithInvocationIdAndStreamId", new InvocationMessage("1", "Target", new object[] { 1 }, new string[] { "2" }), 82), + + new MessageSizeTestData("CloseMessage_Empty", CloseMessage.Empty, 11), + new MessageSizeTestData("CloseMessage_WithError", new CloseMessage("error"), 27), + + new MessageSizeTestData("StreamItemMessage_WithNullItem", new StreamItemMessage("1", null), 42), + new MessageSizeTestData("StreamItemMessage_WithItem", new StreamItemMessage("1", 1), 39), + + new MessageSizeTestData("CompletionMessage_Empty", CompletionMessage.Empty("1"), 30), + new MessageSizeTestData("CompletionMessage_WithResult", CompletionMessage.WithResult("1", 1), 41), + new MessageSizeTestData("CompletionMessage_WithError", CompletionMessage.WithError("1", "error"), 46), + + new MessageSizeTestData("StreamInvocationMessage", new StreamInvocationMessage("1", "target", Array.Empty()), 63), + new MessageSizeTestData("StreamInvocationMessage_WithStreamId", new StreamInvocationMessage("1", "target", Array.Empty(), new [] { "2" }), 81), + + new MessageSizeTestData("CancelInvocationMessage", new CancelInvocationMessage("1"), 30), + + new MessageSizeTestData("PingMessage", PingMessage.Instance, 11), + }.ToDictionary(t => t.Name); + + public static IEnumerable MessageSizeDataNames => MessageSizeData.Keys.Select(name => new object[] { name }); + + [Theory] + [MemberData(nameof(MessageSizeDataNames))] + // These tests check that the message size doesn't change without us being aware of it and making a conscious decision to increase the size + public void VerifyMessageSize(string testDataName) + { + var testData = MessageSizeData[testDataName]; + + var writer = MemoryBufferWriter.Get(); + try + { + JsonHubProtocol.WriteMessage(testData.Message, writer); + Assert.Equal(testData.Size, writer.Length); + } + finally + { + MemoryBufferWriter.Return(writer); + } + } + public static string Frame(string input) { var data = Encoding.UTF8.GetBytes(input); @@ -305,5 +392,21 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol public override string ToString() => Name; } + + public class MessageSizeTestData + { + public string Name { get; } + public HubMessage Message { get; } + public int Size { get; } + + public MessageSizeTestData(string name, HubMessage message, int size) + { + Name = name; + Message = message; + Size = size; + } + + public override string ToString() => Name; + } } } diff --git a/src/SignalR/common/SignalR.Common/test/Internal/Protocol/MessagePackHubProtocolTestBase.cs b/src/SignalR/common/SignalR.Common/test/Internal/Protocol/MessagePackHubProtocolTestBase.cs index 34d97e10f3..0192641dad 100644 --- a/src/SignalR/common/SignalR.Common/test/Internal/Protocol/MessagePackHubProtocolTestBase.cs +++ b/src/SignalR/common/SignalR.Common/test/Internal/Protocol/MessagePackHubProtocolTestBase.cs @@ -375,6 +375,57 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol Assert.Null(message); } + public static IDictionary MessageSizeData => new[] + { + new MessageSizeTestData("InvocationMessage_WithoutInvocationId", new InvocationMessage("Target", new object[] { 1 }), 15), + new MessageSizeTestData("InvocationMessage_WithInvocationId", new InvocationMessage("1", "Target", new object[] { 1 }), 16), + new MessageSizeTestData("InvocationMessage_WithInvocationIdAndStreamId", new InvocationMessage("1", "Target", new object[] { 1 }, new string[] { "2" }), 18), + + new MessageSizeTestData("CloseMessage_Empty", CloseMessage.Empty, 5), + new MessageSizeTestData("CloseMessage_WithError", new CloseMessage("error"), 10), + + new MessageSizeTestData("StreamItemMessage_WithNullItem", new StreamItemMessage("1", null), 7), + new MessageSizeTestData("StreamItemMessage_WithItem", new StreamItemMessage("1", 1), 7), + + new MessageSizeTestData("CompletionMessage_Empty", CompletionMessage.Empty("1"), 7), + new MessageSizeTestData("CompletionMessage_WithResult", CompletionMessage.WithResult("1", 1), 8), + new MessageSizeTestData("CompletionMessage_WithError", CompletionMessage.WithError("1", "error"), 13), + + new MessageSizeTestData("StreamInvocationMessage", new StreamInvocationMessage("1", "target", Array.Empty()), 15), + new MessageSizeTestData("StreamInvocationMessage_WithStreamId", new StreamInvocationMessage("1", "target", Array.Empty(), new [] { "2" }), 17), + + new MessageSizeTestData("CancelInvocationMessage", new CancelInvocationMessage("1"), 6), + + new MessageSizeTestData("PingMessage", PingMessage.Instance, 3), + }.ToDictionary(t => t.Name); + + public static IEnumerable MessageSizeDataNames => MessageSizeData.Keys.Select(name => new object[] { name }); + + [Theory] + [MemberData(nameof(MessageSizeDataNames))] + // These tests check that the message size doesn't change without us being aware of it and making a conscious decision to increase the size + public void VerifyMessageSize(string testDataName) + { + var testData = MessageSizeData[testDataName]; + Assert.Equal(testData.Size, Write(testData.Message).Length); + } + + public class MessageSizeTestData + { + public string Name { get; } + public HubMessage Message { get; } + public int Size { get; } + + public MessageSizeTestData(string name, HubMessage message, int size) + { + Name = name; + Message = message; + Size = size; + } + + public override string ToString() => Name; + } + protected byte ArrayBytes(int size) { Debug.Assert(size < 16, "Test code doesn't support array sizes greater than 15"); diff --git a/src/SignalR/common/SignalR.Common/test/Internal/Protocol/MessagePackHubProtocolTests.cs b/src/SignalR/common/SignalR.Common/test/Internal/Protocol/MessagePackHubProtocolTests.cs index 8134f8e9bf..cf193576d7 100644 --- a/src/SignalR/common/SignalR.Common/test/Internal/Protocol/MessagePackHubProtocolTests.cs +++ b/src/SignalR/common/SignalR.Common/test/Internal/Protocol/MessagePackHubProtocolTests.cs @@ -26,15 +26,20 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol AssertMessages(new byte[] { ArrayBytes(5), 3, 0x80, StringBytes(1), (byte)'0', 0x03, ArrayBytes(1), 42 }, result); } - [Fact] - public void WriteAndParseDateTimeConvertsToUTC() + [Theory] + [InlineData(DateTimeKind.Utc)] + [InlineData(DateTimeKind.Local)] + [InlineData(DateTimeKind.Unspecified)] + public void WriteAndParseDateTimeConvertsToUTC(DateTimeKind dateTimeKind) { - var dateTime = new DateTime(2018, 4, 9); + // The messagepack Timestamp format always converts input DateTime to Utc if they are passed as "DateTimeKind.Local" : + // https://github.com/neuecc/MessagePack-CSharp/pull/520/files#diff-ed970b3daebc708ce49f55d418075979 + var originalDateTime = new DateTime(2018, 4, 9, 0, 0, 0, dateTimeKind); var writer = MemoryBufferWriter.Get(); try { - HubProtocol.WriteMessage(CompletionMessage.WithResult("xyz", dateTime), writer); + HubProtocol.WriteMessage(CompletionMessage.WithResult("xyz", originalDateTime), writer); var bytes = new ReadOnlySequence(writer.ToArray()); HubProtocol.TryParseMessage(ref bytes, new TestBinder(typeof(DateTime)), out var hubMessage); @@ -44,7 +49,10 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol // The messagepack Timestamp format specifies that time is stored as seconds since 1970-01-01 UTC // so the library has no choice but to store the time as UTC // https://github.com/msgpack/msgpack/blob/master/spec.md#timestamp-extension-type - Assert.Equal(dateTime.ToUniversalTime(), resultDateTime); + // So If the original DateTiem was a "Local" one, we create a new DateTime equivalent to the original one but converted to Utc + var expectedUtcDateTime = (originalDateTime.Kind == DateTimeKind.Local) ? originalDateTime.ToUniversalTime() : originalDateTime; + + Assert.Equal(expectedUtcDateTime, resultDateTime); } finally { diff --git a/src/SignalR/common/SignalR.Common/test/Internal/Protocol/NewtonsoftJsonHubProtocolTests.cs b/src/SignalR/common/SignalR.Common/test/Internal/Protocol/NewtonsoftJsonHubProtocolTests.cs index 255cdead83..de10b1dbf6 100644 --- a/src/SignalR/common/SignalR.Common/test/Internal/Protocol/NewtonsoftJsonHubProtocolTests.cs +++ b/src/SignalR/common/SignalR.Common/test/Internal/Protocol/NewtonsoftJsonHubProtocolTests.cs @@ -40,6 +40,7 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol [InlineData("", "Unexpected end when reading JSON.")] [InlineData("42", "Unexpected JSON Token Type 'Integer'. Expected a JSON Object.")] [InlineData("{\"type\":\"foo\"}", "Expected 'type' to be of type Integer.")] + [InlineData("{\"type\":3,\"invocationId\":\"42\",\"result\":true", "Unexpected end when reading JSON.")] public void CustomInvalidMessages(string input, string expectedMessage) { input = Frame(input); @@ -93,35 +94,13 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol public static IDictionary CustomProtocolTestData => new[] { new JsonProtocolTestData("InvocationMessage_HasFloatArgument", new InvocationMessage(null, "Target", new object[] { 1, "Foo", 2.0f }), true, true, "{\"type\":1,\"target\":\"Target\",\"arguments\":[1,\"Foo\",2.0]}"), - new JsonProtocolTestData("InvocationMessage_StringIsoDateArgument", new InvocationMessage("Method", new object[] { "2016-05-10T13:51:20+12:34" }), false, true, "{\"type\":1,\"target\":\"Method\",\"arguments\":[\"2016-05-10T13:51:20+12:34\"]}"), - new JsonProtocolTestData("InvocationMessage_HasCustomArgumentWithNoCamelCase", new InvocationMessage(null, "Target", new object[] { new CustomObject() }), false, true, "{\"type\":1,\"target\":\"Target\",\"arguments\":[{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"ByteArrProp\":\"AQID\"}]}"), - new JsonProtocolTestData("InvocationMessage_HasCustomArgumentWithNullValueIgnore", new InvocationMessage(null, "Target", new object[] { new CustomObject() }), true, true, "{\"type\":1,\"target\":\"Target\",\"arguments\":[{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"byteArrProp\":\"AQID\"}]}"), - new JsonProtocolTestData("InvocationMessage_HasCustomArgumentWithNullValueIgnoreAndNoCamelCase", new InvocationMessage(null, "Target", new object[] { new CustomObject() }), false, false, "{\"type\":1,\"target\":\"Target\",\"arguments\":[{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"NullProp\":null,\"ByteArrProp\":\"AQID\"}]}"), - new JsonProtocolTestData("InvocationMessage_HasCustomArgumentWithNullValueInclude", new InvocationMessage(null, "Target", new object[] { new CustomObject() }), true, false, "{\"type\":1,\"target\":\"Target\",\"arguments\":[{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}]}"), new JsonProtocolTestData("InvocationMessage_HasHeaders", AddHeaders(TestHeaders, new InvocationMessage("123", "Target", new object[] { 1, "Foo", 2.0f })), true, true, "{\"type\":1," + SerializedHeaders + ",\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[1,\"Foo\",2.0]}"), new JsonProtocolTestData("StreamItemMessage_HasFloatItem", new StreamItemMessage("123", 2.0f), true, true, "{\"type\":2,\"invocationId\":\"123\",\"item\":2.0}"), - new JsonProtocolTestData("StreamItemMessage_HasCustomItemWithNoCamelCase", new StreamItemMessage("123", new CustomObject()), false, true, "{\"type\":2,\"invocationId\":\"123\",\"item\":{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"ByteArrProp\":\"AQID\"}}"), - new JsonProtocolTestData("StreamItemMessage_HasCustomItemWithNullValueIgnore", new StreamItemMessage("123", new CustomObject()), true, true, "{\"type\":2,\"invocationId\":\"123\",\"item\":{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"byteArrProp\":\"AQID\"}}"), - new JsonProtocolTestData("StreamItemMessage_HasCustomItemWithNullValueIgnoreAndNoCamelCase", new StreamItemMessage("123", new CustomObject()), false, false, "{\"type\":2,\"invocationId\":\"123\",\"item\":{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"NullProp\":null,\"ByteArrProp\":\"AQID\"}}"), - new JsonProtocolTestData("StreamItemMessage_HasCustomItemWithNullValueInclude", new StreamItemMessage("123", new CustomObject()), true, false, "{\"type\":2,\"invocationId\":\"123\",\"item\":{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}}"), - new JsonProtocolTestData("StreamItemMessage_HasHeaders", AddHeaders(TestHeaders, new StreamItemMessage("123", new CustomObject())), true, false, "{\"type\":2," + SerializedHeaders + ",\"invocationId\":\"123\",\"item\":{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}}"), - new JsonProtocolTestData("CompletionMessage_HasCustomResultWithNoCamelCase", CompletionMessage.WithResult("123", new CustomObject()), false, true, "{\"type\":3,\"invocationId\":\"123\",\"result\":{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"ByteArrProp\":\"AQID\"}}"), - new JsonProtocolTestData("CompletionMessage_HasCustomResultWithNullValueIgnore", CompletionMessage.WithResult("123", new CustomObject()), true, true, "{\"type\":3,\"invocationId\":\"123\",\"result\":{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"byteArrProp\":\"AQID\"}}"), - new JsonProtocolTestData("CompletionMessage_HasCustomResultWithNullValueIncludeAndNoCamelCase", CompletionMessage.WithResult("123", new CustomObject()), false, false, "{\"type\":3,\"invocationId\":\"123\",\"result\":{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"NullProp\":null,\"ByteArrProp\":\"AQID\"}}"), - new JsonProtocolTestData("CompletionMessage_HasCustomResultWithNullValueInclude", CompletionMessage.WithResult("123", new CustomObject()), true, false, "{\"type\":3,\"invocationId\":\"123\",\"result\":{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}}"), - new JsonProtocolTestData("CompletionMessage_HasTestHeadersAndCustomItemResult", AddHeaders(TestHeaders, CompletionMessage.WithResult("123", new CustomObject())), true, false, "{\"type\":3," + SerializedHeaders + ",\"invocationId\":\"123\",\"result\":{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}}"), - new JsonProtocolTestData("CompletionMessage_HasErrorAndCamelCase", CompletionMessage.Empty("123"), true, true, "{\"type\":3,\"invocationId\":\"123\"}"), - new JsonProtocolTestData("CompletionMessage_HasErrorAndHeadersAndCamelCase", AddHeaders(TestHeaders, CompletionMessage.Empty("123")), true, true, "{\"type\":3," + SerializedHeaders + ",\"invocationId\":\"123\"}"), new JsonProtocolTestData("CompletionMessage_HasFloatResult", CompletionMessage.WithResult("123", 2.0f), true, true, "{\"type\":3,\"invocationId\":\"123\",\"result\":2.0}"), new JsonProtocolTestData("StreamInvocationMessage_HasFloatArgument", new StreamInvocationMessage("123", "Target", new object[] { 1, "Foo", 2.0f }), true, true, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[1,\"Foo\",2.0]}"), - new JsonProtocolTestData("StreamInvocationMessage_HasCustomArgumentWithNoCamelCase", new StreamInvocationMessage("123", "Target", new object[] { new CustomObject() }), false, true, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"ByteArrProp\":\"AQID\"}]}"), - new JsonProtocolTestData("StreamInvocationMessage_HasCustomArgumentWithNullValueIgnore", new StreamInvocationMessage("123", "Target", new object[] { new CustomObject() }), true, true, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"byteArrProp\":\"AQID\"}]}"), - new JsonProtocolTestData("StreamInvocationMessage_HasCustomArgumentWithNullValueIgnoreAndNoCamelCase", new StreamInvocationMessage("123", "Target", new object[] { new CustomObject() }), false, false, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"NullProp\":null,\"ByteArrProp\":\"AQID\"}]}"), - new JsonProtocolTestData("StreamInvocationMessage_HasCustomArgumentWithNullValueInclude", new StreamInvocationMessage("123", "Target", new object[] { new CustomObject() }), true, false, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}]}"), - new JsonProtocolTestData("StreamInvocationMessage_HasHeaders", AddHeaders(TestHeaders, new StreamInvocationMessage("123", "Target", new object[] { new CustomObject() })), true, false, "{\"type\":4," + SerializedHeaders + ",\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}]}"), }.ToDictionary(t => t.Name); public static IEnumerable CustomProtocolTestDataNames => CustomProtocolTestData.Keys.Select(name => new object[] { name }); diff --git a/src/SignalR/common/SignalR.Common/test/Internal/Protocol/Utf8BufferTextWriterTests.cs b/src/SignalR/common/SignalR.Common/test/Internal/Protocol/Utf8BufferTextWriterTests.cs index b1fe8c76fb..6b7a5563d3 100644 --- a/src/SignalR/common/SignalR.Common/test/Internal/Protocol/Utf8BufferTextWriterTests.cs +++ b/src/SignalR/common/SignalR.Common/test/Internal/Protocol/Utf8BufferTextWriterTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; @@ -245,7 +245,7 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol } [Fact] - private void WriteMultiByteCharactersToSmallBuffers() + public void WriteMultiByteCharactersToSmallBuffers() { // Test string breakdown (char => UTF-8 hex values): // a => 61 diff --git a/src/SignalR/common/testassets/Tests.Utils/FunctionalTestBase.cs b/src/SignalR/common/testassets/Tests.Utils/FunctionalTestBase.cs index 797c7a846a..daa33a8021 100644 --- a/src/SignalR/common/testassets/Tests.Utils/FunctionalTestBase.cs +++ b/src/SignalR/common/testassets/Tests.Utils/FunctionalTestBase.cs @@ -1,8 +1,9 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections.Generic; +using System.Threading.Tasks; using Microsoft.Extensions.Logging.Testing; namespace Microsoft.AspNetCore.SignalR.Tests @@ -35,28 +36,10 @@ namespace Microsoft.AspNetCore.SignalR.Tests }; } - public IDisposable StartServer(out InProcessTestServer testServer, Func expectedErrorsFilter = null) where T : class + public Task> StartServer(Func expectedErrorsFilter = null) where T : class { var disposable = base.StartVerifiableLog(ResolveExpectedErrorsFilter(expectedErrorsFilter)); - testServer = new InProcessTestServer(LoggerFactory); - return new MultiDisposable(testServer, disposable); - } - - private class MultiDisposable : IDisposable - { - List _disposables; - public MultiDisposable(params IDisposable[] disposables) - { - _disposables = new List(disposables); - } - - public void Dispose() - { - foreach (var disposable in _disposables) - { - disposable.Dispose(); - } - } + return InProcessTestServer.StartServer(LoggerFactory, disposable); } } -} \ No newline at end of file +} diff --git a/src/SignalR/common/testassets/Tests.Utils/InProcessTestServer.cs b/src/SignalR/common/testassets/Tests.Utils/InProcessTestServer.cs index b4d9a8ec8d..a02e4bb1de 100644 --- a/src/SignalR/common/testassets/Tests.Utils/InProcessTestServer.cs +++ b/src/SignalR/common/testassets/Tests.Utils/InProcessTestServer.cs @@ -6,9 +6,11 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; +using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting.Server.Features; +using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; @@ -35,6 +37,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests private IWebHost _host; private IHostApplicationLifetime _lifetime; private readonly IDisposable _logToken; + private readonly IDisposable _extraDisposable; private readonly LogSinkProvider _logSinkProvider; private string _url; @@ -49,12 +52,20 @@ namespace Microsoft.AspNetCore.SignalR.Tests public override string Url => _url; - public InProcessTestServer() : this(loggerFactory: null) + public static async Task> StartServer(ILoggerFactory loggerFactory, IDisposable disposable = null) + { + var server = new InProcessTestServer(loggerFactory, disposable); + await server.StartServerInner(); + return server; + } + + private InProcessTestServer() : this(loggerFactory: null, null) { } - public InProcessTestServer(ILoggerFactory loggerFactory) + private InProcessTestServer(ILoggerFactory loggerFactory, IDisposable disposable) { + _extraDisposable = disposable; _logSinkProvider = new LogSinkProvider(); if (loggerFactory == null) @@ -71,11 +82,9 @@ namespace Microsoft.AspNetCore.SignalR.Tests _loggerFactory = new WrappingLoggerFactory(_loggerFactory); _loggerFactory.AddProvider(_logSinkProvider); _logger = _loggerFactory.CreateLogger>(); - - StartServer(); } - private void StartServer() + private async Task StartServerInner() { // We're using 127.0.0.1 instead of localhost to ensure that we use IPV4 across different OSes var url = "http://127.0.0.1:0"; @@ -90,27 +99,24 @@ namespace Microsoft.AspNetCore.SignalR.Tests .UseContentRoot(Directory.GetCurrentDirectory()) .Build(); - var t = Task.Run(() => _host.Start()); _logger.LogInformation("Starting test server..."); - _lifetime = _host.Services.GetRequiredService(); - - // This only happens once per fixture, so we can afford to wait a little bit on it. - if (!_lifetime.ApplicationStarted.WaitHandle.WaitOne(TimeSpan.FromSeconds(20))) + var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30)); + try + { + await _host.StartAsync(cts.Token); + } + catch (OperationCanceledException) { - // t probably faulted - if (t.IsFaulted) - { - throw t.Exception.InnerException; - } - var logs = _logSinkProvider.GetLogs(); throw new TimeoutException($"Timed out waiting for application to start.{Environment.NewLine}Startup Logs:{Environment.NewLine}{RenderLogs(logs)}"); } + _logger.LogInformation("Test Server started"); // Get the URL from the server _url = _host.ServerFeatures.Get().Addresses.Single(); + _lifetime = _host.Services.GetRequiredService(); _lifetime.ApplicationStopped.Register(() => { _logger.LogInformation("Test server shut down"); @@ -138,6 +144,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests public override void Dispose() { + _extraDisposable?.Dispose(); _logger.LogInformation("Shutting down test server"); _host.Dispose(); _loggerFactory.Dispose(); diff --git a/src/SignalR/common/testassets/Tests.Utils/Microsoft.AspNetCore.SignalR.Tests.Utils.csproj b/src/SignalR/common/testassets/Tests.Utils/Microsoft.AspNetCore.SignalR.Tests.Utils.csproj index 3271bd2ef5..9e69675720 100644 --- a/src/SignalR/common/testassets/Tests.Utils/Microsoft.AspNetCore.SignalR.Tests.Utils.csproj +++ b/src/SignalR/common/testassets/Tests.Utils/Microsoft.AspNetCore.SignalR.Tests.Utils.csproj @@ -22,9 +22,10 @@ - + + diff --git a/src/SignalR/common/testassets/Tests.Utils/TestClient.cs b/src/SignalR/common/testassets/Tests.Utils/TestClient.cs index ddb7dee201..fe7605fed7 100644 --- a/src/SignalR/common/testassets/Tests.Utils/TestClient.cs +++ b/src/SignalR/common/testassets/Tests.Utils/TestClient.cs @@ -120,7 +120,8 @@ namespace Microsoft.AspNetCore.SignalR.Tests messages.Add(message); return messages; default: - throw new NotSupportedException("TestClient does not support receiving invocations!"); + // Message implement ToString so this should be helpful. + throw new NotSupportedException($"TestClient recieved an unexpected message: {message}."); } } } @@ -153,7 +154,8 @@ namespace Microsoft.AspNetCore.SignalR.Tests // Pings are ignored break; default: - throw new NotSupportedException("TestClient does not support receiving invocations!"); + // Message implement ToString so this should be helpful. + throw new NotSupportedException($"TestClient recieved an unexpected message: {message}."); } } } diff --git a/src/SignalR/common/testassets/Tests.Utils/VerifiableLoggedTest.cs b/src/SignalR/common/testassets/Tests.Utils/VerifiableLoggedTest.cs index 691338e3f9..4901e85969 100644 --- a/src/SignalR/common/testassets/Tests.Utils/VerifiableLoggedTest.cs +++ b/src/SignalR/common/testassets/Tests.Utils/VerifiableLoggedTest.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Logging.Testing; diff --git a/src/SignalR/docs/specs/TransportProtocols.md b/src/SignalR/docs/specs/TransportProtocols.md index f11b80bc74..a4c10f4ead 100644 --- a/src/SignalR/docs/specs/TransportProtocols.md +++ b/src/SignalR/docs/specs/TransportProtocols.md @@ -12,19 +12,65 @@ A transport is required to have the following attributes: The only transport which fully implements the duplex requirement is WebSockets, the others are "half-transports" which implement one end of the duplex connection. They are used in combination to achieve a duplex connection. -Throughout this document, the term `[endpoint-base]` is used to refer to the route assigned to a particular end point. The term `[connection-id]` is used to refer to the connection ID provided by the `POST [endpoint-base]/negotiate` request. +Throughout this document, the term `[endpoint-base]` is used to refer to the route assigned to a particular end point. The terms `connection-id` and `connectionToken` are used to refer to the connection ID and connection token provided by the `POST [endpoint-base]/negotiate` request. **NOTE on errors:** In all error cases, by default, the detailed exception message is **never** provided; a short description string may be provided. However, an application developer may elect to allow detailed exception messages to be emitted, which should only be used in the `Development` environment. Unexpected errors are communicated by HTTP `500 Server Error` status codes or WebSockets non-`1000 Normal Closure` close frames; in these cases the connection should be considered to be terminated. ## `POST [endpoint-base]/negotiate` request -The `POST [endpoint-base]/negotiate` request is used to establish a connection between the client and the server. The content type of the response is `application/json`. The response to the `POST [endpoint-base]/negotiate` request contains one of three types of responses: +The `POST [endpoint-base]/negotiate` request is used to establish a connection between the client and the server. -1. A response that contains the `connectionId` which will be used to identify the connection on the server and the list of the transports supported by the server. +In the POST request the client sends a query string parameter with the key "negotiateVersion" and the value as the negotiate protocol version it would like to use. If the query string is omitted, the server treats the version as zero. The server will include a "negotiateVersion" property in the json response that says which version it will be using. The version is chosen as described below: +* If the servers minimum supported protocol version is greater than the version requested by the client it will send an error response and close the connection +* If the server supports the request version it will respond with the requested version +* If the requested version is greater than the servers largest supported version the server will respond with its largest supported version +The client may close the connection if the "negotiateVersion" in the response is not acceptable. +The content type of the response is `application/json` and is a JSON payload containing properties to assist the client in establishing a persistent connection. Extra JSON properties that the client does not know about should be ignored. This allows for future additions without breaking older clients. + +### Version 1 + +When the server and client agree on version 1 the server response will include a "connectionToken" property in addition to the "connectionId" property. The value of the "connectionToken" property will be used in the "id" query string for the HTTP requests described below, this value should be kept secret. + +A successful negotiate response will look similar to the following payload: + ```json + { + "connectionToken":"05265228-1e2c-46c5-82a1-6a5bcc3f0143", + "connectionId":"807809a5-31bf-470d-9e23-afaee35d8a0d", + "negotiateVersion":1, + "availableTransports":[ + { + "transport": "WebSockets", + "transferFormats": [ "Text", "Binary" ] + }, + { + "transport": "ServerSentEvents", + "transferFormats": [ "Text" ] + }, + { + "transport": "LongPolling", + "transferFormats": [ "Text", "Binary" ] + } + ] + } + ``` + + The payload returned from this endpoint provides the following data: + + * The `connectionToken` which is **required** by the Long Polling and Server-Sent Events transports (in order to correlate sends and receives). + * The `connectionId` which is the id by which other clients can refer to it. + * The `negotiateVersion` which is the negotiation protocol version being used between the server and client. + * The `availableTransports` list which describes the transports the server supports. For each transport, the name of the transport (`transport`) is listed, as is a list of "transfer formats" supported by the transport (`transferFormats`) + +### Version 0 + +When the server and client agree on version 0 the server response will include a "connectionId" property that is used in the "id" query string for the HTTP requests described below. + +A successful negotiate response will look similar to the following payload: ```json { "connectionId":"807809a5-31bf-470d-9e23-afaee35d8a0d", + "negotiateVersion":0, "availableTransports":[ { "transport": "WebSockets", @@ -45,10 +91,14 @@ The `POST [endpoint-base]/negotiate` request is used to establish a connection b The payload returned from this endpoint provides the following data: * The `connectionId` which is **required** by the Long Polling and Server-Sent Events transports (in order to correlate sends and receives). + * The `negotiateVersion` which is the negotiation protocol version being used between the server and client. * The `availableTransports` list which describes the transports the server supports. For each transport, the name of the transport (`transport`) is listed, as is a list of "transfer formats" supported by the transport (`transferFormats`) +### All versions -2. A redirect response which tells the client which URL and optionally access token to use as a result. +There are two other possible negotiation responses: + +1. A redirect response which tells the client which URL and optionally access token to use as a result. ```json { @@ -63,7 +113,7 @@ The `POST [endpoint-base]/negotiate` request is used to establish a connection b * The `accessToken` which is an optional bearer token for accessing the specified url. -3. A response that contains an `error` which should stop the connection attempt. +1. A response that contains an `error` which should stop the connection attempt. ```json { @@ -136,10 +186,14 @@ Long Polling requires that the client poll the server for new messages. Unlike t A Poll is established by sending an HTTP GET request to `[endpoint-base]` with the following query string parameters +#### Version 1 +* `id` (Required) - The Connection Token of the destination connection. + +#### Version 0 * `id` (Required) - The Connection ID of the destination connection. When data is available, the server responds with a body in one of the two formats below (depending upon the value of the `Accept` header). The response may be chunked, as per the chunked encoding part of the HTTP spec. If the `id` parameter is missing, a `400 Bad Request` response is returned. If there is no connection with the ID specified in `id`, a `404 Not Found` response is returned. -When the client has finished with the connection, it can issue a `DELETE` request to `[endpoint-base]` (with the `id` in the querystring) to gracefully terminate the connection. The server will complete the latest poll with `204` to indicate that it has shut down. +When the client has finished with the connection, it can issue a `DELETE` request to `[endpoint-base]` (with the `id` in the query string) to gracefully terminate the connection. The server will complete the latest poll with `204` to indicate that it has shut down. diff --git a/src/SignalR/perf/Microbenchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks.csproj b/src/SignalR/perf/Microbenchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks.csproj index 84f5308ae5..8bcc41b572 100644 --- a/src/SignalR/perf/Microbenchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks.csproj +++ b/src/SignalR/perf/Microbenchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks.csproj @@ -24,7 +24,6 @@ - @@ -34,10 +33,13 @@ - + + + + diff --git a/src/SignalR/perf/Microbenchmarks/TypedClientBuilderBenchmark.cs b/src/SignalR/perf/Microbenchmarks/TypedClientBuilderBenchmark.cs new file mode 100644 index 0000000000..8a386fd228 --- /dev/null +++ b/src/SignalR/perf/Microbenchmarks/TypedClientBuilderBenchmark.cs @@ -0,0 +1,31 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Threading; +using System.Threading.Tasks; +using BenchmarkDotNet.Attributes; +using Microsoft.AspNetCore.SignalR.Internal; + +namespace Microsoft.AspNetCore.SignalR.Microbenchmarks +{ + public class TypedClientBuilderBenchmark + { + private static readonly IClientProxy Dummy = new DummyProxy(); + + [Benchmark] + public ITestClient Build() + { + return TypedClientBuilder.Build(Dummy); + } + + public interface ITestClient { } + + private class DummyProxy : IClientProxy + { + public Task SendCoreAsync(string method, object[] args, CancellationToken cancellationToken = default) + { + return Task.CompletedTask; + } + } + } +} diff --git a/src/SignalR/perf/benchmarkapps/Crankier/Commands/Defaults.cs b/src/SignalR/perf/benchmarkapps/Crankier/Commands/Defaults.cs index 732ccb6af3..84027fff16 100644 --- a/src/SignalR/perf/benchmarkapps/Crankier/Commands/Defaults.cs +++ b/src/SignalR/perf/benchmarkapps/Crankier/Commands/Defaults.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNetCore.Http.Connections; +using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.SignalR.Crankier.Commands { @@ -11,5 +12,6 @@ namespace Microsoft.AspNetCore.SignalR.Crankier.Commands public static readonly int NumberOfConnections = 10_000; public static readonly int SendDurationInSeconds = 300; public static readonly HttpTransportType TransportType = HttpTransportType.WebSockets; + public static readonly LogLevel LogLevel = LogLevel.None; } } diff --git a/src/SignalR/perf/benchmarkapps/Crankier/Commands/ServerCommand.cs b/src/SignalR/perf/benchmarkapps/Crankier/Commands/ServerCommand.cs new file mode 100644 index 0000000000..738d3232fd --- /dev/null +++ b/src/SignalR/perf/benchmarkapps/Crankier/Commands/ServerCommand.cs @@ -0,0 +1,75 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http.Connections; +using Microsoft.Extensions.CommandLineUtils; +using static Microsoft.AspNetCore.SignalR.Crankier.Commands.CommandLineUtilities; +using System.Diagnostics; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using Microsoft.AspNetCore.SignalR.Crankier.Server; +using System.Collections.Generic; + +namespace Microsoft.AspNetCore.SignalR.Crankier.Commands +{ + internal class ServerCommand + { + public static void Register(CommandLineApplication app) + { + app.Command("server", cmd => + { + var logLevelOption = cmd.Option("--log ", "The LogLevel to use.", CommandOptionType.SingleValue); + var azureSignalRConnectionString = cmd.Option("--azure-signalr-connectionstring ", "Azure SignalR Connection string to use", CommandOptionType.SingleValue); + + cmd.OnExecute(() => + { + LogLevel logLevel = Defaults.LogLevel; + + if (logLevelOption.HasValue() && !Enum.TryParse(logLevelOption.Value(), out logLevel)) + { + return InvalidArg(logLevelOption); + } + + if (azureSignalRConnectionString.HasValue() && string.IsNullOrWhiteSpace(azureSignalRConnectionString.Value())) + { + return InvalidArg(azureSignalRConnectionString); + } + + return Execute(logLevel, azureSignalRConnectionString.Value()); + }); + }); + } + + private static int Execute(LogLevel logLevel, string azureSignalRConnectionString) + { + Console.WriteLine($"Process ID: {Process.GetCurrentProcess().Id}"); + + var configBuilder = new ConfigurationBuilder() + .AddEnvironmentVariables(prefix: "ASPNETCORE_"); + + if (azureSignalRConnectionString != null) + { + configBuilder.AddInMemoryCollection(new [] { new KeyValuePair("Azure:SignalR:ConnectionString", azureSignalRConnectionString) }); + Console.WriteLine("Using Azure SignalR"); + } + + var config = configBuilder.Build(); + + var host = new WebHostBuilder() + .UseConfiguration(config) + .ConfigureLogging(loggerFactory => + { + loggerFactory.AddConsole().SetMinimumLevel(logLevel); + }) + .UseKestrel() + .UseStartup(); + + host.Build().Run(); + + return 0; + } + } +} diff --git a/src/SignalR/perf/benchmarkapps/Crankier/Crankier.csproj b/src/SignalR/perf/benchmarkapps/Crankier/Crankier.csproj index 1c1b18a058..001d8138de 100644 --- a/src/SignalR/perf/benchmarkapps/Crankier/Crankier.csproj +++ b/src/SignalR/perf/benchmarkapps/Crankier/Crankier.csproj @@ -4,12 +4,20 @@ Exe $(DefaultNetCoreTargetFramework) Microsoft.AspNetCore.SignalR.CranksRevenge + true - + + + + + + + + diff --git a/src/SignalR/perf/benchmarkapps/Crankier/Program.cs b/src/SignalR/perf/benchmarkapps/Crankier/Program.cs index a3443ecd94..93f53ea328 100644 --- a/src/SignalR/perf/benchmarkapps/Crankier/Program.cs +++ b/src/SignalR/perf/benchmarkapps/Crankier/Program.cs @@ -30,6 +30,7 @@ namespace Microsoft.AspNetCore.SignalR.Crankier LocalCommand.Register(app); AgentCommand.Register(app); WorkerCommand.Register(app); + ServerCommand.Register(app); app.Command("help", cmd => { diff --git a/src/SignalR/perf/benchmarkapps/Crankier/Readme.md b/src/SignalR/perf/benchmarkapps/Crankier/Readme.md index 20f56cc720..dc783bcc9e 100644 --- a/src/SignalR/perf/benchmarkapps/Crankier/Readme.md +++ b/src/SignalR/perf/benchmarkapps/Crankier/Readme.md @@ -4,6 +4,26 @@ Load testing for ASP.NET Core SignalR ## Commands +### server + +The `server` command runs a web host exposing a single SignalR `Hub` endpoint on `/echo`. After the first client connection, the server will periodically write concurrent connection information to the console. + +``` +> dotnet run -- help server + +Usage: server [options] + +Options: + --log The LogLevel to use. + --azure-signalr-connectionstring Azure SignalR Connection string to use + +``` + +Notes: + +* `LOG_LEVEL` switches internal logging only, not concurrent connection information, and defaults to `LogLevel.None`. Use this option to control Kestrel / SignalR Warnings & Errors being logged to console. + + ### local The `local` command launches a set of local worker clients to establish connections to your SignalR server. @@ -31,13 +51,25 @@ Notes: #### Examples -Attempt to make 10,000 connections to the `echo` hub using WebSockets and 10 workers: +Run the server: + +``` +dotnet run -- server +``` + +Run the server using Azure SignalR: + +``` +dotnet run -- server --azure-signalr-connectionstring Endpoint=https://your-url.service.signalr.net;AccessKey=yourAccessKey;Version=1.0; +``` + +Attempt to make 10,000 connections to the server using WebSockets and 10 workers: ``` dotnet run -- local --target-url https://localhost:5001/echo --workers 10 ``` -Attempt to make 5,000 connections to the `echo` hub using Long Polling +Attempt to make 5,000 connections to the server using Long Polling ``` dotnet run -- local --target-url https://localhost:5001/echo --connections 5000 --transport LongPolling diff --git a/src/SignalR/perf/benchmarkapps/Crankier/Server/ConnectionCounter.cs b/src/SignalR/perf/benchmarkapps/Crankier/Server/ConnectionCounter.cs new file mode 100644 index 0000000000..1ab6a25abc --- /dev/null +++ b/src/SignalR/perf/benchmarkapps/Crankier/Server/ConnectionCounter.cs @@ -0,0 +1,60 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.AspNetCore.SignalR.Crankier.Server +{ + public class ConnectionCounter + { + private int _totalConnectedCount; + private int _peakConnectedCount; + private int _totalDisconnectedCount; + private int _receivedCount; + + private readonly object _lock = new object(); + + public ConnectionSummary Summary + { + get + { + lock (_lock) + { + return new ConnectionSummary + { + CurrentConnections = _totalConnectedCount - _totalDisconnectedCount, + PeakConnections = _peakConnectedCount, + TotalConnected = _totalConnectedCount, + TotalDisconnected = _totalDisconnectedCount, + ReceivedCount = _receivedCount + }; + } + } + } + + public void Receive(string payload) + { + lock (_lock) + { + _receivedCount += payload.Length; + } + } + + public void Connected() + { + lock (_lock) + { + _totalConnectedCount++; + _peakConnectedCount = Math.Max(_totalConnectedCount - _totalDisconnectedCount, _peakConnectedCount); + } + } + + public void Disconnected() + { + lock (_lock) + { + _totalDisconnectedCount++; + } + } + } +} \ No newline at end of file diff --git a/src/SignalR/perf/benchmarkapps/Crankier/Server/ConnectionCounterHostedService.cs b/src/SignalR/perf/benchmarkapps/Crankier/Server/ConnectionCounterHostedService.cs new file mode 100644 index 0000000000..44b8bb26f2 --- /dev/null +++ b/src/SignalR/perf/benchmarkapps/Crankier/Server/ConnectionCounterHostedService.cs @@ -0,0 +1,79 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Hosting; + +namespace Microsoft.AspNetCore.SignalR.Crankier.Server +{ + public class ConnectionCounterHostedService : IHostedService, IDisposable + { + private Stopwatch _timeSinceFirstConnection; + private readonly ConnectionCounter _counter; + private ConnectionSummary _lastSummary; + private Timer _timer; + private int _executingDoWork; + + public ConnectionCounterHostedService(ConnectionCounter counter) + { + _counter = counter; + _timeSinceFirstConnection = new Stopwatch(); + } + + public Task StartAsync(CancellationToken cancellationToken) + { + _timer = new Timer(DoWork, null, TimeSpan.Zero, TimeSpan.FromSeconds(1)); + + return Task.CompletedTask; + } + + private void DoWork(object state) + { + if (Interlocked.Exchange(ref _executingDoWork, 1) == 0) + { + var summary = _counter.Summary; + + if (summary.PeakConnections > 0) + { + if (_timeSinceFirstConnection.ElapsedTicks == 0) + { + _timeSinceFirstConnection.Start(); + } + + var elapsed = _timeSinceFirstConnection.Elapsed; + + if (_lastSummary != null) + { + Console.WriteLine(@"[{0:hh\:mm\:ss}] Current: {1}, peak: {2}, connected: {3}, disconnected: {4}, rate: {5}/s", + elapsed, + summary.CurrentConnections, + summary.PeakConnections, + summary.TotalConnected - _lastSummary.TotalConnected, + summary.TotalDisconnected - _lastSummary.TotalDisconnected, + summary.CurrentConnections - _lastSummary.CurrentConnections + ); + } + + _lastSummary = summary; + } + + Interlocked.Exchange(ref _executingDoWork, 0); + } + } + + public Task StopAsync(CancellationToken cancellationToken) + { + _timer?.Change(Timeout.Infinite, 0); + + return Task.CompletedTask; + } + + public void Dispose() + { + _timer?.Dispose(); + } + } +} \ No newline at end of file diff --git a/src/SignalR/perf/benchmarkapps/Crankier/Server/ConnectionSummary.cs b/src/SignalR/perf/benchmarkapps/Crankier/Server/ConnectionSummary.cs new file mode 100644 index 0000000000..83f38aaf62 --- /dev/null +++ b/src/SignalR/perf/benchmarkapps/Crankier/Server/ConnectionSummary.cs @@ -0,0 +1,18 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.AspNetCore.SignalR.Crankier.Server +{ + public class ConnectionSummary + { + public int TotalConnected { get; set; } + + public int TotalDisconnected { get; set; } + + public int PeakConnections { get; set; } + + public int CurrentConnections { get; set; } + + public int ReceivedCount { get; set; } + } +} \ No newline at end of file diff --git a/src/SignalR/perf/benchmarkapps/Crankier/Server/EchoHub.cs b/src/SignalR/perf/benchmarkapps/Crankier/Server/EchoHub.cs new file mode 100644 index 0000000000..0b24b46e9b --- /dev/null +++ b/src/SignalR/perf/benchmarkapps/Crankier/Server/EchoHub.cs @@ -0,0 +1,72 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.SignalR; + +namespace Microsoft.AspNetCore.SignalR.Crankier.Server +{ + public class EchoHub : Hub + { + private ConnectionCounter _counter; + + public EchoHub(ConnectionCounter counter) + { + _counter = counter; + } + + public async Task Broadcast(int duration) + { + var sent = 0; + try + { + var t = new CancellationTokenSource(); + t.CancelAfter(TimeSpan.FromSeconds(duration)); + while (!t.IsCancellationRequested && !Context.ConnectionAborted.IsCancellationRequested) + { + await Clients.All.SendAsync("send", DateTime.UtcNow); + sent++; + } + } + catch (Exception e) + { + Console.WriteLine(e); + } + Console.WriteLine("Broadcast exited: Sent {0} messages", sent); + } + + public override Task OnConnectedAsync() + { + _counter?.Connected(); + return Task.CompletedTask; + } + + public override Task OnDisconnectedAsync(Exception exception) + { + _counter?.Disconnected(); + return Task.CompletedTask; + } + + public DateTime Echo(DateTime time) + { + return time; + } + + public Task EchoAll(DateTime time) + { + return Clients.All.SendAsync("send", time); + } + + public void SendPayload(string payload) + { + _counter?.Receive(payload); + } + + public DateTime GetCurrentTime() + { + return DateTime.UtcNow; + } + } +} diff --git a/src/SignalR/perf/benchmarkapps/Crankier/Server/Startup.cs b/src/SignalR/perf/benchmarkapps/Crankier/Server/Startup.cs new file mode 100644 index 0000000000..6b3154416c --- /dev/null +++ b/src/SignalR/perf/benchmarkapps/Crankier/Server/Startup.cs @@ -0,0 +1,57 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNetCore.SignalR.Crankier.Server +{ + public class Startup + { + private readonly IConfiguration _config; + private readonly string _azureSignalrConnectionString; + public Startup(IConfiguration configuration) + { + _config = configuration; + _azureSignalrConnectionString = configuration.GetSection("Azure:SignalR").GetValue("ConnectionString", null); + } + + public void ConfigureServices(IServiceCollection services) + { + var signalrBuilder = services.AddSignalR(); + + if (_azureSignalrConnectionString != null) + { + signalrBuilder.AddAzureSignalR(); + } + + signalrBuilder.AddMessagePackProtocol(); + + services.AddSingleton(); + + services.AddHostedService(); + } + + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + app.UseRouting(); + + if (_azureSignalrConnectionString != null) + { + app.UseAzureSignalR(routes => { + routes.MapHub("/echo"); + }); + } + else + { + app.UseEndpoints(endpoints => + { + endpoints.MapHub("/echo"); + }); + } + } + } +} diff --git a/src/SignalR/publish-apps.ps1 b/src/SignalR/publish-apps.ps1 index fcb8c99cae..8c7b2de4a4 100644 --- a/src/SignalR/publish-apps.ps1 +++ b/src/SignalR/publish-apps.ps1 @@ -1,4 +1,4 @@ -param($RootDirectory = (Get-Location), $Framework = "netcoreapp3.1", $Runtime = "win-x64", $CommitHash, $BranchName, $BuildNumber) +param($RootDirectory = (Get-Location), $Framework = "netcoreapp5.0", $Runtime = "win-x64", $CommitHash, $BranchName, $BuildNumber) # De-Powershell the path $RootDirectory = (Convert-Path $RootDirectory) diff --git a/src/SignalR/samples/ClientSample/ClientSample.csproj b/src/SignalR/samples/ClientSample/ClientSample.csproj index 654bf67bef..e5f4fb420f 100644 --- a/src/SignalR/samples/ClientSample/ClientSample.csproj +++ b/src/SignalR/samples/ClientSample/ClientSample.csproj @@ -9,13 +9,13 @@ + - diff --git a/src/SignalR/server/Core/ref/Microsoft.AspNetCore.SignalR.Core.netcoreapp.cs b/src/SignalR/server/Core/ref/Microsoft.AspNetCore.SignalR.Core.netcoreapp.cs index 5309dae32c..f23da84b52 100644 --- a/src/SignalR/server/Core/ref/Microsoft.AspNetCore.SignalR.Core.netcoreapp.cs +++ b/src/SignalR/server/Core/ref/Microsoft.AspNetCore.SignalR.Core.netcoreapp.cs @@ -84,6 +84,7 @@ namespace Microsoft.AspNetCore.SignalR } public static partial class HubClientsExtensions { + public static T AllExcept(this Microsoft.AspNetCore.SignalR.IHubClients hubClients, System.Collections.Generic.IEnumerable excludedConnectionIds) { throw null; } public static T AllExcept(this Microsoft.AspNetCore.SignalR.IHubClients hubClients, string excludedConnectionId1) { throw null; } public static T AllExcept(this Microsoft.AspNetCore.SignalR.IHubClients hubClients, string excludedConnectionId1, string excludedConnectionId2) { throw null; } public static T AllExcept(this Microsoft.AspNetCore.SignalR.IHubClients hubClients, string excludedConnectionId1, string excludedConnectionId2, string excludedConnectionId3) { throw null; } @@ -92,6 +93,7 @@ namespace Microsoft.AspNetCore.SignalR public static T AllExcept(this Microsoft.AspNetCore.SignalR.IHubClients hubClients, string excludedConnectionId1, string excludedConnectionId2, string excludedConnectionId3, string excludedConnectionId4, string excludedConnectionId5, string excludedConnectionId6) { throw null; } public static T AllExcept(this Microsoft.AspNetCore.SignalR.IHubClients hubClients, string excludedConnectionId1, string excludedConnectionId2, string excludedConnectionId3, string excludedConnectionId4, string excludedConnectionId5, string excludedConnectionId6, string excludedConnectionId7) { throw null; } public static T AllExcept(this Microsoft.AspNetCore.SignalR.IHubClients hubClients, string excludedConnectionId1, string excludedConnectionId2, string excludedConnectionId3, string excludedConnectionId4, string excludedConnectionId5, string excludedConnectionId6, string excludedConnectionId7, string excludedConnectionId8) { throw null; } + public static T Clients(this Microsoft.AspNetCore.SignalR.IHubClients hubClients, System.Collections.Generic.IEnumerable connectionIds) { throw null; } public static T Clients(this Microsoft.AspNetCore.SignalR.IHubClients hubClients, string connection1) { throw null; } public static T Clients(this Microsoft.AspNetCore.SignalR.IHubClients hubClients, string connection1, string connection2) { throw null; } public static T Clients(this Microsoft.AspNetCore.SignalR.IHubClients hubClients, string connection1, string connection2, string connection3) { throw null; } @@ -100,6 +102,7 @@ namespace Microsoft.AspNetCore.SignalR public static T Clients(this Microsoft.AspNetCore.SignalR.IHubClients hubClients, string connection1, string connection2, string connection3, string connection4, string connection5, string connection6) { throw null; } public static T Clients(this Microsoft.AspNetCore.SignalR.IHubClients hubClients, string connection1, string connection2, string connection3, string connection4, string connection5, string connection6, string connection7) { throw null; } public static T Clients(this Microsoft.AspNetCore.SignalR.IHubClients hubClients, string connection1, string connection2, string connection3, string connection4, string connection5, string connection6, string connection7, string connection8) { throw null; } + public static T GroupExcept(this Microsoft.AspNetCore.SignalR.IHubClients hubClients, string groupName, System.Collections.Generic.IEnumerable excludedConnectionIds) { throw null; } public static T GroupExcept(this Microsoft.AspNetCore.SignalR.IHubClients hubClients, string groupName, string excludedConnectionId1) { throw null; } public static T GroupExcept(this Microsoft.AspNetCore.SignalR.IHubClients hubClients, string groupName, string excludedConnectionId1, string excludedConnectionId2) { throw null; } public static T GroupExcept(this Microsoft.AspNetCore.SignalR.IHubClients hubClients, string groupName, string excludedConnectionId1, string excludedConnectionId2, string excludedConnectionId3) { throw null; } @@ -108,6 +111,7 @@ namespace Microsoft.AspNetCore.SignalR public static T GroupExcept(this Microsoft.AspNetCore.SignalR.IHubClients hubClients, string groupName, string excludedConnectionId1, string excludedConnectionId2, string excludedConnectionId3, string excludedConnectionId4, string excludedConnectionId5, string excludedConnectionId6) { throw null; } public static T GroupExcept(this Microsoft.AspNetCore.SignalR.IHubClients hubClients, string groupName, string excludedConnectionId1, string excludedConnectionId2, string excludedConnectionId3, string excludedConnectionId4, string excludedConnectionId5, string excludedConnectionId6, string excludedConnectionId7) { throw null; } public static T GroupExcept(this Microsoft.AspNetCore.SignalR.IHubClients hubClients, string groupName, string excludedConnectionId1, string excludedConnectionId2, string excludedConnectionId3, string excludedConnectionId4, string excludedConnectionId5, string excludedConnectionId6, string excludedConnectionId7, string excludedConnectionId8) { throw null; } + public static T Groups(this Microsoft.AspNetCore.SignalR.IHubClients hubClients, System.Collections.Generic.IEnumerable groupNames) { throw null; } public static T Groups(this Microsoft.AspNetCore.SignalR.IHubClients hubClients, string group1) { throw null; } public static T Groups(this Microsoft.AspNetCore.SignalR.IHubClients hubClients, string group1, string group2) { throw null; } public static T Groups(this Microsoft.AspNetCore.SignalR.IHubClients hubClients, string group1, string group2, string group3) { throw null; } @@ -116,6 +120,7 @@ namespace Microsoft.AspNetCore.SignalR public static T Groups(this Microsoft.AspNetCore.SignalR.IHubClients hubClients, string group1, string group2, string group3, string group4, string group5, string group6) { throw null; } public static T Groups(this Microsoft.AspNetCore.SignalR.IHubClients hubClients, string group1, string group2, string group3, string group4, string group5, string group6, string group7) { throw null; } public static T Groups(this Microsoft.AspNetCore.SignalR.IHubClients hubClients, string group1, string group2, string group3, string group4, string group5, string group6, string group7, string group8) { throw null; } + public static T Users(this Microsoft.AspNetCore.SignalR.IHubClients hubClients, System.Collections.Generic.IEnumerable userIds) { throw null; } public static T Users(this Microsoft.AspNetCore.SignalR.IHubClients hubClients, string user1) { throw null; } public static T Users(this Microsoft.AspNetCore.SignalR.IHubClients hubClients, string user1, string user2) { throw null; } public static T Users(this Microsoft.AspNetCore.SignalR.IHubClients hubClients, string user1, string user2, string user3) { throw null; } @@ -128,13 +133,13 @@ namespace Microsoft.AspNetCore.SignalR public partial class HubConnectionContext { public HubConnectionContext(Microsoft.AspNetCore.Connections.ConnectionContext connectionContext, Microsoft.AspNetCore.SignalR.HubConnectionContextOptions contextOptions, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { } - public virtual System.Threading.CancellationToken ConnectionAborted { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public virtual System.Threading.CancellationToken ConnectionAborted { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } public virtual string ConnectionId { get { throw null; } } public virtual Microsoft.AspNetCore.Http.Features.IFeatureCollection Features { get { throw null; } } public virtual System.Collections.Generic.IDictionary Items { get { throw null; } } - public virtual Microsoft.AspNetCore.SignalR.Protocol.IHubProtocol Protocol { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public virtual Microsoft.AspNetCore.SignalR.Protocol.IHubProtocol Protocol { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public virtual System.Security.Claims.ClaimsPrincipal User { get { throw null; } } - public string UserIdentifier { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public string UserIdentifier { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public virtual void Abort() { } public virtual System.Threading.Tasks.ValueTask WriteAsync(Microsoft.AspNetCore.SignalR.Protocol.HubMessage message, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } public virtual System.Threading.Tasks.ValueTask WriteAsync(Microsoft.AspNetCore.SignalR.SerializedHubMessage message, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } @@ -142,10 +147,10 @@ namespace Microsoft.AspNetCore.SignalR public partial class HubConnectionContextOptions { public HubConnectionContextOptions() { } - public System.TimeSpan ClientTimeoutInterval { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public System.TimeSpan KeepAliveInterval { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public long? MaximumReceiveMessageSize { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public int StreamBufferCapacity { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public System.TimeSpan ClientTimeoutInterval { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public System.TimeSpan KeepAliveInterval { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public long? MaximumReceiveMessageSize { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public int StreamBufferCapacity { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } public partial class HubConnectionHandler : Microsoft.AspNetCore.Connections.ConnectionHandler where THub : Microsoft.AspNetCore.SignalR.Hub { @@ -165,6 +170,7 @@ namespace Microsoft.AspNetCore.SignalR public readonly partial struct Enumerator : System.Collections.Generic.IEnumerator, System.Collections.IEnumerator, System.IDisposable { private readonly object _dummy; + private readonly int _dummyPrimitive; public Enumerator(Microsoft.AspNetCore.SignalR.HubConnectionStore hubConnectionList) { throw null; } public Microsoft.AspNetCore.SignalR.HubConnectionContext Current { get { throw null; } } object System.Collections.IEnumerator.Current { get { throw null; } } @@ -176,9 +182,11 @@ namespace Microsoft.AspNetCore.SignalR public partial class HubInvocationContext { public HubInvocationContext(Microsoft.AspNetCore.SignalR.HubCallerContext context, string hubMethodName, object[] hubMethodArguments) { } - public Microsoft.AspNetCore.SignalR.HubCallerContext Context { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public System.Collections.Generic.IReadOnlyList HubMethodArguments { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public string HubMethodName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public HubInvocationContext(Microsoft.AspNetCore.SignalR.HubCallerContext context, System.Type hubType, string hubMethodName, object[] hubMethodArguments) { } + public Microsoft.AspNetCore.SignalR.HubCallerContext Context { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public System.Collections.Generic.IReadOnlyList HubMethodArguments { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public string HubMethodName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public System.Type HubType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } } public abstract partial class HubLifetimeManager where THub : Microsoft.AspNetCore.SignalR.Hub { @@ -200,24 +208,24 @@ namespace Microsoft.AspNetCore.SignalR public partial class HubMetadata { public HubMetadata(System.Type hubType) { } - public System.Type HubType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public System.Type HubType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } } [System.AttributeUsageAttribute(System.AttributeTargets.Method, AllowMultiple=false, Inherited=true)] public partial class HubMethodNameAttribute : System.Attribute { public HubMethodNameAttribute(string name) { } - public string Name { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public string Name { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } } public partial class HubOptions { public HubOptions() { } - public System.TimeSpan? ClientTimeoutInterval { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public bool? EnableDetailedErrors { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public System.TimeSpan? HandshakeTimeout { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public System.TimeSpan? KeepAliveInterval { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public long? MaximumReceiveMessageSize { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public int? StreamBufferCapacity { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public System.Collections.Generic.IList SupportedProtocols { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public System.TimeSpan? ClientTimeoutInterval { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public bool? EnableDetailedErrors { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public System.TimeSpan? HandshakeTimeout { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public System.TimeSpan? KeepAliveInterval { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public long? MaximumReceiveMessageSize { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public int? StreamBufferCapacity { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public System.Collections.Generic.IList SupportedProtocols { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } public partial class HubOptionsSetup : Microsoft.Extensions.Options.IConfigureOptions { @@ -302,16 +310,17 @@ namespace Microsoft.AspNetCore.SignalR { public SerializedHubMessage(Microsoft.AspNetCore.SignalR.Protocol.HubMessage message) { } public SerializedHubMessage(System.Collections.Generic.IReadOnlyList messages) { } - public Microsoft.AspNetCore.SignalR.Protocol.HubMessage Message { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public Microsoft.AspNetCore.SignalR.Protocol.HubMessage Message { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } public System.ReadOnlyMemory GetSerializedMessage(Microsoft.AspNetCore.SignalR.Protocol.IHubProtocol protocol) { throw null; } } [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] public readonly partial struct SerializedMessage { private readonly object _dummy; + private readonly int _dummyPrimitive; public SerializedMessage(string protocolName, System.ReadOnlyMemory serialized) { throw null; } - public string ProtocolName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public System.ReadOnlyMemory Serialized { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public string ProtocolName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public System.ReadOnlyMemory Serialized { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } } public static partial class SignalRConnectionBuilderExtensions { diff --git a/src/SignalR/server/Core/src/DefaultHubLifetimeManager.cs b/src/SignalR/server/Core/src/DefaultHubLifetimeManager.cs index 02609bce1e..c00264ea2b 100644 --- a/src/SignalR/server/Core/src/DefaultHubLifetimeManager.cs +++ b/src/SignalR/server/Core/src/DefaultHubLifetimeManager.cs @@ -82,10 +82,10 @@ namespace Microsoft.AspNetCore.SignalR /// public override Task SendAllAsync(string methodName, object[] args, CancellationToken cancellationToken = default) { - return SendToAllConnections(methodName, args, include: null, cancellationToken); + return SendToAllConnections(methodName, args, include: null, state: null, cancellationToken); } - private Task SendToAllConnections(string methodName, object[] args, Func include, CancellationToken cancellationToken) + private Task SendToAllConnections(string methodName, object[] args, Func include, object state = null, CancellationToken cancellationToken = default) { List tasks = null; SerializedHubMessage message = null; @@ -93,7 +93,7 @@ namespace Microsoft.AspNetCore.SignalR // foreach over HubConnectionStore avoids allocating an enumerator foreach (var connection in _connections) { - if (include != null && !include(connection)) + if (include != null && !include(connection, state)) { continue; } @@ -127,13 +127,12 @@ namespace Microsoft.AspNetCore.SignalR // Tasks and message are passed by ref so they can be lazily created inside the method post-filtering, // while still being re-usable when sending to multiple groups - private void SendToGroupConnections(string methodName, object[] args, ConcurrentDictionary connections, Func include, - ref List tasks, ref SerializedHubMessage message, CancellationToken cancellationToken) + private void SendToGroupConnections(string methodName, object[] args, ConcurrentDictionary connections, Func include, object state, ref List tasks, ref SerializedHubMessage message, CancellationToken cancellationToken) { // foreach over ConcurrentDictionary avoids allocating an enumerator foreach (var connection in connections) { - if (include != null && !include(connection.Value)) + if (include != null && !include(connection.Value, state)) { continue; } @@ -194,7 +193,7 @@ namespace Microsoft.AspNetCore.SignalR // group might be modified inbetween checking and sending List tasks = null; SerializedHubMessage message = null; - SendToGroupConnections(methodName, args, group, null, ref tasks, ref message, cancellationToken); + SendToGroupConnections(methodName, args, group, null, null, ref tasks, ref message, cancellationToken); if (tasks != null) { @@ -222,7 +221,7 @@ namespace Microsoft.AspNetCore.SignalR var group = _groups[groupName]; if (group != null) { - SendToGroupConnections(methodName, args, group, null, ref tasks, ref message, cancellationToken); + SendToGroupConnections(methodName, args, group, null, null, ref tasks, ref message, cancellationToken); } } @@ -248,7 +247,7 @@ namespace Microsoft.AspNetCore.SignalR List tasks = null; SerializedHubMessage message = null; - SendToGroupConnections(methodName, args, group, connection => !excludedConnectionIds.Contains(connection.ConnectionId), ref tasks, ref message, cancellationToken); + SendToGroupConnections(methodName, args, group, (connection, state) => !((IReadOnlyList)state).Contains(connection.ConnectionId), excludedConnectionIds, ref tasks, ref message, cancellationToken); if (tasks != null) { @@ -272,7 +271,7 @@ namespace Microsoft.AspNetCore.SignalR /// public override Task SendUserAsync(string userId, string methodName, object[] args, CancellationToken cancellationToken = default) { - return SendToAllConnections(methodName, args, connection => string.Equals(connection.UserIdentifier, userId, StringComparison.Ordinal), cancellationToken); + return SendToAllConnections(methodName, args, (connection, state) => string.Equals(connection.UserIdentifier, (string)state, StringComparison.Ordinal), userId, cancellationToken); } /// @@ -293,19 +292,19 @@ namespace Microsoft.AspNetCore.SignalR /// public override Task SendAllExceptAsync(string methodName, object[] args, IReadOnlyList excludedConnectionIds, CancellationToken cancellationToken = default) { - return SendToAllConnections(methodName, args, connection => !excludedConnectionIds.Contains(connection.ConnectionId), cancellationToken); + return SendToAllConnections(methodName, args, (connection, state) => !((IReadOnlyList)state).Contains(connection.ConnectionId), excludedConnectionIds, cancellationToken); } /// public override Task SendConnectionsAsync(IReadOnlyList connectionIds, string methodName, object[] args, CancellationToken cancellationToken = default) { - return SendToAllConnections(methodName, args, connection => connectionIds.Contains(connection.ConnectionId), cancellationToken); + return SendToAllConnections(methodName, args, (connection, state) => ((IReadOnlyList)state).Contains(connection.ConnectionId), connectionIds, cancellationToken); } /// public override Task SendUsersAsync(IReadOnlyList userIds, string methodName, object[] args, CancellationToken cancellationToken = default) { - return SendToAllConnections(methodName, args, connection => userIds.Contains(connection.UserIdentifier), cancellationToken); + return SendToAllConnections(methodName, args, (connection, state) => ((IReadOnlyList)state).Contains(connection.UserIdentifier), userIds, cancellationToken); } } } diff --git a/src/SignalR/server/Core/src/HubClientsExtensions.cs b/src/SignalR/server/Core/src/HubClientsExtensions.cs index 8936bbc979..8f29cb89f3 100644 --- a/src/SignalR/server/Core/src/HubClientsExtensions.cs +++ b/src/SignalR/server/Core/src/HubClientsExtensions.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Collections.Generic; +using System.Linq; namespace Microsoft.AspNetCore.SignalR { @@ -126,6 +127,17 @@ namespace Microsoft.AspNetCore.SignalR return hubClients.AllExcept(new [] { excludedConnectionId1, excludedConnectionId2, excludedConnectionId3, excludedConnectionId4, excludedConnectionId5, excludedConnectionId6, excludedConnectionId7, excludedConnectionId8 }); } + /// + /// Gets a that can be used to invoke methods on all clients connected to the hub excluding the specified connections. + /// + /// The abstraction that provides access to connections. + /// The connection IDs to exclude. + /// A representing the methods that can be invoked on the clients. + public static T AllExcept(this IHubClients hubClients, IEnumerable excludedConnectionIds) + { + return hubClients.AllExcept(excludedConnectionIds.ToList()); + } + /// /// Gets a that can be used to invoke methods on the specified connections. /// @@ -242,6 +254,17 @@ namespace Microsoft.AspNetCore.SignalR return hubClients.Clients(new [] { connection1, connection2, connection3, connection4, connection5, connection6, connection7, connection8 }); } + /// + /// Gets a that can be used to invoke methods on the specified connections. + /// + /// The abstraction that provides access to connections. + /// The connection IDs. + /// A representing the methods that can be invoked on the clients. + public static T Clients(this IHubClients hubClients, IEnumerable connectionIds) + { + return hubClients.Clients(connectionIds.ToList()); + } + /// /// Gets a that can be used to invoke methods on all connections in all of the specified groups. /// @@ -358,6 +381,17 @@ namespace Microsoft.AspNetCore.SignalR return hubClients.Groups(new [] { group1, group2, group3, group4, group5, group6, group7, group8 }); } + /// + /// Gets a that can be used to invoke methods on all connections in all of the specified groups. + /// + /// The abstraction that provides access to connections. + /// The group names. + /// A representing the methods that can be invoked on the clients. + public static T Groups(this IHubClients hubClients, IEnumerable groupNames) + { + return hubClients.Groups(groupNames.ToList()); + } + /// /// Gets a that can be used to invoke methods on all connections in the specified group excluding the specified connections. /// @@ -482,6 +516,18 @@ namespace Microsoft.AspNetCore.SignalR return hubClients.GroupExcept(groupName, new [] { excludedConnectionId1, excludedConnectionId2, excludedConnectionId3, excludedConnectionId4, excludedConnectionId5, excludedConnectionId6, excludedConnectionId7, excludedConnectionId8 }); } + /// + /// Gets a that can be used to invoke methods on all connections in the specified group excluding the specified connections. + /// + /// The abstraction that provides access to connections. + /// The group name. + /// The connection IDs to exclude. + /// A representing the methods that can be invoked on the clients. + public static T GroupExcept(this IHubClients hubClients, string groupName, IEnumerable excludedConnectionIds) + { + return hubClients.GroupExcept(groupName, excludedConnectionIds.ToList()); + } + /// /// Gets a that can be used to invoke methods on all connections associated with all of the specified users. /// @@ -597,5 +643,16 @@ namespace Microsoft.AspNetCore.SignalR { return hubClients.Users(new [] { user1, user2, user3, user4, user5, user6, user7, user8 }); } + + /// + /// Gets a that can be used to invoke methods on all connections associated with all of the specified users. + /// + /// The abstraction that provides access to connections. + /// The user IDs. + /// A representing the methods that can be invoked on the clients. + public static T Users(this IHubClients hubClients, IEnumerable userIds) + { + return hubClients.Users(userIds.ToList()); + } } } diff --git a/src/SignalR/server/Core/src/HubInvocationContext.cs b/src/SignalR/server/Core/src/HubInvocationContext.cs index a62706d6a8..967db6ebb5 100644 --- a/src/SignalR/server/Core/src/HubInvocationContext.cs +++ b/src/SignalR/server/Core/src/HubInvocationContext.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.Collections.Generic; using Microsoft.AspNetCore.Authorization; @@ -11,6 +12,18 @@ namespace Microsoft.AspNetCore.SignalR /// public class HubInvocationContext { + /// + /// Instantiates a new instance of the class. + /// + /// Context for the active Hub connection and caller. + /// The type of the Hub. + /// The name of the Hub method being invoked. + /// The arguments provided by the client. + public HubInvocationContext(HubCallerContext context, Type hubType, string hubMethodName, object[] hubMethodArguments): this(context, hubMethodName, hubMethodArguments) + { + HubType = hubType; + } + /// /// Instantiates a new instance of the class. /// @@ -29,6 +42,11 @@ namespace Microsoft.AspNetCore.SignalR /// public HubCallerContext Context { get; } + /// + /// Gets the Hub type. + /// + public Type HubType { get; } + /// /// Gets the name of the Hub method being invoked. /// diff --git a/src/SignalR/server/Core/src/Internal/DefaultHubDispatcher.cs b/src/SignalR/server/Core/src/Internal/DefaultHubDispatcher.cs index 1f5faf714d..e625acfff8 100644 --- a/src/SignalR/server/Core/src/Internal/DefaultHubDispatcher.cs +++ b/src/SignalR/server/Core/src/Internal/DefaultHubDispatcher.cs @@ -489,12 +489,12 @@ namespace Microsoft.AspNetCore.SignalR.Internal private Task IsHubMethodAuthorized(IServiceProvider provider, HubConnectionContext hubConnectionContext, IList policies, string hubMethodName, object[] hubMethodArguments) { // If there are no policies we don't need to run auth - if (!policies.Any()) + if (policies.Count == 0) { return TaskCache.True; } - return IsHubMethodAuthorizedSlow(provider, hubConnectionContext.User, policies, new HubInvocationContext(hubConnectionContext.HubCallerContext, hubMethodName, hubMethodArguments)); + return IsHubMethodAuthorizedSlow(provider, hubConnectionContext.User, policies, new HubInvocationContext(hubConnectionContext.HubCallerContext, typeof(THub), hubMethodName, hubMethodArguments)); } private static async Task IsHubMethodAuthorizedSlow(IServiceProvider provider, ClaimsPrincipal principal, IList policies, HubInvocationContext resource) @@ -547,6 +547,11 @@ namespace Microsoft.AspNetCore.SignalR.Internal foreach (var methodInfo in HubReflectionHelper.GetHubMethods(hubType)) { + if (methodInfo.IsGenericMethod) + { + throw new NotSupportedException($"Method '{methodInfo.Name}' is a generic method which is not supported on a Hub."); + } + var methodName = methodInfo.GetCustomAttribute()?.Name ?? methodInfo.Name; diff --git a/src/SignalR/server/Core/src/Internal/HubMethodDescriptor.cs b/src/SignalR/server/Core/src/Internal/HubMethodDescriptor.cs index c6ad4fec10..f868c2bcd7 100644 --- a/src/SignalR/server/Core/src/Internal/HubMethodDescriptor.cs +++ b/src/SignalR/server/Core/src/Internal/HubMethodDescriptor.cs @@ -132,13 +132,13 @@ namespace Microsoft.AspNetCore.SignalR.Internal var genericMethodInfo = adapterMethodInfo.MakeGenericMethod(streamReturnType); var methodParameters = genericMethodInfo.GetParameters(); - var methodArguements = new Expression[] + var methodArguments = new Expression[] { Expression.Convert(parameters[0], methodParameters[0].ParameterType), parameters[1], }; - var methodCall = Expression.Call(null, genericMethodInfo, methodArguements); + var methodCall = Expression.Call(null, genericMethodInfo, methodArguments); var lambda = Expression.Lambda>>(methodCall, parameters); return lambda.Compile(); } diff --git a/src/SignalR/server/Core/src/Internal/TypedClientBuilder.cs b/src/SignalR/server/Core/src/Internal/TypedClientBuilder.cs index 511280d636..b0343577cb 100644 --- a/src/SignalR/server/Core/src/Internal/TypedClientBuilder.cs +++ b/src/SignalR/server/Core/src/Internal/TypedClientBuilder.cs @@ -20,6 +20,10 @@ namespace Microsoft.AspNetCore.SignalR.Internal private static readonly PropertyInfo CancellationTokenNoneProperty = typeof(CancellationToken).GetProperty("None", BindingFlags.Public | BindingFlags.Static); + private static readonly ConstructorInfo ObjectConstructor = typeof(object).GetConstructors().Single(); + + private static readonly Type[] ParameterTypes = new Type[] { typeof(IClientProxy) }; + public static T Build(IClientProxy proxy) { return _builder.Value(proxy); @@ -40,20 +44,24 @@ namespace Microsoft.AspNetCore.SignalR.Internal var moduleBuilder = assemblyBuilder.DefineDynamicModule(ClientModuleName); var clientType = GenerateInterfaceImplementation(moduleBuilder); - return proxy => (T)Activator.CreateInstance(clientType, proxy); + var factoryMethod = clientType.GetMethod(nameof(Build), BindingFlags.Public | BindingFlags.Static); + return (Func)factoryMethod.CreateDelegate(typeof(Func)); } private static Type GenerateInterfaceImplementation(ModuleBuilder moduleBuilder) { - var type = moduleBuilder.DefineType( - ClientModuleName + "." + typeof(T).Name + "Impl", - TypeAttributes.Public, - typeof(Object), - new[] { typeof(T) }); + var name = ClientModuleName + "." + typeof(T).Name + "Impl"; - var proxyField = type.DefineField("_proxy", typeof(IClientProxy), FieldAttributes.Private); + var type = moduleBuilder.DefineType(name, TypeAttributes.Public, typeof(object), new[] { typeof(T) }); - BuildConstructor(type, proxyField); + var proxyField = type.DefineField("_proxy", typeof(IClientProxy), FieldAttributes.Private | FieldAttributes.InitOnly); + + var ctor = BuildConstructor(type, proxyField); + + // Because a constructor doesn't return anything, it can't be wrapped in a + // delegate directly, so we emit a factory method that just takes the IClientProxy, + // invokes the constructor (using newobj) and returns the new instance of type T. + BuildFactoryMethod(type, ctor); foreach (var method in GetAllInterfaceMethods(typeof(T))) { @@ -79,27 +87,23 @@ namespace Microsoft.AspNetCore.SignalR.Internal } } - private static void BuildConstructor(TypeBuilder type, FieldInfo proxyField) + private static ConstructorInfo BuildConstructor(TypeBuilder type, FieldInfo proxyField) { - var method = type.DefineMethod(".ctor", System.Reflection.MethodAttributes.Public | System.Reflection.MethodAttributes.HideBySig); + var ctor = type.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, ParameterTypes); - var ctor = typeof(object).GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, - null, new Type[] { }, null); - - method.SetReturnType(typeof(void)); - method.SetParameters(typeof(IClientProxy)); - - var generator = method.GetILGenerator(); + var generator = ctor.GetILGenerator(); // Call object constructor generator.Emit(OpCodes.Ldarg_0); - generator.Emit(OpCodes.Call, ctor); + generator.Emit(OpCodes.Call, ObjectConstructor); // Assign constructor argument to the proxyField generator.Emit(OpCodes.Ldarg_0); // type generator.Emit(OpCodes.Ldarg_1); // type proxyfield generator.Emit(OpCodes.Stfld, proxyField); // type.proxyField = proxyField generator.Emit(OpCodes.Ret); + + return ctor; } private static void BuildMethod(TypeBuilder type, MethodInfo interfaceMethodInfo, FieldInfo proxyField) @@ -127,11 +131,19 @@ namespace Microsoft.AspNetCore.SignalR.Internal var genericTypeNames = paramTypes.Where(p => p.IsGenericParameter).Select(p => p.Name).Distinct().ToArray(); - if (genericTypeNames.Any()) + if (genericTypeNames.Length > 0) { methodBuilder.DefineGenericParameters(genericTypeNames); } + // Check to see if the last parameter of the method is a CancellationToken + bool hasCancellationToken = paramTypes.LastOrDefault() == typeof(CancellationToken); + if (hasCancellationToken) + { + // remove CancellationToken from input paramTypes + paramTypes = paramTypes.Take(paramTypes.Length - 1).ToArray(); + } + var generator = methodBuilder.GetILGenerator(); // Declare local variable to store the arguments to IClientProxy.SendCoreAsync @@ -145,7 +157,7 @@ namespace Microsoft.AspNetCore.SignalR.Internal generator.Emit(OpCodes.Ldstr, interfaceMethodInfo.Name); // Create an new object array to hold all the parameters to this method - generator.Emit(OpCodes.Ldc_I4, parameters.Length); // Stack: + generator.Emit(OpCodes.Ldc_I4, paramTypes.Length); // Stack: generator.Emit(OpCodes.Newarr, typeof(object)); // allocate object array generator.Emit(OpCodes.Stloc_0); @@ -162,8 +174,16 @@ namespace Microsoft.AspNetCore.SignalR.Internal // Load parameter array on to the stack. generator.Emit(OpCodes.Ldloc_0); - // Get 'CancellationToken.None' and put it on the stack, since we don't support CancellationToken right now - generator.Emit(OpCodes.Call, CancellationTokenNoneProperty.GetMethod); + if (hasCancellationToken) + { + // Get CancellationToken from input argument and put it on the stack + generator.Emit(OpCodes.Ldarg, paramTypes.Length + 1); + } + else + { + // Get 'CancellationToken.None' and put it on the stack, for when method does not have CancellationToken + generator.Emit(OpCodes.Call, CancellationTokenNoneProperty.GetMethod); + } // Send! generator.Emit(OpCodes.Callvirt, invokeMethod); @@ -171,6 +191,17 @@ namespace Microsoft.AspNetCore.SignalR.Internal generator.Emit(OpCodes.Ret); // Return the Task returned by 'invokeMethod' } + private static void BuildFactoryMethod(TypeBuilder type, ConstructorInfo ctor) + { + var method = type.DefineMethod(nameof(Build), MethodAttributes.Public | MethodAttributes.Static, CallingConventions.Standard, typeof(T), ParameterTypes); + + var generator = method.GetILGenerator(); + + generator.Emit(OpCodes.Ldarg_0); // Load the IClientProxy argument onto the stack + generator.Emit(OpCodes.Newobj, ctor); // Call the generated constructor with the proxy + generator.Emit(OpCodes.Ret); // Return the typed client + } + private static void VerifyInterface(Type interfaceType) { if (!interfaceType.IsInterface) @@ -190,7 +221,7 @@ namespace Microsoft.AspNetCore.SignalR.Internal foreach (var method in interfaceType.GetMethods()) { - VerifyMethod(interfaceType, method); + VerifyMethod(method); } foreach (var parent in interfaceType.GetInterfaces()) @@ -199,7 +230,7 @@ namespace Microsoft.AspNetCore.SignalR.Internal } } - private static void VerifyMethod(Type interfaceType, MethodInfo interfaceMethod) + private static void VerifyMethod(MethodInfo interfaceMethod) { if (interfaceMethod.ReturnType != typeof(Task)) { diff --git a/src/SignalR/server/SignalR/ref/Microsoft.AspNetCore.SignalR.netcoreapp.cs b/src/SignalR/server/SignalR/ref/Microsoft.AspNetCore.SignalR.netcoreapp.cs index 0021c03cbd..de595a611b 100644 --- a/src/SignalR/server/SignalR/ref/Microsoft.AspNetCore.SignalR.netcoreapp.cs +++ b/src/SignalR/server/SignalR/ref/Microsoft.AspNetCore.SignalR.netcoreapp.cs @@ -16,11 +16,6 @@ namespace Microsoft.AspNetCore.Builder public partial interface IHubEndpointConventionBuilder : Microsoft.AspNetCore.Builder.IEndpointConventionBuilder { } - public static partial class SignalRAppBuilderExtensions - { - [System.ObsoleteAttribute("This method is obsolete and will be removed in a future version. The recommended alternative is to use MapHub inside Microsoft.AspNetCore.Builder.UseEndpoints(...).")] - public static Microsoft.AspNetCore.Builder.IApplicationBuilder UseSignalR(this Microsoft.AspNetCore.Builder.IApplicationBuilder app, System.Action configure) { throw null; } - } } namespace Microsoft.AspNetCore.SignalR { @@ -29,13 +24,6 @@ namespace Microsoft.AspNetCore.SignalR public static Microsoft.AspNetCore.Http.HttpContext GetHttpContext(this Microsoft.AspNetCore.SignalR.HubCallerContext connection) { throw null; } public static Microsoft.AspNetCore.Http.HttpContext GetHttpContext(this Microsoft.AspNetCore.SignalR.HubConnectionContext connection) { throw null; } } - [System.ObsoleteAttribute("This class is obsolete and will be removed in a future version. The recommended alternative is to use MapHub inside Microsoft.AspNetCore.Builder.UseEndpoints(...).")] - public partial class HubRouteBuilder - { - public HubRouteBuilder(Microsoft.AspNetCore.Http.Connections.ConnectionsRouteBuilder routes) { } - public void MapHub(Microsoft.AspNetCore.Http.PathString path) where THub : Microsoft.AspNetCore.SignalR.Hub { } - public void MapHub(Microsoft.AspNetCore.Http.PathString path, System.Action configureOptions) where THub : Microsoft.AspNetCore.SignalR.Hub { } - } } namespace Microsoft.Extensions.DependencyInjection { diff --git a/src/SignalR/server/SignalR/src/HubRouteBuilder.cs b/src/SignalR/server/SignalR/src/HubRouteBuilder.cs deleted file mode 100644 index cf9f694bb3..0000000000 --- a/src/SignalR/server/SignalR/src/HubRouteBuilder.cs +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Reflection; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Http.Connections; -using Microsoft.AspNetCore.Routing; - -namespace Microsoft.AspNetCore.SignalR -{ - /// - /// Maps incoming requests to types. - /// - /// This class is obsolete and will be removed in a future version. - /// The recommended alternative is to use MapHub<THub> inside Microsoft.AspNetCore.Builder.UseEndpoints(...). - /// - /// - [Obsolete("This class is obsolete and will be removed in a future version. The recommended alternative is to use MapHub inside Microsoft.AspNetCore.Builder.UseEndpoints(...).")] - public class HubRouteBuilder - { - private readonly ConnectionsRouteBuilder _routes; - private readonly IEndpointRouteBuilder _endpoints; - - /// - /// Initializes a new instance of the class. - /// - /// The routes builder. - public HubRouteBuilder(ConnectionsRouteBuilder routes) - { - _routes = routes; - } - - internal HubRouteBuilder(IEndpointRouteBuilder endpoints) - { - _endpoints = endpoints; - } - - /// - /// Maps incoming requests with the specified path to the specified type. - /// - /// The type to map requests to. - /// The request path. - public void MapHub(PathString path) where THub : Hub - { - MapHub(path, configureOptions: null); - } - - /// - /// Maps incoming requests with the specified path to the specified type. - /// - /// The type to map requests to. - /// The request path. - /// A callback to configure dispatcher options. - public void MapHub(PathString path, Action configureOptions) where THub : Hub - { - // This will be null if someone is manually using the HubRouteBuilder(ConnectionsRouteBuilder routes) constructor - // SignalR itself will only use the IEndpointRouteBuilder overload - if (_endpoints != null) - { - _endpoints.MapHub(path, configureOptions); - return; - } - - // find auth attributes - var authorizeAttributes = typeof(THub).GetCustomAttributes(inherit: true); - var options = new HttpConnectionDispatcherOptions(); - foreach (var attribute in authorizeAttributes) - { - options.AuthorizationData.Add(attribute); - } - configureOptions?.Invoke(options); - - _routes.MapConnections(path, options, builder => - { - builder.UseHub(); - }); - } - } -} diff --git a/src/SignalR/server/SignalR/src/SignalRAppBuilderExtensions.cs b/src/SignalR/server/SignalR/src/SignalRAppBuilderExtensions.cs deleted file mode 100644 index e01870193a..0000000000 --- a/src/SignalR/server/SignalR/src/SignalRAppBuilderExtensions.cs +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using Microsoft.AspNetCore.SignalR; -using Microsoft.Extensions.DependencyInjection; - -namespace Microsoft.AspNetCore.Builder -{ - /// - /// Extension methods for . - /// - public static class SignalRAppBuilderExtensions - { - /// - /// Adds SignalR to the request execution pipeline. - /// - /// This method is obsolete and will be removed in a future version. - /// The recommended alternative is to use MapHub<THub> inside Microsoft.AspNetCore.Builder.UseEndpoints(...). - /// - /// - /// The . - /// A callback to configure hub routes. - /// The same instance of the for chaining. - [Obsolete("This method is obsolete and will be removed in a future version. The recommended alternative is to use MapHub inside Microsoft.AspNetCore.Builder.UseEndpoints(...).")] - public static IApplicationBuilder UseSignalR(this IApplicationBuilder app, Action configure) - { - var marker = app.ApplicationServices.GetService(); - if (marker == null) - { - throw new InvalidOperationException("Unable to find the required services. Please add all the required services by calling " + - "'IServiceCollection.AddSignalR' inside the call to 'ConfigureServices(...)' in the application startup code."); - } - - app.UseWebSockets(); - app.UseRouting(); - app.UseAuthorization(); - app.UseEndpoints(endpoints => - { - configure(new HubRouteBuilder(endpoints)); - }); - - return app; - } - } -} diff --git a/src/SignalR/server/SignalR/test/EndToEndTests.cs b/src/SignalR/server/SignalR/test/EndToEndTests.cs index afd93d2c42..c9f85fefbd 100644 --- a/src/SignalR/server/SignalR/test/EndToEndTests.cs +++ b/src/SignalR/server/SignalR/test/EndToEndTests.cs @@ -36,7 +36,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests [Fact] public async Task CanStartAndStopConnectionUsingDefaultTransport() { - using (StartServer(out var server)) + using (var server = await StartServer()) { var url = server.Url + "/echo"; // The test should connect to the server using WebSockets transport on Windows 8 and newer. @@ -56,7 +56,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests writeContext.EventId.Name == "ErrorStartingTransport"; } - using (StartServer(out var server, expectedErrorsFilter: ExpectedErrors)) + using (var server = await StartServer(expectedErrorsFilter: ExpectedErrors)) { var url = server.Url + "/echo"; // The test should connect to the server using WebSockets transport on Windows 8 and newer. @@ -74,7 +74,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests [LogLevel(LogLevel.Trace)] public async Task CanStartAndStopConnectionUsingGivenTransport(HttpTransportType transportType) { - using (StartServer(out var server)) + using (var server = await StartServer()) { var url = server.Url + "/echo"; var connection = new HttpConnection(new HttpConnectionOptions { Url = new Uri(url), Transports = transportType, DefaultTransferFormat = TransferFormat.Text }, LoggerFactory); @@ -87,7 +87,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests [WebSocketsSupportedCondition] public async Task WebSocketsTest() { - using (StartServer(out var server)) + using (var server = await StartServer()) { var logger = LoggerFactory.CreateLogger(); @@ -125,7 +125,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests [WebSocketsSupportedCondition] public async Task WebSocketsReceivesAndSendsPartialFramesTest() { - using (StartServer(out var server)) + using (var server = await StartServer()) { var logger = LoggerFactory.CreateLogger(); @@ -164,7 +164,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests [WebSocketsSupportedCondition] public async Task HttpRequestsNotSentWhenWebSocketsTransportRequestedAndSkipNegotiationSet() { - using (StartServer(out var server)) + using (var server = await StartServer()) { var logger = LoggerFactory.CreateLogger(); var url = server.Url + "/echo"; @@ -214,7 +214,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests [InlineData(HttpTransportType.ServerSentEvents)] public async Task HttpConnectionThrowsIfSkipNegotiationSetAndTransportIsNotWebSockets(HttpTransportType transportType) { - using (StartServer(out var server)) + using (var server = await StartServer()) { var logger = LoggerFactory.CreateLogger(); var url = server.Url + "/echo"; @@ -257,7 +257,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests [LogLevel(LogLevel.Trace)] public async Task ConnectionCanSendAndReceiveMessages(HttpTransportType transportType, TransferFormat requestedTransferFormat) { - using (StartServer(out var server)) + using (var server = await StartServer()) { var logger = LoggerFactory.CreateLogger(); @@ -308,7 +308,6 @@ namespace Microsoft.AspNetCore.SignalR.Tests } [ConditionalTheory] - [Flaky("https://github.com/aspnet/AspNetCore-Internal/issues/1383", FlakyOn.All)] [WebSocketsSupportedCondition] [InlineData(5 * 4096)] [InlineData(1000 * 4096 + 32)] @@ -316,7 +315,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests public async Task ConnectionCanSendAndReceiveDifferentMessageSizesWebSocketsTransport(int length) { var message = new string('A', length); - using (StartServer(out var server)) + using (var server = await StartServer()) { var logger = LoggerFactory.CreateLogger(); @@ -365,7 +364,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests writeContext.EventId.Name == "ErrorWithNegotiation"; } - using (StartServer(out var server, ExpectedErrors)) + using (var server = await StartServer(ExpectedErrors)) { var logger = LoggerFactory.CreateLogger(); @@ -389,7 +388,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests writeContext.EventId.Name == "ErrorStartingTransport"; } - using (StartServer(out var server, ExpectedErrors)) + using (var server = await StartServer(ExpectedErrors)) { var logger = LoggerFactory.CreateLogger(); @@ -419,7 +418,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests writeContext.EventId.Name == "ErrorWithNegotiation"; } - using (StartServer(out var server, ExpectedErrors)) + using (var server = await StartServer(ExpectedErrors)) { var logger = LoggerFactory.CreateLogger(); @@ -455,7 +454,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests writeContext.EventId.Name == "ErrorWithNegotiation"; } - using (StartServer(out var server, ExpectedErrors)) + using (var server = await StartServer(ExpectedErrors)) { var logger = LoggerFactory.CreateLogger(); @@ -525,7 +524,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests private async Task ServerClosesConnectionWithErrorIfHubCannotBeCreated(HttpTransportType transportType) { - using (StartServer(out var server)) + using (var server = await StartServer()) { var logger = LoggerFactory.CreateLogger(); @@ -580,45 +579,6 @@ namespace Microsoft.AspNetCore.SignalR.Tests } } - [Fact] - [LogLevel(LogLevel.Trace)] - public async Task UnauthorizedHubConnectionDoesNotConnectWithEndpoints() - { - bool ExpectedErrors(WriteContext writeContext) - { - return writeContext.LoggerName == typeof(HttpConnection).FullName && - writeContext.EventId.Name == "ErrorWithNegotiation"; - } - - using (StartServer(out var server, ExpectedErrors)) - { - var logger = LoggerFactory.CreateLogger(); - - var url = server.Url + "/authHubEndpoints"; - var connection = new HubConnectionBuilder() - .WithLoggerFactory(LoggerFactory) - .WithUrl(url, HttpTransportType.LongPolling) - .Build(); - - try - { - logger.LogInformation("Starting connection to {url}", url); - await connection.StartAsync().OrTimeout(); - Assert.True(false); - } - catch (Exception ex) - { - Assert.Equal("Response status code does not indicate success: 401 (Unauthorized).", ex.Message); - } - finally - { - logger.LogInformation("Disposing Connection"); - await connection.DisposeAsync().OrTimeout(); - logger.LogInformation("Disposed Connection"); - } - } - } - [Fact] [LogLevel(LogLevel.Trace)] public async Task UnauthorizedHubConnectionDoesNotConnect() @@ -629,7 +589,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests writeContext.EventId.Name == "ErrorWithNegotiation"; } - using (StartServer(out var server, ExpectedErrors)) + using (var server = await StartServer(ExpectedErrors)) { var logger = LoggerFactory.CreateLogger(); @@ -658,53 +618,6 @@ namespace Microsoft.AspNetCore.SignalR.Tests } } - [Fact] - [LogLevel(LogLevel.Trace)] - public async Task AuthorizedHubConnectionCanConnectWithEndpoints() - { - bool ExpectedErrors(WriteContext writeContext) - { - return writeContext.LoggerName == typeof(HttpConnection).FullName && - writeContext.EventId.Name == "ErrorWithNegotiation"; - } - - using (StartServer(out var server, ExpectedErrors)) - { - var logger = LoggerFactory.CreateLogger(); - - string token; - using (var client = new HttpClient()) - { - client.BaseAddress = new Uri(server.Url); - - var response = await client.GetAsync("generatetoken?user=bob"); - token = await response.Content.ReadAsStringAsync(); - } - - var url = server.Url + "/authHubEndpoints"; - var connection = new HubConnectionBuilder() - .WithLoggerFactory(LoggerFactory) - .WithUrl(url, HttpTransportType.LongPolling, o => - { - o.AccessTokenProvider = () => Task.FromResult(token); - }) - .Build(); - - try - { - logger.LogInformation("Starting connection to {url}", url); - await connection.StartAsync().OrTimeout(); - logger.LogInformation("Connected to {url}", url); - } - finally - { - logger.LogInformation("Disposing Connection"); - await connection.DisposeAsync().OrTimeout(); - logger.LogInformation("Disposed Connection"); - } - } - } - [Fact] [LogLevel(LogLevel.Trace)] public async Task AuthorizedHubConnectionCanConnect() @@ -715,7 +628,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests writeContext.EventId.Name == "ErrorWithNegotiation"; } - using (StartServer(out var server, ExpectedErrors)) + using (var server = await StartServer(ExpectedErrors)) { var logger = LoggerFactory.CreateLogger(); diff --git a/src/SignalR/server/SignalR/test/HubConnectionHandlerTestUtils/Hubs.cs b/src/SignalR/server/SignalR/test/HubConnectionHandlerTestUtils/Hubs.cs index 3cae11661a..0de55bacef 100644 --- a/src/SignalR/server/SignalR/test/HubConnectionHandlerTestUtils/Hubs.cs +++ b/src/SignalR/server/SignalR/test/HubConnectionHandlerTestUtils/Hubs.cs @@ -182,7 +182,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests public Task ProtocolError() { - return Clients.Caller.SendAsync("Send", new string('x', 3000), new SelfRef()); + return Clients.Caller.SendAsync("Send", new SelfRef()); } public void InvalidArgument(CancellationToken token) @@ -196,7 +196,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests Self = this; } - public SelfRef Self; + public SelfRef Self { get; set; } } public async Task StreamingConcat(ChannelReader source) @@ -569,6 +569,13 @@ namespace Microsoft.AspNetCore.SignalR.Tests } } + public class GenericMethodHub : Hub + { + public void GenericMethod() + { + } + } + public class DisposeTrackingHub : TestHub { private readonly TrackDispose _trackDispose; @@ -660,7 +667,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests return new AsyncEnumerableImpl(CounterAsyncEnumerable(count)); } - public AsyncEnumerableImplChannelThrows AsyncEnumerableIsPreferedOverChannelReader(int count) + public AsyncEnumerableImplChannelThrows AsyncEnumerableIsPreferredOverChannelReader(int count) { return new AsyncEnumerableImplChannelThrows(CounterChannel(count)); } diff --git a/src/SignalR/server/SignalR/test/HubConnectionHandlerTests.cs b/src/SignalR/server/SignalR/test/HubConnectionHandlerTests.cs index c3911252a5..aaf4cfa582 100644 --- a/src/SignalR/server/SignalR/test/HubConnectionHandlerTests.cs +++ b/src/SignalR/server/SignalR/test/HubConnectionHandlerTests.cs @@ -20,6 +20,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Connections.Features; using Microsoft.AspNetCore.SignalR.Internal; using Microsoft.AspNetCore.SignalR.Protocol; +using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Testing; @@ -1328,6 +1329,19 @@ namespace Microsoft.AspNetCore.SignalR.Tests } } + [Fact] + public void CannotHaveGenericMethodOnHub() + { + using (StartVerifiableLog()) + { + var serviceProvider = HubConnectionHandlerTestUtils.CreateServiceProvider(null, LoggerFactory); + + var exception = Assert.Throws(() => serviceProvider.GetService>()); + + Assert.Equal("Method 'GenericMethod' is a generic method which is not supported on a Hub.", exception.Message); + } + } + [Theory] [MemberData(nameof(HubTypes))] public async Task BroadcastHubMethodSendsToAllClients(Type hubType) @@ -2124,7 +2138,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests nameof(StreamingHub.CounterAsyncEnumerable), nameof(StreamingHub.CounterAsyncEnumerableAsync), nameof(StreamingHub.CounterAsyncEnumerableImpl), - nameof(StreamingHub.AsyncEnumerableIsPreferedOverChannelReader), + nameof(StreamingHub.AsyncEnumerableIsPreferredOverChannelReader), }; foreach (var method in methods) @@ -2216,6 +2230,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests { Assert.NotNull(context.Resource); var resource = Assert.IsType(context.Resource); + Assert.Equal(typeof(MethodHub), resource.HubType); Assert.Equal(nameof(MethodHub.MultiParamAuthMethod), resource.HubMethodName); Assert.Equal(2, resource.HubMethodArguments?.Count); Assert.Equal("Hello", resource.HubMethodArguments[0]); @@ -2499,33 +2514,15 @@ namespace Microsoft.AspNetCore.SignalR.Tests private class StringFormatter : IMessagePackFormatter { - public T Deserialize(byte[] bytes, int offset, IFormatterResolver formatterResolver, out int readSize) + public T Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options) { // this method isn't used in our tests - readSize = 0; return default; } - public int Serialize(ref byte[] bytes, int offset, T value, IFormatterResolver formatterResolver) + public void Serialize(ref MessagePackWriter writer, T value, MessagePackSerializerOptions options) { - // string of size 15 - bytes[offset] = 0xAF; - bytes[offset + 1] = (byte)'f'; - bytes[offset + 2] = (byte)'o'; - bytes[offset + 3] = (byte)'r'; - bytes[offset + 4] = (byte)'m'; - bytes[offset + 5] = (byte)'a'; - bytes[offset + 6] = (byte)'t'; - bytes[offset + 7] = (byte)'t'; - bytes[offset + 8] = (byte)'e'; - bytes[offset + 9] = (byte)'d'; - bytes[offset + 10] = (byte)'S'; - bytes[offset + 11] = (byte)'t'; - bytes[offset + 12] = (byte)'r'; - bytes[offset + 13] = (byte)'i'; - bytes[offset + 14] = (byte)'n'; - bytes[offset + 15] = (byte)'g'; - return 16; + writer.Write("formattedString"); } } } @@ -2772,6 +2769,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests } [Fact] + [QuarantinedTest] public async Task ReceivingMessagesPreventsConnectionTimeoutFromOccuring() { using (StartVerifiableLog()) @@ -3212,7 +3210,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests } } - [Fact(Skip = "Object not supported yet")] + [Fact] public async Task UploadStreamedObjects() { var serviceProvider = HubConnectionHandlerTestUtils.CreateServiceProvider(); @@ -3276,7 +3274,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests } } - [Fact(Skip = "Cyclic parsing is not supported yet")] + [Fact] public async Task ConnectionAbortedIfSendFailsWithProtocolError() { using (StartVerifiableLog()) @@ -3292,8 +3290,6 @@ namespace Microsoft.AspNetCore.SignalR.Tests var connectionHandlerTask = await client.ConnectAsync(connectionHandler).OrTimeout(); await client.SendInvocationAsync(nameof(MethodHub.ProtocolError)).OrTimeout(); - - await client.Connected.OrTimeout(); await connectionHandlerTask.OrTimeout(); } } @@ -3861,7 +3857,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests // The usage of TypeNameHandling.All is a security risk. // If you're implementing this in your own application instead use your own 'type' field and a custom JsonConverter // or ensure you're restricting to only known types with a custom SerializationBinder like we are here. - // See https://github.com/aspnet/AspNetCore/issues/11495#issuecomment-505047422 + // See https://github.com/dotnet/aspnetcore/issues/11495#issuecomment-505047422 TypeNameHandling = TypeNameHandling.All, SerializationBinder = StreamingHub.DerivedParameterKnownTypesBinder.Instance } diff --git a/src/SignalR/server/SignalR/test/Internal/TypedClientBuilderTests.cs b/src/SignalR/server/SignalR/test/Internal/TypedClientBuilderTests.cs index 4f68f6fe74..f88820cb78 100644 --- a/src/SignalR/server/SignalR/test/Internal/TypedClientBuilderTests.cs +++ b/src/SignalR/server/SignalR/test/Internal/TypedClientBuilderTests.cs @@ -75,6 +75,41 @@ namespace Microsoft.AspNetCore.SignalR.Tests.Internal await task2.OrTimeout(); } + [Fact] + public async Task SupportsCancellationToken() + { + var clientProxy = new MockProxy(); + var typedProxy = TypedClientBuilder.Build(clientProxy); + CancellationTokenSource cts1 = new CancellationTokenSource(); + var task1 = typedProxy.Method("foo", cts1.Token); + Assert.False(task1.IsCompleted); + + CancellationTokenSource cts2 = new CancellationTokenSource(); + var task2 = typedProxy.NoArgumentMethod(cts2.Token); + Assert.False(task2.IsCompleted); + + Assert.Collection(clientProxy.Sends, + send1 => + { + Assert.Equal("Method", send1.Method); + Assert.Single(send1.Arguments); + Assert.Collection(send1.Arguments, + arg1 => Assert.Equal("foo", arg1)); + Assert.Equal(cts1.Token, send1.CancellationToken); + send1.Complete(); + }, + send2 => + { + Assert.Equal("NoArgumentMethod", send2.Method); + Assert.Empty(send2.Arguments); + Assert.Equal(cts2.Token, send2.CancellationToken); + send2.Complete(); + }); + + await task1.OrTimeout(); + await task2.OrTimeout(); + } + [Fact] public void ThrowsIfProvidedAClass() { @@ -179,6 +214,12 @@ namespace Microsoft.AspNetCore.SignalR.Tests.Internal Task SubMethod(string foo); } + public interface ICancellationTokenMethod + { + Task Method(string foo, CancellationToken cancellationToken); + Task NoArgumentMethod(CancellationToken cancellationToken); + } + public interface IPropertiesClient { string Property { get; } diff --git a/src/SignalR/server/SignalR/test/MapSignalRTests.cs b/src/SignalR/server/SignalR/test/MapSignalRTests.cs index 01bc4d2e33..8f1a79dc4a 100644 --- a/src/SignalR/server/SignalR/test/MapSignalRTests.cs +++ b/src/SignalR/server/SignalR/test/MapSignalRTests.cs @@ -31,41 +31,6 @@ namespace Microsoft.AspNetCore.SignalR.Tests var executedConfigure = false; var builder = new WebHostBuilder(); - builder - .UseKestrel() - .Configure(app => - { - executedConfigure = true; - - var ex = Assert.Throws(() => - { -#pragma warning disable CS0618 // Type or member is obsolete - app.UseSignalR(routes => - { - routes.MapHub("/overloads"); - }); -#pragma warning restore CS0618 // Type or member is obsolete - }); - - Assert.Equal("Unable to find the required services. Please add all the required services by calling " + - "'IServiceCollection.AddSignalR' inside the call to 'ConfigureServices(...)' in the application startup code.", ex.Message); - }) - .UseUrls("http://127.0.0.1:0"); - - using (var host = builder.Build()) - { - host.Start(); - } - - Assert.True(executedConfigure); - } - - [Fact] - public void NotAddingSignalRServiceThrowsWhenUsingEndpointRouting() - { - var executedConfigure = false; - var builder = new WebHostBuilder(); - builder .UseKestrel() .ConfigureServices(services => @@ -189,7 +154,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests public void MapHubEndPointRoutingFindsAttributesOnHub() { var authCount = 0; - using (var host = BuildWebHostWithEndPointRouting(routes => routes.MapHub("/path", options => + using (var host = BuildWebHost(routes => routes.MapHub("/path", options => { authCount += options.AuthorizationData.Count; }))) @@ -219,7 +184,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests { var authCount = 0; HttpConnectionDispatcherOptions configuredOptions = null; - using (var host = BuildWebHostWithEndPointRouting(routes => routes.MapHub("/path", options => + using (var host = BuildWebHost(routes => routes.MapHub("/path", options => { authCount += options.AuthorizationData.Count; options.AuthorizationData.Add(new AuthorizeAttribute()); @@ -256,7 +221,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests .RequireAuthorization(new AuthorizeAttribute("Foo")); } - using (var host = BuildWebHostWithEndPointRouting(ConfigureRoutes)) + using (var host = BuildWebHost(ConfigureRoutes)) { host.Start(); @@ -295,7 +260,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests endpoints.MapHub("/path"); } - using (var host = BuildWebHostWithEndPointRouting(ConfigureRoutes)) + using (var host = BuildWebHost(ConfigureRoutes)) { host.Start(); @@ -320,9 +285,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests [Fact] public void MapHubAppliesHubMetadata() { -#pragma warning disable CS0618 // Type or member is obsolete - void ConfigureRoutes(HubRouteBuilder routes) -#pragma warning restore CS0618 // Type or member is obsolete + void ConfigureRoutes(IEndpointRouteBuilder routes) { // This "Foo" policy should override the default auth attribute routes.MapHub("/path"); @@ -375,7 +338,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests { } - private IWebHost BuildWebHostWithEndPointRouting(Action configure) + private IWebHost BuildWebHost(Action configure) { return new WebHostBuilder() .UseKestrel() @@ -391,23 +354,5 @@ namespace Microsoft.AspNetCore.SignalR.Tests .UseUrls("http://127.0.0.1:0") .Build(); } - -#pragma warning disable CS0618 // Type or member is obsolete - private IWebHost BuildWebHost(Action configure) - { - return new WebHostBuilder() - .UseKestrel() - .ConfigureServices(services => - { - services.AddSignalR(); - }) - .Configure(app => - { - app.UseSignalR(options => configure(options)); - }) - .UseUrls("http://127.0.0.1:0") - .Build(); - } -#pragma warning restore CS0618 // Type or member is obsolete } } diff --git a/src/SignalR/server/SignalR/test/Startup.cs b/src/SignalR/server/SignalR/test/Startup.cs index 25612f917f..8ee6f7e53f 100644 --- a/src/SignalR/server/SignalR/test/Startup.cs +++ b/src/SignalR/server/SignalR/test/Startup.cs @@ -71,18 +71,10 @@ namespace Microsoft.AspNetCore.SignalR.Tests app.UseAuthentication(); app.UseAuthorization(); - // Legacy routing, runs different code path for mapping hubs -#pragma warning disable CS0618 // Type or member is obsolete - app.UseSignalR(routes => - { - routes.MapHub("/authHub"); - }); -#pragma warning restore CS0618 // Type or member is obsolete - app.UseEndpoints(endpoints => { endpoints.MapHub("/uncreatable"); - endpoints.MapHub("/authHubEndpoints"); + endpoints.MapHub("/authHub"); endpoints.MapConnectionHandler("/echo"); endpoints.MapConnectionHandler("/echoAndClose"); diff --git a/src/SignalR/server/SignalR/test/UserAgentHeaderTest.cs b/src/SignalR/server/SignalR/test/UserAgentHeaderTest.cs new file mode 100644 index 0000000000..35675d450f --- /dev/null +++ b/src/SignalR/server/SignalR/test/UserAgentHeaderTest.cs @@ -0,0 +1,54 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using Xunit; +using Constants = Microsoft.AspNetCore.Http.Connections.Client.Internal.Constants; + +namespace Microsoft.AspNetCore.Http.Connections.Tests +{ + public class UserAgentHeaderTest + { + [Theory] + [MemberData(nameof(UserAgentTestDataNames))] + public void UserAgentHeaderIsCorrect(string testDataName) + { + var testData = UserAgents[testDataName]; + Assert.Equal(testData.Expected, Constants.ConstructUserAgent(testData.Version, testData.DetailedVersion, testData.Os, testData.Runtime, testData.RuntimeVersion)); + } + + public static Dictionary UserAgents => new[] + { + new UserAgentTestData("FullInfo", new Version(1, 4), "1.4.3-preview9", "Windows NT", ".NET", ".NET 4.8.7", "Microsoft SignalR/1.4 (1.4.3-preview9; Windows NT; .NET; .NET 4.8.7)"), + new UserAgentTestData("EmptyOs", new Version(3, 1), "3.1.0", "", ".NET", ".NET 4.8.9", "Microsoft SignalR/3.1 (3.1.0; Unknown OS; .NET; .NET 4.8.9)"), + new UserAgentTestData("EmptyRuntimeVersion", new Version(3, 1), "3.1.0", "", ".NET", "", "Microsoft SignalR/3.1 (3.1.0; Unknown OS; .NET; Unknown Runtime Version)"), + new UserAgentTestData("EmptyDetailedVersion", new Version(3, 1), "", "Linux", ".NET", ".NET 4.5.1", "Microsoft SignalR/3.1 (Unknown Version; Linux; .NET; .NET 4.5.1)"), + }.ToDictionary(t => t.Name); + + public static IEnumerable UserAgentTestDataNames => UserAgents.Keys.Select(name => new object[] { name }); + + public class UserAgentTestData + { + public string Name { get; } + public Version Version { get; } + public string DetailedVersion { get; } + public string Os { get; } + public string Runtime { get; } + public string RuntimeVersion { get; } + public string Expected { get; } + + public UserAgentTestData(string name, Version version, string detailedVersion, string os, string runtime, string runtimeVersion, string expected) + { + Name = name; + Version = version; + DetailedVersion = detailedVersion; + Os = os; + Runtime = runtime; + RuntimeVersion = runtimeVersion; + Expected = expected; + } + } + } +} diff --git a/src/SignalR/server/SignalR/test/WebSocketsTransportTests.cs b/src/SignalR/server/SignalR/test/WebSocketsTransportTests.cs index c40be2b320..2c059c3edc 100644 --- a/src/SignalR/server/SignalR/test/WebSocketsTransportTests.cs +++ b/src/SignalR/server/SignalR/test/WebSocketsTransportTests.cs @@ -13,6 +13,7 @@ using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Http.Connections.Client; using Microsoft.AspNetCore.Http.Connections.Client.Internal; using Microsoft.AspNetCore.Testing; +using Microsoft.Net.Http.Headers; using Moq; using Xunit; @@ -52,7 +53,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests [WebSocketsSupportedCondition] public async Task WebSocketsTransportStopsSendAndReceiveLoopsWhenTransportIsStopped() { - using (StartServer(out var server)) + using (var server = await StartServer()) { var webSocketsTransport = new WebSocketsTransport(httpConnectionOptions: null, loggerFactory: LoggerFactory, accessTokenProvider: null); await webSocketsTransport.StartAsync(new Uri(server.WebSocketsUrl + "/echo"), @@ -62,11 +63,11 @@ namespace Microsoft.AspNetCore.SignalR.Tests } } - [ConditionalFact(Skip = "Issue in ClientWebSocket prevents user-agent being set - https://github.com/dotnet/corefx/issues/26627")] + [ConditionalFact] [WebSocketsSupportedCondition] public async Task WebSocketsTransportSendsUserAgent() { - using (StartServer(out var server)) + using (var server = await StartServer()) { var webSocketsTransport = new WebSocketsTransport(httpConnectionOptions: null, loggerFactory: LoggerFactory, accessTokenProvider: null); await webSocketsTransport.StartAsync(new Uri(server.WebSocketsUrl + "/httpheader"), @@ -86,7 +87,10 @@ namespace Microsoft.AspNetCore.SignalR.Tests .Assembly .GetCustomAttribute(); - Assert.Equal("Microsoft.AspNetCore.Http.Connections.Client/" + assemblyVersion.InformationalVersion, userAgent); + var majorVersion = typeof(HttpConnection).Assembly.GetName().Version.Major; + var minorVersion = typeof(HttpConnection).Assembly.GetName().Version.Minor; + + Assert.StartsWith($"Microsoft SignalR/{majorVersion}.{minorVersion} ({assemblyVersion.InformationalVersion}; ", userAgent); } } @@ -94,13 +98,13 @@ namespace Microsoft.AspNetCore.SignalR.Tests [WebSocketsSupportedCondition] public async Task WebSocketsTransportSendsXRequestedWithHeader() { - using (StartServer(out var server)) + using (var server = await StartServer()) { var webSocketsTransport = new WebSocketsTransport(httpConnectionOptions: null, loggerFactory: LoggerFactory, accessTokenProvider: null); await webSocketsTransport.StartAsync(new Uri(server.WebSocketsUrl + "/httpheader"), TransferFormat.Binary).OrTimeout(); - await webSocketsTransport.Output.WriteAsync(Encoding.UTF8.GetBytes("X-Requested-With")); + await webSocketsTransport.Output.WriteAsync(Encoding.UTF8.GetBytes(HeaderNames.XRequestedWith)); // The HTTP header endpoint closes the connection immediately after sending response which should stop the transport await webSocketsTransport.Running.OrTimeout(); @@ -117,7 +121,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests [WebSocketsSupportedCondition] public async Task WebSocketsTransportStopsWhenConnectionChannelClosed() { - using (StartServer(out var server)) + using (var server = await StartServer()) { var webSocketsTransport = new WebSocketsTransport(httpConnectionOptions: null, loggerFactory: LoggerFactory, accessTokenProvider: null); await webSocketsTransport.StartAsync(new Uri(server.WebSocketsUrl + "/echo"), @@ -133,7 +137,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests [InlineData(TransferFormat.Binary)] public async Task WebSocketsTransportStopsWhenConnectionClosedByTheServer(TransferFormat transferFormat) { - using (StartServer(out var server)) + using (var server = await StartServer()) { var webSocketsTransport = new WebSocketsTransport(httpConnectionOptions: null, loggerFactory: LoggerFactory, accessTokenProvider: null); await webSocketsTransport.StartAsync(new Uri(server.WebSocketsUrl + "/echoAndClose"), transferFormat); @@ -155,7 +159,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests [InlineData(TransferFormat.Binary)] public async Task WebSocketsTransportSetsTransferFormat(TransferFormat transferFormat) { - using (StartServer(out var server)) + using (var server = await StartServer()) { var webSocketsTransport = new WebSocketsTransport(httpConnectionOptions: null, loggerFactory: LoggerFactory, accessTokenProvider: null); diff --git a/src/SignalR/server/Specification.Tests/src/Internal/TaskExtensions.cs b/src/SignalR/server/Specification.Tests/src/Internal/TaskExtensions.cs index 1a6b2a7485..7b431133c5 100644 --- a/src/SignalR/server/Specification.Tests/src/Internal/TaskExtensions.cs +++ b/src/SignalR/server/Specification.Tests/src/Internal/TaskExtensions.cs @@ -9,7 +9,7 @@ using System.Threading.Tasks; namespace Microsoft.AspNetCore.Testing { - // Copied from https://github.com/aspnet/Extensions/blob/master/src/TestingUtils/Microsoft.AspNetCore.Testing/src/TaskExtensions.cs + // Copied from https://github.com/dotnet/extensions/blob/master/src/TestingUtils/Microsoft.AspNetCore.Testing/src/TaskExtensions.cs // Required because Microsoft.AspNetCore.Testing is not shipped internal static class TaskExtensions { diff --git a/src/SignalR/server/Specification.Tests/src/Microsoft.AspNetCore.SignalR.Specification.Tests.csproj b/src/SignalR/server/Specification.Tests/src/Microsoft.AspNetCore.SignalR.Specification.Tests.csproj index 69a2c459c3..932181d408 100644 --- a/src/SignalR/server/Specification.Tests/src/Microsoft.AspNetCore.SignalR.Specification.Tests.csproj +++ b/src/SignalR/server/Specification.Tests/src/Microsoft.AspNetCore.SignalR.Specification.Tests.csproj @@ -3,7 +3,7 @@ Tests for users to verify their own implementations of SignalR types $(DefaultNetCoreTargetFramework) - true + true false false diff --git a/src/SignalR/server/StackExchangeRedis/src/Internal/MessagePackUtil.cs b/src/SignalR/server/StackExchangeRedis/src/Internal/MessagePackUtil.cs deleted file mode 100644 index 7780bca988..0000000000 --- a/src/SignalR/server/StackExchangeRedis/src/Internal/MessagePackUtil.cs +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Diagnostics; -using System.Runtime.InteropServices; -using MessagePack; - -namespace Microsoft.AspNetCore.SignalR.StackExchangeRedis.Internal -{ - internal static class MessagePackUtil - { - public static int ReadArrayHeader(ref ReadOnlyMemory data) - { - var arr = GetArray(data); - var val = MessagePackBinary.ReadArrayHeader(arr.Array, arr.Offset, out var readSize); - data = data.Slice(readSize); - return val; - } - - public static int ReadMapHeader(ref ReadOnlyMemory data) - { - var arr = GetArray(data); - var val = MessagePackBinary.ReadMapHeader(arr.Array, arr.Offset, out var readSize); - data = data.Slice(readSize); - return val; - } - - public static string ReadString(ref ReadOnlyMemory data) - { - var arr = GetArray(data); - var val = MessagePackBinary.ReadString(arr.Array, arr.Offset, out var readSize); - data = data.Slice(readSize); - return val; - } - - public static byte[] ReadBytes(ref ReadOnlyMemory data) - { - var arr = GetArray(data); - var val = MessagePackBinary.ReadBytes(arr.Array, arr.Offset, out var readSize); - data = data.Slice(readSize); - return val; - } - - public static int ReadInt32(ref ReadOnlyMemory data) - { - var arr = GetArray(data); - var val = MessagePackBinary.ReadInt32(arr.Array, arr.Offset, out var readSize); - data = data.Slice(readSize); - return val; - } - - public static byte ReadByte(ref ReadOnlyMemory data) - { - var arr = GetArray(data); - var val = MessagePackBinary.ReadByte(arr.Array, arr.Offset, out var readSize); - data = data.Slice(readSize); - return val; - } - - private static ArraySegment GetArray(ReadOnlyMemory data) - { - var isArray = MemoryMarshal.TryGetArray(data, out var array); - Debug.Assert(isArray); - return array; - } - } -} diff --git a/src/SignalR/server/StackExchangeRedis/src/Internal/RedisProtocol.cs b/src/SignalR/server/StackExchangeRedis/src/Internal/RedisProtocol.cs index 76b5ed1b6e..3126b52f5d 100644 --- a/src/SignalR/server/StackExchangeRedis/src/Internal/RedisProtocol.cs +++ b/src/SignalR/server/StackExchangeRedis/src/Internal/RedisProtocol.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Buffers; using System.Collections.Generic; using System.Diagnostics; using System.IO; @@ -29,7 +30,7 @@ namespace Microsoft.AspNetCore.SignalR.StackExchangeRedis.Internal // * Acks are sent to the Acknowledgement channel. // * See the Write[type] methods for a description of the protocol for each in-depth. // * The "Variable length integer" is the length-prefixing format used by BinaryReader/BinaryWriter: - // * https://docs.microsoft.com/en-us/dotnet/api/system.io.binarywriter.write?view=netstandard-2.0 + // * https://docs.microsoft.com/dotnet/api/system.io.binarywriter.write?view=netcore-2.2 // * The "Length prefixed string" is the string format used by BinaryReader/BinaryWriter: // * A 7-bit variable length integer encodes the length in bytes, followed by the encoded string in UTF-8. @@ -43,30 +44,33 @@ namespace Microsoft.AspNetCore.SignalR.StackExchangeRedis.Internal // * [The output of WriteSerializedHubMessage, which is an 'arr'] // Any additional items are discarded. - var writer = MemoryBufferWriter.Get(); - + var memoryBufferWriter = MemoryBufferWriter.Get(); try { - MessagePackBinary.WriteArrayHeader(writer, 2); + var writer = new MessagePackWriter(memoryBufferWriter); + + writer.WriteArrayHeader(2); if (excludedConnectionIds != null && excludedConnectionIds.Count > 0) { - MessagePackBinary.WriteArrayHeader(writer, excludedConnectionIds.Count); + writer.WriteArrayHeader(excludedConnectionIds.Count); foreach (var id in excludedConnectionIds) { - MessagePackBinary.WriteString(writer, id); + writer.Write(id); } } else { - MessagePackBinary.WriteArrayHeader(writer, 0); + writer.WriteArrayHeader(0); } - WriteHubMessage(writer, new InvocationMessage(methodName, args)); - return writer.ToArray(); + WriteHubMessage(ref writer, new InvocationMessage(methodName, args)); + writer.Flush(); + + return memoryBufferWriter.ToArray(); } finally { - MemoryBufferWriter.Return(writer); + MemoryBufferWriter.Return(memoryBufferWriter); } } @@ -80,21 +84,24 @@ namespace Microsoft.AspNetCore.SignalR.StackExchangeRedis.Internal // * A 'str': The connection Id // Any additional items are discarded. - var writer = MemoryBufferWriter.Get(); + var memoryBufferWriter = MemoryBufferWriter.Get(); try { - MessagePackBinary.WriteArrayHeader(writer, 5); - MessagePackBinary.WriteInt32(writer, command.Id); - MessagePackBinary.WriteString(writer, command.ServerName); - MessagePackBinary.WriteByte(writer, (byte)command.Action); - MessagePackBinary.WriteString(writer, command.GroupName); - MessagePackBinary.WriteString(writer, command.ConnectionId); + var writer = new MessagePackWriter(memoryBufferWriter); - return writer.ToArray(); + writer.WriteArrayHeader(5); + writer.Write(command.Id); + writer.Write(command.ServerName); + writer.Write((byte)command.Action); + writer.Write(command.GroupName); + writer.Write(command.ConnectionId); + writer.Flush(); + + return memoryBufferWriter.ToArray(); } finally { - MemoryBufferWriter.Return(writer); + MemoryBufferWriter.Return(memoryBufferWriter); } } @@ -104,101 +111,110 @@ namespace Microsoft.AspNetCore.SignalR.StackExchangeRedis.Internal // * An 'int': The Id of the command being acknowledged. // Any additional items are discarded. - var writer = MemoryBufferWriter.Get(); + var memoryBufferWriter = MemoryBufferWriter.Get(); try { - MessagePackBinary.WriteArrayHeader(writer, 1); - MessagePackBinary.WriteInt32(writer, messageId); + var writer = new MessagePackWriter(memoryBufferWriter); - return writer.ToArray(); + writer.WriteArrayHeader(1); + writer.Write(messageId); + writer.Flush(); + + return memoryBufferWriter.ToArray(); } finally { - MemoryBufferWriter.Return(writer); + MemoryBufferWriter.Return(memoryBufferWriter); } } public RedisInvocation ReadInvocation(ReadOnlyMemory data) { // See WriteInvocation for the format - ValidateArraySize(ref data, 2, "Invocation"); + var reader = new MessagePackReader(data); + ValidateArraySize(ref reader, 2, "Invocation"); // Read excluded Ids IReadOnlyList excludedConnectionIds = null; - var idCount = MessagePackUtil.ReadArrayHeader(ref data); + var idCount = reader.ReadArrayHeader(); if (idCount > 0) { var ids = new string[idCount]; for (var i = 0; i < idCount; i++) { - ids[i] = MessagePackUtil.ReadString(ref data); + ids[i] = reader.ReadString(); } excludedConnectionIds = ids; } // Read payload - var message = ReadSerializedHubMessage(ref data); + var message = ReadSerializedHubMessage(ref reader); return new RedisInvocation(message, excludedConnectionIds); } public RedisGroupCommand ReadGroupCommand(ReadOnlyMemory data) { - // See WriteGroupCommand for format. - ValidateArraySize(ref data, 5, "GroupCommand"); + var reader = new MessagePackReader(data); - var id = MessagePackUtil.ReadInt32(ref data); - var serverName = MessagePackUtil.ReadString(ref data); - var action = (GroupAction)MessagePackUtil.ReadByte(ref data); - var groupName = MessagePackUtil.ReadString(ref data); - var connectionId = MessagePackUtil.ReadString(ref data); + // See WriteGroupCommand for format. + ValidateArraySize(ref reader, 5, "GroupCommand"); + + var id = reader.ReadInt32(); + var serverName = reader.ReadString(); + var action = (GroupAction)reader.ReadByte(); + var groupName = reader.ReadString(); + var connectionId = reader.ReadString(); return new RedisGroupCommand(id, serverName, action, groupName, connectionId); } public int ReadAck(ReadOnlyMemory data) { + var reader = new MessagePackReader(data); + // See WriteAck for format - ValidateArraySize(ref data, 1, "Ack"); - return MessagePackUtil.ReadInt32(ref data); + ValidateArraySize(ref reader, 1, "Ack"); + return reader.ReadInt32(); } - private void WriteHubMessage(Stream stream, HubMessage message) + private void WriteHubMessage(ref MessagePackWriter writer, HubMessage message) { // Written as a MessagePack 'map' where the keys are the name of the protocol (as a MessagePack 'str') // and the values are the serialized blob (as a MessagePack 'bin'). var serializedHubMessages = _messageSerializer.SerializeMessage(message); - MessagePackBinary.WriteMapHeader(stream, serializedHubMessages.Count); + writer.WriteMapHeader(serializedHubMessages.Count); foreach (var serializedMessage in serializedHubMessages) { - MessagePackBinary.WriteString(stream, serializedMessage.ProtocolName); + writer.Write(serializedMessage.ProtocolName); var isArray = MemoryMarshal.TryGetArray(serializedMessage.Serialized, out var array); Debug.Assert(isArray); - MessagePackBinary.WriteBytes(stream, array.Array, array.Offset, array.Count); + writer.Write(array); } } - public static SerializedHubMessage ReadSerializedHubMessage(ref ReadOnlyMemory data) + public static SerializedHubMessage ReadSerializedHubMessage(ref MessagePackReader reader) { - var count = MessagePackUtil.ReadMapHeader(ref data); + var count = reader.ReadMapHeader(); var serializations = new SerializedMessage[count]; for (var i = 0; i < count; i++) { - var protocol = MessagePackUtil.ReadString(ref data); - var serialized = MessagePackUtil.ReadBytes(ref data); + var protocol = reader.ReadString(); + var serialized = reader.ReadBytes()?.ToArray() ?? Array.Empty(); + serializations[i] = new SerializedMessage(protocol, serialized); } return new SerializedHubMessage(serializations); } - private static void ValidateArraySize(ref ReadOnlyMemory data, int expectedLength, string messageType) + private static void ValidateArraySize(ref MessagePackReader reader, int expectedLength, string messageType) { - var length = MessagePackUtil.ReadArrayHeader(ref data); + var length = reader.ReadArrayHeader(); if (length < expectedLength) { diff --git a/src/SignalR/server/StackExchangeRedis/src/Microsoft.AspNetCore.SignalR.StackExchangeRedis.csproj b/src/SignalR/server/StackExchangeRedis/src/Microsoft.AspNetCore.SignalR.StackExchangeRedis.csproj index affff6ae4a..731b94720d 100644 --- a/src/SignalR/server/StackExchangeRedis/src/Microsoft.AspNetCore.SignalR.StackExchangeRedis.csproj +++ b/src/SignalR/server/StackExchangeRedis/src/Microsoft.AspNetCore.SignalR.StackExchangeRedis.csproj @@ -3,7 +3,7 @@ Provides scale-out support for ASP.NET Core SignalR using a Redis server and the StackExchange.Redis client. $(DefaultNetCoreTargetFramework) - true + true diff --git a/src/SignalR/server/StackExchangeRedis/src/RedisOptions.cs b/src/SignalR/server/StackExchangeRedis/src/RedisOptions.cs index b34c7fb117..4497995673 100644 --- a/src/SignalR/server/StackExchangeRedis/src/RedisOptions.cs +++ b/src/SignalR/server/StackExchangeRedis/src/RedisOptions.cs @@ -30,7 +30,7 @@ namespace Microsoft.AspNetCore.SignalR.StackExchangeRedis internal async Task ConnectAsync(TextWriter log) { - // Factory is publically settable. Assigning to a local variable before null check for thread safety. + // Factory is publicly settable. Assigning to a local variable before null check for thread safety. var factory = ConnectionFactory; if (factory == null) { diff --git a/src/SignalR/server/StackExchangeRedis/test/RedisServerFixture.cs b/src/SignalR/server/StackExchangeRedis/test/RedisServerFixture.cs index a498682567..ab063ee02a 100644 --- a/src/SignalR/server/StackExchangeRedis/test/RedisServerFixture.cs +++ b/src/SignalR/server/StackExchangeRedis/test/RedisServerFixture.cs @@ -2,13 +2,16 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Threading.Tasks; using Microsoft.AspNetCore.SignalR.Tests; +using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Testing; +using Xunit; namespace Microsoft.AspNetCore.SignalR.StackExchangeRedis.Tests { - public class RedisServerFixture : IDisposable + public class RedisServerFixture : IAsyncLifetime, IDisposable where TStartup : class { public InProcessTestServer FirstServer { get; private set; } @@ -32,16 +35,24 @@ namespace Microsoft.AspNetCore.SignalR.StackExchangeRedis.Tests _logger = _loggerFactory.CreateLogger>(); Docker.Default.Start(_logger); - - FirstServer = StartServer(); - SecondServer = StartServer(); } - private InProcessTestServer StartServer() + public Task DisposeAsync() + { + return Task.CompletedTask; + } + + public async Task InitializeAsync() + { + FirstServer = await StartServer(); + SecondServer = await StartServer(); + } + + private async Task> StartServer() { try { - return new InProcessTestServer(_loggerFactory); + return await InProcessTestServer.StartServer(_loggerFactory); } catch (Exception ex) { diff --git a/src/SignalR/xunit.runner.json b/src/SignalR/xunit.runner.json index d25ea9035a..221b37e42d 100644 --- a/src/SignalR/xunit.runner.json +++ b/src/SignalR/xunit.runner.json @@ -2,4 +2,4 @@ "longRunningTestSeconds": 30, "diagnosticMessages": true, "maxParallelThreads": -1 -} +} \ No newline at end of file diff --git a/src/SiteExtensions/LoggingAggregate/src/Microsoft.AspNetCore.AzureAppServices.SiteExtension/Microsoft.AspNetCore.AzureAppServices.SiteExtension.csproj b/src/SiteExtensions/LoggingAggregate/src/Microsoft.AspNetCore.AzureAppServices.SiteExtension/Microsoft.AspNetCore.AzureAppServices.SiteExtension.csproj index 18a0870512..5f51761a31 100644 --- a/src/SiteExtensions/LoggingAggregate/src/Microsoft.AspNetCore.AzureAppServices.SiteExtension/Microsoft.AspNetCore.AzureAppServices.SiteExtension.csproj +++ b/src/SiteExtensions/LoggingAggregate/src/Microsoft.AspNetCore.AzureAppServices.SiteExtension/Microsoft.AspNetCore.AzureAppServices.SiteExtension.csproj @@ -15,7 +15,7 @@ true true true - true + true false true @@ -26,9 +26,11 @@ + + - - + + diff --git a/src/SiteExtensions/Runtime/Microsoft.AspNetCore.Runtime.SiteExtension.pkgproj b/src/SiteExtensions/Runtime/Microsoft.AspNetCore.Runtime.SiteExtension.pkgproj index 1e9d628258..6b7e0dc606 100644 --- a/src/SiteExtensions/Runtime/Microsoft.AspNetCore.Runtime.SiteExtension.pkgproj +++ b/src/SiteExtensions/Runtime/Microsoft.AspNetCore.Runtime.SiteExtension.pkgproj @@ -12,7 +12,7 @@ $(TargetRuntimeIdentifier) true $(RedistSharedFrameworkLayoutRoot) - true + true true diff --git a/src/Testing/Directory.Build.props b/src/Testing/Directory.Build.props new file mode 100644 index 0000000000..b49dba01d9 --- /dev/null +++ b/src/Testing/Directory.Build.props @@ -0,0 +1,9 @@ + + + + + + true + false + + diff --git a/src/Testing/src/AssemblyTestLog.cs b/src/Testing/src/AssemblyTestLog.cs new file mode 100644 index 0000000000..f5a5314a85 --- /dev/null +++ b/src/Testing/src/AssemblyTestLog.cs @@ -0,0 +1,307 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Text; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Serilog; +using Serilog.Core; +using Serilog.Events; +using Serilog.Extensions.Logging; +using Xunit.Abstractions; +using ILogger = Microsoft.Extensions.Logging.ILogger; + +namespace Microsoft.AspNetCore.Testing +{ + public class AssemblyTestLog : IDisposable + { + private static readonly string MaxPathLengthEnvironmentVariableName = "ASPNETCORE_TEST_LOG_MAXPATH"; + private static readonly string LogFileExtension = ".log"; + private static readonly int MaxPathLength = GetMaxPathLength(); + + private static readonly object _lock = new object(); + private static readonly Dictionary _logs = new Dictionary(); + + private readonly ILoggerFactory _globalLoggerFactory; + private readonly ILogger _globalLogger; + private readonly string _baseDirectory; + private readonly Assembly _assembly; + private readonly IServiceProvider _serviceProvider; + + private static int GetMaxPathLength() + { + var maxPathString = Environment.GetEnvironmentVariable(MaxPathLengthEnvironmentVariableName); + var defaultMaxPath = 245; + return string.IsNullOrEmpty(maxPathString) ? defaultMaxPath : int.Parse(maxPathString); + } + + private AssemblyTestLog(ILoggerFactory globalLoggerFactory, ILogger globalLogger, string baseDirectory, Assembly assembly, IServiceProvider serviceProvider) + { + _globalLoggerFactory = globalLoggerFactory; + _globalLogger = globalLogger; + _baseDirectory = baseDirectory; + _assembly = assembly; + _serviceProvider = serviceProvider; + } + + public IDisposable StartTestLog(ITestOutputHelper output, string className, out ILoggerFactory loggerFactory, [CallerMemberName] string testName = null) => + StartTestLog(output, className, out loggerFactory, LogLevel.Debug, testName); + + public IDisposable StartTestLog(ITestOutputHelper output, string className, out ILoggerFactory loggerFactory, LogLevel minLogLevel, [CallerMemberName] string testName = null) => + StartTestLog(output, className, out loggerFactory, minLogLevel, out var _, out var _, testName); + + internal IDisposable StartTestLog(ITestOutputHelper output, string className, out ILoggerFactory loggerFactory, LogLevel minLogLevel, out string resolvedTestName, out string logOutputDirectory, [CallerMemberName] string testName = null) + { + var logStart = DateTimeOffset.UtcNow; + var serviceProvider = CreateLoggerServices(output, className, minLogLevel, out resolvedTestName, out logOutputDirectory, testName, logStart); + var factory = serviceProvider.GetRequiredService(); + loggerFactory = factory; + var logger = loggerFactory.CreateLogger("TestLifetime"); + + var stopwatch = Stopwatch.StartNew(); + + var scope = logger.BeginScope("Test: {testName}", testName); + + _globalLogger.LogInformation("Starting test {testName}", testName); + logger.LogInformation("Starting test {testName} at {logStart}", testName, logStart.ToString("s")); + + return new Disposable(() => + { + stopwatch.Stop(); + _globalLogger.LogInformation("Finished test {testName} in {duration}s", testName, stopwatch.Elapsed.TotalSeconds); + logger.LogInformation("Finished test {testName} in {duration}s", testName, stopwatch.Elapsed.TotalSeconds); + scope.Dispose(); + factory.Dispose(); + (serviceProvider as IDisposable)?.Dispose(); + }); + } + + public ILoggerFactory CreateLoggerFactory(ITestOutputHelper output, string className, [CallerMemberName] string testName = null, DateTimeOffset? logStart = null) + => CreateLoggerFactory(output, className, LogLevel.Trace, testName, logStart); + + public ILoggerFactory CreateLoggerFactory(ITestOutputHelper output, string className, LogLevel minLogLevel, [CallerMemberName] string testName = null, DateTimeOffset? logStart = null) + => CreateLoggerServices(output, className, minLogLevel, out var _, out var _, testName, logStart).GetRequiredService(); + + public IServiceProvider CreateLoggerServices(ITestOutputHelper output, string className, LogLevel minLogLevel, out string normalizedTestName, [CallerMemberName] string testName = null, DateTimeOffset? logStart = null) + => CreateLoggerServices(output, className, minLogLevel, out normalizedTestName, out var _, testName, logStart); + + public IServiceProvider CreateLoggerServices(ITestOutputHelper output, string className, LogLevel minLogLevel, out string normalizedTestName, out string logOutputDirectory, [CallerMemberName] string testName = null, DateTimeOffset? logStart = null) + { + normalizedTestName = string.Empty; + logOutputDirectory = string.Empty; + var assemblyName = _assembly.GetName().Name; + + // Try to shorten the class name using the assembly name + if (className.StartsWith(assemblyName + ".")) + { + className = className.Substring(assemblyName.Length + 1); + } + + SerilogLoggerProvider serilogLoggerProvider = null; + if (!string.IsNullOrEmpty(_baseDirectory)) + { + logOutputDirectory = Path.Combine(_baseDirectory, className); + testName = TestFileOutputContext.RemoveIllegalFileChars(testName); + + if (logOutputDirectory.Length + testName.Length + LogFileExtension.Length >= MaxPathLength) + { + _globalLogger.LogWarning($"Test name {testName} is too long. Please shorten test name."); + + // Shorten the test name by removing the middle portion of the testname + var testNameLength = MaxPathLength - logOutputDirectory.Length - LogFileExtension.Length; + + if (testNameLength <= 0) + { + throw new InvalidOperationException("Output file path could not be constructed due to max path length restrictions. Please shorten test assembly, class or method names."); + } + + testName = testName.Substring(0, testNameLength / 2) + testName.Substring(testName.Length - testNameLength / 2, testNameLength / 2); + + _globalLogger.LogWarning($"To prevent long paths test name was shortened to {testName}."); + } + + var testOutputFile = Path.Combine(logOutputDirectory, $"{testName}{LogFileExtension}"); + + if (File.Exists(testOutputFile)) + { + _globalLogger.LogWarning($"Output log file {testOutputFile} already exists. Please try to keep log file names unique."); + + for (var i = 0; i < 1000; i++) + { + testOutputFile = Path.Combine(logOutputDirectory, $"{testName}.{i}{LogFileExtension}"); + + if (!File.Exists(testOutputFile)) + { + _globalLogger.LogWarning($"To resolve log file collision, the enumerated file {testOutputFile} will be used."); + testName = $"{testName}.{i}"; + break; + } + } + } + + normalizedTestName = testName; + serilogLoggerProvider = ConfigureFileLogging(testOutputFile, logStart); + } + + var serviceCollection = new ServiceCollection(); + serviceCollection.AddLogging(builder => + { + builder.SetMinimumLevel(minLogLevel); + + if (output != null) + { + builder.AddXunit(output, minLogLevel, logStart); + } + + if (serilogLoggerProvider != null) + { + // Use a factory so that the container will dispose it + builder.Services.AddSingleton(_ => serilogLoggerProvider); + } + }); + + return serviceCollection.BuildServiceProvider(); + } + + // For back compat + public static AssemblyTestLog Create(string assemblyName, string baseDirectory) + => Create(Assembly.Load(new AssemblyName(assemblyName)), baseDirectory); + + public static AssemblyTestLog Create(Assembly assembly, string baseDirectory) + { + var logStart = DateTimeOffset.UtcNow; + SerilogLoggerProvider serilogLoggerProvider = null; + if (!string.IsNullOrEmpty(baseDirectory)) + { + baseDirectory = TestFileOutputContext.GetAssemblyBaseDirectory(assembly, baseDirectory); + var globalLogFileName = Path.Combine(baseDirectory, "global.log"); + serilogLoggerProvider = ConfigureFileLogging(globalLogFileName, logStart); + } + + var serviceCollection = new ServiceCollection(); + + serviceCollection.AddLogging(builder => + { + // Global logging, when it's written, is expected to be outputted. So set the log level to minimum. + builder.SetMinimumLevel(LogLevel.Trace); + + if (serilogLoggerProvider != null) + { + // Use a factory so that the container will dispose it + builder.Services.AddSingleton(_ => serilogLoggerProvider); + } + }); + + var serviceProvider = serviceCollection.BuildServiceProvider(); + var loggerFactory = serviceProvider.GetRequiredService(); + + var logger = loggerFactory.CreateLogger("GlobalTestLog"); + logger.LogInformation("Global Test Logging initialized at {logStart}. " + + "Configure the output directory via 'LoggingTestingFileLoggingDirectory' MSBuild property " + + "or set 'LoggingTestingDisableFileLogging' to 'true' to disable file logging.", + logStart.ToString("s")); + return new AssemblyTestLog(loggerFactory, logger, baseDirectory, assembly, serviceProvider); + } + + public static AssemblyTestLog ForAssembly(Assembly assembly) + { + lock (_lock) + { + if (!_logs.TryGetValue(assembly, out var log)) + { + var baseDirectory = TestFileOutputContext.GetOutputDirectory(assembly); + + log = Create(assembly, baseDirectory); + _logs[assembly] = log; + + // Try to clear previous logs, continue if it fails. + var assemblyBaseDirectory = TestFileOutputContext.GetAssemblyBaseDirectory(assembly); + if (!string.IsNullOrEmpty(assemblyBaseDirectory) && !TestFileOutputContext.GetPreserveExistingLogsInOutput(assembly)) + { + try + { + Directory.Delete(assemblyBaseDirectory, recursive: true); + } + catch { } + } + } + return log; + } + } + + private static TestFrameworkFileLoggerAttribute GetFileLoggerAttribute(Assembly assembly) + => assembly.GetCustomAttribute() + ?? throw new InvalidOperationException($"No {nameof(TestFrameworkFileLoggerAttribute)} found on the assembly {assembly.GetName().Name}. " + + "The attribute is added via msbuild properties of the Microsoft.Extensions.Logging.Testing. " + + "Please ensure the msbuild property is imported or a direct reference to Microsoft.Extensions.Logging.Testing is added."); + + private static SerilogLoggerProvider ConfigureFileLogging(string fileName, DateTimeOffset? logStart) + { + var dir = Path.GetDirectoryName(fileName); + if (!Directory.Exists(dir)) + { + Directory.CreateDirectory(dir); + } + + if (File.Exists(fileName)) + { + File.Delete(fileName); + } + + var serilogger = new LoggerConfiguration() + .Enrich.FromLogContext() + .Enrich.With(new AssemblyLogTimestampOffsetEnricher(logStart)) + .MinimumLevel.Verbose() + .WriteTo.File(fileName, outputTemplate: "[{TimestampOffset}] [{SourceContext}] [{Level}] {Message:l}{NewLine}{Exception}", flushToDiskInterval: TimeSpan.FromSeconds(1), shared: true) + .CreateLogger(); + return new SerilogLoggerProvider(serilogger, dispose: true); + } + + public void Dispose() + { + (_serviceProvider as IDisposable)?.Dispose(); + _globalLoggerFactory.Dispose(); + } + + private class AssemblyLogTimestampOffsetEnricher : ILogEventEnricher + { + private DateTimeOffset? _logStart; + + public AssemblyLogTimestampOffsetEnricher(DateTimeOffset? logStart) + { + _logStart = logStart; + } + + public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) + => logEvent.AddPropertyIfAbsent( + propertyFactory.CreateProperty( + "TimestampOffset", + _logStart.HasValue + ? $"{(DateTimeOffset.UtcNow - _logStart.Value).TotalSeconds.ToString("N3")}s" + : DateTimeOffset.UtcNow.ToString("s"))); + } + + private class Disposable : IDisposable + { + private Action _action; + + public Disposable(Action action) + { + _action = action; + } + + public void Dispose() + { + _action(); + } + } + } +} diff --git a/src/Testing/src/CollectDumpAttribute.cs b/src/Testing/src/CollectDumpAttribute.cs new file mode 100644 index 0000000000..24567013d6 --- /dev/null +++ b/src/Testing/src/CollectDumpAttribute.cs @@ -0,0 +1,39 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Diagnostics; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.AspNetCore.Testing +{ + /// + /// Capture the memory dump upon test failure. + /// + /// + /// This currently only works in Windows environments + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] + public class CollectDumpAttribute : Attribute, ITestMethodLifecycle + { + public Task OnTestStartAsync(TestContext context, CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + + public Task OnTestEndAsync(TestContext context, Exception exception, CancellationToken cancellationToken) + { + if (exception != null) + { + var path = Path.Combine(context.FileOutput.TestClassOutputDirectory, context.FileOutput.GetUniqueFileName(context.FileOutput.TestName, ".dmp")); + var process = Process.GetCurrentProcess(); + DumpCollector.Collect(process, path); + } + + return Task.CompletedTask; + } + } +} diff --git a/src/Testing/src/CultureReplacer.cs b/src/Testing/src/CultureReplacer.cs new file mode 100644 index 0000000000..51e35e8354 --- /dev/null +++ b/src/Testing/src/CultureReplacer.cs @@ -0,0 +1,79 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Globalization; +using System.Threading; +using Xunit; + +namespace Microsoft.AspNetCore.Testing +{ + public class CultureReplacer : IDisposable + { + private const string _defaultCultureName = "en-GB"; + private const string _defaultUICultureName = "en-US"; + private static readonly CultureInfo _defaultCulture = new CultureInfo(_defaultCultureName); + private readonly CultureInfo _originalCulture; + private readonly CultureInfo _originalUICulture; + private readonly long _threadId; + + // Culture => Formatting of dates/times/money/etc, defaults to en-GB because en-US is the same as InvariantCulture + // We want to be able to find issues where the InvariantCulture is used, but a specific culture should be. + // + // UICulture => Language + public CultureReplacer(string culture = _defaultCultureName, string uiCulture = _defaultUICultureName) + : this(new CultureInfo(culture), new CultureInfo(uiCulture)) + { + } + + public CultureReplacer(CultureInfo culture, CultureInfo uiCulture) + { + _originalCulture = CultureInfo.CurrentCulture; + _originalUICulture = CultureInfo.CurrentUICulture; + _threadId = Thread.CurrentThread.ManagedThreadId; + CultureInfo.CurrentCulture = culture; + CultureInfo.CurrentUICulture = uiCulture; + } + + /// + /// The name of the culture that is used as the default value for CultureInfo.DefaultThreadCurrentCulture when CultureReplacer is used. + /// + public static string DefaultCultureName + { + get { return _defaultCultureName; } + } + + /// + /// The name of the culture that is used as the default value for [Thread.CurrentThread(NET45)/CultureInfo(K10)].CurrentUICulture when CultureReplacer is used. + /// + public static string DefaultUICultureName + { + get { return _defaultUICultureName; } + } + + /// + /// The culture that is used as the default value for [Thread.CurrentThread(NET45)/CultureInfo(K10)].CurrentCulture when CultureReplacer is used. + /// + public static CultureInfo DefaultCulture + { + get { return _defaultCulture; } + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool disposing) + { + if (disposing) + { + Assert.True(Thread.CurrentThread.ManagedThreadId == _threadId, + "The current thread is not the same as the thread invoking the constructor. This should never happen."); + CultureInfo.CurrentCulture = _originalCulture; + CultureInfo.CurrentUICulture = _originalUICulture; + } + } + } +} diff --git a/src/Testing/src/DumpCollector/DumpCollector.Windows.cs b/src/Testing/src/DumpCollector/DumpCollector.Windows.cs new file mode 100644 index 0000000000..7a71ac34cb --- /dev/null +++ b/src/Testing/src/DumpCollector/DumpCollector.Windows.cs @@ -0,0 +1,77 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Diagnostics; +using System.IO; +using System.Runtime.InteropServices; +using Microsoft.Win32.SafeHandles; + +namespace Microsoft.AspNetCore.Testing +{ + public static partial class DumpCollector + { + private static class Windows + { + internal static void Collect(Process process, string outputFile) + { + // Open the file for writing + using (var stream = new FileStream(outputFile, FileMode.Create, FileAccess.ReadWrite, FileShare.None)) + { + // Dump the process! + var exceptionInfo = new NativeMethods.MINIDUMP_EXCEPTION_INFORMATION(); + if (!NativeMethods.MiniDumpWriteDump(process.Handle, (uint)process.Id, stream.SafeFileHandle, NativeMethods.MINIDUMP_TYPE.MiniDumpWithFullMemory, ref exceptionInfo, IntPtr.Zero, IntPtr.Zero)) + { + var err = Marshal.GetHRForLastWin32Error(); + Marshal.ThrowExceptionForHR(err); + } + } + } + + private static class NativeMethods + { + [DllImport("Dbghelp.dll")] + public static extern bool MiniDumpWriteDump(IntPtr hProcess, uint ProcessId, SafeFileHandle hFile, MINIDUMP_TYPE DumpType, ref MINIDUMP_EXCEPTION_INFORMATION ExceptionParam, IntPtr UserStreamParam, IntPtr CallbackParam); + + [StructLayout(LayoutKind.Sequential, Pack = 4)] + public struct MINIDUMP_EXCEPTION_INFORMATION + { + public uint ThreadId; + public IntPtr ExceptionPointers; + public int ClientPointers; + } + + [Flags] + public enum MINIDUMP_TYPE : uint + { + MiniDumpNormal = 0, + MiniDumpWithDataSegs = 1 << 0, + MiniDumpWithFullMemory = 1 << 1, + MiniDumpWithHandleData = 1 << 2, + MiniDumpFilterMemory = 1 << 3, + MiniDumpScanMemory = 1 << 4, + MiniDumpWithUnloadedModules = 1 << 5, + MiniDumpWithIndirectlyReferencedMemory = 1 << 6, + MiniDumpFilterModulePaths = 1 << 7, + MiniDumpWithProcessThreadData = 1 << 8, + MiniDumpWithPrivateReadWriteMemory = 1 << 9, + MiniDumpWithoutOptionalData = 1 << 10, + MiniDumpWithFullMemoryInfo = 1 << 11, + MiniDumpWithThreadInfo = 1 << 12, + MiniDumpWithCodeSegs = 1 << 13, + MiniDumpWithoutAuxiliaryState = 1 << 14, + MiniDumpWithFullAuxiliaryState = 1 << 15, + MiniDumpWithPrivateWriteCopyMemory = 1 << 16, + MiniDumpIgnoreInaccessibleMemory = 1 << 17, + MiniDumpWithTokenInformation = 1 << 18, + MiniDumpWithModuleHeaders = 1 << 19, + MiniDumpFilterTriage = 1 << 20, + MiniDumpWithAvxXStateContext = 1 << 21, + MiniDumpWithIptTrace = 1 << 22, + MiniDumpValidTypeFlags = (-1) ^ ((~1) << 22) + } + } + } + } +} diff --git a/src/Testing/src/DumpCollector/DumpCollector.cs b/src/Testing/src/DumpCollector/DumpCollector.cs new file mode 100644 index 0000000000..64ddc80ec6 --- /dev/null +++ b/src/Testing/src/DumpCollector/DumpCollector.cs @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace Microsoft.AspNetCore.Testing +{ + public static partial class DumpCollector + { + public static void Collect(Process process, string fileName) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + Windows.Collect(process, fileName); + } + // No implementations yet for macOS and Linux + } + } +} diff --git a/src/Testing/src/ExceptionAssertions.cs b/src/Testing/src/ExceptionAssertions.cs new file mode 100644 index 0000000000..244cad5a37 --- /dev/null +++ b/src/Testing/src/ExceptionAssertions.cs @@ -0,0 +1,271 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Reflection; +using System.Threading.Tasks; +using Xunit; + +namespace Microsoft.AspNetCore.Testing +{ + // TODO: eventually want: public partial class Assert : Xunit.Assert + public static class ExceptionAssert + { + /// + /// Verifies that an exception of the given type (or optionally a derived type) is thrown. + /// + /// The type of the exception expected to be thrown + /// A delegate to the code to be tested + /// The exception that was thrown, when successful + public static TException Throws(Action testCode) + where TException : Exception + { + return VerifyException(RecordException(testCode)); + } + + /// + /// Verifies that an exception of the given type is thrown. + /// Also verifies that the exception message matches. + /// + /// The type of the exception expected to be thrown + /// A delegate to the code to be tested + /// The exception message to verify + /// The exception that was thrown, when successful + public static TException Throws(Action testCode, string exceptionMessage) + where TException : Exception + { + var ex = Throws(testCode); + VerifyExceptionMessage(ex, exceptionMessage); + return ex; + } + + /// + /// Verifies that an exception of the given type is thrown. + /// Also verifies that the exception message matches. + /// + /// The type of the exception expected to be thrown + /// A delegate to the code to be tested + /// The exception message to verify + /// The exception that was thrown, when successful + public static async Task ThrowsAsync(Func testCode, string exceptionMessage) + where TException : Exception + { + // The 'testCode' Task might execute asynchronously in a different thread making it hard to enforce the thread culture. + // The correct way to verify exception messages in such a scenario would be to run the task synchronously inside of a + // culture enforced block. + var ex = await Assert.ThrowsAsync(testCode); + VerifyExceptionMessage(ex, exceptionMessage); + return ex; + } + + /// + /// Verifies that an exception of the given type is thrown. + /// Also verified that the exception message matches. + /// + /// The type of the exception expected to be thrown + /// A delegate to the code to be tested + /// The exception message to verify + /// The exception that was thrown, when successful + public static TException Throws(Func testCode, string exceptionMessage) + where TException : Exception + { + return Throws(() => { testCode(); }, exceptionMessage); + } + + /// + /// Verifies that the code throws an . + /// + /// A delegate to the code to be tested + /// The name of the parameter that should throw the exception + /// The exception message to verify + /// The exception that was thrown, when successful + public static ArgumentException ThrowsArgument(Action testCode, string paramName, string exceptionMessage) + { + return ThrowsArgumentInternal(testCode, paramName, exceptionMessage); + } + + private static TException ThrowsArgumentInternal( + Action testCode, + string paramName, + string exceptionMessage) + where TException : ArgumentException + { + var ex = Throws(testCode); + if (paramName != null) + { + Assert.Equal(paramName, ex.ParamName); + } + VerifyExceptionMessage(ex, exceptionMessage, partialMatch: true); + return ex; + } + + /// + /// Verifies that the code throws an . + /// + /// A delegate to the code to be tested + /// The name of the parameter that should throw the exception + /// The exception message to verify + /// The exception that was thrown, when successful + public static Task ThrowsArgumentAsync(Func testCode, string paramName, string exceptionMessage) + { + return ThrowsArgumentAsyncInternal(testCode, paramName, exceptionMessage); + } + + private static async Task ThrowsArgumentAsyncInternal( + Func testCode, + string paramName, + string exceptionMessage) + where TException : ArgumentException + { + var ex = await Assert.ThrowsAsync(testCode); + if (paramName != null) + { + Assert.Equal(paramName, ex.ParamName); + } + VerifyExceptionMessage(ex, exceptionMessage, partialMatch: true); + return ex; + } + + /// + /// Verifies that the code throws an . + /// + /// A delegate to the code to be tested + /// The name of the parameter that should throw the exception + /// The exception that was thrown, when successful + public static ArgumentNullException ThrowsArgumentNull(Action testCode, string paramName) + { + var ex = Throws(testCode); + if (paramName != null) + { + Assert.Equal(paramName, ex.ParamName); + } + return ex; + } + + /// + /// Verifies that the code throws an ArgumentException with the expected message that indicates that the value cannot + /// be null or empty. + /// + /// A delegate to the code to be tested + /// The name of the parameter that should throw the exception + /// The exception that was thrown, when successful + public static ArgumentException ThrowsArgumentNullOrEmpty(Action testCode, string paramName) + { + return ThrowsArgumentInternal(testCode, paramName, "Value cannot be null or empty."); + } + + /// + /// Verifies that the code throws an ArgumentException with the expected message that indicates that the value cannot + /// be null or empty. + /// + /// A delegate to the code to be tested + /// The name of the parameter that should throw the exception + /// The exception that was thrown, when successful + public static Task ThrowsArgumentNullOrEmptyAsync(Func testCode, string paramName) + { + return ThrowsArgumentAsyncInternal(testCode, paramName, "Value cannot be null or empty."); + } + + /// + /// Verifies that the code throws an ArgumentNullException with the expected message that indicates that the value cannot + /// be null or empty string. + /// + /// A delegate to the code to be tested + /// The name of the parameter that should throw the exception + /// The exception that was thrown, when successful + public static ArgumentException ThrowsArgumentNullOrEmptyString(Action testCode, string paramName) + { + return ThrowsArgumentInternal(testCode, paramName, "Value cannot be null or an empty string."); + } + + /// + /// Verifies that the code throws an ArgumentNullException with the expected message that indicates that the value cannot + /// be null or empty string. + /// + /// A delegate to the code to be tested + /// The name of the parameter that should throw the exception + /// The exception that was thrown, when successful + public static Task ThrowsArgumentNullOrEmptyStringAsync(Func testCode, string paramName) + { + return ThrowsArgumentAsyncInternal(testCode, paramName, "Value cannot be null or an empty string."); + } + + /// + /// Verifies that the code throws an ArgumentOutOfRangeException (or optionally any exception which derives from it). + /// + /// A delegate to the code to be tested + /// The name of the parameter that should throw the exception + /// The exception message to verify + /// The actual value provided + /// The exception that was thrown, when successful + public static ArgumentOutOfRangeException ThrowsArgumentOutOfRange(Action testCode, string paramName, string exceptionMessage, object actualValue = null) + { + var ex = ThrowsArgumentInternal(testCode, paramName, exceptionMessage); + + if (paramName != null) + { + Assert.Equal(paramName, ex.ParamName); + } + + if (actualValue != null) + { + Assert.Equal(actualValue, ex.ActualValue); + } + + return ex; + } + + // We've re-implemented all the xUnit.net Throws code so that we can get this + // updated implementation of RecordException which silently unwraps any instances + // of AggregateException. In addition to unwrapping exceptions, this method ensures + // that tests are executed in with a known set of Culture and UICulture. This prevents + // tests from failing when executed on a non-English machine. + private static Exception RecordException(Action testCode) + { + try + { + using (new CultureReplacer()) + { + testCode(); + } + return null; + } + catch (Exception exception) + { + return UnwrapException(exception); + } + } + + private static Exception UnwrapException(Exception exception) + { + var aggEx = exception as AggregateException; + return aggEx != null ? aggEx.GetBaseException() : exception; + } + + private static TException VerifyException(Exception exception) + { + var tie = exception as TargetInvocationException; + if (tie != null) + { + exception = tie.InnerException; + } + Assert.NotNull(exception); + return Assert.IsAssignableFrom(exception); + } + + private static void VerifyExceptionMessage(Exception exception, string expectedMessage, bool partialMatch = false) + { + if (expectedMessage != null) + { + if (!partialMatch) + { + Assert.Equal(expectedMessage, exception.Message); + } + else + { + Assert.Contains(expectedMessage, exception.Message); + } + } + } + } +} \ No newline at end of file diff --git a/src/Testing/src/HelixQueues.cs b/src/Testing/src/HelixQueues.cs new file mode 100644 index 0000000000..ef5e4d1f5a --- /dev/null +++ b/src/Testing/src/HelixQueues.cs @@ -0,0 +1,18 @@ +namespace Microsoft.AspNetCore.Testing +{ + public static class HelixQueues + { + public const string Fedora28Amd64 = "Fedora.28." + Amd64Suffix; + public const string Fedora27Amd64 = "Fedora.27." + Amd64Suffix; + public const string Redhat7Amd64 = "Redhat.7." + Amd64Suffix; + public const string Debian9Amd64 = "Debian.9." + Amd64Suffix; + public const string Debian8Amd64 = "Debian.8." + Amd64Suffix; + public const string Centos7Amd64 = "Centos.7." + Amd64Suffix; + public const string Ubuntu1604Amd64 = "Ubuntu.1604." + Amd64Suffix; + public const string Ubuntu1810Amd64 = "Ubuntu.1810." + Amd64Suffix; + public const string macOS1012Amd64 = "OSX.1012." + Amd64Suffix; + public const string Windows10Amd64 = "Windows.10.Amd64.ClientRS4.VS2017.Open"; // Doesn't have the default suffix! + + private const string Amd64Suffix = "Amd64.Open"; + } +} diff --git a/src/Testing/src/HttpClientSlim.cs b/src/Testing/src/HttpClientSlim.cs new file mode 100644 index 0000000000..890ec2d160 --- /dev/null +++ b/src/Testing/src/HttpClientSlim.cs @@ -0,0 +1,192 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Globalization; +using System.IO; +using System.Net; +using System.Net.Http; +using System.Net.Security; +using System.Net.Sockets; +using System.Runtime.InteropServices; +using System.Security.Authentication; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.AspNetCore.Testing +{ + /// + /// Lightweight version of HttpClient implemented using Socket and SslStream. + /// + public static class HttpClientSlim + { + public static async Task GetStringAsync(string requestUri, bool validateCertificate = true) + => await GetStringAsync(new Uri(requestUri), validateCertificate).ConfigureAwait(false); + + public static async Task GetStringAsync(Uri requestUri, bool validateCertificate = true) + { + return await RetryRequest(async () => + { + using (var stream = await GetStream(requestUri, validateCertificate).ConfigureAwait(false)) + { + using (var writer = new StreamWriter(stream, Encoding.ASCII, bufferSize: 1024, leaveOpen: true)) + { + await writer.WriteAsync($"GET {requestUri.PathAndQuery} HTTP/1.0\r\n").ConfigureAwait(false); + await writer.WriteAsync($"Host: {GetHost(requestUri)}\r\n").ConfigureAwait(false); + await writer.WriteAsync("\r\n").ConfigureAwait(false); + } + + return await ReadResponse(stream).ConfigureAwait(false); + } + }); + } + + internal static string GetHost(Uri requestUri) + { + var authority = requestUri.Authority; + if (requestUri.HostNameType == UriHostNameType.IPv6) + { + // Make sure there's no % scope id. https://github.com/aspnet/KestrelHttpServer/issues/2637 + var address = IPAddress.Parse(requestUri.Host); + address = new IPAddress(address.GetAddressBytes()); // Drop scope Id. + if (requestUri.IsDefaultPort) + { + authority = $"[{address}]"; + } + else + { + authority = $"[{address}]:{requestUri.Port.ToString(CultureInfo.InvariantCulture)}"; + } + } + return authority; + } + + public static async Task PostAsync(string requestUri, HttpContent content, bool validateCertificate = true) + => await PostAsync(new Uri(requestUri), content, validateCertificate).ConfigureAwait(false); + + public static async Task PostAsync(Uri requestUri, HttpContent content, bool validateCertificate = true) + { + return await RetryRequest(async () => + { + using (var stream = await GetStream(requestUri, validateCertificate)) + { + using (var writer = new StreamWriter(stream, Encoding.ASCII, bufferSize: 1024, leaveOpen: true)) + { + await writer.WriteAsync($"POST {requestUri.PathAndQuery} HTTP/1.0\r\n").ConfigureAwait(false); + await writer.WriteAsync($"Host: {requestUri.Authority}\r\n").ConfigureAwait(false); + await writer.WriteAsync($"Content-Type: {content.Headers.ContentType}\r\n").ConfigureAwait(false); + await writer.WriteAsync($"Content-Length: {content.Headers.ContentLength}\r\n").ConfigureAwait(false); + await writer.WriteAsync("\r\n").ConfigureAwait(false); + } + + await content.CopyToAsync(stream).ConfigureAwait(false); + + return await ReadResponse(stream).ConfigureAwait(false); + } + }); + } + + private static async Task ReadResponse(Stream stream) + { + using (var reader = new StreamReader(stream, Encoding.ASCII, detectEncodingFromByteOrderMarks: true, + bufferSize: 1024, leaveOpen: true)) + { + var response = await reader.ReadToEndAsync().ConfigureAwait(false); + + var status = GetStatus(response); + new HttpResponseMessage(status).EnsureSuccessStatusCode(); + + var body = response.Substring(response.IndexOf("\r\n\r\n") + 4); + return body; + } + } + + private static async Task RetryRequest(Func> retryBlock) + { + var retryCount = 1; + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + retryCount = 3; + } + + for (var retry = 0; retry < retryCount; retry++) + { + try + { + return await retryBlock().ConfigureAwait(false); + } + catch (InvalidDataException) + { + if (retry == retryCount - 1) + { + throw; + } + } + } + + // This will never be hit. + throw new NotSupportedException(); + } + + private static HttpStatusCode GetStatus(string response) + { + var statusStart = response.IndexOf(' ') + 1; + var statusEnd = response.IndexOf(' ', statusStart) - 1; + var statusLength = statusEnd - statusStart + 1; + + if (statusLength < 1) + { + throw new InvalidDataException($"No StatusCode found in '{response}'"); + } + + return (HttpStatusCode)int.Parse(response.Substring(statusStart, statusLength)); + } + + private static async Task GetStream(Uri requestUri, bool validateCertificate) + { + var socket = await GetSocket(requestUri); + var stream = new NetworkStream(socket, ownsSocket: true); + + if (requestUri.Scheme.Equals("https", StringComparison.OrdinalIgnoreCase)) + { + var sslStream = new SslStream(stream, leaveInnerStreamOpen: false, userCertificateValidationCallback: + validateCertificate ? null : (RemoteCertificateValidationCallback)((a, b, c, d) => true)); + + await sslStream.AuthenticateAsClientAsync(requestUri.Host, clientCertificates: null, + enabledSslProtocols: SslProtocols.Tls11 | SslProtocols.Tls12, + checkCertificateRevocation: validateCertificate).ConfigureAwait(false); + return sslStream; + } + else + { + return stream; + } + } + + public static async Task GetSocket(Uri requestUri) + { + var tcs = new TaskCompletionSource(); + + var socketArgs = new SocketAsyncEventArgs(); + socketArgs.RemoteEndPoint = new DnsEndPoint(requestUri.DnsSafeHost, requestUri.Port); + socketArgs.Completed += (s, e) => tcs.TrySetResult(e.ConnectSocket); + + // Must use static ConnectAsync(), since instance Connect() does not support DNS names on OSX/Linux. + if (Socket.ConnectAsync(SocketType.Stream, ProtocolType.Tcp, socketArgs)) + { + await tcs.Task.ConfigureAwait(false); + } + + var socket = socketArgs.ConnectSocket; + + if (socket == null) + { + throw new SocketException((int)socketArgs.SocketError); + } + else + { + return socket; + } + } + } +} diff --git a/src/Testing/src/ITestMethodLifecycle.cs b/src/Testing/src/ITestMethodLifecycle.cs new file mode 100644 index 0000000000..d22779b6dd --- /dev/null +++ b/src/Testing/src/ITestMethodLifecycle.cs @@ -0,0 +1,23 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.AspNetCore.Testing +{ + /// + /// Defines a lifecycle for attributes or classes that want to know about tests starting + /// or ending. Implement this on a test class, or attribute at the method/class/assembly level. + /// + /// + /// Requires defining as the test framework. + /// + public interface ITestMethodLifecycle + { + Task OnTestStartAsync(TestContext context, CancellationToken cancellationToken); + + Task OnTestEndAsync(TestContext context, Exception exception, CancellationToken cancellationToken); + } +} diff --git a/src/Testing/src/LoggedTest/ILoggedTest.cs b/src/Testing/src/LoggedTest/ILoggedTest.cs new file mode 100644 index 0000000000..8a90acd5cd --- /dev/null +++ b/src/Testing/src/LoggedTest/ILoggedTest.cs @@ -0,0 +1,26 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Reflection; +using Microsoft.AspNetCore.Testing; +using Microsoft.Extensions.Logging; +using Xunit.Abstractions; + +namespace Microsoft.AspNetCore.Testing +{ + public interface ILoggedTest : IDisposable + { + ILogger Logger { get; } + + ILoggerFactory LoggerFactory { get; } + + ITestOutputHelper TestOutputHelper { get; } + + // For back compat + IDisposable StartLog(out ILoggerFactory loggerFactory, LogLevel minLogLevel, string testName); + + void Initialize(TestContext context, MethodInfo methodInfo, object[] testMethodArguments, ITestOutputHelper testOutputHelper); + } +} diff --git a/src/Testing/src/LoggedTest/LoggedTest.cs b/src/Testing/src/LoggedTest/LoggedTest.cs new file mode 100644 index 0000000000..2b6a18dfc7 --- /dev/null +++ b/src/Testing/src/LoggedTest/LoggedTest.cs @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Reflection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Testing; +using Xunit.Abstractions; + +namespace Microsoft.AspNetCore.Testing +{ + public class LoggedTest : LoggedTestBase + { + // Obsolete but keeping for back compat + public LoggedTest(ITestOutputHelper output = null) : base (output) { } + + public ITestSink TestSink { get; set; } + + public override void Initialize(TestContext context, MethodInfo methodInfo, object[] testMethodArguments, ITestOutputHelper testOutputHelper) + { + base.Initialize(context, methodInfo, testMethodArguments, testOutputHelper); + + TestSink = new TestSink(); + LoggerFactory.AddProvider(new TestLoggerProvider(TestSink)); + } + } +} diff --git a/src/Testing/src/LoggedTest/LoggedTestBase.cs b/src/Testing/src/LoggedTest/LoggedTestBase.cs new file mode 100644 index 0000000000..aa48eb8d51 --- /dev/null +++ b/src/Testing/src/LoggedTest/LoggedTestBase.cs @@ -0,0 +1,124 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.ExceptionServices; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Testing; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Testing; +using Serilog; +using Xunit.Abstractions; + +namespace Microsoft.AspNetCore.Testing +{ + public class LoggedTestBase : ILoggedTest, ITestMethodLifecycle + { + private ExceptionDispatchInfo _initializationException; + + private IDisposable _testLog; + + // Obsolete but keeping for back compat + public LoggedTestBase(ITestOutputHelper output = null) + { + TestOutputHelper = output; + } + + protected TestContext Context { get; private set; } + + // Internal for testing + internal string ResolvedTestClassName { get; set; } + + public string ResolvedLogOutputDirectory { get; set; } + + public string ResolvedTestMethodName { get; set; } + + public Microsoft.Extensions.Logging.ILogger Logger { get; set; } + + public ILoggerFactory LoggerFactory { get; set; } + + public ITestOutputHelper TestOutputHelper { get; set; } + + public void AddTestLogging(IServiceCollection services) => services.AddSingleton(LoggerFactory); + + // For back compat + public IDisposable StartLog(out ILoggerFactory loggerFactory, [CallerMemberName] string testName = null) => StartLog(out loggerFactory, LogLevel.Debug, testName); + + // For back compat + public IDisposable StartLog(out ILoggerFactory loggerFactory, LogLevel minLogLevel, [CallerMemberName] string testName = null) + { + return AssemblyTestLog.ForAssembly(GetType().GetTypeInfo().Assembly).StartTestLog(TestOutputHelper, GetType().FullName, out loggerFactory, minLogLevel, testName); + } + + public virtual void Initialize(TestContext context, MethodInfo methodInfo, object[] testMethodArguments, ITestOutputHelper testOutputHelper) + { + try + { + TestOutputHelper = testOutputHelper; + + var classType = GetType(); + var logLevelAttribute = methodInfo.GetCustomAttribute() + ?? methodInfo.DeclaringType.GetCustomAttribute() + ?? methodInfo.DeclaringType.Assembly.GetCustomAttribute(); + + // internal for testing + ResolvedTestClassName = context.FileOutput.TestClassName; + + _testLog = AssemblyTestLog + .ForAssembly(classType.GetTypeInfo().Assembly) + .StartTestLog( + TestOutputHelper, + context.FileOutput.TestClassName, + out var loggerFactory, + logLevelAttribute?.LogLevel ?? LogLevel.Debug, + out var resolvedTestName, + out var logDirectory, + context.FileOutput.TestName); + + ResolvedLogOutputDirectory = logDirectory; + ResolvedTestMethodName = resolvedTestName; + + LoggerFactory = loggerFactory; + Logger = loggerFactory.CreateLogger(classType); + } + catch (Exception e) + { + _initializationException = ExceptionDispatchInfo.Capture(e); + } + } + + public virtual void Dispose() + { + if (_testLog == null) + { + // It seems like sometimes the MSBuild goop that adds the test framework can end up in a bad state and not actually add it + // Not sure yet why that happens but the exception isn't clear so I'm adding this error so we can detect it better. + // -anurse + throw new InvalidOperationException("LoggedTest base class was used but nothing initialized it! The test framework may not be enabled. Try cleaning your 'obj' directory."); + } + + _initializationException?.Throw(); + _testLog.Dispose(); + } + + Task ITestMethodLifecycle.OnTestStartAsync(TestContext context, CancellationToken cancellationToken) + { + + Context = context; + + Initialize(context, context.TestMethod, context.MethodArguments, context.Output); + return Task.CompletedTask; + } + + Task ITestMethodLifecycle.OnTestEndAsync(TestContext context, Exception exception, CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + } +} diff --git a/src/Testing/src/Microsoft.AspNetCore.Testing.csproj b/src/Testing/src/Microsoft.AspNetCore.Testing.csproj new file mode 100644 index 0000000000..5ddad7b645 --- /dev/null +++ b/src/Testing/src/Microsoft.AspNetCore.Testing.csproj @@ -0,0 +1,53 @@ + + + + Various helpers for writing tests that use ASP.NET Core. + netstandard2.0;net461 + $(NoWarn);CS1591 + true + aspnetcore + + false + true + true + + true + + + + + + + + + + + + + + + + + + + + + + + + + + + True + contentFiles\cs\netstandard2.0\ + + + + diff --git a/src/Testing/src/RepeatAttribute.cs b/src/Testing/src/RepeatAttribute.cs new file mode 100644 index 0000000000..7bf3073734 --- /dev/null +++ b/src/Testing/src/RepeatAttribute.cs @@ -0,0 +1,27 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.ComponentModel; + +namespace Microsoft.AspNetCore.Testing +{ + /// + /// Runs a test multiple times to stress flaky tests that are believed to be fixed. + /// This can be used on an assembly, class, or method name. Requires using the AspNetCore test framework. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Assembly, AllowMultiple = false)] + public class RepeatAttribute : Attribute + { + public RepeatAttribute(int runCount = 10) + { + RunCount = runCount; + } + + /// + /// The number of times to run a test. + /// + public int RunCount { get; } + } +} diff --git a/src/Testing/src/RepeatContext.cs b/src/Testing/src/RepeatContext.cs new file mode 100644 index 0000000000..d76a0f177e --- /dev/null +++ b/src/Testing/src/RepeatContext.cs @@ -0,0 +1,27 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Threading; + +namespace Microsoft.AspNetCore.Testing +{ + public class RepeatContext + { + private static AsyncLocal _current = new AsyncLocal(); + + public static RepeatContext Current + { + get => _current.Value; + internal set => _current.Value = value; + } + + public RepeatContext(int limit) + { + Limit = limit; + } + + public int Limit { get; } + + public int CurrentIteration { get; set; } + } +} diff --git a/src/Testing/src/ReplaceCulture.cs b/src/Testing/src/ReplaceCulture.cs new file mode 100644 index 0000000000..9580bfd0da --- /dev/null +++ b/src/Testing/src/ReplaceCulture.cs @@ -0,0 +1,70 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Globalization; +using System.Reflection; +using Xunit.Sdk; + +namespace Microsoft.AspNetCore.Testing +{ + /// + /// Replaces the current culture and UI culture for the test. + /// + [AttributeUsage(AttributeTargets.Method)] + public class ReplaceCultureAttribute : BeforeAfterTestAttribute + { + private const string _defaultCultureName = "en-GB"; + private const string _defaultUICultureName = "en-US"; + private CultureInfo _originalCulture; + private CultureInfo _originalUICulture; + + /// + /// Replaces the current culture and UI culture to en-GB and en-US respectively. + /// + public ReplaceCultureAttribute() : + this(_defaultCultureName, _defaultUICultureName) + { + } + + /// + /// Replaces the current culture and UI culture based on specified values. + /// + public ReplaceCultureAttribute(string currentCulture, string currentUICulture) + { + Culture = new CultureInfo(currentCulture); + UICulture = new CultureInfo(currentUICulture); + } + + /// + /// The for the test. Defaults to en-GB. + /// + /// + /// en-GB is used here as the default because en-US is equivalent to the InvariantCulture. We + /// want to be able to find bugs where we're accidentally relying on the Invariant instead of the + /// user's culture. + /// + public CultureInfo Culture { get; } + + /// + /// The for the test. Defaults to en-US. + /// + public CultureInfo UICulture { get; } + + public override void Before(MethodInfo methodUnderTest) + { + _originalCulture = CultureInfo.CurrentCulture; + _originalUICulture = CultureInfo.CurrentUICulture; + + CultureInfo.CurrentCulture = Culture; + CultureInfo.CurrentUICulture = UICulture; + } + + public override void After(MethodInfo methodUnderTest) + { + CultureInfo.CurrentCulture = _originalCulture; + CultureInfo.CurrentUICulture = _originalUICulture; + } + } +} + diff --git a/src/Testing/src/ShortClassNameAttribute.cs b/src/Testing/src/ShortClassNameAttribute.cs new file mode 100644 index 0000000000..6a36575d70 --- /dev/null +++ b/src/Testing/src/ShortClassNameAttribute.cs @@ -0,0 +1,17 @@ +// Copyright(c) .NET Foundation.All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.AspNetCore.Testing +{ + /// + /// Used to specify that should used the + /// unqualified class name. This is needed when a fully-qualified class name exceeds + /// max path for logging. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Assembly, AllowMultiple = false)] + public class ShortClassNameAttribute : Attribute + { + } +} diff --git a/src/Testing/src/TaskExtensions.cs b/src/Testing/src/TaskExtensions.cs new file mode 100644 index 0000000000..f99bf7361a --- /dev/null +++ b/src/Testing/src/TaskExtensions.cs @@ -0,0 +1,66 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.AspNetCore.Testing +{ + public static class TaskExtensions + { + public static async Task TimeoutAfter(this Task task, TimeSpan timeout, + [CallerFilePath] string filePath = null, + [CallerLineNumber] int lineNumber = default) + { + // Don't create a timer if the task is already completed + // or the debugger is attached + if (task.IsCompleted || Debugger.IsAttached) + { + return await task; + } + + var cts = new CancellationTokenSource(); + if (task == await Task.WhenAny(task, Task.Delay(timeout, cts.Token))) + { + cts.Cancel(); + return await task; + } + else + { + throw new TimeoutException(CreateMessage(timeout, filePath, lineNumber)); + } + } + + public static async Task TimeoutAfter(this Task task, TimeSpan timeout, + [CallerFilePath] string filePath = null, + [CallerLineNumber] int lineNumber = default) + { + // Don't create a timer if the task is already completed + // or the debugger is attached + if (task.IsCompleted || Debugger.IsAttached) + { + await task; + return; + } + + var cts = new CancellationTokenSource(); + if (task == await Task.WhenAny(task, Task.Delay(timeout, cts.Token))) + { + cts.Cancel(); + await task; + } + else + { + throw new TimeoutException(CreateMessage(timeout, filePath, lineNumber)); + } + } + + private static string CreateMessage(TimeSpan timeout, string filePath, int lineNumber) + => string.IsNullOrEmpty(filePath) + ? $"The operation timed out after reaching the limit of {timeout.TotalMilliseconds}ms." + : $"The operation at {filePath}:{lineNumber} timed out after reaching the limit of {timeout.TotalMilliseconds}ms."; + } +} diff --git a/src/Testing/src/TestContext.cs b/src/Testing/src/TestContext.cs new file mode 100644 index 0000000000..a702d71ecf --- /dev/null +++ b/src/Testing/src/TestContext.cs @@ -0,0 +1,44 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Reflection; +using Xunit.Abstractions; + +namespace Microsoft.AspNetCore.Testing +{ + /// + /// Provides access to contextual information about the running tests. Get access by + /// implementing . + /// + /// + /// Requires defining as the test framework. + /// + public sealed class TestContext + { + private Lazy _files; + + public TestContext( + Type testClass, + object[] constructorArguments, + MethodInfo testMethod, + object[] methodArguments, + ITestOutputHelper output) + { + TestClass = testClass; + ConstructorArguments = constructorArguments; + TestMethod = testMethod; + MethodArguments = methodArguments; + Output = output; + + _files = new Lazy(() => new TestFileOutputContext(this)); + } + + public Type TestClass { get; } + public MethodInfo TestMethod { get; } + public object[] ConstructorArguments { get; } + public object[] MethodArguments { get; } + public ITestOutputHelper Output { get; } + public TestFileOutputContext FileOutput => _files.Value; + } +} diff --git a/src/Testing/src/TestFileOutputContext.cs b/src/Testing/src/TestFileOutputContext.cs new file mode 100644 index 0000000000..fb79fd7bf7 --- /dev/null +++ b/src/Testing/src/TestFileOutputContext.cs @@ -0,0 +1,146 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; + +namespace Microsoft.AspNetCore.Testing +{ + /// + /// Provides access to file storage for the running test. Get access by + /// implementing , and accessing . + /// + /// + /// Requires defining as the test framework. + /// + public sealed class TestFileOutputContext + { + private static char[] InvalidFileChars = new char[] + { + '\"', '<', '>', '|', '\0', + (char)1, (char)2, (char)3, (char)4, (char)5, (char)6, (char)7, (char)8, (char)9, (char)10, + (char)11, (char)12, (char)13, (char)14, (char)15, (char)16, (char)17, (char)18, (char)19, (char)20, + (char)21, (char)22, (char)23, (char)24, (char)25, (char)26, (char)27, (char)28, (char)29, (char)30, + (char)31, ':', '*', '?', '\\', '/', ' ', (char)127 + }; + + private readonly TestContext _parent; + + public TestFileOutputContext(TestContext parent) + { + _parent = parent; + + TestName = GetTestMethodName(parent.TestMethod, parent.MethodArguments); + TestClassName = GetTestClassName(parent.TestClass); + + AssemblyOutputDirectory = GetAssemblyBaseDirectory(_parent.TestClass.Assembly); + if (!string.IsNullOrEmpty(AssemblyOutputDirectory)) + { + TestClassOutputDirectory = Path.Combine(AssemblyOutputDirectory, TestClassName); + } + } + + public string TestName { get; } + + public string TestClassName { get; } + + public string AssemblyOutputDirectory { get; } + + public string TestClassOutputDirectory { get; } + + public string GetUniqueFileName(string prefix, string extension) + { + if (prefix == null) + { + throw new ArgumentNullException(nameof(prefix)); + } + + if (extension != null && !extension.StartsWith(".", StringComparison.Ordinal)) + { + throw new ArgumentException("The extension must start with '.' if one is provided.", nameof(extension)); + } + + var path = Path.Combine(TestClassOutputDirectory, $"{prefix}{extension}"); + + var i = 1; + while (File.Exists(path)) + { + path = Path.Combine(TestClassOutputDirectory, $"{prefix}{i++}{extension}"); + } + + return path; + } + + // Gets the output directory without appending the TFM or assembly name. + public static string GetOutputDirectory(Assembly assembly) + { + var attribute = assembly.GetCustomAttributes().OfType().FirstOrDefault(); + return attribute?.BaseDirectory; + } + + public static string GetAssemblyBaseDirectory(Assembly assembly, string baseDirectory = null) + { + var attribute = assembly.GetCustomAttributes().OfType().FirstOrDefault(); + baseDirectory = baseDirectory ?? attribute?.BaseDirectory; + if (string.IsNullOrEmpty(baseDirectory)) + { + return string.Empty; + } + + return Path.Combine(baseDirectory, assembly.GetName().Name, attribute.TargetFramework); + } + + public static bool GetPreserveExistingLogsInOutput(Assembly assembly) + { + var attribute = assembly.GetCustomAttributes().OfType().FirstOrDefault(); + return attribute.PreserveExistingLogsInOutput; + } + + public static string GetTestClassName(Type type) + { + var shortNameAttribute = + type.GetCustomAttribute() ?? + type.Assembly.GetCustomAttribute(); + var name = shortNameAttribute == null ? type.FullName : type.Name; + + // Try to shorten the class name using the assembly name + var assemblyName = type.Assembly.GetName().Name; + if (name.StartsWith(assemblyName + ".")) + { + name = name.Substring(assemblyName.Length + 1); + } + + return name; + } + + public static string GetTestMethodName(MethodInfo method, object[] arguments) + { + var name = arguments.Aggregate(method.Name, (a, b) => $"{a}-{(b ?? "null")}"); + return RemoveIllegalFileChars(name); + } + + public static string RemoveIllegalFileChars(string s) + { + var sb = new StringBuilder(); + + foreach (var c in s) + { + if (InvalidFileChars.Contains(c)) + { + if (sb.Length > 0 && sb[sb.Length - 1] != '_') + { + sb.Append('_'); + } + } + else + { + sb.Append(c); + } + } + return sb.ToString(); + } + } +} diff --git a/src/Testing/src/TestFrameworkFileLoggerAttribute.cs b/src/Testing/src/TestFrameworkFileLoggerAttribute.cs new file mode 100644 index 0000000000..52fa979133 --- /dev/null +++ b/src/Testing/src/TestFrameworkFileLoggerAttribute.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; + +namespace Microsoft.AspNetCore.Testing +{ + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false)] + public class TestFrameworkFileLoggerAttribute : TestOutputDirectoryAttribute + { + public TestFrameworkFileLoggerAttribute(string preserveExistingLogsInOutput, string tfm, string baseDirectory = null) + : base(preserveExistingLogsInOutput, tfm, baseDirectory) + { + } + } +} diff --git a/src/Testing/src/TestOutputDirectoryAttribute.cs b/src/Testing/src/TestOutputDirectoryAttribute.cs new file mode 100644 index 0000000000..b1895c1d92 --- /dev/null +++ b/src/Testing/src/TestOutputDirectoryAttribute.cs @@ -0,0 +1,22 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.AspNetCore.Testing +{ + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false, Inherited = true)] + public class TestOutputDirectoryAttribute : Attribute + { + public TestOutputDirectoryAttribute(string preserveExistingLogsInOutput, string targetFramework, string baseDirectory = null) + { + TargetFramework = targetFramework; + BaseDirectory = baseDirectory; + PreserveExistingLogsInOutput = bool.Parse(preserveExistingLogsInOutput); + } + + public string BaseDirectory { get; } + public string TargetFramework { get; } + public bool PreserveExistingLogsInOutput { get; } + } +} diff --git a/src/Testing/src/TestPathUtilities.cs b/src/Testing/src/TestPathUtilities.cs new file mode 100644 index 0000000000..6d4449ca92 --- /dev/null +++ b/src/Testing/src/TestPathUtilities.cs @@ -0,0 +1,37 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; + +namespace Microsoft.AspNetCore.Testing +{ + [Obsolete("This API is obsolete and the pattern its usage encouraged should not be used anymore. See https://github.com/dotnet/extensions/issues/1697 for details.")] + public class TestPathUtilities + { + public static string GetRepoRootDirectory() + { + return GetSolutionRootDirectory("Extensions"); + } + + public static string GetSolutionRootDirectory(string solution) + { + var applicationBasePath = AppContext.BaseDirectory; + var directoryInfo = new DirectoryInfo(applicationBasePath); + + do + { + var projectFileInfo = new FileInfo(Path.Combine(directoryInfo.FullName, $"{solution}.sln")); + if (projectFileInfo.Exists) + { + return projectFileInfo.DirectoryName; + } + + directoryInfo = directoryInfo.Parent; + } + while (directoryInfo.Parent != null); + + throw new Exception($"Solution file {solution}.sln could not be found in {applicationBasePath} or its parent directories."); + } + } +} diff --git a/src/Testing/src/TestPlatformHelper.cs b/src/Testing/src/TestPlatformHelper.cs new file mode 100644 index 0000000000..2c13e08eb3 --- /dev/null +++ b/src/Testing/src/TestPlatformHelper.cs @@ -0,0 +1,23 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Runtime.InteropServices; + +namespace Microsoft.AspNetCore.Testing +{ + public static class TestPlatformHelper + { + public static bool IsMono => + Type.GetType("Mono.Runtime") != null; + + public static bool IsWindows => + RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + + public static bool IsLinux => + RuntimeInformation.IsOSPlatform(OSPlatform.Linux); + + public static bool IsMac => + RuntimeInformation.IsOSPlatform(OSPlatform.OSX); + } +} diff --git a/src/Testing/src/Tracing/CollectingEventListener.cs b/src/Testing/src/Tracing/CollectingEventListener.cs new file mode 100644 index 0000000000..d22a4996af --- /dev/null +++ b/src/Testing/src/Tracing/CollectingEventListener.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics.Tracing; +using System.Linq; + +namespace Microsoft.AspNetCore.Testing.Tracing +{ + public class CollectingEventListener : EventListener + { + private ConcurrentQueue _events = new ConcurrentQueue(); + + private object _lock = new object(); + + private Dictionary _existingSources = new Dictionary(StringComparer.OrdinalIgnoreCase); + private HashSet _requestedEventSources = new HashSet(); + + public void CollectFrom(string eventSourceName) + { + lock(_lock) + { + // Check if it's already been created + if(_existingSources.TryGetValue(eventSourceName, out var existingSource)) + { + // It has, so just enable it now + CollectFrom(existingSource); + } + else + { + // It hasn't, so queue this request for when it is created + _requestedEventSources.Add(eventSourceName); + } + } + } + + public void CollectFrom(EventSource eventSource) => EnableEvents(eventSource, EventLevel.Verbose, EventKeywords.All); + + public IReadOnlyList GetEventsWritten() => _events.ToArray(); + + protected override void OnEventSourceCreated(EventSource eventSource) + { + lock (_lock) + { + // Add this to the list of existing sources for future CollectEventsFrom requests. + _existingSources[eventSource.Name] = eventSource; + + // Check if we have a pending request to enable it + if (_requestedEventSources.Contains(eventSource.Name)) + { + CollectFrom(eventSource); + } + } + } + + protected override void OnEventWritten(EventWrittenEventArgs eventData) + { + _events.Enqueue(eventData); + } + } +} diff --git a/src/Testing/src/Tracing/EventAssert.cs b/src/Testing/src/Tracing/EventAssert.cs new file mode 100644 index 0000000000..b32fb36dad --- /dev/null +++ b/src/Testing/src/Tracing/EventAssert.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.Tracing; +using System.Linq; +using Xunit; + +namespace Microsoft.AspNetCore.Testing.Tracing +{ + public class EventAssert + { + private readonly int _expectedId; + private readonly string _expectedName; + private readonly EventLevel _expectedLevel; + private readonly IList<(string name, Action asserter)> _payloadAsserters = new List<(string, Action)>(); + + public EventAssert(int expectedId, string expectedName, EventLevel expectedLevel) + { + _expectedId = expectedId; + _expectedName = expectedName; + _expectedLevel = expectedLevel; + } + + public static void Collection(IEnumerable events, params EventAssert[] asserts) + { + Assert.Collection( + events, + asserts.Select(a => a.CreateAsserter()).ToArray()); + } + + public static EventAssert Event(int id, string name, EventLevel level) + { + return new EventAssert(id, name, level); + } + + public EventAssert Payload(string name, object expectedValue) => Payload(name, actualValue => Assert.Equal(expectedValue, actualValue)); + + public EventAssert Payload(string name, Action asserter) + { + _payloadAsserters.Add((name, asserter)); + return this; + } + + private Action CreateAsserter() => Execute; + + private void Execute(EventWrittenEventArgs evt) + { + Assert.Equal(_expectedId, evt.EventId); + Assert.Equal(_expectedName, evt.EventName); + Assert.Equal(_expectedLevel, evt.Level); + + Action CreateNameAsserter((string name, Action asserter) val) + { + return actualValue => Assert.Equal(val.name, actualValue); + } + + Assert.Collection(evt.PayloadNames, _payloadAsserters.Select(CreateNameAsserter).ToArray()); + Assert.Collection(evt.Payload, _payloadAsserters.Select(t => t.asserter).ToArray()); + } + } +} diff --git a/src/Testing/src/Tracing/EventSourceTestBase.cs b/src/Testing/src/Tracing/EventSourceTestBase.cs new file mode 100644 index 0000000000..721966d6c5 --- /dev/null +++ b/src/Testing/src/Tracing/EventSourceTestBase.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.Tracing; +using Xunit; + +namespace Microsoft.AspNetCore.Testing.Tracing +{ + // This collection attribute is what makes the "magic" happen. It forces xunit to run all tests that inherit from this + // base class sequentially, preventing conflicts (since EventSource/EventListener is a process-global concept). + [Collection(CollectionName)] + public abstract class EventSourceTestBase : IDisposable + { + public const string CollectionName = "Microsoft.AspNetCore.Testing.Tracing.EventSourceTestCollection"; + + private readonly CollectingEventListener _listener; + + public EventSourceTestBase() + { + _listener = new CollectingEventListener(); + } + + protected void CollectFrom(string eventSourceName) + { + _listener.CollectFrom(eventSourceName); + } + + protected void CollectFrom(EventSource eventSource) + { + _listener.CollectFrom(eventSource); + } + + protected IReadOnlyList GetEvents() => _listener.GetEventsWritten(); + + public void Dispose() + { + _listener.Dispose(); + } + } +} diff --git a/src/Testing/src/build/Microsoft.AspNetCore.Testing.props b/src/Testing/src/build/Microsoft.AspNetCore.Testing.props new file mode 100644 index 0000000000..239da937b8 --- /dev/null +++ b/src/Testing/src/build/Microsoft.AspNetCore.Testing.props @@ -0,0 +1,30 @@ + + + + $(RepositoryRoot) + $(ASPNETCORE_TEST_LOG_DIR) + $(RepoRoot)artifacts\log\ + + + + + true + false + + + + + <_Parameter1>Microsoft.AspNetCore.Testing.AspNetTestFramework + <_Parameter2>Microsoft.AspNetCore.Testing + + + + <_Parameter1>$(PreserveExistingLogsInOutput) + <_Parameter2>$(TargetFramework) + <_Parameter3 Condition="'$(LoggingTestingDisableFileLogging)' != 'true'">$(LoggingTestingFileLoggingDirectory) + + + + diff --git a/src/Testing/src/contentFiles/cs/netstandard2.0/EventSourceTestCollection.cs b/src/Testing/src/contentFiles/cs/netstandard2.0/EventSourceTestCollection.cs new file mode 100644 index 0000000000..0ed9e1a9a9 --- /dev/null +++ b/src/Testing/src/contentFiles/cs/netstandard2.0/EventSourceTestCollection.cs @@ -0,0 +1,10 @@ +namespace Microsoft.AspNetCore.Testing.Tracing +{ + // This file comes from Microsoft.AspNetCore.Testing and has to be defined in the test assembly. + // It enables EventSourceTestBase's parallel isolation functionality. + + [Xunit.CollectionDefinition(EventSourceTestBase.CollectionName, DisableParallelization = true)] + public class EventSourceTestCollection + { + } +} diff --git a/src/Testing/src/xunit/AspNetTestAssemblyRunner.cs b/src/Testing/src/xunit/AspNetTestAssemblyRunner.cs new file mode 100644 index 0000000000..48fbbbfa3b --- /dev/null +++ b/src/Testing/src/xunit/AspNetTestAssemblyRunner.cs @@ -0,0 +1,83 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Xunit.Abstractions; +using Xunit.Sdk; + +namespace Microsoft.AspNetCore.Testing +{ + public class AspNetTestAssemblyRunner : XunitTestAssemblyRunner + { + private readonly Dictionary _assemblyFixtureMappings = new Dictionary(); + + public AspNetTestAssemblyRunner( + ITestAssembly testAssembly, + IEnumerable testCases, + IMessageSink diagnosticMessageSink, + IMessageSink executionMessageSink, + ITestFrameworkExecutionOptions executionOptions) + : base(testAssembly, testCases, diagnosticMessageSink, executionMessageSink, executionOptions) + { + } + + protected override async Task AfterTestAssemblyStartingAsync() + { + await base.AfterTestAssemblyStartingAsync(); + + // Find all the AssemblyFixtureAttributes on the test assembly + Aggregator.Run(() => + { + var fixturesAttributes = ((IReflectionAssemblyInfo)TestAssembly.Assembly) + .Assembly + .GetCustomAttributes(typeof(AssemblyFixtureAttribute), false) + .Cast() + .ToList(); + + // Instantiate all the fixtures + foreach (var fixtureAttribute in fixturesAttributes) + { + var ctorWithDiagnostics = fixtureAttribute.FixtureType.GetConstructor(new[] { typeof(IMessageSink) }); + if (ctorWithDiagnostics != null) + { + _assemblyFixtureMappings[fixtureAttribute.FixtureType] = Activator.CreateInstance(fixtureAttribute.FixtureType, DiagnosticMessageSink); + } + else + { + _assemblyFixtureMappings[fixtureAttribute.FixtureType] = Activator.CreateInstance(fixtureAttribute.FixtureType); + } + } + }); + } + + protected override Task BeforeTestAssemblyFinishedAsync() + { + // Dispose fixtures + foreach (var disposable in _assemblyFixtureMappings.Values.OfType()) + { + Aggregator.Run(disposable.Dispose); + } + + return base.BeforeTestAssemblyFinishedAsync(); + } + + protected override Task RunTestCollectionAsync( + IMessageBus messageBus, + ITestCollection testCollection, + IEnumerable testCases, + CancellationTokenSource cancellationTokenSource) + => new AspNetTestCollectionRunner( + _assemblyFixtureMappings, + testCollection, + testCases, + DiagnosticMessageSink, + messageBus, + TestCaseOrderer, + new ExceptionAggregator(Aggregator), + cancellationTokenSource).RunAsync(); + } +} diff --git a/src/Testing/src/xunit/AspNetTestCaseRunner.cs b/src/Testing/src/xunit/AspNetTestCaseRunner.cs new file mode 100644 index 0000000000..42773db212 --- /dev/null +++ b/src/Testing/src/xunit/AspNetTestCaseRunner.cs @@ -0,0 +1,33 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Threading; +using Xunit.Abstractions; +using Xunit.Sdk; + +namespace Microsoft.AspNetCore.Testing +{ + internal class AspNetTestCaseRunner : XunitTestCaseRunner + { + public AspNetTestCaseRunner( + IXunitTestCase testCase, + string displayName, + string skipReason, + object[] constructorArguments, + object[] testMethodArguments, + IMessageBus messageBus, + ExceptionAggregator aggregator, + CancellationTokenSource cancellationTokenSource) + : base(testCase, displayName, skipReason, constructorArguments, testMethodArguments, messageBus, aggregator, cancellationTokenSource) + { + } + + protected override XunitTestRunner CreateTestRunner(ITest test, IMessageBus messageBus, Type testClass, object[] constructorArguments, MethodInfo testMethod, object[] testMethodArguments, string skipReason, IReadOnlyList beforeAfterAttributes, ExceptionAggregator aggregator, CancellationTokenSource cancellationTokenSource) + { + return new AspNetTestRunner(test, messageBus, testClass, constructorArguments, testMethod, testMethodArguments, skipReason, beforeAfterAttributes, aggregator, cancellationTokenSource); + } + } +} diff --git a/src/Testing/src/xunit/AspNetTestClassRunner.cs b/src/Testing/src/xunit/AspNetTestClassRunner.cs new file mode 100644 index 0000000000..bbefa37427 --- /dev/null +++ b/src/Testing/src/xunit/AspNetTestClassRunner.cs @@ -0,0 +1,44 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Xunit.Abstractions; +using Xunit.Sdk; + +namespace Microsoft.AspNetCore.Testing +{ + internal class AspNetTestClassRunner : XunitTestClassRunner + { + public AspNetTestClassRunner( + ITestClass testClass, + IReflectionTypeInfo @class, + IEnumerable testCases, + IMessageSink diagnosticMessageSink, + IMessageBus messageBus, + ITestCaseOrderer testCaseOrderer, + ExceptionAggregator aggregator, + CancellationTokenSource cancellationTokenSource, + IDictionary collectionFixtureMappings) + : base(testClass, @class, testCases, diagnosticMessageSink, messageBus, testCaseOrderer, aggregator, cancellationTokenSource, collectionFixtureMappings) + { + } + + protected override Task RunTestMethodAsync(ITestMethod testMethod, IReflectionMethodInfo method, IEnumerable testCases, object[] constructorArguments) + { + var runner = new AspNetTestMethodRunner( + testMethod, + Class, + method, + testCases, + DiagnosticMessageSink, + MessageBus, + new ExceptionAggregator(Aggregator), + CancellationTokenSource, + constructorArguments); + return runner.RunAsync(); + } + } +} diff --git a/src/Testing/src/xunit/AspNetTestCollectionRunner.cs b/src/Testing/src/xunit/AspNetTestCollectionRunner.cs new file mode 100644 index 0000000000..522cbd4624 --- /dev/null +++ b/src/Testing/src/xunit/AspNetTestCollectionRunner.cs @@ -0,0 +1,75 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Xunit.Abstractions; +using Xunit.Sdk; + +namespace Microsoft.AspNetCore.Testing +{ + public class AspNetTestCollectionRunner : XunitTestCollectionRunner + { + private readonly IDictionary _assemblyFixtureMappings; + private readonly IMessageSink _diagnosticMessageSink; + + public AspNetTestCollectionRunner( + Dictionary assemblyFixtureMappings, + ITestCollection testCollection, + IEnumerable testCases, + IMessageSink diagnosticMessageSink, + IMessageBus messageBus, + ITestCaseOrderer testCaseOrderer, + ExceptionAggregator aggregator, + CancellationTokenSource cancellationTokenSource) + : base(testCollection, testCases, diagnosticMessageSink, messageBus, testCaseOrderer, aggregator, cancellationTokenSource) + { + _assemblyFixtureMappings = assemblyFixtureMappings; + _diagnosticMessageSink = diagnosticMessageSink; + } + + protected override async Task AfterTestCollectionStartingAsync() + { + await base.AfterTestCollectionStartingAsync(); + + // note: We pass the assembly fixtures into the runner as ICollectionFixture<> - this seems to work OK without any + // drawbacks. It's reasonable that we could add IAssemblyFixture<> and related plumbing if it ever became required. + // + // The reason for assembly fixture is when we want to start/stop something as the project scope - tests can only be + // in one test collection at a time. + foreach (var mapping in _assemblyFixtureMappings) + { + CollectionFixtureMappings.Add(mapping.Key, mapping.Value); + } + } + + protected override Task BeforeTestCollectionFinishedAsync() + { + // We need to remove the assembly fixtures so they won't get disposed. + foreach (var mapping in _assemblyFixtureMappings) + { + CollectionFixtureMappings.Remove(mapping.Key); + } + + return base.BeforeTestCollectionFinishedAsync(); + } + + protected override Task RunTestClassAsync(ITestClass testClass, IReflectionTypeInfo @class, IEnumerable testCases) + { + var runner = new AspNetTestClassRunner( + testClass, + @class, + testCases, + DiagnosticMessageSink, + MessageBus, + TestCaseOrderer, + new ExceptionAggregator(Aggregator), + CancellationTokenSource, + CollectionFixtureMappings); + return runner.RunAsync(); + } + } +} diff --git a/src/Testing/src/xunit/AspNetTestFramework.cs b/src/Testing/src/xunit/AspNetTestFramework.cs new file mode 100644 index 0000000000..0a2dc1b21f --- /dev/null +++ b/src/Testing/src/xunit/AspNetTestFramework.cs @@ -0,0 +1,20 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Reflection; +using Xunit.Abstractions; +using Xunit.Sdk; + +namespace Microsoft.AspNetCore.Testing +{ + public class AspNetTestFramework : XunitTestFramework + { + public AspNetTestFramework(IMessageSink messageSink) + : base(messageSink) + { + } + + protected override ITestFrameworkExecutor CreateExecutor(AssemblyName assemblyName) + => new AspNetTestFrameworkExecutor(assemblyName, SourceInformationProvider, DiagnosticMessageSink); + } +} diff --git a/src/Testing/src/xunit/AspNetTestFrameworkExecutor.cs b/src/Testing/src/xunit/AspNetTestFrameworkExecutor.cs new file mode 100644 index 0000000000..b34f0b715e --- /dev/null +++ b/src/Testing/src/xunit/AspNetTestFrameworkExecutor.cs @@ -0,0 +1,26 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Reflection; +using Xunit.Abstractions; +using Xunit.Sdk; + +namespace Microsoft.AspNetCore.Testing +{ + public class AspNetTestFrameworkExecutor : XunitTestFrameworkExecutor + { + public AspNetTestFrameworkExecutor(AssemblyName assemblyName, ISourceInformationProvider sourceInformationProvider, IMessageSink diagnosticMessageSink) + : base(assemblyName, sourceInformationProvider, diagnosticMessageSink) + { + } + + protected override async void RunTestCases(IEnumerable testCases, IMessageSink executionMessageSink, ITestFrameworkExecutionOptions executionOptions) + { + using (var assemblyRunner = new AspNetTestAssemblyRunner(TestAssembly, testCases, DiagnosticMessageSink, executionMessageSink, executionOptions)) + { + await assemblyRunner.RunAsync(); + } + } + } +} diff --git a/src/Testing/src/xunit/AspNetTestInvoker.cs b/src/Testing/src/xunit/AspNetTestInvoker.cs new file mode 100644 index 0000000000..a764db6622 --- /dev/null +++ b/src/Testing/src/xunit/AspNetTestInvoker.cs @@ -0,0 +1,84 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using Xunit.Abstractions; +using Xunit.Sdk; + +namespace Microsoft.AspNetCore.Testing +{ + internal class AspNetTestInvoker : XunitTestInvoker + { + public AspNetTestInvoker( + ITest test, + IMessageBus messageBus, + Type testClass, + object[] constructorArguments, + MethodInfo testMethod, + object[] testMethodArguments, + IReadOnlyList beforeAfterAttributes, + ExceptionAggregator aggregator, + CancellationTokenSource cancellationTokenSource) + : base(test, messageBus, testClass, constructorArguments, testMethod, testMethodArguments, beforeAfterAttributes, aggregator, cancellationTokenSource) + { + } + + protected override async Task InvokeTestMethodAsync(object testClassInstance) + { + var output = new TestOutputHelper(); + output.Initialize(MessageBus, Test); + + var context = new TestContext(TestClass, ConstructorArguments, TestMethod, TestMethodArguments, output); + var lifecycleHooks = GetLifecycleHooks(testClassInstance, TestClass, TestMethod); + + await Aggregator.RunAsync(async () => + { + foreach (var lifecycleHook in lifecycleHooks) + { + await lifecycleHook.OnTestStartAsync(context, CancellationTokenSource.Token); + } + }); + + var time = await base.InvokeTestMethodAsync(testClassInstance); + + await Aggregator.RunAsync(async () => + { + var exception = Aggregator.HasExceptions ? Aggregator.ToException() : null; + foreach (var lifecycleHook in lifecycleHooks) + { + await lifecycleHook.OnTestEndAsync(context, exception, CancellationTokenSource.Token); + } + }); + + return time; + } + + private static IEnumerable GetLifecycleHooks(object testClassInstance, Type testClass, MethodInfo testMethod) + { + foreach (var attribute in testMethod.GetCustomAttributes(inherit: true).OfType()) + { + yield return attribute; + } + + if (testClassInstance is ITestMethodLifecycle instance) + { + yield return instance; + } + + foreach (var attribute in testClass.GetCustomAttributes(inherit: true).OfType()) + { + yield return attribute; + } + + foreach (var attribute in testClass.Assembly.GetCustomAttributes(inherit: true).OfType()) + { + yield return attribute; + } + } + } +} diff --git a/src/Testing/src/xunit/AspNetTestMethodRunner.cs b/src/Testing/src/xunit/AspNetTestMethodRunner.cs new file mode 100644 index 0000000000..e238d0769d --- /dev/null +++ b/src/Testing/src/xunit/AspNetTestMethodRunner.cs @@ -0,0 +1,73 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Testing.xunit; +using Xunit.Abstractions; +using Xunit.Sdk; + +namespace Microsoft.AspNetCore.Testing +{ + internal class AspNetTestMethodRunner : XunitTestMethodRunner + { + private readonly object[] _constructorArguments; + private readonly IMessageSink _diagnosticMessageSink; + + public AspNetTestMethodRunner( + ITestMethod testMethod, + IReflectionTypeInfo @class, + IReflectionMethodInfo method, + IEnumerable testCases, + IMessageSink diagnosticMessageSink, + IMessageBus messageBus, + ExceptionAggregator aggregator, + CancellationTokenSource cancellationTokenSource, + object[] constructorArguments) + : base(testMethod, @class, method, testCases, diagnosticMessageSink, messageBus, aggregator, cancellationTokenSource, constructorArguments) + { + _diagnosticMessageSink = diagnosticMessageSink; + _constructorArguments = constructorArguments; + } + + protected override Task RunTestCaseAsync(IXunitTestCase testCase) + { + if (testCase.GetType() == typeof(XunitTestCase)) + { + // If we get here this is a 'regular' test case, not something that represents a skipped test. + // + // We can take control of it's invocation thusly. + var runner = new AspNetTestCaseRunner( + testCase, + testCase.DisplayName, + testCase.SkipReason, + _constructorArguments, + testCase.TestMethodArguments, + MessageBus, + new ExceptionAggregator(Aggregator), + CancellationTokenSource); + return runner.RunAsync(); + } + + if (testCase.GetType() == typeof(XunitTheoryTestCase)) + { + // If we get here this is a 'regular' theory test case, not something that represents a skipped test. + // + // We can take control of it's invocation thusly. + var runner = new AspNetTheoryTestCaseRunner( + testCase, + testCase.DisplayName, + testCase.SkipReason, + _constructorArguments, + _diagnosticMessageSink, + MessageBus, + new ExceptionAggregator(Aggregator), + CancellationTokenSource); + return runner.RunAsync(); + } + + return base.RunTestCaseAsync(testCase); + } + } +} diff --git a/src/Testing/src/xunit/AspNetTestRunner.cs b/src/Testing/src/xunit/AspNetTestRunner.cs new file mode 100644 index 0000000000..2786a866b4 --- /dev/null +++ b/src/Testing/src/xunit/AspNetTestRunner.cs @@ -0,0 +1,78 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using Xunit.Abstractions; +using Xunit.Sdk; + +namespace Microsoft.AspNetCore.Testing +{ + internal class AspNetTestRunner : XunitTestRunner + { + public AspNetTestRunner( + ITest test, + IMessageBus messageBus, + Type testClass, + object[] constructorArguments, + MethodInfo testMethod, + object[] testMethodArguments, + string skipReason, + IReadOnlyList beforeAfterAttributes, + ExceptionAggregator aggregator, + CancellationTokenSource cancellationTokenSource) + : base(test, messageBus, testClass, constructorArguments, testMethod, testMethodArguments, skipReason, beforeAfterAttributes, aggregator, cancellationTokenSource) + { + } + + protected override async Task InvokeTestMethodAsync(ExceptionAggregator aggregator) + { + var repeatAttribute = GetRepeatAttribute(TestMethod); + if (repeatAttribute == null) + { + return await InvokeTestMethodCoreAsync(aggregator); + } + + var repeatContext = new RepeatContext(repeatAttribute.RunCount); + RepeatContext.Current = repeatContext; + + var timeTaken = 0.0M; + for (repeatContext.CurrentIteration = 0; repeatContext.CurrentIteration < repeatContext.Limit; repeatContext.CurrentIteration++) + { + timeTaken = await InvokeTestMethodCoreAsync(aggregator); + if (aggregator.HasExceptions) + { + return timeTaken; + } + } + + return timeTaken; + } + + private Task InvokeTestMethodCoreAsync(ExceptionAggregator aggregator) + { + var invoker = new AspNetTestInvoker(Test, MessageBus, TestClass, ConstructorArguments, TestMethod, TestMethodArguments, BeforeAfterAttributes, aggregator, CancellationTokenSource); + return invoker.RunAsync(); + } + + private RepeatAttribute GetRepeatAttribute(MethodInfo methodInfo) + { + var attributeCandidate = methodInfo.GetCustomAttribute(); + if (attributeCandidate != null) + { + return attributeCandidate; + } + + attributeCandidate = methodInfo.DeclaringType.GetCustomAttribute(); + if (attributeCandidate != null) + { + return attributeCandidate; + } + + return methodInfo.DeclaringType.Assembly.GetCustomAttribute(); + } + } +} diff --git a/src/Testing/src/xunit/AspNetTheoryTestCaseRunner.cs b/src/Testing/src/xunit/AspNetTheoryTestCaseRunner.cs new file mode 100644 index 0000000000..a09a17cf69 --- /dev/null +++ b/src/Testing/src/xunit/AspNetTheoryTestCaseRunner.cs @@ -0,0 +1,33 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Threading; +using Xunit.Abstractions; +using Xunit.Sdk; + +namespace Microsoft.AspNetCore.Testing.xunit +{ + internal class AspNetTheoryTestCaseRunner : XunitTheoryTestCaseRunner + { + public AspNetTheoryTestCaseRunner( + IXunitTestCase testCase, + string displayName, + string skipReason, + object[] constructorArguments, + IMessageSink diagnosticMessageSink, + IMessageBus messageBus, + ExceptionAggregator aggregator, + CancellationTokenSource cancellationTokenSource) + : base(testCase, displayName, skipReason, constructorArguments, diagnosticMessageSink, messageBus, aggregator, cancellationTokenSource) + { + } + + protected override XunitTestRunner CreateTestRunner(ITest test, IMessageBus messageBus, Type testClass, object[] constructorArguments, MethodInfo testMethod, object[] testMethodArguments, string skipReason, IReadOnlyList beforeAfterAttributes, ExceptionAggregator aggregator, CancellationTokenSource cancellationTokenSource) + { + return new AspNetTestRunner(test, messageBus, testClass, constructorArguments, testMethod, testMethodArguments, skipReason, beforeAfterAttributes, aggregator, cancellationTokenSource); + } + } +} diff --git a/src/Testing/src/xunit/AssemblyFixtureAttribute.cs b/src/Testing/src/xunit/AssemblyFixtureAttribute.cs new file mode 100644 index 0000000000..c3b9eba31d --- /dev/null +++ b/src/Testing/src/xunit/AssemblyFixtureAttribute.cs @@ -0,0 +1,18 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.AspNetCore.Testing +{ + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + public class AssemblyFixtureAttribute : Attribute + { + public AssemblyFixtureAttribute(Type fixtureType) + { + FixtureType = fixtureType; + } + + public Type FixtureType { get; private set; } + } +} diff --git a/src/Testing/src/xunit/ConditionalFactAttribute.cs b/src/Testing/src/xunit/ConditionalFactAttribute.cs new file mode 100644 index 0000000000..538a055792 --- /dev/null +++ b/src/Testing/src/xunit/ConditionalFactAttribute.cs @@ -0,0 +1,15 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Xunit; +using Xunit.Sdk; + +namespace Microsoft.AspNetCore.Testing +{ + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] + [XunitTestCaseDiscoverer("Microsoft.AspNetCore.Testing." + nameof(ConditionalFactDiscoverer), "Microsoft.AspNetCore.Testing")] + public class ConditionalFactAttribute : FactAttribute + { + } +} diff --git a/src/Testing/src/xunit/ConditionalFactDiscoverer.cs b/src/Testing/src/xunit/ConditionalFactDiscoverer.cs new file mode 100644 index 0000000000..e9a6b895ae --- /dev/null +++ b/src/Testing/src/xunit/ConditionalFactDiscoverer.cs @@ -0,0 +1,28 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Xunit.Abstractions; +using Xunit.Sdk; + +// Do not change this namespace without changing the usage in ConditionalFactAttribute +namespace Microsoft.AspNetCore.Testing +{ + internal class ConditionalFactDiscoverer : FactDiscoverer + { + private readonly IMessageSink _diagnosticMessageSink; + + public ConditionalFactDiscoverer(IMessageSink diagnosticMessageSink) + : base(diagnosticMessageSink) + { + _diagnosticMessageSink = diagnosticMessageSink; + } + + protected override IXunitTestCase CreateTestCase(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo factAttribute) + { + var skipReason = testMethod.EvaluateSkipConditions(); + return skipReason != null + ? new SkippedTestCase(skipReason, _diagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), TestMethodDisplayOptions.None, testMethod) + : base.CreateTestCase(discoveryOptions, testMethod, factAttribute); + } + } +} diff --git a/src/Testing/src/xunit/ConditionalTheoryAttribute.cs b/src/Testing/src/xunit/ConditionalTheoryAttribute.cs new file mode 100644 index 0000000000..2fbac5d90c --- /dev/null +++ b/src/Testing/src/xunit/ConditionalTheoryAttribute.cs @@ -0,0 +1,15 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Xunit; +using Xunit.Sdk; + +namespace Microsoft.AspNetCore.Testing +{ + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] + [XunitTestCaseDiscoverer("Microsoft.AspNetCore.Testing." + nameof(ConditionalTheoryDiscoverer), "Microsoft.AspNetCore.Testing")] + public class ConditionalTheoryAttribute : TheoryAttribute + { + } +} diff --git a/src/Testing/src/xunit/ConditionalTheoryDiscoverer.cs b/src/Testing/src/xunit/ConditionalTheoryDiscoverer.cs new file mode 100644 index 0000000000..e7f655a5be --- /dev/null +++ b/src/Testing/src/xunit/ConditionalTheoryDiscoverer.cs @@ -0,0 +1,87 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using Xunit.Abstractions; +using Xunit.Sdk; + +// Do not change this namespace without changing the usage in ConditionalTheoryAttribute +namespace Microsoft.AspNetCore.Testing +{ + internal class ConditionalTheoryDiscoverer : TheoryDiscoverer + { + public ConditionalTheoryDiscoverer(IMessageSink diagnosticMessageSink) + : base(diagnosticMessageSink) + { + } + + private sealed class OptionsWithPreEnumerationEnabled : ITestFrameworkDiscoveryOptions + { + private const string PreEnumerateTheories = "xunit.discovery.PreEnumerateTheories"; + + private readonly ITestFrameworkDiscoveryOptions _original; + + public OptionsWithPreEnumerationEnabled(ITestFrameworkDiscoveryOptions original) + => _original = original; + + public TValue GetValue(string name) + => (name == PreEnumerateTheories) ? (TValue)(object)true : _original.GetValue(name); + + public void SetValue(string name, TValue value) + => _original.SetValue(name, value); + } + + public override IEnumerable Discover(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo theoryAttribute) + => base.Discover(new OptionsWithPreEnumerationEnabled(discoveryOptions), testMethod, theoryAttribute); + + protected override IEnumerable CreateTestCasesForTheory(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo theoryAttribute) + { + var skipReason = testMethod.EvaluateSkipConditions(); + return skipReason != null + ? new[] { new SkippedTestCase(skipReason, DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), TestMethodDisplayOptions.None, testMethod) } + : base.CreateTestCasesForTheory(discoveryOptions, testMethod, theoryAttribute); + } + + protected override IEnumerable CreateTestCasesForDataRow(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo theoryAttribute, object[] dataRow) + { + var skipReason = testMethod.EvaluateSkipConditions(); + if (skipReason == null && dataRow?.Length > 0) + { + var obj = dataRow[0]; + if (obj != null) + { + var type = obj.GetType(); + var property = type.GetProperty("Skip"); + if (property != null && property.PropertyType.Equals(typeof(string))) + { + skipReason = property.GetValue(obj) as string; + } + } + } + + return skipReason != null ? + base.CreateTestCasesForSkippedDataRow(discoveryOptions, testMethod, theoryAttribute, dataRow, skipReason) + : base.CreateTestCasesForDataRow(discoveryOptions, testMethod, theoryAttribute, dataRow); + } + + protected override IEnumerable CreateTestCasesForSkippedDataRow( + ITestFrameworkDiscoveryOptions discoveryOptions, + ITestMethod testMethod, + IAttributeInfo theoryAttribute, + object[] dataRow, + string skipReason) + { + return new[] + { + new WORKAROUND_SkippedDataRowTestCase(DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), discoveryOptions.MethodDisplayOptionsOrDefault(), testMethod, skipReason, dataRow), + }; + } + + [Obsolete] + protected override IXunitTestCase CreateTestCaseForSkippedDataRow(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo theoryAttribute, object[] dataRow, string skipReason) + { + return new WORKAROUND_SkippedDataRowTestCase(DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), discoveryOptions.MethodDisplayOptionsOrDefault(), testMethod, skipReason, dataRow); + } + } +} diff --git a/src/Testing/src/xunit/DockerOnlyAttribute.cs b/src/Testing/src/xunit/DockerOnlyAttribute.cs new file mode 100644 index 0000000000..7d809884d6 --- /dev/null +++ b/src/Testing/src/xunit/DockerOnlyAttribute.cs @@ -0,0 +1,38 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; + +namespace Microsoft.AspNetCore.Testing +{ + [AttributeUsage(AttributeTargets.Method, Inherited = true, AllowMultiple = false)] + public sealed class DockerOnlyAttribute : Attribute, ITestCondition + { + public string SkipReason { get; } = "This test can only run in a Docker container."; + + public bool IsMet + { + get + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + // we currently don't have a good way to detect if running in a Windows container + return false; + } + + const string procFile = "/proc/1/cgroup"; + if (!File.Exists(procFile)) + { + return false; + } + + var lines = File.ReadAllLines(procFile); + // typically the last line in the file is "1:name=openrc:/docker" + return lines.Reverse().Any(l => l.EndsWith("name=openrc:/docker", StringComparison.Ordinal)); + } + } + } +} diff --git a/src/Testing/src/xunit/EnvironmentVariableSkipConditionAttribute.cs b/src/Testing/src/xunit/EnvironmentVariableSkipConditionAttribute.cs new file mode 100644 index 0000000000..0599e31901 --- /dev/null +++ b/src/Testing/src/xunit/EnvironmentVariableSkipConditionAttribute.cs @@ -0,0 +1,95 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq; + +namespace Microsoft.AspNetCore.Testing +{ + /// + /// Skips a test when the value of an environment variable matches any of the supplied values. + /// + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Assembly, AllowMultiple = true)] + public class EnvironmentVariableSkipConditionAttribute : Attribute, ITestCondition + { + private readonly string _variableName; + private readonly string[] _values; + private string _currentValue; + private readonly IEnvironmentVariable _environmentVariable; + + /// + /// Creates a new instance of . + /// + /// Name of the environment variable. + /// Value(s) of the environment variable to match for the test to be skipped + public EnvironmentVariableSkipConditionAttribute(string variableName, params string[] values) + : this(new EnvironmentVariable(), variableName, values) + { + } + + // To enable unit testing + internal EnvironmentVariableSkipConditionAttribute( + IEnvironmentVariable environmentVariable, + string variableName, + params string[] values) + { + if (environmentVariable == null) + { + throw new ArgumentNullException(nameof(environmentVariable)); + } + if (variableName == null) + { + throw new ArgumentNullException(nameof(variableName)); + } + if (values == null) + { + throw new ArgumentNullException(nameof(values)); + } + + _variableName = variableName; + _values = values; + _environmentVariable = environmentVariable; + } + + /// + /// Runs the test only if the value of the variable matches any of the supplied values. Default is True. + /// + public bool RunOnMatch { get; set; } = true; + + public bool IsMet + { + get + { + _currentValue = _environmentVariable.Get(_variableName); + var hasMatched = _values.Any(value => string.Compare(value, _currentValue, ignoreCase: true) == 0); + + if (RunOnMatch) + { + return hasMatched; + } + else + { + return !hasMatched; + } + } + } + + public string SkipReason + { + get + { + var value = _currentValue == null ? "(null)" : _currentValue; + return $"Test skipped on environment variable with name '{_variableName}' and value '{value}' " + + $"for the '{nameof(RunOnMatch)}' value of '{RunOnMatch}'."; + } + } + + private struct EnvironmentVariable : IEnvironmentVariable + { + public string Get(string name) + { + return Environment.GetEnvironmentVariable(name); + } + } + } +} diff --git a/src/Testing/src/xunit/FrameworkSkipConditionAttribute.cs b/src/Testing/src/xunit/FrameworkSkipConditionAttribute.cs new file mode 100644 index 0000000000..b7719848a6 --- /dev/null +++ b/src/Testing/src/xunit/FrameworkSkipConditionAttribute.cs @@ -0,0 +1,57 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.AspNetCore.Testing +{ + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] + public class FrameworkSkipConditionAttribute : Attribute, ITestCondition + { + private readonly RuntimeFrameworks _excludedFrameworks; + + public FrameworkSkipConditionAttribute(RuntimeFrameworks excludedFrameworks) + { + _excludedFrameworks = excludedFrameworks; + } + + public bool IsMet + { + get + { + return CanRunOnThisFramework(_excludedFrameworks); + } + } + + public string SkipReason { get; set; } = "Test cannot run on this runtime framework."; + + private static bool CanRunOnThisFramework(RuntimeFrameworks excludedFrameworks) + { + if (excludedFrameworks == RuntimeFrameworks.None) + { + return true; + } + +#if NET461 || NET46 + if (excludedFrameworks.HasFlag(RuntimeFrameworks.Mono) && + TestPlatformHelper.IsMono) + { + return false; + } + + if (excludedFrameworks.HasFlag(RuntimeFrameworks.CLR)) + { + return false; + } +#elif NETSTANDARD2_0 + if (excludedFrameworks.HasFlag(RuntimeFrameworks.CoreCLR)) + { + return false; + } +#else +#error Target frameworks need to be updated. +#endif + return true; + } + } +} \ No newline at end of file diff --git a/src/Components/Blazor/Build/src/Core/EmbeddedResources/EmbeddedResourceKind.cs b/src/Testing/src/xunit/IEnvironmentVariable.cs similarity index 58% rename from src/Components/Blazor/Build/src/Core/EmbeddedResources/EmbeddedResourceKind.cs rename to src/Testing/src/xunit/IEnvironmentVariable.cs index caf322ee15..ed06ed6505 100644 --- a/src/Components/Blazor/Build/src/Core/EmbeddedResources/EmbeddedResourceKind.cs +++ b/src/Testing/src/xunit/IEnvironmentVariable.cs @@ -1,12 +1,10 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -namespace Microsoft.AspNetCore.Blazor.Build +namespace Microsoft.AspNetCore.Testing { - internal enum EmbeddedResourceKind + internal interface IEnvironmentVariable { - JavaScript, - Css, - Static + string Get(string name); } } diff --git a/src/Testing/src/xunit/ITestCondition.cs b/src/Testing/src/xunit/ITestCondition.cs new file mode 100644 index 0000000000..34767b8574 --- /dev/null +++ b/src/Testing/src/xunit/ITestCondition.cs @@ -0,0 +1,12 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.AspNetCore.Testing +{ + public interface ITestCondition + { + bool IsMet { get; } + + string SkipReason { get; } + } +} \ No newline at end of file diff --git a/src/Testing/src/xunit/MaximumOSVersionAttribute.cs b/src/Testing/src/xunit/MaximumOSVersionAttribute.cs new file mode 100644 index 0000000000..19ee1098d2 --- /dev/null +++ b/src/Testing/src/xunit/MaximumOSVersionAttribute.cs @@ -0,0 +1,83 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Runtime.InteropServices; + +namespace Microsoft.AspNetCore.Testing +{ + /// + /// Skips a test if the OS is the given type (Windows) and the OS version is greater than specified. + /// E.g. Specifying Window 8 skips on Win 10, but not on Linux. Combine with OSSkipConditionAttribute as needed. + /// + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Assembly, AllowMultiple = true)] + public class MaximumOSVersionAttribute : Attribute, ITestCondition + { + private readonly OperatingSystems _targetOS; + private readonly Version _maxVersion; + private readonly OperatingSystems _currentOS; + private readonly Version _currentVersion; + private readonly bool _skip; + + public MaximumOSVersionAttribute(OperatingSystems operatingSystem, string maxVersion) : + this(operatingSystem, Version.Parse(maxVersion), GetCurrentOS(), GetCurrentOSVersion()) + { + } + + // to enable unit testing + internal MaximumOSVersionAttribute(OperatingSystems targetOS, Version maxVersion, OperatingSystems currentOS, Version currentVersion) + { + if (targetOS != OperatingSystems.Windows) + { + throw new NotImplementedException("Max version support is only implemented for Windows."); + } + _targetOS = targetOS; + _maxVersion = maxVersion; + _currentOS = currentOS; + // We drop the 4th field because it is not significant and it messes up the comparisons. + _currentVersion = new Version(currentVersion.Major, currentVersion.Minor, + // Major and Minor are required by the parser, but if Build isn't specified then it returns -1 + // which the constructor rejects. + currentVersion.Build == -1 ? 0 : currentVersion.Build); + + // Do not skip other OS's, Use OSSkipConditionAttribute or a separate MaximumOsVersionAttribute for that. + _skip = _targetOS == _currentOS && _maxVersion < _currentVersion; + SkipReason = $"This test requires {_targetOS} {_maxVersion} or earlier."; + } + + // Since a test would be executed only if 'IsMet' is true, return false if we want to skip + public bool IsMet => !_skip; + + public string SkipReason { get; set; } + + private static OperatingSystems GetCurrentOS() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return OperatingSystems.Windows; + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + return OperatingSystems.Linux; + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + return OperatingSystems.MacOSX; + } + throw new PlatformNotSupportedException(); + } + + static private Version GetCurrentOSVersion() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return Environment.OSVersion.Version; + } + else + { + // Not implemented, but this will still be called before the OS check happens so don't throw. + return new Version(0, 0); + } + } + } +} diff --git a/src/Testing/src/xunit/MinimumOsVersionAttribute.cs b/src/Testing/src/xunit/MinimumOsVersionAttribute.cs new file mode 100644 index 0000000000..4d016a07e7 --- /dev/null +++ b/src/Testing/src/xunit/MinimumOsVersionAttribute.cs @@ -0,0 +1,79 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Runtime.InteropServices; + +namespace Microsoft.AspNetCore.Testing +{ + /// + /// Skips a test if the OS is the given type (Windows) and the OS version is less than specified. + /// E.g. Specifying Window 10.0 skips on Win 8, but not on Linux. Combine with OSSkipConditionAttribute as needed. + /// + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Assembly, AllowMultiple = true)] + public class MinimumOSVersionAttribute : Attribute, ITestCondition + { + private readonly OperatingSystems _targetOS; + private readonly Version _minVersion; + private readonly OperatingSystems _currentOS; + private readonly Version _currentVersion; + private readonly bool _skip; + + public MinimumOSVersionAttribute(OperatingSystems operatingSystem, string minVersion) : + this(operatingSystem, Version.Parse(minVersion), GetCurrentOS(), GetCurrentOSVersion()) + { + } + + // to enable unit testing + internal MinimumOSVersionAttribute(OperatingSystems targetOS, Version minVersion, OperatingSystems currentOS, Version currentVersion) + { + if (targetOS != OperatingSystems.Windows) + { + throw new NotImplementedException("Min version support is only implemented for Windows."); + } + _targetOS = targetOS; + _minVersion = minVersion; + _currentOS = currentOS; + _currentVersion = currentVersion; + + // Do not skip other OS's, Use OSSkipConditionAttribute or a separate MinimumOSVersionAttribute for that. + _skip = _targetOS == _currentOS && _minVersion > _currentVersion; + SkipReason = $"This test requires {_targetOS} {_minVersion} or later."; + } + + // Since a test would be executed only if 'IsMet' is true, return false if we want to skip + public bool IsMet => !_skip; + + public string SkipReason { get; set; } + + private static OperatingSystems GetCurrentOS() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return OperatingSystems.Windows; + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + return OperatingSystems.Linux; + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + return OperatingSystems.MacOSX; + } + throw new PlatformNotSupportedException(); + } + + static private Version GetCurrentOSVersion() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return Environment.OSVersion.Version; + } + else + { + // Not implemented, but this will still be called before the OS check happens so don't throw. + return new Version(0, 0); + } + } + } +} diff --git a/src/Testing/src/xunit/OSSkipConditionAttribute.cs b/src/Testing/src/xunit/OSSkipConditionAttribute.cs new file mode 100644 index 0000000000..50e3cae192 --- /dev/null +++ b/src/Testing/src/xunit/OSSkipConditionAttribute.cs @@ -0,0 +1,62 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Runtime.InteropServices; + +namespace Microsoft.AspNetCore.Testing +{ + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Assembly, AllowMultiple = true)] + public class OSSkipConditionAttribute : Attribute, ITestCondition + { + private readonly OperatingSystems _excludedOperatingSystem; + private readonly OperatingSystems _osPlatform; + + public OSSkipConditionAttribute(OperatingSystems operatingSystem) : + this(operatingSystem, GetCurrentOS()) + { + } + + [Obsolete("Use the Minimum/MaximumOSVersionAttribute for version checks.", error: true)] + public OSSkipConditionAttribute(OperatingSystems operatingSystem, params string[] versions) : + this(operatingSystem, GetCurrentOS()) + { + } + + // to enable unit testing + internal OSSkipConditionAttribute(OperatingSystems operatingSystem, OperatingSystems osPlatform) + { + _excludedOperatingSystem = operatingSystem; + _osPlatform = osPlatform; + } + + public bool IsMet + { + get + { + var skip = (_excludedOperatingSystem & _osPlatform) == _osPlatform; + // Since a test would be excuted only if 'IsMet' is true, return false if we want to skip + return !skip; + } + } + + public string SkipReason { get; set; } = "Test cannot run on this operating system."; + + static private OperatingSystems GetCurrentOS() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return OperatingSystems.Windows; + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + return OperatingSystems.Linux; + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + return OperatingSystems.MacOSX; + } + throw new PlatformNotSupportedException(); + } + } +} diff --git a/src/Testing/src/xunit/OperatingSystems.cs b/src/Testing/src/xunit/OperatingSystems.cs new file mode 100644 index 0000000000..2ddacacab9 --- /dev/null +++ b/src/Testing/src/xunit/OperatingSystems.cs @@ -0,0 +1,15 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.AspNetCore.Testing +{ + [Flags] + public enum OperatingSystems + { + Linux = 1, + MacOSX = 2, + Windows = 4, + } +} \ No newline at end of file diff --git a/src/Testing/src/xunit/QuarantinedTestAttribute.cs b/src/Testing/src/xunit/QuarantinedTestAttribute.cs new file mode 100644 index 0000000000..350dcf1e12 --- /dev/null +++ b/src/Testing/src/xunit/QuarantinedTestAttribute.cs @@ -0,0 +1,57 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using Xunit.Sdk; + +namespace Microsoft.AspNetCore.Testing +{ + /// + /// Marks a test as "Quarantined" so that the build will sequester it and ignore failures. + /// + /// + /// + /// This attribute works by applying xUnit.net "Traits" based on the criteria specified in the attribute + /// properties. Once these traits are applied, build scripts can include/exclude tests based on them. + /// + /// + /// + /// + /// [Fact] + /// [QuarantinedTest] + /// public void FlakyTest() + /// { + /// // Flakiness + /// } + /// + /// + /// + /// The above example generates the following facet: + /// + /// + /// + /// + /// Quarantined = true + /// + /// + /// + [TraitDiscoverer("Microsoft.AspNetCore.Testing." + nameof(QuarantinedTestTraitDiscoverer), "Microsoft.AspNetCore.Testing")] + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Assembly)] + public sealed class QuarantinedTestAttribute : Attribute, ITraitAttribute + { + /// + /// Gets an optional reason for the quarantining, such as a link to a GitHub issue URL with more details as to why the test is quarantined. + /// + public string Reason { get; } + + /// + /// Initializes a new instance of the class with an optional . + /// + /// A reason that this test is quarantined. + public QuarantinedTestAttribute(string reason = null) + { + Reason = reason; + } + } +} diff --git a/src/Testing/src/xunit/QuarantinedTestTraitDiscoverer.cs b/src/Testing/src/xunit/QuarantinedTestTraitDiscoverer.cs new file mode 100644 index 0000000000..256e2b6cfc --- /dev/null +++ b/src/Testing/src/xunit/QuarantinedTestTraitDiscoverer.cs @@ -0,0 +1,26 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using Xunit.Abstractions; +using Xunit.Sdk; + +// Do not change this namespace without changing the usage in QuarantinedTestAttribute +namespace Microsoft.AspNetCore.Testing +{ + public class QuarantinedTestTraitDiscoverer : ITraitDiscoverer + { + public IEnumerable> GetTraits(IAttributeInfo traitAttribute) + { + if (traitAttribute is ReflectionAttributeInfo attribute && attribute.Attribute is QuarantinedTestAttribute quarantinedTestAttribute) + { + yield return new KeyValuePair("Quarantined", "true"); + } + else + { + throw new InvalidOperationException("The 'QuarantinedTest' attribute is only supported via reflection."); + } + } + } +} diff --git a/src/Testing/src/xunit/RuntimeFrameworks.cs b/src/Testing/src/xunit/RuntimeFrameworks.cs new file mode 100644 index 0000000000..3a69022b88 --- /dev/null +++ b/src/Testing/src/xunit/RuntimeFrameworks.cs @@ -0,0 +1,16 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.AspNetCore.Testing +{ + [Flags] + public enum RuntimeFrameworks + { + None = 0, + Mono = 1 << 0, + CLR = 1 << 1, + CoreCLR = 1 << 2 + } +} \ No newline at end of file diff --git a/src/Testing/src/xunit/SkipOnCIAttribute.cs b/src/Testing/src/xunit/SkipOnCIAttribute.cs new file mode 100644 index 0000000000..1ee0b8cde8 --- /dev/null +++ b/src/Testing/src/xunit/SkipOnCIAttribute.cs @@ -0,0 +1,43 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.AspNetCore.Testing +{ + /// + /// Skip test if running on CI + /// + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false)] + public class SkipOnCIAttribute : Attribute, ITestCondition + { + public SkipOnCIAttribute(string issueUrl = "") + { + IssueUrl = issueUrl; + } + + public string IssueUrl { get; } + + public bool IsMet + { + get + { + return !OnCI(); + } + } + + public string SkipReason + { + get + { + return $"This test is skipped on CI"; + } + } + + public static bool OnCI() => OnHelix() || OnAzdo(); + public static bool OnHelix() => !string.IsNullOrEmpty(GetTargetHelixQueue()); + public static string GetTargetHelixQueue() => Environment.GetEnvironmentVariable("helix"); + public static bool OnAzdo() => !string.IsNullOrEmpty(GetIfOnAzdo()); + public static string GetIfOnAzdo() => Environment.GetEnvironmentVariable("AGENT_OS"); + } +} diff --git a/src/Testing/src/xunit/SkipOnHelixAttribute.cs b/src/Testing/src/xunit/SkipOnHelixAttribute.cs new file mode 100644 index 0000000000..85e82c1154 --- /dev/null +++ b/src/Testing/src/xunit/SkipOnHelixAttribute.cs @@ -0,0 +1,50 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq; + +namespace Microsoft.AspNetCore.Testing +{ + /// + /// Skip test if running on helix (or a particular helix queue). + /// + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false)] + public class SkipOnHelixAttribute : Attribute, ITestCondition + { + public SkipOnHelixAttribute(string issueUrl) + { + if (string.IsNullOrEmpty(issueUrl)) + { + throw new ArgumentException(); + } + IssueUrl = issueUrl; + } + + public string IssueUrl { get; } + + public bool IsMet + { + get + { + var skip = OnHelix() && (Queues == null || Queues.ToLowerInvariant().Split(';').Contains(GetTargetHelixQueue().ToLowerInvariant())); + return !skip; + } + } + + // Queues that should be skipped on, i.e. "Windows.10.Amd64.ClientRS4.VS2017.Open;OSX.1012.Amd64.Open" + public string Queues { get; set; } + + public string SkipReason + { + get + { + return $"This test is skipped on helix"; + } + } + + public static bool OnHelix() => !string.IsNullOrEmpty(GetTargetHelixQueue()); + + public static string GetTargetHelixQueue() => Environment.GetEnvironmentVariable("helix"); + } +} diff --git a/src/Testing/src/xunit/SkippedTestCase.cs b/src/Testing/src/xunit/SkippedTestCase.cs new file mode 100644 index 0000000000..0fdf166f2b --- /dev/null +++ b/src/Testing/src/xunit/SkippedTestCase.cs @@ -0,0 +1,51 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Threading; +using System.Threading.Tasks; +using Xunit.Abstractions; +using Xunit.Sdk; + +namespace Microsoft.AspNetCore.Testing +{ + public class SkippedTestCase : XunitTestCase + { + private string _skipReason; + + [Obsolete("Called by the de-serializer; should only be called by deriving classes for de-serialization purposes")] + public SkippedTestCase() : base() + { + } + + public SkippedTestCase( + string skipReason, + IMessageSink diagnosticMessageSink, + TestMethodDisplay defaultMethodDisplay, + TestMethodDisplayOptions defaultMethodDisplayOptions, + ITestMethod testMethod, + object[] testMethodArguments = null) + : base(diagnosticMessageSink, defaultMethodDisplay, defaultMethodDisplayOptions, testMethod, testMethodArguments) + { + _skipReason = skipReason; + } + + protected override string GetSkipReason(IAttributeInfo factAttribute) + => _skipReason ?? base.GetSkipReason(factAttribute); + + public override void Deserialize(IXunitSerializationInfo data) + { + _skipReason = data.GetValue(nameof(_skipReason)); + + // We need to call base after reading our value, because Deserialize will call + // into GetSkipReason. + base.Deserialize(data); + } + + public override void Serialize(IXunitSerializationInfo data) + { + base.Serialize(data); + data.AddValue(nameof(_skipReason), _skipReason); + } + } +} diff --git a/src/Testing/src/xunit/TestMethodExtensions.cs b/src/Testing/src/xunit/TestMethodExtensions.cs new file mode 100644 index 0000000000..96dd93eb7c --- /dev/null +++ b/src/Testing/src/xunit/TestMethodExtensions.cs @@ -0,0 +1,34 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Linq; +using Xunit.Abstractions; +using Xunit.Sdk; + +namespace Microsoft.AspNetCore.Testing +{ + public static class TestMethodExtensions + { + public static string EvaluateSkipConditions(this ITestMethod testMethod) + { + var testClass = testMethod.TestClass.Class; + var assembly = testMethod.TestClass.TestCollection.TestAssembly.Assembly; + var conditionAttributes = testMethod.Method + .GetCustomAttributes(typeof(ITestCondition)) + .Concat(testClass.GetCustomAttributes(typeof(ITestCondition))) + .Concat(assembly.GetCustomAttributes(typeof(ITestCondition))) + .OfType() + .Select(attributeInfo => attributeInfo.Attribute); + + foreach (ITestCondition condition in conditionAttributes) + { + if (!condition.IsMet) + { + return condition.SkipReason; + } + } + + return null; + } + } +} diff --git a/src/Testing/src/xunit/WORKAROUND_SkippedDataRowTestCase.cs b/src/Testing/src/xunit/WORKAROUND_SkippedDataRowTestCase.cs new file mode 100644 index 0000000000..a86f5645bf --- /dev/null +++ b/src/Testing/src/xunit/WORKAROUND_SkippedDataRowTestCase.cs @@ -0,0 +1,80 @@ +using System; +using System.ComponentModel; +using Xunit.Abstractions; +using Xunit.Sdk; + +namespace Microsoft.AspNetCore.Testing +{ + // This is a workaround for https://github.com/xunit/xunit/issues/1782 - as such, this code is a copy-paste + // from xUnit with the exception of fixing the bug. + // + // This will only work with [ConditionalTheory]. + internal class WORKAROUND_SkippedDataRowTestCase : XunitTestCase + { + string skipReason; + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete("Called by the de-serializer; should only be called by deriving classes for de-serialization purposes")] + public WORKAROUND_SkippedDataRowTestCase() { } + + /// + /// Initializes a new instance of the class. + /// + /// The message sink used to send diagnostic messages + /// Default method display to use (when not customized). + /// The test method this test case belongs to. + /// The reason that this test case will be skipped + /// The arguments for the test method. + [Obsolete("Please call the constructor which takes TestMethodDisplayOptions")] + public WORKAROUND_SkippedDataRowTestCase(IMessageSink diagnosticMessageSink, + TestMethodDisplay defaultMethodDisplay, + ITestMethod testMethod, + string skipReason, + object[] testMethodArguments = null) + : this(diagnosticMessageSink, defaultMethodDisplay, TestMethodDisplayOptions.None, testMethod, skipReason, testMethodArguments) { } + + /// + /// Initializes a new instance of the class. + /// + /// The message sink used to send diagnostic messages + /// Default method display to use (when not customized). + /// Default method display options to use (when not customized). + /// The test method this test case belongs to. + /// The reason that this test case will be skipped + /// The arguments for the test method. + public WORKAROUND_SkippedDataRowTestCase(IMessageSink diagnosticMessageSink, + TestMethodDisplay defaultMethodDisplay, + TestMethodDisplayOptions defaultMethodDisplayOptions, + ITestMethod testMethod, + string skipReason, + object[] testMethodArguments = null) + : base(diagnosticMessageSink, defaultMethodDisplay, defaultMethodDisplayOptions, testMethod, testMethodArguments) + { + this.skipReason = skipReason; + } + + /// + public override void Deserialize(IXunitSerializationInfo data) + { + // SkipReason has to be read before we call base.Deserialize, this is the workaround. + this.skipReason = data.GetValue("SkipReason"); + + base.Deserialize(data); + } + + /// + protected override string GetSkipReason(IAttributeInfo factAttribute) + { + return skipReason; + } + + /// + public override void Serialize(IXunitSerializationInfo data) + { + base.Serialize(data); + + data.AddValue("SkipReason", skipReason); + } + } +} diff --git a/src/Testing/src/xunit/WindowsVersions.cs b/src/Testing/src/xunit/WindowsVersions.cs new file mode 100644 index 0000000000..44448c74d1 --- /dev/null +++ b/src/Testing/src/xunit/WindowsVersions.cs @@ -0,0 +1,49 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.AspNetCore.Testing +{ + /// + /// https://en.wikipedia.org/wiki/Windows_10_version_history + /// + public static class WindowsVersions + { + public const string Win7 = "6.1"; + + [Obsolete("Use Win7 instead.", error: true)] + public const string Win2008R2 = Win7; + + public const string Win8 = "6.2"; + + public const string Win81 = "6.3"; + + public const string Win10 = "10.0"; + + /// + /// 1803, RS4, 17134 + /// + public const string Win10_RS4 = "10.0.17134"; + + /// + /// 1809, RS5, 17763 + /// + public const string Win10_RS5 = "10.0.17763"; + + /// + /// 1903, 19H1, 18362 + /// + public const string Win10_19H1 = "10.0.18362"; + + /// + /// 1909, 19H2, 18363 + /// + public const string Win10_19H2 = "10.0.18363"; + + /// + /// 2004, 20H1, 19033 + /// + public const string Win10_20H1 = "10.0.19033"; + } +} diff --git a/src/Testing/test/AlphabeticalOrderer.cs b/src/Testing/test/AlphabeticalOrderer.cs new file mode 100644 index 0000000000..24970cc281 --- /dev/null +++ b/src/Testing/test/AlphabeticalOrderer.cs @@ -0,0 +1,22 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using Xunit.Abstractions; +using Xunit.Sdk; + +namespace Microsoft.AspNetCore.Testing +{ + public class AlphabeticalOrderer : ITestCaseOrderer + { + public IEnumerable OrderTestCases(IEnumerable testCases) + where TTestCase : ITestCase + { + var result = testCases.ToList(); + result.Sort((x, y) => StringComparer.OrdinalIgnoreCase.Compare(x.TestMethod.Method.Name, y.TestMethod.Method.Name)); + return result; + } + } +} diff --git a/src/Testing/test/AssemblyFixtureTest.cs b/src/Testing/test/AssemblyFixtureTest.cs new file mode 100644 index 0000000000..a5fa73019a --- /dev/null +++ b/src/Testing/test/AssemblyFixtureTest.cs @@ -0,0 +1,47 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Xunit; + +namespace Microsoft.AspNetCore.Testing +{ + // We include a collection and assembly fixture to verify that they both still work. + [Collection("MyCollection")] + [TestCaseOrderer("Microsoft.AspNetCore.Testing.AlphabeticalOrderer", "Microsoft.AspNetCore.Testing.Tests")] + public class AssemblyFixtureTest + { + public AssemblyFixtureTest(TestAssemblyFixture assemblyFixture, TestCollectionFixture collectionFixture) + { + AssemblyFixture = assemblyFixture; + CollectionFixture = collectionFixture; + } + + public TestAssemblyFixture AssemblyFixture { get; } + public TestCollectionFixture CollectionFixture { get; } + + [Fact] + public void A() + { + Assert.NotNull(AssemblyFixture); + Assert.Equal(0, AssemblyFixture.Count); + + Assert.NotNull(CollectionFixture); + Assert.Equal(0, CollectionFixture.Count); + + AssemblyFixture.Count++; + CollectionFixture.Count++; + } + + [Fact] + public void B() + { + Assert.Equal(1, AssemblyFixture.Count); + Assert.Equal(1, CollectionFixture.Count); + } + } + + [CollectionDefinition("MyCollection", DisableParallelization = true)] + public class MyCollection : ICollectionFixture + { + } +} diff --git a/src/Testing/test/AssemblyTestLogTests.cs b/src/Testing/test/AssemblyTestLogTests.cs new file mode 100644 index 0000000000..3db8ffc0ce --- /dev/null +++ b/src/Testing/test/AssemblyTestLogTests.cs @@ -0,0 +1,221 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Testing; +using Xunit; + +namespace Microsoft.Extensions.Logging.Testing.Tests +{ + public class AssemblyTestLogTests : LoggedTest + { + private static readonly Assembly ThisAssembly = typeof(AssemblyTestLogTests).GetTypeInfo().Assembly; + private static readonly string ThisAssemblyName = ThisAssembly.GetName().Name; + private static readonly string TFM = ThisAssembly.GetCustomAttributes().OfType().FirstOrDefault().TargetFramework; + + [Fact] + public void FunctionalLogs_LogsPreservedFromNonQuarantinedTest() + { + } + + [Fact] + [QuarantinedTest] + public void FunctionalLogs_LogsPreservedFromQuarantinedTest() + { + } + + [Fact] + public void ForAssembly_ReturnsSameInstanceForSameAssembly() + { + Assert.Same( + AssemblyTestLog.ForAssembly(ThisAssembly), + AssemblyTestLog.ForAssembly(ThisAssembly)); + } + + [Fact] + public void TestLogWritesToITestOutputHelper() + { + var output = new TestTestOutputHelper(); + var assemblyLog = AssemblyTestLog.Create(ThisAssemblyName, baseDirectory: null); + + using (assemblyLog.StartTestLog(output, "NonExistant.Test.Class", out var loggerFactory)) + { + var logger = loggerFactory.CreateLogger("TestLogger"); + logger.LogInformation("Information!"); + + // Trace is disabled by default + logger.LogTrace("Trace!"); + } + + var testLogContent = MakeConsistent(output.Output); + + Assert.Equal( +@"[OFFSET] TestLifetime Information: Starting test TestLogWritesToITestOutputHelper at TIMESTAMP +[OFFSET] TestLogger Information: Information! +[OFFSET] TestLifetime Information: Finished test TestLogWritesToITestOutputHelper in DURATION +", testLogContent, ignoreLineEndingDifferences: true); + } + + [Fact] + public Task TestLogEscapesIllegalFileNames() => + RunTestLogFunctionalTest((tempDir) => + { + var illegalTestName = "T:e/s//t"; + var escapedTestName = "T_e_s_t"; + using (var testAssemblyLog = AssemblyTestLog.Create(ThisAssemblyName, baseDirectory: tempDir)) + using (testAssemblyLog.StartTestLog(output: null, className: "FakeTestAssembly.FakeTestClass", loggerFactory: out var testLoggerFactory, minLogLevel: LogLevel.Trace, resolvedTestName: out var resolvedTestname, out var _, testName: illegalTestName)) + { + Assert.Equal(escapedTestName, resolvedTestname); + } + }); + + [Fact] + public Task TestLogWritesToGlobalLogFile() => + RunTestLogFunctionalTest((tempDir) => + { + // Because this test writes to a file, it is a functional test and should be logged + // but it's also testing the test logging facility. So this is pretty meta ;) + var logger = LoggerFactory.CreateLogger("Test"); + + using (var testAssemblyLog = AssemblyTestLog.Create(ThisAssemblyName, tempDir)) + { + logger.LogInformation("Created test log in {baseDirectory}", tempDir); + + using (testAssemblyLog.StartTestLog(output: null, className: $"{ThisAssemblyName}.FakeTestClass", loggerFactory: out var testLoggerFactory, minLogLevel: LogLevel.Trace, testName: "FakeTestName")) + { + var testLogger = testLoggerFactory.CreateLogger("TestLogger"); + testLogger.LogInformation("Information!"); + testLogger.LogTrace("Trace!"); + } + } + + logger.LogInformation("Finished test log in {baseDirectory}", tempDir); + + var globalLogPath = Path.Combine(tempDir, ThisAssemblyName, TFM, "global.log"); + var testLog = Path.Combine(tempDir, ThisAssemblyName, TFM, "FakeTestClass", "FakeTestName.log"); + + Assert.True(File.Exists(globalLogPath), $"Expected global log file {globalLogPath} to exist"); + Assert.True(File.Exists(testLog), $"Expected test log file {testLog} to exist"); + + var globalLogContent = MakeConsistent(File.ReadAllText(globalLogPath)); + var testLogContent = MakeConsistent(File.ReadAllText(testLog)); + + Assert.Equal( +@"[OFFSET] [GlobalTestLog] [Information] Global Test Logging initialized at TIMESTAMP. Configure the output directory via 'LoggingTestingFileLoggingDirectory' MSBuild property or set 'LoggingTestingDisableFileLogging' to 'true' to disable file logging. +[OFFSET] [GlobalTestLog] [Information] Starting test FakeTestName +[OFFSET] [GlobalTestLog] [Information] Finished test FakeTestName in DURATION +", globalLogContent, ignoreLineEndingDifferences: true); + Assert.Equal( +@"[OFFSET] [TestLifetime] [Information] Starting test FakeTestName at TIMESTAMP +[OFFSET] [TestLogger] [Information] Information! +[OFFSET] [TestLogger] [Verbose] Trace! +[OFFSET] [TestLifetime] [Information] Finished test FakeTestName in DURATION +", testLogContent, ignoreLineEndingDifferences: true); + }); + + [Fact] + public Task TestLogTruncatesTestNameToAvoidLongPaths() => + RunTestLogFunctionalTest((tempDir) => + { + var longTestName = new string('0', 50) + new string('1', 50) + new string('2', 50) + new string('3', 50) + new string('4', 50); + var logger = LoggerFactory.CreateLogger("Test"); + using (var testAssemblyLog = AssemblyTestLog.Create(ThisAssemblyName, tempDir)) + { + logger.LogInformation("Created test log in {baseDirectory}", tempDir); + + using (testAssemblyLog.StartTestLog(output: null, className: $"{ThisAssemblyName}.FakeTestClass", loggerFactory: out var testLoggerFactory, minLogLevel: LogLevel.Trace, testName: longTestName)) + { + testLoggerFactory.CreateLogger("TestLogger").LogInformation("Information!"); + } + } + logger.LogInformation("Finished test log in {baseDirectory}", tempDir); + + var testLogFiles = new DirectoryInfo(Path.Combine(tempDir, ThisAssemblyName, TFM, "FakeTestClass")).EnumerateFiles(); + var testLog = Assert.Single(testLogFiles); + var testFileName = Path.GetFileNameWithoutExtension(testLog.Name); + + // The first half of the file comes from the beginning of the test name passed to the logger + Assert.Equal(longTestName.Substring(0, testFileName.Length / 2), testFileName.Substring(0, testFileName.Length / 2)); + // The last half of the file comes from the ending of the test name passed to the logger + Assert.Equal(longTestName.Substring(longTestName.Length - testFileName.Length / 2, testFileName.Length / 2), testFileName.Substring(testFileName.Length - testFileName.Length / 2, testFileName.Length / 2)); + }); + + [Fact] + public Task TestLogEnumerateFilenamesToAvoidCollisions() => + RunTestLogFunctionalTest((tempDir) => + { + var logger = LoggerFactory.CreateLogger("Test"); + using (var testAssemblyLog = AssemblyTestLog.Create(ThisAssemblyName, tempDir)) + { + logger.LogInformation("Created test log in {baseDirectory}", tempDir); + + for (var i = 0; i < 10; i++) + { + using (testAssemblyLog.StartTestLog(output: null, className: $"{ThisAssemblyName}.FakeTestClass", loggerFactory: out var testLoggerFactory, minLogLevel: LogLevel.Trace, testName: "FakeTestName")) + { + testLoggerFactory.CreateLogger("TestLogger").LogInformation("Information!"); + } + } + } + logger.LogInformation("Finished test log in {baseDirectory}", tempDir); + + // The first log file exists + Assert.True(File.Exists(Path.Combine(tempDir, ThisAssemblyName, TFM, "FakeTestClass", "FakeTestName.log"))); + + // Subsequent files exist + for (var i = 0; i < 9; i++) + { + Assert.True(File.Exists(Path.Combine(tempDir, ThisAssemblyName, TFM, "FakeTestClass", $"FakeTestName.{i}.log"))); + } + }); + + private static readonly Regex TimestampRegex = new Regex(@"\d+-\d+-\d+T\d+:\d+:\d+"); + private static readonly Regex TimestampOffsetRegex = new Regex(@"\d+\.\d+s"); + private static readonly Regex DurationRegex = new Regex(@"[^ ]+s$"); + + private async Task RunTestLogFunctionalTest(Action action, [CallerMemberName] string testName = null) + { + var tempDir = Path.Combine(Path.GetTempPath(), $"TestLogging_{Guid.NewGuid().ToString("N")}"); + try + { + action(tempDir); + } + finally + { + if (Directory.Exists(tempDir)) + { + try + { + Directory.Delete(tempDir, recursive: true); + } + catch + { + await Task.Delay(100); + Directory.Delete(tempDir, recursive: true); + } + } + } + } + + private static string MakeConsistent(string input) + { + return string.Join(Environment.NewLine, input.Split(new[] { Environment.NewLine }, StringSplitOptions.None) + .Select(line => + { + var strippedPrefix = line.IndexOf("[") >= 0 ? line.Substring(line.IndexOf("[")) : line; + + var strippedDuration = DurationRegex.Replace(strippedPrefix, "DURATION"); + var strippedTimestamp = TimestampRegex.Replace(strippedDuration, "TIMESTAMP"); + var strippedTimestampOffset = TimestampOffsetRegex.Replace(strippedTimestamp, "OFFSET"); + return strippedTimestampOffset; + })); + } + } +} diff --git a/src/Testing/test/CollectingEventListenerTest.cs b/src/Testing/test/CollectingEventListenerTest.cs new file mode 100644 index 0000000000..8f131982f0 --- /dev/null +++ b/src/Testing/test/CollectingEventListenerTest.cs @@ -0,0 +1,87 @@ +using System.Diagnostics.Tracing; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Testing.Tracing; +using Xunit; + +namespace Microsoft.AspNetCore.Testing.Tests +{ + // We are verifying here that when event listener tests are spread among multiple classes, they still + // work, even when run in parallel. To do that we have a bunch of tests in different classes (since + // that affects parallelism) and do some Task.Yielding in them. + public class CollectingEventListenerTests + { + public abstract class CollectingTestBase : EventSourceTestBase + { + [Fact] + public async Task CollectingEventListenerTest() + { + CollectFrom("Microsoft-AspNetCore-Testing-Test"); + + await Task.Yield(); + TestEventSource.Log.Test(); + await Task.Yield(); + TestEventSource.Log.TestWithPayload(42, 4.2); + await Task.Yield(); + + var events = GetEvents(); + EventAssert.Collection(events, + EventAssert.Event(1, "Test", EventLevel.Informational), + EventAssert.Event(2, "TestWithPayload", EventLevel.Verbose) + .Payload("payload1", 42) + .Payload("payload2", 4.2)); + } + } + + // These tests are designed to interfere with the collecting ones by running in parallel and writing events + public abstract class NonCollectingTestBase + { + [Fact] + public async Task CollectingEventListenerTest() + { + await Task.Yield(); + TestEventSource.Log.Test(); + await Task.Yield(); + TestEventSource.Log.TestWithPayload(42, 4.2); + await Task.Yield(); + } + } + + public class CollectingTests + { + public class A : CollectingTestBase { } + public class B : CollectingTestBase { } + public class C : CollectingTestBase { } + public class D : CollectingTestBase { } + public class E : CollectingTestBase { } + public class F : CollectingTestBase { } + public class G : CollectingTestBase { } + } + + public class NonCollectingTests + { + public class A : NonCollectingTestBase { } + public class B : NonCollectingTestBase { } + public class C : NonCollectingTestBase { } + public class D : NonCollectingTestBase { } + public class E : NonCollectingTestBase { } + public class F : NonCollectingTestBase { } + public class G : NonCollectingTestBase { } + } + } + + [EventSource(Name = "Microsoft-AspNetCore-Testing-Test")] + public class TestEventSource : EventSource + { + public static readonly TestEventSource Log = new TestEventSource(); + + private TestEventSource() + { + } + + [Event(eventId: 1, Level = EventLevel.Informational, Message = "Test")] + public void Test() => WriteEvent(1); + + [Event(eventId: 2, Level = EventLevel.Verbose, Message = "Test")] + public void TestWithPayload(int payload1, double payload2) => WriteEvent(2, payload1, payload2); + } +} diff --git a/src/Testing/test/ConditionalFactTest.cs b/src/Testing/test/ConditionalFactTest.cs new file mode 100644 index 0000000000..fefe6c5a42 --- /dev/null +++ b/src/Testing/test/ConditionalFactTest.cs @@ -0,0 +1,66 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Xunit; + +namespace Microsoft.AspNetCore.Testing +{ + [TestCaseOrderer("Microsoft.AspNetCore.Testing.AlphabeticalOrderer", "Microsoft.AspNetCore.Testing.Tests")] + public class ConditionalFactTest : IClassFixture + { + public ConditionalFactTest(ConditionalFactAsserter collector) + { + Asserter = collector; + } + + private ConditionalFactAsserter Asserter { get; } + + [Fact] + public void TestAlwaysRun() + { + // This is required to ensure that the type at least gets initialized. + Assert.True(true); + } + + [ConditionalFact(Skip = "Test is always skipped.")] + public void ConditionalFactSkip() + { + Assert.True(false, "This test should always be skipped."); + } + +#if NETCOREAPP + [ConditionalFact] + [FrameworkSkipCondition(RuntimeFrameworks.CLR)] + public void ThisTestMustRunOnCoreCLR() + { + Asserter.TestRan = true; + } +#elif NET472 + [ConditionalFact] + [FrameworkSkipCondition(RuntimeFrameworks.CoreCLR)] + public void ThisTestMustRunOnCLR() + { + Asserter.TestRan = true; + } +#else +#error Target frameworks need to be updated. +#endif + + // Test is named this way to be the lowest test in the alphabet, it relies on test ordering + [Fact] + public void ZzzzzzzEnsureThisIsTheLastTest() + { + Assert.True(Asserter.TestRan); + } + + public class ConditionalFactAsserter : IDisposable + { + public bool TestRan { get; set; } + + public void Dispose() + { + } + } + } +} diff --git a/src/Testing/test/ConditionalTheoryTest.cs b/src/Testing/test/ConditionalTheoryTest.cs new file mode 100644 index 0000000000..e88a3334f2 --- /dev/null +++ b/src/Testing/test/ConditionalTheoryTest.cs @@ -0,0 +1,162 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Xunit; +using Xunit.Abstractions; + +namespace Microsoft.AspNetCore.Testing +{ + [TestCaseOrderer("Microsoft.AspNetCore.Testing.AlphabeticalOrderer", "Microsoft.AspNetCore.Testing.Tests")] + public class ConditionalTheoryTest : IClassFixture + { + public ConditionalTheoryTest(ConditionalTheoryAsserter asserter) + { + Asserter = asserter; + } + + public ConditionalTheoryAsserter Asserter { get; } + + [ConditionalTheory(Skip = "Test is always skipped.")] + [InlineData(0)] + public void ConditionalTheorySkip(int arg) + { + Assert.True(false, "This test should always be skipped."); + } + + private static int _conditionalTheoryRuns = 0; + + [ConditionalTheory] + [InlineData(0)] + [InlineData(1)] + [InlineData(2, Skip = "Skip these data")] + public void ConditionalTheoryRunOncePerDataLine(int arg) + { + _conditionalTheoryRuns++; + Assert.True(_conditionalTheoryRuns <= 2, $"Theory should run 2 times, but ran {_conditionalTheoryRuns} times."); + } + + [ConditionalTheory, Trait("Color", "Blue")] + [InlineData(1)] + public void ConditionalTheoriesShouldPreserveTraits(int arg) + { + Assert.True(true); + } + + [ConditionalTheory(Skip = "Skip this")] + [MemberData(nameof(GetInts))] + public void ConditionalTheoriesWithSkippedMemberData(int arg) + { + Assert.True(false, "This should never run"); + } + + private static int _conditionalMemberDataRuns = 0; + + [ConditionalTheory] + [InlineData(4)] + [MemberData(nameof(GetInts))] + public void ConditionalTheoriesWithMemberData(int arg) + { + _conditionalMemberDataRuns++; + Assert.True(_conditionalTheoryRuns <= 3, $"Theory should run 2 times, but ran {_conditionalMemberDataRuns} times."); + } + + public static TheoryData GetInts + => new TheoryData { 0, 1 }; + + [ConditionalTheory] + [OSSkipCondition(OperatingSystems.Windows)] + [OSSkipCondition(OperatingSystems.MacOSX)] + [OSSkipCondition(OperatingSystems.Linux)] + [MemberData(nameof(GetActionTestData))] + public void ConditionalTheoryWithFuncs(Func func) + { + Assert.True(false, "This should never run"); + } + + [Fact] + public void TestAlwaysRun() + { + // This is required to ensure that this type at least gets initialized. + Assert.True(true); + } + +#if NETCOREAPP + [ConditionalTheory] + [FrameworkSkipCondition(RuntimeFrameworks.CLR)] + [MemberData(nameof(GetInts))] + public void ThisTestMustRunOnCoreCLR(int value) + { + Asserter.TestRan = true; + } +#elif NET472 + [ConditionalTheory] + [FrameworkSkipCondition(RuntimeFrameworks.CoreCLR)] + [MemberData(nameof(GetInts))] + public void ThisTestMustRunOnCLR(int value) + { + Asserter.TestRan = true; + } +#else +#error Target frameworks need to be updated. +#endif + + // Test is named this way to be the lowest test in the alphabet, it relies on test ordering + [Fact] + public void ZzzzzzzEnsureThisIsTheLastTest() + { + Assert.True(Asserter.TestRan); + } + + public static TheoryData> GetActionTestData + => new TheoryData> + { + (i) => i * 1 + }; + + public class ConditionalTheoryAsserter : IDisposable + { + public bool TestRan { get; set; } + + public void Dispose() + { + } + } + + [ConditionalTheory] + [MemberData(nameof(SkippableData))] + public void WithSkipableData(Skippable skippable) + { + Assert.Null(skippable.Skip); + Assert.Equal(1, skippable.Data); + } + + public static TheoryData SkippableData => new TheoryData + { + new Skippable() { Data = 1 }, + new Skippable() { Data = 2, Skip = "This row should be skipped." } + }; + + public class Skippable : IXunitSerializable + { + public Skippable() { } + public int Data { get; set; } + public string Skip { get; set; } + + public void Serialize(IXunitSerializationInfo info) + { + info.AddValue(nameof(Data), Data, typeof(int)); + } + + public void Deserialize(IXunitSerializationInfo info) + { + Data = info.GetValue(nameof(Data)); + } + + public override string ToString() + { + return Data.ToString(); + } + } + } +} diff --git a/src/Testing/test/DockerTests.cs b/src/Testing/test/DockerTests.cs new file mode 100644 index 0000000000..12735057d3 --- /dev/null +++ b/src/Testing/test/DockerTests.cs @@ -0,0 +1,21 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Runtime.InteropServices; +using Microsoft.AspNetCore.Testing; +using Xunit; + +namespace Microsoft.AspNetCore.Testing +{ + public class DockerTests + { + [ConditionalFact] + [DockerOnly] + [Trait("Docker", "true")] + public void DoesNotRunOnWindows() + { + Assert.False(RuntimeInformation.IsOSPlatform(OSPlatform.Windows)); + } + } +} diff --git a/src/Testing/test/EnvironmentVariableSkipConditionTest.cs b/src/Testing/test/EnvironmentVariableSkipConditionTest.cs new file mode 100644 index 0000000000..cbc8e9adad --- /dev/null +++ b/src/Testing/test/EnvironmentVariableSkipConditionTest.cs @@ -0,0 +1,173 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Xunit; + +namespace Microsoft.AspNetCore.Testing +{ + public class EnvironmentVariableSkipConditionTest + { + private readonly string _skipReason = "Test skipped on environment variable with name '{0}' and value '{1}'" + + $" for the '{nameof(EnvironmentVariableSkipConditionAttribute.RunOnMatch)}' value of '{{2}}'."; + + [Theory] + [InlineData("false")] + [InlineData("")] + [InlineData(null)] + public void IsMet_DoesNotMatch(string environmentVariableValue) + { + // Arrange + var attribute = new EnvironmentVariableSkipConditionAttribute( + new TestEnvironmentVariable("Run", environmentVariableValue), + "Run", + "true"); + + // Act + var isMet = attribute.IsMet; + + // Assert + Assert.False(isMet); + } + + [Theory] + [InlineData("True")] + [InlineData("TRUE")] + [InlineData("true")] + public void IsMet_DoesCaseInsensitiveMatch_OnValue(string environmentVariableValue) + { + // Arrange + var attribute = new EnvironmentVariableSkipConditionAttribute( + new TestEnvironmentVariable("Run", environmentVariableValue), + "Run", + "true"); + + // Act + var isMet = attribute.IsMet; + + // Assert + Assert.True(isMet); + Assert.Equal( + string.Format(_skipReason, "Run", environmentVariableValue, attribute.RunOnMatch), + attribute.SkipReason); + } + + [Fact] + public void IsMet_DoesSuccessfulMatch_OnNull() + { + // Arrange + var attribute = new EnvironmentVariableSkipConditionAttribute( + new TestEnvironmentVariable("Run", null), + "Run", + "true", null); // skip the test when the variable 'Run' is explicitly set to 'true' or is null (default) + + // Act + var isMet = attribute.IsMet; + + // Assert + Assert.True(isMet); + Assert.Equal( + string.Format(_skipReason, "Run", "(null)", attribute.RunOnMatch), + attribute.SkipReason); + } + + [Theory] + [InlineData("false")] + [InlineData("")] + [InlineData(null)] + public void IsMet_MatchesOnMultipleSkipValues(string environmentVariableValue) + { + // Arrange + var attribute = new EnvironmentVariableSkipConditionAttribute( + new TestEnvironmentVariable("Run", environmentVariableValue), + "Run", + "false", "", null); + + // Act + var isMet = attribute.IsMet; + + // Assert + Assert.True(isMet); + } + + [Fact] + public void IsMet_DoesNotMatch_OnMultipleSkipValues() + { + // Arrange + var attribute = new EnvironmentVariableSkipConditionAttribute( + new TestEnvironmentVariable("Build", "100"), + "Build", + "125", "126"); + + // Act + var isMet = attribute.IsMet; + + // Assert + Assert.False(isMet); + } + + [Theory] + [InlineData("CentOS")] + [InlineData(null)] + [InlineData("")] + public void IsMet_Matches_WhenRunOnMatchIsFalse(string environmentVariableValue) + { + // Arrange + var attribute = new EnvironmentVariableSkipConditionAttribute( + new TestEnvironmentVariable("LinuxFlavor", environmentVariableValue), + "LinuxFlavor", + "Ubuntu14.04") + { + // Example: Run this test on all OSes except on "Ubuntu14.04" + RunOnMatch = false + }; + + // Act + var isMet = attribute.IsMet; + + // Assert + Assert.True(isMet); + } + + [Fact] + public void IsMet_DoesNotMatch_WhenRunOnMatchIsFalse() + { + // Arrange + var attribute = new EnvironmentVariableSkipConditionAttribute( + new TestEnvironmentVariable("LinuxFlavor", "Ubuntu14.04"), + "LinuxFlavor", + "Ubuntu14.04") + { + // Example: Run this test on all OSes except on "Ubuntu14.04" + RunOnMatch = false + }; + + // Act + var isMet = attribute.IsMet; + + // Assert + Assert.False(isMet); + } + + private struct TestEnvironmentVariable : IEnvironmentVariable + { + private readonly string _varName; + + public TestEnvironmentVariable(string varName, string value) + { + _varName = varName; + Value = value; + } + + public string Value { get; private set; } + + public string Get(string name) + { + if(string.Equals(name, _varName, System.StringComparison.OrdinalIgnoreCase)) + { + return Value; + } + return string.Empty; + } + } + } +} diff --git a/src/Testing/test/ExceptionAssertTest.cs b/src/Testing/test/ExceptionAssertTest.cs new file mode 100644 index 0000000000..aa7354dca8 --- /dev/null +++ b/src/Testing/test/ExceptionAssertTest.cs @@ -0,0 +1,39 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Xunit; + +namespace Microsoft.AspNetCore.Testing +{ + public class ExceptionAssertTest + { + [Fact] + [ReplaceCulture("fr-FR", "fr-FR")] + public void AssertArgumentNullOrEmptyString_WorksInNonEnglishCultures() + { + // Arrange + Action action = () => + { + throw new ArgumentException("Value cannot be null or an empty string.", "foo"); + }; + + // Act and Assert + ExceptionAssert.ThrowsArgumentNullOrEmptyString(action, "foo"); + } + + [Fact] + [ReplaceCulture("fr-FR", "fr-FR")] + public void AssertArgumentOutOfRangeException_WorksInNonEnglishCultures() + { + // Arrange + Action action = () => + { + throw new ArgumentOutOfRangeException("foo", 10, "exception message."); + }; + + // Act and Assert + ExceptionAssert.ThrowsArgumentOutOfRange(action, "foo", "exception message.", 10); + } + } +} \ No newline at end of file diff --git a/src/Testing/test/HttpClientSlimTest.cs b/src/Testing/test/HttpClientSlimTest.cs new file mode 100644 index 0000000000..ede48243e5 --- /dev/null +++ b/src/Testing/test/HttpClientSlimTest.cs @@ -0,0 +1,116 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Net; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace Microsoft.AspNetCore.Testing +{ + public class HttpClientSlimTest + { + private static readonly byte[] _defaultResponse = Encoding.ASCII.GetBytes("test"); + + [Fact] + public async Task GetStringAsyncHttp() + { + using (var host = StartHost(out var address)) + { + Assert.Equal("test", await HttpClientSlim.GetStringAsync(address)); + } + } + + [Fact] + public async Task GetStringAsyncThrowsForErrorResponse() + { + using (var host = StartHost(out var address, statusCode: 500)) + { + await Assert.ThrowsAnyAsync(() => HttpClientSlim.GetStringAsync(address)); + } + } + + [Fact] + public async Task PostAsyncHttp() + { + using (var host = StartHost(out var address, handler: context => context.Request.InputStream.CopyToAsync(context.Response.OutputStream))) + { + Assert.Equal("test post", await HttpClientSlim.PostAsync(address, new StringContent("test post"))); + } + } + + [Fact] + public async Task PostAsyncThrowsForErrorResponse() + { + using (var host = StartHost(out var address, statusCode: 500)) + { + await Assert.ThrowsAnyAsync( + () => HttpClientSlim.PostAsync(address, new StringContent(""))); + } + } + + [Fact] + public void Ipv6ScopeIdsFilteredOut() + { + var requestUri = new Uri("http://[fe80::5d2a:d070:6fd6:1bac%7]:5003/"); + Assert.Equal("[fe80::5d2a:d070:6fd6:1bac]:5003", HttpClientSlim.GetHost(requestUri)); + } + + [Fact] + public void GetHostExcludesDefaultPort() + { + var requestUri = new Uri("http://[fe80::5d2a:d070:6fd6:1bac%7]:80/"); + Assert.Equal("[fe80::5d2a:d070:6fd6:1bac]", HttpClientSlim.GetHost(requestUri)); + } + + private HttpListener StartHost(out string address, int statusCode = 200, Func handler = null) + { + var listener = new HttpListener(); + var random = new Random(); + address = null; + + for (var i = 0; i < 10; i++) + { + try + { + // HttpListener doesn't support requesting port 0 (dynamic). + // Requesting port 0 from Sockets and then passing that to HttpListener is racy. + // Just keep trying until we find a free one. + address = $"http://localhost:{random.Next(1024, ushort.MaxValue)}/"; + listener.Prefixes.Add(address); + listener.Start(); + break; + } + catch (HttpListenerException) + { + // Address in use + listener.Close(); + listener = new HttpListener(); + } + } + + Assert.True(listener.IsListening, "IsListening"); + + _ = listener.GetContextAsync().ContinueWith(async task => + { + var context = task.Result; + context.Response.StatusCode = statusCode; + + if (handler == null) + { + await context.Response.OutputStream.WriteAsync(_defaultResponse, 0, _defaultResponse.Length); + } + else + { + await handler(context); + } + + context.Response.Close(); + }); + + return listener; + } + } +} diff --git a/src/Testing/test/LoggedTestXunitTests.cs b/src/Testing/test/LoggedTestXunitTests.cs new file mode 100644 index 0000000000..61d7802508 --- /dev/null +++ b/src/Testing/test/LoggedTestXunitTests.cs @@ -0,0 +1,193 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Linq; +using System.Reflection; +using Microsoft.AspNetCore.Testing; +using Microsoft.Extensions.DependencyInjection; +using Xunit; +using Xunit.Abstractions; + +namespace Microsoft.Extensions.Logging.Testing.Tests +{ + [LogLevel(LogLevel.Debug)] + [ShortClassName] + public class LoggedTestXunitTests : TestLoggedTest + { + private readonly ITestOutputHelper _output; + + public LoggedTestXunitTests(ITestOutputHelper output) + { + _output = output; + } + + [Fact] + public void LoggedFactInitializesLoggedTestProperties() + { + Assert.NotNull(Logger); + Assert.NotNull(LoggerFactory); + Assert.NotNull(TestSink); + Assert.NotNull(TestOutputHelper); + } + + [Theory] + [InlineData("Hello world")] + public void LoggedTheoryInitializesLoggedTestProperties(string argument) + { + Assert.NotNull(Logger); + Assert.NotNull(LoggerFactory); + Assert.NotNull(TestSink); + Assert.NotNull(TestOutputHelper); + // Use the test argument + Assert.NotNull(argument); + } + + [ConditionalFact] + public void ConditionalLoggedFactGetsInitializedLoggerFactory() + { + Assert.NotNull(Logger); + Assert.NotNull(LoggerFactory); + Assert.NotNull(TestSink); + Assert.NotNull(TestOutputHelper); + } + + [ConditionalTheory] + [InlineData("Hello world")] + public void LoggedConditionalTheoryInitializesLoggedTestProperties(string argument) + { + Assert.NotNull(Logger); + Assert.NotNull(LoggerFactory); + Assert.NotNull(TestSink); + Assert.NotNull(TestOutputHelper); + // Use the test argument + Assert.NotNull(argument); + } + + [Fact] + [LogLevel(LogLevel.Information)] + public void LoggedFactFilteredByMethodLogLevel() + { + Logger.LogInformation("Information"); + Logger.LogDebug("Debug"); + + var message = Assert.Single(TestSink.Writes); + Assert.Equal(LogLevel.Information, message.LogLevel); + Assert.Equal("Information", message.Formatter(message.State, null)); + } + + [Fact] + public void LoggedFactFilteredByClassLogLevel() + { + Logger.LogDebug("Debug"); + Logger.LogTrace("Trace"); + + var message = Assert.Single(TestSink.Writes); + Assert.Equal(LogLevel.Debug, message.LogLevel); + Assert.Equal("Debug", message.Formatter(message.State, null)); + } + + [Theory] + [InlineData("Hello world")] + [LogLevel(LogLevel.Information)] + public void LoggedTheoryFilteredByLogLevel(string argument) + { + Logger.LogInformation("Information"); + Logger.LogDebug("Debug"); + + var message = Assert.Single(TestSink.Writes); + Assert.Equal(LogLevel.Information, message.LogLevel); + Assert.Equal("Information", message.Formatter(message.State, null)); + + // Use the test argument + Assert.NotNull(argument); + } + + [Fact] + public void AddTestLoggingUpdatedWhenLoggerFactoryIsSet() + { + var loggerFactory = new LoggerFactory(); + var serviceCollection = new ServiceCollection(); + + LoggerFactory = loggerFactory; + AddTestLogging(serviceCollection); + + Assert.Same(loggerFactory, serviceCollection.BuildServiceProvider().GetRequiredService()); + } + + [ConditionalTheory] + [EnvironmentVariableSkipCondition("ASPNETCORE_TEST_LOG_DIR", "")] // The test name is only generated when logging is enabled via the environment variable + [InlineData(null)] + public void LoggedTheoryNullArgumentsAreEscaped(string argument) + { + Assert.NotNull(LoggerFactory); + Assert.Equal($"{nameof(LoggedTheoryNullArgumentsAreEscaped)}_null", ResolvedTestMethodName); + // Use the test argument + Assert.Null(argument); + } + + [Fact] + public void AdditionalSetupInvoked() + { + Assert.True(SetupInvoked); + } + + [Fact] + public void MessageWrittenEventInvoked() + { + WriteContext context = null; + TestSink.MessageLogged += ctx => context = ctx; + Logger.LogInformation("Information"); + Assert.Equal(TestSink.Writes.Single(), context); + } + + [Fact] + public void ScopeStartedEventInvoked() + { + BeginScopeContext context = null; + TestSink.ScopeStarted += ctx => context = ctx; + using (Logger.BeginScope("Scope")) {} + Assert.Equal(TestSink.Scopes.Single(), context); + } + } + + public class LoggedTestXunitLogLevelTests : LoggedTest + { + [Fact] + public void LoggedFactFilteredByAssemblyLogLevel() + { + Logger.LogTrace("Trace"); + + var message = Assert.Single(TestSink.Writes); + Assert.Equal(LogLevel.Trace, message.LogLevel); + Assert.Equal("Trace", message.Formatter(message.State, null)); + } + } + + public class LoggedTestXunitInitializationTests : TestLoggedTest + { + [Fact] + public void ITestOutputHelperInitializedByDefault() + { + Assert.True(ITestOutputHelperIsInitialized); + } + } + + public class TestLoggedTest : LoggedTest + { + public bool SetupInvoked { get; private set; } = false; + public bool ITestOutputHelperIsInitialized { get; private set; } = false; + + public override void Initialize(TestContext context, MethodInfo methodInfo, object[] testMethodArguments, ITestOutputHelper testOutputHelper) + { + base.Initialize(context, methodInfo, testMethodArguments, testOutputHelper); + + try + { + TestOutputHelper.WriteLine("Test"); + ITestOutputHelperIsInitialized = true; + } catch { } + SetupInvoked = true; + } + } +} diff --git a/src/Testing/test/MaximumOSVersionAttributeTest.cs b/src/Testing/test/MaximumOSVersionAttributeTest.cs new file mode 100644 index 0000000000..ca71d7063b --- /dev/null +++ b/src/Testing/test/MaximumOSVersionAttributeTest.cs @@ -0,0 +1,89 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Xunit; + +namespace Microsoft.AspNetCore.Testing +{ + public class MaximumOSVersionAttributeTest + { + [Fact] + public void Linux_ThrowsNotImplemeneted() + { + Assert.Throws(() => new MaximumOSVersionAttribute(OperatingSystems.Linux, "2.5")); + } + + [Fact] + public void Mac_ThrowsNotImplemeneted() + { + Assert.Throws(() => new MaximumOSVersionAttribute(OperatingSystems.MacOSX, "2.5")); + } + + [Fact] + public void WindowsOrLinux_ThrowsNotImplemeneted() + { + Assert.Throws(() => new MaximumOSVersionAttribute(OperatingSystems.Linux | OperatingSystems.Windows, "2.5")); + } + + [Fact] + public void DoesNotSkip_ShortVersions() + { + var osSkipAttribute = new MaximumOSVersionAttribute( + OperatingSystems.Windows, + new Version("2.5"), + OperatingSystems.Windows, + new Version("2.0")); + + Assert.True(osSkipAttribute.IsMet); + } + + [Fact] + public void DoesNotSkip_EarlierVersions() + { + var osSkipAttribute = new MaximumOSVersionAttribute( + OperatingSystems.Windows, + new Version("2.5.9"), + OperatingSystems.Windows, + new Version("2.0.10.12")); + + Assert.True(osSkipAttribute.IsMet); + } + + [Fact] + public void DoesNotSkip_SameVersion() + { + var osSkipAttribute = new MaximumOSVersionAttribute( + OperatingSystems.Windows, + new Version("2.5.10"), + OperatingSystems.Windows, + new Version("2.5.10.12")); + + Assert.True(osSkipAttribute.IsMet); + } + + [Fact] + public void Skip_LaterVersion() + { + var osSkipAttribute = new MaximumOSVersionAttribute( + OperatingSystems.Windows, + new Version("2.5.11"), + OperatingSystems.Windows, + new Version("3.0.10.12")); + + Assert.False(osSkipAttribute.IsMet); + } + + [Fact] + public void DoesNotSkip_WhenOnlyVersionsMatch() + { + var osSkipAttribute = new MaximumOSVersionAttribute( + OperatingSystems.Windows, + new Version("2.5.10.12"), + OperatingSystems.Linux, + new Version("2.5.10.12")); + + Assert.True(osSkipAttribute.IsMet); + } + } +} diff --git a/src/Testing/test/MaximumOSVersionTest.cs b/src/Testing/test/MaximumOSVersionTest.cs new file mode 100644 index 0000000000..e18d828fbf --- /dev/null +++ b/src/Testing/test/MaximumOSVersionTest.cs @@ -0,0 +1,90 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Runtime.InteropServices; +using Microsoft.Win32; +using Xunit; + +namespace Microsoft.AspNetCore.Testing +{ + [OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX)] + public class MaximumOSVersionTest + { + [ConditionalFact] + [MaximumOSVersion(OperatingSystems.Windows, WindowsVersions.Win7)] + public void RunTest_Win7DoesRunOnWin7() + { + Assert.True( + RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && + Environment.OSVersion.Version.ToString().StartsWith("6.1"), + "Test should only be running on Win7 or Win2008R2."); + } + + [ConditionalTheory] + [MaximumOSVersion(OperatingSystems.Windows, WindowsVersions.Win7)] + [InlineData(1)] + public void RunTheory_Win7DoesRunOnWin7(int arg) + { + Assert.True( + RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && + Environment.OSVersion.Version.ToString().StartsWith("6.1"), + "Test should only be running on Win7 or Win2008R2."); + } + + [ConditionalFact] + [MaximumOSVersion(OperatingSystems.Windows, WindowsVersions.Win10_RS4)] + [OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX)] + public void RunTest_Win10_RS4() + { + Assert.True(RuntimeInformation.IsOSPlatform(OSPlatform.Windows)); + var versionKey = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion"); + Assert.NotNull(versionKey); + var currentVersion = (string)versionKey.GetValue("CurrentBuildNumber"); + Assert.NotNull(currentVersion); + Assert.True(17134 >= int.Parse(currentVersion)); + } + + [ConditionalFact] + [MaximumOSVersion(OperatingSystems.Windows, WindowsVersions.Win10_19H2)] + [OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX)] + public void RunTest_Win10_19H2() + { + Assert.True(RuntimeInformation.IsOSPlatform(OSPlatform.Windows)); + var versionKey = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion"); + Assert.NotNull(versionKey); + var currentVersion = (string)versionKey.GetValue("CurrentBuildNumber"); + Assert.NotNull(currentVersion); + Assert.True(18363 >= int.Parse(currentVersion)); + } + } + + [MaximumOSVersion(OperatingSystems.Windows, WindowsVersions.Win7)] + [OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX)] + public class OSMaxVersionClassTest + { + [ConditionalFact] + public void TestSkipClass_Win7DoesRunOnWin7() + { + Assert.True( + RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && + Environment.OSVersion.Version.ToString().StartsWith("6.1"), + "Test should only be running on Win7 or Win2008R2."); + } + } + + // Let this one run cross plat just to check the constructor logic. + [MaximumOSVersion(OperatingSystems.Windows, WindowsVersions.Win7)] + public class OSMaxVersionCrossPlatTest + { + [ConditionalFact] + public void TestSkipClass_Win7DoesRunOnWin7() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + Assert.True(Environment.OSVersion.Version.ToString().StartsWith("6.1"), + "Test should only be running on Win7 or Win2008R2."); + } + } + } +} diff --git a/src/Testing/test/Microsoft.AspNetCore.Testing.Tests.csproj b/src/Testing/test/Microsoft.AspNetCore.Testing.Tests.csproj new file mode 100644 index 0000000000..a203e1289d --- /dev/null +++ b/src/Testing/test/Microsoft.AspNetCore.Testing.Tests.csproj @@ -0,0 +1,23 @@ + + + $(DefaultNetCoreTargetFramework);net472 + + + $(NoWarn);xUnit1004 + + $(NoWarn);xUnit1026 + + + + + + + + + + + + + + + diff --git a/src/Testing/test/MinimumOSVersionAttributeTest.cs b/src/Testing/test/MinimumOSVersionAttributeTest.cs new file mode 100644 index 0000000000..a0a6e84d7d --- /dev/null +++ b/src/Testing/test/MinimumOSVersionAttributeTest.cs @@ -0,0 +1,77 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Xunit; + +namespace Microsoft.AspNetCore.Testing +{ + public class MinimumOSVersionAttributeTest + { + [Fact] + public void Linux_ThrowsNotImplemeneted() + { + Assert.Throws(() => new MinimumOSVersionAttribute(OperatingSystems.Linux, "2.5")); + } + + [Fact] + public void Mac_ThrowsNotImplemeneted() + { + Assert.Throws(() => new MinimumOSVersionAttribute(OperatingSystems.MacOSX, "2.5")); + } + + [Fact] + public void WindowsOrLinux_ThrowsNotImplemeneted() + { + Assert.Throws(() => new MinimumOSVersionAttribute(OperatingSystems.Linux | OperatingSystems.Windows, "2.5")); + } + + [Fact] + public void DoesNotSkip_LaterVersions() + { + var osSkipAttribute = new MinimumOSVersionAttribute( + OperatingSystems.Windows, + new Version("2.0"), + OperatingSystems.Windows, + new Version("2.5")); + + Assert.True(osSkipAttribute.IsMet); + } + + [Fact] + public void DoesNotSkip_SameVersion() + { + var osSkipAttribute = new MinimumOSVersionAttribute( + OperatingSystems.Windows, + new Version("2.5"), + OperatingSystems.Windows, + new Version("2.5")); + + Assert.True(osSkipAttribute.IsMet); + } + + [Fact] + public void Skip_EarlierVersion() + { + var osSkipAttribute = new MinimumOSVersionAttribute( + OperatingSystems.Windows, + new Version("3.0"), + OperatingSystems.Windows, + new Version("2.5")); + + Assert.False(osSkipAttribute.IsMet); + } + + [Fact] + public void DoesNotSkip_WhenOnlyVersionsMatch() + { + var osSkipAttribute = new MinimumOSVersionAttribute( + OperatingSystems.Windows, + new Version("2.5"), + OperatingSystems.Linux, + new Version("2.5")); + + Assert.True(osSkipAttribute.IsMet); + } + } +} diff --git a/src/Testing/test/MinimumOSVersionTest.cs b/src/Testing/test/MinimumOSVersionTest.cs new file mode 100644 index 0000000000..b218cd1ec5 --- /dev/null +++ b/src/Testing/test/MinimumOSVersionTest.cs @@ -0,0 +1,73 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Runtime.InteropServices; +using Microsoft.Win32; +using Xunit; + +namespace Microsoft.AspNetCore.Testing +{ + public class MinimumOSVersionTest + { + [ConditionalFact] + [MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win8)] + public void RunTest_Win8DoesNotRunOnWin7() + { + Assert.False( + RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && + Environment.OSVersion.Version.ToString().StartsWith("6.1"), + "Test should not be running on Win7 or Win2008R2."); + } + + [ConditionalTheory] + [MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win8)] + [InlineData(1)] + public void RunTheory_Win8DoesNotRunOnWin7(int arg) + { + Assert.False( + RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && + Environment.OSVersion.Version.ToString().StartsWith("6.1"), + "Test should not be running on Win7 or Win2008R2."); + } + + [ConditionalFact] + [MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win10_RS4)] + [OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX)] + public void RunTest_Win10_RS4() + { + Assert.True(RuntimeInformation.IsOSPlatform(OSPlatform.Windows)); + var versionKey = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion"); + Assert.NotNull(versionKey); + var currentVersion = (string)versionKey.GetValue("CurrentBuildNumber"); + Assert.NotNull(currentVersion); + Assert.True(17134 <= int.Parse(currentVersion)); + } + + [ConditionalFact] + [MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win10_19H2)] + [OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX)] + public void RunTest_Win10_19H2() + { + Assert.True(RuntimeInformation.IsOSPlatform(OSPlatform.Windows)); + var versionKey = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion"); + Assert.NotNull(versionKey); + var currentVersion = (string)versionKey.GetValue("CurrentBuildNumber"); + Assert.NotNull(currentVersion); + Assert.True(18363 <= int.Parse(currentVersion)); + } + } + + [MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win8)] + public class OSMinVersionClassTest + { + [ConditionalFact] + public void TestSkipClass_Win8DoesNotRunOnWin7() + { + Assert.False( + RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && + Environment.OSVersion.Version.ToString().StartsWith("6.1"), + "Test should not be running on Win7 or Win2008R2."); + } + } +} diff --git a/src/Testing/test/OSSkipConditionAttributeTest.cs b/src/Testing/test/OSSkipConditionAttributeTest.cs new file mode 100644 index 0000000000..d4bc4f2b74 --- /dev/null +++ b/src/Testing/test/OSSkipConditionAttributeTest.cs @@ -0,0 +1,72 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Runtime.InteropServices; +using Xunit; + +namespace Microsoft.AspNetCore.Testing +{ + public class OSSkipConditionAttributeTest + { + [Fact] + public void Skips_WhenOperatingSystemMatches() + { + // Act + var osSkipAttribute = new OSSkipConditionAttribute( + OperatingSystems.Windows, + OperatingSystems.Windows); + + // Assert + Assert.False(osSkipAttribute.IsMet); + } + + [Fact] + public void DoesNotSkip_WhenOperatingSystemDoesNotMatch() + { + // Act + var osSkipAttribute = new OSSkipConditionAttribute( + OperatingSystems.Linux, + OperatingSystems.Windows); + + // Assert + Assert.True(osSkipAttribute.IsMet); + } + + [Fact] + public void Skips_BothMacOSXAndLinux() + { + // Act + var osSkipAttributeLinux = new OSSkipConditionAttribute(OperatingSystems.Linux | OperatingSystems.MacOSX, OperatingSystems.Linux); + var osSkipAttributeMacOSX = new OSSkipConditionAttribute(OperatingSystems.Linux | OperatingSystems.MacOSX, OperatingSystems.MacOSX); + + // Assert + Assert.False(osSkipAttributeLinux.IsMet); + Assert.False(osSkipAttributeMacOSX.IsMet); + } + + [Fact] + public void Skips_BothMacOSXAndWindows() + { + // Act + var osSkipAttribute = new OSSkipConditionAttribute(OperatingSystems.Windows | OperatingSystems.MacOSX, OperatingSystems.Windows); + var osSkipAttributeMacOSX = new OSSkipConditionAttribute(OperatingSystems.Windows | OperatingSystems.MacOSX, OperatingSystems.MacOSX); + + // Assert + Assert.False(osSkipAttribute.IsMet); + Assert.False(osSkipAttributeMacOSX.IsMet); + } + + [Fact] + public void Skips_BothWindowsAndLinux() + { + // Act + var osSkipAttribute = new OSSkipConditionAttribute(OperatingSystems.Linux | OperatingSystems.Windows, OperatingSystems.Windows); + var osSkipAttributeLinux = new OSSkipConditionAttribute(OperatingSystems.Linux | OperatingSystems.Windows, OperatingSystems.Linux); + + // Assert + Assert.False(osSkipAttribute.IsMet); + Assert.False(osSkipAttributeLinux.IsMet); + } + } +} diff --git a/src/Testing/test/OSSkipConditionTest.cs b/src/Testing/test/OSSkipConditionTest.cs new file mode 100644 index 0000000000..6aeecaddcc --- /dev/null +++ b/src/Testing/test/OSSkipConditionTest.cs @@ -0,0 +1,105 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Runtime.InteropServices; +using Xunit; + +namespace Microsoft.AspNetCore.Testing +{ + public class OSSkipConditionTest + { + [ConditionalFact] + [OSSkipCondition(OperatingSystems.Linux)] + public void TestSkipLinux() + { + Assert.False( + RuntimeInformation.IsOSPlatform(OSPlatform.Linux), + "Test should not be running on Linux"); + } + + [ConditionalFact] + [OSSkipCondition(OperatingSystems.MacOSX)] + public void TestSkipMacOSX() + { + Assert.False( + RuntimeInformation.IsOSPlatform(OSPlatform.OSX), + "Test should not be running on MacOSX."); + } + + [ConditionalFact] + [OSSkipCondition(OperatingSystems.Windows)] + public void TestSkipWindows() + { + Assert.False( + RuntimeInformation.IsOSPlatform(OSPlatform.Windows), + "Test should not be running on Windows."); + } + + [ConditionalFact] + [OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX)] + public void TestSkipLinuxAndMacOSX() + { + Assert.False( + RuntimeInformation.IsOSPlatform(OSPlatform.Linux), + "Test should not be running on Linux."); + Assert.False( + RuntimeInformation.IsOSPlatform(OSPlatform.OSX), + "Test should not be running on MacOSX."); + } + + [ConditionalTheory] + [OSSkipCondition(OperatingSystems.Linux)] + [InlineData(1)] + public void TestTheorySkipLinux(int arg) + { + Assert.False( + RuntimeInformation.IsOSPlatform(OSPlatform.Linux), + "Test should not be running on Linux"); + } + + [ConditionalTheory] + [OSSkipCondition(OperatingSystems.MacOSX)] + [InlineData(1)] + public void TestTheorySkipMacOS(int arg) + { + Assert.False( + RuntimeInformation.IsOSPlatform(OSPlatform.OSX), + "Test should not be running on MacOSX."); + } + + [ConditionalTheory] + [OSSkipCondition(OperatingSystems.Windows)] + [InlineData(1)] + public void TestTheorySkipWindows(int arg) + { + Assert.False( + RuntimeInformation.IsOSPlatform(OSPlatform.Windows), + "Test should not be running on Windows."); + } + + [ConditionalTheory] + [OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX)] + [InlineData(1)] + public void TestTheorySkipLinuxAndMacOSX(int arg) + { + Assert.False( + RuntimeInformation.IsOSPlatform(OSPlatform.Linux), + "Test should not be running on Linux."); + Assert.False( + RuntimeInformation.IsOSPlatform(OSPlatform.OSX), + "Test should not be running on MacOSX."); + } + } + + [OSSkipCondition(OperatingSystems.Windows)] + public class OSSkipConditionClassTest + { + [ConditionalFact] + public void TestSkipClassWindows() + { + Assert.False( + RuntimeInformation.IsOSPlatform(OSPlatform.Windows), + "Test should not be running on Windows."); + } + } +} diff --git a/src/Testing/test/Properties/AssemblyInfo.cs b/src/Testing/test/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..417cd2d3fd --- /dev/null +++ b/src/Testing/test/Properties/AssemblyInfo.cs @@ -0,0 +1,8 @@ +using Microsoft.AspNetCore.Testing; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Testing; +using Xunit; + +[assembly: Repeat(1)] +[assembly: LogLevel(LogLevel.Trace)] +[assembly: AssemblyFixture(typeof(TestAssemblyFixture))] diff --git a/src/Testing/test/QuarantinedTestAttributeTest.cs b/src/Testing/test/QuarantinedTestAttributeTest.cs new file mode 100644 index 0000000000..5fc6c58041 --- /dev/null +++ b/src/Testing/test/QuarantinedTestAttributeTest.cs @@ -0,0 +1,22 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using Xunit; + +namespace Microsoft.AspNetCore.Testing.Tests +{ + public class QuarantinedTestAttributeTest + { + [Fact(Skip = "These tests are nice when you need them but annoying when on all the time.")] + [QuarantinedTest] + public void AlwaysFlakyInCI() + { + if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("HELIX")) || !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("AGENT_OS"))) + { + throw new Exception("Flaky!"); + } + } + } +} diff --git a/src/Testing/test/RepeatTest.cs b/src/Testing/test/RepeatTest.cs new file mode 100644 index 0000000000..0d995fad59 --- /dev/null +++ b/src/Testing/test/RepeatTest.cs @@ -0,0 +1,43 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Xunit; + +namespace Microsoft.AspNetCore.Testing +{ + [Repeat] + public class RepeatTest + { + public static int _runCount = 0; + + [Fact] + [Repeat(5)] + public void RepeatLimitIsSetCorrectly() + { + Assert.Equal(5, RepeatContext.Current.Limit); + } + + [Fact] + [Repeat(5)] + public void RepeatRunsTestSpecifiedNumberOfTimes() + { + Assert.Equal(RepeatContext.Current.CurrentIteration, _runCount); + _runCount++; + } + + [Fact] + public void RepeatCanBeSetOnClass() + { + Assert.Equal(10, RepeatContext.Current.Limit); + } + } + + public class LoggedTestXunitRepeatAssemblyTests + { + [Fact] + public void RepeatCanBeSetOnAssembly() + { + Assert.Equal(1, RepeatContext.Current.Limit); + } + } +} diff --git a/src/Testing/test/ReplaceCultureAttributeTest.cs b/src/Testing/test/ReplaceCultureAttributeTest.cs new file mode 100644 index 0000000000..6b8df346c9 --- /dev/null +++ b/src/Testing/test/ReplaceCultureAttributeTest.cs @@ -0,0 +1,66 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Globalization; +using Xunit; + +namespace Microsoft.AspNetCore.Testing +{ + public class RepalceCultureAttributeTest + { + [Fact] + public void DefaultsTo_EnGB_EnUS() + { + // Arrange + var culture = new CultureInfo("en-GB"); + var uiCulture = new CultureInfo("en-US"); + + // Act + var replaceCulture = new ReplaceCultureAttribute(); + + // Assert + Assert.Equal(culture, replaceCulture.Culture); + Assert.Equal(uiCulture, replaceCulture.UICulture); + } + + [Fact] + public void UsesSuppliedCultureAndUICulture() + { + // Arrange + var culture = "de-DE"; + var uiCulture = "fr-CA"; + + // Act + var replaceCulture = new ReplaceCultureAttribute(culture, uiCulture); + + // Assert + Assert.Equal(new CultureInfo(culture), replaceCulture.Culture); + Assert.Equal(new CultureInfo(uiCulture), replaceCulture.UICulture); + } + + [Fact] + public void BeforeAndAfterTest_ReplacesCulture() + { + // Arrange + var originalCulture = CultureInfo.CurrentCulture; + var originalUICulture = CultureInfo.CurrentUICulture; + var culture = "de-DE"; + var uiCulture = "fr-CA"; + var replaceCulture = new ReplaceCultureAttribute(culture, uiCulture); + + // Act + replaceCulture.Before(methodUnderTest: null); + + // Assert + Assert.Equal(new CultureInfo(culture), CultureInfo.CurrentCulture); + Assert.Equal(new CultureInfo(uiCulture), CultureInfo.CurrentUICulture); + + // Act + replaceCulture.After(methodUnderTest: null); + + // Assert + Assert.Equal(originalCulture, CultureInfo.CurrentCulture); + Assert.Equal(originalUICulture, CultureInfo.CurrentUICulture); + } + } +} \ No newline at end of file diff --git a/src/Testing/test/SkipOnCITests.cs b/src/Testing/test/SkipOnCITests.cs new file mode 100644 index 0000000000..8df5e73c30 --- /dev/null +++ b/src/Testing/test/SkipOnCITests.cs @@ -0,0 +1,22 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNetCore.Testing; +using Xunit; + +namespace Microsoft.AspNetCore.Testing.Tests +{ + public class SkipOnCITests + { + [ConditionalFact] + [SkipOnCI] + public void AlwaysSkipOnCI() + { + if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("HELIX")) || !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("AGENT_OS"))) + { + throw new Exception("Flaky!"); + } + } + } +} diff --git a/src/Testing/test/TaskExtensionsTest.cs b/src/Testing/test/TaskExtensionsTest.cs new file mode 100644 index 0000000000..f7ad603df5 --- /dev/null +++ b/src/Testing/test/TaskExtensionsTest.cs @@ -0,0 +1,18 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Threading.Tasks; +using Xunit; + +namespace Microsoft.AspNetCore.Testing +{ + public class TaskExtensionsTest + { + [Fact] + public async Task TimeoutAfterTest() + { + await Assert.ThrowsAsync(async () => await Task.Delay(1000).TimeoutAfter(TimeSpan.FromMilliseconds(50))); + } + } +} diff --git a/src/Testing/test/TestAssemblyFixture.cs b/src/Testing/test/TestAssemblyFixture.cs new file mode 100644 index 0000000000..44308160bd --- /dev/null +++ b/src/Testing/test/TestAssemblyFixture.cs @@ -0,0 +1,10 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.AspNetCore.Testing +{ + public class TestAssemblyFixture + { + public int Count { get; set; } + } +} diff --git a/src/Testing/test/TestCollectionFixture.cs b/src/Testing/test/TestCollectionFixture.cs new file mode 100644 index 0000000000..b9aed01e41 --- /dev/null +++ b/src/Testing/test/TestCollectionFixture.cs @@ -0,0 +1,10 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.AspNetCore.Testing +{ + public class TestCollectionFixture + { + public int Count { get; set; } + } +} diff --git a/src/Testing/test/TestContextTest.cs b/src/Testing/test/TestContextTest.cs new file mode 100644 index 0000000000..944d706477 --- /dev/null +++ b/src/Testing/test/TestContextTest.cs @@ -0,0 +1,83 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace Microsoft.AspNetCore.Testing +{ + public class TestContextTest : ITestMethodLifecycle + { + public TestContext Context { get; private set; } + + [Fact] + public void FullName_IsUsed_ByDefault() + { + Assert.Equal(GetType().FullName, Context.FileOutput.TestClassName); + } + + Task ITestMethodLifecycle.OnTestStartAsync(TestContext context, CancellationToken cancellationToken) + { + Context = context; + return Task.CompletedTask; + } + + Task ITestMethodLifecycle.OnTestEndAsync(TestContext context, Exception exception, CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + } +} + +namespace Microsoft.AspNetCore.Testing.Tests +{ + public class TestContextNameShorteningTest : ITestMethodLifecycle + { + public TestContext Context { get; private set; } + + [Fact] + public void NameIsShortenedWhenAssemblyNameIsAPrefix() + { + Assert.Equal(GetType().Name, Context.FileOutput.TestClassName); + } + + Task ITestMethodLifecycle.OnTestStartAsync(TestContext context, CancellationToken cancellationToken) + { + Context = context; + return Task.CompletedTask; + } + + Task ITestMethodLifecycle.OnTestEndAsync(TestContext context, Exception exception, CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + } +} + +namespace Microsoft.AspNetCore.Testing +{ + [ShortClassName] + public class TestContextTestClassShortNameAttributeTest : ITestMethodLifecycle + { + public TestContext Context { get; private set; } + + [Fact] + public void ShortClassNameUsedWhenShortClassNameAttributeSpecified() + { + Assert.Equal(GetType().Name, Context.FileOutput.TestClassName); + } + + Task ITestMethodLifecycle.OnTestStartAsync(TestContext context, CancellationToken cancellationToken) + { + Context = context; + return Task.CompletedTask; + } + + Task ITestMethodLifecycle.OnTestEndAsync(TestContext context, Exception exception, CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + } +} diff --git a/src/Testing/test/TestPathUtilitiesTest.cs b/src/Testing/test/TestPathUtilitiesTest.cs new file mode 100644 index 0000000000..ff3c18e552 --- /dev/null +++ b/src/Testing/test/TestPathUtilitiesTest.cs @@ -0,0 +1,35 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; +using Xunit; + +namespace Microsoft.AspNetCore.Testing +{ + public class TestPathUtilitiesTest + { + // Entire test pending removal - see https://github.com/dotnet/extensions/issues/1697 +#pragma warning disable 0618 + + [Fact(Skip="https://github.com/dotnet/extensions/issues/1697")] + public void GetSolutionRootDirectory_ResolvesSolutionRoot() + { + // Directory.GetCurrentDirectory() gives: + // Testing\test\Microsoft.AspNetCore.Testing.Tests\bin\Debug\netcoreapp2.0 + // Testing\test\Microsoft.AspNetCore.Testing.Tests\bin\Debug\net461 + // Testing\test\Microsoft.AspNetCore.Testing.Tests\bin\Debug\net46 + var expectedPath = Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), "..", "..", "..", "..", "..")); + + Assert.Equal(expectedPath, TestPathUtilities.GetSolutionRootDirectory("Extensions")); + } + + [Fact] + public void GetSolutionRootDirectory_Throws_IfNotFound() + { + var exception = Assert.Throws(() => TestPathUtilities.GetSolutionRootDirectory("NotTesting")); + Assert.Equal($"Solution file NotTesting.sln could not be found in {AppContext.BaseDirectory} or its parent directories.", exception.Message); + } +#pragma warning restore 0618 + } +} diff --git a/src/Testing/test/TestPlatformHelperTest.cs b/src/Testing/test/TestPlatformHelperTest.cs new file mode 100644 index 0000000000..b1c2fbf2f8 --- /dev/null +++ b/src/Testing/test/TestPlatformHelperTest.cs @@ -0,0 +1,55 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNetCore.Testing; +using Xunit; + +namespace Microsoft.AspNetCore.Testing +{ + public class TestPlatformHelperTest + { + [ConditionalFact] + [OSSkipCondition(OperatingSystems.MacOSX)] + [OSSkipCondition(OperatingSystems.Windows)] + public void IsLinux_TrueOnLinux() + { + Assert.True(TestPlatformHelper.IsLinux); + Assert.False(TestPlatformHelper.IsMac); + Assert.False(TestPlatformHelper.IsWindows); + } + + [ConditionalFact] + [OSSkipCondition(OperatingSystems.Linux)] + [OSSkipCondition(OperatingSystems.Windows)] + public void IsMac_TrueOnMac() + { + Assert.False(TestPlatformHelper.IsLinux); + Assert.True(TestPlatformHelper.IsMac); + Assert.False(TestPlatformHelper.IsWindows); + } + + [ConditionalFact] + [OSSkipCondition(OperatingSystems.Linux)] + [OSSkipCondition(OperatingSystems.MacOSX)] + public void IsWindows_TrueOnWindows() + { + Assert.False(TestPlatformHelper.IsLinux); + Assert.False(TestPlatformHelper.IsMac); + Assert.True(TestPlatformHelper.IsWindows); + } + + [ConditionalFact] + [FrameworkSkipCondition(RuntimeFrameworks.CLR | RuntimeFrameworks.CoreCLR | RuntimeFrameworks.None)] + public void IsMono_TrueOnMono() + { + Assert.True(TestPlatformHelper.IsMono); + } + + [ConditionalFact] + [FrameworkSkipCondition(RuntimeFrameworks.Mono)] + public void IsMono_FalseElsewhere() + { + Assert.False(TestPlatformHelper.IsMono); + } + } +} diff --git a/src/Testing/test/TestTestOutputHelper.cs b/src/Testing/test/TestTestOutputHelper.cs new file mode 100644 index 0000000000..5a5f6aa85f --- /dev/null +++ b/src/Testing/test/TestTestOutputHelper.cs @@ -0,0 +1,37 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Text; +using Xunit.Abstractions; + +namespace Microsoft.Extensions.Logging.Testing.Tests +{ + public class TestTestOutputHelper : ITestOutputHelper + { + private StringBuilder _output = new StringBuilder(); + + public bool Throw { get; set; } + + public string Output => _output.ToString(); + + public void WriteLine(string message) + { + if (Throw) + { + throw new Exception("Boom!"); + } + _output.AppendLine(message); + } + + public void WriteLine(string format, params object[] args) + { + if (Throw) + { + throw new Exception("Boom!"); + } + _output.AppendLine(string.Format(format, args)); + } + } +} diff --git a/src/Tools/Extensions.ApiDescription.Client/src/CSharpIdentifier.cs b/src/Tools/Extensions.ApiDescription.Client/src/CSharpIdentifier.cs index c7d2d13e49..347c0d82ee 100644 --- a/src/Tools/Extensions.ApiDescription.Client/src/CSharpIdentifier.cs +++ b/src/Tools/Extensions.ApiDescription.Client/src/CSharpIdentifier.cs @@ -5,7 +5,7 @@ using System.Globalization; using System.Text; // Copied from -// https://github.com/aspnet/AspNetCore-Tooling/blob/master/src/Razor/src/Microsoft.AspNetCore.Razor.Language/CSharpIdentifier.cs +// https://github.com/dotnet/aspnetcore-tooling/blob/master/src/Razor/src/Microsoft.AspNetCore.Razor.Language/CSharpIdentifier.cs namespace Microsoft.Extensions.ApiDescription.Client { internal static class CSharpIdentifier diff --git a/src/Tools/Extensions.ApiDescription.Client/src/Microsoft.Extensions.ApiDescription.Client.csproj b/src/Tools/Extensions.ApiDescription.Client/src/Microsoft.Extensions.ApiDescription.Client.csproj index c50a0115fa..0c631a6211 100644 --- a/src/Tools/Extensions.ApiDescription.Client/src/Microsoft.Extensions.ApiDescription.Client.csproj +++ b/src/Tools/Extensions.ApiDescription.Client/src/Microsoft.Extensions.ApiDescription.Client.csproj @@ -8,7 +8,7 @@ $(MSBuildProjectName).nuspec $(MSBuildProjectName) Build Tasks;MSBuild;Swagger;OpenAPI;code generation;Web API client;service reference - true + true netstandard2.0 true false diff --git a/src/Tools/Extensions.ApiDescription.Server/src/Microsoft.Extensions.ApiDescription.Server.csproj b/src/Tools/Extensions.ApiDescription.Server/src/Microsoft.Extensions.ApiDescription.Server.csproj index e0d39cff01..40ddf13258 100644 --- a/src/Tools/Extensions.ApiDescription.Server/src/Microsoft.Extensions.ApiDescription.Server.csproj +++ b/src/Tools/Extensions.ApiDescription.Server/src/Microsoft.Extensions.ApiDescription.Server.csproj @@ -11,7 +11,7 @@ $(MSBuildProjectName).nuspec $(MSBuildProjectName) MSBuild;Swagger;OpenAPI;code generation;Web API;service reference;document generation - true + true true + + false false false diff --git a/src/Tools/FirstRunCertGenerator/test/CertificateManagerTests.cs b/src/Tools/FirstRunCertGenerator/test/CertificateManagerTests.cs index d3be27defb..5651ba4622 100644 --- a/src/Tools/FirstRunCertGenerator/test/CertificateManagerTests.cs +++ b/src/Tools/FirstRunCertGenerator/test/CertificateManagerTests.cs @@ -29,7 +29,7 @@ namespace Microsoft.AspNetCore.Certificates.Generation.Tests public ITestOutputHelper Output { get; } [ConditionalFact] - [SkipOnHelix("https://github.com/aspnet/AspNetCore/issues/6721")] + [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/6721")] public void EnsureCreateHttpsCertificate_CreatesACertificate_WhenThereAreNoHttpsCertificates() { try @@ -124,7 +124,7 @@ namespace Microsoft.AspNetCore.Certificates.Generation.Tests } [ConditionalFact] - [SkipOnHelix("https://github.com/aspnet/AspNetCore/issues/6721")] + [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/6721")] public void EnsureCreateHttpsCertificate_DoesNotCreateACertificate_WhenThereIsAnExistingHttpsCertificates() { // Arrange @@ -155,7 +155,7 @@ namespace Microsoft.AspNetCore.Certificates.Generation.Tests } [ConditionalFact] - [SkipOnHelix("https://github.com/aspnet/AspNetCore/issues/6721")] + [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/6721")] public void EnsureCreateHttpsCertificate_ReturnsExpiredCertificateIfVersionIsIncorrect() { _fixture.CleanupCertificates(); @@ -171,7 +171,7 @@ namespace Microsoft.AspNetCore.Certificates.Generation.Tests } [ConditionalFact] - [SkipOnHelix("https://github.com/aspnet/AspNetCore/issues/6721")] + [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/6721")] public void EnsureCreateHttpsCertificate_ReturnsExpiredCertificateForEmptyVersionField() { _fixture.CleanupCertificates(); @@ -188,7 +188,7 @@ namespace Microsoft.AspNetCore.Certificates.Generation.Tests } [ConditionalFact] - [SkipOnHelix("https://github.com/aspnet/AspNetCore/issues/6721")] + [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/6721")] public void EnsureCreateHttpsCertificate_ReturnsValidIfVersionIsZero() { _fixture.CleanupCertificates(); @@ -203,7 +203,7 @@ namespace Microsoft.AspNetCore.Certificates.Generation.Tests } [ConditionalFact] - [SkipOnHelix("https://github.com/aspnet/AspNetCore/issues/6721")] + [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/6721")] public void EnsureCreateHttpsCertificate_ReturnValidIfCertIsNewer() { _fixture.CleanupCertificates(); diff --git a/src/Tools/GetDocumentInsider/src/GetDocumentInsider.csproj b/src/Tools/GetDocumentInsider/src/GetDocumentInsider.csproj index ebaffde463..7f61a987b3 100644 --- a/src/Tools/GetDocumentInsider/src/GetDocumentInsider.csproj +++ b/src/Tools/GetDocumentInsider/src/GetDocumentInsider.csproj @@ -15,10 +15,9 @@ + - - - + diff --git a/src/Tools/Microsoft.dotnet-openapi/README.md b/src/Tools/Microsoft.dotnet-openapi/README.md index 9ad333bddc..e5c004c2a5 100644 --- a/src/Tools/Microsoft.dotnet-openapi/README.md +++ b/src/Tools/Microsoft.dotnet-openapi/README.md @@ -6,14 +6,13 @@ ### Add Commands - + diff --git a/src/Tools/dotnet-user-secrets/src/CommandLineOptions.cs b/src/Tools/dotnet-user-secrets/src/CommandLineOptions.cs index 8495b6de9d..7d998f77d7 100644 --- a/src/Tools/dotnet-user-secrets/src/CommandLineOptions.cs +++ b/src/Tools/dotnet-user-secrets/src/CommandLineOptions.cs @@ -19,7 +19,7 @@ namespace Microsoft.Extensions.SecretManager.Tools public static CommandLineOptions Parse(string[] args, IConsole console) { - var app = new CommandLineApplication() + var app = new CommandLineApplication(treatUnmatchedOptionsAsArguments: true) { Out = console.Out, Error = console.Error, diff --git a/src/Tools/dotnet-user-secrets/src/Internal/InitCommand.cs b/src/Tools/dotnet-user-secrets/src/Internal/InitCommand.cs index e54f04ff7c..5b8b038596 100644 --- a/src/Tools/dotnet-user-secrets/src/Internal/InitCommand.cs +++ b/src/Tools/dotnet-user-secrets/src/Internal/InitCommand.cs @@ -4,6 +4,7 @@ using System; using System.IO; using System.Linq; +using System.Xml; using System.Xml.Linq; using System.Xml.XPath; using Microsoft.Extensions.CommandLineUtils; @@ -122,7 +123,16 @@ namespace Microsoft.Extensions.SecretManager.Tools.Internal propertyGroup.Add(new XElement("UserSecretsId", newSecretsId)); } - projectDocument.Save(projectPath); + var settings = new XmlWriterSettings + { + Indent = true, + OmitXmlDeclaration = true, + }; + + using (var xw = XmlWriter.Create(projectPath, settings)) + { + projectDocument.Save(xw); + } context.Reporter.Output(Resources.FormatMessage_SetUserSecretsIdForProject(newSecretsId, projectPath)); } diff --git a/src/Tools/dotnet-user-secrets/src/Internal/ProjectIdResolver.cs b/src/Tools/dotnet-user-secrets/src/Internal/ProjectIdResolver.cs index c2a81b5c2d..1c00b4b7b0 100644 --- a/src/Tools/dotnet-user-secrets/src/Internal/ProjectIdResolver.cs +++ b/src/Tools/dotnet-user-secrets/src/Internal/ProjectIdResolver.cs @@ -5,7 +5,7 @@ using System; using System.Diagnostics; using System.IO; using System.Linq; -using System.Reflection; +using System.Text; using Microsoft.Extensions.CommandLineUtils; using Microsoft.Extensions.Tools.Internal; @@ -53,26 +53,51 @@ namespace Microsoft.Extensions.SecretManager.Tools.Internal "/p:Configuration=" + configuration, "/p:CustomAfterMicrosoftCommonTargets=" + _targetsFile, "/p:CustomAfterMicrosoftCommonCrossTargetingTargets=" + _targetsFile, + "-verbosity:detailed", }; var psi = new ProcessStartInfo { FileName = DotNetMuxer.MuxerPathOrDefault(), Arguments = ArgumentEscaper.EscapeAndConcatenate(args), RedirectStandardOutput = true, - RedirectStandardError = true + RedirectStandardError = true, + UseShellExecute = false, }; #if DEBUG _reporter.Verbose($"Invoking '{psi.FileName} {psi.Arguments}'"); #endif - var process = Process.Start(psi); + using var process = new Process() + { + StartInfo = psi, + }; + + var outputBuilder = new StringBuilder(); + var errorBuilder = new StringBuilder(); + process.OutputDataReceived += (_, d) => + { + if (!string.IsNullOrEmpty(d.Data)) + { + outputBuilder.AppendLine(d.Data); + } + }; + process.ErrorDataReceived += (_, d) => + { + if (!string.IsNullOrEmpty(d.Data)) + { + errorBuilder.AppendLine(d.Data); + } + }; + process.Start(); + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); process.WaitForExit(); if (process.ExitCode != 0) { - _reporter.Verbose(process.StandardOutput.ReadToEnd()); - _reporter.Verbose(process.StandardError.ReadToEnd()); + _reporter.Verbose(outputBuilder.ToString()); + _reporter.Verbose(errorBuilder.ToString()); throw new InvalidOperationException(Resources.FormatError_ProjectFailedToLoad(projectFile)); } diff --git a/src/Tools/dotnet-user-secrets/src/dotnet-user-secrets.csproj b/src/Tools/dotnet-user-secrets/src/dotnet-user-secrets.csproj index 7fce951f0c..1ef774e1c5 100644 --- a/src/Tools/dotnet-user-secrets/src/dotnet-user-secrets.csproj +++ b/src/Tools/dotnet-user-secrets/src/dotnet-user-secrets.csproj @@ -14,13 +14,13 @@ + - diff --git a/src/Tools/dotnet-user-secrets/test/InitCommandTest.cs b/src/Tools/dotnet-user-secrets/test/InitCommandTest.cs index d1558e8811..d299c208ca 100644 --- a/src/Tools/dotnet-user-secrets/test/InitCommandTest.cs +++ b/src/Tools/dotnet-user-secrets/test/InitCommandTest.cs @@ -4,6 +4,8 @@ using System; using System.IO; using System.Text; +using System.Xml.Linq; +using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.Configuration.UserSecrets.Tests; using Microsoft.Extensions.SecretManager.Tools.Internal; using Microsoft.Extensions.Tools.Internal; @@ -17,19 +19,13 @@ namespace Microsoft.Extensions.SecretManager.Tools.Tests private UserSecretsTestFixture _fixture; private ITestOutputHelper _output; private TestConsole _console; - private StringBuilder _textOutput; public InitCommandTests(UserSecretsTestFixture fixture, ITestOutputHelper output) { _fixture = fixture; _output = output; - _textOutput = new StringBuilder(); - _console = new TestConsole(output) - { - Error = new StringWriter(_textOutput), - Out = new StringWriter(_textOutput), - }; + _console = new TestConsole(output); } private CommandContext MakeCommandContext() => new CommandContext(null, new TestReporter(_output), _console); @@ -61,6 +57,7 @@ namespace Microsoft.Extensions.SecretManager.Tools.Tests } [Fact] + [QuarantinedTest] public void AddsEscapedSpecificSecretIdToProject() { const string SecretId = @"&"; @@ -88,6 +85,18 @@ namespace Microsoft.Extensions.SecretManager.Tools.Tests Assert.Equal(SecretId, idResolver.Resolve(null, null)); } + [Fact] + public void DoesNotAddXmlDeclarationToProject() + { + var projectDir = _fixture.CreateProject(null); + var projectFile = Path.Combine(projectDir, "TestProject.csproj"); + + new InitCommand(null, null).Execute(MakeCommandContext(), projectDir); + + var projectDocument = XDocument.Load(projectFile); + Assert.Null(projectDocument.Declaration); + } + [Fact] public void OverridesIdForProjectWithSecretId() { diff --git a/src/Tools/dotnet-user-secrets/test/SecretManagerTests.cs b/src/Tools/dotnet-user-secrets/test/SecretManagerTests.cs index 3a390d4439..48f6774b2b 100644 --- a/src/Tools/dotnet-user-secrets/test/SecretManagerTests.cs +++ b/src/Tools/dotnet-user-secrets/test/SecretManagerTests.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Text; +using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.Configuration.UserSecrets; using Microsoft.Extensions.Configuration.UserSecrets.Tests; using Microsoft.Extensions.Tools.Internal; @@ -17,17 +18,15 @@ namespace Microsoft.Extensions.SecretManager.Tools.Tests { private readonly TestConsole _console; private readonly UserSecretsTestFixture _fixture; - private readonly StringBuilder _output = new StringBuilder(); + private readonly ITestOutputHelper _testOut; public SecretManagerTests(UserSecretsTestFixture fixture, ITestOutputHelper output) { _fixture = fixture; - _console = new TestConsole(output) - { - Error = new StringWriter(_output), - Out = new StringWriter(_output), - }; + _testOut = output; + + _console = new TestConsole(output); } private Program CreateProgram() @@ -38,13 +37,14 @@ namespace Microsoft.Extensions.SecretManager.Tools.Tests [Theory] [InlineData(null)] [InlineData("")] + [QuarantinedTest] public void Error_MissingId(string id) { var project = Path.Combine(_fixture.CreateProject(id), "TestProject.csproj"); var secretManager = CreateProgram(); - secretManager.RunInternal("list", "-p", project); - Assert.Contains(Resources.FormatError_ProjectMissingId(project), _output.ToString()); + secretManager.RunInternal("list", "-p", project, "--verbose"); + Assert.Contains(Resources.FormatError_ProjectMissingId(project), _console.GetOutput()); } [Fact] @@ -54,7 +54,7 @@ namespace Microsoft.Extensions.SecretManager.Tools.Tests var secretManager = CreateProgram(); secretManager.RunInternal("list", "-p", project); - Assert.Contains(Resources.FormatError_ProjectFailedToLoad(project), _output.ToString()); + Assert.Contains(Resources.FormatError_ProjectFailedToLoad(project), _console.GetOutput()); } [Fact] @@ -64,7 +64,7 @@ namespace Microsoft.Extensions.SecretManager.Tools.Tests var secretManager = CreateProgram(); secretManager.RunInternal("list", "--project", projectPath); - Assert.Contains(Resources.FormatError_ProjectPath_NotFound(projectPath), _output.ToString()); + Assert.Contains(Resources.FormatError_ProjectPath_NotFound(projectPath), _console.GetOutput()); } [Fact] @@ -77,20 +77,23 @@ namespace Microsoft.Extensions.SecretManager.Tools.Tests secretManager.RunInternal("list", "-p", ".." + Path.DirectorySeparatorChar, "--verbose"); - Assert.Contains(Resources.FormatMessage_Project_File_Path(Path.Combine(cwd, "..", "TestProject.csproj")), _output.ToString()); + Assert.Contains(Resources.FormatMessage_Project_File_Path(Path.Combine(cwd, "..", "TestProject.csproj")), _console.GetOutput()); } [Theory] [InlineData(true)] [InlineData(false)] + [QuarantinedTest] public void SetSecrets(bool fromCurrentDirectory) { var secrets = new KeyValuePair[] { - new KeyValuePair("key1", Guid.NewGuid().ToString()), - new KeyValuePair("Facebook:AppId", Guid.NewGuid().ToString()), - new KeyValuePair(@"key-@\/.~123!#$%^&*())-+==", @"key-@\/.~123!#$%^&*())-+=="), - new KeyValuePair("key2", string.Empty) + new KeyValuePair("key1", Guid.NewGuid().ToString()), + new KeyValuePair("Facebook:AppId", Guid.NewGuid().ToString()), + new KeyValuePair(@"key-@\/.~123!#$%^&*())-+==", @"key-@\/.~123!#$%^&*())-+=="), + new KeyValuePair("key2", string.Empty), + new KeyValuePair("-oneDashedKey", "-oneDashedValue"), + new KeyValuePair("--twoDashedKey", "--twoDashedValue") }; var projectPath = _fixture.GetTempSecretProject(); @@ -102,8 +105,8 @@ namespace Microsoft.Extensions.SecretManager.Tools.Tests foreach (var secret in secrets) { var parameters = fromCurrentDirectory ? - new string[] { "set", secret.Key, secret.Value } : - new string[] { "set", secret.Key, secret.Value, "-p", projectPath }; + new string[] { "set", secret.Key, secret.Value, "--verbose" } : + new string[] { "set", secret.Key, secret.Value, "-p", projectPath, "--verbose" }; secretManager.RunInternal(parameters); } @@ -111,55 +114,56 @@ namespace Microsoft.Extensions.SecretManager.Tools.Tests { Assert.Contains( string.Format("Successfully saved {0} = {1} to the secret store.", keyValue.Key, keyValue.Value), - _output.ToString()); + _console.GetOutput()); } - _output.Clear(); + _console.ClearOutput(); var args = fromCurrentDirectory - ? new string[] { "list" } - : new string[] { "list", "-p", projectPath }; + ? new string[] { "list", "--verbose" } + : new string[] { "list", "-p", projectPath, "--verbose" }; secretManager.RunInternal(args); foreach (var keyValue in secrets) { Assert.Contains( string.Format("{0} = {1}", keyValue.Key, keyValue.Value), - _output.ToString()); + _console.GetOutput()); } // Remove secrets. - _output.Clear(); + _console.ClearOutput(); foreach (var secret in secrets) { var parameters = fromCurrentDirectory ? - new string[] { "remove", secret.Key } : - new string[] { "remove", secret.Key, "-p", projectPath }; + new string[] { "remove", secret.Key, "--verbose" } : + new string[] { "remove", secret.Key, "-p", projectPath, "--verbose" }; secretManager.RunInternal(parameters); } // Verify secrets are removed. - _output.Clear(); + _console.ClearOutput(); args = fromCurrentDirectory - ? new string[] { "list" } - : new string[] { "list", "-p", projectPath }; + ? new string[] { "list", "--verbose" } + : new string[] { "list", "-p", projectPath, "--verbose" }; secretManager.RunInternal(args); - Assert.Contains(Resources.Error_No_Secrets_Found, _output.ToString()); + Assert.Contains(Resources.Error_No_Secrets_Found, _console.GetOutput()); } [Fact] + [QuarantinedTest] public void SetSecret_Update_Existing_Secret() { var projectPath = _fixture.GetTempSecretProject(); var secretManager = CreateProgram(); - secretManager.RunInternal("set", "secret1", "value1", "-p", projectPath); - Assert.Contains("Successfully saved secret1 = value1 to the secret store.", _output.ToString()); - secretManager.RunInternal("set", "secret1", "value2", "-p", projectPath); - Assert.Contains("Successfully saved secret1 = value2 to the secret store.", _output.ToString()); + secretManager.RunInternal("set", "secret1", "value1", "-p", projectPath, "--verbose"); + Assert.Contains("Successfully saved secret1 = value1 to the secret store.", _console.GetOutput()); + secretManager.RunInternal("set", "secret1", "value2", "-p", projectPath, "--verbose"); + Assert.Contains("Successfully saved secret1 = value2 to the secret store.", _console.GetOutput()); - _output.Clear(); + _console.ClearOutput(); - secretManager.RunInternal("list", "-p", projectPath); - Assert.Contains("secret1 = value2", _output.ToString()); + secretManager.RunInternal("list", "-p", projectPath, "--verbose"); + Assert.Contains("secret1 = value2", _console.GetOutput()); } [Fact] @@ -170,44 +174,47 @@ namespace Microsoft.Extensions.SecretManager.Tools.Tests var secretManager = CreateProgram(); secretManager.RunInternal("-v", "set", "secret1", "value1", "-p", projectPath); - Assert.Contains(string.Format("Project file path {0}.", Path.Combine(projectPath, "TestProject.csproj")), _output.ToString()); - Assert.Contains(string.Format("Secrets file path {0}.", PathHelper.GetSecretsPathFromSecretsId(secretId)), _output.ToString()); - Assert.Contains("Successfully saved secret1 = value1 to the secret store.", _output.ToString()); - _output.Clear(); + Assert.Contains(string.Format("Project file path {0}.", Path.Combine(projectPath, "TestProject.csproj")), _console.GetOutput()); + Assert.Contains(string.Format("Secrets file path {0}.", PathHelper.GetSecretsPathFromSecretsId(secretId)), _console.GetOutput()); + Assert.Contains("Successfully saved secret1 = value1 to the secret store.", _console.GetOutput()); + _console.ClearOutput(); secretManager.RunInternal("-v", "list", "-p", projectPath); - Assert.Contains(string.Format("Project file path {0}.", Path.Combine(projectPath, "TestProject.csproj")), _output.ToString()); - Assert.Contains(string.Format("Secrets file path {0}.", PathHelper.GetSecretsPathFromSecretsId(secretId)), _output.ToString()); - Assert.Contains("secret1 = value1", _output.ToString()); + Assert.Contains(string.Format("Project file path {0}.", Path.Combine(projectPath, "TestProject.csproj")), _console.GetOutput()); + Assert.Contains(string.Format("Secrets file path {0}.", PathHelper.GetSecretsPathFromSecretsId(secretId)), _console.GetOutput()); + Assert.Contains("secret1 = value1", _console.GetOutput()); } [Fact] + [QuarantinedTest] public void Remove_Non_Existing_Secret() { var projectPath = _fixture.GetTempSecretProject(); var secretManager = CreateProgram(); - secretManager.RunInternal("remove", "secret1", "-p", projectPath); - Assert.Contains("Cannot find 'secret1' in the secret store.", _output.ToString()); + secretManager.RunInternal("remove", "secret1", "-p", projectPath, "--verbose"); + Assert.Contains("Cannot find 'secret1' in the secret store.", _console.GetOutput()); } [Fact] + [QuarantinedTest] public void Remove_Is_Case_Insensitive() { var projectPath = _fixture.GetTempSecretProject(); var secretManager = CreateProgram(); - secretManager.RunInternal("set", "SeCreT1", "value", "-p", projectPath); - secretManager.RunInternal("list", "-p", projectPath); - Assert.Contains("SeCreT1 = value", _output.ToString()); - secretManager.RunInternal("remove", "secret1", "-p", projectPath); + secretManager.RunInternal("set", "SeCreT1", "value", "-p", projectPath, "--verbose"); + secretManager.RunInternal("list", "-p", projectPath, "--verbose"); + Assert.Contains("SeCreT1 = value", _console.GetOutput()); + secretManager.RunInternal("remove", "secret1", "-p", projectPath, "--verbose"); - _output.Clear(); - secretManager.RunInternal("list", "-p", projectPath); + _console.ClearOutput(); + secretManager.RunInternal("list", "-p", projectPath, "--verbose"); - Assert.Contains(Resources.Error_No_Secrets_Found, _output.ToString()); + Assert.Contains(Resources.Error_No_Secrets_Found, _console.GetOutput()); } [Fact] + [QuarantinedTest] public void List_Flattens_Nested_Objects() { string secretId; @@ -216,8 +223,8 @@ namespace Microsoft.Extensions.SecretManager.Tools.Tests Directory.CreateDirectory(Path.GetDirectoryName(secretsFile)); File.WriteAllText(secretsFile, @"{ ""AzureAd"": { ""ClientSecret"": ""abcd郩˙î""} }", Encoding.UTF8); var secretManager = CreateProgram(); - secretManager.RunInternal("list", "-p", projectPath); - Assert.Contains("AzureAd:ClientSecret = abcd郩˙î", _output.ToString()); + secretManager.RunInternal("list", "-p", projectPath, "--verbose"); + Assert.Contains("AzureAd:ClientSecret = abcd郩˙î", _console.GetOutput()); } [Fact] @@ -230,7 +237,7 @@ namespace Microsoft.Extensions.SecretManager.Tools.Tests File.WriteAllText(secretsFile, @"{ ""AzureAd"": { ""ClientSecret"": ""abcd郩˙î""} }", Encoding.UTF8); var secretManager = new Program(_console, Path.GetDirectoryName(projectPath)); secretManager.RunInternal("list", "--id", id, "--json"); - var stdout = _output.ToString(); + var stdout = _console.GetOutput(); Assert.Contains("//BEGIN", stdout); Assert.Contains(@"""AzureAd:ClientSecret"": ""abcd郩˙î""", stdout); Assert.Contains("//END", stdout); @@ -248,7 +255,7 @@ namespace Microsoft.Extensions.SecretManager.Tools.Tests secretManager.RunInternal("set", "AzureAd:ClientSecret", "¡™£¢∞", "-p", projectPath); secretManager.RunInternal("list", "-p", projectPath); - Assert.Contains("AzureAd:ClientSecret = ¡™£¢∞", _output.ToString()); + Assert.Contains("AzureAd:ClientSecret = ¡™£¢∞", _console.GetOutput()); var fileContents = File.ReadAllText(secretsFile, Encoding.UTF8); Assert.Equal(@"{ ""AzureAd:ClientSecret"": ""¡™£¢∞"" @@ -257,14 +264,16 @@ namespace Microsoft.Extensions.SecretManager.Tools.Tests } [Fact] + [QuarantinedTest] public void List_Empty_Secrets_File() { var projectPath = _fixture.GetTempSecretProject(); var secretManager = CreateProgram(); - secretManager.RunInternal("list", "-p", projectPath); - Assert.Contains(Resources.Error_No_Secrets_Found, _output.ToString()); + secretManager.RunInternal("list", "-p", projectPath, "--verbose"); + Assert.Contains(Resources.Error_No_Secrets_Found, _console.GetOutput()); } + [QuarantinedTest] [Theory] [InlineData(true)] [InlineData(false)] @@ -289,8 +298,8 @@ namespace Microsoft.Extensions.SecretManager.Tools.Tests foreach (var secret in secrets) { var parameters = fromCurrentDirectory ? - new string[] { "set", secret.Key, secret.Value } : - new string[] { "set", secret.Key, secret.Value, "-p", projectPath }; + new string[] { "set", secret.Key, secret.Value, "--verbose" } : + new string[] { "set", secret.Key, secret.Value, "-p", projectPath, "--verbose" }; secretManager.RunInternal(parameters); } @@ -298,30 +307,30 @@ namespace Microsoft.Extensions.SecretManager.Tools.Tests { Assert.Contains( string.Format("Successfully saved {0} = {1} to the secret store.", keyValue.Key, keyValue.Value), - _output.ToString()); + _console.GetOutput()); } // Verify secrets are persisted. - _output.Clear(); + _console.ClearOutput(); var args = fromCurrentDirectory ? - new string[] { "list" } : - new string[] { "list", "-p", projectPath }; + new string[] { "list", "--verbose" } : + new string[] { "list", "-p", projectPath, "--verbose" }; secretManager.RunInternal(args); foreach (var keyValue in secrets) { Assert.Contains( string.Format("{0} = {1}", keyValue.Key, keyValue.Value), - _output.ToString()); + _console.GetOutput()); } // Clear secrets. - _output.Clear(); - args = fromCurrentDirectory ? new string[] { "clear" } : new string[] { "clear", "-p", projectPath }; + _console.ClearOutput(); + args = fromCurrentDirectory ? new string[] { "clear", "--verbose" } : new string[] { "clear", "-p", projectPath, "--verbose" }; secretManager.RunInternal(args); - args = fromCurrentDirectory ? new string[] { "list" } : new string[] { "list", "-p", projectPath }; + args = fromCurrentDirectory ? new string[] { "list", "--verbose" } : new string[] { "list", "-p", projectPath, "--verbose" }; secretManager.RunInternal(args); - Assert.Contains(Resources.Error_No_Secrets_Found, _output.ToString()); + Assert.Contains(Resources.Error_No_Secrets_Found, _console.GetOutput()); } [Fact] @@ -333,8 +342,8 @@ namespace Microsoft.Extensions.SecretManager.Tools.Tests secretManager.RunInternal("init", "-p", project); - Assert.DoesNotContain(Resources.FormatError_ProjectMissingId(project), _output.ToString()); - Assert.DoesNotContain("--help", _output.ToString()); + Assert.DoesNotContain(Resources.FormatError_ProjectMissingId(project), _console.GetOutput()); + Assert.DoesNotContain("--help", _console.GetOutput()); } } } diff --git a/src/Tools/dotnet-user-secrets/test/UserSecretsTestFixture.cs b/src/Tools/dotnet-user-secrets/test/UserSecretsTestFixture.cs index adcbe32b1e..5a42903675 100644 --- a/src/Tools/dotnet-user-secrets/test/UserSecretsTestFixture.cs +++ b/src/Tools/dotnet-user-secrets/test/UserSecretsTestFixture.cs @@ -35,7 +35,7 @@ namespace Microsoft.Extensions.Configuration.UserSecrets.Tests private const string ProjectTemplate = @" Exe - netcoreapp3.1 + netcoreapp5.0 {0} false diff --git a/src/Tools/dotnet-watch/src/dotnet-watch.csproj b/src/Tools/dotnet-watch/src/dotnet-watch.csproj index 3c20912211..ee7fc09d15 100644 --- a/src/Tools/dotnet-watch/src/dotnet-watch.csproj +++ b/src/Tools/dotnet-watch/src/dotnet-watch.csproj @@ -13,13 +13,10 @@ + - - - - diff --git a/src/Tools/dotnet-watch/test/AppWithDepsTests.cs b/src/Tools/dotnet-watch/test/AppWithDepsTests.cs index 9888258327..5954f540b5 100644 --- a/src/Tools/dotnet-watch/test/AppWithDepsTests.cs +++ b/src/Tools/dotnet-watch/test/AppWithDepsTests.cs @@ -19,7 +19,8 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests _app = new AppWithDeps(logger); } - [Fact(Skip = "https://github.com/aspnet/AspNetCore/issues/8267")] + [Fact] + [QuarantinedTest] public async Task ChangeFileInDependency() { await _app.StartWatcherAsync(); diff --git a/src/Tools/dotnet-watch/test/AwaitableProcess.cs b/src/Tools/dotnet-watch/test/AwaitableProcess.cs index 3e22d53245..553b7a4d11 100644 --- a/src/Tools/dotnet-watch/test/AwaitableProcess.cs +++ b/src/Tools/dotnet-watch/test/AwaitableProcess.cs @@ -21,6 +21,7 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests private BufferBlock _source; private ITestOutputHelper _logger; private TaskCompletionSource _exited; + private bool _started; public AwaitableProcess(ProcessSpec spec, ITestOutputHelper logger) { @@ -71,10 +72,12 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests _process.ErrorDataReceived += OnData; _process.Exited += OnExit; + _logger.WriteLine($"{DateTime.Now}: starting process: '{_process.StartInfo.FileName} {_process.StartInfo.Arguments}'"); _process.Start(); + _started = true; _process.BeginErrorReadLine(); _process.BeginOutputReadLine(); - _logger.WriteLine($"{DateTime.Now}: process start: '{_process.StartInfo.FileName} {_process.StartInfo.Arguments}'"); + _logger.WriteLine($"{DateTime.Now}: process started: '{_process.StartInfo.FileName} {_process.StartInfo.Arguments}'"); } public async Task GetOutputLineAsync(string message, TimeSpan timeout) @@ -150,7 +153,7 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests if (_process != null) { - if (!_process.HasExited) + if (_started && !_process.HasExited) { _process.KillTree(); } diff --git a/src/Tools/dotnet-watch/test/CommandLineOptionsTests.cs b/src/Tools/dotnet-watch/test/CommandLineOptionsTests.cs index 129d1219fa..5d00c179ec 100644 --- a/src/Tools/dotnet-watch/test/CommandLineOptionsTests.cs +++ b/src/Tools/dotnet-watch/test/CommandLineOptionsTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.IO; @@ -13,15 +13,11 @@ namespace Microsoft.DotNet.Watcher.Tools.Tests { public class CommandLineOptionsTests { - private readonly IConsole _console; - private readonly StringBuilder _stdout = new StringBuilder(); + private readonly TestConsole _console; public CommandLineOptionsTests(ITestOutputHelper output) { - _console = new TestConsole(output) - { - Out = new StringWriter(_stdout), - }; + _console = new TestConsole(output); } [Theory] @@ -36,7 +32,7 @@ namespace Microsoft.DotNet.Watcher.Tools.Tests var options = CommandLineOptions.Parse(args, _console); Assert.True(options.IsHelp); - Assert.Contains("Usage: dotnet watch ", _stdout.ToString()); + Assert.Contains("Usage: dotnet watch ", _console.GetOutput()); } [Theory] @@ -50,7 +46,7 @@ namespace Microsoft.DotNet.Watcher.Tools.Tests Assert.Equal(expected, options.RemainingArguments.ToArray()); Assert.False(options.IsHelp); - Assert.Empty(_stdout.ToString()); + Assert.Empty(_console.GetOutput()); } [Fact] diff --git a/src/Tools/dotnet-watch/test/DotNetWatcherTests.cs b/src/Tools/dotnet-watch/test/DotNetWatcherTests.cs index d6e57ee5cf..d07f4e25a4 100644 --- a/src/Tools/dotnet-watch/test/DotNetWatcherTests.cs +++ b/src/Tools/dotnet-watch/test/DotNetWatcherTests.cs @@ -22,8 +22,8 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests _app = new KitchenSinkApp(logger); } - [ConditionalFact] - [SkipOnHelix("https://github.com/aspnet/AspNetCore/issues/8267")] + [Fact] + [QuarantinedTest] public async Task RunsWithDotnetWatchEnvVariable() { Assert.True(string.IsNullOrEmpty(Environment.GetEnvironmentVariable("DOTNET_WATCH")), "DOTNET_WATCH cannot be set already when this test is running"); @@ -36,7 +36,7 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests } [Fact] - [Flaky("https://github.com/aspnet/AspNetCore-Internal/issues/1826", FlakyOn.All)] + [QuarantinedTest] public async Task RunsWithIterationEnvVariable() { await _app.StartWatcherAsync(); diff --git a/src/Tools/dotnet-watch/test/GlobbingAppTests.cs b/src/Tools/dotnet-watch/test/GlobbingAppTests.cs index 8667c06e7d..9383b2d728 100644 --- a/src/Tools/dotnet-watch/test/GlobbingAppTests.cs +++ b/src/Tools/dotnet-watch/test/GlobbingAppTests.cs @@ -21,9 +21,10 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests _app = new GlobbingApp(logger); } - [Theory(Skip = "https://github.com/aspnet/AspNetCore/issues/8267")] + [Theory] [InlineData(true)] [InlineData(false)] + [QuarantinedTest] public async Task ChangeCompiledFile(bool usePollingWatcher) { _app.UsePollingWatcher = usePollingWatcher; @@ -41,7 +42,8 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests Assert.Equal(2, types); } - [Fact(Skip = "https://github.com/aspnet/AspNetCore/issues/8267")] + [Fact] + [QuarantinedTest] public async Task DeleteCompiledFile() { await _app.StartWatcherAsync(); @@ -57,7 +59,8 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests Assert.Equal(1, types); } - [Fact(Skip = "https://github.com/aspnet/AspNetCore/issues/8267")] + [Fact] + [QuarantinedTest] public async Task DeleteSourceFolder() { await _app.StartWatcherAsync(); @@ -73,7 +76,8 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests Assert.Equal(1, types); } - [Fact(Skip = "https://github.com/aspnet/AspNetCore/issues/8987")] + [Fact] + [QuarantinedTest] public async Task RenameCompiledFile() { await _app.StartWatcherAsync(); @@ -85,8 +89,8 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests await _app.HasRestarted(); } - [ConditionalFact] - [SkipOnHelix("https://github.com/aspnet/AspNetCore/issues/8267")] + [Fact] + [QuarantinedTest] public async Task ChangeExcludedFile() { await _app.StartWatcherAsync(); @@ -99,12 +103,11 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests Assert.NotSame(restart, finished); } - [ConditionalFact] - [SkipOnHelix("https://github.com/aspnet/AspNetCore/issues/8267")] + [Fact] public async Task ListsFiles() { await _app.PrepareAsync(); - _app.Start(new [] { "--list" }); + _app.Start(new[] { "--list" }); var cts = new CancellationTokenSource(); cts.CancelAfter(TimeSpan.FromSeconds(30)); var lines = await _app.Process.GetAllOutputLinesAsync(cts.Token); diff --git a/src/Tools/dotnet-watch/test/NoDepsAppTests.cs b/src/Tools/dotnet-watch/test/NoDepsAppTests.cs index 4f2b420492..b7171d5ad2 100644 --- a/src/Tools/dotnet-watch/test/NoDepsAppTests.cs +++ b/src/Tools/dotnet-watch/test/NoDepsAppTests.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Diagnostics; using System.IO; using System.Threading.Tasks; using Microsoft.AspNetCore.Testing; @@ -24,7 +23,8 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests _output = logger; } - [Fact(Skip = "https://github.com/aspnet/AspNetCore/issues/8267")] + [Fact] + [QuarantinedTest] public async Task RestartProcessOnFileChange() { await _app.StartWatcherAsync(new[] { "--no-exit" }); @@ -42,8 +42,8 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests Assert.NotEqual(processIdentifier, processIdentifier2); } - [ConditionalFact] - [SkipOnHelix("https://github.com/aspnet/AspNetCore/issues/8267")] + [Fact] + [QuarantinedTest] public async Task RestartProcessThatTerminatesAfterFileChange() { await _app.StartWatcherAsync(); diff --git a/src/Tools/dotnet-watch/test/ProgramTests.cs b/src/Tools/dotnet-watch/test/ProgramTests.cs index 0e7dff9b82..40c2af5214 100644 --- a/src/Tools/dotnet-watch/test/ProgramTests.cs +++ b/src/Tools/dotnet-watch/test/ProgramTests.cs @@ -28,13 +28,11 @@ namespace Microsoft.DotNet.Watcher.Tools.Tests { _tempDir .WithCSharpProject("testproj") - .WithTargetFrameworks("netcoreapp3.1") + .WithTargetFrameworks("netcoreapp5.0") .Dir() .WithFile("Program.cs") .Create(); - var output = new StringBuilder(); - _console.Error = _console.Out = new StringWriter(output); using (var app = new Program(_console, _tempDir.Root)) { var run = app.RunAsync(new[] { "run" }); @@ -44,7 +42,7 @@ namespace Microsoft.DotNet.Watcher.Tools.Tests var exitCode = await run.TimeoutAfter(TimeSpan.FromSeconds(30)); - Assert.Contains("Shutdown requested. Press Ctrl+C again to force exit.", output.ToString()); + Assert.Contains("Shutdown requested. Press Ctrl+C again to force exit.", _console.GetOutput()); Assert.Equal(0, exitCode); } } diff --git a/src/Tools/dotnet-watch/test/Scenario/ProjectToolScenario.cs b/src/Tools/dotnet-watch/test/Scenario/ProjectToolScenario.cs index 44d49a203a..6c40a01309 100644 --- a/src/Tools/dotnet-watch/test/Scenario/ProjectToolScenario.cs +++ b/src/Tools/dotnet-watch/test/Scenario/ProjectToolScenario.cs @@ -62,7 +62,7 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests public Task RestoreAsync(string project) { _logger?.WriteLine($"Restoring msbuild project in {project}"); - return ExecuteCommandAsync(project, TimeSpan.FromSeconds(120), "restore"); + return ExecuteCommandAsync(project, TimeSpan.FromSeconds(120), "restore", "--ignore-failed-sources"); } public Task BuildAsync(string project) @@ -149,15 +149,6 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests File.WriteAllText(Path.Combine(WorkFolder, "Directory.Build.targets"), ""); } - private string GetMetadata(string key) - { - return typeof(ProjectToolScenario) - .Assembly - .GetCustomAttributes() - .First(a => string.Equals(a.Key, key, StringComparison.Ordinal)) - .Value; - } - public void Dispose() { try diff --git a/src/Tools/dotnet-watch/test/Scenario/WatchableApp.cs b/src/Tools/dotnet-watch/test/Scenario/WatchableApp.cs index 6d89b4861a..eeae109bf9 100644 --- a/src/Tools/dotnet-watch/test/Scenario/WatchableApp.cs +++ b/src/Tools/dotnet-watch/test/Scenario/WatchableApp.cs @@ -8,7 +8,6 @@ using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Threading.Tasks; -using Microsoft.Extensions.CommandLineUtils; using Xunit.Abstractions; namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests @@ -89,9 +88,15 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests }; args.AddRange(arguments); - var dotnetPath = typeof(WatchableApp).Assembly.GetCustomAttributes() - .Single(s => s.Key == "DotnetPath").Value; - + var dotnetPath = "dotnet"; + + // Fallback to embedded path to dotnet when not on helix + if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("helix"))) + { + dotnetPath = typeof(WatchableApp).Assembly.GetCustomAttributes() + .Single(s => s.Key == "DotnetPath").Value; + } + var spec = new ProcessSpec { Executable = dotnetPath, @@ -99,12 +104,15 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests WorkingDirectory = SourceDirectory, EnvironmentVariables = { - ["DOTNET_CLI_CONTEXT_VERBOSE"] = bool.TrueString, ["DOTNET_USE_POLLING_FILE_WATCHER"] = UsePollingWatcher.ToString(), - ["DOTNET_ROOT"] = Directory.GetParent(dotnetPath).FullName, }, }; + if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("helix"))) + { + spec.EnvironmentVariables["DOTNET_ROOT"] = Directory.GetParent(dotnetPath).FullName; + } + Process = new AwaitableProcess(spec, _logger); Process.Start(); } diff --git a/src/Tools/dotnet-watch/test/TestProjects/AppWithDeps/AppWithDeps.csproj b/src/Tools/dotnet-watch/test/TestProjects/AppWithDeps/AppWithDeps.csproj index d0cde953c7..7399c1018d 100644 --- a/src/Tools/dotnet-watch/test/TestProjects/AppWithDeps/AppWithDeps.csproj +++ b/src/Tools/dotnet-watch/test/TestProjects/AppWithDeps/AppWithDeps.csproj @@ -1,7 +1,7 @@ - netcoreapp3.1 + netcoreapp5.0 exe true diff --git a/src/Tools/dotnet-watch/test/TestProjects/GlobbingApp/GlobbingApp.csproj b/src/Tools/dotnet-watch/test/TestProjects/GlobbingApp/GlobbingApp.csproj index 9f015b1ee4..8f8043d0de 100644 --- a/src/Tools/dotnet-watch/test/TestProjects/GlobbingApp/GlobbingApp.csproj +++ b/src/Tools/dotnet-watch/test/TestProjects/GlobbingApp/GlobbingApp.csproj @@ -1,7 +1,7 @@ - netcoreapp3.1 + netcoreapp5.0 exe false true diff --git a/src/Tools/dotnet-watch/test/TestProjects/KitchenSink/KitchenSink.csproj b/src/Tools/dotnet-watch/test/TestProjects/KitchenSink/KitchenSink.csproj index af6de1b33f..6de103d382 100644 --- a/src/Tools/dotnet-watch/test/TestProjects/KitchenSink/KitchenSink.csproj +++ b/src/Tools/dotnet-watch/test/TestProjects/KitchenSink/KitchenSink.csproj @@ -9,7 +9,7 @@ Exe - netcoreapp3.1 + netcoreapp5.0 true diff --git a/src/Tools/dotnet-watch/test/TestProjects/NoDepsApp/NoDepsApp.csproj b/src/Tools/dotnet-watch/test/TestProjects/NoDepsApp/NoDepsApp.csproj index 95412443e6..110ff7686b 100644 --- a/src/Tools/dotnet-watch/test/TestProjects/NoDepsApp/NoDepsApp.csproj +++ b/src/Tools/dotnet-watch/test/TestProjects/NoDepsApp/NoDepsApp.csproj @@ -1,7 +1,7 @@ - netcoreapp3.1 + netcoreapp5.0 exe true diff --git a/src/WebEncoders/Directory.Build.props b/src/WebEncoders/Directory.Build.props new file mode 100644 index 0000000000..81557e1bca --- /dev/null +++ b/src/WebEncoders/Directory.Build.props @@ -0,0 +1,8 @@ + + + + + true + + + diff --git a/src/WebEncoders/ref/Microsoft.Extensions.WebEncoders.csproj b/src/WebEncoders/ref/Microsoft.Extensions.WebEncoders.csproj new file mode 100644 index 0000000000..4fbb9b15e3 --- /dev/null +++ b/src/WebEncoders/ref/Microsoft.Extensions.WebEncoders.csproj @@ -0,0 +1,18 @@ + + + + netstandard2.0;$(DefaultNetCoreTargetFramework) + $(DefaultNetCoreTargetFramework) + + + + + + + + + + + + + diff --git a/src/WebEncoders/ref/Microsoft.Extensions.WebEncoders.netcoreapp.cs b/src/WebEncoders/ref/Microsoft.Extensions.WebEncoders.netcoreapp.cs new file mode 100644 index 0000000000..ad8e11a40e --- /dev/null +++ b/src/WebEncoders/ref/Microsoft.Extensions.WebEncoders.netcoreapp.cs @@ -0,0 +1,55 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.Extensions.DependencyInjection +{ + public static partial class EncoderServiceCollectionExtensions + { + public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddWebEncoders(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) { throw null; } + public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddWebEncoders(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Action setupAction) { throw null; } + } +} +namespace Microsoft.Extensions.WebEncoders +{ + public sealed partial class WebEncoderOptions + { + public WebEncoderOptions() { } + public System.Text.Encodings.Web.TextEncoderSettings TextEncoderSettings { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + } +} +namespace Microsoft.Extensions.WebEncoders.Testing +{ + public sealed partial class HtmlTestEncoder : System.Text.Encodings.Web.HtmlEncoder + { + public HtmlTestEncoder() { } + public override int MaxOutputCharactersPerInputCharacter { get { throw null; } } + public override void Encode(System.IO.TextWriter output, char[] value, int startIndex, int characterCount) { } + public override void Encode(System.IO.TextWriter output, string value, int startIndex, int characterCount) { } + public override string Encode(string value) { throw null; } + public unsafe override int FindFirstCharacterToEncode(char* text, int textLength) { throw null; } + public unsafe override bool TryEncodeUnicodeScalar(int unicodeScalar, char* buffer, int bufferLength, out int numberOfCharactersWritten) { throw null; } + public override bool WillEncode(int unicodeScalar) { throw null; } + } + public partial class JavaScriptTestEncoder : System.Text.Encodings.Web.JavaScriptEncoder + { + public JavaScriptTestEncoder() { } + public override int MaxOutputCharactersPerInputCharacter { get { throw null; } } + public override void Encode(System.IO.TextWriter output, char[] value, int startIndex, int characterCount) { } + public override void Encode(System.IO.TextWriter output, string value, int startIndex, int characterCount) { } + public override string Encode(string value) { throw null; } + public unsafe override int FindFirstCharacterToEncode(char* text, int textLength) { throw null; } + public unsafe override bool TryEncodeUnicodeScalar(int unicodeScalar, char* buffer, int bufferLength, out int numberOfCharactersWritten) { throw null; } + public override bool WillEncode(int unicodeScalar) { throw null; } + } + public partial class UrlTestEncoder : System.Text.Encodings.Web.UrlEncoder + { + public UrlTestEncoder() { } + public override int MaxOutputCharactersPerInputCharacter { get { throw null; } } + public override void Encode(System.IO.TextWriter output, char[] value, int startIndex, int characterCount) { } + public override void Encode(System.IO.TextWriter output, string value, int startIndex, int characterCount) { } + public override string Encode(string value) { throw null; } + public unsafe override int FindFirstCharacterToEncode(char* text, int textLength) { throw null; } + public unsafe override bool TryEncodeUnicodeScalar(int unicodeScalar, char* buffer, int bufferLength, out int numberOfCharactersWritten) { throw null; } + public override bool WillEncode(int unicodeScalar) { throw null; } + } +} diff --git a/src/WebEncoders/ref/Microsoft.Extensions.WebEncoders.netstandard2.0.cs b/src/WebEncoders/ref/Microsoft.Extensions.WebEncoders.netstandard2.0.cs new file mode 100644 index 0000000000..ad8e11a40e --- /dev/null +++ b/src/WebEncoders/ref/Microsoft.Extensions.WebEncoders.netstandard2.0.cs @@ -0,0 +1,55 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.Extensions.DependencyInjection +{ + public static partial class EncoderServiceCollectionExtensions + { + public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddWebEncoders(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) { throw null; } + public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddWebEncoders(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Action setupAction) { throw null; } + } +} +namespace Microsoft.Extensions.WebEncoders +{ + public sealed partial class WebEncoderOptions + { + public WebEncoderOptions() { } + public System.Text.Encodings.Web.TextEncoderSettings TextEncoderSettings { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + } +} +namespace Microsoft.Extensions.WebEncoders.Testing +{ + public sealed partial class HtmlTestEncoder : System.Text.Encodings.Web.HtmlEncoder + { + public HtmlTestEncoder() { } + public override int MaxOutputCharactersPerInputCharacter { get { throw null; } } + public override void Encode(System.IO.TextWriter output, char[] value, int startIndex, int characterCount) { } + public override void Encode(System.IO.TextWriter output, string value, int startIndex, int characterCount) { } + public override string Encode(string value) { throw null; } + public unsafe override int FindFirstCharacterToEncode(char* text, int textLength) { throw null; } + public unsafe override bool TryEncodeUnicodeScalar(int unicodeScalar, char* buffer, int bufferLength, out int numberOfCharactersWritten) { throw null; } + public override bool WillEncode(int unicodeScalar) { throw null; } + } + public partial class JavaScriptTestEncoder : System.Text.Encodings.Web.JavaScriptEncoder + { + public JavaScriptTestEncoder() { } + public override int MaxOutputCharactersPerInputCharacter { get { throw null; } } + public override void Encode(System.IO.TextWriter output, char[] value, int startIndex, int characterCount) { } + public override void Encode(System.IO.TextWriter output, string value, int startIndex, int characterCount) { } + public override string Encode(string value) { throw null; } + public unsafe override int FindFirstCharacterToEncode(char* text, int textLength) { throw null; } + public unsafe override bool TryEncodeUnicodeScalar(int unicodeScalar, char* buffer, int bufferLength, out int numberOfCharactersWritten) { throw null; } + public override bool WillEncode(int unicodeScalar) { throw null; } + } + public partial class UrlTestEncoder : System.Text.Encodings.Web.UrlEncoder + { + public UrlTestEncoder() { } + public override int MaxOutputCharactersPerInputCharacter { get { throw null; } } + public override void Encode(System.IO.TextWriter output, char[] value, int startIndex, int characterCount) { } + public override void Encode(System.IO.TextWriter output, string value, int startIndex, int characterCount) { } + public override string Encode(string value) { throw null; } + public unsafe override int FindFirstCharacterToEncode(char* text, int textLength) { throw null; } + public unsafe override bool TryEncodeUnicodeScalar(int unicodeScalar, char* buffer, int bufferLength, out int numberOfCharactersWritten) { throw null; } + public override bool WillEncode(int unicodeScalar) { throw null; } + } +} diff --git a/src/WebEncoders/src/EncoderServiceCollectionExtensions.cs b/src/WebEncoders/src/EncoderServiceCollectionExtensions.cs new file mode 100644 index 0000000000..72f5e369a1 --- /dev/null +++ b/src/WebEncoders/src/EncoderServiceCollectionExtensions.cs @@ -0,0 +1,83 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Text.Encodings.Web; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Options; +using Microsoft.Extensions.WebEncoders; + +namespace Microsoft.Extensions.DependencyInjection +{ + /// + /// Extension methods for setting up web encoding services in an . + /// + public static class EncoderServiceCollectionExtensions + { + /// + /// Adds , and + /// to the specified . + /// + /// The . + /// The so that additional calls can be chained. + public static IServiceCollection AddWebEncoders(this IServiceCollection services) + { + if (services == null) + { + throw new ArgumentNullException(nameof(services)); + } + + services.AddOptions(); + + // Register the default encoders + // We want to call the 'Default' property getters lazily since they perform static caching + services.TryAddSingleton( + CreateFactory(() => HtmlEncoder.Default, settings => HtmlEncoder.Create(settings))); + services.TryAddSingleton( + CreateFactory(() => JavaScriptEncoder.Default, settings => JavaScriptEncoder.Create(settings))); + services.TryAddSingleton( + CreateFactory(() => UrlEncoder.Default, settings => UrlEncoder.Create(settings))); + + return services; + } + + /// + /// Adds , and + /// to the specified . + /// + /// The . + /// An to configure the provided . + /// The so that additional calls can be chained. + public static IServiceCollection AddWebEncoders(this IServiceCollection services, Action setupAction) + { + if (services == null) + { + throw new ArgumentNullException(nameof(services)); + } + + if (setupAction == null) + { + throw new ArgumentNullException(nameof(setupAction)); + } + + services.AddWebEncoders(); + services.Configure(setupAction); + + return services; + } + + private static Func CreateFactory( + Func defaultFactory, + Func customSettingsFactory) + { + return serviceProvider => + { + var settings = serviceProvider + ?.GetService>() + ?.Value + ?.TextEncoderSettings; + return (settings != null) ? customSettingsFactory(settings) : defaultFactory(); + }; + } + } +} diff --git a/src/WebEncoders/src/Microsoft.Extensions.WebEncoders.csproj b/src/WebEncoders/src/Microsoft.Extensions.WebEncoders.csproj new file mode 100644 index 0000000000..9b8efbd16f --- /dev/null +++ b/src/WebEncoders/src/Microsoft.Extensions.WebEncoders.csproj @@ -0,0 +1,23 @@ + + + + Contains registration and configuration APIs to add the core framework encoders to a dependency injection container. + netstandard2.0;$(DefaultNetCoreTargetFramework) + $(DefaultNetCoreTargetFramework) + $(NoWarn);CS1591 + true + aspnetcore + true + true + + + + + + + + + + + + diff --git a/src/WebEncoders/src/Testing/HtmlTestEncoder.cs b/src/WebEncoders/src/Testing/HtmlTestEncoder.cs new file mode 100644 index 0000000000..162ce4f6c1 --- /dev/null +++ b/src/WebEncoders/src/Testing/HtmlTestEncoder.cs @@ -0,0 +1,104 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; +using System.Text.Encodings.Web; + +namespace Microsoft.Extensions.WebEncoders.Testing +{ + /// + /// Encoder used for unit testing. + /// + public sealed class HtmlTestEncoder : HtmlEncoder + { + public override int MaxOutputCharactersPerInputCharacter + { + get { return 1; } + } + + public override string Encode(string value) + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + if (value.Length == 0) + { + return string.Empty; + } + + return $"HtmlEncode[[{value}]]"; + } + + public override void Encode(TextWriter output, char[] value, int startIndex, int characterCount) + { + if (output == null) + { + throw new ArgumentNullException(nameof(output)); + } + + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + if (characterCount == 0) + { + return; + } + + output.Write("HtmlEncode[["); + output.Write(value, startIndex, characterCount); + output.Write("]]"); + } + + public override void Encode(TextWriter output, string value, int startIndex, int characterCount) + { + if (output == null) + { + throw new ArgumentNullException(nameof(output)); + } + + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + if (characterCount == 0) + { + return; + } + + output.Write("HtmlEncode[["); + output.Write(value.Substring(startIndex, characterCount)); + output.Write("]]"); + } + + public override bool WillEncode(int unicodeScalar) + { + return false; + } + + public override unsafe int FindFirstCharacterToEncode(char* text, int textLength) + { + return -1; + } + + public override unsafe bool TryEncodeUnicodeScalar( + int unicodeScalar, + char* buffer, + int bufferLength, + out int numberOfCharactersWritten) + { + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + numberOfCharactersWritten = 0; + return false; + } + } +} \ No newline at end of file diff --git a/src/WebEncoders/src/Testing/JavaScriptTestEncoder.cs b/src/WebEncoders/src/Testing/JavaScriptTestEncoder.cs new file mode 100644 index 0000000000..bef4461676 --- /dev/null +++ b/src/WebEncoders/src/Testing/JavaScriptTestEncoder.cs @@ -0,0 +1,104 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; +using System.Text.Encodings.Web; + +namespace Microsoft.Extensions.WebEncoders.Testing +{ + /// + /// Encoder used for unit testing. + /// + public class JavaScriptTestEncoder : JavaScriptEncoder + { + public override int MaxOutputCharactersPerInputCharacter + { + get { return 1; } + } + + public override string Encode(string value) + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + if (value.Length == 0) + { + return string.Empty; + } + + return $"JavaScriptEncode[[{value}]]"; + } + + public override void Encode(TextWriter output, char[] value, int startIndex, int characterCount) + { + if (output == null) + { + throw new ArgumentNullException(nameof(output)); + } + + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + if (characterCount == 0) + { + return; + } + + output.Write("JavaScriptEncode[["); + output.Write(value, startIndex, characterCount); + output.Write("]]"); + } + + public override void Encode(TextWriter output, string value, int startIndex, int characterCount) + { + if (output == null) + { + throw new ArgumentNullException(nameof(output)); + } + + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + if (characterCount == 0) + { + return; + } + + output.Write("JavaScriptEncode[["); + output.Write(value.Substring(startIndex, characterCount)); + output.Write("]]"); + } + + public override bool WillEncode(int unicodeScalar) + { + return false; + } + + public override unsafe int FindFirstCharacterToEncode(char* text, int textLength) + { + return -1; + } + + public override unsafe bool TryEncodeUnicodeScalar( + int unicodeScalar, + char* buffer, + int bufferLength, + out int numberOfCharactersWritten) + { + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + numberOfCharactersWritten = 0; + return false; + } + } +} \ No newline at end of file diff --git a/src/WebEncoders/src/Testing/UrlTestEncoder.cs b/src/WebEncoders/src/Testing/UrlTestEncoder.cs new file mode 100644 index 0000000000..295bda63e8 --- /dev/null +++ b/src/WebEncoders/src/Testing/UrlTestEncoder.cs @@ -0,0 +1,104 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; +using System.Text.Encodings.Web; + +namespace Microsoft.Extensions.WebEncoders.Testing +{ + /// + /// Encoder used for unit testing. + /// + public class UrlTestEncoder : UrlEncoder + { + public override int MaxOutputCharactersPerInputCharacter + { + get { return 1; } + } + + public override string Encode(string value) + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + if (value.Length == 0) + { + return string.Empty; + } + + return $"UrlEncode[[{value}]]"; + } + + public override void Encode(TextWriter output, char[] value, int startIndex, int characterCount) + { + if (output == null) + { + throw new ArgumentNullException(nameof(output)); + } + + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + if (characterCount == 0) + { + return; + } + + output.Write("UrlEncode[["); + output.Write(value, startIndex, characterCount); + output.Write("]]"); + } + + public override void Encode(TextWriter output, string value, int startIndex, int characterCount) + { + if (output == null) + { + throw new ArgumentNullException(nameof(output)); + } + + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + if (characterCount == 0) + { + return; + } + + output.Write("UrlEncode[["); + output.Write(value.Substring(startIndex, characterCount)); + output.Write("]]"); + } + + public override bool WillEncode(int unicodeScalar) + { + return false; + } + + public override unsafe int FindFirstCharacterToEncode(char* text, int textLength) + { + return -1; + } + + public override unsafe bool TryEncodeUnicodeScalar( + int unicodeScalar, + char* buffer, + int bufferLength, + out int numberOfCharactersWritten) + { + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + numberOfCharactersWritten = 0; + return false; + } + } +} \ No newline at end of file diff --git a/src/WebEncoders/src/WebEncoderOptions.cs b/src/WebEncoders/src/WebEncoderOptions.cs new file mode 100644 index 0000000000..2f5e770a0c --- /dev/null +++ b/src/WebEncoders/src/WebEncoderOptions.cs @@ -0,0 +1,21 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Text.Encodings.Web; + +namespace Microsoft.Extensions.WebEncoders +{ + /// + /// Specifies options common to all three encoders (HtmlEncode, JavaScriptEncode, UrlEncode). + /// + public sealed class WebEncoderOptions + { + /// + /// Specifies which code points are allowed to be represented unescaped by the encoders. + /// + /// + /// If this property is null, then the encoders will use their default allow lists. + /// + public TextEncoderSettings TextEncoderSettings { get; set; } + } +} diff --git a/src/WebEncoders/test/EncoderServiceCollectionExtensionsTests.cs b/src/WebEncoders/test/EncoderServiceCollectionExtensionsTests.cs new file mode 100644 index 0000000000..0178bba2d5 --- /dev/null +++ b/src/WebEncoders/test/EncoderServiceCollectionExtensionsTests.cs @@ -0,0 +1,90 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Text.Encodings.Web; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.WebEncoders.Testing; +using Xunit; + +namespace Microsoft.Extensions.WebEncoders +{ + public class EncoderServiceCollectionExtensionsTests + { + [Fact] + public void AddWebEncoders_WithoutOptions_RegistersDefaultEncoders() + { + // Arrange + var serviceCollection = new ServiceCollection(); + + // Act + serviceCollection.AddWebEncoders(); + + // Assert + var serviceProvider = serviceCollection.BuildServiceProvider(); + Assert.Same(HtmlEncoder.Default, serviceProvider.GetRequiredService()); // default encoder + Assert.Same(HtmlEncoder.Default, serviceProvider.GetRequiredService()); // as singleton instance + Assert.Same(JavaScriptEncoder.Default, serviceProvider.GetRequiredService()); // default encoder + Assert.Same(JavaScriptEncoder.Default, serviceProvider.GetRequiredService()); // as singleton instance + Assert.Same(UrlEncoder.Default, serviceProvider.GetRequiredService()); // default encoder + Assert.Same(UrlEncoder.Default, serviceProvider.GetRequiredService()); // as singleton instance + } + + [Fact] + public void AddWebEncoders_WithOptions_RegistersEncodersWithCustomCodeFilter() + { + // Arrange + var serviceCollection = new ServiceCollection(); + + // Act + serviceCollection.AddWebEncoders(options => + { + options.TextEncoderSettings = new TextEncoderSettings(); + options.TextEncoderSettings.AllowCharacters("ace".ToCharArray()); // only these three chars are allowed + }); + + // Assert + var serviceProvider = serviceCollection.BuildServiceProvider(); + + var htmlEncoder = serviceProvider.GetRequiredService(); + Assert.Equal("abcde", htmlEncoder.Encode("abcde")); + Assert.Same(htmlEncoder, serviceProvider.GetRequiredService()); // as singleton instance + + var javaScriptEncoder = serviceProvider.GetRequiredService(); + Assert.Equal(@"a\u0062c\u0064e", javaScriptEncoder.Encode("abcde")); + Assert.Same(javaScriptEncoder, serviceProvider.GetRequiredService()); // as singleton instance + + var urlEncoder = serviceProvider.GetRequiredService(); + Assert.Equal("a%62c%64e", urlEncoder.Encode("abcde")); + Assert.Same(urlEncoder, serviceProvider.GetRequiredService()); // as singleton instance + } + + [Fact] + public void AddWebEncoders_DoesNotOverrideExistingRegisteredEncoders() + { + // Arrange + var serviceCollection = new ServiceCollection(); + + // Act + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); + // we don't register an existing URL encoder + serviceCollection.AddWebEncoders(options => + { + options.TextEncoderSettings = new TextEncoderSettings(); + options.TextEncoderSettings.AllowCharacters("ace".ToCharArray()); // only these three chars are allowed + }); + + // Assert + var serviceProvider = serviceCollection.BuildServiceProvider(); + + var htmlEncoder = serviceProvider.GetRequiredService(); + Assert.Equal("HtmlEncode[[abcde]]", htmlEncoder.Encode("abcde")); + + var javaScriptEncoder = serviceProvider.GetRequiredService(); + Assert.Equal("JavaScriptEncode[[abcde]]", javaScriptEncoder.Encode("abcde")); + + var urlEncoder = serviceProvider.GetRequiredService(); + Assert.Equal("a%62c%64e", urlEncoder.Encode("abcde")); + } + } +} diff --git a/src/WebEncoders/test/HtmlTestEncoderTest.cs b/src/WebEncoders/test/HtmlTestEncoderTest.cs new file mode 100644 index 0000000000..baafedc4de --- /dev/null +++ b/src/WebEncoders/test/HtmlTestEncoderTest.cs @@ -0,0 +1,26 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Xunit; + +namespace Microsoft.Extensions.WebEncoders.Testing +{ + public class HtmlTestEncoderTest + { + [Theory] + [InlineData("", "")] + [InlineData("abcd", "HtmlEncode[[abcd]]")] + [InlineData("<<''\"\">>", "HtmlEncode[[<<''\"\">>]]")] + public void StringEncode_EncodesAsExpected(string input, string expectedOutput) + { + // Arrange + var encoder = new HtmlTestEncoder(); + + // Act + var output = encoder.Encode(input); + + // Assert + Assert.Equal(expectedOutput, output); + } + } +} diff --git a/src/WebEncoders/test/Microsoft.Extensions.WebEncoders.Tests.csproj b/src/WebEncoders/test/Microsoft.Extensions.WebEncoders.Tests.csproj new file mode 100755 index 0000000000..3bf6fc1c9c --- /dev/null +++ b/src/WebEncoders/test/Microsoft.Extensions.WebEncoders.Tests.csproj @@ -0,0 +1,14 @@ + + + + $(DefaultNetCoreTargetFramework);net472 + + + + + + + + + + diff --git a/src/submodules/MessagePack-CSharp b/src/submodules/MessagePack-CSharp index 8861abdde9..a4a14ce447 160000 --- a/src/submodules/MessagePack-CSharp +++ b/src/submodules/MessagePack-CSharp @@ -1 +1 @@ -Subproject commit 8861abdde93a3b97180ac3b2eafa33459ad52392 +Subproject commit a4a14ce447e4ef694af1a485fb672db35e766111 diff --git a/startvs.cmd b/startvs.cmd index 0de7bb7d56..c9613224fc 100644 --- a/startvs.cmd +++ b/startvs.cmd @@ -13,7 +13,7 @@ SET DOTNET_MULTILEVEL_LOOKUP=0 :: Put our local dotnet.exe on PATH first so Visual Studio knows which one to use SET PATH=%DOTNET_ROOT%;%PATH% -SET sln=%1 +SET sln=%~1 IF "%sln%"=="" ( echo Error^: Expected argument ^ @@ -27,4 +27,4 @@ IF NOT EXIST "%DOTNET_ROOT%\dotnet.exe" ( exit /b 1 ) -start %sln% +start "" "%sln%"